fix: restore script

This commit is contained in:
2026-06-06 15:10:39 +01:00
parent 715993af46
commit a7c3e3e7fe
2 changed files with 51 additions and 139 deletions

View File

@@ -746,7 +746,8 @@ def restore_start():
if target == 'local': if target == 'local':
hostname = os.uname().nodename hostname = os.uname().nodename
session_dir = f"/tmp/restore-session-{uuid.uuid4().hex[:8]}" os.makedirs('/root/tmp', exist_ok=True)
session_dir = f"/root/tmp/restore-session-{uuid.uuid4().hex[:8]}"
cmd = ( cmd = (
f"set -e && " f"set -e && "
f"echo 'Restoring on this server ({hostname})...' && " f"echo 'Restoring on this server ({hostname})...' && "

View File

@@ -1,36 +1,12 @@
#!/bin/bash #!/bin/bash
# ============================================= # =============================================
# restore-myapps.sh — Smart Restore Script # restore-myapps.sh — Smart Restore Script
#
# Can run:
# - Locally on the server/vm:
# ./restore-myapps.sh
#
# - Remotely (from VM targeting the main server, or vice versa):
# ./restore-myapps.sh --remote <IP> <USER> [--key /path/to/key | --password]
#
# Features:
# - Skips containers that are already healthy/running
# - Applies all known post-restore fixes per app
# - Detects target IP automatically or uses provided one
# - Works whether run locally or proxied over SSH
# - Works inside Docker containers (no ip command needed)
# - Supports both docker-compose and docker compose
# ============================================= # =============================================
set -uo pipefail set -uo pipefail
# -------------------------------------------------- # docker compose v2 compat
# Compatibility fixes — must come first command -v docker-compose &>/dev/null || docker-compose() { docker compose "$@"; }
# --------------------------------------------------
# docker compose v2 compat (fixes "docker-compose: command not found")
if ! command -v docker-compose &>/dev/null; then
if docker compose version &>/dev/null 2>&1; then
docker-compose() { docker compose "$@"; }
export -f docker-compose
fi
fi
# -------------------------------------------------- # --------------------------------------------------
# Parse arguments # Parse arguments
@@ -39,7 +15,6 @@ REMOTE_MODE=false
REMOTE_IP="" REMOTE_IP=""
REMOTE_USER="root" REMOTE_USER="root"
SSH_KEY="" SSH_KEY=""
SSH_PASSWORD=""
USE_PASSWORD=false USE_PASSWORD=false
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
@@ -65,12 +40,11 @@ while [[ $# -gt 0 ]]; do
done done
# -------------------------------------------------- # --------------------------------------------------
# If remote mode: copy this script + backup to target and run it there # Remote mode
# -------------------------------------------------- # --------------------------------------------------
if [ "$REMOTE_MODE" = true ]; then if [ "$REMOTE_MODE" = true ]; then
if [ -z "$REMOTE_IP" ]; then if [ -z "$REMOTE_IP" ]; then
echo "❌ --remote requires an IP address." echo "❌ --remote requires an IP address."
echo " Usage: $0 --remote <IP> [USER] [--key /path/key]"
exit 1 exit 1
fi fi
@@ -87,7 +61,7 @@ if [ "$REMOTE_MODE" = true ]; then
fi fi
read -s -p "SSH password for ${REMOTE_USER}@${REMOTE_IP}: " SSH_PASS read -s -p "SSH password for ${REMOTE_USER}@${REMOTE_IP}: " SSH_PASS
echo "" echo ""
SSH_CMD="sshpass -p '$SSH_PASS' ssh $SSH_OPTS" SSH_CMD="sshpass -p '$SSH_PASS' ssh $SSH_OPTS ${REMOTE_USER}@${REMOTE_IP}"
SCP_CMD="sshpass -p '$SSH_PASS' scp $SSH_OPTS" SCP_CMD="sshpass -p '$SSH_PASS' scp $SSH_OPTS"
fi fi
@@ -100,66 +74,28 @@ if [ "$REMOTE_MODE" = true ]; then
echo "=========================================" echo "========================================="
echo "📡 REMOTE RESTORE MODE" echo "📡 REMOTE RESTORE MODE"
echo " Target: ${REMOTE_USER}@${REMOTE_IP}" echo " Target: ${REMOTE_USER}@${REMOTE_IP}"
echo " Backup: $SCRIPT_DIR"
echo "=========================================" echo "========================================="
echo ""
echo "📤 Copying backup to remote server..."
$SSH_CMD "mkdir -p $REMOTE_DEST" $SSH_CMD "mkdir -p $REMOTE_DEST"
$SCP_CMD -r "$SCRIPT_DIR/." "${REMOTE_USER}@${REMOTE_IP}:${REMOTE_DEST}/" $SCP_CMD -r "$SCRIPT_DIR/." "${REMOTE_USER}@${REMOTE_IP}:${REMOTE_DEST}/"
if [ $? -ne 0 ]; then
echo "❌ Failed to copy backup to remote server."
exit 1
fi
echo "✅ Backup copied."
echo ""
echo "🚀 Running restore on remote server..."
$SSH_CMD "chmod +x $REMOTE_DEST/restore-myapps.sh && cd $REMOTE_DEST && bash restore-myapps.sh" $SSH_CMD "chmod +x $REMOTE_DEST/restore-myapps.sh && cd $REMOTE_DEST && bash restore-myapps.sh"
echo ""
echo "🧹 Cleaning up remote temp files..."
$SSH_CMD "rm -rf $REMOTE_DEST" $SSH_CMD "rm -rf $REMOTE_DEST"
echo "========================================="
echo "✅ Remote restore complete on $REMOTE_IP" echo "✅ Remote restore complete on $REMOTE_IP"
echo "========================================="
exit 0 exit 0
fi fi
# =================================================== # ===================================================
# LOCAL RESTORE (runs directly on the target machine) # LOCAL RESTORE
# =================================================== # ===================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
# -------------------------------------------------- # Detect IP — works inside containers and on host
# Detect IP — works inside containers too
# --------------------------------------------------
VM_IP=""
# Try hostname -I first (works inside containers)
if command -v hostname &>/dev/null; then
VM_IP=$(hostname -I 2>/dev/null | awk '{print $1}') VM_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
fi [ -z "$VM_IP" ] && VM_IP=$(ip -4 addr show 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127.0.0.1 | head -1)
[ -z "$VM_IP" ] && VM_IP="173.249.20.244"
# Fallback: try ip command
if [ -z "$VM_IP" ] && command -v ip &>/dev/null; then
VM_IP=$(ip -4 addr show 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127.0.0.1 | head -1)
fi
# Fallback: try /proc/net/fib_trie (works in minimal containers)
if [ -z "$VM_IP" ] && [ -f /proc/net/fib_trie ]; then
VM_IP=$(awk '/32 HOST/{print f} {f=$2}' /proc/net/fib_trie 2>/dev/null | grep -v 127.0.0.1 | head -1)
fi
# Final fallback: use known main server IP
if [ -z "$VM_IP" ]; then
VM_IP="173.249.20.244"
echo " ⚠️ Could not detect IP, defaulting to $VM_IP"
fi
echo "=========================================" echo "========================================="
echo "🔄 Smart Restore — LOCAL MODE" echo "🔄 Smart Restore — LOCAL MODE"
@@ -167,25 +103,20 @@ echo " Machine IP: $VM_IP"
echo " Backup dir: $SCRIPT_DIR" echo " Backup dir: $SCRIPT_DIR"
echo "=========================================" echo "========================================="
# --------------------------------------------------
# Helper: check if a container is healthy/running
# Returns 0 (true) if container should be skipped
# --------------------------------------------------
container_is_healthy() { container_is_healthy() {
local name="$1" local name="$1"
local status local status
status=$(docker inspect --format='{{.State.Status}}' "$name" 2>/dev/null || echo "missing") status=$(docker inspect --format='{{.State.Status}}' "$name" 2>/dev/null || echo "missing")
local health local health
health=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$name" 2>/dev/null || echo "none") health=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$name" 2>/dev/null || echo "none")
if [ "$status" = "running" ] && { [ "$health" = "healthy" ] || [ "$health" = "none" ]; }; then if [ "$status" = "running" ] && { [ "$health" = "healthy" ] || [ "$health" = "none" ]; }; then
return 0 # healthy → skip return 0
fi fi
return 1 # not healthy → restore return 1
} }
# -------------------------------------------------- # --------------------------------------------------
# 1. Restore volumes — skip if container using it is healthy # STEP 1: Restore Volumes
# -------------------------------------------------- # --------------------------------------------------
echo "" echo ""
echo "=========================================" echo "========================================="
@@ -208,11 +139,10 @@ declare -A VOLUME_OWNERS=(
if [ -d "$SCRIPT_DIR/volumes" ]; then if [ -d "$SCRIPT_DIR/volumes" ]; then
cd "$SCRIPT_DIR/volumes" cd "$SCRIPT_DIR/volumes"
for backup in *.tar.gz; do for backup in *.tar.gz; do
[ -f "$backup" ] || { echo " ⚠️ No volume backups found in $SCRIPT_DIR/volumes"; break; } [ -f "$backup" ] || continue
volume=$(basename "$backup" .tar.gz) volume=$(basename "$backup" .tar.gz)
owner="${VOLUME_OWNERS[$volume]:-}" owner="${VOLUME_OWNERS[$volume]:-}"
# If the owning container is healthy, skip this volume
if [ -n "$owner" ] && container_is_healthy "$owner"; then if [ -n "$owner" ] && container_is_healthy "$owner"; then
echo " ⏭️ $volume — container '$owner' is healthy, skipping" echo " ⏭️ $volume — container '$owner' is healthy, skipping"
continue continue
@@ -229,11 +159,11 @@ if [ -d "$SCRIPT_DIR/volumes" ]; then
done done
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
else else
echo " ⚠️ No volumes/ directory found in backup — skipping volume restore" echo " ⚠️ No volumes/ directory found — skipping"
fi fi
# -------------------------------------------------- # --------------------------------------------------
# 2. Start containers — skip healthy ones # STEP 2: Start Containers
# -------------------------------------------------- # --------------------------------------------------
echo "" echo ""
echo "=========================================" echo "========================================="
@@ -270,7 +200,7 @@ if [ -d "$SCRIPT_DIR/compose-files" ]; then
if [ -d "$dir" ]; then if [ -d "$dir" ]; then
echo " 🚀 Starting $app..." echo " 🚀 Starting $app..."
cd "$dir" cd "$dir"
docker compose up -d 2>&1 | tail -3 docker-compose up -d 2>&1 | tail -3
cd .. cd ..
else else
echo " ⏭️ $app compose dir '$dir' not found, skipping" echo " ⏭️ $app compose dir '$dir' not found, skipping"
@@ -278,7 +208,7 @@ if [ -d "$SCRIPT_DIR/compose-files" ]; then
done done
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
else else
echo " ⚠️ No compose-files/ directory found — skipping container start" echo " ⚠️ No compose-files/ directory — skipping"
fi fi
echo "" echo ""
@@ -286,7 +216,7 @@ echo "⏳ Waiting 60s for containers to initialize..."
sleep 60 sleep 60
# -------------------------------------------------- # --------------------------------------------------
# 3. Post-restore fixes # STEP 3: Post-Restore Fixes
# -------------------------------------------------- # --------------------------------------------------
echo "" echo ""
echo "=========================================" echo "========================================="
@@ -306,16 +236,12 @@ else
docker exec nextcloud-app php /var/www/html/occ config:system:set trusted_domains 1 --value="$VM_IP" docker exec nextcloud-app php /var/www/html/occ config:system:set trusted_domains 1 --value="$VM_IP"
docker exec nextcloud-app php /var/www/html/occ config:system:set trusted_domains 2 --value="${VM_IP}:8082" docker exec nextcloud-app php /var/www/html/occ config:system:set trusted_domains 2 --value="${VM_IP}:8082"
docker exec nextcloud-app php /var/www/html/occ config:system:set trusted_domains 3 --value="localhost:8082" docker exec nextcloud-app php /var/www/html/occ config:system:set trusted_domains 3 --value="localhost:8082"
docker restart nextcloud-app
# Fix permissions for oc_admin on nextcloud tables
docker exec nextcloud-postgres psql -U nextcloud -d nextcloud -c " docker exec nextcloud-postgres psql -U nextcloud -d nextcloud -c "
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO oc_admin; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO oc_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO oc_admin; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO oc_admin;
" 2>/dev/null || true " 2>/dev/null || true
docker restart nextcloud-app docker restart nextcloud-app
echo " ✅ Nextcloud fixed"
echo " ✅ Nextcloud trusted domains fixed"
else else
echo " ⚠️ nextcloud-app not running" echo " ⚠️ nextcloud-app not running"
fi fi
@@ -346,7 +272,6 @@ EOF
docker exec mautic-app chown -R www-data:www-data /var/www/html/var /var/www/html/config 2>/dev/null || true docker exec mautic-app chown -R www-data:www-data /var/www/html/var /var/www/html/config 2>/dev/null || true
docker restart mautic-app docker restart mautic-app
sleep 10 sleep 10
HASH=$(docker exec mautic-app php -r "echo password_hash('Admin!Password123', PASSWORD_BCRYPT);" 2>/dev/null || true) HASH=$(docker exec mautic-app php -r "echo password_hash('Admin!Password123', PASSWORD_BCRYPT);" 2>/dev/null || true)
if [ -n "$HASH" ]; then if [ -n "$HASH" ]; then
docker exec mautic-mariadb mysql -uroot -pmautic_root_password \ docker exec mautic-mariadb mysql -uroot -pmautic_root_password \
@@ -362,17 +287,34 @@ fi
# ---- ODOO ---- # ---- ODOO ----
echo "" echo ""
echo "📌 Odoo — Assets + DB user..." echo "📌 Odoo — Assets + DB + Config..."
if container_is_healthy odoo-clean-odoo-1; then if container_is_healthy odoo-clean-odoo-1; then
echo " ⏭️ Odoo is healthy, skipping fix" echo " ⏭️ Odoo is healthy, skipping fix"
else else
if docker ps | grep -q odoo-clean-db-1; then if docker ps | grep -q odoo-clean-db-1; then
# Fix odoo.conf with proper settings
docker run --rm -v odoo-clean_odoo-etc:/etc/odoo alpine sh -c 'cat > /etc/odoo/odoo.conf << CONF
[options]
addons_path = /mnt/extra-addons
data_dir = /var/lib/odoo
db_host = db
db_user = odoo
db_password = odoo
db_name = odoo
workers = 4
max_cron_threads = 1
proxy_mode = True
assets_bundle_enabled = True
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
CONF' 2>/dev/null || true
docker exec odoo-clean-db-1 psql -U odoo -d odoo \ docker exec odoo-clean-db-1 psql -U odoo -d odoo \
-c "DELETE FROM ir_attachment WHERE url LIKE '/web/assets/%';" 2>/dev/null || true -c "DELETE FROM ir_attachment WHERE url LIKE '/web/assets/%';" 2>/dev/null || true
docker exec odoo-clean-db-1 psql -U odoo -d odoo \
-c "UPDATE ir_config_parameter SET value='https://odooo.nav.ovh' WHERE key='web.base.url';" 2>/dev/null || true
docker exec odoo-clean-db-1 psql -U odoo \ docker exec odoo-clean-db-1 psql -U odoo \
-c "ALTER USER odoo WITH PASSWORD 'odoo';" 2>/dev/null || true -c "ALTER USER odoo WITH PASSWORD 'odoo';" 2>/dev/null || true
docker exec odoo-clean-odoo-1 bash -c \
"grep -q 'filestore_check_missing' /etc/odoo/odoo.conf || echo 'filestore_check_missing = False' >> /etc/odoo/odoo.conf" 2>/dev/null || true
docker restart odoo-clean-odoo-1 docker restart odoo-clean-odoo-1
echo " ✅ Odoo fixed → http://${VM_IP}:8069/web" echo " ✅ Odoo fixed → http://${VM_IP}:8069/web"
else else
@@ -388,14 +330,10 @@ if container_is_healthy frappe-erpnext; then
else else
if docker ps | grep -q frappe-erpnext && docker ps | grep -q frappe-mariadb; then if docker ps | grep -q frappe-erpnext && docker ps | grep -q frappe-mariadb; then
SITE_CONFIG="/home/frappe/frappe-bench/sites/erpnext.navitrends.ovh/site_config.json" SITE_CONFIG="/home/frappe/frappe-bench/sites/erpnext.navitrends.ovh/site_config.json"
DB_NAME=$(docker exec frappe-erpnext cat "$SITE_CONFIG" 2>/dev/null \ DB_NAME=$(docker exec frappe-erpnext cat "$SITE_CONFIG" 2>/dev/null | grep -o '"db_name": *"[^"]*"' | cut -d'"' -f4)
| grep -o '"db_name": *"[^"]*"' | cut -d'"' -f4) DB_PASS=$(docker exec frappe-erpnext cat "$SITE_CONFIG" 2>/dev/null | grep -o '"db_password": *"[^"]*"' | cut -d'"' -f4)
DB_PASS=$(docker exec frappe-erpnext cat "$SITE_CONFIG" 2>/dev/null \
| grep -o '"db_password": *"[^"]*"' | cut -d'"' -f4)
if [ -n "$DB_NAME" ] && [ -n "$DB_PASS" ]; then if [ -n "$DB_NAME" ] && [ -n "$DB_PASS" ]; then
echo " DB: $DB_NAME"
docker exec frappe-mariadb mysql -uroot -p123 -e " docker exec frappe-mariadb mysql -uroot -p123 -e "
GRANT ALL PRIVILEGES ON *.* TO '${DB_NAME}'@'%' IDENTIFIED BY '${DB_PASS}' WITH GRANT OPTION; GRANT ALL PRIVILEGES ON *.* TO '${DB_NAME}'@'%' IDENTIFIED BY '${DB_PASS}' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON *.* TO '${DB_NAME}'@'172.%' IDENTIFIED BY '${DB_PASS}' WITH GRANT OPTION; GRANT ALL PRIVILEGES ON *.* TO '${DB_NAME}'@'172.%' IDENTIFIED BY '${DB_PASS}' WITH GRANT OPTION;
@@ -424,49 +362,22 @@ fi
# ---- N8N ---- # ---- N8N ----
echo "" echo ""
echo "📌 n8n — Network + volumes check..." echo "📌 n8n — Network check..."
if container_is_healthy n8n-app; then if container_is_healthy n8n-app; then
echo " ⏭️ n8n is healthy, skipping fix" echo " ⏭️ n8n is healthy, skipping fix"
else else
# Ensure network exists
docker network inspect integration-network &>/dev/null \ docker network inspect integration-network &>/dev/null \
|| { docker network create integration-network && echo " ✅ Created integration-network"; } || { docker network create integration-network && echo " ✅ Created integration-network"; }
# Check if n8n volumes exist in backup — restore them if not already done if ! docker ps | grep -q n8n-app; then
N8N_VOLUMES=("n8n-setup_n8n-data" "n8n-setup_n8n-db-data") if [ -d "$SCRIPT_DIR/compose-files/n8n-setup" ]; then
for vol in "${N8N_VOLUMES[@]}"; do cd "$SCRIPT_DIR/compose-files/n8n-setup"
vol_backup="$SCRIPT_DIR/volumes/${vol}.tar.gz" docker-compose up -d 2>&1 | tail -3
if [ -f "$vol_backup" ]; then
echo -n " 📁 Restoring $vol ... "
docker volume create "$vol" &>/dev/null || true
docker run --rm \
-v "${vol}:/target" \
-v "$SCRIPT_DIR/volumes:/backup:ro" \
alpine \
sh -c "cd /target && tar xzf /backup/${vol}.tar.gz" \
&& echo "✅" || echo "⚠️ FAILED"
else
echo " ⚠️ $vol backup not found in this archive — n8n will start fresh"
docker volume create "$vol" &>/dev/null || true
fi
done
# Start n8n via compose if compose file exists
N8N_COMPOSE_DIR="$SCRIPT_DIR/compose-files/n8n-setup"
if [ -d "$N8N_COMPOSE_DIR" ]; then
cd "$N8N_COMPOSE_DIR"
docker compose up -d 2>&1 | tail -3
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
else else
# Fallback: check if n8n-app container exists but is stopped docker start n8n-app 2>/dev/null && echo " ✅ Started n8n-app" || echo " ⚠️ Could not start n8n"
if docker ps -a | grep -q n8n-app; then
docker start n8n-app && echo " ✅ Started existing n8n-app container"
else
echo " ⚠️ No compose file and no existing n8n-app container found"
echo " Start manually: docker start n8n-app"
fi fi
fi fi
echo " ✅ n8n → http://${VM_IP}:5678" echo " ✅ n8n → http://${VM_IP}:5678"
fi fi
@@ -479,7 +390,7 @@ echo "✅ RESTORE COMPLETE"
echo "=========================================" echo "========================================="
echo " Nextcloud → http://${VM_IP}:8082" echo " Nextcloud → http://${VM_IP}:8082"
echo " Mautic → http://${VM_IP}:8081/s/login (admin / Admin!Password123)" echo " Mautic → http://${VM_IP}:8081/s/login (admin / Admin!Password123)"
echo " Odoo → http://${VM_IP}:8069/web" echo " Odoo → https://odooo.nav.ovh/web"
echo " n8n → http://${VM_IP}:5678" echo " n8n → http://${VM_IP}:5678"
echo " Frappe → http://${VM_IP}:8080" echo " Frappe → http://${VM_IP}:8080"
echo "=========================================" echo "========================================="