menu-admin was the odd one out: it built from the working directory with a date tag and a multi-arch manifest, deployed with `-e tag=`, and kept the Docker-era container name. The other two apps build from `git archive HEAD` and tag with the commit, so what runs on the host is exactly what git knows about; build only for the architecture emily actually runs; and use one slug for the container, its network and its data directory. The build is now their three lines: `podman build --platform linux/arm64` from `git archive HEAD`, tagged `:<commit>` and `:latest`, pushed with `podman push` rather than a manifest. `deploy.sh` pushes the commit first, then refuses a dirty tracked tree or a HEAD that is not origin/main — which is what makes the commit tag trustworthy — takes the same `--force` and `--no-push`, and ends in the same `-u tanshu ... -e image_tag=<commit>`. Its own lint/typecheck/test gate stays, but behind `--checks`, so a plain deploy behaves as theirs does. The registry credential moves into vars/default.yml and the role logs in, pulls and logs out around it, as their roles do. The image is only pulled at deploy time, so the unit keeps podman's default `Pull=missing` and a reboot never needs the registry. This is the same plaintext credential the other two repos carry. One real bug goes with it: the healthcheck gate ran before the handler that restarts a changed unit, so a run that only changed the unit checked the stale container. `meta: flush_handlers` before the gate is the fix the sibling repos already made. The container, network and data directory are all menu-<site> now. The dotted Docker-era name and the NetworkAlias it needed are gone, so `podman logs menu-<site>` and `systemctl --user status menu-<site>` are the same two commands across all three apps. The Dockerfile keeps its name for the same reason: podman reads it as readily as a Containerfile, and the siblings use that name too.
137 lines
7.4 KiB
Markdown
137 lines
7.4 KiB
Markdown
# Operating the menus
|
|
|
|
For whoever is on the other end of "the menu is wrong". The design is in
|
|
`plan.md`, the vocabulary in `CONTEXT.md`, and the deployment in
|
|
`../ansible/readme.md`.
|
|
|
|
Each Site is one rootless Podman **Quadlet** unit on `emily` — a systemd *user*
|
|
service named `menu-<site>` — behind the shared Caddy unit. There is no Docker in
|
|
this path.
|
|
|
|
## Where things are
|
|
|
|
| Thing | Where |
|
|
| --------------- | ----------------------------------------------------------------------------------------- |
|
|
| The app | user unit `menu-<site>.service` on `emily`, container `menu-<site>`, image `registry.tanshu.com/menu-admin:<commit>` |
|
|
| Menu content | `~/podman-data/menu-<site>/data` — `originals/`, `drafts/`, `published/` |
|
|
| Admin | `https://<vhost>/admin` |
|
|
| The password | `~/podman-data/menu-<site>/site.env` (mode 600, never in git) |
|
|
| Categories | `data/sections.json`, edited in the admin |
|
|
| Brand and theme | `config/<site>.site.json` in this repo, baked into the image (the palette follows the Stitch system in `docs/design/stitch/`) |
|
|
|
|
Everything runs as the user `tanshu`, so every command below is run on `emily` as
|
|
that user. `sudo` is never needed, and never works for these units.
|
|
|
|
## Everyday tasks
|
|
|
|
**Publish a new menu.** Sign in at `/admin`, find the Section, fill in "New Menu
|
|
Set" (label, optional valid-until date), Create Draft, then drop the photos in.
|
|
Drag to reorder — the order in the editor is the order customers see. Press
|
|
Preview to check it, then Publish. Publishing archives whatever was current.
|
|
|
|
**Put an older menu back.** Open the Section's "older menus" fold in the admin,
|
|
open the set you want and press "Make current". Its URL does not change.
|
|
|
|
**Change or reset the admin password.** Run the provision script again with
|
|
`--force`: it overwrites the password hash, and generates a fresh session secret,
|
|
which signs out anyone who was signed in. Nothing else changes.
|
|
|
|
```sh
|
|
ssh -t emily bash /tmp/provision-site-env.sh ~/podman-data/menu-hopsngrains --force
|
|
ssh emily systemctl --user restart menu-hopsngrains
|
|
```
|
|
|
|
There is no minimum length and no password policy — the one rule is that the
|
|
password cannot be empty. It can also be set from a machine that has this repo
|
|
checked out, with the prompt on that machine rather than the server:
|
|
|
|
```sh
|
|
cd ~/programming/menu-admin
|
|
read -r -s -p 'Admin password: ' PW; echo
|
|
HASH=$(printf '%s' "$PW" | node -e "let s='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>s+=c).on('end',async()=>{const m=await import('./src/auth.ts');process.stdout.write(await m.hashPassword(s))})")
|
|
SECRET=$(node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('base64url'))")
|
|
printf 'ADMIN_PASSWORD_HASH=%s
|
|
SESSION_SECRET=%s
|
|
' "$HASH" "$SECRET" | ssh emily "podman run --rm -i --userns=keep-id:uid=1000,gid=1000 -v ~/podman-data/menu-hopsngrains:/data --entrypoint sh registry.tanshu.com/menu-admin:<commit> -c 'umask 077 && cat > /data/site.env'"
|
|
ssh emily systemctl --user restart menu-hopsngrains
|
|
```
|
|
|
|
**Add, rename, reorder or remove a category.** In the admin, open the
|
|
**Categories** fold at the bottom of the dashboard. A new category starts empty,
|
|
so customers do not see it until you publish a menu into it. Reordering there is
|
|
the order customers see. Two rules: a category's web address is fixed when it is
|
|
created (rename the name as often as you like — a printed QR code keeps working),
|
|
and a category can only be removed once it holds no menus.
|
|
|
|
**Change the restaurant's name or tagline, the icon, or the published dates.** In
|
|
the admin, open the **Site identity** fold: the name and tagline customers see,
|
|
a checkbox for whether a menu says when it was published, and an SVG upload for
|
|
the icon (under 256 KB; the upload replaces the bundled mark and "Remove the
|
|
uploaded icon" brings it back). None of this changes your web addresses.
|
|
|
|
**Change the colours.** Those are still `config/<site>.site.json`, so they need a
|
|
new build and push (`./deploy.sh`).
|
|
|
|
**Fix a missing image.** If a Page's thumbnail or viewing image is lost while its
|
|
Original survives, the app can rebuild them:
|
|
|
|
```sh
|
|
podman run --rm --userns=keep-id:uid=1000,gid=1000 -e MENU_ROOT=/data \
|
|
-e MENU_SITE_CONFIG=/app/config/hopsngrains.site.json \
|
|
-v ~/podman-data/menu-hopsngrains:/data \
|
|
--entrypoint node registry.tanshu.com/menu-admin:<commit> src/cli/repair.ts
|
|
```
|
|
|
|
`src/cli/verify.ts` does the same inventory without changing anything, and is
|
|
what the server refuses to boot on. Both need the `--userns=keep-id` mapping, or
|
|
they would see the files as a different user and fail to write.
|
|
|
|
## When something looks wrong
|
|
|
|
| Symptom | Look here |
|
|
| --- | --- |
|
|
| Landing page says the menus are being updated | no Section has a Current Menu Set — publish one in the admin |
|
|
| A Section 404s | that Section has no Current set, or the URL is misspelled |
|
|
| Sign-in rejects the right password | `podman logs menu-<site>`, then check `site.env` exists and is owned by `tanshu` |
|
|
| Admin is missing entirely | `site.env` was unreadable at boot, so the admin is disabled: run the provision script and `systemctl --user restart menu-<site>` |
|
|
| A photo uploads but looks wrong | the browser shrinks to 3000px before upload; that is expected, and the Original is kept |
|
|
| The container will not start | `systemctl --user status menu-<site>` and `podman logs menu-<site>` — a fatal integrity problem makes it refuse to serve a data directory it cannot trust |
|
|
| The unit is "not found" | its `.network` file is missing, so Quadlet refused to generate it: check `~/.config/containers/systemd/` |
|
|
| Caddy reload failed during a deploy | nothing is down: Caddy validates before applying. Read the error in the playbook output |
|
|
| Everything is slow | check the tag is current; a Viewing image is ~1600px, so a Section page should not be megabytes |
|
|
|
|
**Logs.** `podman logs -f menu-<site>`, or `journalctl --user -u menu-<site>
|
|
-f` for the unit. The app logs JSON lines, one per event, including every upload,
|
|
publish, restore and failed sign-in.
|
|
|
|
**Restarting.** `systemctl --user restart menu-<site>`. A *reload* of the Caddy
|
|
config is `podman exec caddy caddy reload --config /etc/caddy/Caddyfile`, which
|
|
does not drop the other sites.
|
|
|
|
**Checking from outside.** No sign-in, no writes, exits non-zero on failure, so
|
|
it can gate a cutover:
|
|
|
|
```sh
|
|
npm run check-site -- https://menu.hopsngrains.com
|
|
```
|
|
|
|
## Backups
|
|
|
|
The app is the only writer. The irreplaceable part is
|
|
`~/podman-data/menu-<site>/data/originals` — the photos exactly as uploaded, from
|
|
which everything else can be regenerated with `repair`. Back that directory up;
|
|
`site.env` is worth keeping too (otherwise you re-provision the password).
|
|
|
|
Because of `keep-id`, everything there is owned by `tanshu`, so a plain
|
|
`tar`/`rsync` of `~/podman-data/menu-<site>` is complete — no subuid-owned files
|
|
to trip over and no `podman unshare` needed.
|
|
|
|
## Rolling back
|
|
|
|
Deploy the previous tag — `./deploy.sh <previous-tag>` — which rebuilds the Site
|
|
at that version. `podman logs menu-<site>` is where a container that will not
|
|
start says why.
|
|
|
|
The Docker-era container was left on `monoco`, stopped and not removed, so the
|
|
last resort is to start it again and point Caddy back at it.
|