---
name: vps-report
description: Health-check ricorrente, sola lettura, della VPS Digital On. Raccoglie 14 sezioni (sistema, risorse, kernel, docker, processi, cron, utenti/sudo, rete, TLS/reachability, log, igiene git vault, footprint Claude Code, backup, documentazione) in un unico report Markdown con delta rispetto al run precedente, e un frontmatter `findings[]` machine-readable (contratto v1.0, vedi vps-report-spec.md §17) consumato dalla skill gemella vps-action-plan. Trigger: "/vps-report", "health check vps", "stato della vps", "report vps".
allowed-tools: Read, Bash, Write
---

# Skill: vps-report

## Obiettivo

Produrre un report diagnostico di sola lettura sullo stato della VPS, in un unico file
Markdown in `infra/vps/reports/`, confrontato con una baseline "atteso" (`expected.conf`)
e con il report precedente (delta). Nessuna proposta di fix, nessun piano d'azione: solo
ricognizione onesta, comprese le sezioni fallite (manifesto errori).

## Prerequisiti

- Vault in `/opt/vault-digitalon/`
- Script installato `/usr/local/bin/digitalon-vps-report` (root:root, 755) + voce sudoers
  NOPASSWD scoped (vedi "Bootstrap" in fondo) — finché non installato, eseguire con
  `sudo bash claude/skills/vps-report/collect.sh` chiedendo la password all'utente
  (nessun collector NOPASSWD ancora disponibile)
- `expected.conf` root-owned fuori dal vault (bootstrap) — finché non installato, si usa
  la copia vault as-is, segnalandolo nel report
- `cron-read-all` + `croniter` (dipendenze di `cron-stato`, vedi quella skill)

## Processo

### Step 1 — Guardrail "VPS giusta"

`collect.sh` verifica in autonomia che `/opt/vault-digitalon` esista (stop altrimenti) e
segnala warning se altri segnali attesi (`/docker`, utente `niccolo`) non tornano, senza
fermarsi. Se il guardrail STOP scatta, riporta subito il problema e non proseguire.

### Step 2 — Esegui il collector

Se lo script installato esiste (`/usr/local/bin/digitalon-vps-report`):
```bash
sudo /usr/local/bin/digitalon-vps-report
```
Altrimenti (pre-bootstrap), esegui la copia-sorgente nel vault:
```bash
sudo bash /opt/vault-digitalon/claude/skills/vps-report/collect.sh
```
Lo script stampa il path del file temporaneo con l'output grezzo redatto, sezioni 1-5 e
7-14 (§6 Cron è vuota di proposito, la popoli tu allo Step 3). Leggi quel file con Read.

### Step 3 — Sezione Cron (§6), fuori da collect.sh

`cron-stato` è una skill Claude (Read/Bash/Write), non uno script bash: non può essere
richiamata da dentro `collect.sh`. Eseguila tu stesso, in modalità **report-only**:

- segui `claude/skills/cron-stato.md` Step 1-9 esattamente
- **salta lo Step 10** (rigenerazione `REGISTRO.md`) e la domanda finale sì/no — questa
  skill non scrive né chiede conferme
- prendi la tabella prodotta allo Step 9 punto 3 (tabella completa) + la sezione anomalie
  punto 2, e incassale nella §6 del template

### Step 4 — Self-check segreti

Prima di usare l'output grezzo, fai un grep di controllo sul file temporaneo contro gli
stessi pattern di `redact()` in `collect.sh` (PASSWORD=, TOKEN=, SECRET=, `sk-`, `xox`,
webhook Discord/Slack, `BEGIN PRIVATE KEY`, `://user:pass@`). Se trovi un match non già
mascherato, **fermati** e segnalalo: non scrivere il report finché non è risolto.

### Step 5 — Confronto con l'atteso (`expected.conf`)

Leggi `expected.conf` (vault, o la copia root-owned se bootstrap fatto — verifica sha256,
vedi "Bootstrap"). Confronta con i dati grezzi:

- **utenti**: nuovo → profilo ricco (nome, quando creato best-effort, da chi best-effort,
  peso home, ultime attività, segnali di ruolo); sparito → nota informativa
- **container**: di troppo → profilo entità sconosciuta; atteso mancante/non running →
  stato reale; atteso ma cambiato → diff porte/immagine/mount/rete
- **porte pubbliche**: fuori da `[ports_public]` → profila (PID, comando, utente, etimes,
  reachability, descrizione NL best-effort dichiarata come tale)
- **host/TLS**: sotto soglia `[tls_thresholds]` → 🟠/🔴 (vedi rubrica severità sotto)
- **sudo**: regola nuova/modificata → profilata; regole in `[sudo_rules_known_undesired]`
  restano senza delta ma vanno riportate come finding fisso ad ogni run

Tutto questo confronto è "delta da verificare", mai un'azione.

### Step 6 — Delta vs report precedente

Cerca in `infra/vps/reports/` il file con data più recente **prima di oggi**. Se non
esiste, è il primo run: dichiaralo, nessun delta. Altrimenti leggi il suo frontmatter
`snapshot:` e confronta con lo snapshot di questo run:
- comparse/sparizioni: utenti, container, porte esposte, regole sudo, host irraggiungibili
- trend numerici: disco, `.git` vault, `.claude` totale, swap → in crescita/stabile/calo

L'inventario dettagliato non entra nel diff, solo questi campi.

### Step 7 — Componi il report

Usa il template canonico (sezioni 1-14 fisse, numerate anche se vuote) — vedi "Struttura
report" sotto. Per ogni fatto rilevante (deviazione da `expected.conf` o fatto strutturale
qualificato, es. node_modules committati, `.env` in history, permessi larghi, reboot
pendente, porta fuori baseline) **emetti un finding** nel frontmatter secondo lo schema
`vps-report-spec.md` §17: `id` (area dedotta da `tipo` via tabella §17.3, mai a mano),
`sezione`, `tipo` (solo valori dell'enum), `risorsa`, `osservato`, `atteso`,
`deviazione_da_atteso`, `descrizione` **strettamente neutra** (un fatto singolo, mai una
correlazione con altri finding né un giudizio di gravità), `dati` (ogni numero usabile per
soglie/ordinamento — MAI solo dentro la prosa). **Non assegnare severità qui**: non
etichette 🔴🟠🟡, non "indesiderata"/"grave"/"critico" nella prosa dei findings — quel
giudizio vive solo in vps-action-plan, a valle. Compila anche `conteggi`, `sezioni_stato`
(1–14) e `template_version: "1.0"`. Scrivi con Write in
`infra/vps/reports/YYYY-MM-DD-vps-report.md` (crea la cartella se non esiste — è l'unica
scrittura nel vault che questa skill fa senza chiedere conferma).

Se una sezione è fallita (comando assente, permesso negato, timeout), NON fermarti:
annotala nel Manifesto errori (§ coda) e imposta `esito: con-errori`, `errori: N` nel
frontmatter.

### Step 8 — Riepilogo a terminale

Stampa in chat, sempre:
```
Esito: ok|con-errori (N errori)
Δ dal report precedente: <schematico, o "primo run">
Manifesto errori: <se presente, righe>
File: infra/vps/reports/YYYY-MM-DD-vps-report.md
```

## Giudizio di severità — non qui

Questa skill è **descrittiva, senza giudizio** (vps-report-spec.md §4, cambio v1.0): non
assegna più 🔴🟠🟡, non usa aggettivi di gravità nella prosa dei findings. Emette solo
`findings[]` neutri (schema §17 della spec). La severità/priorità/azione le calcola la
skill gemella **vps-action-plan** a partire da questi fatti. Unica regola deterministica
mantenuta qui (perché è un fatto, non un giudizio): porta ∉ {22,80,443} in ascolto su
`0.0.0.0` → **sempre** un finding `exposed-port`, senza eccezioni.

## Struttura report

Frontmatter YAML (vedi esempio in `vps-report-spec.md` §14.1: `tipo`, `template_version`,
`skill_versione`, `generato`, `host`, `durata_sec`, `esito`, `errori`, `script_sha256`,
`conteggi`, `sezioni_stato`, `snapshot:`, `findings: []` — schema completo dei findings in
§17).

Corpo, sezioni 1-14 fisse + coda:
```
# VPS Report — YYYY-MM-DD
## Indice
## 0. Sintesi (esito + conteggio findings per severità)
## 1. Baseline sistema
## 2. Risorse (disco/RAM/swap/inode)
## 3. Kernel/update/reboot
## 4. Docker (container, immagini, volumi, cache, health/restart)
## 5. Processi/lifecycle
## 6. Cron (via cron-stato)
## 7. Utenti/permessi/segreti (metadati)/sudo
## 8. Rete/porte/firewall
## 9. Host pubblici & TLS/reachability
## 10. Log
## 11. Git vault (igiene)
## 12. Footprint Claude Code
## 13. Backup & freschezza git
## 14. Documentazione
## Δ dal report precedente
## Manifesto errori
```

## Output

Un file `infra/vps/reports/YYYY-MM-DD-vps-report.md`, più riepilogo in chat (Step 8).

## Fuori scope

- Nessun fix, nessuna modifica al sistema (unica scrittura: il file report)
- Non gestisce l'adozione di cron fantasma → `cron-modifica`
- Non sostituisce `cron-stato` come strumento — la richiama, non la duplica
- Bootstrap (installazione script/sudoers/`expected.conf` root-owned) è un'azione
  separata, una-tantum, mai eseguita in autonomia: vedi sotto

## Bootstrap (una-tantum, richiede conferma esplicita dell'utente)

Da NON eseguire silenziosamente. Mostrare questi comandi e spiegarli, eseguirli solo dopo
conferma esplicita:

```bash
# 1. installa il collector fuori dal vault, root-owned, non scrivibile dal gruppo digitalon
sudo install -o root -g root -m 755 \
  /opt/vault-digitalon/claude/skills/vps-report/collect.sh \
  /usr/local/bin/digitalon-vps-report

# 2. installa expected.conf root-owned fuori dal vault
sudo install -o root -g root -m 644 \
  /opt/vault-digitalon/claude/skills/vps-report/expected.conf \
  /etc/digitalon/expected.conf

# 3. sudoers scoped, path assoluto, nessun argomento libero
echo 'niccolo ALL=(root) NOPASSWD: /usr/local/bin/digitalon-vps-report' | \
  sudo tee /etc/sudoers.d/digitalon-vps-report
sudo visudo -c
```

Dopo il bootstrap, ad ogni esecuzione la skill verifica:
```bash
sha256sum /usr/local/bin/digitalon-vps-report /opt/vault-digitalon/claude/skills/vps-report/collect.sh
sha256sum /etc/digitalon/expected.conf /opt/vault-digitalon/claude/skills/vps-report/expected.conf
```
Se gli hash non coincidono, avvisa in testa al report: la copia installata è disallineata
dalla copia versionata nel vault (serve ri-bootstrap).
