Files
g9-migration/g9-migration/scripts/10-registry.sh
T

141 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# 10-registry.sh — T-A.1 + T-A.5 : moitié technique du registre, GÉNÉRÉE, + détection de dérive.
#
# Lecture seule : `docker ps` / `docker inspect` distants, lecture des compose du kit.
# Aucun conteneur n'est démarré, arrêté, modifié. Aucun secret n'est lu ni affiché.
#
# Modes :
# ./10-registry.sh génère REGISTRE-BRIQUES.md
# ./10-registry.sh --check régénère en mémoire, compare, alerte si dérive (rc=1)
# ./10-registry.sh --check --alert idem + envoie le payload JSON dans le salon ops
#
# Sortie : REGISTRE-BRIQUES.md (moitié A, générée — ne pas éditer à la main)
# La moitié humaine (comptes, reset, responsables) est dans REGISTRE-IDENTITES.md.
set -euo pipefail
KIT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="$KIT/REGISTRE-BRIQUES.md"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519_openfox11}"
HOSTS="${HOSTS:-g9:folzo@192.168.1.24 j5040:folzo@192.168.1.27}"
RESTIC_COVERAGE="${RESTIC_COVERAGE:-g9=restic (sources /data /home/folzo /etc, cron 03:30) ; j5040=non (à couvrir Phase 5)}"
MODE="generate"
ALERT=0
for a in "$@"; do
[[ "$a" == "--check" ]] && MODE="check"
[[ "$a" == "--alert" ]] && ALERT=1
done
export SSH_KEY
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
declared_json="$(python3 "$KIT/scripts/registry_inputs.py" declared)"
caddy_json="$(python3 "$KIT/scripts/registry_inputs.py" caddy)"
echo "$caddy_json" > "$TMP/caddy.json"
rows_all="$TMP/rows.jsonl"
findings_all="$TMP/findings.txt"
: > "$rows_all"
: > "$findings_all"
for entry in $HOSTS; do
machine="${entry%%:*}"
host="${entry#*:}"
mip="${host#*@}"
ssh -o ConnectTimeout=8 -o BatchMode=yes -i "$SSH_KEY" "$host" \
'docker ps -a --format json' > "$TMP/$machine.ps.json" 2>"$TMP/$machine.err" || {
echo "[registry] SSH/docker indisponible sur $machine ($host)" >&2
echo "$machine: inaccessible (SSH ou docker)" >> "$findings_all"
continue
}
ssh -o ConnectTimeout=8 -o BatchMode=yes -i "$SSH_KEY" "$host" \
'docker ps --format "{{.Names}}\t{{.Ports}}"' > "$TMP/$machine.ports.tsv" 2>/dev/null || : > "$TMP/$machine.ports.tsv"
python3 "$KIT/scripts/registry_inputs.py" endpoints "$TMP/$machine.ports.tsv" "$TMP/caddy.json" "$mip" > "$TMP/$machine.ep.json"
python3 "$KIT/scripts/registry_parse.py" "$machine" "$(cat "$TMP/$machine.ep.json")" "$declared_json" \
< "$TMP/$machine.ps.json" > "$TMP/$machine.rows.json" 2>"$TMP/$machine.findings"
python3 -c 'import json,sys
for r in json.load(open(sys.argv[1])):
print(json.dumps(r, ensure_ascii=False))' "$TMP/$machine.rows.json" >> "$rows_all"
sed -n 's/^#findings# //p' "$TMP/$machine.findings" | python3 -c 'import json,sys
for line in sys.stdin:
line=line.strip()
if line:
for f in json.loads(line): print(f)' >> "$findings_all"
done
nb_conteneurs="$(wc -l < "$rows_all" | tr -d ' ')"
nb_findings="$(grep -c . "$findings_all" || true)"
generated="$(python3 - "$rows_all" "$nb_conteneurs" <<'PY'
import json, sys, datetime
rows = [json.loads(l) for l in open(sys.argv[1]) if l.strip()]
ts = datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
print(f"<!-- GÉNÉRÉ PAR scripts/10-registry.sh — ne pas éditer à la main. ts={ts} ; {sys.argv[2]} conteneurs -->")
print()
print("| Machine | Conteneur | Projet / service (compose) | Image | État | Épinglée | Watchtower | Type | Dans le kit | Endpoint (Caddy) |")
print("|---|---|---|---|---|---|---|---|---|---|")
for r in rows:
print(f"| {r['machine']} | `{r['name']}` | {r['project']} / {r['service']} | `{r['image']}` | {r['state']} | {r['pinned']} | {r['wt']} | {r['stateful']} | {r['kit']} | {r['endpoint'] or '—'} |")
PY
)"
render="$(cat <<EOF
# REGISTRE-BRIQUES — moitié technique (générée)
> Source : \`scripts/10-registry.sh\` (\`docker ps\` + \`docker inspect\` en **lecture seule** sur .24 et .27,
> compose du kit, Caddyfile pour les endpoints). **Ne pas éditer à la main** : la moitié humaine
> (comptes, 2FA, procédure de réinitialisation, responsable, break-glass) vit dans
> \`REGISTRE-IDENTITES.md\`.
>
> Périmètre (T-A.10) : **.24 (G9) et .27 (J5040) uniquement**. Le xpenology **.25 est hors périmètre** :
> il est décrit dans \`INVENTAIRE-STATEFUL.md\` et ses briques sont **à mourir en Phase 6**.
>
> Couverture backup : $RESTIC_COVERAGE
$generated
## Constats automatiques (détection de dérive)
$(if [[ -s "$findings_all" ]]; then sed 's/^/- /' "$findings_all"; else echo "- aucun"; fi)
## Utiliser ce fichier
- \`./10-registry.sh\` : régénère (à lancer après chaque déploiement / MAJ d'image).
- \`./10-registry.sh --check --alert\` : régénère, compare à la version commitée, **alerte en payload JSON**
si le réel a dérivé du registre (label perdu, image non épinglée, conteneur hors kit, état ≠ running).
- Un \`git diff\` sur ce fichier est, par construction, la détection de dérive.
EOF
)"
if [[ "$MODE" == "check" ]]; then
if [[ ! -f "$OUT" ]]; then
echo "[registry] REGISTRE-BRIQUES.md absent — générer d'abord" >&2
exit 2
fi
base="$(git -C "$KIT" show HEAD:g9-migration/REGISTRE-BRIQUES.md 2>/dev/null || git -C "$KIT" show HEAD:REGISTRE-BRIQUES.md 2>/dev/null || true)"
body_new="$(printf '%s' "$render" | sed '1,4d')"
body_old="$(printf '%s' "$base" | sed '1,4d')"
if [[ -z "$body_old" ]]; then
echo "[registry] pas de version commitée de comparaison" >&2
exit 2
fi
if [[ "$(printf '%s' "$body_new" | md5sum)" == "$(printf '%s' "$body_old" | md5sum)" ]]; then
echo "[registry] aucune dérive ($nb_conteneurs conteneurs, $nb_findings constats)"
exit 0
fi
echo "[registry] DÉRIVE détectée :"
printf '%s\n' "$nb_findings constats"
if [[ "$ALERT" == "1" ]]; then
source "$KIT/scripts/lib-notify.sh"
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
payload="$(python3 -c 'import json,sys; print(json.dumps({"ts":sys.argv[1],"source":"10-registry","brique":"registre","host":"g9","check":"registry-drift","http_code":0,"severite":"warn","message":sys.argv[2],"runbook":"RUNBOOKS/registre.md"},ensure_ascii=False))' "$ts" "$nb_conteneurs conteneurs, $nb_findings constats automatiques, diff registre/réel à lire dans REGISTRE-BRIQUES.md")"
notify_json "$payload"
fi
exit 1
fi
printf '%s\n' "$render" > "$OUT"
echo "[registry] $OUT généré : $nb_conteneurs conteneurs, $nb_findings constats automatiques"