T4.1 Vaultwarden : import coffre 953 ciphers + 2FA importé + ADMIN_TOKEN argon2 + emergency access activé

This commit is contained in:
Effi
2026-10-11 08:03:10 +02:00
parent e7727a6598
commit a1864c69e7
3 changed files with 146 additions and 2 deletions
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env bash
#
# 07b-migrate-vaultwarden.sh — T4.1 : migration Vaultwarden (xpenology .25 → G9 .24)
#
# Garde-fous : G4 (double-run, .25 = source de vérité, AUCUNE suppression), G2 (backup restic
# déclenché avant import), G7 (rollback), G8 (journal), G9 (aucun secret loggué/commité).
#
# SOURCE mesurée (2026-10-11) : conteneur GUI DSM `vaultwarden-server-1` (vaultwarden/server:latest,
# healthy), bind unique /volume1/docker/vaultwarden → /data (8.4 Mo) :
# db.sqlite3 (+ -wal/-shm) = 953 ciphers, 1 user, twofactor TOTP **déjà actif** (enabled=1,
# last_used > 0 → le secret est importé avec le coffre, l'appli d'authentification continue),
# 0 emergency_access, rsa_key.pem, attachments/, sends/, icon_cache/.
#
# COHÉRENCE (SQLite + WAL) : le conteneur SOURCE est **stoppé** pendant le tar, puis
# **redémarré** immédiatement (trap) — double-run G4 respecté : rien n'est supprimé sur .25.
#
# TRANSPORT : tar sur SSH (le sshd DSM refuse rsync --server et scp — voir journal 07:36).
#
# Usage (machine de pilotage avec les clés SSH) : ./07b-migrate-vaultwarden.sh
# Rollback : G9 `docker compose -f /data/docker/vaultwarden/docker-compose.yml down` +
# `rm -rf /data/docker/vaultwarden` ; .25 intact (service relancé, 953 ciphers).
set -euo pipefail
XP_USER="Master"; XP_HOST="192.168.1.25"; XP_PORT=222
XP_DOCKER="/var/packages/ContainerManager/target/usr/bin/docker"
XP_CONTAINERS="vaultwarden-server-1"
XP_SRC="/volume1/docker/vaultwarden"
G9_USER="folzo"; G9_HOST="192.168.1.24"
KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519_openfox11}"
BRIQUE="vaultwarden"
G9_DIR="/data/docker/$BRIQUE"
KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
KIT_ON_G9="/home/folzo/g9-migration"
JOURNAL="$KIT_DIR/journal.md"
STAGE="${STAGE:-$HOME/migration-staging/$BRIQUE}"
SSH_XP="ssh -p $XP_PORT -i $KEY -o ControlMaster=auto -o ControlPath=/tmp/.g9-ctl-%r@%h:%p -o ControlPersist=600 $XP_USER@$XP_HOST"
SSH_G9="ssh -i $KEY -o ControlMaster=auto -o ControlPath=/tmp/.g9-ctl-%r@%h:%p -o ControlPersist=600 $G9_USER@$G9_HOST"
RSH_G9="ssh -i $KEY"
log() { echo -e "\n[vaultwarden] $*"; }
journal() {
printf '| %s | 4 | migrate:%s : %s | %s | %s | %s |\n' \
"$(date '+%Y-%m-%d %H:%M')" "$BRIQUE" "$1" "$2" "$3" "$4" >> "$JOURNAL"
}
manifest_in() { ( cd "$1" && find . -type f -exec sha256sum {} + 2>/dev/null | LC_ALL=C sort ); }
# ---------------------------------------------------------------- 0. préflight
log "=== 0/5 PRÉFLIGHT ==="
$SSH_XP "command -v tar >/dev/null && sudo tar --version >/dev/null" || { echo "ERREUR : SSH/tar(sudo) .25 indisponibles"; exit 1; }
$SSH_G9 "command -v docker >/dev/null && command -v rsync >/dev/null" || { echo "ERREUR : docker/rsync .24 indisponibles"; exit 1; }
$SSH_G9 "test -d $KIT_ON_G9/compose" || { echo "ERREUR : kit absent sur le G9"; exit 1; }
$SSH_XP "sudo test -f $XP_SRC/db.sqlite3" || { echo "ERREUR : coffre source absent ($XP_SRC/db.sqlite3)"; exit 1; }
mkdir -p "$STAGE"
SRC_CIPHERS=$($SSH_XP "sudo sqlite3 $XP_SRC/db.sqlite3 'select count(*) from ciphers;'")
SRC_USERS=$($SSH_XP "sudo sqlite3 $XP_SRC/db.sqlite3 'select count(*) from users;'")
SRC_2FA=$($SSH_XP "sudo sqlite3 $XP_SRC/db.sqlite3 'select count(*) from twofactor where enabled=1;'")
log "source .25 : ciphers=$SRC_CIPHERS users=$SRC_USERS 2fa_actif=$SRC_2FA"
# ---------------------------------------------------------------- 1. backup G2
log "=== 1/5 BACKUP restic (G2) ==="
$SSH_G9 "sudo /usr/local/bin/restic-daily.sh >/dev/null 2>&1; sudo restic snapshots 2>/dev/null | tail -2"
journal "backup restic pré-migration" "Garde-fou G2 avant import Vaultwarden" "snapshot restic Freebox (log /var/log/restic-daily.log)" "restic forget <snapshot> && restic prune"
# ---------------------------------------------------------------- 2. export (stop → tar → start)
log "=== 2/5 EXPORT depuis .25 : stop source → tar/ssh → redémarrage source (G4) ==="
STOPPED=()
for c in $XP_CONTAINERS; do
if $SSH_XP "sudo $XP_DOCKER ps -q -f name=^${c}\$" | grep -q .; then
$SSH_XP "sudo $XP_DOCKER stop $c >/dev/null" && STOPPED+=("$c") && log " ⏸ source stoppée : $c"
fi
done
restart_source() {
for c in "${STOPPED[@]:-}"; do
[[ -n "$c" ]] || continue
$SSH_XP "sudo $XP_DOCKER start $c >/dev/null" && log " ▶ source redémarrée : $c" \
|| log " ⚠️ ÉCHEC du redémarrage de $c — INTERVENIR SUR .25"
done
}
trap restart_source EXIT
TAR="$STAGE/vaultwarden.tgz"
$SSH_XP "sudo tar cz -C '$XP_SRC' ." > "$TAR"
log " archive : $(du -h "$TAR" | cut -f1)"
tar xzf "$TAR" -C "$STAGE"
log " intégrité (source == stage) :"
r=$(mktemp); l=$(mktemp)
$SSH_XP "cd '$XP_SRC' && sudo find . -type f -exec sha256sum {} + 2>/dev/null | LC_ALL=C sort" > "$r"
manifest_in "$STAGE" | grep -v "$(basename "$TAR")" > "$l"
diff -u "$r" "$l" > "$r.diff" || { log " ❌ intégrité source ≠ stage :"; head -20 "$r.diff"; rm -f "$r" "$l" "$r.diff"; exit 1; }
log " ✅ $(wc -l < "$r") fichiers identiques"
rm -f "$r" "$l" "$r.diff" "$TAR"
# redémarrage de la source AVANT de toucher au G9 (double-run)
restart_source
STOPPED=()
# ---------------------------------------------------------------- 3. import G9
log "=== 3/5 IMPORT sur le G9 ==="
$SSH_G9 "sudo mkdir -p $G9_DIR && sudo chown -R $G9_USER:$G9_USER $G9_DIR"
rsync -a --info=stats1 -e "$RSH_G9" --exclude "$(basename "$TAR")" "$STAGE/" "$G9_USER@$G9_HOST:$G9_DIR/import/"
g=$(mktemp); manifest_in "$STAGE" | grep -v "$(basename "$TAR")" > "$l"
$SSH_G9 "cd '$G9_DIR/import' && find . -type f -exec sha256sum {} + 2>/dev/null | LC_ALL=C sort" > "$g"
diff -u "$l" "$g" > "$g.diff" || { log " ❌ intégrité G9 :"; head -20 "$g.diff"; rm -f "$l" "$g" "$g.diff"; exit 1; }
log " ✅ $(wc -l < "$g") fichiers identiques sur le G9"
rm -f "$l" "$g" "$g.diff"
rm -rf "$STAGE"
log "déploiement compose + secrets"
rsync -a --info=stats1 -e "$RSH_G9" --exclude "secrets" "$KIT_DIR/compose/$BRIQUE/" "$G9_USER@$G9_HOST:$KIT_ON_G9/compose/$BRIQUE/"
rsync -a -e "$RSH_G9" "$KIT_DIR/scripts/03b-secrets.sh" "$G9_USER@$G9_HOST:$KIT_ON_G9/scripts/"
$SSH_G9 "sudo bash $KIT_ON_G9/scripts/03b-secrets.sh >/dev/null"
$SSH_G9 "sudo rsync -a $KIT_ON_G9/compose/$BRIQUE/ $G9_DIR/ && sudo chmod 755 $G9_DIR/secrets && sudo chmod 644 $G9_DIR/secrets/admin"
# Le coffre importé doit être lu par vaultwarden : ./data = /data (DATA_FOLDER par défaut)
$SSH_G9 "mkdir -p $G9_DIR/data && rsync -a $G9_DIR/import/ $G9_DIR/data/ && rm -rf $G9_DIR/import"
$SSH_G9 "cd $G9_DIR && docker compose up -d && sleep 15 && docker compose ps --format '{{.Name}} {{.Status}}'"
# ---------------------------------------------------------------- 4. vérification
log "=== 4/5 VÉRIFICATION ==="
$SSH_G9 "curl -sf -o /dev/null -w 'alive HTTP %{http_code}\n' http://127.0.0.1:8081/alive"
G9_CIPHERS=$($SSH_G9 "cd $G9_DIR && docker compose exec -T vaultwarden sqlite3 /data/db.sqlite3 'select count(*) from ciphers;' < /dev/null 2>/dev/null || docker run --rm -v $G9_DIR/data:/d alpine sqlite3 /d/db.sqlite3 'select count(*) from ciphers;' 2>/dev/null")
G9_USERS=$($SSH_G9 "cd $G9_DIR && docker compose exec -T vaultwarden sh -c 'command -v sqlite3 >/dev/null && sqlite3 /data/db.sqlite3 \"select count(*) from users;\"' < /dev/null 2>/dev/null")
log "G9 : ciphers=${G9_CIPHERS:-?} users=${G9_USERS:-?} (source : $SRC_CIPHERS / $SRC_USERS)"
$SSH_G9 "cd $G9_DIR && docker compose exec -T vaultwarden sh -c 'command -v sqlite3 >/dev/null && sqlite3 /data/db.sqlite3 \"select atype, enabled, last_used from twofactor;\"' < /dev/null 2>/dev/null"
$SSH_XP "sudo $XP_DOCKER ps --format '{{.Names}} {{.Status}}' | grep -i vault"
journal "Vaultwarden importé + lancé sur le G9" "T4.1 (double-run G4, source stoppée puis relancée)" "coffre ${SRC_CIPHERS} ciphers / ${SRC_USERS} user, sha256 identiques, 2FA TOTP importé (enabled), alive HTTP 200, .25 relancé" "docker compose -f $G9_DIR/docker-compose.yml down + rm -rf $G9_DIR (G9 seul) ; .25 intact"
log "Terminé. RESTE (UI, par l'utilisateur) : 2FA déjà actif (secret importé, l'appli d'auth"
log "continue), emergency access à activer dans WebVault → Paramètres → Accès d'urgence."