Files
esh-pfi-infrastructure/stacks/althing-post-office/compose.yaml
T
vh 9f87ff87e1 feat(althing): surface the post office on Homepage under Toolchain
Labels the container into `Toolchain`, an existing group under the
existing Toolchain tab -- "the plumbing", which is where a message bus
belongs. Confirmed live: Homepage's API now returns it.

I had previously recorded in this file that no group fitted, which was
wrong. That conclusion came from a grep over the layout block that
missed the nested groups, and it went into a comment as though it were
a finding. The group was there the whole time.

Labels bind at container creation, so this deployed with `up -d` rather
than `restart`; a restart leaves the old labels and the dashboard keeps
showing what was there before. nh3-docker is already a discovered host
in homepage's docker.yaml as `nh3-pfi-docker`, so the label alone is
enough -- adding a services.yaml entry as well would render the card
twice.

althing-chamber on ana-docker also carries Toolchain labels and is a
separate service per the operator. Left alone.
2026-08-28 10:37:12 -07:00

154 lines
7.6 KiB
YAML

# althing v3 — the post office. The fleet's message bus.
#
# MOVED nh3-dev -> nh3-docker on 2026-08-28 (operator: "I want it on the docker
# machine — that was always the goal"). It was deployed to nh3-dev at the U9b flag
# day because the herald lives there; but the herald is the piece that MUST be
# host-local (it reads route files and pokes FIFOs), and the post office is
# explicitly the piece that is not.
#
# Three reasons the dev box was wrong:
# - our own server table calls nh3-dev "not a Docker-stack host", and NH-site
# non-GPU services belong here.
# - nh3-dev had three OOM events in fourteen days with the interval HALVING
# (2026-08-14, -26, -28), and the confirmed hog is Claude Code sessions at
# 5-18 GB each, which is that box's actual job.
# - `mem_limit` protects the fleet FROM the post office. It does nothing to
# protect the post office from the box: oom_score_adj is 0, so it was an
# ordinary kill candidate, and the 08-28 sweep took althing-herald and
# uvicorn. A sweep that takes the post office takes mail for all 73 handles.
#
# ⚠ MOVING THE DATA: `docker stop` does NOT checkpoint the WAL. Measured on the
# 08-28 move: post_office.db was 155 KB / mtime 15:09 while post_office.db-wal
# was 4.1 MB / mtime 16:56 — every recent message lived in the WAL. Copying the
# .db alone yields a database that opens cleanly, passes a smoke test, and is
# missing the day's mail. Stop the container, then
# `PRAGMA wal_checkpoint(TRUNCATE)` explicitly, then verify row counts on BOTH
# sides before deleting anything.
#
# Deploy: scripts/deploy-stack.sh nh3-docker althing-post-office
# Image: built from the althing repo's Dockerfile (vh/althing @ v3.0.0), pushed to
# the gitea registry 2026-08-28. Rebuild + republish:
#
# cd ~/development/althing
# docker build -t gitea.phasefinal.com/claude-bot/althing-post-office:<ver> .
# echo $(cat ~/.config/claude-bot/gitea-token) | \
# docker login gitea.phasefinal.com -u claude-bot --password-stdin
# docker push gitea.phasefinal.com/claude-bot/althing-post-office:<ver>
#
# then update the digest below and redeploy. Supersedes the
# `docker save | ssh | docker load` hand-carry the move originally used.
#
# ⚠ NAMESPACE IS `claude-bot`, NOT `vh`. claude-bot's token carries write:package
# but package namespaces are owned: pushing to `vh/...` returns
# `unauthorized: authentication required` AFTER a successful `docker login`, which
# reads like a credential fault and is actually an ownership one. Publishing under
# claude-bot's own namespace also satisfies the standing directive to stop reusing
# the operator's personal credentials for infra work. Both hosts are logged in as
# claude-bot; a new host needs that login before it can pull.
services:
post-office:
# Digest-pinned, not tag-floating: `:3.0.0` is a mutable pointer on a registry
# anyone can re-push, and this container is the fleet's whole message bus. The
# tag is kept alongside the digest purely so a human can read what it is.
image: gitea.phasefinal.com/claude-bot/althing-post-office:3.0.0@sha256:410fed41fa049c41cf83577fd2e49831ea1959ca50b597a8bd35a548e267cf01
container_name: althing-post-office
# ─── Host networking, so the bind guard keeps working ────────────
#
# ⚠ DO NOT "fix" this into a bridged container with `-p`. api.py's
# resolve_bind_host refuses any address that resolves to a wildcard, and it
# tests the RESOLVED PROPERTY rather than matching strings, so there is no
# spelling of "everything" that gets past it. A bridged container cannot
# satisfy that guard honestly: inside its own netns the only reachable bind
# is a wildcard, and publishing the port would move access control from the
# address the application checks to a `-p` flag it cannot see.
#
# Reachability on the private network IS the authorisation story here —
# there is no login and none is wanted.
#
# Cost, stated plainly: no network namespace isolation, and port 8390 is
# claimed host-wide. For a single-service private-network deployment that is
# the right trade, but it IS a trade.
network_mode: host
# The store is the only thing that must survive. Named volume rather than a
# bind mount: uid 1000 inside the container owns it, and docker creates it
# with the right ownership instead of inheriting the host path's.
volumes:
- post-office-data:/var/lib/althing
# The private address of THIS box. Single place it is named; the image ships
# no default on purpose, so a deployment that omits it is refused at startup
# rather than binding wide.
environment:
ALTHING_BIND_HOST: "10.100.50.40"
ALTHING_PORT: "8390"
ALTHING_DB: "/var/lib/althing/post_office.db"
# `unless-stopped` rather than `always` so an operator who deliberately stops
# it during a flag day does not find it running again after a reboot.
restart: unless-stopped
# One Python interpreter holding one SQLite connection; it idles far below
# this. The cap is not a tuning parameter, it is a promise that the post
# office can never be its host's next OOM story.
#
# ⚠ nh3-docker runs Compose v5, which ignores `version:` and honours
# `mem_limit` directly. On a docker-compose 1.x host this needs schema 2.4 —
# under 3.x the key moves to `deploy:`, which is swarm-only and SILENTLY
# IGNORED. Verify with `docker inspect` (want 536870912), never by reading
# the yaml: a cap that does nothing reads as protection.
mem_limit: 512m
# ⚠ The fleet's entire bus. Make the kernel shoot almost anything else first.
# This is the gap the nh3-dev deployment had: a 512m cap and oom_score_adj 0
# means "cannot cause an OOM, is an ordinary victim of one".
oom_score_adj: -500
# Docker's default json-file driver has no size limit. v2's herald left a
# 60 MB log on nh3-dev; an uncapped container log is the same mistake with a
# different name.
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
# Inherited from the image, restated so it is visible at deploy time rather
# than only in `docker inspect`.
healthcheck:
test:
- CMD
- python
- -c
- "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://10.100.50.40:8390/',timeout=4).status==200 else 1)"
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
# `Toolchain` is an EXISTING group under the existing Toolchain tab in
# stacks/homepage/conf/settings.yaml — "the plumbing", which is where a
# message bus belongs. Naming a group the layout has never heard of gets no
# `tab:` and renders the group on ALL FOUR tabs (the Scriberr "AI Systems"
# bug, 2026-08-23), so this must stay a group that already exists.
#
# ⚠ Labels bind at container CREATION. Editing them needs `up -d`, never
# `restart` — a restart leaves the old labels in place and the dashboard
# keeps showing whatever was there before.
#
# nh3-docker is a discovered Docker host in homepage's docker.yaml (as
# `nh3-pfi-docker`), so the label is enough; do NOT also add a services.yaml
# entry or the card renders twice.
labels:
- homepage.group=Toolchain
- homepage.name=althing post office
- homepage.icon=mdi-mailbox
- homepage.description=althing v3 message bus — handles, unread counts, node liveness
- homepage.href=http://10.100.50.40:8390
volumes:
post-office-data:
name: althing-post-office-data