#!/usr/bin/env bash # backup_all — Backup completo del estado del registry: registry.db, operations.db de cada app, y vaults declarados. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../infra/backup_sqlite_db.sh" source "$SCRIPT_DIR/../infra/rotate_backups.sh" backup_all() { local backup_root="${1:?Arg 1 requerido: backup_root}" local start_ts start_ts=$(date +%s) # --- 1. Localizar FN_REGISTRY_ROOT --- local registry_root if [[ -n "${FN_REGISTRY_ROOT:-}" && -f "$FN_REGISTRY_ROOT/registry.db" ]]; then registry_root="$FN_REGISTRY_ROOT" elif [[ -f "$(pwd)/registry.db" ]]; then registry_root="$(pwd)" else echo "ERROR: No se puede localizar registry.db. Setea FN_REGISTRY_ROOT o ejecuta desde la raiz del registry." >&2 return 1 fi # --- 2. Verificar herramientas del sistema --- local missing_tools=() for tool in sqlite3 rsync find; do command -v "$tool" &>/dev/null || missing_tools+=("$tool") done if [[ ${#missing_tools[@]} -gt 0 ]]; then echo "ERROR: Herramientas faltantes: ${missing_tools[*]}" >&2 return 5 fi # --- 3. Crear backup_root --- if ! mkdir -p "$backup_root/registry" "$backup_root/operations" "$backup_root/vaults"; then echo "ERROR: No se puede crear/escribir en $backup_root" >&2 return 2 fi local log_file="$backup_root/backup_log.txt" local iso_ts iso_ts=$(date --iso-8601=seconds) local ops_count=0 local vaults_count=0 local registry_bytes=0 local partial_errors=0 # --- 4. Backup registry.db --- local snap_registry="/tmp/registry-snap-$$.db" if ! backup_sqlite_db "$registry_root/registry.db" "$snap_registry"; then echo "ERROR critico: Fallo snapshot de registry.db" >&2 rm -f "$snap_registry" return 3 fi registry_bytes=$(stat -c%s "$snap_registry" 2>/dev/null || echo 0) rotate_backups "$backup_root/registry" "$snap_registry" 7 4 12 rm -f "$snap_registry" # --- 5. Backup operations.db de cada app --- while IFS= read -r ops_db; do local app_dir app_dir="$(dirname "$ops_db")" local app_name app_name="$(basename "$app_dir")" local snap_ops="/tmp/ops-snap-$$-${app_name}.db" if backup_sqlite_db "$ops_db" "$snap_ops"; then rotate_backups "$backup_root/operations/$app_name" "$snap_ops" 7 4 12 || partial_errors=$((partial_errors + 1)) rm -f "$snap_ops" ops_count=$((ops_count + 1)) else echo "WARN: Fallo snapshot de $ops_db — skipped" >&2 rm -f "$snap_ops" partial_errors=$((partial_errors + 1)) fi done < <(find "$registry_root/apps" "$registry_root/projects" -name "operations.db" -maxdepth 4 2>/dev/null || true) # --- 6. Backup vaults via rsync + link-dest --- local vault_yaml while IFS= read -r vault_yaml; do if [[ ! -f "$vault_yaml" ]]; then continue; fi # Parsear entradas de vault.yaml: buscar pares name/path local current_name="" while IFS= read -r line; do if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*name:[[:space:]]*(.+)$ ]]; then current_name="${BASH_REMATCH[1]}" elif [[ "$line" =~ ^[[:space:]]*path:[[:space:]]*(.+)$ && -n "$current_name" ]]; then local vault_path="${BASH_REMATCH[1]}" # Strip surrounding quotes ("..." o '...') vault_path="${vault_path%\"}" vault_path="${vault_path#\"}" vault_path="${vault_path%\'}" vault_path="${vault_path#\'}" # Expandir ~ si fuera necesario vault_path="${vault_path/#\~/$HOME}" if [[ ! -d "$vault_path" ]]; then echo "WARN: Vault '$current_name' path '$vault_path' no existe — skipped" >&2 partial_errors=$((partial_errors + 1)) current_name="" continue fi local vault_dest="$backup_root/vaults/$current_name" mkdir -p "$vault_dest" local link_dest="$vault_dest/daily.1" local tmp_dest="$vault_dest/daily.0.tmp" rm -rf "$tmp_dest" if [[ -d "$link_dest" ]]; then rsync -a --link-dest="$link_dest" "$vault_path/" "$tmp_dest/" else rsync -a "$vault_path/" "$tmp_dest/" fi # Rotacion manual de directorios (7 daily, 4 weekly, 12 monthly) _rotate_vault_dirs "$vault_dest" 7 4 12 mv "$tmp_dest" "$vault_dest/daily.0" vaults_count=$((vaults_count + 1)) current_name="" fi done < "$vault_yaml" done < <(find "$registry_root/projects" -name "vault.yaml" -maxdepth 4 2>/dev/null || true) # --- 7. Log y stdout --- local end_ts elapsed end_ts=$(date +%s) elapsed=$(( end_ts - start_ts )) local summary="${iso_ts} registry=${registry_bytes}B ops=${ops_count} vaults=${vaults_count} partial_errors=${partial_errors} elapsed=${elapsed}s" echo "$summary" >> "$log_file" echo "$summary" if [[ $partial_errors -gt 0 ]]; then echo "WARN: $partial_errors errores parciales (no criticos). Ver $log_file" >&2 return 4 fi return 0 } # Rotacion manual de directorios vault (mismo algoritmo que rotate_backups pero sobre dirs) _rotate_vault_dirs() { local dir="$1" local daily="${2:-7}" local weekly="${3:-4}" local monthly="${4:-12}" # Promover a monthly (del weekly.0 al ultimo monthly) local week_day week_day=$(date +%u) # 1=lunes..7=domingo local month_day month_day=$(date +%d) if [[ "$month_day" == "01" && -d "$dir/weekly.0" ]]; then for ((i=monthly-1; i>=1; i--)); do if [[ -d "$dir/monthly.$((i-1))" ]]; then mv "$dir/monthly.$((i-1))" "$dir/monthly.$i"; fi done if [[ -d "$dir/weekly.0" ]]; then cp -al "$dir/weekly.0" "$dir/monthly.0"; fi fi if [[ "$week_day" == "7" && -d "$dir/daily.0" ]]; then for ((i=weekly-1; i>=1; i--)); do if [[ -d "$dir/weekly.$((i-1))" ]]; then mv "$dir/weekly.$((i-1))" "$dir/weekly.$i"; fi done if [[ -d "$dir/daily.0" ]]; then cp -al "$dir/daily.0" "$dir/weekly.0"; fi fi # Rotar daily if [[ -d "$dir/daily.$((daily-1))" ]]; then rm -rf "$dir/daily.$((daily-1))"; fi for ((i=daily-1; i>=1; i--)); do if [[ -d "$dir/daily.$((i-1))" ]]; then mv "$dir/daily.$((i-1))" "$dir/daily.$i"; fi done } backup_all "$@"