#!/usr/bin/env bash # glchat: бэкап БД и файлов с age-шифрованием (AGENT.md 10.8). set -euo pipefail GLCHAT_ETC_DIR="${GLCHAT_ETC_DIR:-/opt/glchat}" GLCHAT_DATA_DIR="${GLCHAT_DATA_DIR:-${GLCHAT_ETC_DIR}/data}" ENV_FILE="${GLCHAT_ETC_DIR}/.env" BACKUP_DIR="${GLCHAT_DATA_DIR}/backups" KEEP_DAYS_DEFAULT=7 log() { printf '[backup] %s\n' "$*"; } die() { printf '[backup] ошибка: %s\n' "$*" >&2 exit 1 } [ -f "$ENV_FILE" ] || die "не найден ${ENV_FILE}" # shellcheck disable=SC1090 set -a && . "$ENV_FILE" && set +a KEEP_DAYS="${BACKUP_KEEP_DAYS:-$KEEP_DAYS_DEFAULT}" AGE_RECIPIENT="${AGE_RECIPIENT:-}" STAMP="$(date -u +%Y%m%dT%H%M%SZ)" WORK_DIR="$(mktemp -d)" ARCHIVE="${BACKUP_DIR}/glchat-${STAMP}.tar.gz" TARGET="${ARCHIVE}.age" cleanup() { rm -rf "$WORK_DIR"; } trap cleanup EXIT mkdir -p "$BACKUP_DIR" chmod 700 "$BACKUP_DIR" log "дамп SQLite (sqlite3 .backup)" if command -v sqlite3 >/dev/null 2>&1; then sqlite3 "${GLCHAT_DATA_DIR}/db/glchat.db" ".backup '${WORK_DIR}/glchat.db'" || die "не удалось сделать дамп базы" else docker exec glchat-app true 2>/dev/null || die "sqlite3 не установлен и контейнер недоступен" docker cp glchat-app:/app/data/db/glchat.db "${WORK_DIR}/glchat.db" fi log "копируем файлы" mkdir -p "${WORK_DIR}/files" if [ -d "${GLCHAT_DATA_DIR}/files" ]; then if command -v rsync >/dev/null 2>&1; then rsync -a --delete "${GLCHAT_DATA_DIR}/files/" "${WORK_DIR}/files/" else cp -a "${GLCHAT_DATA_DIR}/files/." "${WORK_DIR}/files/" fi fi log "сохраняем конфиги (без секретов)" mkdir -p "${WORK_DIR}/config" cp -a "${GLCHAT_ETC_DIR}/compose.yaml" "${WORK_DIR}/config/" 2>/dev/null || true cp -a "${GLCHAT_ETC_DIR}/livekit" "${WORK_DIR}/config/" 2>/dev/null || true cp -a "${GLCHAT_ETC_DIR}/external-proxy.conf" "${WORK_DIR}/config/" 2>/dev/null || true cat >"${WORK_DIR}/MANIFEST.txt" </dev/null 2>&1 || die "age не установлен, а AGE_RECIPIENT задан" age -r "$AGE_RECIPIENT" -o "$TARGET" "$ARCHIVE" || die "age-шифрование не удалось" rm -f "$ARCHIVE" chmod 600 "$TARGET" log "готово: ${TARGET} ($(du -h "$TARGET" | cut -f1))" else chmod 600 "$ARCHIVE" log "готово (БЕЗ шифрования, AGE_RECIPIENT не задан): ${ARCHIVE}" fi log "ротация: оставляем ${KEEP_DAYS} дн." find "$BACKUP_DIR" -maxdepth 1 -name 'glchat-*.tar.gz*' -mtime "+${KEEP_DAYS}" -print -delete || true log "бэкапов в каталоге: $(find "$BACKUP_DIR" -maxdepth 1 -name 'glchat-*.tar.gz*' | wc -l)"