#!/bin/bash
# pre-backup.sh — ana-docker.
# Runs as root from resticprofile's `run-before`, before `restic backup`.
#
# Produces DB dumps in /var/lib/restic/stage/ so the nightly restic
# snapshot captures consistent point-in-time data for services whose
# raw volume files are not safe to back up live.
#
# Containers handled here:
#   - synapse-db        (internal Postgres 16 — pg_dump)
#   - seafile-mysql     (internal MariaDB 10.6 — mysqldump)
#   - vaultwarden       (external Postgres on PFI-Postgres 10.250.50.80)
#   - gitea             (`gitea dump` captures DB + repos + config + LFS)
#   - openwebui         (local SQLite × 2 — main db + ChromaDB vector store)
#
# External DB credentials live in /etc/restic/dbcreds.env (root:600).
# Template: configs/restic/ana-docker/dbcreds.env.example in the repo.
#
# Required tooling on the host:
#   docker                  — always
#   pg_dump                 — for vaultwarden external Postgres; install via
#                             `apt install postgresql-client`. Without it,
#                             the vaultwarden block logs a warning and skips.
#
# Intentionally NOT handled:
#   - mattermost (retired 2026-04-21 — stack dir lingers but is not running)
#
# Idempotent: clears and recreates its staging files each run.
# Errors in individual blocks are logged as WARN but don't abort the whole
# script — partial dumps are better than no dumps.

set -euo pipefail

STAGE=/var/lib/restic/stage
install -d -o root -g root -m 0700 "$STAGE"

log()  { printf '%s pre-backup(ana-docker): %s\n' "$(date -Is)" "$*"; }
warn() { log "WARN: $*" >&2; }

# Purge previous stage so stale dumps don't pile up into the snapshot.
find "$STAGE" -mindepth 1 -maxdepth 1 -exec rm -rf {} +

# Load external-DB creds. Silently skipped if missing — individual blocks
# that need them will log their own WARN.
if [ -r /etc/restic/dbcreds.env ]; then
  set -a; . /etc/restic/dbcreds.env; set +a
fi

# ---------- synapse (internal Postgres) ---------------------------------------
if docker inspect synapse-db >/dev/null 2>&1; then
  log "dumping synapse postgres"
  docker exec synapse-db \
    pg_dump -U synapse -d synapse -Fc --clean --if-exists \
    > "$STAGE/synapse.pg_dump" \
    || warn "synapse pg_dump failed"
else
  log "skip synapse: container not present"
fi

# ---------- seafile (internal MariaDB) ----------------------------------------
if docker inspect seafile-mysql >/dev/null 2>&1; then
  log "dumping seafile mariadb"
  docker exec seafile-mysql sh -c \
    'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases --single-transaction --quick 2>/dev/null' \
    | gzip -c > "$STAGE/seafile.sql.gz" \
    || warn "seafile mysqldump failed"
else
  log "skip seafile: container not present"
fi

# ---------- vaultwarden (external Postgres on PFI-Postgres 10.250.50.80) ------
# The vault moved from SQLite to external Postgres (date unclear). Any
# /data/db.sqlite3* files in the container are stale leftovers and should
# be deleted separately — this hook captures the live Postgres data only.
if docker inspect vaultwarden >/dev/null 2>&1; then
  if [ -z "${VW_PGPASS:-}" ]; then
    warn "vaultwarden: VW_PGPASS unset in /etc/restic/dbcreds.env — skipping"
  elif ! command -v pg_dump >/dev/null 2>&1; then
    warn "vaultwarden: pg_dump not installed — skipping (apt install postgresql-client)"
  else
    log "dumping vaultwarden postgres (external: ${VW_PGHOST}:${VW_PGPORT:-5432})"
    PGPASSWORD="$VW_PGPASS" pg_dump \
      -h "$VW_PGHOST" -p "${VW_PGPORT:-5432}" \
      -U "$VW_PGUSER" -d "$VW_PGDB" \
      -Fc --clean --if-exists \
      > "$STAGE/vaultwarden.pg_dump" \
      || warn "vaultwarden pg_dump failed"
  fi
else
  log "skip vaultwarden: container not present"
fi

# ---------- gitea (native `gitea dump`) ---------------------------------------
# `gitea dump` produces a single archive with the DB dump, repo trees,
# config, LFS objects and attachments. The in-container command reads
# its own DB creds (from GITEA__database__* env vars) — no external
# creds needed.
#
# `--type tar` produces an UNCOMPRESSED tarball. Compressed formats
# (zip/tar.gz) defeat restic's content-defined chunking: each day's
# dump looks completely different to restic even when the underlying
# data barely changed, so repo grows by ~full dump size every day.
# Uncompressed tar lets restic dedup aggressively — after the first
# snapshot, daily incrementals only cost the actual new-data delta.
#
# Tradeoff: on-disk stage file is larger (~2-3x the zip size) but that's
# transient (deleted next run). Repo-side storage is much smaller.
#
# Size trimming flags if the tar becomes excessive: --skip-lfs-data,
# --skip-repository, --skip-attachment-data.
if docker inspect gitea >/dev/null 2>&1; then
  log "dumping gitea (gitea dump, uncompressed tar)"
  if docker exec -u git gitea sh -c \
       'rm -f /tmp/gitea-dump.tar && gitea dump -c /data/gitea/conf/app.ini -f /tmp/gitea-dump.tar --type tar' \
       >/dev/null 2>&1; then
    docker cp gitea:/tmp/gitea-dump.tar "$STAGE/gitea-dump.tar" \
      && docker exec -u git gitea rm -f /tmp/gitea-dump.tar \
      || warn "gitea dump copy/cleanup failed"
  else
    warn "gitea dump command failed"
  fi
else
  log "skip gitea: container not present"
fi

# ---------- openwebui (local SQLite × 2) --------------------------------------
# Two SQLite databases: main app (/app/backend/data/webui.db) and the
# ChromaDB vector store (.../vector_db/chroma.sqlite3). Uses SQLite's
# .backup command for a consistent snapshot if sqlite3 is available in
# the container; falls back to restic's volume-level capture otherwise.
OWUI_CONTAINER=openwebui-open-webui-1
if docker inspect "$OWUI_CONTAINER" >/dev/null 2>&1; then
  if docker exec "$OWUI_CONTAINER" sh -c 'command -v sqlite3 >/dev/null 2>&1'; then
    log "dumping openwebui sqlite (webui.db + chroma.sqlite3) via .backup"
    for pair in \
        "/app/backend/data/webui.db:webui.db" \
        "/app/backend/data/vector_db/chroma.sqlite3:chroma.sqlite3"; do
      src=${pair%:*}; dst=${pair#*:}
      if docker exec "$OWUI_CONTAINER" sqlite3 "$src" ".backup /tmp/$dst" 2>/dev/null; then
        docker cp "$OWUI_CONTAINER:/tmp/$dst" "$STAGE/openwebui.$dst" \
          && docker exec "$OWUI_CONTAINER" rm -f "/tmp/$dst" \
          || warn "openwebui copy/cleanup failed for $dst"
      else
        warn "openwebui .backup failed for $src (missing file or locked?)"
      fi
    done
  else
    warn "openwebui: sqlite3 not in container — relying on restic volume-level capture"
  fi
else
  log "skip openwebui: container not present"
fi

# ---------- summary -----------------------------------------------------------
size=$(du -sh "$STAGE" 2>/dev/null | awk '{print $1}')
count=$(find "$STAGE" -type f | wc -l)
log "stage ready: $count files, $size total"
