#!/bin/bash set -e BACKUP_TARGET="${BACKUP_TARGET:-/backups}" BACKUP_PASSWORD="${BACKUP_PASSWORD:-}" BACKUP_RETENTION="${BACKUP_RETENTION:-7d}" # Paths to backup BACKUP_PATHS=( "/data/postgres" "/data/redis" "/data/duckdb" "/data/loki" "/data/caddy" "/personalities" "/workspaces" ) # Validate password if [ -z "$BACKUP_PASSWORD" ]; then echo "ERROR: BACKUP_PASSWORD is required" exit 1 fi # Setup restic environment export RESTIC_PASSWORD="$BACKUP_PASSWORD" export RESTIC_REPOSITORY="$BACKUP_TARGET" # Setup cloud credentials if provided [ -n "$BACKUP_S3_ACCESS_KEY" ] && export AWS_ACCESS_KEY_ID="$BACKUP_S3_ACCESS_KEY" [ -n "$BACKUP_S3_SECRET_KEY" ] && export AWS_SECRET_ACCESS_KEY="$BACKUP_S3_SECRET_KEY" [ -n "$BACKUP_B2_ACCOUNT_ID" ] && export B2_ACCOUNT_ID="$BACKUP_B2_ACCOUNT_ID" [ -n "$BACKUP_B2_ACCOUNT_KEY" ] && export B2_ACCOUNT_KEY="$BACKUP_B2_ACCOUNT_KEY" START_TIME=$(date +%s) echo "=== VibeStack Backup ===" echo "Time: $(date -Iseconds)" echo "Target: $BACKUP_TARGET" echo "" # Pre-backup: dump PostgreSQL if running pre_backup_postgres() { if command -v pg_dump &>/dev/null && [ -d "/data/postgres" ]; then echo "Creating PostgreSQL dump..." # Source postgres env if available [ -f /run/vibestack/postgres.env ] && source /run/vibestack/postgres.env local pg_user="${POSTGRES_USER:-vibestack}" local pg_db="${POSTGRES_DB:-vibestack}" local dump_file="/data/postgres/backup.sql" if pg_dump -U "$pg_user" -d "$pg_db" -f "$dump_file" 2>/dev/null; then echo " PostgreSQL dump created: $dump_file" else echo " PostgreSQL dump skipped (database not running or accessible)" fi fi } # Pre-backup: trigger Redis BGSAVE if running pre_backup_redis() { if command -v redis-cli &>/dev/null; then echo "Triggering Redis BGSAVE..." # Source redis env if available [ -f /run/vibestack/redis.env ] && source /run/vibestack/redis.env local redis_pass="${REDIS_PASSWORD:-}" local auth_args="" [ -n "$redis_pass" ] && auth_args="-a $redis_pass" if redis-cli $auth_args BGSAVE 2>/dev/null; then # Wait for save to complete sleep 2 echo " Redis BGSAVE triggered" else echo " Redis BGSAVE skipped (not running)" fi fi } # Run pre-backup hooks echo "Running pre-backup hooks..." pre_backup_postgres pre_backup_redis echo "" # Build list of paths that exist existing_paths=() for path in "${BACKUP_PATHS[@]}"; do if [ -e "$path" ]; then existing_paths+=("$path") echo "Including: $path" fi done # Allow overriding with specific path if [ -n "$1" ] && [ -e "$1" ]; then existing_paths=("$1") echo "Backing up only: $1" fi if [ ${#existing_paths[@]} -eq 0 ]; then echo "WARNING: No paths to backup" exit 0 fi echo "" echo "Starting backup..." # Run restic backup BACKUP_OUTPUT=$(restic backup "${existing_paths[@]}" --json 2>&1 | tail -1) # Parse results SNAPSHOT_ID=$(echo "$BACKUP_OUTPUT" | jq -r '.snapshot_id // empty' 2>/dev/null || echo "") BYTES_ADDED=$(echo "$BACKUP_OUTPUT" | jq -r '.data_added // 0' 2>/dev/null || echo "0") END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) echo "" echo "Backup complete!" echo " Snapshot: ${SNAPSHOT_ID:-unknown}" echo " Duration: ${DURATION}s" echo " Data added: $BYTES_ADDED bytes" # Apply retention policy echo "" echo "Applying retention policy: keep within $BACKUP_RETENTION..." restic forget --keep-within "$BACKUP_RETENTION" --prune # Write status cat > /run/vibestack/backup-status.json << EOF { "status": "idle", "last_backup": "$(date -Iseconds)", "last_status": "success", "snapshot_id": "$SNAPSHOT_ID", "duration_seconds": $DURATION, "bytes_added": $BYTES_ADDED } EOF echo "" echo "=== Backup Complete ==="