Merge pull request 'orchestrator-host: weekly opencode auto-upgrade (latest-release check, reinstall, restart web)' (#29) from opencode-auto-upgrade into main
This commit was merged in pull request #29.
This commit is contained in:
@@ -188,6 +188,94 @@ SSHCFG
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# ---- weekly opencode CLI auto-upgrade ----------------------------------------------------
|
||||||
|
# opencode is not in nixpkgs: opencode-install (above) only installs the standalone CLI when
|
||||||
|
# the binary is MISSING, so the installed version just ages in place (it sat on 1.18.29 for
|
||||||
|
# weeks while 1.18.33 was out). opencode's built-in autoupdate does not cover this host: it
|
||||||
|
# auto-applies patch releases only and only fires on a fresh interactive TUI start, and every
|
||||||
|
# agent here lives inside the long-lived `opencode serve` (opencode-web). This unit is the
|
||||||
|
# mechanism instead. It runs weekly in the Tuesday maintenance window (an hour BEFORE the
|
||||||
|
# host auto-update at Tue 03:00 UTC, and clear of the Thursday recipe-upgrade run and the
|
||||||
|
# Sunday canonical sweep), and:
|
||||||
|
# 1. compares the installed version with the latest GitHub release (no-op when equal);
|
||||||
|
# 2. reinstalls via the official installer (same code path as opencode-install) when they
|
||||||
|
# differ, and re-links ~/.local/bin/opencode;
|
||||||
|
# 3. restarts opencode-web so the new binary actually takes effect — sessions attached to
|
||||||
|
# it (orchestrator, upgrader, report) drop and their supervisors re-attach/resume, which
|
||||||
|
# is why the busy gate below must hold: a mid-flight CI/upgrade run is never cut, and a
|
||||||
|
# skipped run simply retries next week.
|
||||||
|
# Deploying this module never itself bumps opencode: boot/activation installs stay
|
||||||
|
# install-if-missing in opencode-install, and this unit is timer-driven only.
|
||||||
|
# The outcome of each run lands in .cc-ci-logs/opencode-update-state (read by /cc-ci-status).
|
||||||
|
systemd.services.opencode-upgrade = {
|
||||||
|
description = "Weekly opencode CLI auto-upgrade (latest release → reinstall → restart opencode-web)";
|
||||||
|
after = [ "network-online.target" "opencode-install.service" "opencode-web.service" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
serviceConfig = { Type = "oneshot"; TimeoutStartSec = "30min"; };
|
||||||
|
path = with pkgs; [ curl bash coreutils gnugrep gnutar gzip unzip systemd util-linux procps ];
|
||||||
|
environment = { HOME = "/home/loops"; OPENCODE_UI_HOST = cfg.opencodeUiHost; };
|
||||||
|
script = ''
|
||||||
|
set -u
|
||||||
|
BIN=/home/loops/.local/bin/opencode
|
||||||
|
STATE=/srv/cc-ci-orch/.cc-ci-logs/opencode-update-state
|
||||||
|
ocver() { "$BIN" --version 2>/dev/null | tail -1 || true; }
|
||||||
|
state() { printf '%s result=%s installed=%s note=%s\n' "$(date -u +%FT%TZ)" "$1" "$2" "$3" > "$STATE"; chown loops:users "$STATE" 2>/dev/null || true; }
|
||||||
|
busy() { echo "BUSY: $1 — skipping this week's opencode upgrade (retries next week)"; state skipped "$(ocver)" "$1"; exit 0; }
|
||||||
|
|
||||||
|
# Same busy gate as auto-update.nix: never cut a CI run, the weekly upgrade or the sweep.
|
||||||
|
pgrep -f run_recipe_ci >/dev/null && busy "a CI run is in flight"
|
||||||
|
systemctl is-active --quiet nightly-sweep.service && busy "the canonical sweep is running"
|
||||||
|
runuser -u loops -- tmux has-session -t cc-ci-upgrader 2>/dev/null && busy "the weekly recipe-upgrade run is in flight (tmux cc-ci-upgrader)"
|
||||||
|
runuser -u loops -- tmux has-session -t cc-ci-report 2>/dev/null && busy "the weekly report is being written (tmux cc-ci-report)"
|
||||||
|
if [ -r /run/secrets/bridge_drone_token ]; then
|
||||||
|
running=$(curl -s -m 20 -H "Authorization: Bearer $(cat /run/secrets/bridge_drone_token)" \
|
||||||
|
"https://drone.ci.commoninternet.net/api/repos/recipe-maintainers/cc-ci/builds?per_page=10" \
|
||||||
|
| grep -o '"status":"running"' | wc -l)
|
||||||
|
[ "''${running:-0}" -eq 0 ] || busy "$running Drone build(s) running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
INSTALLED=$(ocver)
|
||||||
|
LATEST=$(curl -fsSL -m 30 https://api.github.com/repos/anomalyco/opencode/releases/latest \
|
||||||
|
| grep -Po '"tag_name":\s*"v?\K[0-9][0-9.]*' || true)
|
||||||
|
[ -n "$LATEST" ] || { echo "could not determine the latest opencode release"; state failed "''${INSTALLED:-none}" "latest-unresolved"; exit 1; }
|
||||||
|
echo "installed: ''${INSTALLED:-none} latest: $LATEST"
|
||||||
|
if [ "$INSTALLED" = "$LATEST" ]; then
|
||||||
|
echo "opencode is up to date"
|
||||||
|
state ok "$INSTALLED" "up-to-date"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "upgrading opencode: ''${INSTALLED:-none} -> $LATEST"
|
||||||
|
runuser -u loops -- env HOME=/home/loops bash -c 'curl -fsSL https://opencode.ai/install | bash' \
|
||||||
|
|| { echo "install failed — the previously installed version is untouched"; state failed "''${INSTALLED:-none}" "install-failed"; exit 1; }
|
||||||
|
mkdir -p /home/loops/.local/bin
|
||||||
|
ln -sfn /home/loops/.opencode/bin/opencode /home/loops/.local/bin/opencode
|
||||||
|
NEWVER=$(ocver)
|
||||||
|
[ "$NEWVER" = "$LATEST" ] || { echo "install ran but opencode reports $NEWVER (wanted $LATEST) — not restarting"; state failed "$NEWVER" "install-mismatch"; exit 1; }
|
||||||
|
|
||||||
|
echo "restarting opencode-web so the new binary takes effect (attached sessions drop; their supervisors resume)"
|
||||||
|
systemctl restart opencode-web.service
|
||||||
|
sleep 10
|
||||||
|
systemctl is-active --quiet opencode-web.service || { echo "opencode-web did not come back after the upgrade"; state failed "$NEWVER" "web-restart-failed"; exit 1; }
|
||||||
|
code=$(curl -s -m 20 -o /dev/null -w '%{http_code}' --resolve "$OPENCODE_UI_HOST:443:127.0.0.1" "https://$OPENCODE_UI_HOST/")
|
||||||
|
[ "$code" = "401" ] || { echo "opencode UI answered $code, not the 401 auth challenge"; state failed "$NEWVER" "ui-check-$code"; exit 1; }
|
||||||
|
echo "opencode upgraded to $NEWVER; opencode-web restarted and healthy"
|
||||||
|
state ok "$NEWVER" "upgraded; web restarted"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.opencode-upgrade = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
# Weekly slot in the Tuesday maintenance window, an hour BEFORE the host auto-update
|
||||||
|
# (cc-ci-auto-update.timer: Tue 03:00 UTC) so a restarted opencode-web has settled before
|
||||||
|
# that run's health check; Persistent=false, like the auto-update — no catch-up at boot.
|
||||||
|
OnCalendar = "Tue *-*-* 02:00:00 UTC";
|
||||||
|
Persistent = false;
|
||||||
|
RandomizedDelaySec = "10min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# ---- opencode web server: one shared instance the opencode-backed agents attach to -------
|
# ---- opencode web server: one shared instance the opencode-backed agents attach to -------
|
||||||
# Provider creds come from /srv/cc-ci/.testenv (out of band, see README).
|
# Provider creds come from /srv/cc-ci/.testenv (out of band, see README).
|
||||||
systemd.services.opencode-web = {
|
systemd.services.opencode-web = {
|
||||||
|
|||||||
Reference in New Issue
Block a user