docs: create immich_backup
This commit is contained in:
parent
6b3000fa28
commit
034047d0c2
1 changed files with 230 additions and 0 deletions
230
immich_backup.md
Normal file
230
immich_backup.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
---
|
||||
title: Immich Backup and Restore
|
||||
description: Immich backup with Kopia
|
||||
published: true
|
||||
date: 2026-02-14T03:14:32.594Z
|
||||
tags:
|
||||
editor: markdown
|
||||
dateCreated: 2026-02-14T03:14:32.594Z
|
||||
---
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Immich Backup Script
|
||||
# Two-tier approach matching mailcow backup strategy:
|
||||
# Tier 1 (Local): Component-level backups (database + volumes)
|
||||
# Tier 2 (Offsite): Kopia snapshots to vaults
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
BACKUP_DIR="/opt/immich-backups"
|
||||
RETENTION_DAYS=7 # Keep local backups for 7 days (like mailcow)
|
||||
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_PATH="${BACKUP_DIR}/immich-${BACKUP_DATE}"
|
||||
LOG_FILE="/var/log/immich-backup.log"
|
||||
|
||||
# Immich configuration (adjust these to your setup)
|
||||
IMMICH_DIR="/opt/immich" # or wherever your docker-compose.yml is
|
||||
COMPOSE_FILE="${IMMICH_DIR}/docker-compose.yml"
|
||||
POSTGRES_CONTAINER="immich_postgres" # adjust if different
|
||||
PROJECT_NAME="immich" # Docker Compose project name
|
||||
|
||||
# Kopia configuration
|
||||
ENABLE_KOPIA=true # Set to false to disable offsite backups
|
||||
KOPIA_TAGS="immich,tier1-backup"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Create backup directory
|
||||
mkdir -p "${BACKUP_PATH}"
|
||||
|
||||
log "========================================"
|
||||
log "Starting Immich Tier 1 (Local) backup"
|
||||
log "Backup path: ${BACKUP_PATH}"
|
||||
log "========================================"
|
||||
|
||||
# 1. Backup PostgreSQL database
|
||||
log "Backing up PostgreSQL database..."
|
||||
docker exec -t "${POSTGRES_CONTAINER}" pg_dumpall -c -U postgres | gzip > "${BACKUP_PATH}/database.sql.gz"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
DB_SIZE=$(du -h "${BACKUP_PATH}/database.sql.gz" | cut -f1)
|
||||
log "Database backup completed successfully (${DB_SIZE})"
|
||||
else
|
||||
error "Database backup failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Backup Immich volumes/data
|
||||
log "Backing up Immich data volumes..."
|
||||
|
||||
# Get actual Docker volumes for this project
|
||||
DOCKER_VOLUMES=$(docker volume ls --filter "name=${PROJECT_NAME}" --format "{{.Name}}")
|
||||
|
||||
if [ -z "${DOCKER_VOLUMES}" ]; then
|
||||
warn "No Docker volumes found with name pattern '${PROJECT_NAME}'. Checking for bind mounts..."
|
||||
|
||||
# Fallback to bind mounts if present
|
||||
VOLUMES=(
|
||||
"upload"
|
||||
"library"
|
||||
"profile"
|
||||
"video"
|
||||
"thumbs"
|
||||
"encoded-video"
|
||||
)
|
||||
|
||||
for volume in "${VOLUMES[@]}"; do
|
||||
SOURCE_PATH="${IMMICH_DIR}/${volume}"
|
||||
if [ -d "${SOURCE_PATH}" ]; then
|
||||
log "Backing up bind mount: ${volume}..."
|
||||
tar -czf "${BACKUP_PATH}/${volume}.tar.gz" -C "${IMMICH_DIR}" "${volume}" 2>/dev/null || warn "${volume} backup failed"
|
||||
fi
|
||||
done
|
||||
else
|
||||
# Backup Docker volumes
|
||||
for vol in ${DOCKER_VOLUMES}; do
|
||||
log "Backing up Docker volume: ${vol}"
|
||||
docker run --rm \
|
||||
-v "${vol}:/data:ro" \
|
||||
-v "${BACKUP_PATH}:/backup" \
|
||||
alpine tar -czf "/backup/${vol}.tar.gz" -C /data . || warn "Failed to backup ${vol}"
|
||||
|
||||
if [ -f "${BACKUP_PATH}/${vol}.tar.gz" ]; then
|
||||
VOL_SIZE=$(du -h "${BACKUP_PATH}/${vol}.tar.gz" | cut -f1)
|
||||
log " ✓ ${vol} backed up (${VOL_SIZE})"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 3. Backup configuration files
|
||||
log "Backing up configuration files..."
|
||||
if [ -f "${COMPOSE_FILE}" ]; then
|
||||
cp "${COMPOSE_FILE}" "${BACKUP_PATH}/docker-compose.yml"
|
||||
log " ✓ docker-compose.yml"
|
||||
fi
|
||||
|
||||
if [ -f "${IMMICH_DIR}/.env" ]; then
|
||||
cp "${IMMICH_DIR}/.env" "${BACKUP_PATH}/.env"
|
||||
log " ✓ .env"
|
||||
fi
|
||||
|
||||
# Backup any custom config files
|
||||
if [ -d "${IMMICH_DIR}/config" ]; then
|
||||
tar -czf "${BACKUP_PATH}/config.tar.gz" -C "${IMMICH_DIR}" config 2>/dev/null && log " ✓ config directory" || true
|
||||
fi
|
||||
|
||||
# 4. Create backup manifest (matching mailcow style)
|
||||
log "Creating backup manifest..."
|
||||
cat > "${BACKUP_PATH}/manifest.txt" << EOF
|
||||
Immich Backup Manifest
|
||||
Created: ${BACKUP_DATE}
|
||||
Hostname: $(hostname)
|
||||
Immich Directory: ${IMMICH_DIR}
|
||||
Project Name: ${PROJECT_NAME}
|
||||
|
||||
Backup Contents:
|
||||
$(ls -lh "${BACKUP_PATH}" 2>/dev/null)
|
||||
|
||||
Component Sizes:
|
||||
Database: $(du -h "${BACKUP_PATH}/database.sql.gz" 2>/dev/null | cut -f1 || echo "N/A")
|
||||
Total Backup Size: $(du -sh "${BACKUP_PATH}" 2>/dev/null | cut -f1)
|
||||
|
||||
Docker Volumes Backed Up:
|
||||
${DOCKER_VOLUMES:-None (using bind mounts)}
|
||||
EOF
|
||||
|
||||
# 5. Calculate checksums
|
||||
log "Calculating checksums..."
|
||||
cd "${BACKUP_PATH}"
|
||||
find . -type f -name "*.tar.gz" -o -name "*.sql.gz" | xargs sha256sum > checksums.sha256 2>/dev/null || warn "Checksum creation failed"
|
||||
cd - > /dev/null
|
||||
|
||||
TIER1_SIZE=$(du -sh "${BACKUP_PATH}" | cut -f1)
|
||||
log "Tier 1 (Local) backup completed! Size: ${TIER1_SIZE}"
|
||||
|
||||
# 6. Tier 2: Create Kopia snapshot for offsite backup
|
||||
if [ "$ENABLE_KOPIA" = true ]; then
|
||||
log "========================================"
|
||||
log "Starting Tier 2 (Offsite) Kopia backup"
|
||||
log "========================================"
|
||||
|
||||
# Check if kopia is available
|
||||
if ! command -v kopia &> /dev/null; then
|
||||
warn "Kopia not found. Skipping offsite backup."
|
||||
warn "Install kopia or set ENABLE_KOPIA=false"
|
||||
else
|
||||
# Snapshot the backup directory
|
||||
log "Creating Kopia snapshot of backup directory..."
|
||||
kopia snapshot create "${BACKUP_DIR}" \
|
||||
--tags "${KOPIA_TAGS}" \
|
||||
--description "Immich backup ${BACKUP_DATE}" \
|
||||
2>&1 | tee -a "$LOG_FILE"
|
||||
|
||||
KOPIA_EXIT=${PIPESTATUS[0]}
|
||||
|
||||
if [ $KOPIA_EXIT -eq 0 ]; then
|
||||
log "Kopia snapshot completed successfully"
|
||||
|
||||
# Also snapshot the Immich installation directory (configs)
|
||||
log "Creating Kopia snapshot of installation directory..."
|
||||
kopia snapshot create "${IMMICH_DIR}" \
|
||||
--tags "immich,config,docker-compose" \
|
||||
--description "Immich config ${BACKUP_DATE}" \
|
||||
2>&1 | tee -a "$LOG_FILE"
|
||||
else
|
||||
warn "Kopia snapshot failed with exit code ${KOPIA_EXIT}"
|
||||
warn "Local backup exists but offsite copy may be incomplete"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 7. Cleanup old backups (retention policy)
|
||||
log "========================================"
|
||||
log "Applying retention policy (${RETENTION_DAYS} days)"
|
||||
log "========================================"
|
||||
|
||||
# Find and remove old local backups
|
||||
REMOVED_COUNT=0
|
||||
while IFS= read -r old_backup; do
|
||||
log "Removing old backup: $(basename "$old_backup")"
|
||||
rm -rf "$old_backup"
|
||||
((REMOVED_COUNT++))
|
||||
done < <(find "${BACKUP_DIR}" -maxdepth 1 -type d -name "immich-*" -mtime +${RETENTION_DAYS} 2>/dev/null)
|
||||
|
||||
if [ $REMOVED_COUNT -gt 0 ]; then
|
||||
log "Removed ${REMOVED_COUNT} old backup(s)"
|
||||
else
|
||||
log "No old backups to remove"
|
||||
fi
|
||||
|
||||
# 8. Final summary
|
||||
log "========================================"
|
||||
log "Backup Summary"
|
||||
log "========================================"
|
||||
log "Tier 1 (Local): ${TIER1_SIZE} in ${BACKUP_PATH}"
|
||||
log "Tier 2 (Offsite): Kopia snapshot created"
|
||||
log "Retention: Keeping last ${RETENTION_DAYS} days locally"
|
||||
log "Log file: ${LOG_FILE}"
|
||||
log "========================================"
|
||||
log "Backup completed successfully!"
|
||||
log "========================================"
|
||||
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue