512 lines
22 KiB
Bash
512 lines
22 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
# =============================================================================
|
||
|
|
# LetsBe Restore Script
|
||
|
|
# =============================================================================
|
||
|
|
#
|
||
|
|
# Restores backups created by backups.sh.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# restore.sh list List available local backups
|
||
|
|
# restore.sh list-remote List available remote backups
|
||
|
|
# restore.sh download <DATE> Download a remote backup set locally
|
||
|
|
# restore.sh postgres <TOOL> <FILE> Restore a PostgreSQL database
|
||
|
|
# restore.sh mysql <TOOL> <FILE> Restore a MySQL/MariaDB database
|
||
|
|
# restore.sh mongo <TOOL> <FILE> Restore a MongoDB database
|
||
|
|
# restore.sh env <FILE> Restore env files
|
||
|
|
# restore.sh configs <FILE> Restore config files
|
||
|
|
# restore.sh nginx <FILE> Restore nginx configs
|
||
|
|
# restore.sh full <DATE> Full restore from a backup date
|
||
|
|
#
|
||
|
|
# Examples:
|
||
|
|
# restore.sh list
|
||
|
|
# restore.sh postgres chatwoot /tmp/letsbe-backups/pg_chatwoot_20260207_020000.sql.gz
|
||
|
|
# restore.sh env /tmp/letsbe-backups/dir_env-files_20260207_020000.tar.gz
|
||
|
|
# restore.sh download 20260207_020000
|
||
|
|
# restore.sh full 20260207_020000
|
||
|
|
#
|
||
|
|
# IMPORTANT:
|
||
|
|
# - Always stop the tool's application containers before restoring its database.
|
||
|
|
# - Database containers must remain running during restore.
|
||
|
|
# - After restore, restart the full tool stack.
|
||
|
|
#
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
set -uo pipefail
|
||
|
|
|
||
|
|
LETSBE_BASE="/opt/letsbe"
|
||
|
|
BACKUP_DIR="/tmp/letsbe-backups"
|
||
|
|
RCLONE_REMOTE="backup"
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# HELPERS
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
log() {
|
||
|
|
echo "[RESTORE] $*"
|
||
|
|
}
|
||
|
|
|
||
|
|
die() {
|
||
|
|
echo "[RESTORE ERROR] $*" >&2
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
require_file() {
|
||
|
|
local file=$1
|
||
|
|
[[ -f "$file" ]] || die "File not found: $file"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Find a running container by suffix pattern
|
||
|
|
find_container() {
|
||
|
|
local pattern=$1
|
||
|
|
docker ps --format '{{.Names}}' | grep -E "(^|-)${pattern}$" | head -1
|
||
|
|
}
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# COMMANDS
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
cmd_list() {
|
||
|
|
log "Available local backups in ${BACKUP_DIR}:"
|
||
|
|
echo ""
|
||
|
|
if [[ -d "$BACKUP_DIR" ]]; then
|
||
|
|
ls -lhS "$BACKUP_DIR"/ 2>/dev/null || echo " (empty)"
|
||
|
|
else
|
||
|
|
echo " No backup directory found."
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_list_remote() {
|
||
|
|
if ! command -v rclone &> /dev/null; then
|
||
|
|
die "rclone not installed"
|
||
|
|
fi
|
||
|
|
if ! rclone listremotes 2>/dev/null | grep -q "^${RCLONE_REMOTE}:"; then
|
||
|
|
die "rclone remote '${RCLONE_REMOTE}' not configured"
|
||
|
|
fi
|
||
|
|
|
||
|
|
log "Available remote backups:"
|
||
|
|
echo ""
|
||
|
|
echo "Daily:"
|
||
|
|
rclone lsd "${RCLONE_REMOTE}:letsbe-backups/" 2>/dev/null | grep -v "weekly" | awk '{print " " $NF}'
|
||
|
|
echo ""
|
||
|
|
echo "Weekly:"
|
||
|
|
rclone lsd "${RCLONE_REMOTE}:letsbe-backups/weekly/" 2>/dev/null | awk '{print " " $NF}'
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_download() {
|
||
|
|
local date_str=$1
|
||
|
|
if ! command -v rclone &> /dev/null; then
|
||
|
|
die "rclone not installed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
local remote_path="${RCLONE_REMOTE}:letsbe-backups/${date_str}/"
|
||
|
|
log "Downloading backup from ${remote_path}..."
|
||
|
|
mkdir -p "$BACKUP_DIR"
|
||
|
|
rclone copy "$remote_path" "$BACKUP_DIR/" --progress
|
||
|
|
log "Download complete. Files in ${BACKUP_DIR}/"
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_restore_postgres() {
|
||
|
|
local tool=$1
|
||
|
|
local file=$2
|
||
|
|
require_file "$file"
|
||
|
|
|
||
|
|
# Map tool name to container suffix, db name, and user
|
||
|
|
local container db_name db_user
|
||
|
|
case "$tool" in
|
||
|
|
chatwoot) container="chatwoot-postgres"; db_name="chatwoot_production"; db_user="chatwoot" ;;
|
||
|
|
nextcloud) container="nextcloud-postgres"; db_name="nextcloud"; db_user="nextcloud" ;;
|
||
|
|
keycloak) container="keycloak-db"; db_name="keycloak"; db_user="keycloak" ;;
|
||
|
|
n8n) container="n8n-postgres"; db_name="n8n"; db_user="postgres" ;;
|
||
|
|
calcom) container="calcom-postgres"; db_name="calcom"; db_user="postgres" ;;
|
||
|
|
umami) container="umami-db"; db_name="umami"; db_user="postgres" ;;
|
||
|
|
nocodb) container="nocodb-postgres"; db_name="nocodb"; db_user="postgres" ;;
|
||
|
|
typebot) container="typebot-db"; db_name="typebot"; db_user="postgres" ;;
|
||
|
|
windmill) container="windmill-db"; db_name="windmill"; db_user="postgres" ;;
|
||
|
|
glitchtip) container="glitchtip-postgres"; db_name="postgres"; db_user="postgres" ;;
|
||
|
|
penpot) container="penpot-postgres"; db_name="penpot"; db_user="postgres" ;;
|
||
|
|
gitea) container="gitea-db"; db_name="gitea"; db_user="postgres" ;;
|
||
|
|
odoo) container="odoo-postgres"; db_name="postgres"; db_user="postgres" ;;
|
||
|
|
listmonk) container="listmonk-db"; db_name="listmonk"; db_user="postgres" ;;
|
||
|
|
documenso) container="documenso-db"; db_name="documenso_db"; db_user="postgres" ;;
|
||
|
|
redash) container="redash-postgres"; db_name="postgres"; db_user="postgres" ;;
|
||
|
|
activepieces) container="activepieces-postgres"; db_name="activepieces"; db_user="postgres" ;;
|
||
|
|
orchestrator) container="orchestrator-db"; db_name="orchestrator"; db_user="orchestrator" ;;
|
||
|
|
*) die "Unknown PostgreSQL tool: $tool. Use one of: chatwoot, nextcloud, keycloak, n8n, calcom, umami, nocodb, typebot, windmill, glitchtip, penpot, gitea, odoo, listmonk, documenso, redash, activepieces, orchestrator" ;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
local actual_container
|
||
|
|
actual_container=$(find_container "$container")
|
||
|
|
[[ -z "$actual_container" ]] && die "Container matching '$container' not found. Is it running?"
|
||
|
|
|
||
|
|
log "Restoring PostgreSQL: $tool"
|
||
|
|
log " Container: $actual_container"
|
||
|
|
log " Database: $db_name"
|
||
|
|
log " File: $file"
|
||
|
|
echo ""
|
||
|
|
read -p "WARNING: This will DROP and recreate database '$db_name'. Continue? (yes/no): " confirm
|
||
|
|
[[ "$confirm" == "yes" ]] || die "Restore cancelled."
|
||
|
|
|
||
|
|
log "Dropping and recreating database..."
|
||
|
|
docker exec "$actual_container" psql -U "$db_user" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$db_name' AND pid <> pg_backend_pid();" postgres 2>/dev/null || true
|
||
|
|
docker exec "$actual_container" psql -U "$db_user" -c "DROP DATABASE IF EXISTS \"$db_name\";" postgres
|
||
|
|
docker exec "$actual_container" psql -U "$db_user" -c "CREATE DATABASE \"$db_name\";" postgres
|
||
|
|
|
||
|
|
log "Restoring from backup..."
|
||
|
|
if [[ "$file" == *.gz ]]; then
|
||
|
|
gunzip -c "$file" | docker exec -i "$actual_container" psql -U "$db_user" "$db_name"
|
||
|
|
else
|
||
|
|
docker exec -i "$actual_container" psql -U "$db_user" "$db_name" < "$file"
|
||
|
|
fi
|
||
|
|
|
||
|
|
log "PostgreSQL restore complete for $tool."
|
||
|
|
log "Restart the $tool application containers to reconnect."
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_restore_mysql() {
|
||
|
|
local tool=$1
|
||
|
|
local file=$2
|
||
|
|
require_file "$file"
|
||
|
|
|
||
|
|
local container db_name db_user
|
||
|
|
case "$tool" in
|
||
|
|
wordpress) container="wordpress-mysql"; db_name="wordpress"; db_user="root" ;;
|
||
|
|
ghost) container="ghost-db"; db_name="ghost"; db_user="root" ;;
|
||
|
|
*) die "Unknown MySQL tool: $tool. Use one of: wordpress, ghost" ;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
local actual_container
|
||
|
|
actual_container=$(find_container "$container")
|
||
|
|
[[ -z "$actual_container" ]] && die "Container matching '$container' not found. Is it running?"
|
||
|
|
|
||
|
|
log "Restoring MySQL: $tool"
|
||
|
|
log " Container: $actual_container"
|
||
|
|
log " Database: $db_name"
|
||
|
|
log " File: $file"
|
||
|
|
echo ""
|
||
|
|
read -p "WARNING: This will overwrite database '$db_name'. Continue? (yes/no): " confirm
|
||
|
|
[[ "$confirm" == "yes" ]] || die "Restore cancelled."
|
||
|
|
|
||
|
|
log "Restoring from backup..."
|
||
|
|
# Read root password from credentials
|
||
|
|
local creds_file="${LETSBE_BASE}/env/credentials.env"
|
||
|
|
local db_pass=""
|
||
|
|
if [[ "$tool" == "wordpress" ]]; then
|
||
|
|
db_pass=$(grep "^WORDPRESS_MARIADB_ROOT_PASSWORD=" "$creds_file" 2>/dev/null | cut -d'=' -f2-)
|
||
|
|
elif [[ "$tool" == "ghost" ]]; then
|
||
|
|
db_pass=$(grep "^GHOST_MYSQL_PASSWORD=" "$creds_file" 2>/dev/null | cut -d'=' -f2-)
|
||
|
|
fi
|
||
|
|
[[ -z "$db_pass" ]] && die "Could not read database password from $creds_file"
|
||
|
|
|
||
|
|
if [[ "$file" == *.gz ]]; then
|
||
|
|
gunzip -c "$file" | docker exec -i "$actual_container" mysql -u"$db_user" -p"$db_pass" "$db_name"
|
||
|
|
else
|
||
|
|
docker exec -i "$actual_container" mysql -u"$db_user" -p"$db_pass" "$db_name" < "$file"
|
||
|
|
fi
|
||
|
|
|
||
|
|
log "MySQL restore complete for $tool."
|
||
|
|
log "Restart the $tool application containers to reconnect."
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_restore_mongo() {
|
||
|
|
local tool=$1
|
||
|
|
local file=$2
|
||
|
|
require_file "$file"
|
||
|
|
|
||
|
|
local container db_name
|
||
|
|
case "$tool" in
|
||
|
|
librechat) container="librechat-mongodb"; db_name="LibreChat" ;;
|
||
|
|
*) die "Unknown MongoDB tool: $tool. Use: librechat" ;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
local actual_container
|
||
|
|
actual_container=$(find_container "$container")
|
||
|
|
[[ -z "$actual_container" ]] && die "Container matching '$container' not found. Is it running?"
|
||
|
|
|
||
|
|
log "Restoring MongoDB: $tool"
|
||
|
|
log " Container: $actual_container"
|
||
|
|
log " Database: $db_name"
|
||
|
|
log " File: $file"
|
||
|
|
echo ""
|
||
|
|
read -p "WARNING: This will drop and restore database '$db_name'. Continue? (yes/no): " confirm
|
||
|
|
[[ "$confirm" == "yes" ]] || die "Restore cancelled."
|
||
|
|
|
||
|
|
log "Restoring from backup..."
|
||
|
|
if [[ "$file" == *.gz ]]; then
|
||
|
|
gunzip -c "$file" | docker exec -i "$actual_container" mongorestore --db "$db_name" --drop --archive
|
||
|
|
else
|
||
|
|
docker exec -i "$actual_container" mongorestore --db "$db_name" --drop --archive < "$file"
|
||
|
|
fi
|
||
|
|
|
||
|
|
log "MongoDB restore complete for $tool."
|
||
|
|
log "Restart the $tool application containers to reconnect."
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_restore_env() {
|
||
|
|
local file=$1
|
||
|
|
require_file "$file"
|
||
|
|
|
||
|
|
log "Restoring env files from: $file"
|
||
|
|
echo ""
|
||
|
|
read -p "WARNING: This will overwrite files in ${LETSBE_BASE}/env/. Continue? (yes/no): " confirm
|
||
|
|
[[ "$confirm" == "yes" ]] || die "Restore cancelled."
|
||
|
|
|
||
|
|
# Backup current env files first
|
||
|
|
local timestamp
|
||
|
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
||
|
|
if [[ -d "${LETSBE_BASE}/env" ]]; then
|
||
|
|
log "Backing up current env files to ${LETSBE_BASE}/env.pre-restore.${timestamp}..."
|
||
|
|
cp -a "${LETSBE_BASE}/env" "${LETSBE_BASE}/env.pre-restore.${timestamp}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
log "Extracting..."
|
||
|
|
tar xzf "$file" -C "${LETSBE_BASE}/"
|
||
|
|
chmod 600 "${LETSBE_BASE}/env/"*.env 2>/dev/null || true
|
||
|
|
log "Env files restored."
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_restore_configs() {
|
||
|
|
local file=$1
|
||
|
|
require_file "$file"
|
||
|
|
|
||
|
|
log "Restoring config files from: $file"
|
||
|
|
echo ""
|
||
|
|
read -p "WARNING: This will overwrite files in ${LETSBE_BASE}/config/. Continue? (yes/no): " confirm
|
||
|
|
[[ "$confirm" == "yes" ]] || die "Restore cancelled."
|
||
|
|
|
||
|
|
local timestamp
|
||
|
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
||
|
|
if [[ -d "${LETSBE_BASE}/config" ]]; then
|
||
|
|
cp -a "${LETSBE_BASE}/config" "${LETSBE_BASE}/config.pre-restore.${timestamp}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
tar xzf "$file" -C "${LETSBE_BASE}/"
|
||
|
|
log "Config files restored."
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_restore_nginx() {
|
||
|
|
local file=$1
|
||
|
|
require_file "$file"
|
||
|
|
|
||
|
|
log "Restoring nginx configs from: $file"
|
||
|
|
echo ""
|
||
|
|
read -p "WARNING: This will overwrite files in ${LETSBE_BASE}/nginx/. Continue? (yes/no): " confirm
|
||
|
|
[[ "$confirm" == "yes" ]] || die "Restore cancelled."
|
||
|
|
|
||
|
|
local timestamp
|
||
|
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
||
|
|
if [[ -d "${LETSBE_BASE}/nginx" ]]; then
|
||
|
|
cp -a "${LETSBE_BASE}/nginx" "${LETSBE_BASE}/nginx.pre-restore.${timestamp}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
tar xzf "$file" -C "${LETSBE_BASE}/"
|
||
|
|
log "Nginx configs restored."
|
||
|
|
log "Run: systemctl restart nginx"
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd_full_restore() {
|
||
|
|
local date_str=$1
|
||
|
|
local backup_path="$BACKUP_DIR"
|
||
|
|
|
||
|
|
log "=== Full System Restore for date: $date_str ==="
|
||
|
|
echo ""
|
||
|
|
echo "This will restore ALL databases and configuration files from the backup."
|
||
|
|
echo "Make sure all tool containers are stopped (except database containers)."
|
||
|
|
echo ""
|
||
|
|
read -p "Continue with full restore? (yes/no): " confirm
|
||
|
|
[[ "$confirm" == "yes" ]] || die "Full restore cancelled."
|
||
|
|
|
||
|
|
# Check if files exist locally, download if not
|
||
|
|
local pg_count
|
||
|
|
pg_count=$(ls "${backup_path}"/pg_*"${date_str}"* 2>/dev/null | wc -l)
|
||
|
|
if [[ "$pg_count" -eq 0 ]]; then
|
||
|
|
log "Backup files not found locally. Attempting remote download..."
|
||
|
|
cmd_download "$date_str"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Restore env files
|
||
|
|
local env_file="${backup_path}/dir_env-files_${date_str}.tar.gz"
|
||
|
|
if [[ -f "$env_file" ]]; then
|
||
|
|
log "Restoring env files..."
|
||
|
|
# Non-interactive for full restore (already confirmed above)
|
||
|
|
local timestamp
|
||
|
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
||
|
|
[[ -d "${LETSBE_BASE}/env" ]] && cp -a "${LETSBE_BASE}/env" "${LETSBE_BASE}/env.pre-restore.${timestamp}"
|
||
|
|
tar xzf "$env_file" -C "${LETSBE_BASE}/"
|
||
|
|
chmod 600 "${LETSBE_BASE}/env/"*.env 2>/dev/null || true
|
||
|
|
log " Env files restored."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Restore configs
|
||
|
|
local cfg_file="${backup_path}/dir_letsbe-config_${date_str}.tar.gz"
|
||
|
|
if [[ -f "$cfg_file" ]]; then
|
||
|
|
log "Restoring config files..."
|
||
|
|
tar xzf "$cfg_file" -C "${LETSBE_BASE}/"
|
||
|
|
log " Config files restored."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Restore nginx configs
|
||
|
|
local nginx_file="${backup_path}/dir_nginx-configs_${date_str}.tar.gz"
|
||
|
|
if [[ -f "$nginx_file" ]]; then
|
||
|
|
log "Restoring nginx configs..."
|
||
|
|
tar xzf "$nginx_file" -C "${LETSBE_BASE}/"
|
||
|
|
log " Nginx configs restored."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Restore all PostgreSQL databases found for this date
|
||
|
|
log "Restoring PostgreSQL databases..."
|
||
|
|
for pg_file in "${backup_path}"/pg_*"${date_str}"*.sql.gz; do
|
||
|
|
[[ -f "$pg_file" ]] || continue
|
||
|
|
# Extract tool name from filename: pg_<tool>_<date>.sql.gz
|
||
|
|
local tool_name
|
||
|
|
tool_name=$(basename "$pg_file" | sed "s/^pg_//;s/_${date_str}.*//")
|
||
|
|
log " Restoring PostgreSQL: $tool_name"
|
||
|
|
|
||
|
|
# Find container and restore without interactive prompt
|
||
|
|
local container db_name db_user
|
||
|
|
case "$tool_name" in
|
||
|
|
chatwoot) container="chatwoot-postgres"; db_name="chatwoot_production"; db_user="chatwoot" ;;
|
||
|
|
nextcloud) container="nextcloud-postgres"; db_name="nextcloud"; db_user="nextcloud" ;;
|
||
|
|
keycloak) container="keycloak-db"; db_name="keycloak"; db_user="keycloak" ;;
|
||
|
|
n8n) container="n8n-postgres"; db_name="n8n"; db_user="postgres" ;;
|
||
|
|
calcom) container="calcom-postgres"; db_name="calcom"; db_user="postgres" ;;
|
||
|
|
umami) container="umami-db"; db_name="umami"; db_user="postgres" ;;
|
||
|
|
nocodb) container="nocodb-postgres"; db_name="nocodb"; db_user="postgres" ;;
|
||
|
|
typebot) container="typebot-db"; db_name="typebot"; db_user="postgres" ;;
|
||
|
|
windmill) container="windmill-db"; db_name="windmill"; db_user="postgres" ;;
|
||
|
|
glitchtip) container="glitchtip-postgres"; db_name="postgres"; db_user="postgres" ;;
|
||
|
|
penpot) container="penpot-postgres"; db_name="penpot"; db_user="postgres" ;;
|
||
|
|
gitea) container="gitea-db"; db_name="gitea"; db_user="postgres" ;;
|
||
|
|
odoo) container="odoo-postgres"; db_name="postgres"; db_user="postgres" ;;
|
||
|
|
listmonk) container="listmonk-db"; db_name="listmonk"; db_user="postgres" ;;
|
||
|
|
documenso) container="documenso-db"; db_name="documenso_db"; db_user="postgres" ;;
|
||
|
|
redash) container="redash-postgres"; db_name="postgres"; db_user="postgres" ;;
|
||
|
|
activepieces) container="activepieces-postgres"; db_name="activepieces"; db_user="postgres" ;;
|
||
|
|
orchestrator) container="orchestrator-db"; db_name="orchestrator"; db_user="orchestrator" ;;
|
||
|
|
*) log " Skipping unknown tool: $tool_name"; continue ;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
local actual_container
|
||
|
|
actual_container=$(find_container "$container")
|
||
|
|
if [[ -z "$actual_container" ]]; then
|
||
|
|
log " WARNING: Container '$container' not running, skipping $tool_name"
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
|
||
|
|
docker exec "$actual_container" psql -U "$db_user" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$db_name' AND pid <> pg_backend_pid();" postgres 2>/dev/null || true
|
||
|
|
docker exec "$actual_container" psql -U "$db_user" -c "DROP DATABASE IF EXISTS \"$db_name\";" postgres 2>/dev/null || true
|
||
|
|
docker exec "$actual_container" psql -U "$db_user" -c "CREATE DATABASE \"$db_name\";" postgres 2>/dev/null || true
|
||
|
|
gunzip -c "$pg_file" | docker exec -i "$actual_container" psql -U "$db_user" "$db_name" > /dev/null 2>&1
|
||
|
|
log " OK: $tool_name"
|
||
|
|
done
|
||
|
|
|
||
|
|
# Restore MySQL databases
|
||
|
|
log "Restoring MySQL databases..."
|
||
|
|
for mysql_file in "${backup_path}"/mysql_*"${date_str}"*.sql.gz; do
|
||
|
|
[[ -f "$mysql_file" ]] || continue
|
||
|
|
local tool_name
|
||
|
|
tool_name=$(basename "$mysql_file" | sed "s/^mysql_//;s/_${date_str}.*//")
|
||
|
|
log " Restoring MySQL: $tool_name"
|
||
|
|
|
||
|
|
local container db_name db_pass
|
||
|
|
case "$tool_name" in
|
||
|
|
wordpress)
|
||
|
|
container="wordpress-mysql"; db_name="wordpress"
|
||
|
|
db_pass=$(grep "^WORDPRESS_MARIADB_ROOT_PASSWORD=" "${LETSBE_BASE}/env/credentials.env" 2>/dev/null | cut -d'=' -f2-)
|
||
|
|
;;
|
||
|
|
ghost)
|
||
|
|
container="ghost-db"; db_name="ghost"
|
||
|
|
db_pass=$(grep "^GHOST_MYSQL_PASSWORD=" "${LETSBE_BASE}/env/credentials.env" 2>/dev/null | cut -d'=' -f2-)
|
||
|
|
;;
|
||
|
|
*) log " Skipping unknown MySQL tool: $tool_name"; continue ;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
local actual_container
|
||
|
|
actual_container=$(find_container "$container")
|
||
|
|
if [[ -z "$actual_container" ]]; then
|
||
|
|
log " WARNING: Container '$container' not running, skipping $tool_name"
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ -n "$db_pass" ]]; then
|
||
|
|
gunzip -c "$mysql_file" | docker exec -i "$actual_container" mysql -uroot -p"$db_pass" "$db_name" 2>/dev/null
|
||
|
|
log " OK: $tool_name"
|
||
|
|
else
|
||
|
|
log " SKIP: No password found for $tool_name"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
# Restore MongoDB databases
|
||
|
|
log "Restoring MongoDB databases..."
|
||
|
|
for mongo_file in "${backup_path}"/mongo_*"${date_str}"*.archive.gz; do
|
||
|
|
[[ -f "$mongo_file" ]] || continue
|
||
|
|
local tool_name
|
||
|
|
tool_name=$(basename "$mongo_file" | sed "s/^mongo_//;s/_${date_str}.*//")
|
||
|
|
log " Restoring MongoDB: $tool_name"
|
||
|
|
|
||
|
|
case "$tool_name" in
|
||
|
|
librechat)
|
||
|
|
local actual_container
|
||
|
|
actual_container=$(find_container "librechat-mongodb")
|
||
|
|
if [[ -n "$actual_container" ]]; then
|
||
|
|
gunzip -c "$mongo_file" | docker exec -i "$actual_container" mongorestore --db LibreChat --drop --archive 2>/dev/null
|
||
|
|
log " OK: $tool_name"
|
||
|
|
else
|
||
|
|
log " WARNING: Container not running, skipping"
|
||
|
|
fi
|
||
|
|
;;
|
||
|
|
*) log " Skipping unknown MongoDB tool: $tool_name" ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
log ""
|
||
|
|
log "=== Full Restore Complete ==="
|
||
|
|
log "Now restart all tool stacks:"
|
||
|
|
log " for stack in ${LETSBE_BASE}/stacks/*/docker-compose.yml; do"
|
||
|
|
log " docker-compose -f \"\$stack\" restart"
|
||
|
|
log " done"
|
||
|
|
log " systemctl restart nginx"
|
||
|
|
}
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# MAIN
|
||
|
|
# =============================================================================
|
||
|
|
|
||
|
|
if [[ $# -lt 1 ]]; then
|
||
|
|
echo "LetsBe Restore Tool"
|
||
|
|
echo ""
|
||
|
|
echo "Usage:"
|
||
|
|
echo " $0 list List local backups"
|
||
|
|
echo " $0 list-remote List remote backups"
|
||
|
|
echo " $0 download <DATE> Download remote backup"
|
||
|
|
echo " $0 postgres <TOOL> <FILE> Restore PostgreSQL database"
|
||
|
|
echo " $0 mysql <TOOL> <FILE> Restore MySQL database"
|
||
|
|
echo " $0 mongo <TOOL> <FILE> Restore MongoDB database"
|
||
|
|
echo " $0 env <FILE> Restore env files"
|
||
|
|
echo " $0 configs <FILE> Restore config files"
|
||
|
|
echo " $0 nginx <FILE> Restore nginx configs"
|
||
|
|
echo " $0 full <DATE> Full system restore"
|
||
|
|
echo ""
|
||
|
|
echo "PostgreSQL tools: chatwoot, nextcloud, keycloak, n8n, calcom, umami,"
|
||
|
|
echo " nocodb, typebot, windmill, glitchtip, penpot, gitea, odoo, listmonk,"
|
||
|
|
echo " documenso, redash, activepieces, orchestrator"
|
||
|
|
echo ""
|
||
|
|
echo "MySQL tools: wordpress, ghost"
|
||
|
|
echo ""
|
||
|
|
echo "MongoDB tools: librechat"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
case "$1" in
|
||
|
|
list) cmd_list ;;
|
||
|
|
list-remote) cmd_list_remote ;;
|
||
|
|
download) [[ $# -ge 2 ]] || die "Usage: $0 download <DATE>"; cmd_download "$2" ;;
|
||
|
|
postgres) [[ $# -ge 3 ]] || die "Usage: $0 postgres <TOOL> <FILE>"; cmd_restore_postgres "$2" "$3" ;;
|
||
|
|
mysql) [[ $# -ge 3 ]] || die "Usage: $0 mysql <TOOL> <FILE>"; cmd_restore_mysql "$2" "$3" ;;
|
||
|
|
mongo) [[ $# -ge 3 ]] || die "Usage: $0 mongo <TOOL> <FILE>"; cmd_restore_mongo "$2" "$3" ;;
|
||
|
|
env) [[ $# -ge 2 ]] || die "Usage: $0 env <FILE>"; cmd_restore_env "$2" ;;
|
||
|
|
configs) [[ $# -ge 2 ]] || die "Usage: $0 configs <FILE>"; cmd_restore_configs "$2" ;;
|
||
|
|
nginx) [[ $# -ge 2 ]] || die "Usage: $0 nginx <FILE>"; cmd_restore_nginx "$2" ;;
|
||
|
|
full) [[ $# -ge 2 ]] || die "Usage: $0 full <DATE>"; cmd_full_restore "$2" ;;
|
||
|
|
*) die "Unknown command: $1. Run '$0' for usage." ;;
|
||
|
|
esac
|