#!/usr/bin/env bash # ============================================================= # GoToSocial backup: SQLite consistent + медиа → backups/ # + копия на Яндекс.Диск (ротация 7 дней локально / 30 на ЯД) # # Топология: /opt/gotosocial/data → /gotosocial/storage (контейнер) # data/sqlite.db + media лежат прямо в data/ (storage-маунт) # SQLite: Backup API через python3 (sqlite3 CLI не установлен) # Тихий: stdout пуст при успехе (для no_agent cron), не-ноль при ошибке # ============================================================= set -euo pipefail BASE_DIR="/opt/gotosocial" DATA_DIR="${BASE_DIR}/data" BACKUP_DIR="${BASE_DIR}/backups" YANDEX_MOUNT="/mnt/yandex-disk" YANDEX_DIR="${YANDEX_MOUNT}/backup/gotosocial-backups" STAGE="${BACKUP_DIR}/stage" TS="$(date +%Y%m%d_%H%M%S)" mkdir -p "${BACKUP_DIR}" rm -rf "${STAGE}" && mkdir -p "${STAGE}" # --- Компактный снапшот БД через SQLite Backup API (online, без конфликтов) --- python3 - "${DATA_DIR}/sqlite.db" "${STAGE}/sqlite.db" <<'PYEOF' import sqlite3, sys src, dst = sys.argv[1], sys.argv[2] s = sqlite3.connect(src) d = sqlite3.connect(dst) with d: s.backup(d) d.close() s.close() PYEOF # --- Медиа копируем в stage (без WAL/SHM, с сохранением структуры) --- # Исключаем служебные sqlite-файлы из data/, чтобы не дублировать БД (cd "${DATA_DIR}" && tar -cf - --exclude='sqlite.db*' .) | tar -xf - -C "${STAGE}" # --- Итоговый архив: consistent db + медиа --- ARCHIVE="${BACKUP_DIR}/gotosocial_${TS}.tar.gz" tar -C "${STAGE}" -czf "${ARCHIVE}" . # --- Архив секретов (.env) отдельно --- ENV_ARCHIVE="${BACKUP_DIR}/gotosocial_env_${TS}.tar.gz" tar -C "${BASE_DIR}" -czf "${ENV_ARCHIVE}" .env # --- Копия на Яндекс.Диск --- if ! mountpoint -q "${YANDEX_MOUNT}"; then echo "ERROR: Яндекс.Диск не примонтирован (${YANDEX_MOUNT})" >&2 exit 1 fi mkdir -p "${YANDEX_DIR}" cp -p "${ARCHIVE}" "${ENV_ARCHIVE}" "${YANDEX_DIR}/" # --- Ротация: локально 7 дней, ЯД 30 дней --- find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'gotosocial_*' -mtime +7 -delete find "${YANDEX_DIR}" -maxdepth 1 -type f -name 'gotosocial_*' -mtime +30 -delete rm -rf "${STAGE}" # Успех — тихо (для no_agent cron) exit 0