Files
menu/ansible/readme.md
T
tanshu 2ef044aa45 Let Docker allocate the network subnet again
Reverts the explicit subnets: the pools were freed by deleting five networks, so
the network role is back to its single idempotent task - ensure the network
exists and Caddy is on it - and the Sites no longer declare a subnet. The
deployment readme keeps the note that a new Site needs a config, and therefore a
new tag, but no longer asks for a free /24.

If the pools fill up again the failure is recognisable: 'all predefined address
pools have been fully subnetted', and the choices are deleting stale networks or
declaring a subnet per Site.
2026-09-23 00:21:57 +05:30

4.0 KiB

The menu deployment

Lives here, beside the app it deploys. One run deploys a Site: it starts the app's container and points the Site's web address at it. There is no older system to keep serving, so there is no staged rollout and nothing to retire — deploying an earlier tag is the rollback.

The app's data root is the directory the old static menus were served from (/var/lib/menu.<site>), on purpose, so the app inherits the location instead of a second one. The app only reads and writes data/ and site.env inside it. If you later want the old menu files gone, delete those files — not the directory, which now holds the live content.

What lives where

Thing Where
Name, tagline, icon, categories, menus on the server under /var/lib/menu.<site>/data — the admin edits them
Theme colours config/<site>.site.json in the repo, baked into the image
Admin password hash, session secret /var/lib/menu.<site>/site.env (untracked, mode 600)
Which image tag runs -e tag=<tag>
Which Site is deployed the inventory entry in hosts
The web address it serves host: in vars/<site>.yml

Deploying

From the repo root:

./deploy.sh 2026-09-22                 # checks, podman build+push, then deploys
SKIP_DEPLOY=1 ./deploy.sh 2026-09-22   # build and push only

deploy.sh runs make build-production TAG=<tag>, which builds a multi-arch podman manifest (linux/amd64,linux/arm64) and pushes latest and the tag to registry.tanshu.com/menu-admin. Then it runs this playbook against the Site named by SITE (default hopsngrains).

By hand:

make build-production TAG=2026-09-22
cd ansible
ansible-playbook --inventory=hosts playbook.yml --limit hopsngrains -e tag=2026-09-22

Every Site runs on the same box, so hosts has one entry per Site pointing at the same ansible_host with its own var_file. ansible_host is written as the resolvable name rather than an ssh alias, so the inventory works whatever a machine's ~/.ssh/config says. (ssh on some machines complains about /etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf being owned by nobody; that is a local file-permission bug whose fix is sudo chown -R root:root /etc/ssh/ssh_config.d, not something this playbook works around.)

Adding a Site is one line in hosts, a copy of vars/hopsngrains.yml, and a config/<site>.site.json. The image is shared, so the config has to be in it: a new Site needs a new tag.

Checking it landed

npm run check-site -- https://menu.hopsngrains.com

Read-only, exits non-zero on failure. Then from a phone: the landing page, a Section page, the page counter, tap-to-zoom, and signing into /admin to publish something.

The admin password

The app starts with the admin disabled while /var/lib/menu.<site>/site.env is missing, and the playbook says so rather than failing. Create it once per Site; run it again with --force to reset the password (a fresh session secret signs everyone out). There is no minimum password length.

scp scripts/provision-site-env.sh monoco:/tmp/
ssh -t monoco bash /tmp/provision-site-env.sh /var/lib/menu.hopsngrains.com
ssh monoco docker restart menu-app.hopsngrains.com

The -t matters: the script prompts for the password, and without a terminal on the remote side the prompt cannot be read, so it would sit there looking frozen. With no image argument it borrows the one the running container uses.

Rolling back

Deploy the previous tag:

./deploy.sh <previous-tag>

docker logs menu-app.<site> is where a failed container says why.