Repository Public
1#!/bin/bash2# CLIP nightly backup: PostgreSQL dump + incremental copy of the asset store, optional off-node copy.3# Run by the worker at 04:20 (or by hand). Exits non-zero on any failure.4set -euo pipefail5export PATH="/opt/homebrew/opt/postgresql@17/bin:/opt/homebrew/bin:/usr/bin:/bin:$PATH"6cd "$(dirname "$0")/.."7set -a; . ./.env; set +a89: "${DATABASE_URL:?}" "${ASSET_ROOT:?}"10BACKUP_DIR="${BACKUP_DIR:-$HOME/backups/clip-spboucher-ai}"11STAMP="$(date +%Y%m%d-%H%M%S)"12mkdir -p "$BACKUP_DIR/db" "$BACKUP_DIR/assets"13chmod 700 "$BACKUP_DIR"1415# 1. Database (custom format, compressed). Written to a temp name first so a partial dump is never mistaken for a backup.16pg_dump --format=custom --no-owner --file="$BACKUP_DIR/db/.clip-$STAMP.dump.part" "$DATABASE_URL"17mv "$BACKUP_DIR/db/.clip-$STAMP.dump.part" "$BACKUP_DIR/db/clip-$STAMP.dump"18pg_restore --list "$BACKUP_DIR/db/clip-$STAMP.dump" > /dev/null # readable?1920# 2. Assets: content-addressed, so the copy is append-only and incremental.21rsync -a "$ASSET_ROOT/objects/" "$BACKUP_DIR/assets/objects/"2223# 3. Retention: keep 14 daily dumps.24ls -1t "$BACKUP_DIR/db"/clip-*.dump 2>/dev/null | tail -n +15 | while read -r old; do unlink "$old"; done2526# 4. Optional off-node copy (rsync over SSH), e.g. BACKUP_RSYNC_TARGET=user@192.168.2.80:backups/clip-spboucher-ai27if [ -n "${BACKUP_RSYNC_TARGET:-}" ]; then28 rsync -a -e "ssh -o BatchMode=yes -o ConnectTimeout=15" "$BACKUP_DIR/" "$BACKUP_RSYNC_TARGET/"29 OFFSITE="copied to ${BACKUP_RSYNC_TARGET%%:*}"30else31 OFFSITE="no off-node target configured"32fi3334SIZE="$(du -sh "$BACKUP_DIR" | cut -f1)"35echo "backup ok $STAMP: db $(du -h "$BACKUP_DIR/db/clip-$STAMP.dump" | cut -f1), total $SIZE, $OFFSITE"36