main
This commit is contained in:
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# ZBrain Backup
|
||||
# ==============
|
||||
# Делает дамп всех Postgres БД (brainhub + все gbrain_*) и конфигов.
|
||||
# Шифрует архивы через age (нужен age и публичный ключ).
|
||||
#
|
||||
# Запуск из cron, например:
|
||||
# 0 3 * * * root /opt/zbrain/scripts/backup.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Конфигурация
|
||||
# ============================================================
|
||||
BACKUP_DIR="${BACKUP_DIR:-/var/backups/zbrain}"
|
||||
RETENTION_DAYS="${RETENTION_DAYS:-14}"
|
||||
AGE_RECIPIENT_FILE="${AGE_RECIPIENT_FILE:-/etc/zbrain/backup.age.pub}"
|
||||
LOG_FILE="${LOG_FILE:-/var/log/zbrain/backup.log}"
|
||||
ZBRAIN_CONFIG_DIR="/etc/zbrain"
|
||||
ZBRAIN_DATA_DIR="/var/lib/zbrain"
|
||||
|
||||
DATE=$(date +%Y%m%d-%H%M%S)
|
||||
BACKUP_PATH="${BACKUP_DIR}/${DATE}"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
err() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Проверки
|
||||
# ============================================================
|
||||
[[ $EUID -eq 0 ]] || { err "Требуется root"; exit 1; }
|
||||
|
||||
if ! command -v age &>/dev/null; then
|
||||
err "age не установлен. apt install age"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$AGE_RECIPIENT_FILE" ]]; then
|
||||
err "Файл получателя age не найден: $AGE_RECIPIENT_FILE"
|
||||
err "Создай ключ: age-keygen -o /etc/zbrain/backup.age"
|
||||
err "И публичную часть в $AGE_RECIPIENT_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_PATH" "$(dirname "$LOG_FILE")"
|
||||
chmod 700 "$BACKUP_DIR"
|
||||
|
||||
log "=== ZBrain backup: $DATE ==="
|
||||
|
||||
# ============================================================
|
||||
# Postgres dump (каждая БД отдельно)
|
||||
# ============================================================
|
||||
log "Дамп Postgres БД..."
|
||||
|
||||
# Список всех ZBrain БД (brainhub + gbrain_*)
|
||||
DATABASES=$(sudo -u postgres psql -tAc "
|
||||
SELECT datname FROM pg_database
|
||||
WHERE datname = 'brainhub' OR datname LIKE 'gbrain_%'
|
||||
ORDER BY datname
|
||||
")
|
||||
|
||||
for db in $DATABASES; do
|
||||
log " ${db}"
|
||||
dump_file="${BACKUP_PATH}/${db}.sql"
|
||||
sudo -u postgres pg_dump --format=custom --compress=9 --file="${dump_file}.dump" "$db"
|
||||
|
||||
# Шифруем
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "${dump_file}.dump.age" "${dump_file}.dump"
|
||||
rm "${dump_file}.dump"
|
||||
log " ✓ ${db}.sql.dump.age ($(du -h "${dump_file}.dump.age" | cut -f1))"
|
||||
done
|
||||
|
||||
# pg_dumpall для глобальных объектов (roles, tablespaces)
|
||||
log "Дамп глобальных Postgres объектов..."
|
||||
sudo -u postgres pg_dumpall --globals-only > "${BACKUP_PATH}/_globals.sql"
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "${BACKUP_PATH}/_globals.sql.age" "${BACKUP_PATH}/_globals.sql"
|
||||
rm "${BACKUP_PATH}/_globals.sql"
|
||||
|
||||
# ============================================================
|
||||
# Конфиги
|
||||
# ============================================================
|
||||
log "Архив конфигов..."
|
||||
|
||||
tar czf "${BACKUP_PATH}/config.tar.gz" \
|
||||
-C / \
|
||||
etc/zbrain \
|
||||
etc/postgresql/16/main/postgresql.conf \
|
||||
etc/postgresql/16/main/conf.d \
|
||||
etc/postgresql/16/main/pg_hba.conf \
|
||||
etc/systemd/system/zbrain-*.service \
|
||||
etc/nginx 2>/dev/null || true
|
||||
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "${BACKUP_PATH}/config.tar.gz.age" "${BACKUP_PATH}/config.tar.gz"
|
||||
rm "${BACKUP_PATH}/config.tar.gz"
|
||||
|
||||
log "✓ config.tar.gz.age"
|
||||
|
||||
# ============================================================
|
||||
# gbrain data dir (config.json per-brain содержит пароли)
|
||||
# ============================================================
|
||||
log "Архив brain configs..."
|
||||
|
||||
if [[ -d "$ZBRAIN_DATA_DIR/brains" ]]; then
|
||||
tar czf "${BACKUP_PATH}/brains-meta.tar.gz" \
|
||||
-C "$ZBRAIN_DATA_DIR" \
|
||||
--exclude='brains/*/data' \
|
||||
brains/
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "${BACKUP_PATH}/brains-meta.tar.gz.age" "${BACKUP_PATH}/brains-meta.tar.gz"
|
||||
rm "${BACKUP_PATH}/brains-meta.tar.gz"
|
||||
log "✓ brains-meta.tar.gz.age"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Манифест
|
||||
# ============================================================
|
||||
cat > "${BACKUP_PATH}/MANIFEST.txt" <<EOF
|
||||
ZBrain Backup
|
||||
=============
|
||||
Date: $(date -Iseconds)
|
||||
Hostname: $(hostname -f)
|
||||
Postgres version: $(sudo -u postgres psql -tAc 'SHOW server_version')
|
||||
|
||||
Databases:
|
||||
$(echo "$DATABASES" | sed 's/^/ - /')
|
||||
|
||||
Files:
|
||||
$(ls -lah "$BACKUP_PATH" | tail -n +2 | awk '{print " - " $NF " (" $5 ")"}')
|
||||
|
||||
Восстановление:
|
||||
bash /opt/zbrain/scripts/restore.sh ${DATE}
|
||||
EOF
|
||||
|
||||
# ============================================================
|
||||
# Размер итогового бэкапа
|
||||
# ============================================================
|
||||
TOTAL_SIZE=$(du -sh "$BACKUP_PATH" | cut -f1)
|
||||
log "✓ Backup завершён: $BACKUP_PATH ($TOTAL_SIZE)"
|
||||
|
||||
# ============================================================
|
||||
# Retention - удаляем старые бэкапы
|
||||
# ============================================================
|
||||
log "Чистка бэкапов старше $RETENTION_DAYS дней..."
|
||||
find "$BACKUP_DIR" -maxdepth 1 -type d -name '????????-??????' -mtime +$RETENTION_DAYS -exec rm -rf {} \;
|
||||
log "✓ Чистка завершена"
|
||||
|
||||
# ============================================================
|
||||
# (опционально) push на удалённое хранилище
|
||||
# ============================================================
|
||||
# rsync -avz "$BACKUP_PATH" backup@remote-host:/backups/zbrain/
|
||||
# или rclone copy "$BACKUP_PATH" "remote:zbrain-backups/${DATE}"
|
||||
|
||||
log "=== Готово ==="
|
||||
Reference in New Issue
Block a user