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.py injects 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_URL and MARVIS_ALERTMANAGER_URL to 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.yml because the ssl/ 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|false overrides Marvis detection
  • P5G_RADIO_ENABLED=true|false overrides Radio detection
  • by default it probes 127.0.0.1:8100 for Marvis and 127.0.0.1:4000 for 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 the JS variable at the top of patch-ncm.py if 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:

  1. Sidebar nav entry — appends P5G Marvis (with 4 sub-items) and P5G Radio items to the existing top-level navigation after the UPF entry.

  2. React Router routes — inserts /marvis and /radio route objects between the existing UPF and Platform routes. Each renders an iframe pointing to /core/marvis/<page> or /core/radio/.

  3. Permissions registry — registers all /marvis/* and /radio paths in the BW permissions map with Tt (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.
Description
No description provided
Readme 2.9 MiB
Languages
HTML 54%
Python 40.4%
Shell 5%
Dockerfile 0.6%