P5G Marvis — Full Setup Guide
This document describes the complete architecture and deployment procedure for the P5G Marvis sidebar extension that injects custom pages into the Athonet NCM UI.
Deployment Model
The target environment for this project is a host where services are started by
systemd, including Docker-backed services. Marvis is intended to run the same
way:
- the FastAPI app runs in a container with host networking on
127.0.0.1:8100 - Traefik exposes it at
/core/marvis/* patch-ncm.pyinjects the sidebar entries and iframe routes into the NCM UI- the injected entries should only be added for services that are actually reachable on the host when the patch is applied
For a containerised deployment without Compose, this repo includes an example
unit file at config/p5g-marvis.service.
Build the image
docker build -t p5g-marvis:latest .
Install the systemd unit
cp config/p5g-marvis.service /usr/lib/systemd/system/p5g-marvis.service
mkdir -p /etc/p5g-marvis
cp config/marvis.env.example /etc/p5g-marvis/marvis.env
systemctl daemon-reload
systemctl enable --now p5g-marvis
Marvis will then be reachable on:
http://127.0.0.1:8100
http://127.0.0.1:8100/api/docs
Edit /etc/p5g-marvis/marvis.env per host instead of hardcoding addresses in the
unit file. In particular:
- set
MARVIS_PROMETHEUS_URLandMARVIS_ALERTMANAGER_URLto the correct host endpoints for that appliance - keep the defaults if Prometheus and Alertmanager are already available on host loopback from the appliance network namespace
Architecture Overview
Browser → Traefik (HTTPS :443)
├── /core/marvis/* → strip prefix → http://127.0.0.1:8100 (p5g-marvis FastAPI)
└── /core/radio/* → strip prefix → http://127.0.0.1:4000 (rm-ui, if present)
NCM UI (React SPA)
└── index-Cw8Irsq8.js (patched by patch-ncm.py)
├── Sidebar: P5G Marvis (Insights, Actions, Minis, AI) + P5G Radio
├── Router: /marvis/* and /radio/* → <iframe> loading the FastAPI pages
└── Perms: BW registry entries for /marvis and /radio (Tt = always allowed)
p5g-marvis FastAPI (port 8100)
├── GET /overview → app/ui/overview.html
├── GET /minis → app/ui/tasks.html
├── GET /actions → app/ui/actions.html
├── GET / → app/ui/index.html (catch-all SPA)
├── GET /api/network/nf-status → Prometheus metrics
├── GET /api/alerts → Alertmanager
└── GET /api/actions → log_analyzer service
Local Files
All source files live at: ~/p5g-marvis/
~/p5g-marvis/
├── patch-ncm.py # Injects sidebar/routes/perms into the NCM JS bundle
├── requirements.txt # fastapi==0.115.0, uvicorn[standard]==0.30.6, httpx==0.27.2
├── app/
│ ├── main.py # FastAPI app — route definitions and UI file serving
│ ├── config.py # Reads env vars (prometheus URL, alertmanager URL, AI config)
│ ├── ui/
│ │ ├── overview.html # P5G Marvis Insights page
│ │ ├── tasks.html # P5G Marvis Minis page (action tiles)
│ │ ├── actions.html # P5G Marvis Actions page
│ │ └── index.html # SPA catch-all
│ ├── routers/
│ │ ├── actions.py # /api/actions — log analysis
│ │ ├── alerts.py # /api/alerts — Alertmanager
│ │ ├── network.py # /api/network/nf-status — Prometheus
│ │ └── query.py # /api/query — PromQL passthrough
│ └── services/
│ ├── prometheus.py
│ ├── alertmanager.py
│ ├── log_analyzer.py
│ └── ai.py
Deployed Hosts
| Host | IP | Status | P5G Radio |
|---|---|---|---|
| Primary | 172.27.0.159 | ✅ Deployed | ✅ (rm-ui on :4000) |
| Secondary | 192.168.86.150 | ✅ Deployed | ⚠️ No rm-ui container |
Deploy / Re-deploy to a Host
Prerequisites
- SSH key:
~/.ssh/5G-SSH-Key.pem - Target host must have Python 3.x and the Athonet NCM stack running
Step 1 — Copy application files
TARGET=<IP> # e.g. 172.27.0.159 or 192.168.86.150
ssh -i ~/.ssh/5G-SSH-Key.pem root@$TARGET 'mkdir -p /opt/p5g-marvis/app/ui /opt/p5g-marvis/app/routers /opt/p5g-marvis/app/services /etc/athonet/traefik/ssl'
scp -i ~/.ssh/5G-SSH-Key.pem \
~/p5g-marvis/requirements.txt ~/p5g-marvis/patch-ncm.py \
root@$TARGET:/opt/p5g-marvis/
scp -i ~/.ssh/5G-SSH-Key.pem \
~/p5g-marvis/app/__init__.py ~/p5g-marvis/app/main.py ~/p5g-marvis/app/config.py \
root@$TARGET:/opt/p5g-marvis/app/
scp -i ~/.ssh/5G-SSH-Key.pem ~/p5g-marvis/app/ui/*.html \
root@$TARGET:/opt/p5g-marvis/app/ui/
scp -i ~/.ssh/5G-SSH-Key.pem ~/p5g-marvis/app/routers/*.py \
root@$TARGET:/opt/p5g-marvis/app/routers/
scp -i ~/.ssh/5G-SSH-Key.pem ~/p5g-marvis/app/services/*.py \
root@$TARGET:/opt/p5g-marvis/app/services/
Step 2 — Install Python dependencies
ssh -i ~/.ssh/5G-SSH-Key.pem root@$TARGET '
python3 -m ensurepip --upgrade 2>/dev/null
python3 -m pip install -r /opt/p5g-marvis/requirements.txt --break-system-packages -q
'
Step 3 — Create systemd service
ssh -i ~/.ssh/5G-SSH-Key.pem root@$TARGET 'cat > /etc/systemd/system/p5g-marvis.service << EOF
[Unit]
Description=P5G Marvis AI Network Assistant
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/p5g-marvis
Environment=MARVIS_PROMETHEUS_URL=http://127.0.0.1:9090
Environment=MARVIS_PROMETHEUS_PREFIX=/prometheus
Environment=MARVIS_ALERTMANAGER_URL=http://127.0.0.1:9093
Environment=MARVIS_AI_MODE=openai
Environment=MARVIS_OPENAI_BASE_URL=https://172.27.0.135:8001
Environment=MARVIS_OPENAI_MODEL=gemma-4-26B-A4B-it-UD-Q4_K_S.gguf
ExecStart=/usr/bin/python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8100
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable p5g-marvis
systemctl start p5g-marvis'
Step 4 — Create Traefik routing config
Create /etc/athonet/traefik/ssl/marvis.yml:
ssh -i ~/.ssh/5G-SSH-Key.pem root@$TARGET 'cat > /etc/athonet/traefik/ssl/marvis.yml << EOF
http:
routers:
router-marvis-0:
rule: "PathPrefix(\`/core/marvis\`)"
service: service-marvis
entryPoints:
- websecure
tls: {}
priority: 25
middlewares:
- cors@http
- strip-path-marvis-0
# Add router-radio-0 here if rm-ui is present (port 4000)
middlewares:
strip-path-marvis-0:
stripPrefix:
prefixes:
- "/core/marvis"
services:
service-marvis:
loadBalancer:
servers:
- url: "http://127.0.0.1:8100"
passHostHeader: false
EOF'
Step 5 — Add file provider to Traefik static config
Append to /etc/athonet/traefik/traefik.yml under the providers: section:
file:
filename: "/etc/traefik/ssl/marvis.yml"
watch: true
Note
: The path inside the container is
/etc/traefik/ssl/marvis.ymlbecause thessl/directory is bind-mounted. The host path is/etc/athonet/traefik/ssl/marvis.yml.
ssh -i ~/.ssh/5G-SSH-Key.pem root@$TARGET '
grep -q "file:" /etc/athonet/traefik/traefik.yml || (
echo " file:" >> /etc/athonet/traefik/traefik.yml
echo " filename: \"/etc/traefik/ssl/marvis.yml\"" >> /etc/athonet/traefik/traefik.yml
echo " watch: true" >> /etc/athonet/traefik/traefik.yml
)
docker restart traefik'
Step 6 — Patch the NCM JS bundle
ssh -i ~/.ssh/5G-SSH-Key.pem root@$TARGET '
JS="/etc/athonet/ems-frontend/advanced/assets/index-Cw8Irsq8.js"
# Only create .bak if it does not already exist (preserve the clean original)
[ -f "$JS.bak" ] || cp "$JS" "$JS.bak"
cd /opt/p5g-marvis && python3 patch-ncm.py'
patch-ncm.py injects only the services it detects as reachable on the host:
P5G_MARVIS_ENABLED=true|falseoverrides Marvis detectionP5G_RADIO_ENABLED=true|falseoverrides Radio detection- by default it probes
127.0.0.1:8100for Marvis and127.0.0.1:4000for Radio
Expected output:
Applied: sidebar entry
Applied: injected iframe routes
Applied: permissions entry
Done — P5G Marvis: 10 occurrences, P5G Radio: 3 occurrences
If you see Skipped: instead of Applied:, the patch was already applied from a previous run — this is safe.
Step 7 — Verify
ssh -i ~/.ssh/5G-SSH-Key.pem root@$TARGET '
systemctl status p5g-marvis --no-pager | head -5
curl -sk http://127.0.0.1:8100/health'
Expected: {"status":"ok"}
Update a UI Page
To update any UI page (e.g. after editing tasks.html locally):
/usr/bin/scp -i ~/.ssh/5G-SSH-Key.pem \
~/p5g-marvis/app/ui/tasks.html \
root@<IP>:/opt/p5g-marvis/app/ui/tasks.html
No service restart needed — FastAPI reads the file on each request.
Re-run the JS Patch (e.g. after an NCM upgrade)
If NCM is upgraded and the JS bundle is replaced:
ssh -i ~/.ssh/5G-SSH-Key.pem root@<IP> '
JS="/etc/athonet/ems-frontend/advanced/assets/index-Cw8Irsq8.js"
# The new build will have a different filename — update patch-ncm.py JS= and BAK= lines
# Then create a fresh .bak and re-run
cp "$JS" "$JS.bak"
cd /opt/p5g-marvis && python3 patch-ncm.py'
Warning
: Check if the bundle filename changed after the upgrade. The filename is
index-<hash>.js. Update theJSvariable at the top ofpatch-ncm.pyif it has changed.
What the Patch Does (patch-ncm.py)
The script modifies the minified NCM React JS bundle in 3 places — it always reads from the .bak (clean original) ensuring it is safe to re-run:
-
Sidebar nav entry — appends P5G Marvis (with 4 sub-items) and P5G Radio items to the existing top-level navigation after the UPF entry.
-
React Router routes — inserts
/marvisand/radioroute objects between the existing UPF and Platform routes. Each renders an iframe pointing to/core/marvis/<page>or/core/radio/. -
Permissions registry — registers all
/marvis/*and/radiopaths in the BW permissions map withTt(always-allowed), preventing "Permissions for route not managed" errors.
P5G Marvis Sidebar Structure
P5G Marvis
├── P5G Marvis Insights → /marvis/overview → iframes /core/marvis/overview
├── P5G Marvis Actions → /marvis/actions → iframes /core/marvis/actions
├── P5G Marvis Minis → /marvis/minis → iframes /core/marvis/minis (tasks.html)
└── P5G Marvis AI → /marvis/ai → iframes /core/marvis/ (index.html)
P5G Radio → /radio → iframes /core/radio/ (rm-ui :4000)
Minis Page — Action Tiles (tasks.html)
Diagnostics & Health
| Tile | Description |
|---|---|
| Ping All NFs | ICMP probes to all NFs via Prometheus |
| Refresh Alerts | Pull latest alerts from Alertmanager |
| Full NF Status Report | Query all 12 NF health metrics |
| Trace UE Data Path | Trace AMF→SMF→UPF path for a sample SUPI |
Network Operations
| Tile | Description |
|---|---|
| Perform Emulated Data Session | Full attach + data session end-to-end test (non-disruptive) |
| Check Connected Devices | Query AMF/UPF state and report registration status |
| Generate Capacity Report | Device counts, bandwidth utilisation, peak hour trends |
| Clear All UE Sessions | Force-release all active sessions (requires confirmation) |
Maintenance
| Tile | Description |
|---|---|
| Backup Configuration | Export configs for all NFs to timestamped archive |
| Reload Configuration | Reload from disk without restarting services |
| Purge Old Logs | Delete log files older than 7 days |
| Export Debug Bundle | Collect and compress NF logs, configs, metrics |
Troubleshooting
| Problem | Check |
|---|---|
| Sidebar items not visible | Browser hard-refresh (Cmd+Shift+R). Confirm patch ran successfully. |
| Clicking sidebar shows blank page | Traefik routing — check docker logs traefik. marvis.yml must be present and file provider added to traefik.yml. |
| Service won't start | journalctl -u p5g-marvis -n 50. Usually a missing Python package. |
| Metrics not loading | Confirm Prometheus is on 127.0.0.1:9090 — check MARVIS_PROMETHEUS_URL env var in the service file. |
| patch-ncm.py: ERROR anchor not found | The NCM JS bundle was upgraded and the filename/content changed. Find new anchors in the .bak file and update patch-ncm.py. |
| After NCM upgrade patch not applied | Re-run Step 6 — but first check if the bundle filename changed. |