Files
menu/docs/operating.md
T
tanshu 32eeed144f Let the owner change the name, the icon and the dates
Three things that needed a rebuild and a redeploy, or had no way in at all, now
have a Site identity panel in the admin: the restaurant's name and tagline, an
SVG icon upload, and a switch for whether a menu says when it was published.
They live in data/site.json and data/brand/icon.svg, extending ADR 0007: what
the owner changes is content, and the config is the seed plus the theme, which
still needs a rebuild.

An uploaded SVG is a document that would run with this origin's authority, so it
is reduced to shapes on the way in and served with default-src 'none'. The
archive list keeps its dates even when the switch is off, because those dates
are how one old menu is told from another.

Also fixes the live check's own trap: the app caches identity in memory, so the
tests drive it through the app rather than a second store instance, which would
be a second writer.
2026-09-22 23:11:40 +05:30

4.8 KiB

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-playbooks/menu-admin/readme.md.

Where things are

Thing Where
The app container menu-app.<site> on monoco, image registry.tanshu.com/menu-admin:<tag>
Menu content /var/lib/menu-app.<site>/data — originals/, drafts/, published/
Admin https://<vhost>/admin
The password /var/lib/menu-app.<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/)

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 the admin password. On the server:

bash /tmp/provision-site-env.sh /var/lib/menu-app.hopsngrains.com --force
docker restart menu-app.hopsngrains.com

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 rebuild and push (./deploy.sh <tag>) and the playbook with the new tag.

Fix a missing image. If a Page's thumbnail or viewing image is lost while its Original survives, the app can rebuild them:

docker run --rm --user 1000:1000 -e MENU_ROOT=/data \
  -e MENU_SITE_CONFIG=/app/config/hopsngrains.site.json \
  -v /var/lib/menu-app.hopsngrains.com:/data \
  --entrypoint node registry.tanshu.com/menu-admin:<tag> src/cli/repair.ts

src/cli/verify.ts does the same inventory without changing anything, and is what the server refuses to boot on.

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 docker logs menu-app.<site>, then check site.env exists and is owned by the app user
Admin is missing entirely site.env was unreadable at boot, so the admin is disabled: run the provision script and docker restart
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 docker logs — a fatal integrity problem makes it refuse to serve a data directory it cannot trust
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. docker logs -f menu-app.<site> — JSON lines, one per event, including every upload, publish, restore and failed sign-in.

Checking from outside. No sign-in, no writes, exits non-zero on failure, so it can gate a cutover:

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

Backups

The app is the only writer. The irreplaceable part is /var/lib/menu-app.<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).

Rolling back

The old nginx Site is kept, not deleted. To go back:

docker start nginx-menu.hopsngrains.com

then point Caddy back at it — the cutover section of the playbook readme has the exact invocation, including the flag that stops the app's snippet from being re-added.