350eeb4f76
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>
82 lines
2.7 KiB
Bash
Executable File
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"
|