#!/bin/bash # ============================================================================= # DashCaddy Gitea — Off-host backup to Dropbox # ============================================================================= # - Stops gitea container briefly to ensure SQLite DB consistency # - Syncs /var/lib/docker/volumes/gitea-data to dropbox:/Apps/dashcaddy-gitea-backups// # - Date-stamped snapshots (one per day), kept for 7 days locally # - Restarts gitea even if sync fails # - Logs to /var/log/gitea-backup.log # ============================================================================= set -u # don't use -e: we want to always restart gitea LOG=/var/log/gitea-backup.log DATA_SRC=/var/lib/docker/volumes/gitea-data/_data DEST="dropbox:/Apps/dashcaddy-gitea-backups" TODAY=$(date -u +%Y-%m-%d) BACKUP_PATH="${DEST}/${TODAY}" RETENTION_DAYS=7 log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG"; } log "=== Backup start ===" # 0. Sanity checks if [ ! -d "$DATA_SRC" ]; then log "ERROR: data dir $DATA_SRC missing" exit 1 fi # 1. Stop gitea to flush SQLite log "Stopping gitea container..." docker stop gitea >> "$LOG" 2>&1 STOP_RC=$? if [ $STOP_RC -ne 0 ]; then log "WARNING: docker stop returned $STOP_RC — container may not be running" fi # 2. Sync (use copy so source files are preserved as-is, no --delete) log "Syncing $DATA_SRC -> $BACKUP_PATH" rclone copy "$DATA_SRC" "$BACKUP_PATH" \ --transfers 4 \ --checkers 8 \ --retries 3 \ --low-level-retries 10 \ --stats 30s \ --log-file "$LOG" \ --log-level INFO SYNC_RC=$? # 3. Always restart gitea log "Starting gitea container..." docker start gitea >> "$LOG" 2>&1 START_RC=$? # Wait for gitea to be ready for i in {1..30}; do if curl -sf http://localhost:3000/api/v1/version > /dev/null 2>&1; then log "Gitea is up after ${i}s" break fi sleep 1 done # 4. Cleanup old backups (older than RETENTION_DAYS) log "Pruning local + remote snapshots older than ${RETENTION_DAYS} days..." CUTOFF=$(date -u -d "${RETENTION_DAYS} days ago" +%Y-%m-%d) rclone lsf "$DEST/" --dirs-only 2>/dev/null | while read -r d; do # rclone returns names with trailing / name="${d%/}" if [[ "$name" < "$CUTOFF" ]]; then log " removing old: $name" rclone purge "${DEST}/${name}" >> "$LOG" 2>&1 fi done # 5. Report if [ $SYNC_RC -eq 0 ] && [ $START_RC -eq 0 ]; then log "=== Backup OK ===" exit 0 else log "=== Backup completed with errors (sync=$SYNC_RC, start=$START_RC) ===" exit 1 fi