#!/bin/bash # tver DBバックアップ(Synology ds923p / DSMタスクスケジューラで日次実行) # # 配置先: /volume1/docker/tver-db/backup-db.sh # DSM設定: コントロールパネル → タスクスケジューラ → 作成 # → スケジュールされたタスク → ユーザー定義のスクリプト # ユーザーはroot、実行周期は毎日とする。 # 実行コマンド: bash /volume1/docker/tver-db/backup-db.sh # # コンテナ内のpg_dumpを使い、サーバーとクライアントのバージョンを合わせる。 # PostgreSQLのローカル接続を使うため、DBパスワードはスクリプトへ書かない。 set -euo pipefail BACKUP_DIR="${BACKUP_DIR:-/volume1/backups/tver}" KEEP_DAYS="${KEEP_DAYS:-14}" CONTAINER="${CONTAINER:-tver-postgres}" DB_USER="${DB_USER:-tver}" DB_NAME="${DB_NAME:-tver}" DOCKER="${DOCKER:-$(command -v docker || printf '/usr/local/bin/docker')}" STAMP="${STAMP:-$(date +%Y%m%d_%H%M%S)}" OUT="${BACKUP_DIR}/tver_${STAMP}.sql.gz" RAW_TMP="${BACKUP_DIR}/.tver_${STAMP}.$$.sql" GZ_TMP="${OUT}.tmp.$$" case "$KEEP_DAYS" in ''|*[!0-9]*) echo "ERROR: KEEP_DAYS must be a non-negative integer: $KEEP_DAYS" >&2 exit 1 ;; esac mkdir -p "$BACKUP_DIR" umask 077 cleanup() { rm -f "$RAW_TMP" "$GZ_TMP" } trap cleanup EXIT # 先に非圧縮の一時ファイルへ出し、pg_dump成功と空でないことを確認する。 "$DOCKER" exec "$CONTAINER" pg_dump -U "$DB_USER" -d "$DB_NAME" > "$RAW_TMP" if [[ ! -s "$RAW_TMP" ]]; then echo "ERROR: dump is empty" >&2 exit 1 fi gzip -9 -c "$RAW_TMP" > "$GZ_TMP" if [[ ! -s "$GZ_TMP" ]]; then echo "ERROR: compressed dump is empty" >&2 exit 1 fi # 完成ファイルは上書きしない。同名が同時に作られても、ハードリンクの # 作成に成功した一方だけを公開する。一時ファイルは同じディレクトリに # あるため同一ファイルシステム内でリンクできる。 if ! ln "$GZ_TMP" "$OUT"; then echo "ERROR: backup already exists or could not be published: $OUT" >&2 exit 1 fi rm -f "$GZ_TMP" "$RAW_TMP" # KEEP_DAYS日より古い完了済みバックアップだけを削除する。 find "$BACKUP_DIR" -name 'tver_*.sql.gz' -type f -mtime "+$KEEP_DAYS" -delete SIZE_BYTES="$(wc -c < "$OUT" | tr -d '[:space:]')" echo "OK: $OUT ($SIZE_BYTES bytes)"