Files
server-infra/roles/app-deploy/tasks/django.yml
T
westfarn 95fbae8db4
Sync runner checkout / sync (push) Successful in 6s
Register college_craft for django deploy (closes #24) (#25)
## Summary
- Closes [#24](#24).
- Register `college_craft` in `app_catalog` (`type: django`, repo `ai_ml_operations/college_craft`, default branch `master`).
- Add `host_apps` prod `:8006` / beta `:8016` 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 `--app client_site` leftover.
- Add `college_craft` to `scripts/deploy.sh` `--app` help.

## Out of scope (ops, after merge)
- Create Postgres DBs `college_craft` / `college_craft_beta` on `10.0.0.230` and grant `westfarn`.
- Write control-node secrets `~/Documents/secrets/college_craft/college_craft_{prod,beta}.env`.
- NPM: `collegecraft.com` (+ www) → `:8006`; `college-craft-preview.aimloperations.com` → `:8016`.
- Smoke deploy after secrets + DBs exist:
  ```bash
  ~/Documents/repos/server-infra/scripts/deploy.sh --app college_craft --env beta --ref master
  ~/Documents/repos/server-infra/scripts/deploy.sh --app college_craft --env prod --ref master
  ```

## Test plan
- [ ] Confirm `app_catalog.college_craft` matches other django apps (`compose_file`, `migrate_cmd`).
- [ ] Confirm ports 8006/8016 unused elsewhere and match across all five hosts.
- [ ] Confirm only adama has `compose_profiles: [worker]` for college_craft.
- [ ] After secrets + DBs: deploy beta then prod; `/healthz/` up on both; prod holding page, beta full site.

Reviewed-on: #25
2026-09-02 07:39:03 -07:00

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 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