diff --git a/ansible/restore.sh.j2 b/ansible/restore.sh.j2 new file mode 100644 index 0000000..81fd134 --- /dev/null +++ b/ansible/restore.sh.j2 @@ -0,0 +1,184 @@ +#!/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}" diff --git a/migrations/Version20260409141035.php b/migrations/Version20260409141035.php new file mode 100644 index 0000000..16ba93d --- /dev/null +++ b/migrations/Version20260409141035.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE customer_payment_method (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, stripe_payment_method_id VARCHAR(255) NOT NULL, type VARCHAR(20) NOT NULL, last4 VARCHAR(4) DEFAULT NULL, brand VARCHAR(50) DEFAULT NULL, country VARCHAR(2) DEFAULT NULL, is_default BOOLEAN NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, customer_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_E20C953C9395C3F3 ON customer_payment_method (customer_id)'); + $this->addSql('CREATE INDEX idx_customer_payment_method_default ON customer_payment_method (customer_id, is_default)'); + $this->addSql('ALTER TABLE customer_payment_method ADD CONSTRAINT FK_E20C953C9395C3F3 FOREIGN KEY (customer_id) REFERENCES customer (id) ON DELETE CASCADE NOT DEFERRABLE'); + $this->addSql('ALTER TABLE contrat ALTER services DROP DEFAULT'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE customer_payment_method DROP CONSTRAINT FK_E20C953C9395C3F3'); + $this->addSql('DROP TABLE customer_payment_method'); + $this->addSql('ALTER TABLE contrat ALTER services SET DEFAULT \'[]\''); + } +}