# nh3-extdev: cut the system-wide althing install over from v2.1.0 to v3.0.x (U9b flag day; re-run for each release). # # nh3-extdev is the one box a `git pull` cannot move: althing lives there as a system WHEEL # under /opt/uv-tools/althing-core with entry points in /usr/local/bin, installed from a wheel # that was copied to /tmp -- not from a checkout. So it needs its own install or it goes dark # at the cutover. # # ⚠ Two things about this box that differ from nh3-dev: # - the v2 daemons are SYSTEM units here (althing-herald, althing-receiver), not user units. # - `uv` is not on lkraven's PATH; it lives at /home/infra-ops/.local/bin/uv. The original # install used it under sudo with UV_TOOL_DIR=/opt/uv-tools, per the uv-receipt.toml. # # ⚠ There is a live agent session here (ldp-dev) holding a v2 light-monitor. Retiring the v2 # herald does not kill it, but it will never fire again -- that session has to re-arm on # althing-listen after this. Its handle survives: bare `ldp-dev` is in the authoritative 73; # only the machine-qualified `ldp-dev@nh3-extdev` was on the legacy exclusion list. # # Run: scripts/elway lkraven@10.100.50.42 --playbook playbooks/nh3-extdev-althing-v3.yaml # Rerunnable: a second run shows the install and unit steps skipped. vars: wheel_src: /home/lkraven/development/althing/dist/althing_core-3.0.1-py3-none-any.whl wheel_dest: /tmp/althing_core-3.0.1-py3-none-any.whl uv: /home/infra-ops/.local/bin/uv tool_dir: /opt/uv-tools bin_dir: /usr/local/bin # ⚠ Moved off nh3-dev 2026-08-28. A stale value here does not fail loudly at # install time — it fails in the VERIFY, which then reads as a broken deploy. post_office: http://10.100.50.40:8390 steps: - name: Stage the v3.0.0 wheel upload: src: /home/lkraven/development/althing/dist/althing_core-3.0.1-py3-none-any.whl dest: "{{ wheel_dest }}" mode: "0644" - name: Retire the v2 system daemons BEFORE swapping the package # Order matters: these run out of /opt/uv-tools/althing-core/bin/python, which the # install is about to replace. Stopping first means they never see a half-swapped tree. # v3 has no counterpart to either -- the post office replaced the herald and deleted the # reason for the receiver, since there is no longer a mailbox per machine to deliver between. shell: sudo systemctl disable --now althing-herald.service althing-receiver.service when: "systemctl is-active --quiet althing-herald.service || systemctl is-active --quiet althing-receiver.service" - name: Install the staged althing-core wheel over the system wheel install # NOT gated on `postbox` existing — that guard was right for the cutover and # wrong for every release after it: postbox exists now, so a version bump would # silently skip. `--force` makes the reinstall idempotent on its own. shell: sudo env UV_TOOL_DIR={{ tool_dir }} UV_TOOL_BIN_DIR={{ bin_dir }} {{ uv }} tool install --force {{ wheel_dest }} - name: Install the post-office herald as a system unit # A system unit rather than a user unit because that is how v2 was supervised here and # because this box has no lingering user session to hang a --user unit from. shell: | printf '%s\n' \ '[Unit]' \ 'Description=Althing post-office herald — per-machine relay (v3)' \ 'Documentation=https://gitea.phasefinal.com/vh/althing' \ 'After=network-online.target' \ 'Wants=network-online.target' \ '' \ '[Service]' \ 'Type=simple' \ 'User=lkraven' \ 'Environment=ALTHING_POST_OFFICE={{ post_office }}' \ 'ExecStart={{ bin_dir }}/althing-po-herald' \ 'Restart=always' \ 'RestartSec=5' \ '' \ '# Dials out, opens no port, holds no state. Refuses to start if another herald' \ '# already holds this node — two would double every poke and both write liveness.' \ '# Replaces althing-herald.service + althing-receiver.service, retired 2026-08-28.' \ '' \ '[Install]' \ 'WantedBy=multi-user.target' \ | sudo tee /etc/systemd/system/althing-po-herald.service >/dev/null sudo systemctl daemon-reload when: "! test -f /etc/systemd/system/althing-po-herald.service" - name: Enable and (re)start the herald so it picks up the new code shell: sudo systemctl enable --now althing-po-herald.service && sudo systemctl restart althing-po-herald.service verify: - name: postbox is installed and is v3 shell: "{{ bin_dir }}/postbox --help | grep -q 'send,reply,read,peek,thread,search,status,handles,register,sign-off'" changed_when: "false" - name: the v2 entry points are GONE, not merely shadowed # Assert absence of the binaries themselves. A `which` that still resolves would mean the # old wheel's entry points survived the --force and agents could keep calling a dead CLI. shell: "! test -e {{ bin_dir }}/althing-cli && ! test -e {{ bin_dir }}/althing-receiver && ! test -e {{ bin_dir }}/althing-herald" changed_when: "false" - name: v2 daemons are stopped and disabled shell: "! systemctl is-active --quiet althing-herald.service && ! systemctl is-active --quiet althing-receiver.service" changed_when: "false" - name: the po-herald is running shell: systemctl is-active --quiet althing-po-herald.service changed_when: "false" - name: this box can reach the post office and the roster is populated # ⚠ --handle is required. postbox resolves its identity from ALTHING_HANDLE, which # dev-launch sets per pane and which a playbook shell does not have -- without it this # check fails on identity, not on reachability, and reads as a deployment fault. shell: ALTHING_POST_OFFICE={{ post_office }} {{ bin_dir }}/postbox --handle operator handles | wc -l | awk '{ if ($1 >= 70) exit 0; else exit 1 }' changed_when: "false" - name: The 3.0.1 pane-route fix is present BY CONTENT, not by version string # forseti's own checks. A dist-info directory records what was installed, not # what the files contain — verify the code, not the label. shell: | SP={{ tool_dir }}/althing-core/lib/python3.13/site-packages/althing grep -q _PANE_ID "$SP/zellij.py" && grep -q _live_pid "$SP/session_source.py" changed_when: "false"