#!/bin/bash
# =============================================================================
# Scheduled master-data sync: fetch the feed, then apply it.
#
# Shipped in muli-neo-database, run by muli-db-seed.timer. Not normally run by
# hand - use muli-db-seed-pull and muli-db-seed directly for that, which report
# more and decide less.
#
# This exists because the two halves need different failure behaviour when
# nobody is watching:
#
#   * A container with no deploy key is not broken, it is unprovisioned. That
#     is a deliberate state - see provision_seed_access.py - so the timer says
#     so once and exits 0 rather than failing every night forever.
#   * A pull failure does not skip the apply. Data already on disk may still be
#     unapplied from a previous run whose apply stage died, and applying an
#     already-current revision is a no-op. Both stages run; the exit code
#     reflects either failing, so `systemctl status` and the journal show it.
# =============================================================================
set -uo pipefail

CONF="${MULI_SEED_CONF:-/etc/muli/seed.conf}"
LOCK="${MULI_SEED_LOCK:-/run/muli-db-seed-sync.lock}"
BIN_DIR="${MULI_BIN_DIR:-/usr/local/muli/bin}"

log()  { echo "[muli-db-seed-sync] $*"; }
warn() { echo "[muli-db-seed-sync] WARNING: $*" >&2; }

# Serialise. A daily timer against a run measured at about ninety seconds should
# never overlap, but a wedged run must not stack up behind itself - and the
# apply stage writes to business tables, so two at once is not a risk worth
# leaving open. Non-blocking: if one is already running, this one is redundant.
#
# Locking on a held file descriptor rather than re-execing through flock. The
# `exec flock ... "$0"` form cannot report this case: exec replaces the shell,
# so nothing survives to run a fallback, and the script exits with flock's 1 -
# which would mark the unit failed every time the lock was merely busy. Being
# locked out is a normal outcome, not a failure. The lock releases when the
# process exits and the descriptor closes.
exec 9>"$LOCK"
if ! flock --nonblock 9; then
    log "another sync is already running; nothing to do"
    exit 0
fi

# Unprovisioned is a state, not a fault. Report it once per run and stop:
# failing nightly would train whoever reads the journal to ignore it.
# shellcheck source=/dev/null
[ -f "$CONF" ] && . "$CONF"

if [ -z "${SEED_REMOTE:-}" ]; then
    log "no seed feed configured in ${CONF}; nothing to sync"
    exit 0
fi
if [ -z "${SEED_SSH_KEY:-}" ] || [ ! -f "${SEED_SSH_KEY}" ]; then
    log "no deploy key installed; this container is not provisioned for the seed feed"
    log "provision it with provision_seed_access.py, then this timer starts working"
    exit 0
fi

rc=0

log "fetching the feed"
if ! "${BIN_DIR}/muli-db-seed-pull"; then
    # Deliberately not fatal - see the header. Whatever is already on disk may
    # still need applying.
    warn "fetch failed; applying whatever revision is already on disk"
    rc=1
fi

log "applying"
if ! "${BIN_DIR}/muli-db-seed"; then
    warn "apply failed"
    rc=1
fi

[ "$rc" -eq 0 ] && log "sync complete" || warn "sync finished with errors"
exit "$rc"
