Il y avait deux dépôts d'infrastructure et aucune frontière entre eux. La seule distinction défendable — « documentation » d'un côté, « fichiers de configuration » de l'autre — ne tenait plus : infra avait fini par contenir plus de documentation qu'owni, pendant qu'owni décrivait du concret (IP, bases, comptes). Tout vient donc ici : conf/ (vhosts de référence), dns/ (export de zone), services/ (units systemd), scripts/ (sauvegarde) et docs/ (courrier sortant, certificats, sauvegardes, et le déploiement par service). Dans ce sens plutôt que l'inverse parce qu'« owni » nomme la machine, là où « infra » ne dit pas de quoi il s'agit — et c'est le nom que Cédric emploie spontanément, y compris pour le dossier de secrets. Les renvois croisés entre les deux dépôts deviennent des liens internes : un lien vers un dépôt qu'on s'apprête à archiver aurait pourri en silence. Reste une incohérence, signalée plutôt que corrigée à la hâte : docs/admin.md, static.md, wiki.md, dynamic.md et proxy-calendar.md décrivent le déploiement de sites, pas la machine. Ils ont la même place ici que celle que la messagerie n'avait pas — ils devraient rejoindre leurs dépôts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFbwnJurBwTs7x7t93ecku
95 lines
3.3 KiB
Bash
Executable file
95 lines
3.3 KiB
Bash
Executable file
#!/bin/bash
|
|
# Sauvegarde des bases d'owni, tirée depuis le poste.
|
|
#
|
|
# scripts/sauvegarder-owni.sh # sauvegarde
|
|
# scripts/sauvegarder-owni.sh --essai # montre ce qui serait fait
|
|
#
|
|
# Tiré depuis le poste et non poussé depuis owni : une machine compromise ne
|
|
# doit pas pouvoir atteindre les sauvegardes de ce qu'elle héberge. C'est le
|
|
# poste qui va chercher, owni n'a aucun accès en retour.
|
|
#
|
|
# Les dumps sont faits ici plutôt que repris de /var/backup : ISPConfig n'en
|
|
# produit qu'un sur six (seule la messagerie y est réglée), et n'en garde
|
|
# qu'une copie. Ce script prend les six et garde ce qu'on lui dit.
|
|
#
|
|
# Ce qu'il ne fait pas : les fichiers des sites. Le code est dans Gitea, les
|
|
# pièces jointes et les boîtes mail sont un autre sujet — voir docs/sauvegardes.md.
|
|
|
|
set -u -o pipefail
|
|
|
|
DEST="${SAUVEGARDE_DEST:-$HOME/Sauvegardes/owni}"
|
|
GARDE="${SAUVEGARDE_GARDE:-30}" # jours de rétention
|
|
SSH_OPTS=(-o BatchMode=yes -o IdentitiesOnly=yes -o ConnectTimeout=20
|
|
-i "$HOME/.ssh/id_rsa_owni.alpinux.org")
|
|
HOTE="abonnelc@owni.alpinux.org"
|
|
ESSAI=0
|
|
[ "${1:-}" = "--essai" ] && ESSAI=1
|
|
|
|
# Les bases applicatives. dbispconfig est à part : c'est la configuration
|
|
# d'ISPConfig, sans laquelle les sites ne se reconstruisent pas à l'identique.
|
|
BASES=(c1_messagerie_db c1dolibarr c1gitea c1alpid c1_installparty c1_evenements dbispconfig)
|
|
|
|
jour=$(date +%Y-%m-%d)
|
|
cible="$DEST/$jour"
|
|
|
|
echo "Sauvegarde d'owni vers $cible"
|
|
[ "$ESSAI" = 1 ] && echo "(essai : rien ne sera écrit)"
|
|
|
|
if [ "$ESSAI" = 0 ]; then
|
|
mkdir -p "$cible" || { echo "impossible de créer $cible" >&2; exit 1; }
|
|
chmod 700 "$DEST" "$cible"
|
|
fi
|
|
|
|
# Une base qui échoue ne doit pas interrompre les autres : mieux vaut cinq
|
|
# sauvegardes sur six qu'aucune, et le compte final dit ce qui manque.
|
|
faites=0
|
|
ratees=()
|
|
|
|
for base in "${BASES[@]}"; do
|
|
fichier="$cible/${base}_${jour}.sql.gz"
|
|
printf ' %-20s ' "$base"
|
|
|
|
if [ "$ESSAI" = 1 ]; then
|
|
echo "→ ${fichier/#$HOME/\~}"
|
|
continue
|
|
fi
|
|
|
|
# --single-transaction : pas de verrou sur les tables InnoDB, le site
|
|
# continue de répondre pendant le dump.
|
|
if ssh "${SSH_OPTS[@]}" "$HOTE" \
|
|
"sudo mysqldump --no-tablespaces --single-transaction --quick '$base'" \
|
|
2>/dev/null | gzip -c > "$fichier"; then
|
|
taille=$(du -h "$fichier" | cut -f1)
|
|
# Un dump vide ou tronqué se reconnaît à sa dernière ligne.
|
|
if zcat "$fichier" 2>/dev/null | tail -2 | grep -q "Dump completed"; then
|
|
echo "ok $taille"
|
|
faites=$((faites + 1))
|
|
else
|
|
echo "INCOMPLET ($taille) — dump tronqué"
|
|
ratees+=("$base")
|
|
fi
|
|
else
|
|
echo "ÉCHEC"
|
|
ratees+=("$base")
|
|
rm -f "$fichier"
|
|
fi
|
|
done
|
|
|
|
[ "$ESSAI" = 1 ] && exit 0
|
|
|
|
chmod 600 "$cible"/*.sql.gz 2>/dev/null
|
|
|
|
# Rotation : on efface les jours entiers passés la rétention. Faite après, et
|
|
# seulement si la sauvegarde du jour a réussi — sinon on effacerait les
|
|
# anciennes sans en avoir de nouvelle.
|
|
if [ "$faites" -gt 0 ]; then
|
|
find "$DEST" -maxdepth 1 -type d -name '20*-*-*' -mtime "+$GARDE" \
|
|
-exec rm -rf {} + 2>/dev/null
|
|
fi
|
|
|
|
echo
|
|
echo "$faites base(s) sauvegardée(s) sur ${#BASES[@]}, rétention $GARDE jours."
|
|
if [ ${#ratees[@]} -gt 0 ]; then
|
|
echo "Échec : ${ratees[*]}" >&2
|
|
exit 1
|
|
fi
|