#!/bin/bash
# Nightly Forgejo updater for the PACKAGED forgejo (forgejo LXD container on ollivanders).
# The binary is the `forgejo` package from the ctg apt repo (official release, sha256-pinned and
# OpenPGP-verified when it was packaged); this script never touches the binary itself. It only:
#  - applies patch/minor updates within the installed major (a major bump is logged and left for a human),
#  - waits for running CI tasks to drain (a restart mid-job loses the job's final report; Rustinion run, 2026-10-16),
#  - installs the new package, restarts once, health-checks, and rolls back to the previous package on failure.
set -u
STATE=/var/lib/ctg-forgejo-update
mkdir -p "$STATE"
log() { logger -t ctg-forgejo-update "$*"; echo "$*"; }
apt-get update -qq >/dev/null 2>&1 || { log "apt-get update failed, skipping"; exit 0; }
cur=$(dpkg-query -W -f='${Version}' forgejo 2>/dev/null) || { log "forgejo package not installed"; exit 0; }
cand=$(apt-cache policy forgejo | awk '/Candidate:/{print $2}')
[[ -n "$cand" && "$cand" != "(none)" ]] || { log "no candidate, skipping"; exit 0; }
cur_ver=${cur%%-*}; new_ver=${cand%%-*}
if dpkg --compare-versions "$cand" le "$cur"; then echo "No update available! ($cur)"; exit 0; fi
if [[ "${new_ver%%.*}" != "${cur_ver%%.*}" ]]; then
    log "MAJOR upgrade $cur_ver -> $new_ver available; not auto-applying (needs manual upgrade + runner compatibility check)"
    echo "$new_ver" > "$STATE/pending-major"
    exit 0
fi

# Don't restart under running CI jobs. A restart mid-job loses the job's
# final report: the runner logs "Job succeeded" but Forgejo marks the task
# failed ~15 min later ("Cannot transfer logs of task N") and skips every
# downstream job, including deploys (Rustinion ci, 2026-10-16 01:00). Wait
# for running tasks (action_task.status 6) to drain; if CI is still busy
# after DRAIN_MAX_MIN, leave the update for the next night.
DRAIN_MAX_MIN=${DRAIN_MAX_MIN:-180}
running_tasks() {
    local pw
    pw=$(awk -F' *= *' '/^\[database\]/{d=1;next} /^\[/{d=0} d&&$1=="PASSWD"{print $2}' /etc/forgejo/app.ini)
    local host name user
    host=$(awk -F' *= *' '/^\[database\]/{d=1;next} /^\[/{d=0} d&&$1=="HOST"{print $2}' /etc/forgejo/app.ini)
    name=$(awk -F' *= *' '/^\[database\]/{d=1;next} /^\[/{d=0} d&&$1=="NAME"{print $2}' /etc/forgejo/app.ini)
    user=$(awk -F' *= *' '/^\[database\]/{d=1;next} /^\[/{d=0} d&&$1=="USER"{print $2}' /etc/forgejo/app.ini)
    PGPASSWORD="$pw" psql -h "${host%%:*}" -p "${host##*:}" -U "$user" -d "$name" -tAc \
        "select count(*) from action_task where status = 6" 2>/dev/null
}
waited=0
while :; do
    n=$(running_tasks)
    if [[ ! "$n" =~ ^[0-9]+$ ]]; then
        log "cannot count running CI tasks, deferring $cur_ver -> $new_ver to the next run"; exit 0
    fi
    (( n == 0 )) && break
    if (( waited >= DRAIN_MAX_MIN )); then
        log "$n CI task(s) still running after ${waited} min, deferring $cur_ver -> $new_ver to the next run"; exit 0
    fi
    (( waited == 0 )) && log "$n CI task(s) running, waiting up to ${DRAIN_MAX_MIN} min before updating"
    sleep 60; waited=$((waited + 1))
done


log "installing forgejo $cand (was $cur), then restarting"
if ! DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --only-upgrade forgejo >/dev/null 2>&1; then
    log "apt install of forgejo $cand failed, keeping $cur"; exit 0
fi
systemctl restart forgejo
ok=0
for i in $(seq 1 30); do
    sleep 2
    if systemctl is-active --quiet forgejo && curl -fsS --max-time 5 http://127.0.0.1:3000/api/healthz >/dev/null 2>&1; then ok=1; break; fi
done
if (( ok )); then log "forgejo $cand is up"; exit 0; fi
log "forgejo $cand did not come up, rolling back to $cur"
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --allow-downgrades "forgejo=$cur" >/dev/null 2>&1
systemctl restart forgejo
exit 1
