#!/bin/bash # CRM SITECONSEIL - Restore script (database + files) # Usage: # /usr/local/bin/crm-siteconseil-restore.sh # restore the latest backup # /usr/local/bin/crm-siteconseil-restore.sh 20260410_143000 # restore a specific backup # /usr/local/bin/crm-siteconseil-restore.sh --list # list available backups # /usr/local/bin/crm-siteconseil-restore.sh -y 20260410_143000 # skip confirmation # # WARNING: this script will REPLACE the current database and files. set -euo pipefail ####################################### # Configuration ####################################### APP_DIR="{{ app_dir | default('/var/www/crm-siteconseil') }}" COMPOSE_FILE="${APP_DIR}/docker-compose-prod.yml" BACKUP_DIR="{{ backup_dir | default('/var/backups/crm-siteconseil') }}" DB_SERVICE="{{ db_service | default('db-master') }}" DB_USER="{{ db_user | default('crm-siteconseil') }}" DB_NAME="{{ db_name | default('crm-siteconseil') }}" LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S')]" ASSUME_YES=0 TARGET="" ####################################### # Helpers ####################################### log() { echo "${LOG_PREFIX} $*"; } fail() { echo "${LOG_PREFIX} ERROR: $*" >&2; exit 1; } usage() { cat < pg_backend_pid();" \ >/dev/null docker compose -f "${COMPOSE_FILE}" exec -T "${DB_SERVICE}" \ psql -U "${DB_USER}" -d postgres -v ON_ERROR_STOP=1 -c "DROP DATABASE IF EXISTS \"${DB_NAME}\";" \ >/dev/null docker compose -f "${COMPOSE_FILE}" exec -T "${DB_SERVICE}" \ psql -U "${DB_USER}" -d postgres -v ON_ERROR_STOP=1 -c "CREATE DATABASE \"${DB_NAME}\" OWNER \"${DB_USER}\";" \ >/dev/null if ! docker compose -f "${COMPOSE_FILE}" exec -T "${DB_SERVICE}" \ pg_restore -U "${DB_USER}" -d "${DB_NAME}" --no-owner --no-acl --clean --if-exists \ < "${DB_FILE}"; then fail "pg_restore failed" fi log "Database restored OK" else log "WARNING: no database.dump in backup, skipping DB restore" fi ####################################### # 2. Restore public/uploads ####################################### if [ -f "${UPLOADS_FILE}" ]; then log "Restoring public/uploads..." BACKUP_OLD="${APP_DIR}/public/uploads.bak.$(date +%s)" if [ -d "${APP_DIR}/public/uploads" ]; then mv "${APP_DIR}/public/uploads" "${BACKUP_OLD}" log "Existing uploads moved to ${BACKUP_OLD}" fi if tar -xzf "${UPLOADS_FILE}" -C "${APP_DIR}/public"; then log "Uploads restored OK" else log "ERROR: uploads extraction failed, rolling back" rm -rf "${APP_DIR}/public/uploads" [ -d "${BACKUP_OLD}" ] && mv "${BACKUP_OLD}" "${APP_DIR}/public/uploads" fail "uploads restore failed" fi else log "WARNING: no uploads.tar.gz in backup, skipping uploads restore" fi ####################################### # 3. Restore var/share ####################################### if [ -f "${SHARE_FILE}" ]; then log "Restoring var/share..." BACKUP_OLD="${APP_DIR}/var/share.bak.$(date +%s)" if [ -d "${APP_DIR}/var/share" ]; then mv "${APP_DIR}/var/share" "${BACKUP_OLD}" log "Existing share moved to ${BACKUP_OLD}" fi if tar -xzf "${SHARE_FILE}" -C "${APP_DIR}/var"; then log "Share restored OK" else log "ERROR: share extraction failed, rolling back" rm -rf "${APP_DIR}/var/share" [ -d "${BACKUP_OLD}" ] && mv "${BACKUP_OLD}" "${APP_DIR}/var/share" fail "share restore failed" fi else log "WARNING: no share.tar.gz in backup, skipping share restore" fi ####################################### # 4. Post-restore: clear caches ####################################### log "Clearing application cache..." docker compose -f "${COMPOSE_FILE}" exec -T php php bin/console cache:clear --no-warmup >/dev/null 2>&1 || \ log "WARNING: cache:clear failed (php service down?)" log "Restore completed from ${TARGET}"