# 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-` — behind the shared Caddy unit. There is no Docker in this path. ## Where things are | Thing | Where | | --------------- | ----------------------------------------------------------------------------------------- | | The app | user unit `menu-.service` on `emily`, container `menu-`, image `registry.tanshu.com/menu-admin:` | | Menu content | `~/podman-data/menu-/data` — `originals/`, `drafts/`, `published/` | | Admin | `https:///admin` | | The password | `~/podman-data/menu-/site.env` (mode 600, never in git) | | Categories | `data/sections.json`, edited in the admin | | Brand and theme | `config/.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: -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.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: 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-`, 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-` | | 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-` and `podman logs menu-` — 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-`, or `journalctl --user -u menu- -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-`. 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-/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-` is complete — no subuid-owned files to trip over and no `podman unshare` needed. ## Rolling back Deploy the previous tag — `./deploy.sh ` — which rebuilds the Site at that version. `podman logs menu-` 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.