The script refused anything under ten characters. That is a policy the owner should hold, not one the tooling invents; the only rule left is that the password cannot be empty, since an empty one would let anyone in. Resetting is the same script with --force, which overwrites the hash and generates a fresh session secret, signing out anyone who was signed in - now written down in the operating notes and the deployment readme, including the form that prompts on your own machine instead of the server.
237 lines
12 KiB
Markdown
237 lines
12 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-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.<site>/data` — `originals/`, `drafts/`, `published/` |
|
|
| Admin | `https://<vhost>/admin` |
|
|
| The password | `/var/lib/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/`) |
|
|
|
|
## 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 monoco bash /tmp/provision-site-env.sh /var/lib/menu.hopsngrains.com --force
|
|
ssh monoco docker restart menu-app.hopsngrains.com
|
|
```
|
|
|
|
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/static-menu/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 monoco "docker run --rm -i --user 1000:1000 -v /var/lib/menu.hopsngrains.com:/data --entrypoint sh registry.tanshu.com/menu-admin:2026-09-22 -c 'umask 077 && cat > /data/site.env'"
|
|
ssh monoco 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:
|
|
|
|
```sh
|
|
docker run --rm --user 1000:1000 -e MENU_ROOT=/data \
|
|
-e MENU_SITE_CONFIG=/app/config/hopsngrains.site.json \
|
|
-v /var/lib/menu.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:
|
|
|
|
```sh
|
|
npm run check-site -- https://menu.hopsngrains.com
|
|
```
|
|
|
|
## Backups
|
|
|
|
The app is the only writer. The irreplaceable part is
|
|
`/var/lib/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).
|
|
|
|
## Rolling back
|
|
|
|
Deploy the previous tag — `./deploy.sh <previous-tag>` — which rebuilds the Site at
|
|
that version. `docker logs menu-app.<site>` is where a container that will not start
|
|
says why.
|
|
|
|
|
|
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.<site>/data` — `originals/`, `drafts/`, `published/` |
|
|
| Admin | `https://<vhost>/admin` |
|
|
| The password | `/var/lib/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/`) |
|
|
|
|
## 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 monoco bash /tmp/provision-site-env.sh /var/lib/menu.hopsngrains.com --force
|
|
ssh monoco docker restart menu-app.hopsngrains.com
|
|
```
|
|
|
|
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/static-menu/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 monoco "docker run --rm -i --user 1000:1000 -v /var/lib/menu.hopsngrains.com:/data --entrypoint sh registry.tanshu.com/menu-admin:2026-09-22 -c 'umask 077 && cat > /data/site.env'"
|
|
ssh monoco 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:
|
|
|
|
```sh
|
|
docker run --rm --user 1000:1000 -e MENU_ROOT=/data \
|
|
-e MENU_SITE_CONFIG=/app/config/hopsngrains.site.json \
|
|
-v /var/lib/menu.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:
|
|
|
|
```sh
|
|
npm run check-site -- https://menu.hopsngrains.com
|
|
```
|
|
|
|
## Backups
|
|
|
|
The app is the only writer. The irreplaceable part is
|
|
`/var/lib/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).
|
|
|
|
## Rolling back
|
|
|
|
The old nginx Site is kept, not deleted. To go back:
|
|
|
|
```sh
|
|
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.
|