#!/usr/bin/env bash # Backup criptat SQLite pentru gateway RAR AUTOPASS (T2/P0-4, evidenta legala L.142/2023). # # Foloseste API-ul de backup online SQLite (sqlite3.Connection.backup, stdlib python3) — # NU `cp`/`rsync`: baza ruleaza in WAL, iar o copie simpla a fisierului .db poate prinde # tranzactii necheckpoint-ate (copie inconsistenta). python3 e ales ca prim mijloc (nu # binarul CLI `sqlite3`) pentru ca e garantat prezent in imaginea Docker si pe masina de # dezvoltare; binarul `sqlite3` e folosit doar daca python3 lipseste (caz neasteptat). # # Rezultatul e comprimat (gzip) si criptat simetric (gpg AES256) — snapshot-ul contine # PII criptat Fernet plus metadate, un backup NECRIPTAT nu e permis. # # Utilizare: # AUTOPASS_BACKUP_PASSPHRASE_FILE=/run/secrets/backup_pass tools/backup_db.sh # AUTOPASS_BACKUP_PASSPHRASE=parola-de-test tools/backup_db.sh # # Variabile de mediu: # AUTOPASS_DB_PATH cale baza sursa (default /data/autopass.db) # AUTOPASS_BACKUP_DIR director destinatie backup-uri (default /data/backups) # AUTOPASS_BACKUP_KEEP cate backup-uri locale pastreaza (default 14) # AUTOPASS_BACKUP_PASSPHRASE_FILE fisier cu parola de criptare (preferat) # AUTOPASS_BACKUP_PASSPHRASE parola de criptare inline (fallback, ex. teste) # AUTOPASS_BACKUP_RCLONE_REMOTE tinta rclone optionala (ex. remote:bucket/autopass) set -euo pipefail DB_PATH="${AUTOPASS_DB_PATH:-/data/autopass.db}" BACKUP_DIR="${AUTOPASS_BACKUP_DIR:-/data/backups}" KEEP="${AUTOPASS_BACKUP_KEEP:-14}" RCLONE_REMOTE="${AUTOPASS_BACKUP_RCLONE_REMOTE:-}" log() { echo "[backup_db] $*"; } err() { echo "[backup_db] EROARE: $*" >&2; } CLEANUP_PATHS=() cleanup() { local p for p in "${CLEANUP_PATHS[@]:-}"; do [[ -n "$p" ]] && rm -rf "$p" done } trap cleanup EXIT # --- parola de criptare: fara ea, refuzam sa scriem un backup necriptat --- if [[ -n "${AUTOPASS_BACKUP_PASSPHRASE_FILE:-}" ]]; then if [[ ! -f "$AUTOPASS_BACKUP_PASSPHRASE_FILE" ]]; then err "AUTOPASS_BACKUP_PASSPHRASE_FILE indica un fisier inexistent: $AUTOPASS_BACKUP_PASSPHRASE_FILE" exit 1 fi PASSPHRASE_FILE="$AUTOPASS_BACKUP_PASSPHRASE_FILE" elif [[ -n "${AUTOPASS_BACKUP_PASSPHRASE:-}" ]]; then PASSPHRASE_FILE="$(mktemp)" chmod 600 "$PASSPHRASE_FILE" CLEANUP_PATHS+=("$PASSPHRASE_FILE") printf '%s' "$AUTOPASS_BACKUP_PASSPHRASE" > "$PASSPHRASE_FILE" else err "lipseste parola de criptare a backup-ului." err "seteaza AUTOPASS_BACKUP_PASSPHRASE_FILE (recomandat) sau AUTOPASS_BACKUP_PASSPHRASE." err "backup necriptat NU este permis (contine PII criptat Fernet si metadate)." exit 1 fi if [[ ! -f "$DB_PATH" ]]; then err "baza de date nu exista: $DB_PATH" exit 1 fi mkdir -p "$BACKUP_DIR" STAMP="$(date -u +%Y%m%d-%H%M%S)" TMP_DIR="$(mktemp -d)" CLEANUP_PATHS+=("$TMP_DIR") SNAPSHOT="$TMP_DIR/autopass-$STAMP.db" log "snapshot online din $DB_PATH" if command -v python3 >/dev/null 2>&1; then python3 - "$DB_PATH" "$SNAPSHOT" <<'PYEOF' import sqlite3 import sys src = sqlite3.connect(sys.argv[1]) dst = sqlite3.connect(sys.argv[2]) try: with dst: src.backup(dst) finally: src.close() dst.close() PYEOF elif command -v sqlite3 >/dev/null 2>&1; then sqlite3 "$DB_PATH" ".backup '$SNAPSHOT'" else err "nici python3, nici binarul sqlite3 nu sunt disponibile - nu pot face backup online" exit 1 fi if [[ ! -s "$SNAPSHOT" ]]; then err "snapshot-ul rezultat e gol sau lipseste: $SNAPSHOT" exit 1 fi log "comprim (gzip)" gzip -9 "$SNAPSHOT" SNAPSHOT_GZ="$SNAPSHOT.gz" FINAL="$BACKUP_DIR/autopass-$STAMP.db.gz.gpg" log "criptez simetric (gpg AES256) -> $FINAL" gpg --batch --yes --symmetric --cipher-algo AES256 \ --passphrase-file "$PASSPHRASE_FILE" \ --output "$FINAL" \ "$SNAPSHOT_GZ" log "backup scris: $FINAL ($(du -h "$FINAL" | cut -f1))" # --- retentie locala: pastreaza ultimele $KEEP, sterge restul --- mapfile -t OLD_BACKUPS < <(ls -1t "$BACKUP_DIR"/autopass-*.db.gz.gpg 2>/dev/null | tail -n "+$((KEEP + 1))") if [[ "${#OLD_BACKUPS[@]}" -gt 0 ]]; then log "sterg ${#OLD_BACKUPS[@]} backup(uri) vechi (pastrez ultimele $KEEP)" rm -f "${OLD_BACKUPS[@]}" fi # --- upload optional remote --- if [[ -n "$RCLONE_REMOTE" ]]; then if command -v rclone >/dev/null 2>&1; then log "upload rclone -> $RCLONE_REMOTE" rclone copy "$FINAL" "$RCLONE_REMOTE" else log "AUTOPASS_BACKUP_RCLONE_REMOTE setat dar rclone nu e instalat - sar peste upload remote" fi else log "AUTOPASS_BACKUP_RCLONE_REMOTE nesetat - sar peste upload remote (doar local)" fi log "gata."