Files
menu/ansible/readme.md
T
tanshu 75b6492493 Move the deployment into the app repo, and build it with podman
The playbook, its roles, its vars and a new inventory now live in ansible/,
beside the app they deploy, the way the other apps on this machine are laid out.
The inventory follows the same shape: one entry per Site, all on the same box,
each naming its own vars file through var_file, so adding a Site is one line and
a file rather than a command-line flag. ansible_host is written as the
resolvable name rather than an ssh alias so the inventory works on any machine.

The image is now built with podman as a multi-arch manifest and pushed to
registry.tanshu.com/menu-admin, mirroring the Makefile used elsewhere on this
machine: build-production, plus build-check and build-check-local for a build
that does not push. deploy.sh runs the checks, builds, pushes, then runs the
playbook for the Site named by SITE, with SKIP_DEPLOY for a push without a
deploy.

ESLint comes out again: this project's typescript@7 has no JavaScript compiler
API, so typescript-eslint cannot install, and linting only the JavaScript was
not worth keeping. Prettier stays, and the README records why ESLint is absent
and what would bring it back.
2026-09-22 23:18:18 +05:30

101 lines
4.0 KiB
Markdown

# The menu deployment
Lives here, beside the app it deploys, the way the other apps on this machine do.
It owns the `menu-app` container and the Site's snippet in the shared Caddyfile.
## What lives where
| Thing | Where |
| -------------------------------------- | --------------------------------------------------------------------------- |
| Name, tagline, icon, categories, menus | on the server, under `/var/lib/menu-app.<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-app.<site>/site.env` (untracked, mode 600) |
| Which image tag runs | `-e tag=<tag>` |
| Which Site is being deployed | the inventory entry in `hosts` |
## Deploying
From the repo root:
```sh
./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:
```sh
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 with the fix `sudo chown -R root:root
/etc/ssh/ssh_config.d`, not something this playbook works around.)
## The first run, and cutover
The first run targets a temporary vhost, so the live site keeps serving:
```sh
ansible-playbook --inventory=hosts playbook.yml --limit hopsngrains \
-e tag=2026-09-22 -e vhost=menu-new.hopsngrains.com
```
`menu-new.hopsngrains.com` needs a DNS A record pointing at the same address as
the live site before this runs: Caddy fetches a certificate for the name, which
needs it to resolve publicly.
Then, from the outside (read-only, exits non-zero on failure):
```sh
npm run check-site -- https://menu-new.hopsngrains.com
```
And from a phone on restaurant wifi: the landing page, a Section page, the page
counter, tap-to-zoom, and signing into `/admin` to publish something.
Cutover is the same playbook with the real vhost and the two retire flags:
```sh
ansible-playbook --inventory=hosts playbook.yml --limit hopsngrains \
-e tag=2026-09-22 -e vhost=menu.hopsngrains.com \
-e retire_nginx_snippet=true -e retire_nginx_container=true
```
That repoints the real vhost at the app, removes the snippet the old `nginx-menu`
playbook wrote for that hostname, and stops the old container. The old container
and `/var/lib/menu.hopsngrains.com` are kept, not deleted: deleting 319 MB of the
only copy of the old menus is a human decision, made once the new site has been
trusted.
## The admin password
The app starts with the admin disabled when `/var/lib/menu-app.<site>/site.env`
is missing, and the playbook says so. Create it once per Site:
```sh
scp scripts/provision-site-env.sh monoco:/tmp/
ssh monoco bash /tmp/provision-site-env.sh /var/lib/menu-app.hopsngrains.com
ssh monoco docker restart menu-app.hopsngrains.com
```
## Rolling back
```sh
ssh monoco docker start nginx-menu.hopsngrains.com
```
then point Caddy back at it — the cutover flags above with
`retire_nginx_snippet=false`.