n8n-auto-backup.sh
· 9.5 KiB · Bash
Raw
#!/bin/bash
# n8n Auto-Backup Script
# Location: /var/www/docker/n8n/auto-backup.sh
# Purpose: Automatically backup n8n data and configurations
# Configuration
COMPOSE_DIR="/var/www/docker/n8n"
BACKUP_DIR="${COMPOSE_DIR}/backups"
LOG_DIR="${COMPOSE_DIR}/logs"
TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S')
LOG_FILE="${LOG_DIR}/n8n-backups_${TIMESTAMP}.log"
# Backup retention settings
KEEP_BACKUPS=5 # Keep last 5 backups
KEEP_LOGS=60 # Keep last 60 log files
MIN_FREE_SPACE_GB=15 # Gagalkan backup jika ruang kurang dari ini
# Create directories if they don't exist
mkdir -p "$BACKUP_DIR"
mkdir -p "$LOG_DIR"
# Function to log messages
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Function to log separator
log_separator() {
echo "================================================================" | tee -a "$LOG_FILE"
}
# Start logging
log_separator
log_message "💾 Starting n8n backup process"
log_separator
# Check free disk space
FREE_SPACE=$(df -BG "$COMPOSE_DIR" | awk 'NR==2 {print $4}' | sed 's/G//')
if [ "$FREE_SPACE" -lt "$MIN_FREE_SPACE_GB" ]; then
log_message "❌ ERROR: Insufficient disk space (${FREE_SPACE}GB < ${MIN_FREE_SPACE_GB}GB). Backup aborted."
exit 1
fi
# Record start time
START_TIME=$(date +%s)
# Navigate to compose directory
cd "$COMPOSE_DIR" || {
log_message "❌ ERROR: Failed to navigate to $COMPOSE_DIR"
exit 1
}
# Check if n8n container is running
if ! docker ps --format '{{.Names}}' | grep -q "^n8n$"; then
log_message "⚠️ WARNING: n8n container is not running!"
log_message " Backup will continue, but this is unusual."
fi
# Get current n8n version
log_message "📊 Current n8n information:"
CURRENT_VERSION=$(docker exec n8n n8n --version 2>/dev/null | tr -d '\n' || echo "unknown")
log_message " ΓÇó Version: $CURRENT_VERSION"
# Get container uptime
CONTAINER_STARTED=$(docker inspect n8n --format='{{.State.StartedAt}}' 2>/dev/null | cut -d'.' -f1)
log_message " ΓÇó Started at: $CONTAINER_STARTED"
# Get data size before backup
DATA_SIZE=$(du -sh "${COMPOSE_DIR}/data" 2>/dev/null | cut -f1)
log_message " ΓÇó Data size: $DATA_SIZE"
# Count workflows
WORKFLOW_COUNT=$(docker exec n8n sqlite3 /home/node/.n8n/database.sqlite "SELECT COUNT(*) FROM workflow_entity;" 2>/dev/null || echo "0")
log_message " ΓÇó Total workflows: $WORKFLOW_COUNT"
# Count credentials
CREDENTIAL_COUNT=$(docker exec n8n sqlite3 /home/node/.n8n/database.sqlite "SELECT COUNT(*) FROM credentials_entity;" 2>/dev/null || echo "0")
log_message " ΓÇó Total credentials: $CREDENTIAL_COUNT"
# Count active workflows
ACTIVE_WORKFLOW_COUNT=$(docker exec n8n sqlite3 /home/node/.n8n/database.sqlite "SELECT COUNT(*) FROM workflow_entity WHERE active = 1;" 2>/dev/null || echo "0")
log_message " ΓÇó Active workflows: $ACTIVE_WORKFLOW_COUNT"
# Check if database exists
if [ -f "${COMPOSE_DIR}/data/database.sqlite" ]; then
DB_SIZE=$(du -sh "${COMPOSE_DIR}/data/database.sqlite" 2>/dev/null | cut -f1)
log_message " ΓÇó Database size: $DB_SIZE"
log_message " • Database file: ✅ Present"
else
log_message " • Database file: ⚠️ Not found"
fi
# List active workflows with details
if [ "$ACTIVE_WORKFLOW_COUNT" -gt "0" ]; then
log_message ""
log_separator
log_message "🔄 Active Workflows (sorted by last updated):"
log_separator
# Query active workflows ordered by updatedAt
docker exec n8n sqlite3 /home/node/.n8n/database.sqlite \
"SELECT name, id, updatedAt FROM workflow_entity WHERE active = 1 ORDER BY updatedAt DESC;" 2>/dev/null | \
awk -F'|' '{
counter++
name=$1
id=$2
timestamp=$3
# Convert timestamp (format: "2025-10-22T03:30:45.123Z" or similar)
# Extract date and time parts
split(timestamp, dt, "T")
date_part=dt[1]
# Extract time part (remove everything after the first dot or Z or +/-)
time_full=dt[2]
gsub(/\.[0-9]+Z?$/, "", time_full) # Remove milliseconds and Z
gsub(/\+.*$/, "", time_full) # Remove timezone offset like +07:00
gsub(/-[0-9]{2}:.*$/, "", time_full) # Remove timezone offset like -05:00
time_part=time_full
# Format date from YYYY-MM-DD to YYYY/MM/DD
gsub("-", "/", date_part)
printf "[%s] %d. %s\n", strftime("%Y-%m-%d %H:%M:%S"), counter, name
printf "[%s] - ID: %s\n", strftime("%Y-%m-%d %H:%M:%S"), id
printf "[%s] - Last updated: %s %s\n", strftime("%Y-%m-%d %H:%M:%S"), date_part, time_part
if (counter < NR) printf "[%s]\n", strftime("%Y-%m-%d %H:%M:%S")
}' | tee -a "$LOG_FILE"
fi
log_message ""
log_separator
log_message "🗜️ Creating backup archive..."
log_separator
# Create backup filename
BACKUP_NAME="n8n-data_${TIMESTAMP}.tar.gz"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
# Create the backup
log_message " Source: ${COMPOSE_DIR}/data"
log_message " Destination: ${BACKUP_PATH}"
log_message ""
log_message "⏳ Backup in progress..."
docker run --rm \
-v "${COMPOSE_DIR}/data":/source \
-v "$BACKUP_DIR":/backup \
alpine tar cfz "/backup/${BACKUP_NAME}" -C /source . >> "$LOG_FILE" 2>&1
# Check if backup was created successfully
if [ -f "$BACKUP_PATH" ]; then
BACKUP_SIZE=$(du -h "$BACKUP_PATH" | cut -f1)
BACKUP_SIZE_BYTES=$(stat -f%z "$BACKUP_PATH" 2>/dev/null || stat -c%s "$BACKUP_PATH" 2>/dev/null)
BACKUP_SIZE_MB=$((BACKUP_SIZE_BYTES / 1024 / 1024))
log_separator
log_message "✅ Backup created successfully!"
log_separator
log_message "📦 BACKUP DETAILS:"
log_message " ΓÇó Filename: $BACKUP_NAME"
log_message " ΓÇó Location: $BACKUP_PATH"
log_message " ΓÇó Size: $BACKUP_SIZE (${BACKUP_SIZE_MB} MB)"
log_message " ΓÇó Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"
log_separator
else
log_separator
log_message "❌ ERROR: Backup creation failed!"
log_separator
log_message " Expected location: $BACKUP_PATH"
log_message " Please check disk space and permissions."
exit 1
fi
# Verify backup integrity
log_message ""
log_message "🔍 Verifying backup integrity..."
if tar -tf "$BACKUP_PATH" > /dev/null 2>&1; then
log_message " ✅ Backup archive is valid"
# List what's inside the backup
log_message ""
log_message "📋 Backup contents:"
BACKUP_FILES=$(tar -tf "$BACKUP_PATH" 2>/dev/null | wc -l)
log_message " ΓÇó Total files/directories: $BACKUP_FILES"
# Check for critical files
if tar -tf "$BACKUP_PATH" 2>/dev/null | grep -q "database.sqlite"; then
log_message " ✅ Database file present"
else
log_message " ⚠️ WARNING: Database file not found in backup!"
fi
if tar -tf "$BACKUP_PATH" 2>/dev/null | grep -q "config"; then
log_message " ✅ Config file present"
fi
else
log_message " ❌ WARNING: Backup archive may be corrupted!"
fi
# Manage old backups
log_message ""
log_separator
log_message "🗂️ Managing backup retention..."
log_separator
cd "$BACKUP_DIR"
TOTAL_BACKUPS=$(ls -1 n8n-data_*.tar 2>/dev/null | wc -l)
log_message " ΓÇó Total backups before cleanup: $TOTAL_BACKUPS"
if [ "$TOTAL_BACKUPS" -gt "$KEEP_BACKUPS" ]; then
BACKUPS_TO_DELETE=$((TOTAL_BACKUPS - KEEP_BACKUPS))
log_message " ΓÇó Removing $BACKUPS_TO_DELETE old backup(s)..."
ls -t n8n-data_*.tar 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1)) | while read -r old_backup; do
OLD_SIZE=$(du -h "$old_backup" | cut -f1)
log_message " - Deleting: $old_backup ($OLD_SIZE)"
rm -f "$old_backup"
done
fi
REMAINING_BACKUPS=$(ls -1 n8n-data_*.tar 2>/dev/null | wc -l)
TOTAL_BACKUP_SIZE=$(du -sh . 2>/dev/null | cut -f1)
log_message " ΓÇó Backups remaining: $REMAINING_BACKUPS"
log_message " ΓÇó Total backup size: $TOTAL_BACKUP_SIZE"
# Manage old log files
cd "$LOG_DIR"
TOTAL_LOGS=$(ls -1 n8n-backups_*.log 2>/dev/null | wc -l)
if [ "$TOTAL_LOGS" -gt "$KEEP_LOGS" ]; then
log_message " ΓÇó Cleaning up old log files..."
ls -t n8n-backups_*.log 2>/dev/null | tail -n +$((KEEP_LOGS + 1)) | xargs -r rm -f
REMAINING_LOGS=$(ls -1 n8n-backups_*.log 2>/dev/null | wc -l)
log_message " ✅ Kept last $REMAINING_LOGS log files"
fi
# Calculate execution time
END_TIME=$(date +%s)
EXECUTION_TIME=$((END_TIME - START_TIME))
# ... (setelah backup sukses, tambahkan vacuum)
log_message ""
log_separator
log_message "🔄 Optimizing database (VACUUM)..."
docker exec n8n sqlite3 /home/node/.n8n/database.sqlite "VACUUM;" 2>/dev/null && \
log_message " ✅ Database vacuum completed" || \
log_message " ⚠️ Vacuum failed (maybe database locked)"
log_message ""
log_separator
log_message "📊 BACKUP SUMMARY:"
log_message " ΓÇó n8n version: $CURRENT_VERSION"
log_message " ΓÇó Total workflows: $WORKFLOW_COUNT"
log_message " ΓÇó Total credentials: $CREDENTIAL_COUNT"
log_message " ΓÇó Active workflows: $ACTIVE_WORKFLOW_COUNT"
log_message " ΓÇó Data backed up: $DATA_SIZE"
log_message " ΓÇó Backup file: $BACKUP_NAME"
log_message " ΓÇó Backup size: $BACKUP_SIZE"
log_message " ΓÇó Execution time: ${EXECUTION_TIME} seconds"
log_message " • Status: ✅ SUCCESS"
log_separator
log_message ""
log_message "⏱️ Total execution time: ${EXECUTION_TIME} seconds"
log_message "📝 Log file: $LOG_FILE"
log_separator
log_message "🏁 Backup process completed successfully"
log_separator
| 1 | #!/bin/bash |
| 2 | |
| 3 | # n8n Auto-Backup Script |
| 4 | # Location: /var/www/docker/n8n/auto-backup.sh |
| 5 | # Purpose: Automatically backup n8n data and configurations |
| 6 | |
| 7 | # Configuration |
| 8 | COMPOSE_DIR="/var/www/docker/n8n" |
| 9 | BACKUP_DIR="${COMPOSE_DIR}/backups" |
| 10 | LOG_DIR="${COMPOSE_DIR}/logs" |
| 11 | TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S') |
| 12 | LOG_FILE="${LOG_DIR}/n8n-backups_${TIMESTAMP}.log" |
| 13 | |
| 14 | # Backup retention settings |
| 15 | KEEP_BACKUPS=5 # Keep last 5 backups |
| 16 | KEEP_LOGS=60 # Keep last 60 log files |
| 17 | MIN_FREE_SPACE_GB=15 # Gagalkan backup jika ruang kurang dari ini |
| 18 | |
| 19 | # Create directories if they don't exist |
| 20 | mkdir -p "$BACKUP_DIR" |
| 21 | mkdir -p "$LOG_DIR" |
| 22 | |
| 23 | # Function to log messages |
| 24 | log_message() { |
| 25 | echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" |
| 26 | } |
| 27 | |
| 28 | # Function to log separator |
| 29 | log_separator() { |
| 30 | echo "================================================================" | tee -a "$LOG_FILE" |
| 31 | } |
| 32 | |
| 33 | # Start logging |
| 34 | log_separator |
| 35 | log_message "💾 Starting n8n backup process" |
| 36 | log_separator |
| 37 | |
| 38 | # Check free disk space |
| 39 | FREE_SPACE=$(df -BG "$COMPOSE_DIR" | awk 'NR==2 {print $4}' | sed 's/G//') |
| 40 | if [ "$FREE_SPACE" -lt "$MIN_FREE_SPACE_GB" ]; then |
| 41 | log_message "❌ ERROR: Insufficient disk space (${FREE_SPACE}GB < ${MIN_FREE_SPACE_GB}GB). Backup aborted." |
| 42 | exit 1 |
| 43 | fi |
| 44 | |
| 45 | # Record start time |
| 46 | START_TIME=$(date +%s) |
| 47 | |
| 48 | # Navigate to compose directory |
| 49 | cd "$COMPOSE_DIR" || { |
| 50 | log_message "❌ ERROR: Failed to navigate to $COMPOSE_DIR" |
| 51 | exit 1 |
| 52 | } |
| 53 | |
| 54 | # Check if n8n container is running |
| 55 | if ! docker ps --format '{{.Names}}' | grep -q "^n8n$"; then |
| 56 | log_message "⚠️ WARNING: n8n container is not running!" |
| 57 | log_message " Backup will continue, but this is unusual." |
| 58 | fi |
| 59 | |
| 60 | # Get current n8n version |
| 61 | log_message "📊 Current n8n information:" |
| 62 | CURRENT_VERSION=$(docker exec n8n n8n --version 2>/dev/null | tr -d '\n' || echo "unknown") |
| 63 | log_message " ΓÇó Version: $CURRENT_VERSION" |
| 64 | |
| 65 | # Get container uptime |
| 66 | CONTAINER_STARTED=$(docker inspect n8n --format='{{.State.StartedAt}}' 2>/dev/null | cut -d'.' -f1) |
| 67 | log_message " ΓÇó Started at: $CONTAINER_STARTED" |
| 68 | |
| 69 | # Get data size before backup |
| 70 | DATA_SIZE=$(du -sh "${COMPOSE_DIR}/data" 2>/dev/null | cut -f1) |
| 71 | log_message " ΓÇó Data size: $DATA_SIZE" |
| 72 | |
| 73 | # Count workflows |
| 74 | WORKFLOW_COUNT=$(docker exec n8n sqlite3 /home/node/.n8n/database.sqlite "SELECT COUNT(*) FROM workflow_entity;" 2>/dev/null || echo "0") |
| 75 | log_message " ΓÇó Total workflows: $WORKFLOW_COUNT" |
| 76 | |
| 77 | # Count credentials |
| 78 | CREDENTIAL_COUNT=$(docker exec n8n sqlite3 /home/node/.n8n/database.sqlite "SELECT COUNT(*) FROM credentials_entity;" 2>/dev/null || echo "0") |
| 79 | log_message " ΓÇó Total credentials: $CREDENTIAL_COUNT" |
| 80 | |
| 81 | # Count active workflows |
| 82 | ACTIVE_WORKFLOW_COUNT=$(docker exec n8n sqlite3 /home/node/.n8n/database.sqlite "SELECT COUNT(*) FROM workflow_entity WHERE active = 1;" 2>/dev/null || echo "0") |
| 83 | log_message " ΓÇó Active workflows: $ACTIVE_WORKFLOW_COUNT" |
| 84 | |
| 85 | # Check if database exists |
| 86 | if [ -f "${COMPOSE_DIR}/data/database.sqlite" ]; then |
| 87 | DB_SIZE=$(du -sh "${COMPOSE_DIR}/data/database.sqlite" 2>/dev/null | cut -f1) |
| 88 | log_message " ΓÇó Database size: $DB_SIZE" |
| 89 | log_message " • Database file: ✅ Present" |
| 90 | else |
| 91 | log_message " • Database file: ⚠️ Not found" |
| 92 | fi |
| 93 | |
| 94 | # List active workflows with details |
| 95 | if [ "$ACTIVE_WORKFLOW_COUNT" -gt "0" ]; then |
| 96 | log_message "" |
| 97 | log_separator |
| 98 | log_message "🔄 Active Workflows (sorted by last updated):" |
| 99 | log_separator |
| 100 | |
| 101 | # Query active workflows ordered by updatedAt |
| 102 | docker exec n8n sqlite3 /home/node/.n8n/database.sqlite \ |
| 103 | "SELECT name, id, updatedAt FROM workflow_entity WHERE active = 1 ORDER BY updatedAt DESC;" 2>/dev/null | \ |
| 104 | awk -F'|' '{ |
| 105 | counter++ |
| 106 | name=$1 |
| 107 | id=$2 |
| 108 | timestamp=$3 |
| 109 | |
| 110 | # Convert timestamp (format: "2025-10-22T03:30:45.123Z" or similar) |
| 111 | # Extract date and time parts |
| 112 | split(timestamp, dt, "T") |
| 113 | date_part=dt[1] |
| 114 | |
| 115 | # Extract time part (remove everything after the first dot or Z or +/-) |
| 116 | time_full=dt[2] |
| 117 | gsub(/\.[0-9]+Z?$/, "", time_full) # Remove milliseconds and Z |
| 118 | gsub(/\+.*$/, "", time_full) # Remove timezone offset like +07:00 |
| 119 | gsub(/-[0-9]{2}:.*$/, "", time_full) # Remove timezone offset like -05:00 |
| 120 | time_part=time_full |
| 121 | |
| 122 | # Format date from YYYY-MM-DD to YYYY/MM/DD |
| 123 | gsub("-", "/", date_part) |
| 124 | |
| 125 | printf "[%s] %d. %s\n", strftime("%Y-%m-%d %H:%M:%S"), counter, name |
| 126 | printf "[%s] - ID: %s\n", strftime("%Y-%m-%d %H:%M:%S"), id |
| 127 | printf "[%s] - Last updated: %s %s\n", strftime("%Y-%m-%d %H:%M:%S"), date_part, time_part |
| 128 | if (counter < NR) printf "[%s]\n", strftime("%Y-%m-%d %H:%M:%S") |
| 129 | }' | tee -a "$LOG_FILE" |
| 130 | fi |
| 131 | |
| 132 | log_message "" |
| 133 | log_separator |
| 134 | log_message "🗜️ Creating backup archive..." |
| 135 | log_separator |
| 136 | |
| 137 | # Create backup filename |
| 138 | BACKUP_NAME="n8n-data_${TIMESTAMP}.tar.gz" |
| 139 | BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}" |
| 140 | |
| 141 | # Create the backup |
| 142 | log_message " Source: ${COMPOSE_DIR}/data" |
| 143 | log_message " Destination: ${BACKUP_PATH}" |
| 144 | log_message "" |
| 145 | log_message "⏳ Backup in progress..." |
| 146 | |
| 147 | docker run --rm \ |
| 148 | -v "${COMPOSE_DIR}/data":/source \ |
| 149 | -v "$BACKUP_DIR":/backup \ |
| 150 | alpine tar cfz "/backup/${BACKUP_NAME}" -C /source . >> "$LOG_FILE" 2>&1 |
| 151 | |
| 152 | # Check if backup was created successfully |
| 153 | if [ -f "$BACKUP_PATH" ]; then |
| 154 | BACKUP_SIZE=$(du -h "$BACKUP_PATH" | cut -f1) |
| 155 | BACKUP_SIZE_BYTES=$(stat -f%z "$BACKUP_PATH" 2>/dev/null || stat -c%s "$BACKUP_PATH" 2>/dev/null) |
| 156 | BACKUP_SIZE_MB=$((BACKUP_SIZE_BYTES / 1024 / 1024)) |
| 157 | |
| 158 | log_separator |
| 159 | log_message "✅ Backup created successfully!" |
| 160 | log_separator |
| 161 | log_message "📦 BACKUP DETAILS:" |
| 162 | log_message " ΓÇó Filename: $BACKUP_NAME" |
| 163 | log_message " ΓÇó Location: $BACKUP_PATH" |
| 164 | log_message " ΓÇó Size: $BACKUP_SIZE (${BACKUP_SIZE_MB} MB)" |
| 165 | log_message " ΓÇó Timestamp: $(date '+%Y-%m-%d %H:%M:%S')" |
| 166 | log_separator |
| 167 | else |
| 168 | log_separator |
| 169 | log_message "❌ ERROR: Backup creation failed!" |
| 170 | log_separator |
| 171 | log_message " Expected location: $BACKUP_PATH" |
| 172 | log_message " Please check disk space and permissions." |
| 173 | exit 1 |
| 174 | fi |
| 175 | |
| 176 | # Verify backup integrity |
| 177 | log_message "" |
| 178 | log_message "🔍 Verifying backup integrity..." |
| 179 | if tar -tf "$BACKUP_PATH" > /dev/null 2>&1; then |
| 180 | log_message " ✅ Backup archive is valid" |
| 181 | |
| 182 | # List what's inside the backup |
| 183 | log_message "" |
| 184 | log_message "📋 Backup contents:" |
| 185 | BACKUP_FILES=$(tar -tf "$BACKUP_PATH" 2>/dev/null | wc -l) |
| 186 | log_message " ΓÇó Total files/directories: $BACKUP_FILES" |
| 187 | |
| 188 | # Check for critical files |
| 189 | if tar -tf "$BACKUP_PATH" 2>/dev/null | grep -q "database.sqlite"; then |
| 190 | log_message " ✅ Database file present" |
| 191 | else |
| 192 | log_message " ⚠️ WARNING: Database file not found in backup!" |
| 193 | fi |
| 194 | |
| 195 | if tar -tf "$BACKUP_PATH" 2>/dev/null | grep -q "config"; then |
| 196 | log_message " ✅ Config file present" |
| 197 | fi |
| 198 | else |
| 199 | log_message " ❌ WARNING: Backup archive may be corrupted!" |
| 200 | fi |
| 201 | |
| 202 | # Manage old backups |
| 203 | log_message "" |
| 204 | log_separator |
| 205 | log_message "🗂️ Managing backup retention..." |
| 206 | log_separator |
| 207 | |
| 208 | cd "$BACKUP_DIR" |
| 209 | TOTAL_BACKUPS=$(ls -1 n8n-data_*.tar 2>/dev/null | wc -l) |
| 210 | log_message " ΓÇó Total backups before cleanup: $TOTAL_BACKUPS" |
| 211 | |
| 212 | if [ "$TOTAL_BACKUPS" -gt "$KEEP_BACKUPS" ]; then |
| 213 | BACKUPS_TO_DELETE=$((TOTAL_BACKUPS - KEEP_BACKUPS)) |
| 214 | log_message " ΓÇó Removing $BACKUPS_TO_DELETE old backup(s)..." |
| 215 | |
| 216 | ls -t n8n-data_*.tar 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1)) | while read -r old_backup; do |
| 217 | OLD_SIZE=$(du -h "$old_backup" | cut -f1) |
| 218 | log_message " - Deleting: $old_backup ($OLD_SIZE)" |
| 219 | rm -f "$old_backup" |
| 220 | done |
| 221 | fi |
| 222 | |
| 223 | REMAINING_BACKUPS=$(ls -1 n8n-data_*.tar 2>/dev/null | wc -l) |
| 224 | TOTAL_BACKUP_SIZE=$(du -sh . 2>/dev/null | cut -f1) |
| 225 | log_message " ΓÇó Backups remaining: $REMAINING_BACKUPS" |
| 226 | log_message " ΓÇó Total backup size: $TOTAL_BACKUP_SIZE" |
| 227 | |
| 228 | # Manage old log files |
| 229 | cd "$LOG_DIR" |
| 230 | TOTAL_LOGS=$(ls -1 n8n-backups_*.log 2>/dev/null | wc -l) |
| 231 | |
| 232 | if [ "$TOTAL_LOGS" -gt "$KEEP_LOGS" ]; then |
| 233 | log_message " ΓÇó Cleaning up old log files..." |
| 234 | ls -t n8n-backups_*.log 2>/dev/null | tail -n +$((KEEP_LOGS + 1)) | xargs -r rm -f |
| 235 | REMAINING_LOGS=$(ls -1 n8n-backups_*.log 2>/dev/null | wc -l) |
| 236 | log_message " ✅ Kept last $REMAINING_LOGS log files" |
| 237 | fi |
| 238 | |
| 239 | # Calculate execution time |
| 240 | END_TIME=$(date +%s) |
| 241 | EXECUTION_TIME=$((END_TIME - START_TIME)) |
| 242 | |
| 243 | # ... (setelah backup sukses, tambahkan vacuum) |
| 244 | log_message "" |
| 245 | log_separator |
| 246 | log_message "🔄 Optimizing database (VACUUM)..." |
| 247 | docker exec n8n sqlite3 /home/node/.n8n/database.sqlite "VACUUM;" 2>/dev/null && \ |
| 248 | log_message " ✅ Database vacuum completed" || \ |
| 249 | log_message " ⚠️ Vacuum failed (maybe database locked)" |
| 250 | |
| 251 | log_message "" |
| 252 | log_separator |
| 253 | log_message "📊 BACKUP SUMMARY:" |
| 254 | log_message " ΓÇó n8n version: $CURRENT_VERSION" |
| 255 | log_message " ΓÇó Total workflows: $WORKFLOW_COUNT" |
| 256 | log_message " ΓÇó Total credentials: $CREDENTIAL_COUNT" |
| 257 | log_message " ΓÇó Active workflows: $ACTIVE_WORKFLOW_COUNT" |
| 258 | log_message " ΓÇó Data backed up: $DATA_SIZE" |
| 259 | log_message " ΓÇó Backup file: $BACKUP_NAME" |
| 260 | log_message " ΓÇó Backup size: $BACKUP_SIZE" |
| 261 | log_message " ΓÇó Execution time: ${EXECUTION_TIME} seconds" |
| 262 | log_message " • Status: ✅ SUCCESS" |
| 263 | log_separator |
| 264 | log_message "" |
| 265 | log_message "⏱️ Total execution time: ${EXECUTION_TIME} seconds" |
| 266 | log_message "📝 Log file: $LOG_FILE" |
| 267 | log_separator |
| 268 | log_message "🏁 Backup process completed successfully" |
| 269 | log_separator |
| 270 |