# 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 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

```bash
docker build -t p5g-marvis:latest .
```

### Install the systemd unit

```bash
cp config/p5g-marvis.service /usr/lib/systemd/system/p5g-marvis.service
systemctl daemon-reload
systemctl enable --now p5g-marvis
```

Marvis will then be reachable on:

```text
http://127.0.0.1:8100
http://127.0.0.1:8100/api/docs
```

---

## 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

```bash
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

```bash
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

```bash
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`:

```bash
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:

```yaml
  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`.

```bash
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

```bash
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

```bash
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):

```bash
/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:

```bash
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. |
