supersam/scripts/backup-code-s3.sh

118 lines
5.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Supersam app code backup to S3 (Beget Cloud Storage)
# Archives /opt/supersam source code (excluding node_modules, .git, dist)
# and uploads to S3. Retention: keeps last 30 days.
#
# Cron: 0 1 * * * /opt/supersam/scripts/backup-code-s3.sh >> /var/log/supersam-backup-code.log 2>&1
# Also triggered after deploy via deploy.sh
# Каждая операция (успех или провал) логируется в public.backup_logs.
set -euo pipefail
# ── Config ────────────────────────────────────────────────────────────────
AWS_ACCESS_KEY_ID="YG4MQNKAPNL65200MBUY"
AWS_SECRET_ACCESS_KEY="8mXkFM2VRQ3pN1Nx4mhmJ2jrZoB5YTPUa4CaZh43"
S3_BUCKET="02f162ff4a18-supersam-s3"
S3_PREFIX="backups"
SOURCE_DIR="/opt/supersam"
SOURCE_DIR_DEV="/opt/supersam-dev"
RETENTION_DAYS=30
DB_CONTAINER="supabase-db"
DB_USER="supabase_admin"
DB_NAME="postgres"
DB_PASS="${POSTGRES_PASSWORD:-$(grep -m1 '^POSTGRES_PASSWORD=' /opt/supabase/docker/.env 2>/dev/null | cut -d= -f2- || true)}"
DB_PASS="${DB_PASS:-4fe80bb21c7c3d17a8d8b226adf7a479}"
BACKUP_TYPE="code"
# ── Logging в public.backup_logs ──────────────────────────────────────────
LOGGED=0
log_backup() {
local status="$1" size="$2" bytes="$3" s3_path="$4" tables="$5"
printf "INSERT INTO public.backup_logs (backup_type, status, file_size, file_size_bytes, s3_path, tables_included) VALUES ('%s', '%s', '%s', %s, '%s', '%s');\n" \
"${BACKUP_TYPE}" "${status}" "${size}" "${bytes:-0}" "${s3_path}" "${tables}" \
| docker exec -i -e PGPASSWORD="${DB_PASS}" "${DB_CONTAINER}" \
psql -U "${DB_USER}" -d "${DB_NAME}" -q -t -A >/dev/null \
|| echo "[$(date)] WARN: не удалось записать backup_logs"
LOGGED=1
}
on_exit() {
local rc=$?
if [ "${rc}" -ne 0 ] && [ "${LOGGED}" -eq 0 ]; then
echo "[$(date)] FAILED: скрипт завершился с кодом ${rc}"
log_backup "failed" "" 0 "" "exit code ${rc}"
fi
exit "${rc}"
}
trap on_exit EXIT
# ── Runtime ───────────────────────────────────────────────────────────────
TIMESTAMP=$(date +%Y-%m-%d_%H%M)
ARCHIVE_FILE="/tmp/supersam-code-${TIMESTAMP}.tar.gz"
S3_KEY="${S3_PREFIX}/code/supersam-code-${TIMESTAMP}.tar.gz"
S3_PATH="s3://${S3_BUCKET}/${S3_KEY}"
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
echo "[$(date)] Starting code backup..."
# ── Archive ──────────────────────────────────────────────────────────────
echo "[$(date)] Archiving ${SOURCE_DIR}..."
# Рантайм-чекаут + актуальный dev-чекаут (источник прод-фронта)
tar -czf "${ARCHIVE_FILE}" \
--directory="/" \
--exclude="node_modules" \
--exclude=".git" \
--exclude="dist" \
--exclude="volumes/db" \
--exclude="volumes/storage" \
--exclude="volumes/logs" \
--exclude="volumes/api" \
--exclude="volumes/snippets" \
--exclude="__pycache__" \
"${SOURCE_DIR#/}" "${SOURCE_DIR_DEV#/}" 2>/dev/null
ARCHIVE_SIZE=$(du -h "${ARCHIVE_FILE}" | cut -f1)
ARCHIVE_BYTES=$(stat -c%s "${ARCHIVE_FILE}")
echo "[$(date)] Archive created: ${ARCHIVE_FILE} (${ARCHIVE_SIZE}, ${ARCHIVE_BYTES} bytes)"
# ── Upload via mc ─────────────────────────────────────────────────────────
echo "[$(date)] Uploading to S3..."
mc cp "${ARCHIVE_FILE}" "beget/${S3_BUCKET}/${S3_KEY}" --quiet 2>&1
echo "[$(date)] Uploaded to ${S3_PATH}"
# ── Verify: объект существует в S3 ────────────────────────────────────────
S3_BYTES=$(mc stat --json "beget/${S3_BUCKET}/${S3_KEY}" 2>/dev/null | grep -o '"size":[0-9]*' | head -1 | cut -d: -f2 || true)
if [ -z "${S3_BYTES}" ]; then
echo "[$(date)] FAILED: объект не найден в S3 после загрузки: ${S3_PATH}"
log_backup "failed" "${ARCHIVE_SIZE}" "${ARCHIVE_BYTES}" "${S3_PATH}" "upload not verified"
exit 1
fi
if [ "${S3_BYTES}" != "${ARCHIVE_BYTES}" ]; then
echo "[$(date)] WARN: размер в S3 (${S3_BYTES}) не совпадает с локальным (${ARCHIVE_BYTES})"
fi
# ── Cleanup local ─────────────────────────────────────────────────────────
rm -f "${ARCHIVE_FILE}"
echo "[$(date)] Local cleanup done"
# ── Retention: delete S3 code backups older than RETENTION_DAYS ───────────
echo "[$(date)] Cleaning S3 code backups older than ${RETENTION_DAYS} days..."
CUTOFF=$(date -d "-${RETENTION_DAYS} days" +%Y-%m-%d)
{ mc ls "beget/${S3_BUCKET}/${S3_PREFIX}/code/" 2>/dev/null || true; } \
| awk '{print $NF}' \
| while read -r fname; do
file_date=$(echo "$fname" | grep -oP '\d{4}-\d{2}-\d{2}' || echo "")
if [ -n "$file_date" ] && [[ "$file_date" < "$CUTOFF" ]]; then
mc rm "beget/${S3_BUCKET}/${S3_PREFIX}/code/${fname}" --quiet 2>/dev/null
echo "[$(date)] Deleted old backup: ${fname}"
fi
done
log_backup "success" "${ARCHIVE_SIZE}" "${ARCHIVE_BYTES}" "${S3_PATH}" "source code"
echo "[$(date)] Code backup complete."