Files
g9-migration/g9-migration/scripts/07d-migrate-photoprism.sh
T

134 lines
9.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# 07d-migrate-photoprism.sh — T4.3 : PhotoPrism sur le G9, photos COPIÉES (décision #3).
#
# Vérité terrain mesurée 2026-10-11 : il n'y a **aucun conteneur ni image PhotoPrism sur .25**
# (`docker ps -a` : rien, `docker images` : rien). PhotoPrism n'est donc pas « migré » :
# il est **installé** sur le G9 à partir des photos du NAS (/volume1/photo = 530 Go,
# 305 059 fichiers media, 2002→2026), avec un index neuf (décision #4 : indexation progressive).
#
# Transport : le sshd de DSM refuse rsync --server et scp (voir journal 2026-10-11) →
# flux tar sur SSH, relayé par la machine de pilotage : .25 → pilot → .24.
#
# Intégrité : manifeste **métadonnées** (chemin + taille, LC_ALL=C) source vs G9, totaux
# (nb fichiers, octets), plus échantillon sha256 de 200 fichiers.
#
# Usage : sur la machine de pilotage (détenant la clé), ./07d-migrate-photoprism.sh
# Étape longue (530 Go). `SKIP_COPY=1` pour ne redéployer que la stack.
# Rollback : `docker compose -f /data/docker/photoprism/docker-compose.yml down` +
# `rm -rf /data/docker/photoprism` ; .25 intact (lecture seule sur /volume1/photo).
set -euo pipefail
XP_USER="Master"; XP_HOST="192.168.1.25"; XP_PORT=222
XP_PHOTO="/volume1/photo"
G9_USER="folzo"; G9_HOST="192.168.1.24"
KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519_openfox11}"
BRIQUE="photoprism"
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}"
MIN_FREE_GB=600
INDEX_TIMEOUT="${INDEX_TIMEOUT:-3600}"
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[photoprism] $*"; }
journal() {
printf '| %s | 4 | migrate:%s : %s | %s | %s | %s |\n' \
"$(date '+%Y-%m-%d %H:%M')" "$BRIQUE" "$1" "$2" "$3" "$4" >> "$JOURNAL"
}
meta_manifest() { ( cd "$1" && find . -type f -printf '%p\t%s\n' 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 .25"; exit 1; }
$SSH_G9 "command -v docker >/dev/null && command -v rsync >/dev/null" || { echo "ERREUR : docker/rsync .24"; exit 1; }
$SSH_XP "sudo test -d $XP_PHOTO" || { echo "ERREUR : $XP_PHOTO absent sur .25"; exit 1; }
FREE_GB=$($SSH_G9 "df -BG --output=avail /data | tail -1 | tr -dc '0-9'")
[[ "$FREE_GB" -ge "$MIN_FREE_GB" ]] || { echo "ERREUR : espace /data insuffisant (${FREE_GB} Go < ${MIN_FREE_GB} Go)"; exit 1; }
log "espace /data = ${FREE_GB} Go"
mkdir -p "$STAGE"
SRC_COUNT=$($SSH_XP "sudo find $XP_PHOTO -type f | wc -l")
SRC_BYTES=$($SSH_XP "sudo find $XP_PHOTO -type f -printf '%s\n' | awk '{s+=\$1} END {print s}'")
log "source .25 : $SRC_COUNT fichiers, $SRC_BYTES octets"
# ---------------------------------------------------------------- 1. backup G2
log "=== 1/5 BACKUP restic (G2) ==="
SNAP_TODAY=$(date '+%Y-%m-%d')
if $SSH_G9 "sudo sh -c 'set -a; . /etc/restic.env; set +a; restic -r \"\$RESTIC_REPOSITORY\" snapshots 2>/dev/null' | grep -q '$SNAP_TODAY'"; then
log " ⏭ snapshot du jour déjà présent — backup complet sauté (idempotence)"
else
$SSH_G9 "sudo /usr/local/bin/restic-daily.sh >/dev/null 2>&1; sudo sh -c 'set -a; . /etc/restic.env; set +a; restic -r \"\$RESTIC_REPOSITORY\" snapshots 2>/dev/null' | tail -2"
journal "backup restic pré-migration" "Garde-fou G2 avant import PhotoPrism" "snapshot restic Freebox (log /var/log/restic-daily.log)" "restic forget <snapshot> && restic prune"
fi
# ---------------------------------------------------------------- 2. copie des photos
if [[ "${SKIP_COPY:-0}" != "1" ]]; then
log "=== 2/5 COPIE $XP_PHOTO → $G9_DIR/originals (tar sur SSH, relais pilot) ==="
$SSH_G9 "sudo mkdir -p $G9_DIR/originals $G9_DIR/storage $G9_DIR/import && sudo chown -R $G9_USER:$G9_USER $G9_DIR"
# MariaDB = uid 999 : datadir à lui (sinon « Permission denied » sur db.opt).
$SSH_G9 "sudo mkdir -p $G9_DIR/db && sudo chown -R 999:999 $G9_DIR/db && sudo chmod 750 $G9_DIR/db"
t0=$(date +%s)
# Progression : tar GNU du G9 émet un pointage sur stderr tous les 50 000 enregistrements
# (stderr relayé par ssh) → la copie de 530 Go n'est pas silencieuse pendant des heures.
# PAS de gzip : photos déjà compressées (JPEG/RAW), gzip sur le CPU du NAS = 10 Mo/s mesuré.
# Flux brut = lié par le réseau (~100 Mo/s).
if ! $SSH_XP "sudo tar c -C '$XP_PHOTO' ." | $SSH_G9 "tar x --checkpoint=50000 --checkpoint-action=echo='[copie] %n enregistrements' -C $G9_DIR/originals"; then
echo "ERREUR : flux tar interrompu"; exit 1
fi
t=$(( $(date +%s) - t0 ))
# DSM arrive avec des modes 0000/2700 (ACL Synology) → les dirs sont illisibles par folzo.
$SSH_G9 "sudo chmod -R u+rwX $G9_DIR/originals"
log " copie terminée en ${t} s"
journal "copie photos .25 → G9" "530 Go, source en lecture seule (aucune écriture sur .25)" "tar/ssh relayé par le pilot, ${t} s ; $SRC_COUNT fichiers source" "rm -rf $G9_DIR/originals (source .25 intacte)"
fi
# ---------------------------------------------------------------- 3. intégrité
log "=== 3/5 INTÉGRITÉ (manifeste métadonnées + échantillon sha256) ==="
$SSH_XP "sudo sh -c \"cd '$XP_PHOTO' && find . -type f -printf '%p\t%s\n' | LC_ALL=C sort\"" > "$STAGE/src.meta"
$SSH_G9 "cd '$G9_DIR/originals' && find . -type f -printf '%p\t%s\n' | LC_ALL=C sort" > "$STAGE/g9.meta"
DST_COUNT=$(wc -l < "$STAGE/g9.meta")
DST_BYTES=$(awk -F'\t' '{s+=$2} END {print s}' "$STAGE/g9.meta")
log " source=$SRC_COUNT fichiers / $SRC_BYTES o — G9=$DST_COUNT fichiers / $DST_BYTES o"
[[ "$SRC_COUNT" == "$DST_COUNT" && "$SRC_BYTES" == "$DST_BYTES" ]] || { echo "ERREUR : compte/taille différents"; diff "$STAGE/src.meta" "$STAGE/g9.meta" | head -20; exit 1; }
diff "$STAGE/src.meta" "$STAGE/g9.meta" > "$STAGE/meta.diff" || { log "❌ chemins/tailles différents :"; head -20 "$STAGE/meta.diff"; exit 1; }
log " ✅ manifeste métadonnées identique ($DST_COUNT fichiers)"
SAMPLE=$(shuf -n 200 "$STAGE/g9.meta" | cut -f1)
printf '%s\n' "$SAMPLE" > "$STAGE/sample.txt"
n=$(wc -l < "$STAGE/sample.txt")
SRC_SHA=$(printf '%s\n' "$SAMPLE" | $SSH_XP "sudo sh -c \"cd '$XP_PHOTO' && xargs -d '\n' sha256sum\"" | awk '{print $1}' | LC_ALL=C sort)
DST_SHA=$(printf '%s\n' "$SAMPLE" | $SSH_G9 "cd '$G9_DIR/originals' && xargs -d '\n' sha256sum" | awk '{print $1}' | LC_ALL=C sort)
if [[ "$SRC_SHA" == "$DST_SHA" ]]; then
log " ✅ échantillon sha256 : $n/$n fichiers identiques"
else
log "❌ échantillon sha256 divergent"; diff <(echo "$SRC_SHA") <(echo "$DST_SHA") | head -10; exit 1
fi
journal "intégration PhotoPrism" "copie de 530 Go à vérifier avant indexation" "manifeste chemins+tailles identique ($DST_COUNT fichiers, $DST_BYTES o) + échantillon sha256 $n/$n" "re-copie ciblée via tar/ssh"
# ---------------------------------------------------------------- 4. stack + secrets
log "=== 4/5 STACK + 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 700 $G9_DIR/secrets && sudo sh -c 'chmod 600 $G9_DIR/secrets/*'"
$SSH_G9 "sudo chown -R 999:999 $G9_DIR/db && sudo chmod 750 $G9_DIR/db"
# PhotoPrism n'accepte pas les *_FILE : env_file construit à partir des secrets (600, root, hors git).
$SSH_G9 "sudo sh -c 'umask 077; { echo PHOTOPRISM_ADMIN_PASSWORD=\$(cat $G9_DIR/secrets/admin); echo PHOTOPRISM_DATABASE_PASSWORD=\$(cat $G9_DIR/secrets/dbpass); } > $G9_DIR/secrets/photoprism.env' && sudo chown root:root $G9_DIR/secrets/photoprism.env"
$SSH_G9 "cd $G9_DIR && sudo docker compose up -d && sleep 30 && sudo docker compose ps --format '{{.Name}} {{.Status}}'"
# ---------------------------------------------------------------- 5. indexation + vérifs
log "=== 5/5 INDEXATION (progressive, décision #4) + VÉRIFS ==="
$SSH_G9 "curl -s -o /dev/null -w 'photoprism HTTP %{http_code}\n' http://127.0.0.1:2342/api/v1/health"
$SSH_G9 "cd $G9_DIR && timeout $INDEX_TIMEOUT sudo docker compose exec -T photoprism photoprism index < /dev/null 2>&1 | tail -6 || true"
$SSH_G9 "cd $G9_DIR && sudo docker compose exec -T db sh -c 'mariadb -u photoprism -p\"\$(cat /run/secrets/dbpass)\" photoprism -e \"SELECT COUNT(*) AS photos_indexes FROM photos\"' 2>/dev/null || true"
journal "stack PhotoPrism déployée + indexation lancée" "T4.3 : stack saine, index progressif (photothèque 530 Go sur le G9)" "image épinglée 260919, MariaDB 10.11 (watchtower OFF), secrets via env_file 600, port 2342 LAN ; originals = copie locale" "docker compose down + rm -rf /data/docker/photoprism"
echo
log "TERMINÉ. Prochaine étape : laisser monter l'index (cron/kommand), puis T4.5 (n8n)."