Sync runner checkout / sync (push) Successful in 6s
## Summary - Closes [#27](#27). - Register `print_forge` in `app_catalog` (`type: django`, repo `ai_ml_operations/print_forge`, default branch `master`). - Add `host_apps` prod `:8007` / beta `:8019` on **adama**, **roslin**, **starbuck**, **apollo**, and **ai-server-4080**. - dj-queue **worker singleton on adama** only (`compose_profiles: [worker]`); other hosts web-only. - Document ports, NPM hosts, Postgres DBs, secrets, Nominatim (`10.0.0.128:8089`), and the app-repo companion ([print_forge#1](ai_ml_operations/print_forge#1)). - Update `scripts/deploy.sh` `--app` help. - Move unused `abc_be` prod port reserve **8007 → 8009** so it does not collide with `print_forge`. ## Test plan - [ ] Confirm `print_forge` appears in `app_catalog` and `--app print_forge` is listed in `deploy.sh --help` - [ ] Confirm beta `host_apps` on all five hosts (port **8019**); adama has `compose_profiles: [worker]` - [ ] Confirm prod port **8007** reserved on those hosts (no NPM until launch) - [ ] After merge (ops, not this PR): create Postgres DBs `print_forge_beta` + `print_forge` on `10.0.0.230`, grant `westfarn` - [ ] After merge (ops): write `~/Documents/secrets/print_forge/print_forge_{beta,prod}.env` from app `.env.prod.example`; add `print_forge:<token>` to `SHORTENER_API_TOKENS` (beta first) - [ ] After merge (ops): NPM `print-forge-preview.aimloperations.com` → `:8019` active/active; do **not** NPM-route `printforgeprints.com` until launch - [ ] After secrets + DBs: `~/Documents/repos/server-infra/scripts/deploy.sh --app print_forge --env beta --ref master` and hit `/healthz/` Reviewed-on: #28
120 lines
4.2 KiB
YAML
120 lines
4.2 KiB
YAML
---
|
|
# Deploy one Django app+env as its own docker compose project.
|
|
# Called per item with loop_var app_item = { name, env, port }.
|
|
|
|
- name: "django[{{ app_item.name }}/{{ app_item.env }}] locals"
|
|
ansible.builtin.set_fact:
|
|
_spec: "{{ app_catalog[app_item.name] }}"
|
|
_src: "{{ apps_src_dir }}/{{ app_item.name }}_{{ app_item.env }}"
|
|
_envfile: "{{ apps_env_dir }}/{{ app_item.name }}_{{ app_item.env }}.env"
|
|
_secret_src: "{{ secrets_dir }}/{{ app_item.name }}/{{ app_item.name }}_{{ app_item.env }}.env"
|
|
_project: "{{ app_item.name }}_{{ app_item.env }}"
|
|
_ref: "{{ app_ref | default(app_catalog[app_item.name].default_branch) }}"
|
|
|
|
- name: "django[{{ _project }}] ensure base dirs"
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: "{{ admin_user }}"
|
|
group: "{{ admin_user }}"
|
|
mode: "0750"
|
|
loop:
|
|
- "{{ apps_src_dir }}"
|
|
- "{{ apps_env_dir }}"
|
|
|
|
- name: "django[{{ _project }}] check local secret exists"
|
|
ansible.builtin.stat:
|
|
path: "{{ _secret_src }}"
|
|
register: _secret_stat
|
|
delegate_to: localhost
|
|
become: false
|
|
|
|
- name: "django[{{ _project }}] fail when local secret missing"
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
Missing local secret {{ _secret_src }} on the control node.
|
|
Create it (DATABASE_URL to the shared external Postgres, DJANGO_SECRET_KEY,
|
|
WEB_PORT={{ app_item.port }}, etc.) before deploying. It is never committed
|
|
to git.
|
|
when: not _secret_stat.stat.exists
|
|
|
|
- name: "django[{{ _project }}] push secret to {{ _envfile }}"
|
|
ansible.builtin.copy:
|
|
src: "{{ _secret_src }}"
|
|
dest: "{{ _envfile }}"
|
|
owner: "{{ admin_user }}"
|
|
group: "{{ admin_user }}"
|
|
mode: "0600"
|
|
|
|
- name: "django[{{ _project }}] checkout {{ _ref }}"
|
|
ansible.builtin.git:
|
|
repo: "{{ _spec.repo }}"
|
|
dest: "{{ _src }}"
|
|
version: "{{ _ref }}"
|
|
force: true
|
|
accept_hostkey: true
|
|
become: true
|
|
become_user: "{{ admin_user }}"
|
|
|
|
- name: "django[{{ _project }}] install .env into checkout"
|
|
ansible.builtin.copy:
|
|
src: "{{ _envfile }}"
|
|
dest: "{{ _src }}/.env"
|
|
remote_src: true
|
|
owner: "{{ admin_user }}"
|
|
group: "{{ admin_user }}"
|
|
mode: "0600"
|
|
|
|
# COMPOSE_PROFILES must match the start step below: without it, profile-gated
|
|
# services (e.g. the dj-queue worker) are skipped here and keep a stale image.
|
|
- name: "django[{{ _project }}] build images"
|
|
ansible.builtin.command:
|
|
cmd: "docker compose -f {{ _spec.compose_file }} --env-file .env build"
|
|
chdir: "{{ _src }}"
|
|
environment:
|
|
COMPOSE_PROJECT_NAME: "{{ _project }}"
|
|
WEB_PORT: "{{ app_item.port }}"
|
|
COMPOSE_PROFILES: "{{ (app_item.compose_profiles | default([])) | join(',') }}"
|
|
become: true
|
|
become_user: "{{ admin_user }}"
|
|
changed_when: true
|
|
|
|
- name: "django[{{ _project }}] legacy systemd unit"
|
|
ansible.builtin.set_fact:
|
|
_legacy_systemd: "{{ _spec.legacy_systemd_units[app_item.env] | default('') }}"
|
|
when: _spec.legacy_systemd_units is defined
|
|
|
|
- name: "django[{{ _project }}] probe legacy systemd unit"
|
|
ansible.builtin.command:
|
|
cmd: systemctl list-unit-files {{ _legacy_systemd }}.service --no-legend
|
|
register: _legacy_systemd_probe
|
|
changed_when: false
|
|
failed_when: false
|
|
become: true
|
|
when: _legacy_systemd | default('') | length > 0
|
|
|
|
- name: "django[{{ _project }}] stop legacy systemd unit"
|
|
ansible.builtin.systemd:
|
|
name: "{{ _legacy_systemd }}"
|
|
state: stopped
|
|
enabled: false
|
|
become: true
|
|
when:
|
|
- _legacy_systemd | default('') | length > 0
|
|
- _legacy_systemd_probe.stdout | default('') | length > 0
|
|
|
|
# Optional host_apps.compose_profiles (e.g. [worker]) activates compose profiles
|
|
# on this host only. Used for monica_site / college_craft / print_forge dj-queue singleton on
|
|
# adama — without it, plain up --remove-orphans can drop a manually started worker.
|
|
- name: "django[{{ _project }}] start containers"
|
|
ansible.builtin.command:
|
|
cmd: "docker compose -f {{ _spec.compose_file }} --env-file .env up -d --remove-orphans"
|
|
chdir: "{{ _src }}"
|
|
environment:
|
|
COMPOSE_PROJECT_NAME: "{{ _project }}"
|
|
WEB_PORT: "{{ app_item.port }}"
|
|
COMPOSE_PROFILES: "{{ (app_item.compose_profiles | default([])) | join(',') }}"
|
|
become: true
|
|
become_user: "{{ admin_user }}"
|
|
changed_when: true
|