# Cockpit NVIDIA GPU Dashboard

> Custom Cockpit package that displays live GPU stats (model, driver, CUDA
> version, temperature, utilization, memory, processes) in the web UI sidebar
> on port 9090.

## When to use

- User wants GPU temperature / utilization / memory visible in Cockpit
- User prefers a web UI over terminal `nvidia-smi` or `nvtop`

## Two approaches

`cockpit.script()` (run nvidia-smi directly) **silently fails on some Cockpit
versions** (Ubuntu 26+, Cockpit ~314+). The **systemd + file-read** approach
is more reliable and preferred.

---

## Approach A (recommended): systemd service + file-read

Write GPU data to files periodically via a background service, then read them
from the Cockpit page with `cockpit.file().read()`. Avoids all PATH and
permission issues with running nvidia-smi from the Cockpit bridge.

### 1. Create the data-collection script

**`/usr/local/bin/nvidia-gpu-monitor.sh`:**

```bash
#!/bin/bash
while true; do
  nvidia-smi --query-gpu=name,driver_version,temperature.gpu,utilization.gpu,memory.used,memory.total,power.draw,pstate,fan.speed --format=csv,noheader,nounits 2>/dev/null | head -1 > /run/nvidia-gpu.txt
  nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv,noheader,nounits 2>/dev/null > /run/nvidia-processes.txt
  nvidia-smi 2>/dev/null | grep "CUDA Version" | sed 's/.*CUDA Version: //' | head -1 > /run/nvidia-cuda.txt
  sleep 5
done
```

Make executable: `sudo chmod +x /usr/local/bin/nvidia-gpu-monitor.sh`

### 2. Create systemd service

**`/etc/systemd/system/nvidia-gpu-monitor.service`:**

```ini
[Unit]
Description=NVIDIA GPU Monitor - writes stats to /run
After=nvidia-persistenced.service

[Service]
Type=simple
ExecStart=/usr/local/bin/nvidia-gpu-monitor.sh
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
```

Enable and start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now nvidia-gpu-monitor
```

### 3. Create the Cockpit package

**`/usr/share/cockpit/nvidia-gpu/manifest.json`:**

```json
{
    "version": 1,
    "requires": { "cockpit": "260" },
    "menu": {
        "index": { "label": "NVIDIA GPU", "order": 90 }
    },
    "content-security-policy": "default-src 'self'; script-src 'unsafe-inline'"
}
```

**`/usr/share/cockpit/nvidia-gpu/index.html`:**

The page uses `cockpit.file(path).read()` to read `/run/nvidia-gpu.txt`,
`/run/nvidia-processes.txt`, and `/run/nvidia-cuda.txt`. Each file contains
a single line of comma-separated values written by the systemd service.

Key implementation:
```javascript
function readFile(path) { return cockpit.file(path).read(); }

function update() {
  readFile('/run/nvidia-gpu.txt').done(function(data) {
    if (!data || !data.trim()) { /* show error */ return; }
    var parts = data.trim().split(', ');
    // parts[0] = name, [1] = driver, [2] = temp, [3] = util,
    // [4] = mem_used, [5] = mem_total, [6] = power, [7] = pstate, [8] = fan
    // ... render cards ...
  });

  readFile('/run/nvidia-cuda.txt').done(function(d) { /* show CUDA version */ });
  readFile('/run/nvidia-processes.txt').done(function(d) { /* render process table */ });
}
setInterval(update, 5000);
```

### 4. Install and restart Cockpit

```bash
sudo mkdir -p /usr/share/cockpit/nvidia-gpu
# Write manifest.json and index.html (use sudo tee or sudo cp)
sudo chmod 644 /usr/share/cockpit/nvidia-gpu/*
sudo systemctl restart cockpit
```

### 5. Verify

1. Open `https://<server>:9090` in browser
2. Log in with server credentials
3. Click **NVIDIA GPU** in sidebar — live metrics refresh every 5s

---

## Approach B (fallback): cockpit.script() directly

Use when Approach A isn't practical (no systemd, containerized Cockpit, etc.):

```javascript
cockpit.script("/usr/bin/nvidia-smi --query-gpu=... --format=csv,noheader,nounits", null, {superuser: 'try'})
  .done(function(data) { /* parse and render */ })
  .fail(function(err) { /* show error */ });
```

**Caveats (why Approach A is preferred):**
- `cockpit.script()` may silently hang on Cockpit v314+ (Ubuntu 26+)
- PATH may not include `/usr/bin/` in non-interactive bridge sessions
- The `.fail()` handler may catch but not always fire for certain errors
- Debugging is harder — the error object from Cockpit bridge is opaque

If using this approach, use the full path `/usr/bin/nvidia-smi` and avoid
`{superuser: 'try'}` unless nvidia-smi requires root on the target system.

---

## Troubleshooting

| Symptom | Likely Cause | Fix |
|---------|-------------|-----|
| "No NVIDIA GPU detected" | Can't read data files or run nvidia-smi | Check `/run/nvidia-gpu.txt` exists and is populated. Restart service: `sudo systemctl restart nvidia-gpu-monitor` |
| Page shows stale data | systemd service stopped or crashed | `sudo systemctl status nvidia-gpu-monitor` — restart if dead |
| Power shows `[N/A]` | GTX 1050 Ti doesn't report power draw | Normal — handle gracefully in display |
| Page loads blank | CSP blocking inline script | Add `'unsafe-inline'` to manifest CSP |
| "No GPU processes" when Immich is processing | Jobs may be between batches | Check `nvidia-smi` directly — should show python process |
| Ctrl+Shift+R needed after updates | Cockpit aggressively caches package content | Hard-refresh or open in private/incognito tab |

## Related

- Install `nvtop` for terminal TUI monitor: `apt install nvtop` (or build from https://github.com/Syllo/nvtop)
- GPU acceleration setup: see the parent `docker-gpu-acceleration` skill
