7.9 KiB
albok-service — fleet knowledgebase service (nh3-docker)
The FastAPI process that owns Albok's buildings-and-wings store (vh/albok,
packages/albok-service, contract docs/contracts/unit2_service.contract.md). It is the
only writer of the store. Deployed 2026-10-02 at albok-dev's request (operator-approved).
- URL:
http://albok.nh3.internal:8392(=http://10.100.50.40:8392). Container port 8390. ⚠ Host port 8392, because 8390 on nh3-docker is the althing post office. - Image:
gitea.phasefinal.com/pfi/albok-service:0.1.8@sha256:239f900e…(pinned incompose.yaml), built 2026-10-04 ~1115 from vh/alboke653c29(tag albok-service/v0.1.8; heid re-pass fixes over 0.1.7: keyword-index lock mismatch, journal-stranding replay path, bypassable 400, link-kind handling in the collapse). fleet/memory reindexed on it per albok-dev (10,849 obj / 15,418 chunks, 1613.6 s, clean, commit 563dcae; their 0.1.7 stash + agent-feedback reindexes stand — 0.1.8 does not change what a reindex writes). ⚠ reindex path shape: POST /buildings//wings//reindex (the wing segment is required). Prior: 0.1.7sha256:0dbb8cc7…(64f75dc), fleet/stash + personal/agent-feedback reindexed on it. Prior: 0.1.6sha256:2b44c9d4…, built 2026-10-04 ~0544 from vh/albok6c1adcd(tags albok-service/v0.1.6 + core v0.1.4; pushed by infra-ops on Prime's go). 0.1.6 is the review fold on 0.1.5's search fix: the keyword-index build now takes a lock (two concurrent first queries could cross ids or KeyError), the core search keyword is keyword-only (do not pin albok 0.1.3), and the store wrapper fails closed. 0.1.5 (below) was built 2026-10-03 ~2038 frome70321c(tags albok-service/v0.1.5 + core v0.1.3). 0.1.5 = search speed: an index on the facet table's object_id and one cached keyword index per wing (fleet/memory query 4.5 s → ~0.25 s warm; the first search per wing after a start or an ingest rebuilds the keyword index, ~1.7 s). The facet index is built at the first open (~0.35 s, +17 MB). Measured live by albok-dev 2026-10-03 ~2245 (same idle container, before/after): fleet/memory top-5 median 3.97 s → 0.22 s (n=5; first query after start 2.11 s); all three wings top-20 5.62 s → 0.41 s (n=3); an unseen query 0.39 s (n=3). Prior: 0.1.4 (3d6bdba, version header from metadata) ~1611–2039; 0.1.3 (fc4c97a) 1558–1611; 0.1.2 (544ecfd) 0251–1558. Built from albok-dev's local commit before the operator pushed it. Verified 2026-10-03 1000 withgit ls-remote: origin'salbok-service/v0.1.2andv0.1.2are544ecfd, so the image matches the published tag. Image labels carry the full SHA. History: 0.1.0 (0b37431) 2026-10-02 1754–1805; 0.1.1 (e349d50) 1805 to 2026-10-03 0251. - Health:
GET /health(no auth) →"status": "ok"on 0.1.1. (0.1.0 always saiddegraded: its canary reportsaliveand the check expectedok; fixed in 0.1.1.) The Docker healthcheck checks HTTP 200. - 0.1.1 also accepts numeric
default_gid/gidin the config, which would make the mounted/etc/groupunnecessary. The current name-based config stays valid, so it is left as is. - Why 0.1.2: 0.1.1 leaked a Chroma client (and its sqlite fds) on every health probe. After 9 h it held 1018 of
1024 fds, 478 of them deleted canary files, and
/healthand/searchanswered 500. 0.1.2 closes the client. Verified 0251: 30/healthcalls left the fd count at 59, with 0 deleted. The same count had caught the leak on 0.1.1. 0.1.2 also stampsX-Albok-Service: <version>on every response.
Pieces and where they live
| What | Where |
|---|---|
| store root (one git repo per wing) | /srv/albok/store — local ext4, albok:albok 0711 |
| private root (lease, journal, tokens.db, per-wing chroma) | /srv/albok/private — local ext4, albok:albok 0700, single-attach |
| config (holds the embedder key) | /srv/albok/etc/albok.yaml — root:albok 0640; rendered from conf/albok.yaml.template |
container /etc/group |
/opt/docker/conf/albok-service/group (= conf/group) |
| compose | /opt/docker/compose/albok-service/compose.yaml |
Identities (fixed numeric ids, created by playbooks/albok-service-host.yaml): user and group
albok 1500:1500 (the image's user), read groups albok-read 1510 (building fleet) and
albok-personal 1511 (building personal). albok-feedback 1512 = restricted wing personal/agent-feedback (2026-10-03, default-deny: no viewer is
in it). Reserve 1513+ for the vault wing and any later restricted wing; add each to the host, conf/group and group_add.
Why the group file and group_add: the service resolves group NAMES with grp.getgrnam()
inside the container and chgrps wing dirs as uid 1500. Without the names in the container's
/etc/group it reports "group does not resolve", and without membership the chgrp is refused.
Verified: wings came up drwxr-s--- albok:albok-read / albok:albok-personal, drift 0.
Secrets (vault): albok/litellm-key, a LiteLLM key scoped to qwen3-embedding only (alias
albok-service; verified 200 on embeddings, 403 on any other model); albok/bootstrap-admin-token,
the first-start admin token (also at /srv/albok/private/bootstrap-admin-token, 0600).
Nemi (albok's tagger, runs on nh3-dev): albok/token-nemi, a service token minted 0255 with read+write on
fleet/memory, fleet/stash and personal/agent-feedback; it sees exactly those three wings (personal/notes hidden).
The previous token (01M40979…, without agent-feedback) is still live until albok-dev revokes it.
albok/litellm-key-nemi, alias albok-nemi, scoped to gen-small, summarizer and qwen3-embedding. Verified 200 on
all three and 403 on another model.
Backups: nh3-docker is VM 100 on nh3-pve, in the nightly 21:00 vzdump (snapshot mode → pbs-ana),
so /srv/albok is covered whole-VM. No file-level restic job for it.
Deploy / redeploy
⚠ Do not restart the service in the middle of a Nemi walk. The hourly walk on nh3-dev (services/albok-nemi/) fails with
Unreachable and mails OnFailure; that happened 2026-10-03 16:11 at the 0.1.4 redeploy. It is harmless, since the next walk redoes the
work and the service commits per batch, but avoidable. Check first, on nh3-dev as lkraven:
flock -n ~/.local/state/albok-nemi/run.lock true && echo idle || echo WALK RUNNING
# 1. host prep (idempotent)
scripts/elway infra-ops@10.100.50.40 --playbook playbooks/albok-service-host.yaml
# 2. config: render the template with the vaulted key (never commit the rendered file)
umask 077; secret get albok/litellm-key | tr -d '\n' | python3 -c "import sys; k=sys.stdin.read(); \
open('/tmp/albok.yaml','w').write(open('stacks/albok-service/conf/albok.yaml.template').read().replace('__ALBOK_LITELLM_KEY__', k))"
scripts/elway infra-ops@10.100.50.40 --upload /tmp/albok.yaml:/srv/albok/etc/albok.yaml:0640 --sudo --owner root:albok
# then delete /tmp/albok.yaml by its literal path
# 3. compose + conf, then start (root holds the registry login on nh3-docker)
scripts/deploy-stack.sh nh3-docker albok-service
ssh infra-ops@10.100.50.40 'cd /opt/docker/compose/albok-service && sudo docker compose up -d'
Image rebuild (from a clean archive, never a working tree):
git -C ~/development/albok archive <sha> | tar -x -C <scratch>
cd <scratch> && docker build -f packages/albok-service/Dockerfile -t gitea.phasefinal.com/pfi/albok-service:<ver> .
secret get nh3-dev/.config/claude-bot/gitea-token-sdkops | docker login gitea.phasefinal.com -u claude-bot --password-stdin
docker push gitea.phasefinal.com/pfi/albok-service:<ver> # then pin the new digest in compose.yaml
Viewers (read-only access to the store)
Mount /srv/albok/store :ro, group_add the read group's numeric gid (1510 / 1511), set
GIT_OPTIONAL_LOCKS=0, and add safe.directory for the path as the viewer sees it. Nothing else
may mount the store read-write.