From f89bd636851c77583d447dc7375fece625d9fe9e Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Tue, 22 Sep 2026 21:27:23 +0530 Subject: [PATCH] Point at the playbook's new home The playbook now lives in ansible-playbooks/menu-admin/ with the other playbooks; a second copy here would be edited by mistake. --- README.md | 4 + deploy/ansible/playbook.yml | 19 ---- deploy/ansible/readme.md | 81 ----------------- deploy/ansible/roles/caddy/handlers/main.yaml | 6 -- deploy/ansible/roles/caddy/tasks/main.yaml | 39 -------- deploy/ansible/roles/menu-app/tasks/main.yaml | 91 ------------------- deploy/ansible/roles/network/tasks/main.yaml | 8 -- deploy/ansible/vars/default.yml | 29 ------ deploy/ansible/vars/hopsngrains.yml | 20 ---- 9 files changed, 4 insertions(+), 293 deletions(-) delete mode 100644 deploy/ansible/playbook.yml delete mode 100644 deploy/ansible/readme.md delete mode 100644 deploy/ansible/roles/caddy/handlers/main.yaml delete mode 100644 deploy/ansible/roles/caddy/tasks/main.yaml delete mode 100644 deploy/ansible/roles/menu-app/tasks/main.yaml delete mode 100644 deploy/ansible/roles/network/tasks/main.yaml delete mode 100644 deploy/ansible/vars/default.yml delete mode 100644 deploy/ansible/vars/hopsngrains.yml diff --git a/README.md b/README.md index 2747504..00ba007 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,12 @@ config/ Per-Site config, one file per Site test/ node:test suites docs/plan.md The agreed plan docs/adr/ Decisions worth remembering +deploy.sh Tests, build, push ``` +The Ansible playbook lives in `ansible-playbooks/menu-admin/`, beside the other +playbooks, because that is where it is run from. + ## Working on it Node 24 or newer runs the TypeScript sources directly. diff --git a/deploy/ansible/playbook.yml b/deploy/ansible/playbook.yml deleted file mode 100644 index b420741..0000000 --- a/deploy/ansible/playbook.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -# Deploys the menu app for one Site. Copy this directory to -# ansible-playbooks/menu-admin/ and run it from there: -# -# ansible-playbook -l monoco -e site=hopsngrains -e tag= playbook.yml -# -# The first run targets a temporary vhost (menu-new...) so the new site can be -# checked against the live one. Cutover is the same playbook with the real vhost -# and the two retire flags set; see readme.md. -- hosts: all - become: true - vars_files: - - vars/default.yml - - "vars/{{ site }}.yml" - - roles: - - network - - menu-app - - caddy diff --git a/deploy/ansible/readme.md b/deploy/ansible/readme.md deleted file mode 100644 index 010267d..0000000 --- a/deploy/ansible/readme.md +++ /dev/null @@ -1,81 +0,0 @@ -# menu-admin playbook - -Deploys the menu app for one Site, beside the existing nginx container at first -and in place of it after cutover. Copy this directory to -`ansible-playbooks/menu-admin/` and run it from there, so `playbook_dir` and the -inventory match the other playbooks. - -This playbook owns the `menu-app` container and the Site's snippet in the shared -Caddyfile. It does not touch the app's source, the image, or the other Sites' -containers. - -## What lives where - -| Thing | Where | -| --- | --- | -| Sections, brand, theme | `config/.site.json` in the app repo, baked into the image | -| Menu content, Drafts, Originals | `/var/lib/menu-app./data` on the host | -| Admin password hash, session secret | `/var/lib/menu-app./site.env` (untracked, mode 600) | -| Which image tag runs | `-e tag=` | - -Sections are deliberately not editable on the server: changing them is a commit -to the app repo, a new image, and a redeploy (ADR 0005). - -## Deploying - -```sh -# 1. Build and push from the app repo -./deploy.sh 2026-09-22 # -> registry.tanshu.com/menu-admin:2026-09-22 - -# 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 -``` - -The first run creates `/var/lib/menu-app.hopsngrains.com`, starts the container -on the `menu-app.hopsngrains.com_net` network, adds a snippet for -`menu-new.hopsngrains.com` to the shared Caddyfile, and reloads Caddy. The -existing `menu.hopsngrains.com` vhost still points at the old nginx container. - -`site.env` is deliberately not created by Ansible: the password is typed by a -human on the server. Until it exists the admin is disabled and the playbook says -so — the public menu still serves. - -## Checking the parallel run - -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. -- The admin signs in, uploads a real photo, reorders, previews, publishes. -- After publishing, the phone shows the new menu and the old set appears under - "Older menus". - -## Cutover - -```sh -ansible-playbook -l monoco -e site=hopsngrains -e tag=2026-09-22 \ - -e vhost=menu.hopsngrains.com \ - -e retire_nginx_snippet=true \ - -e retire_nginx_container=true \ - playbook.yml -``` - -This repoints the real vhost at the app, removes the snippet the old playbook -wrote for that hostname, and stops the old container. The old container and -`/var/lib/menu.hopsngrains.com` are kept, not deleted: deleting 319 MB of the -only copy of the old menus is a human decision, made a week later, once the new -site has been trusted. - -## Rolling back - -```sh -docker start nginx-menu.hopsngrains.com -ansible-playbook -l monoco -e site=hopsngrains -e tag= \ - -e vhost=menu.hopsngrains.com -e retire_nginx_snippet=false playbook.yml -``` - -Then delete the app's snippet from `/var/lib/caddy/conf/Caddyfile` by hand, or -set `vhost` back to the temporary name, and reload Caddy. diff --git a/deploy/ansible/roles/caddy/handlers/main.yaml b/deploy/ansible/roles/caddy/handlers/main.yaml deleted file mode 100644 index e33300c..0000000 --- a/deploy/ansible/roles/caddy/handlers/main.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Reload Caddy - # Caddy validates before it applies, so a mistake here leaves the running - # config untouched and fails the play loudly. - command: "docker exec -w /etc/caddy {{ caddy_container }} caddy reload" - listen: Reload Caddy diff --git a/deploy/ansible/roles/caddy/tasks/main.yaml b/deploy/ansible/roles/caddy/tasks/main.yaml deleted file mode 100644 index e67b55e..0000000 --- a/deploy/ansible/roles/caddy/tasks/main.yaml +++ /dev/null @@ -1,39 +0,0 @@ ---- -- name: Check the Caddyfile exists - stat: - path: "{{ caddyfile_path }}" - register: menu_caddyfile - -- name: Fail when Caddy is not where we expect it - fail: - msg: >- - {{ caddyfile_path }} is missing. Deploy the caddy playbook (which owns the - Caddy container and its config) before this one. - when: not menu_caddyfile.stat.exists - -# blockinfile replaces whatever sits between its markers, so changing `vhost` -# updates the snippet in place. The sibling nginx-menu role only appended a -# block when its exact text was absent, which cannot change a proxy target. -- name: Point Caddy at the app - blockinfile: - path: "{{ caddyfile_path }}" - marker: "# {mark} ANSIBLE MANAGED: menu app ({{ site }})" - block: "{{ caddy_snippet }}" - create: no - notify: Reload Caddy - -- name: Remove the snippet the nginx-menu playbook wrote for this vhost - # A different marker, written by a different role. Leaving it in place would - # leave Caddy with two blocks for the same hostname. - blockinfile: - path: "{{ caddyfile_path }}" - marker: "# {mark} Ansible managed Caddy snippet for {{ host }}" - state: absent - when: retire_nginx_snippet | bool - notify: Reload Caddy - -- name: Stop the old nginx container - docker_container: - name: "{{ legacy_container }}" - state: stopped - when: retire_nginx_container | bool diff --git a/deploy/ansible/roles/menu-app/tasks/main.yaml b/deploy/ansible/roles/menu-app/tasks/main.yaml deleted file mode 100644 index 95107ba..0000000 --- a/deploy/ansible/roles/menu-app/tasks/main.yaml +++ /dev/null @@ -1,91 +0,0 @@ ---- -- name: Pull the app image - docker_image: - name: "{{ image_name }}" - tag: "{{ image_tag }}" - source: pull - force_source: yes - -- name: Ensure the app root exists - file: - path: "{{ app_root }}" - state: directory - owner: "{{ app_uid }}" - group: "{{ app_gid }}" - mode: "0750" - -- name: Ensure the data directory exists - file: - path: "{{ app_root }}/data" - state: directory - owner: "{{ app_uid }}" - group: "{{ app_gid }}" - mode: "0750" - -- name: Look for the untracked site.env - stat: - path: "{{ app_root }}/site.env" - register: menu_site_env - -- name: Warn when the admin has no password yet - debug: - msg: >- - {{ app_root }}/site.env is missing, so the admin is disabled: the public - menu still serves. Run scripts/provision-site-env.sh {{ app_root }} on - this host, then re-run this playbook. - when: not menu_site_env.stat.exists - -- name: Run the menu app - docker_container: - name: "{{ docker_container }}" - image: "{{ image_name }}:{{ image_tag }}" - state: started - restart_policy: unless-stopped - user: "{{ app_uid }}:{{ app_gid }}" - env: - MENU_ROOT: /data - MENU_SITE_CONFIG: "/app/config/{{ site }}.site.json" - SECURE_COOKIES: "true" - LOG_LEVEL: info - PORT: "3000" - volumes: - - "{{ app_root }}:/data" - networks: - - name: "{{ docker_network }}" - -- name: Wait for the app to answer - uri: - url: "http://{{ docker_container }}:3000/healthz" - status_code: 200 - register: menu_health - retries: 10 - delay: 2 - until: menu_health.status == 200 - changed_when: false - -- name: Check the data directory for integrity problems - # Reads the same directory the container writes, as the same user, so a - # permissions mistake shows up here rather than at 8pm service. - command: - argv: - - docker - - run - - --rm - - --user - - "{{ app_uid }}:{{ app_gid }}" - - -e - - MENU_ROOT=/data - - -e - - "MENU_SITE_CONFIG=/app/config/{{ site }}.site.json" - - --entrypoint - - node - - "{{ image_name }}:{{ image_tag }}" - - src/cli/verify.ts - register: menu_verify - changed_when: false - failed_when: false - -- name: Report integrity problems - debug: - msg: "{{ menu_verify.stdout_lines | default([]) }}" - when: menu_verify.rc != 0 diff --git a/deploy/ansible/roles/network/tasks/main.yaml b/deploy/ansible/roles/network/tasks/main.yaml deleted file mode 100644 index 049e60b..0000000 --- a/deploy/ansible/roles/network/tasks/main.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- name: "Ensure {{ docker_network }} exists and Caddy is on it" - docker_network: - name: "{{ docker_network }}" - state: present - connected: - - "{{ caddy_container }}" - appends: yes diff --git a/deploy/ansible/vars/default.yml b/deploy/ansible/vars/default.yml deleted file mode 100644 index a4be919..0000000 --- a/deploy/ansible/vars/default.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -# Defaults shared by every Site. Override per Site in vars/.yml, and pass -# -e site= on the command line. - -# Which Site config inside the image to run, and therefore which vhost it is -# for. The file lives in the app repo at config/.site.json. -site: hopsngrains - -image_name: registry.tanshu.com/menu-admin -image_tag: "{{ tag | default('latest') }}" - -# The hostname Caddy serves this Site on. The parallel run uses a temporary -# name; cutover points the real one at the app. -vhost: "{{ host }}" - -# Cutover switches. Both stay off until the parallel run has been checked. -retire_nginx_snippet: false -retire_nginx_container: false - -app_uid: 1000 -app_gid: 1000 - -caddy_container: caddy -caddyfile_path: /var/lib/caddy/conf/Caddyfile - -caddy_snippet: | - {{ vhost }} { - reverse_proxy {{ docker_container }}:3000 - } diff --git a/deploy/ansible/vars/hopsngrains.yml b/deploy/ansible/vars/hopsngrains.yml deleted file mode 100644 index 2b70206..0000000 --- a/deploy/ansible/vars/hopsngrains.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -# Hops n Grains. The Section list and branding live in the app repo at -# config/hopsngrains.site.json, which is baked into the image, so this file -# only says where to run it. -site: hopsngrains - -host: menu.hopsngrains.com - -docker_container: menu-app.hopsngrains.com -docker_network: "{{ docker_container }}_net" - -# First deployment: a temporary hostname beside the existing site. -vhost: menu-new.hopsngrains.com - -# Everything the app writes — data/ and the untracked site.env. -app_root: /var/lib/menu-app.hopsngrains.com - -# The old nginx container serving /var/lib/menu.hopsngrains.com. Left alone -# until cutover. -legacy_container: nginx-menu.hopsngrains.com