diff --git a/menu-admin/readme.md b/menu-admin/readme.md index 010267d..4322b4c 100644 --- a/menu-admin/readme.md +++ b/menu-admin/readme.md @@ -30,8 +30,12 @@ to the app repo, a new image, and a redeploy (ADR 0005). # 2. First run: a temporary hostname, so the live site keeps serving ansible-playbook -l monoco -e site=hopsngrains -e tag=2026-09-22 playbook.yml -# 3. The admin needs a password before it can be used -ssh monoco scripts/provision-site-env.sh /var/lib/menu-app.hopsngrains.com +# 3. The admin needs a password before it can be used. The script lives in the +# app repo and writes site.env through a container running as the app user, +# so the SSH user never needs write access to the data root. +scp scripts/provision-site-env.sh monoco:/tmp/ +ssh monoco bash /tmp/provision-site-env.sh /var/lib/menu-app.hopsngrains.com +ssh monoco docker restart menu-app.hopsngrains.com ``` The first run creates `/var/lib/menu-app.hopsngrains.com`, starts the container @@ -45,7 +49,17 @@ so — the public menu still serves. ## Checking the parallel run -From a phone on restaurant wifi, not from the server: +`menu-new.hopsngrains.com` needs a DNS A record pointing at the same address as +`menu.hopsngrains.com` before this runs: Caddy fetches a certificate for the +name, which needs it to resolve publicly. + +Then, from the outside (read-only, exits non-zero on failure): + +```sh +npm run check-site -- https://menu-new.hopsngrains.com +``` + +And from a phone on restaurant wifi, not from the server: - `https://menu-new.hopsngrains.com/` lists the Sections that have a current menu. - A Section page scrolls, the pour line tracks, tapping a page opens it full width. diff --git a/menu-admin/roles/menu-app/tasks/main.yaml b/menu-admin/roles/menu-app/tasks/main.yaml index 95107ba..9a00a7d 100644 --- a/menu-admin/roles/menu-app/tasks/main.yaml +++ b/menu-admin/roles/menu-app/tasks/main.yaml @@ -53,15 +53,35 @@ networks: - name: "{{ docker_network }}" -- name: Wait for the app to answer - uri: - url: "http://{{ docker_container }}:3000/healthz" - status_code: 200 +- name: Keep site.env readable only by the app user + file: + path: "{{ app_root }}/site.env" + owner: "{{ app_uid }}" + group: "{{ app_gid }}" + mode: "0600" + when: menu_site_env.stat.exists + +- name: Wait for the app to report healthy + # The image's own HEALTHCHECK fetches /healthz inside the container. Polling + # that is deliberate: a container name resolves only on the Docker network, so + # the host cannot fetch http://{{ docker_container }}:3000/healthz. + docker_container_info: + name: "{{ docker_container }}" register: menu_health - retries: 10 - delay: 2 - until: menu_health.status == 200 changed_when: false + retries: 20 + delay: 5 + until: >- + menu_health.container is defined and + menu_health.container.State.Health is defined and + menu_health.container.State.Health.Status == 'healthy' + +- name: Fail when the app never becomes healthy + fail: + msg: >- + {{ docker_container }} did not report healthy within 100s. Check + `docker logs {{ docker_container }}`. + when: menu_health.container.State.Health.Status | default('unknown') != 'healthy' - name: Check the data directory for integrity problems # Reads the same directory the container writes, as the same user, so a