#!/usr/bin/env bash # LibreDesk 定时备份:Postgres dump + uploads + 密钥配置,保留 14 天。 # 用法:./scripts/backup.sh # cron:0 3 * * * cd /home/dev/code/yehwang-ai/libredesk && ./scripts/backup.sh >> logs/backup.log 2>&1 set -euo pipefail cd "$(dirname "$0")/.." TS="$(date +%Y%m%d-%H%M%S)" BACKUP_DIR="backups" LOG_PREFIX="[backup $(date -Is)]" mkdir -p "$BACKUP_DIR" logs DB_FILE="$BACKUP_DIR/db-$TS.dump" UPLOADS_FILE="$BACKUP_DIR/uploads-$TS.tar.gz" CONFIG_FILE="$BACKUP_DIR/config.toml.$TS" ENV_FILE="$BACKUP_DIR/env.$TS" POSTGRES_USER="${POSTGRES_USER:-libredesk}" POSTGRES_DB="${POSTGRES_DB:-libredesk}" echo "$LOG_PREFIX start" # 1) 数据库(custom format,可直接 pg_restore) docker compose exec -T db pg_dump -U "$POSTGRES_USER" -Fc "$POSTGRES_DB" > "$DB_FILE" # 2) 上传文件 tar -czf "$UPLOADS_FILE" -C . uploads # 3) 配置文件(含加密密钥 / DB 密码,保持 600) cp -p config.toml "$CONFIG_FILE" cp -p .env "$ENV_FILE" chmod 600 "$CONFIG_FILE" "$ENV_FILE" # 4) 校验 dump:先看 PGDMP 头,再用 pg_restore 读出 TOC(容器内 bind mount,避免 stdin 不可 seek) if [ "$(head -c 5 "$DB_FILE")" != "PGDMP" ]; then echo "$LOG_PREFIX ERROR: dump 文件头不是 PGDMP:$DB_FILE" >&2 exit 1 fi TOC_COUNT="$(docker run --rm -v "$PWD/$BACKUP_DIR:/b:ro" "${VERIFY_IMAGE:-pgvector/pgvector:pg18}" pg_restore -l "/b/$(basename "$DB_FILE")" | grep -cE '^[0-9]+;' || true)" if [ "${TOC_COUNT:-0}" -lt 100 ]; then echo "$LOG_PREFIX ERROR: dump 校验失败(TOC 条目=$TOC_COUNT),保留文件供排查:$DB_FILE" >&2 exit 1 fi # 5) 保留 14 天 find "$BACKUP_DIR" -maxdepth 1 -type f \ \( -name 'db-*.dump' -o -name 'uploads-*.tar.gz' -o -name 'config.toml.*' -o -name 'env.*' \) \ -mtime +14 -delete echo "$LOG_PREFIX ok db=$(du -h "$DB_FILE" | cut -f1) uploads=$(du -h "$UPLOADS_FILE" | cut -f1) toc=$TOC_COUNT"