Commit Graph
12 Commits
Author SHA1 Message Date
tanshu 2ef044aa45 Let Docker allocate the network subnet again
Reverts the explicit subnets: the pools were freed by deleting five networks, so
the network role is back to its single idempotent task - ensure the network
exists and Caddy is on it - and the Sites no longer declare a subnet. The
deployment readme keeps the note that a new Site needs a config, and therefore a
new tag, but no longer asks for a free /24.

If the pools fill up again the failure is recognisable: 'all predefined address
pools have been fully subnetted', and the choices are deleting stale networks or
declaring a subnet per Site.
2026-09-23 00:21:57 +05:30
tanshu 01ce0bd609 Create each Site's network with its own subnet
The deploy failed on hngmohali with 'all predefined address pools have been
fully subnetted': every 172.17-172.30/16 is in use, 172.31/16 is the host's own
VPC so Docker will not allocate from it, and all sixteen 192.168.0-240/20 blocks
are taken. Docker therefore refuses a network created without a subnet.

Each Site now names a free network_subnet (10.210.0.0/16, one /24 each), the role
looks the network up first and only creates it when it is absent and with that
subnet, and it fails with an explanation when a Site declares none. Existing
networks are left alone, because Docker cannot change a subnet in place and the
Sites already running have theirs. The test checks every Site declares one, and
the deployment readme says a new Site needs a free subnet.

deploy.sh now deploys every Site by default, with SITE=<name> to narrow it, which
is what the local edit was reaching for.
2026-09-23 00:18:04 +05:30
tanshu 33a8d56e8b Add the Great Bear and Hops n Grains Mohali Sites
One image serves every Site, so a new Site is three small files: an inventory
entry, a vars file naming its web address and data root, and a config that seeds
its theme and first category list (baked into the image, which is why a new Site
needs a new tag).

Both reuse the directory their old static menus were served from, and the caddy
role drops the snippet the retired nginx-menu playbook wrote for each hostname,
so a deploy repoints each address at its own container with nothing else to do.

The seeded category lists are a starting point, not a decision: categories, names
and icons are all editable in each Site's own admin. A test now checks the
inventory, config and vars agree for every Site and that no config is orphaned,
because a half-added Site was the obvious way to get this wrong.
2026-09-23 00:09:36 +05:30
tanshu 11f60c619f No minimum password length, and say how to reset it
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.
2026-09-23 00:02:35 +05:30
tanshu b178e00ebf Say why the password step needs ssh -t 2026-09-22 23:55:49 +05:30
tanshu c2e499ad87 Declare the healthcheck in Ansible instead of trusting the image
podman builds an OCI image by default and drops Docker's HEALTHCHECK instruction,
so the container had no health at all and the play failed waiting for one. The
check is now declared on the docker_container task - node is in the image and the
exec form needs no shell quoting - which is both what operators see in docker ps
and what an orchestrator would use.

The wait still asks the app directly rather than reading State.Health, because a
container that already exists is only rebuilt when the image tag changes or when
-e recreate_container=true is passed, and a wait must not depend on a container
attribute an existing one may not have.
2026-09-22 23:47:30 +05:30
tanshu 1923c80298 Stop the health wait from failing a healthy deploy, and clear a stale address
The first real deploy left the site on 502 and this is why. podman builds an OCI
image by default and drops the Docker HEALTHCHECK instruction, so the app
container has no State.Health at all; the role waited on that field, exhausted
its retries and failed the play, so the caddy role never ran and Caddy kept
proxying menu.hopsngrains.com to the old nginx container that is now gone.

The wait now asks the app itself: docker exec node -e fetch /healthz, which needs
no healthcheck and no DNS. And because the address may still be claimed by the
snippet the retired nginx-menu playbook wrote, the caddy role drops that marker
before writing its own - two blocks for one address make Caddy refuse the config,
and a refused reload quietly leaves the old proxy in place.

check-site also asked for the font the old design used, so it would have failed a
healthy site; it checks one we ship.
2026-09-22 23:44:54 +05:30
tanshu 25ef94d7af Use the old static menu directory as the app's data root
That was the intent of the uncommitted edit, and it is a better location than a
second directory: the app inherits the place the menus were already served from
and only ever reads and writes its own data/ and site.env inside it.

app_root is written as /var/lib/{{ host }}, quoted, because the unquoted form is
what failed to parse. Every documented path follows, and the deployment readme
now says that emptying the old menu files means deleting those files rather than
the directory, which holds the live content.
2026-09-22 23:38:21 +05:30
tanshu 65b66f712d Quote every template block in the Site vars
The deploy failed on 'YAML parsing failed: missing quotes around a template
block'. vars/hopsngrains.yml had been edited to derive its names from the web
address, but the two derived values were unquoted and YAML reads a leading brace
as a flow mapping, so the file did not parse at all.

The names now derive from a title (the domain) with every interpolation quoted,
which is what nginx-menu did and what makes the file copyable for another Site:
host menu.<title>, container menu-app.<title>, network <container>_net, data
/var/lib/menu-app.<title>. Resolving the vars offline gives exactly the values
the docs promise, including /app/config/hopsngrains.site.json inside the image.

The old edit derived from host, so app_root would have been
/var/lib/menu.hopsngrains.com — the directory the old static menus were served
from.
2026-09-22 23:36:42 +05:30
tanshu f2d8058456 Deploy in one run, with nothing older to preserve
There is no previous system kept serving, so the playbook no longer knows about
one: the retire flags, the temporary web address, the task that removed the old
nginx snippet and the task that stopped the old container are gone. One run
starts the container and points the Site's web address at it, and deploying an
earlier tag is the rollback. The vars lose vhost, retire_nginx_snippet,
retire_nginx_container and legacy_container; the Caddy role keeps only the
snippet upsert.

The deployment readme drops its staged-rollout sections and the glossary of words
that no longer exist, and the plan's sequence and the operating notes describe
the single run instead.
2026-09-22 23:27:17 +05:30
tanshu 041b8b62fe Say what vhost, parallel run and cutover mean
The deployment readme used three pieces of jargon without defining any of them,
which reads as a locked door to anyone doing their first deploy. It now opens
with what each word means, the table of which address serves what at each stage,
and the note that skipping the parallel run is a legitimate choice.

Domain terms stay in CONTEXT.md; these are operational words for this directory,
so they are defined here.
2026-09-22 23:24:50 +05:30
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