feat(discord-bridge): dashboard de control si restart, dupa modelul agentului echo
Panou web pe 127.0.0.1:18790, unit systemd separat de al puntii. Server stdlib
(fara dependinte noi), tokenii de design si tiparul de endpoint-uri preluate din
/home/moltbot/echo-core/dashboard (handlers/eco.py) de pe LXC 110.
Arata: starea unitatii (uptime, PID, memoria cgroup, restarturi), firele din
state.json cu tur in zbor si cost, costul zilei fata de plafon, confirmarile
PreToolUse in asteptare (aprobabile direct din pagina), bot.log / infra.log si
opt verificari de diagnostic.
Face: start / stop / restart pe punte, cautarea si curatarea orfanilor prin
cleanup.py, repornirea propriului serviciu.
Garantii, cu teste:
- unitatea controlata e fixa in cod; un {"unit": "ssh.service"} in cerere nu
schimba nimic, altfel panoul ar fi systemctl remote fara parola;
- stop/restart intorc 409 cu lista firelor active si cer force explicit, fiindca
KillMode=control-group taie tururile in desfasurare;
- state.json se citeste fara lock: panoul nu are voie sa blocheze botul;
- diagnosticul pica daca reapare Bash(ssh:*) in deny (regresia de azi).
Uptime-ul se calculeaza din time.monotonic(), nu din /proc/uptime: in LXC acela
e virtualizat de lxcfs si da diferenta negativa fata de monotonic-ul systemd.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
This commit is contained in:
125
proxmox/lxc171-claude-agent/discord-bridge/dashboard/README.md
Normal file
125
proxmox/lxc171-claude-agent/discord-bridge/dashboard/README.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Dashboard de control pentru puntea Discord (LXC 171)
|
||||
|
||||
Panou web pentru starea si repornirea puntii, modelat dupa dashboard-ul agentului
|
||||
**echo** de pe LXC 110 (`/home/moltbot/echo-core/dashboard`, port 8088): acelasi tip
|
||||
de server (stdlib `http.server`, zero dependinte), aceiasi tokeni de design, acelasi
|
||||
tipar de endpoint-uri ca in `handlers/eco.py`.
|
||||
|
||||
```
|
||||
http://127.0.0.1:18790
|
||||
```
|
||||
|
||||
## Ce arata si ce poate face
|
||||
|
||||
| Zona | Continut |
|
||||
|---|---|
|
||||
| **Serviciu** | stare `active/running`, uptime, PID, memoria cgroup-ului, numarul de restarturi, firele active, costul zilei fata de plafon |
|
||||
| **Butoane** | Pornește / Oprește / Repornește puntea, cauta si curata procese orfane (`cleanup.py`), reporneste dashboard-ul insusi |
|
||||
| **Fire active** | ce e in `state.json`: fir, model, `cwd`, daca procesul `claude` traieste, daca are tur in desfasurare, cost |
|
||||
| **Diagnostic** | 8 verificari (vezi mai jos), reimprospatate la 30s |
|
||||
| **Confirmari in asteptare** | cererile hook-ului `PreToolUse` — se pot aproba/refuza direct din pagina, nu doar din Discord |
|
||||
| **Jurnal** | ultimele 300 de linii din `bot.log` sau `infra.log` |
|
||||
|
||||
Starea se reimprospateaza automat la 5 secunde.
|
||||
|
||||
## Decizii de proiectare
|
||||
|
||||
**O singura unitate controlata.** `/api/service` actioneaza intotdeauna pe
|
||||
`claude-discord.service`; numele unitatii nu vine niciodata din cerere. Altfel
|
||||
panoul ar fi un `systemctl` remote fara parola pentru tot ce ruleaza sub `claude`.
|
||||
Exista un test care trimite `{"unit": "ssh.service"}` si verifica faptul ca tot
|
||||
puntea e repornita.
|
||||
|
||||
**Bind pe 127.0.0.1.** Butonul de restart opreste un agent care ruleaza cu
|
||||
`bypassPermissions` si are chei SSH catre tot clusterul. Se ajunge la el prin
|
||||
tunel SSH (mai jos), nu expus in LAN. `DASHBOARD_BIND` poate schimba asta, dar
|
||||
atunci tokenul ramane singura bariera.
|
||||
|
||||
**Restart protejat de tururi in zbor.** `stop`/`restart` intorc **409** cu lista
|
||||
firelor care au un tur in desfasurare; interfata intreaba si retrimite cu
|
||||
`force: true` doar dupa confirmare. `KillMode=control-group` din unitul puntii
|
||||
omoara tot cgroup-ul, deci un restart neatent taie raspunsuri pe jumatate scrise.
|
||||
|
||||
**Dashboard-ul e un unit separat** (`claude-discord-dashboard.service`), tocmai ca
|
||||
o repornire a puntii sa nu ia si panoul din care ai apasat butonul. Invers,
|
||||
`/api/restart-self` iese cu cod 0 si lasa `Restart=always` sa-l reporneasca.
|
||||
|
||||
**Citire fara lock.** `state.json` e citit direct, fara `flock`: panoul nu are voie
|
||||
sa blocheze botul. Un JSON prins la mijlocul unei scrieri se ignora si se reia la
|
||||
urmatorul poll (test: `test_state_corupt_nu_arunca`).
|
||||
|
||||
## Verificarile din Diagnostic
|
||||
|
||||
1. serviciul `claude-discord` (stare + numar de restarturi)
|
||||
2. `state.json` citibil, cate fire contine
|
||||
3. costul zilei fata de `COST_CAP_USD_DAY`
|
||||
4. spatiu liber pe disc (prag 10%)
|
||||
5. dimensiunea `bot.log` (prag 100 MB)
|
||||
6. **regulile `deny` din `bot-settings.json`** — pica daca reapare `Bash(ssh:*)` sau
|
||||
`Bash(scp:*)`. Sunt regulile care pe 2026-08-30 au taiat complet accesul puntii la
|
||||
infrastructura: `deny` are precedenta peste `bypassPermissions` si opreste turul
|
||||
inainte de hook (vezi `../security/README.md`).
|
||||
7. prezenta hook-ului de confirmare
|
||||
8. CLI-ul `claude` in PATH
|
||||
|
||||
## Endpoint-uri
|
||||
|
||||
Toate cer cookie-ul de sesiune, obtinut cu `POST /api/auth/login`.
|
||||
|
||||
| Metoda | Ruta | Ce face |
|
||||
|---|---|---|
|
||||
| GET | `/api/status` | serviciu + dashboard + fire + cost + numar de confirmari |
|
||||
| GET | `/api/logs?lines=N&file=bot\|infra` | ultimele N linii (plafon 2000) |
|
||||
| GET | `/api/doctor` | verificarile de mai sus |
|
||||
| GET | `/api/approvals` | cererile `pending` |
|
||||
| GET/POST | `/api/cleanup` | GET = doar cauta; POST `{"dry_run": false}` = omoara orfanii |
|
||||
| POST | `/api/service` | `{"action": "start\|stop\|restart", "force": bool}` |
|
||||
| POST | `/api/approvals/decide` | `{"request_id": "...", "decision": "allow\|deny"}` |
|
||||
| POST | `/api/restart-self` | reporneste dashboard-ul |
|
||||
| POST | `/api/auth/login` / `/api/auth/logout` | `{"token": "..."}` / sterge cookie-ul |
|
||||
|
||||
Autentificarea e un token din `~/.claude-discord/env` schimbat pe un cookie
|
||||
`HttpOnly; SameSite=Strict` valabil 30 de zile, comparat cu `secrets.compare_digest`.
|
||||
Fara `DASHBOARD_TOKEN` in env, procesul isi genereaza unul aleator si il scrie in
|
||||
`logs/dashboard.log` — **nu** ramane deschis.
|
||||
|
||||
## Instalare
|
||||
|
||||
`ops/install.sh` face totul (leaga unitul, genereaza `DASHBOARD_TOKEN` daca lipseste,
|
||||
porneste serviciul). Manual:
|
||||
|
||||
```bash
|
||||
ln -sfn /workspace/romfastsql/proxmox/lxc171-claude-agent/discord-bridge/dashboard/claude-discord-dashboard.service \
|
||||
~/.config/systemd/user/claude-discord-dashboard.service
|
||||
printf 'DASHBOARD_TOKEN=%s\n' "$(python3 -c 'import secrets;print(secrets.token_urlsafe(24))')" >> ~/.claude-discord/env
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now claude-discord-dashboard
|
||||
```
|
||||
|
||||
Setari optionale in `~/.claude-discord/env`: `DASHBOARD_BIND` (implicit `127.0.0.1`),
|
||||
`DASHBOARD_PORT` (implicit `18790`).
|
||||
|
||||
## Acces
|
||||
|
||||
Fiind legat de localhost, se ajunge la el prin tunel SSH — la fel ca la dashboard-ul
|
||||
lui echo:
|
||||
|
||||
```bash
|
||||
# de pe statia de lucru (direct sau prin Tailscale: 100.95.55.51)
|
||||
ssh -L 18790:127.0.0.1:18790 -N claude@10.0.20.171 &
|
||||
# apoi http://localhost:18790
|
||||
```
|
||||
|
||||
Tokenul se citeste cu `grep DASHBOARD_TOKEN ~/.claude-discord/env`.
|
||||
|
||||
## Teste
|
||||
|
||||
```bash
|
||||
cd proxmox/lxc171-claude-agent/discord-bridge
|
||||
python3 -m pytest tests/test_dashboard.py -q
|
||||
```
|
||||
|
||||
25 de teste, fara retea si fara `systemctl` real (dublura inregistreaza apelurile).
|
||||
Acopera autentificarea, faptul ca unitatea nu poate fi aleasa din cerere, blocajul pe
|
||||
tur in zbor si trecerea cu `force`, traversarea de cale in `request_id`, `state.json`
|
||||
corupt si verificarea de regresie pentru `deny(ssh)`.
|
||||
Reference in New Issue
Block a user