sneak's goal is six apps in beta under upaas (https://git.eeqj.de/sneak/upaas) on fsn1app1: webhooker and pixa first, then dnswatcher, netwatch and routewatch (his ruling of 24 September, sneak/project-management#1). This is routewatch's readiness issue; it starts after webhooker and pixa. Setting the app up in upaas and deploying it are sneak's.
It builds on #3, which keeps routewatch under 5 GiB of memory on the real feed and documents the container memory limit.
Definition of done
A prod branch exists, cut from main. It moves forward only through reviewed main to prod PRs (sneak's ruling of 30 August).
The image built from Dockerfile runs routewatch with its state under one volume path, which survives a restart.
Every setting comes from an environment variable; a value that is set but invalid stops the start.
The image has a HEALTHCHECK.
README.md has a short "Running under upaas" section listing exactly what the upaas app needs: the container port, the volume path, each environment variable with its value, the memory limit from issue 3, and the health check.
Code changes land on next through reviewed PRs, with make check green.
Model: opus-5-5
sneak's goal is six apps in beta under upaas (https://git.eeqj.de/sneak/upaas) on fsn1app1: webhooker and pixa first, then dnswatcher, netwatch and routewatch (his ruling of 24 September, https://git.eeqj.de/sneak/project-management/issues/1). This is routewatch's readiness issue; it starts after webhooker and pixa. Setting the app up in upaas and deploying it are sneak's.
It builds on https://git.eeqj.de/sneak/routewatch/issues/3, which keeps routewatch under 5 GiB of memory on the real feed and documents the container memory limit.
## Definition of done
- A `prod` branch exists, cut from `main`. It moves forward only through reviewed `main` to `prod` PRs (sneak's ruling of 30 August).
- The image built from `Dockerfile` runs routewatch with its state under one volume path, which survives a restart.
- Every setting comes from an environment variable; a value that is set but invalid stops the start.
- The image has a `HEALTHCHECK`.
- `README.md` has a short "Running under upaas" section listing exactly what the upaas app needs: the container port, the volume path, each environment variable with its value, the memory limit from issue 3, and the health check.
- Code changes land on `next` through reviewed PRs, with `make check` green.
Model: opus-5-5
clawbot
self-assigned this 2026-09-25 11:02:24 +02:00
Implementer's brief. prod is cut from main at ddf0b2f; that part of the definition of done is done, and nothing else touches prod.
What upaas does (its main, 97a17e5), from its code:
Volume: a bind mount of an absolute host path onto a container path (internal/docker/client.go, buildMounts). upaas never creates the host directory and has no setting for the container user.
Port: per-app mappings of a host port to a container port.
Health check: the image's own HEALTHCHECK. 60 seconds after a deploy, upaas fails the deploy unless the container is healthy (internal/service/deploy/deploy.go, checkHealthAfterDelay).
Memory: the app's "Memory Limit" field (256m, 1g or plain bytes) becomes the container's memory limit (buildResources). upaas sets no swap limit, so on a host with swap Docker allows the same amount of swap again.
Environment: per-app environment variables.
What routewatch has on next (df9e23d): state under /var/lib/berlin.sneak.app.routewatch (from XDG_DATA_HOME=/var/lib, set in the image); entrypoint.sh starts as root, takes ownership of that directory, then runs the daemon as the routewatch user (UID 1000), so a root-owned fresh volume works; port 8080; a HEALTHCHECK on /.well-known/healthcheck.json that always probes port 8080, whatever PORT says. Settings read from the environment: PORT, DEBUG, XDG_DATA_HOME, and the image's GOMEMLIMIT and MALLOC_ARENA_MAX.
Changes, one PR to next:
A value that is set but invalid stops the start with a clear error and a non-zero exit:
PORT: a whole number from 1 to 65535. Today a bad value only logs an error from the listener goroutine and the daemon runs on without HTTP (internal/server/server.go, Start). Check it before anything starts.
XDG_DATA_HOME: an absolute path, as the XDG spec requires. Today a relative one silently puts the database somewhere else.
MALLOC_ARENA_MAX: glibc ignores a bad value silently, so entrypoint.sh refuses anything but a positive whole number.
GOMEMLIMIT: the Go runtime already refuses a malformed value at start. Confirm that with the built image; add nothing.
DEBUG takes any text; nothing to check.
The HEALTHCHECK probes the port PORT names, 8080 when unset (shell form with ${PORT:-8080}).
README.md:
Configuration: the Linux state directory is /var/lib/berlin.sneak.app.routewatch (or under XDG_DATA_HOME), not /var/lib/routewatch/; add XDG_DATA_HOME to the table; say that a set but invalid value stops the start.
A short "Running under upaas" section after Memory, listing exactly: the container port 8080; one volume at container path /var/lib/berlin.sneak.app.routewatch, with the host directory created before the first deploy (the entrypoint takes ownership of it); environment: nothing required, leave XDG_DATA_HOME, GOMEMLIMIT and MALLOC_ARENA_MAX at the image's values, DEBUG=routewatch optional for the memory line in the log; Memory Limit 5g, the 5 GiB cap from #3, and that upaas sets no swap limit; the health check path, and that upaas reads it 60 seconds after a deploy.
Add no new settings and no new environment variables; the hard-coded defaults in internal/config/config.go stay. Tests cover each refusal in item 1 that is made in Go.
Checks (verify, publish none of the evidence), on an image built with make docker:
Run it with --mount type=bind,source=DIR,target=/var/lib/berlin.sneak.app.routewatch on an empty root-owned directory (make it through a throwaway root container): the container is healthy within 60 seconds.
Stop and remove it, start a new container on the same directory: it starts with the first run's database.
Each invalid value from item 1, a malformed GOMEMLIMIT included, stops the start with a non-zero exit; PORT=9090 still ends healthy.
The memory limit cannot be exercised on the build host: Docker there refuses --memory (the cgroup is in threaded mode). Say so on the PR as a disclosure; do not work around it.
Model: opus-5-5
Implementer's brief. `prod` is cut from `main` at `ddf0b2f`; that part of the definition of done is done, and nothing else touches `prod`.
What upaas does (its `main`, `97a17e5`), from its code:
- Volume: a bind mount of an absolute host path onto a container path (`internal/docker/client.go`, `buildMounts`). upaas never creates the host directory and has no setting for the container user.
- Port: per-app mappings of a host port to a container port.
- Health check: the image's own `HEALTHCHECK`. 60 seconds after a deploy, upaas fails the deploy unless the container is `healthy` (`internal/service/deploy/deploy.go`, `checkHealthAfterDelay`).
- Memory: the app's "Memory Limit" field (`256m`, `1g` or plain bytes) becomes the container's memory limit (`buildResources`). upaas sets no swap limit, so on a host with swap Docker allows the same amount of swap again.
- Environment: per-app environment variables.
What routewatch has on `next` (`df9e23d`): state under `/var/lib/berlin.sneak.app.routewatch` (from `XDG_DATA_HOME=/var/lib`, set in the image); `entrypoint.sh` starts as root, takes ownership of that directory, then runs the daemon as the `routewatch` user (UID 1000), so a root-owned fresh volume works; port 8080; a `HEALTHCHECK` on `/.well-known/healthcheck.json` that always probes port 8080, whatever `PORT` says. Settings read from the environment: `PORT`, `DEBUG`, `XDG_DATA_HOME`, and the image's `GOMEMLIMIT` and `MALLOC_ARENA_MAX`.
Changes, one PR to `next`:
1. A value that is set but invalid stops the start with a clear error and a non-zero exit:
- `PORT`: a whole number from 1 to 65535. Today a bad value only logs an error from the listener goroutine and the daemon runs on without HTTP (`internal/server/server.go`, `Start`). Check it before anything starts.
- `XDG_DATA_HOME`: an absolute path, as the XDG spec requires. Today a relative one silently puts the database somewhere else.
- `MALLOC_ARENA_MAX`: glibc ignores a bad value silently, so `entrypoint.sh` refuses anything but a positive whole number.
- `GOMEMLIMIT`: the Go runtime already refuses a malformed value at start. Confirm that with the built image; add nothing.
- `DEBUG` takes any text; nothing to check.
2. The `HEALTHCHECK` probes the port `PORT` names, 8080 when unset (shell form with `${PORT:-8080}`).
3. `README.md`:
- Configuration: the Linux state directory is `/var/lib/berlin.sneak.app.routewatch` (or under `XDG_DATA_HOME`), not `/var/lib/routewatch/`; add `XDG_DATA_HOME` to the table; say that a set but invalid value stops the start.
- A short "Running under upaas" section after Memory, listing exactly: the container port `8080`; one volume at container path `/var/lib/berlin.sneak.app.routewatch`, with the host directory created before the first deploy (the entrypoint takes ownership of it); environment: nothing required, leave `XDG_DATA_HOME`, `GOMEMLIMIT` and `MALLOC_ARENA_MAX` at the image's values, `DEBUG=routewatch` optional for the memory line in the log; Memory Limit `5g`, the 5 GiB cap from https://git.eeqj.de/sneak/routewatch/issues/3, and that upaas sets no swap limit; the health check path, and that upaas reads it 60 seconds after a deploy.
Add no new settings and no new environment variables; the hard-coded defaults in `internal/config/config.go` stay. Tests cover each refusal in item 1 that is made in Go.
Checks (verify, publish none of the evidence), on an image built with `make docker`:
1. Run it with `--mount type=bind,source=DIR,target=/var/lib/berlin.sneak.app.routewatch` on an empty root-owned directory (make it through a throwaway root container): the container is `healthy` within 60 seconds.
2. Stop and remove it, start a new container on the same directory: it starts with the first run's database.
3. Each invalid value from item 1, a malformed `GOMEMLIMIT` included, stops the start with a non-zero exit; `PORT=9090` still ends `healthy`.
4. The memory limit cannot be exercised on the build host: Docker there refuses `--memory` (the cgroup is in threaded mode). Say so on the PR as a disclosure; do not work around it.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
sneak's goal is six apps in beta under upaas (https://git.eeqj.de/sneak/upaas) on fsn1app1: webhooker and pixa first, then dnswatcher, netwatch and routewatch (his ruling of 24 September, sneak/project-management#1). This is routewatch's readiness issue; it starts after webhooker and pixa. Setting the app up in upaas and deploying it are sneak's.
It builds on #3, which keeps routewatch under 5 GiB of memory on the real feed and documents the container memory limit.
Definition of done
prodbranch exists, cut frommain. It moves forward only through reviewedmaintoprodPRs (sneak's ruling of 30 August).Dockerfileruns routewatch with its state under one volume path, which survives a restart.HEALTHCHECK.README.mdhas a short "Running under upaas" section listing exactly what the upaas app needs: the container port, the volume path, each environment variable with its value, the memory limit from issue 3, and the health check.nextthrough reviewed PRs, withmake checkgreen.Model: opus-5-5
Implementer's brief.
prodis cut frommainatddf0b2f; that part of the definition of done is done, and nothing else touchesprod.What upaas does (its
main,97a17e5), from its code:internal/docker/client.go,buildMounts). upaas never creates the host directory and has no setting for the container user.HEALTHCHECK. 60 seconds after a deploy, upaas fails the deploy unless the container ishealthy(internal/service/deploy/deploy.go,checkHealthAfterDelay).256m,1gor plain bytes) becomes the container's memory limit (buildResources). upaas sets no swap limit, so on a host with swap Docker allows the same amount of swap again.What routewatch has on
next(df9e23d): state under/var/lib/berlin.sneak.app.routewatch(fromXDG_DATA_HOME=/var/lib, set in the image);entrypoint.shstarts as root, takes ownership of that directory, then runs the daemon as theroutewatchuser (UID 1000), so a root-owned fresh volume works; port 8080; aHEALTHCHECKon/.well-known/healthcheck.jsonthat always probes port 8080, whateverPORTsays. Settings read from the environment:PORT,DEBUG,XDG_DATA_HOME, and the image'sGOMEMLIMITandMALLOC_ARENA_MAX.Changes, one PR to
next:PORT: a whole number from 1 to 65535. Today a bad value only logs an error from the listener goroutine and the daemon runs on without HTTP (internal/server/server.go,Start). Check it before anything starts.XDG_DATA_HOME: an absolute path, as the XDG spec requires. Today a relative one silently puts the database somewhere else.MALLOC_ARENA_MAX: glibc ignores a bad value silently, soentrypoint.shrefuses anything but a positive whole number.GOMEMLIMIT: the Go runtime already refuses a malformed value at start. Confirm that with the built image; add nothing.DEBUGtakes any text; nothing to check.HEALTHCHECKprobes the portPORTnames, 8080 when unset (shell form with${PORT:-8080}).README.md:/var/lib/berlin.sneak.app.routewatch(or underXDG_DATA_HOME), not/var/lib/routewatch/; addXDG_DATA_HOMEto the table; say that a set but invalid value stops the start.8080; one volume at container path/var/lib/berlin.sneak.app.routewatch, with the host directory created before the first deploy (the entrypoint takes ownership of it); environment: nothing required, leaveXDG_DATA_HOME,GOMEMLIMITandMALLOC_ARENA_MAXat the image's values,DEBUG=routewatchoptional for the memory line in the log; Memory Limit5g, the 5 GiB cap from #3, and that upaas sets no swap limit; the health check path, and that upaas reads it 60 seconds after a deploy.Add no new settings and no new environment variables; the hard-coded defaults in
internal/config/config.gostay. Tests cover each refusal in item 1 that is made in Go.Checks (verify, publish none of the evidence), on an image built with
make docker:--mount type=bind,source=DIR,target=/var/lib/berlin.sneak.app.routewatchon an empty root-owned directory (make it through a throwaway root container): the container ishealthywithin 60 seconds.GOMEMLIMITincluded, stops the start with a non-zero exit;PORT=9090still endshealthy.--memory(the cgroup is in threaded mode). Say so on the PR as a disclosure; do not work around it.Model: opus-5-5
PR: #32
Model: opus-5-5