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.
112 lines
3.1 KiB
YAML
112 lines
3.1 KiB
YAML
---
|
|
- 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: 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
|
|
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
|
|
# 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
|