110 lines
3.0 KiB
Bash
110 lines
3.0 KiB
Bash
#!/usr/bin/env bash
|
|
# Daily backup: SQLite DB + static/images → /root/backups/<instance>/<YYYY-MM-DD>/
|
|
# Prune backup folders older than RETENTION_DAYS (default 30).
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
cd "$PROJECT_DIR"
|
|
|
|
BACKUP_ROOT="${BACKUP_ROOT:-/root/backups}"
|
|
RETENTION_DAYS="${RETENTION_DAYS:-30}"
|
|
INSTANCE_NAME="${BACKUP_INSTANCE:-$(basename "$PROJECT_DIR")}"
|
|
TZ_NAME="${BACKUP_TZ:-Asia/Shanghai}"
|
|
|
|
log() {
|
|
printf '[%s] %s\n' "$(TZ="$TZ_NAME" date '+%Y-%m-%d %H:%M:%S %Z')" "$*"
|
|
}
|
|
|
|
read_env_var() {
|
|
local key="$1"
|
|
local default="$2"
|
|
local line
|
|
if [[ ! -f .env ]]; then
|
|
printf '%s' "$default"
|
|
return
|
|
fi
|
|
line="$(grep -E "^${key}=" .env 2>/dev/null | tail -1 || true)"
|
|
if [[ -z "$line" ]]; then
|
|
printf '%s' "$default"
|
|
return
|
|
fi
|
|
printf '%s' "${line#*=}" | tr -d '\r'
|
|
}
|
|
|
|
resolve_project_path() {
|
|
local p="$1"
|
|
if [[ "$p" == /* ]]; then
|
|
printf '%s' "$p"
|
|
else
|
|
printf '%s' "$PROJECT_DIR/$p"
|
|
fi
|
|
}
|
|
|
|
prune_old_backups() {
|
|
local base="$BACKUP_ROOT/$INSTANCE_NAME"
|
|
[[ -d "$base" ]] || return 0
|
|
local cutoff
|
|
cutoff="$(TZ="$TZ_NAME" date -d "-${RETENTION_DAYS} days" +%Y-%m-%d 2>/dev/null || true)"
|
|
if [[ -z "$cutoff" ]]; then
|
|
find "$base" -mindepth 1 -maxdepth 1 -type d -mtime +"$RETENTION_DAYS" -print0 |
|
|
xargs -r -0 rm -rf
|
|
return 0
|
|
fi
|
|
local dir name
|
|
for dir in "$base"/*/; do
|
|
[[ -d "$dir" ]] || continue
|
|
name="$(basename "$dir")"
|
|
[[ "$name" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || continue
|
|
if [[ "$name" < "$cutoff" ]]; then
|
|
log "prune: remove $dir (older than ${RETENTION_DAYS} days)"
|
|
rm -rf "$dir"
|
|
fi
|
|
done
|
|
}
|
|
|
|
DB_REL="$(read_env_var DB_PATH crypto.db)"
|
|
UPLOAD_REL="$(read_env_var UPLOAD_DIR static/images)"
|
|
BACKUP_ROOT="$(read_env_var BACKUP_ROOT "$BACKUP_ROOT")"
|
|
RETENTION_DAYS="$(read_env_var BACKUP_RETENTION_DAYS "$RETENTION_DAYS")"
|
|
INSTANCE_NAME="$(read_env_var BACKUP_INSTANCE "$INSTANCE_NAME")"
|
|
|
|
DB_PATH="$(resolve_project_path "$DB_REL")"
|
|
UPLOAD_DIR="$(resolve_project_path "$UPLOAD_REL")"
|
|
DATE_TAG="$(TZ="$TZ_NAME" date +%Y-%m-%d)"
|
|
DEST="$BACKUP_ROOT/$INSTANCE_NAME/$DATE_TAG"
|
|
|
|
if [[ ! -f "$DB_PATH" ]]; then
|
|
log "error: database not found: $DB_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$DEST"
|
|
log "start backup instance=$INSTANCE_NAME dest=$DEST"
|
|
|
|
if command -v sqlite3 >/dev/null 2>&1; then
|
|
sqlite3 "$DB_PATH" ".backup '$DEST/crypto.db'"
|
|
log "db: sqlite3 backup -> $DEST/crypto.db"
|
|
else
|
|
cp -a "$DB_PATH" "$DEST/crypto.db"
|
|
log "db: cp -> $DEST/crypto.db (sqlite3 not installed)"
|
|
fi
|
|
|
|
if [[ -d "$UPLOAD_DIR" ]]; then
|
|
tar -czf "$DEST/static_images.tar.gz" -C "$(dirname "$UPLOAD_DIR")" "$(basename "$UPLOAD_DIR")"
|
|
log "images: $UPLOAD_DIR -> $DEST/static_images.tar.gz"
|
|
else
|
|
log "warn: upload dir missing, skip images: $UPLOAD_DIR"
|
|
fi
|
|
|
|
{
|
|
echo "instance=$INSTANCE_NAME"
|
|
echo "project_dir=$PROJECT_DIR"
|
|
echo "backup_date=$DATE_TAG"
|
|
echo "db_path=$DB_PATH"
|
|
echo "upload_dir=$UPLOAD_DIR"
|
|
} >"$DEST/manifest.txt"
|
|
|
|
prune_old_backups
|
|
log "done"
|