#!/usr/bin/env bash # HIY daily backup script # # What is backed up: # 1. SQLite database (hiy.db) — apps, deploys, env vars, users # 2. Env files directory — decrypted env files written per deploy # 3. Git repos — bare repos for git-push deploys # # Destination options (mutually exclusive; set one): # HIY_BACKUP_DIR — local path (e.g. /mnt/usb/hiy-backups, default /tmp/hiy-backups) # HIY_BACKUP_REMOTE — rclone remote:path (e.g. "b2:mybucket/hiy") # requires rclone installed and configured # # Retention: 30 days (local only; remote retention is managed by the storage provider) # # Suggested cron (run as the same user as hiy-server): # 0 3 * * * /path/to/infra/backup.sh >> /var/log/hiy-backup.log 2>&1 set -euo pipefail # ── Config ───────────────────────────────────────────────────────────────────── HIY_DATA_DIR="${HIY_DATA_DIR:-/data}" BACKUP_DIR="${HIY_BACKUP_DIR:-/tmp/hiy-backups}" BACKUP_REMOTE="${HIY_BACKUP_REMOTE:-}" RETAIN_DAYS="${HIY_BACKUP_RETAIN_DAYS:-30}" TIMESTAMP=$(date +%Y%m%d-%H%M%S) ARCHIVE_NAME="hiy-backup-${TIMESTAMP}.tar.gz" STAGING="${BACKUP_DIR}/staging-${TIMESTAMP}" log() { echo "[hiy-backup] $(date '+%H:%M:%S') $*"; } log "=== HIY Backup ===" log "Data dir: ${HIY_DATA_DIR}" log "Staging: ${STAGING}" # ── 1. Stage files ───────────────────────────────────────────────────────────── mkdir -p "${STAGING}" # SQLite: use the .dump command to produce a portable SQL text dump. if [ -f "${HIY_DATA_DIR}/hiy.db" ]; then log "Dumping SQLite database…" sqlite3 "${HIY_DATA_DIR}/hiy.db" .dump > "${STAGING}/hiy.sql" else log "WARNING: ${HIY_DATA_DIR}/hiy.db not found — skipping SQLite dump" fi # Env files (contain decrypted secrets — handle with care). if [ -d "${HIY_DATA_DIR}/envs" ]; then log "Copying env files…" cp -r "${HIY_DATA_DIR}/envs" "${STAGING}/envs" fi # Bare git repos. if [ -d "${HIY_DATA_DIR}/repos" ]; then log "Copying git repos…" cp -r "${HIY_DATA_DIR}/repos" "${STAGING}/repos" fi # ── 2. Create archive ────────────────────────────────────────────────────────── mkdir -p "${BACKUP_DIR}" ARCHIVE_PATH="${BACKUP_DIR}/${ARCHIVE_NAME}" log "Creating archive: ${ARCHIVE_PATH}" tar -czf "${ARCHIVE_PATH}" -C "${STAGING}" . rm -rf "${STAGING}" ARCHIVE_SIZE=$(du -sh "${ARCHIVE_PATH}" | cut -f1) log "Archive size: ${ARCHIVE_SIZE}" # ── 3. Upload to remote (optional) ──────────────────────────────────────────── if [ -n "${BACKUP_REMOTE}" ]; then if command -v rclone &>/dev/null; then log "Uploading to remote: ${BACKUP_REMOTE}" rclone copy "${ARCHIVE_PATH}" "${BACKUP_REMOTE}/" log "Upload complete." else log "WARNING: HIY_BACKUP_REMOTE is set but rclone is not installed — skipping upload" log "Install rclone: https://rclone.org/install/" fi fi # ── 4. Rotate old local backups ──────────────────────────────────────────────── log "Removing local backups older than ${RETAIN_DAYS} days…" find "${BACKUP_DIR}" -maxdepth 1 -name 'hiy-backup-*.tar.gz' \ -mtime "+${RETAIN_DAYS}" -delete REMAINING=$(find "${BACKUP_DIR}" -maxdepth 1 -name 'hiy-backup-*.tar.gz' | wc -l) log "Local backups retained: ${REMAINING}" log "=== Backup complete: ${ARCHIVE_NAME} ==="