## Env var encryption at rest (AES-256-GCM) - server/src/crypto.rs: new module — encrypt/decrypt with AES-256-GCM Key = SHA-256(HIY_SECRET_KEY); non-prefixed values pass through transparently for zero-downtime migration - Cargo.toml: aes-gcm = "0.10" - routes/envvars.rs: encrypt on SET; list returns masked values (••••) - routes/databases.rs: pg_password and DATABASE_URL stored encrypted - routes/ui.rs: decrypt pg_password when rendering DB card - builder.rs: decrypt env vars when writing the .env file for containers - .env.example: add HIY_SECRET_KEY entry ## Per-app resource limits - apps table: memory_limit (default 512m) + cpu_limit (default 0.5) added via idempotent ALTER TABLE in db.rs migration - models.rs: App, CreateApp, UpdateApp gain memory_limit + cpu_limit - routes/apps.rs: persist limits on create, update via PUT - builder.rs: pass MEMORY_LIMIT + CPU_LIMIT to build script - builder/build.sh: use $MEMORY_LIMIT / $CPU_LIMIT in podman run (replaces hardcoded --cpus="0.5"; --memory now also set) ## Monitoring (opt-in compose profile) - infra/docker-compose.yml: gatus + netdata under `monitoring` profile Enable: podman compose --profile monitoring up -d Gatus on :8080, Netdata on :19999 - infra/gatus.yml: Gatus config checking HIY /api/status every minute ## Backup cron job - infra/backup.sh: dumps SQLite, copies env files + git repos into a dated .tar.gz; optional rclone upload; 30-day local retention Suggested cron: 0 3 * * * /path/to/infra/backup.sh https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
90 lines
3.8 KiB
Bash
Executable file
90 lines
3.8 KiB
Bash
Executable file
#!/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} ==="
|