3d63c4484d
Etage 2 de la strategie de backup : 3-2-1 complet. - rclone v1.74.4 (ARM64) + remote 'proton' sur compte DEDIE bertha.cloud@proton.me (cloisonnement : une fuite n expose pas le compte Proton principal) - push apres verification d integrite, rotation distante alignee (30) - l echec du push n echoue PAS le backup local : sortie offsite:OK(n)/KO, visible dans l alerte mail DSM - config rclone en 600 chez root (la tache DSM tourne en root)
72 lines
3.0 KiB
Bash
72 lines
3.0 KiB
Bash
#!/bin/sh
|
|
# backup_trilium.sh - Backup local horodate de la base Trilium (etage 1).
|
|
# Execute en ROOT par le planificateur DSM (le dossier backup/ est en 700 uid 1000).
|
|
# 1) demande a Trilium une copie coherente via ETAPI (jamais de copie SQLite a chaud)
|
|
# 2) la sort du dossier Docker vers /volume1/backups/trilium, horodatee
|
|
# 3) verifie l integrite du fichier copie
|
|
# 4) garde les 30 derniers
|
|
# Sortie : "OK ..." ou "ERREUR ..." (pour alerte mail DSM).
|
|
|
|
BASE=/volume1/homes/Master/App/Context_continuity
|
|
PY=$BASE/venv/bin/python3
|
|
SRC=/volume1/docker/trilium/backup/backup-quotidien.db
|
|
DEST=/volume1/backups/trilium
|
|
URL=http://localhost:4292
|
|
GARDER=30
|
|
|
|
STAMP=$(date +%Y%m%d_%H%M)
|
|
CIBLE=$DEST/trilium_$STAMP.db
|
|
|
|
TOKEN=$(grep '^TRILIUM_TOKEN=' $BASE/.env | cut -d= -f2- | tr -d '"' | tr -d "'")
|
|
if [ -z "$TOKEN" ]; then echo "ERREUR token ETAPI introuvable dans $BASE/.env"; exit 1; fi
|
|
|
|
mkdir -p $DEST
|
|
|
|
# 1. Demander a Trilium de regenerer la copie (nom dedie : n ecrase pas daily/weekly/monthly)
|
|
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X PUT "$URL/etapi/backup/quotidien" -H "Authorization: $TOKEN" --max-time 60)
|
|
if [ "$CODE" != "204" ]; then echo "ERREUR ETAPI backup HTTP $CODE"; exit 1; fi
|
|
|
|
# 2. Verifier que le fichier existe et vient d etre ecrit, puis le sortir du dossier Docker
|
|
if [ ! -f "$SRC" ]; then echo "ERREUR fichier source absent : $SRC"; exit 1; fi
|
|
find "$SRC" -mmin -5 | grep -q . || { echo "ERREUR source non regeneree (plus de 5 min)"; exit 1; }
|
|
cp "$SRC" "$CIBLE" || { echo "ERREUR copie vers $CIBLE"; exit 1; }
|
|
|
|
# 3. Verifier l integrite de la COPIE (un backup non verifie n est pas un backup)
|
|
INTEG=$($PY -c "import sqlite3,sys
|
|
try:
|
|
c=sqlite3.connect('$CIBLE')
|
|
r=c.execute('PRAGMA integrity_check').fetchone()[0]
|
|
n=c.execute('SELECT count(*) FROM notes').fetchone()[0]
|
|
c.close()
|
|
print('%s|%d' % (r,n))
|
|
except Exception as e:
|
|
print('erreur|%s' % e)")
|
|
RESU=$(echo "$INTEG" | cut -d'|' -f1)
|
|
NBNOTES=$(echo "$INTEG" | cut -d'|' -f2)
|
|
if [ "$RESU" != "ok" ]; then rm -f "$CIBLE"; echo "ERREUR integrite KO ($INTEG) - copie supprimee"; exit 1; fi
|
|
|
|
# 4. Rotation : garder les N plus recents
|
|
cd $DEST || exit 1
|
|
ls -1t trilium_*.db 2>/dev/null | tail -n +$((GARDER+1)) | while read f; do rm -f "$f"; done
|
|
|
|
chmod 600 "$CIBLE"
|
|
# 5. Push off-site vers Proton Drive (n echoue PAS le backup local si KO)
|
|
REMOTE="proton:Backups/trilium"
|
|
OFFSITE="offsite:KO"
|
|
if command -v rclone >/dev/null 2>&1; then
|
|
if rclone copy "$CIBLE" "$REMOTE/" --max-duration 5m >/dev/null 2>&1; then
|
|
# rotation distante : garder les N plus recents
|
|
rclone lsf "$REMOTE/" 2>/dev/null | grep '^trilium_.*\.db$' | sort -r | tail -n +$((GARDER+1)) | while read f; do
|
|
rclone deletefile "$REMOTE/$f" >/dev/null 2>&1
|
|
done
|
|
NBREMOTE=$(rclone lsf "$REMOTE/" 2>/dev/null | grep -c '^trilium_.*\.db$')
|
|
OFFSITE="offsite:OK($NBREMOTE)"
|
|
fi
|
|
else
|
|
OFFSITE="offsite:rclone-absent"
|
|
fi
|
|
|
|
TAILLE=$(du -h "$CIBLE" | cut -f1)
|
|
RESTANT=$(ls -1 trilium_*.db 2>/dev/null | wc -l)
|
|
echo "OK $CIBLE ($TAILLE, $NBNOTES notes, $RESTANT locaux, $OFFSITE)"
|