9f87ff87e1
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.
154 lines
7.6 KiB
YAML
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
|