Files
Cerbero-mcp/scripts/prune-acme-stale.sh
T
Adriano 350eeb4f76 chore(ops): add prune-acme-stale.sh to remove dead domains from Traefik acme.json
Stale certificate entries (mattermost/zar/registry.tielogic.xyz) in
Traefik's acme.json triggered repeated NXDOMAIN renewal errors. Script
atomically stops Traefik, backs up acme.json, filters out the listed
domains with JSON validation + auto-rollback, restores 600 root:adriano
perms, and restarts. Domain list is extensible via the STALE array.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:06:26 +00:00

82 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Rimuove certificati stale da acme.json di Traefik (domini non più serviti).
# Idempotente e atomico: stop Traefik -> backup -> filtro+valida -> ripristino
# perms/owner (600 root:adriano) -> restart Traefik.
#
# Uso: sudo bash scripts/prune-acme-stale.sh
set -euo pipefail
ACME="/opt/docker/traefik/acme.json"
CONTAINER="traefik-traefik-1"
# Domini da rimuovere (match esatto su .domain.main). Estendibile.
STALE=("mattermost.tielogic.xyz" "zar.tielogic.xyz" "registry.tielogic.xyz")
if [[ $EUID -ne 0 ]]; then
echo "Deve girare come root (usa: sudo bash $0)" >&2
exit 1
fi
[[ -f "$ACME" ]] || { echo "Non trovo $ACME" >&2; exit 1; }
ts="$(date +%Y%m%d-%H%M%S)"
backup="${ACME}.bak-${ts}"
cp -p "$ACME" "$backup"
echo "Backup: $backup"
echo "=== Domini attualmente in storage ==="
python3 - "$ACME" <<'PY'
import json,sys
d=json.load(open(sys.argv[1]))
for res,blk in d.items():
for c in (blk or {}).get("Certificates") or []:
print(f" [{res}] {c.get('domain',{}).get('main')}")
PY
echo "Stop $CONTAINER (evita riscrittura concorrente)…"
docker stop "$CONTAINER" >/dev/null
# Filtro in Python: rimuove i cert il cui domain.main è nella lista STALE.
removed=$(python3 - "$ACME" "${STALE[@]}" <<'PY'
import json,sys,os,tempfile
path=sys.argv[1]; stale=set(sys.argv[2:])
d=json.load(open(path))
removed=[]
for res,blk in d.items():
certs=(blk or {}).get("Certificates")
if not certs: continue
kept=[]
for c in certs:
main=c.get("domain",{}).get("main")
if main in stale: removed.append(main)
else: kept.append(c)
blk["Certificates"]=kept
# scrittura atomica nella stessa dir, poi os.replace
dir_=os.path.dirname(path)
fd,tmp=tempfile.mkstemp(dir=dir_)
with os.fdopen(fd,"w") as f:
json.dump(d,f,indent=2) # ri-serializza valido
os.replace(tmp,path)
print("\n".join(removed))
PY
)
# Ripristina perms/owner che Traefik esige (600) e ownership originale.
chmod 600 "$ACME"
chown root:adriano "$ACME"
# Validazione finale: deve essere JSON valido (altrimenti rollback dal backup).
if ! python3 -c "import json,sys; json.load(open('$ACME'))" 2>/dev/null; then
echo "JSON risultante NON valido — rollback dal backup." >&2
cp -p "$backup" "$ACME"; chmod 600 "$ACME"; chown root:adriano "$ACME"
docker start "$CONTAINER" >/dev/null
exit 1
fi
echo "Restart $CONTAINER…"
docker start "$CONTAINER" >/dev/null
echo "=== Rimossi ==="
if [[ -n "${removed//[$'\n ']/}" ]]; then echo "$removed" | sed 's/^/ /'; else echo " (nessuno — già puliti)"; fi
echo "=== Permessi finali ==="
ls -la "$ACME"
echo "Fatto. Se qualcosa va storto, ripristina: sudo cp -p $backup $ACME && sudo chmod 600 $ACME && docker restart $CONTAINER"