backup_git.bracken.jp.sh (2532B)
1 #!/bin/sh 2 # Backup script for git repos, invoked from Synology Task Scheduler. 3 # 4 # Invoke with a backup tag, typically "daily", "weekly", "monthly" and the 5 # number of previous backups to be retained. Older backups that fall out of 6 # retention are deleted. 7 # 8 # backup_git.bracken.jp.sh daily 10 9 # backup_git.bracken.jp.sh weekly 100 10 11 # Fail if any step fails, so we don't collect half-baked backups. 12 set -e 13 14 TAG=$1 15 RETAIN_COUNT=$2 16 17 # Check script arguments. 18 if [ -z "$TAG" -o -z "$RETAIN_COUNT" ]; then 19 echo "usage: $0 BACKUP_TAG RETAIN_COUNT" 20 exit 1 21 fi 22 23 USER=chris 24 HOST=git.bracken.jp 25 PORT=1022 26 27 # NOTE: the remote side runs a FORCED COMMAND configured in the server's 28 # ~/.ssh/authorized_keys: 29 # 30 # restrict,command="tar Jcf - -C /zroot/data git-repos" ssh-ed25519 ... 31 # 32 # Whatever command this script sends is ignored. The archive contains 33 # git-repos/ at the top level (previously '*.git' and 'git_infra'), so restore 34 # paths differ from archives taken before 2026-08. 35 # 36 # To change WHAT is backed up, edit authorized_keys on the server, not here. 37 38 BACKUP_OUT_DIR="/volume1/Backups/git.bracken.jp/$TAG" 39 BACKUP_OUT_FILE="$(date +"%Y-%m-%d_%H.%M").txz" 40 # Deliberately on /volume1 rather than /tmp to ensure we have enough space. 41 # Deliberately OUTSIDE $BACKUP_OUT_DIR, so a leftover .part file can never be 42 # counted by the retention find below. 43 BACKUP_TMP_FILE="/volume1/Backups/.git-backup-$TAG.$$.part" 44 45 echo "[git backup] Backup of $HOST ($TAG) starting" 46 mkdir -p "$BACKUP_OUT_DIR" 47 48 # Write to a temp file outside the backup directory, and only move it into 49 # place on success. 50 if ssh "$USER@$HOST" -p "$PORT" > "$BACKUP_TMP_FILE"; then 51 mv "$BACKUP_TMP_FILE" "$BACKUP_OUT_DIR/$BACKUP_OUT_FILE" 52 echo "[git backup] Wrote archive $BACKUP_OUT_DIR/$BACKUP_OUT_FILE" 53 else 54 echo "[git backup] ERROR: transfer failed, no archive written" >&2 55 rm -f "$BACKUP_TMP_FILE" 56 exit 1 57 fi 58 59 # Sanity check: a valid xz archive is never this small. Catches the case where 60 # the transfer "succeeded" but produced nothing useful. 61 if [ "$(stat -c %s "$BACKUP_OUT_DIR/$BACKUP_OUT_FILE")" -lt 1000000 ]; then 62 echo "[git backup] ERROR: archive suspiciously small, keeping it but not pruning" >&2 63 exit 1 64 fi 65 66 # Delete archives past the retain count. 67 EXPIRED_ARCHIVES="$(find "$BACKUP_OUT_DIR" -maxdepth 1 -type f \ 68 | sort -r | tail -n +$(($RETAIN_COUNT + 1)))" 69 for f in $EXPIRED_ARCHIVES; do 70 echo "[git backup] Deleting expired archive: $f" 71 rm -f "$f" 72 done 73 74 echo "[git backup] Backup of $HOST ($TAG) complete"