#!/bin/bash # Pear Admin Flask - SQLite 在线热备脚本 # # 用法: docker exec pear-admin-nas /app/deploy/nas/backup.sh # (也支持本地直接 ./backup.sh) # # 关键:不能用 cp 复制 pear.db —— WAL 模式下 .db 文件单独 cp 不含未刷盘的页。 # 改用 SQLite 自带的 `sqlite3 pear.db ".backup target"` 或 # `VACUUM INTO 'target'`,两者都在 SQLite 内部完成拷贝,含 WAL 的一致性快照。 # # 产物:${DATA_DIR}/backup/pear-YYYYMMDD_HHMMSS.db # 保留:最近 14 份(可通过 BACKUP_KEEP 覆盖) set -e DATA_DIR=${PEAR_DATA_DIR:-/app/data} DB_PATH="${DATA_DIR}/pear.db" BACKUP_DIR="${DATA_DIR}/backup" KEEP=${BACKUP_KEEP:-14} TIMESTAMP=$(date +%Y%m%d_%H%M%S) TARGET="${BACKUP_DIR}/pear-${TIMESTAMP}.db" if [[ ! -f "${DB_PATH}" ]]; then echo "FATAL: 数据库文件不存在: ${DB_PATH}" >&2 exit 1 fi mkdir -p "${BACKUP_DIR}" echo "[backup] db: ${DB_PATH}" echo "[backup] -> : ${TARGET}" # sqlite3 二进制在 Dockerfile 里 apt install 了;镜像外本机有的话也用得上 sqlite3 "${DB_PATH}" ".backup '${TARGET}'" # 压缩一份小的;同时保留原文件方便快速恢复 gzip -f "${TARGET}" # 清理旧备份:保留文件名最近 KEEP 个(含 .gz 后缀) KEPT=0 for f in $(ls -1t "${BACKUP_DIR}"/pear-*.db.gz 2>/dev/null); do KEPT=$((KEPT + 1)) if [[ "${KEPT}" -gt "${KEEP}" ]]; then rm -f "${f}" echo "[backup] removed old: ${f}" fi done # 健康检查:备份文件必须 > 0 字节且能正常打开 LATEST="${BACKUP_DIR}/pear-${TIMESTAMP}.db.gz" if [[ ! -s "${LATEST}" ]]; then echo "FATAL: 备份文件为空: ${LATEST}" >&2 exit 1 fi echo "[backup] done. latest = ${LATEST}" echo "[backup] current keep: $(ls -1 "${BACKUP_DIR}"/pear-*.db.gz 2>/dev/null | wc -l) of ${KEEP}"