506
scripts/cloud_backup_restore.sh
Executable file
506
scripts/cloud_backup_restore.sh
Executable file
@@ -0,0 +1,506 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Cloud Backup Restore Script
|
||||
#
|
||||
# Restores the full media-downloader + Immich stack from a B2 cloud backup.
|
||||
# Run on a fresh Ubuntu 24.04 server (or the same machine after failure).
|
||||
#
|
||||
# Usage:
|
||||
# sudo bash cloud_backup_restore.sh [--rclone-conf /path/to/rclone.conf]
|
||||
#
|
||||
# Prerequisites on a fresh machine:
|
||||
# apt update && apt install -y rclone
|
||||
# Then place your rclone.conf at /root/.config/rclone/rclone.conf
|
||||
# (contains cloud-backup-remote + cloud-backup-crypt sections)
|
||||
#
|
||||
# The script is interactive — it will ask before each destructive step.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Configuration ──────────────────────────────────────────────────────────
|
||||
|
||||
IMMICH_BASE="/opt/immich"
|
||||
APP_DIR="/opt/media-downloader"
|
||||
RCLONE_CONF="${1:---rclone-conf}"
|
||||
RESTORE_TMP="/tmp/cloud-backup-restore"
|
||||
LOG_FILE="/tmp/cloud_backup_restore.log"
|
||||
|
||||
# If --rclone-conf was passed, grab the value
|
||||
if [[ "${1:-}" == "--rclone-conf" ]]; then
|
||||
RCLONE_CONF_PATH="${2:-/root/.config/rclone/rclone.conf}"
|
||||
else
|
||||
RCLONE_CONF_PATH="/root/.config/rclone/rclone.conf"
|
||||
fi
|
||||
|
||||
RCLONE_CRYPT="cloud-backup-crypt"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# ── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
log() { echo -e "${GREEN}[$(date '+%H:%M:%S')]${NC} $*" | tee -a "$LOG_FILE"; }
|
||||
warn() { echo -e "${YELLOW}[$(date '+%H:%M:%S')] WARNING:${NC} $*" | tee -a "$LOG_FILE"; }
|
||||
err() { echo -e "${RED}[$(date '+%H:%M:%S')] ERROR:${NC} $*" | tee -a "$LOG_FILE"; }
|
||||
step() { echo -e "\n${BLUE}━━━ $* ━━━${NC}" | tee -a "$LOG_FILE"; }
|
||||
|
||||
confirm() {
|
||||
local msg="$1"
|
||||
echo -en "${YELLOW}$msg [y/N]: ${NC}"
|
||||
read -r answer
|
||||
[[ "$answer" =~ ^[Yy]$ ]]
|
||||
}
|
||||
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
err "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Pre-flight checks ─────────────────────────────────────────────────────
|
||||
|
||||
preflight() {
|
||||
step "Pre-flight checks"
|
||||
|
||||
check_root
|
||||
|
||||
# Check rclone
|
||||
if ! command -v rclone &>/dev/null; then
|
||||
err "rclone not installed. Install with: apt install -y rclone"
|
||||
exit 1
|
||||
fi
|
||||
log "rclone: $(rclone --version | head -1)"
|
||||
|
||||
# Check rclone config
|
||||
if [[ ! -f "$RCLONE_CONF_PATH" ]]; then
|
||||
err "rclone config not found at $RCLONE_CONF_PATH"
|
||||
echo "You need the rclone.conf with [cloud-backup-remote] and [cloud-backup-crypt] sections."
|
||||
echo "If restoring to a new machine, copy rclone.conf from your backup records."
|
||||
exit 1
|
||||
fi
|
||||
log "rclone config: $RCLONE_CONF_PATH"
|
||||
|
||||
# Test remote connection
|
||||
log "Testing remote connection..."
|
||||
if rclone lsd "${RCLONE_CRYPT}:" --config "$RCLONE_CONF_PATH" --max-depth 1 &>/dev/null; then
|
||||
log "Remote connection: OK"
|
||||
else
|
||||
err "Cannot connect to remote. Check your rclone config and encryption passwords."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Show what's available on remote
|
||||
log "Remote directories:"
|
||||
rclone lsd "${RCLONE_CRYPT}:" --config "$RCLONE_CONF_PATH" --max-depth 1 2>/dev/null | tee -a "$LOG_FILE"
|
||||
|
||||
mkdir -p "$RESTORE_TMP"
|
||||
}
|
||||
|
||||
# ── Step 1: Download app_backup and db_dumps first ────────────────────────
|
||||
|
||||
download_configs() {
|
||||
step "Step 1: Downloading app_backup and db_dumps from remote"
|
||||
|
||||
mkdir -p "$RESTORE_TMP/app_backup" "$RESTORE_TMP/db_dumps"
|
||||
|
||||
log "Downloading app_backup..."
|
||||
rclone copy "${RCLONE_CRYPT}:app_backup" "$RESTORE_TMP/app_backup" \
|
||||
--config "$RCLONE_CONF_PATH" --progress 2>&1 | tee -a "$LOG_FILE"
|
||||
|
||||
log "Downloading db_dumps..."
|
||||
rclone copy "${RCLONE_CRYPT}:db_dumps" "$RESTORE_TMP/db_dumps" \
|
||||
--config "$RCLONE_CONF_PATH" --progress 2>&1 | tee -a "$LOG_FILE"
|
||||
|
||||
# Verify we got the essentials
|
||||
if [[ ! -f "$RESTORE_TMP/app_backup/media-downloader-app.tar.gz" ]]; then
|
||||
err "media-downloader-app.tar.gz not found in backup!"
|
||||
exit 1
|
||||
fi
|
||||
log "App archive size: $(du -sh "$RESTORE_TMP/app_backup/media-downloader-app.tar.gz" | cut -f1)"
|
||||
|
||||
ls -la "$RESTORE_TMP/db_dumps/" | tee -a "$LOG_FILE"
|
||||
ls -la "$RESTORE_TMP/app_backup/" | tee -a "$LOG_FILE"
|
||||
ls -la "$RESTORE_TMP/app_backup/systemd/" 2>/dev/null | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# ── Step 2: Install system dependencies ───────────────────────────────────
|
||||
|
||||
install_dependencies() {
|
||||
step "Step 2: Install system dependencies"
|
||||
|
||||
if ! confirm "Install system packages (python3, postgresql, docker, node, etc.)?"; then
|
||||
warn "Skipping dependency installation"
|
||||
return
|
||||
fi
|
||||
|
||||
log "Updating package lists..."
|
||||
apt update
|
||||
|
||||
log "Installing core packages..."
|
||||
apt install -y \
|
||||
python3 python3-venv python3-pip python3-dev \
|
||||
postgresql postgresql-client \
|
||||
docker.io docker-compose-v2 \
|
||||
nodejs npm \
|
||||
rclone \
|
||||
xvfb \
|
||||
python3-pyinotify \
|
||||
nginx \
|
||||
git curl wget jq \
|
||||
build-essential libffi-dev libssl-dev \
|
||||
libgl1-mesa-glx libglib2.0-0 \
|
||||
ffmpeg \
|
||||
2>&1 | tee -a "$LOG_FILE"
|
||||
|
||||
# Enable and start essential services
|
||||
systemctl enable --now docker
|
||||
systemctl enable --now postgresql
|
||||
|
||||
log "System dependencies installed"
|
||||
}
|
||||
|
||||
# ── Step 3: Restore media-downloader application ──────────────────────────
|
||||
|
||||
restore_app() {
|
||||
step "Step 3: Restore media-downloader application"
|
||||
|
||||
if [[ -d "$APP_DIR" ]]; then
|
||||
if confirm "$APP_DIR already exists. Back it up and replace?"; then
|
||||
local backup_name="${APP_DIR}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||
log "Moving existing app to $backup_name"
|
||||
mv "$APP_DIR" "$backup_name"
|
||||
else
|
||||
warn "Skipping app restore"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Extracting media-downloader app..."
|
||||
mkdir -p /opt
|
||||
tar xzf "$RESTORE_TMP/app_backup/media-downloader-app.tar.gz" -C /opt
|
||||
log "App extracted to $APP_DIR"
|
||||
|
||||
# Recreate venv
|
||||
log "Creating Python virtual environment..."
|
||||
python3 -m venv "$APP_DIR/venv"
|
||||
|
||||
log "Installing Python dependencies (this may take a while)..."
|
||||
"$APP_DIR/venv/bin/pip" install --upgrade pip wheel 2>&1 | tail -3 | tee -a "$LOG_FILE"
|
||||
"$APP_DIR/venv/bin/pip" install -r "$APP_DIR/requirements.txt" 2>&1 | tail -10 | tee -a "$LOG_FILE"
|
||||
log "Python dependencies installed"
|
||||
|
||||
# Rebuild frontend
|
||||
log "Installing frontend dependencies..."
|
||||
cd "$APP_DIR/web/frontend"
|
||||
npm install 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
log "Building frontend..."
|
||||
npx tsc && npx vite build 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
log "Frontend built"
|
||||
|
||||
# Install Playwright browsers
|
||||
log "Installing Playwright browsers..."
|
||||
"$APP_DIR/venv/bin/python3" -m playwright install chromium firefox 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
"$APP_DIR/venv/bin/python3" -m playwright install-deps 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
|
||||
# Create required directories
|
||||
mkdir -p "$APP_DIR/logs" "$APP_DIR/temp" "$APP_DIR/cache/thumbnails"
|
||||
|
||||
log "Application restored"
|
||||
}
|
||||
|
||||
# ── Step 4: Restore Immich ────────────────────────────────────────────────
|
||||
|
||||
restore_immich() {
|
||||
step "Step 4: Restore Immich configuration"
|
||||
|
||||
mkdir -p "$IMMICH_BASE"
|
||||
|
||||
# Restore docker-compose and .env
|
||||
if [[ -f "$RESTORE_TMP/app_backup/immich-docker-compose.yml" ]]; then
|
||||
cp "$RESTORE_TMP/app_backup/immich-docker-compose.yml" "$IMMICH_BASE/docker-compose.yml"
|
||||
log "Restored Immich docker-compose.yml"
|
||||
fi
|
||||
if [[ -f "$RESTORE_TMP/app_backup/immich-env" ]]; then
|
||||
cp "$RESTORE_TMP/app_backup/immich-env" "$IMMICH_BASE/.env"
|
||||
log "Restored Immich .env"
|
||||
fi
|
||||
|
||||
# Create required directories
|
||||
mkdir -p "$IMMICH_BASE/upload" "$IMMICH_BASE/db" "$IMMICH_BASE/db_dumps" "$IMMICH_BASE/app_backup"
|
||||
|
||||
log "Immich config restored. Media files will be synced in Step 7."
|
||||
}
|
||||
|
||||
# ── Step 5: Restore databases ─────────────────────────────────────────────
|
||||
|
||||
restore_databases() {
|
||||
step "Step 5: Restore databases"
|
||||
|
||||
# Media Downloader PostgreSQL (supports both .dump and legacy .sql)
|
||||
# Media Downloader PostgreSQL (supports directory dump, .dump, and legacy .sql)
|
||||
local md_dir="$RESTORE_TMP/db_dumps/media_downloader_dump"
|
||||
local md_dump="$RESTORE_TMP/db_dumps/media_downloader.dump"
|
||||
local md_sql="$RESTORE_TMP/db_dumps/media_downloader.sql"
|
||||
if [[ -d "$md_dir" || -f "$md_dump" || -f "$md_sql" ]]; then
|
||||
if confirm "Restore media_downloader PostgreSQL database?"; then
|
||||
log "Creating media_downloader database and user..."
|
||||
sudo -u postgres psql -c "CREATE USER media_downloader WITH PASSWORD 'PNsihOXvvuPwWiIvGlsc9Fh2YmMmB';" 2>/dev/null || true
|
||||
sudo -u postgres psql -c "DROP DATABASE IF EXISTS media_downloader;" 2>/dev/null || true
|
||||
sudo -u postgres psql -c "CREATE DATABASE media_downloader OWNER media_downloader;" 2>/dev/null || true
|
||||
|
||||
if [[ -d "$md_dir" ]]; then
|
||||
log "Importing media_downloader dump (parallel directory format)..."
|
||||
PGPASSWORD=PNsihOXvvuPwWiIvGlsc9Fh2YmMmB pg_restore -h localhost -U media_downloader \
|
||||
-d media_downloader --no-owner --no-acl -j 4 "$md_dir" 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
elif [[ -f "$md_dump" ]]; then
|
||||
log "Importing media_downloader dump (custom format)..."
|
||||
PGPASSWORD=PNsihOXvvuPwWiIvGlsc9Fh2YmMmB pg_restore -h localhost -U media_downloader \
|
||||
-d media_downloader --no-owner --no-acl "$md_dump" 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
else
|
||||
log "Importing media_downloader dump (SQL format)..."
|
||||
PGPASSWORD=PNsihOXvvuPwWiIvGlsc9Fh2YmMmB psql -h localhost -U media_downloader \
|
||||
-d media_downloader < "$md_sql" 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
fi
|
||||
log "media_downloader database restored"
|
||||
fi
|
||||
else
|
||||
warn "media_downloader dump not found in backup"
|
||||
fi
|
||||
|
||||
# Immich PostgreSQL (supports .tar directory dump, .dump, and legacy .sql)
|
||||
local im_tar="$RESTORE_TMP/db_dumps/immich_dump.tar"
|
||||
local im_dump="$RESTORE_TMP/db_dumps/immich.dump"
|
||||
local im_sql="$RESTORE_TMP/db_dumps/immich.sql"
|
||||
if [[ -f "$im_tar" || -f "$im_dump" || -f "$im_sql" ]]; then
|
||||
if confirm "Restore Immich PostgreSQL database? (starts Immich containers first)"; then
|
||||
log "Starting Immich database container..."
|
||||
cd "$IMMICH_BASE"
|
||||
docker compose up -d database 2>&1 | tee -a "$LOG_FILE"
|
||||
|
||||
log "Waiting for Immich PostgreSQL to be ready..."
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec immich_postgres pg_isready -U postgres &>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [[ -f "$im_tar" ]]; then
|
||||
log "Importing Immich dump (parallel directory format)..."
|
||||
docker cp "$im_tar" immich_postgres:/tmp/immich_dump.tar
|
||||
docker exec immich_postgres sh -c "cd /tmp && tar xf immich_dump.tar"
|
||||
docker exec immich_postgres pg_restore -U postgres -d immich \
|
||||
--no-owner --no-acl -j 4 /tmp/immich_dump 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
docker exec immich_postgres sh -c "rm -rf /tmp/immich_dump /tmp/immich_dump.tar"
|
||||
elif [[ -f "$im_dump" ]]; then
|
||||
log "Importing Immich dump (custom format)..."
|
||||
docker cp "$im_dump" immich_postgres:/tmp/immich.dump
|
||||
docker exec immich_postgres pg_restore -U postgres -d immich \
|
||||
--no-owner --no-acl /tmp/immich.dump 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
docker exec immich_postgres rm -f /tmp/immich.dump
|
||||
else
|
||||
log "Importing Immich dump (SQL format)..."
|
||||
docker exec -i immich_postgres psql -U postgres -d immich \
|
||||
< "$im_sql" 2>&1 | tail -5 | tee -a "$LOG_FILE"
|
||||
fi
|
||||
log "Immich database restored"
|
||||
fi
|
||||
else
|
||||
warn "immich dump not found in backup"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Step 6: Restore systemd services & rclone config ─────────────────────
|
||||
|
||||
restore_services() {
|
||||
step "Step 6: Restore systemd services and configs"
|
||||
|
||||
# Systemd service files
|
||||
if [[ -d "$RESTORE_TMP/app_backup/systemd" ]]; then
|
||||
if confirm "Install systemd service files?"; then
|
||||
for svc in "$RESTORE_TMP/app_backup/systemd/"*; do
|
||||
local name=$(basename "$svc")
|
||||
cp "$svc" "/etc/systemd/system/$name"
|
||||
log "Installed $name"
|
||||
done
|
||||
systemctl daemon-reload
|
||||
log "systemd reloaded"
|
||||
fi
|
||||
fi
|
||||
|
||||
# rclone config
|
||||
if [[ -f "$RESTORE_TMP/app_backup/rclone.conf" ]]; then
|
||||
if confirm "Restore rclone.conf?"; then
|
||||
mkdir -p /root/.config/rclone
|
||||
cp "$RESTORE_TMP/app_backup/rclone.conf" /root/.config/rclone/rclone.conf
|
||||
chmod 600 /root/.config/rclone/rclone.conf
|
||||
log "rclone.conf restored"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Step 7: Download media files ──────────────────────────────────────────
|
||||
|
||||
restore_media() {
|
||||
step "Step 7: Download media files from remote"
|
||||
|
||||
local dirs=$(rclone lsd "${RCLONE_CRYPT}:" --config "$RCLONE_CONF_PATH" --max-depth 1 2>/dev/null | awk '{print $NF}')
|
||||
|
||||
echo ""
|
||||
log "Available remote directories:"
|
||||
echo "$dirs" | while read -r d; do echo " - $d"; done
|
||||
|
||||
if ! confirm "Download all media directories? (This may take a long time for large backups)"; then
|
||||
warn "Skipping media download. You can sync manually later with:"
|
||||
echo " rclone copy ${RCLONE_CRYPT}:<dir> ${IMMICH_BASE}/<dir> --config $RCLONE_CONF_PATH --progress --transfers 4"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "$dirs" | while read -r dir_name; do
|
||||
# Skip dirs we already downloaded
|
||||
[[ "$dir_name" == "app_backup" || "$dir_name" == "db_dumps" ]] && continue
|
||||
[[ -z "$dir_name" ]] && continue
|
||||
|
||||
log "Syncing $dir_name..."
|
||||
mkdir -p "$IMMICH_BASE/$dir_name"
|
||||
rclone copy "${RCLONE_CRYPT}:${dir_name}" "$IMMICH_BASE/$dir_name" \
|
||||
--config "$RCLONE_CONF_PATH" \
|
||||
--progress \
|
||||
--transfers 4 \
|
||||
--checkers 8 \
|
||||
2>&1 | tee -a "$LOG_FILE"
|
||||
log "$dir_name done"
|
||||
done
|
||||
|
||||
log "Media files restored"
|
||||
}
|
||||
|
||||
# ── Step 8: Start services ───────────────────────────────────────────────
|
||||
|
||||
start_services() {
|
||||
step "Step 8: Start services"
|
||||
|
||||
if confirm "Start all services?"; then
|
||||
# Start Immich stack
|
||||
log "Starting Immich..."
|
||||
cd "$IMMICH_BASE"
|
||||
docker compose up -d 2>&1 | tee -a "$LOG_FILE"
|
||||
|
||||
# Enable and start media-downloader services
|
||||
log "Starting media-downloader services..."
|
||||
systemctl enable --now xvfb-media-downloader.service 2>/dev/null || true
|
||||
systemctl enable --now media-downloader-api.service
|
||||
systemctl enable --now media-downloader.service
|
||||
systemctl enable --now media-downloader-frontend.service 2>/dev/null || true
|
||||
systemctl enable --now media-downloader-db-cleanup.timer 2>/dev/null || true
|
||||
systemctl enable --now cloud-backup-sync.service
|
||||
|
||||
sleep 5
|
||||
|
||||
# Status check
|
||||
log "Service status:"
|
||||
for svc in media-downloader-api media-downloader cloud-backup-sync; do
|
||||
local status=$(systemctl is-active "$svc" 2>/dev/null || echo "not found")
|
||||
if [[ "$status" == "active" ]]; then
|
||||
echo -e " ${GREEN}●${NC} $svc: $status"
|
||||
else
|
||||
echo -e " ${RED}●${NC} $svc: $status"
|
||||
fi
|
||||
done
|
||||
|
||||
# Docker containers
|
||||
log "Docker containers:"
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Step 9: Post-restore verification ─────────────────────────────────────
|
||||
|
||||
verify() {
|
||||
step "Step 9: Post-restore verification"
|
||||
|
||||
local issues=0
|
||||
|
||||
# Check API
|
||||
if curl -sf http://localhost:8000/api/health &>/dev/null; then
|
||||
log "API health check: OK"
|
||||
else
|
||||
warn "API health check: FAILED (may still be starting)"
|
||||
((issues++))
|
||||
fi
|
||||
|
||||
# Check Immich
|
||||
if curl -sf http://localhost:2283/api/server-info/ping &>/dev/null; then
|
||||
log "Immich health check: OK"
|
||||
else
|
||||
warn "Immich health check: FAILED (may still be starting)"
|
||||
((issues++))
|
||||
fi
|
||||
|
||||
# Check database
|
||||
if PGPASSWORD=PNsihOXvvuPwWiIvGlsc9Fh2YmMmB psql -h localhost -U media_downloader -d media_downloader -c "SELECT 1" &>/dev/null; then
|
||||
log "Media Downloader DB: OK"
|
||||
else
|
||||
warn "Media Downloader DB: FAILED"
|
||||
((issues++))
|
||||
fi
|
||||
|
||||
# Disk usage
|
||||
log "Disk usage:"
|
||||
df -h /opt/immich /opt/media-downloader 2>/dev/null | tee -a "$LOG_FILE"
|
||||
|
||||
echo ""
|
||||
if [[ $issues -eq 0 ]]; then
|
||||
log "${GREEN}Restore completed successfully!${NC}"
|
||||
else
|
||||
warn "Restore completed with $issues issue(s). Check the log: $LOG_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log "Restore log saved to: $LOG_FILE"
|
||||
}
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
echo -e "${BLUE}"
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Cloud Backup Restore — Media Downloader ║"
|
||||
echo "║ ║"
|
||||
echo "║ Restores: App, Databases, Media, Configs, Services ║"
|
||||
echo "║ Source: Backblaze B2 (rclone crypt encrypted) ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo -e "${NC}"
|
||||
|
||||
echo "This script will restore your media-downloader + Immich stack"
|
||||
echo "from an encrypted B2 cloud backup. Each step asks for confirmation."
|
||||
echo ""
|
||||
echo "Log: $LOG_FILE"
|
||||
echo ""
|
||||
|
||||
if ! confirm "Ready to begin restore?"; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "" > "$LOG_FILE"
|
||||
|
||||
preflight
|
||||
download_configs
|
||||
install_dependencies
|
||||
restore_app
|
||||
restore_immich
|
||||
restore_databases
|
||||
restore_services
|
||||
restore_media
|
||||
start_services
|
||||
verify
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user