Files
esh-pfi-infrastructure/stacks/albok-service/README.md
T

104 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 in `compose.yaml`), built
2026-10-04 ~1115 from vh/albok `e653c29` (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 (their 0.1.7 stash + agent-feedback reindexes stand — 0.1.8 does not change what a
reindex writes).
Prior: 0.1.7 `sha256:0dbb8cc7…` (64f75dc), fleet/stash + personal/agent-feedback reindexed on it.
Prior: 0.1.6 `sha256:2b44c9d4…`, built
2026-10-04 ~0544 from vh/albok `6c1adcd` (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 from `e70321c` (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 with `git ls-remote`:
origin's `albok-service/v0.1.2` and `v0.1.2` are `544ecfd`, 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 said `degraded`: its
canary reports `alive` and the check expected `ok`; fixed in 0.1.1.) The Docker healthcheck checks HTTP 200.
- **0.1.1 also accepts numeric `default_gid` / `gid` in the config**, which would make the mounted
`/etc/group` unnecessary. 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 `/health` and `/search` answered 500. 0.1.2 closes the client.
Verified 0251: 30 `/health` calls left the fd count at 59, with 0 deleted. The same count had caught the leak on
0.1.1. 0.1.2 also stamps `X-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 `chgrp`s 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`
```bash
# 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):
```bash
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.