ansible/playbooks/app/configure_app.yml
ilia c2e797a027
All checks were successful
CI / skip-ci-check (pull_request) Successful in 1m22s
CI / lint-and-test (pull_request) Successful in 1m27s
CI / ansible-validation (pull_request) Successful in 2m53s
CI / secret-scanning (pull_request) Successful in 1m24s
CI / dependency-scan (pull_request) Successful in 1m28s
CI / sast-scan (pull_request) Successful in 2m32s
CI / license-check (pull_request) Successful in 1m28s
CI / vault-check (pull_request) Successful in 2m30s
CI / playbook-test (pull_request) Successful in 2m32s
CI / container-scan (pull_request) Successful in 1m53s
CI / sonar-analysis (pull_request) Successful in 2m40s
CI / workflow-summary (pull_request) Successful in 1m22s
feat(app_setup): improve deployment reliability and add mirrormatch support
- Fix deploy script to handle non-git directories by cloning to temp
  location and moving contents, preserving .env files during clone
- Remove comment lines from env.j2 template to prevent xargs errors
- Add initial deploy task to app_setup role to ensure app is deployed
  before service starts
- Fix migrate command precedence to check env-specific overrides first
- Add sudo to systemctl restart commands in deploy script
- Update documentation with project-specific configuration notes

These changes improve deployment reliability for all app projects while
adding support for mirrormatch-specific requirements (db:push, seeding).
All changes are backward-compatible with existing projects (pote, punimTag).
2026-01-04 16:50:54 -05:00

146 lines
7.4 KiB
YAML

---
# Playbook: app/configure_app.yml
# Purpose: Configure OS + app runtime on app project guests created via provision_vms.yml
# Targets: app_all or per-project group created dynamically
# Tags: app, configure
#
# Usage:
# - Run one project: ansible-playbook -i inventories/production playbooks/app/site.yml -e app_project=projectA
# - Run all projects: ansible-playbook -i inventories/production playbooks/app/site.yml
- name: Build dynamic inventory from app_projects (so configure can run standalone)
hosts: localhost
connection: local
gather_facts: false
tags: ['app', 'configure']
vars:
selected_projects: >-
{{
(app_projects | dict2items | map(attribute='key') | list)
if (app_project is not defined or app_project | length == 0)
else [app_project]
}}
selected_envs: >-
{{
[app_env]
if (app_env is defined and app_env | length > 0)
else ['dev', 'qa', 'prod']
}}
app_bootstrap_user_default: root
# If true, configure plays will use vault_lxc_root_password for initial SSH bootstrap.
bootstrap_with_root_password_default: false
tasks:
- name: Validate requested project exists
ansible.builtin.assert:
that:
- app_project is not defined or app_project in app_projects
fail_msg: "Requested app_project={{ app_project }} does not exist in app_projects."
- name: Add each project/env host (by static IP) to dynamic inventory
ansible.builtin.add_host:
name: "{{ app_projects[item.0].envs[item.1].name | default(item.0 ~ '-' ~ item.1) }}"
groups:
- "app_all"
- "app_{{ item.0 }}_all"
- "app_{{ item.0 }}_{{ item.1 }}"
ansible_host: "{{ (app_projects[item.0].envs[item.1].ip | string).split('/')[0] }}"
ansible_user: "{{ app_bootstrap_user | default(app_bootstrap_user_default) }}"
ansible_password: >-
{{
vault_lxc_root_password
if ((bootstrap_with_root_password | default(bootstrap_with_root_password_default) | bool) and (vault_lxc_root_password | default('') | length) > 0)
else omit
}}
app_project: "{{ item.0 }}"
app_env: "{{ item.1 }}"
loop: "{{ selected_projects | product(selected_envs) | list }}"
when:
- app_projects[item.0] is defined
- app_projects[item.0].envs[item.1] is defined
- (app_projects[item.0].envs[item.1].ip | default('')) | length > 0
- name: Configure app guests (base OS + app setup)
hosts: >-
{{
('app_' ~ app_project ~ '_all')
if (app_project is defined and app_project | length > 0)
else 'app_all'
}}
become: true
gather_facts: true
tags: ['app', 'configure']
tasks:
- name: Build project/env effective variables
ansible.builtin.set_fact:
project_def: "{{ app_projects[app_project] }}"
env_def: "{{ app_projects[app_project].envs[app_env] }}"
when: app_project is defined and app_env is defined
- name: Configure base OS
ansible.builtin.include_role:
name: base_os
vars:
base_os_backend_port: "{{ (project_def.backend_port | default(app_backend_port)) if project_def is defined else app_backend_port }}"
base_os_frontend_port: "{{ (project_def.frontend_port | default(app_frontend_port)) if project_def is defined else app_frontend_port }}"
base_os_enable_backend: "{{ project_def.components.backend | default(true) }}"
base_os_enable_frontend: "{{ project_def.components.frontend | default(true) }}"
base_os_user: "{{ project_def.os_user | default(appuser_name) }}"
base_os_user_ssh_public_key: "{{ project_def.os_user_ssh_public_key | default(appuser_ssh_public_key | default('')) }}"
# Only override when explicitly provided (avoids self-referential recursion)
base_os_packages: "{{ project_def.base_os_packages if (project_def is defined and project_def.base_os_packages is defined) else omit }}"
- name: Configure POTE (python/venv + cron)
ansible.builtin.include_role:
name: pote
vars:
pote_git_repo: "{{ project_def.repo_url }}"
pote_git_branch: "{{ env_def.branch }}"
pote_user: "{{ project_def.os_user | default('poteapp') }}"
pote_group: "{{ project_def.os_user | default('poteapp') }}"
pote_app_dir: "{{ project_def.repo_dest | default('/home/' ~ (project_def.os_user | default('poteapp')) ~ '/pote') }}"
pote_env: "{{ app_env }}"
pote_db_host: "{{ env_def.pote_db_host | default(project_def.pote_db_host | default('localhost')) }}"
pote_db_name: "{{ env_def.pote_db_name | default(project_def.pote_db_name | default('potedb')) }}"
pote_db_user: "{{ env_def.pote_db_user | default(project_def.pote_db_user | default('poteuser')) }}"
pote_smtp_host: "{{ env_def.pote_smtp_host | default(project_def.pote_smtp_host | default('mail.levkin.ca')) }}"
pote_smtp_port: "{{ env_def.pote_smtp_port | default(project_def.pote_smtp_port | default(587)) }}"
pote_smtp_user: "{{ env_def.pote_smtp_user | default(project_def.pote_smtp_user | default('')) }}"
pote_from_email: "{{ env_def.pote_from_email | default(project_def.pote_from_email | default('')) }}"
pote_report_recipients: "{{ env_def.pote_report_recipients | default(project_def.pote_report_recipients | default('')) }}"
when: app_project == 'pote'
- name: Configure app layout + deploy + systemd
ansible.builtin.include_role:
name: app_setup
vars:
app_repo_url: "{{ project_def.repo_url }}"
app_repo_dest: "{{ project_def.repo_dest | default('/srv/app') }}"
app_repo_branch: "{{ env_def.branch }}"
# app_env is already set per-host via add_host (dev/qa/prod)
app_owner: "{{ project_def.os_user | default(appuser_name) }}"
app_group: "{{ project_def.os_user | default(appuser_name) }}"
app_backend_dir: "{{ project_def.backend_dir | default(project_def.repo_dest | default(app_repo_dest)) }}"
app_frontend_dir: "{{ project_def.frontend_dir | default(project_def.repo_dest | default(app_repo_dest) + '/frontend') }}"
# Use different variable names to avoid self-referential recursion
app_backend_port: "{{ project_def.backend_port | default(3000) }}"
app_frontend_port: "{{ project_def.frontend_port | default(3000) }}"
app_enable_backend: "{{ project_def.components.backend | default(true) }}"
app_enable_frontend: "{{ project_def.components.frontend | default(true) }}"
app_backend_install_cmd: "{{ project_def.deploy.backend_install_cmd | default('npm ci') }}"
app_backend_build_cmd: "{{ project_def.deploy.backend_build_cmd | default('npm run build') }}"
app_backend_migrate_cmd: "{{ env_def.backend_migrate_cmd | default(project_def.deploy.backend_migrate_cmd | default('npm run migrate')) }}"
app_backend_seed_cmd: "{{ env_def.backend_seed_cmd | default(project_def.deploy.backend_seed_cmd | default('')) }}"
app_backend_start_cmd: "{{ project_def.deploy.backend_start_cmd | default('npm run start') }}"
app_frontend_install_cmd: "{{ project_def.deploy.frontend_install_cmd | default('npm ci') }}"
app_frontend_build_cmd: "{{ project_def.deploy.frontend_build_cmd | default('npm run build') }}"
app_frontend_start_cmd: "{{ project_def.deploy.frontend_start_cmd | default('npm start') }}"
app_env_vars: "{{ env_def.env_vars | default({}) }}"
when: app_project != 'pote'