ansible/playbooks/app/configure_app.yml
ilia c7a300b922
Some checks failed
CI / lint-and-test (pull_request) Successful in 1m21s
CI / ansible-validation (pull_request) Successful in 9m3s
CI / secret-scanning (pull_request) Successful in 3m19s
CI / dependency-scan (pull_request) Successful in 7m13s
CI / sast-scan (pull_request) Successful in 6m38s
CI / license-check (pull_request) Successful in 1m16s
CI / vault-check (pull_request) Failing after 6m40s
CI / playbook-test (pull_request) Successful in 9m28s
CI / container-scan (pull_request) Successful in 7m59s
CI / sonar-analysis (pull_request) Failing after 1m11s
CI / workflow-summary (pull_request) Successful in 1m11s
Add POTE app project support and improve IP conflict detection
- Add roles/pote: Python/venv deployment role with PostgreSQL, cron jobs
- Add playbooks/app/: Proxmox app stack provisioning and configuration
- Add roles/app_setup: Generic app deployment role (Node.js/systemd)
- Add roles/base_os: Base OS hardening role
- Enhance roles/proxmox_vm: Split LXC/KVM tasks, improve error handling
- Add IP uniqueness validation: Preflight check for duplicate IPs within projects
- Add Proxmox-side IP conflict detection: Check existing LXC net0 configs
- Update inventories/production/group_vars/all/main.yml: Add pote project config
- Add vault.example.yml: Template for POTE secrets (git key, DB, SMTP)
- Update .gitignore: Exclude deploy keys, backup files, and other secrets
- Update documentation: README, role docs, execution flow guides

Security:
- All secrets stored in encrypted vault.yml (never committed in plaintext)
- Deploy keys excluded via .gitignore
- IP conflict guardrails prevent accidental duplicate IP assignments
2025-12-28 20:54:50 -05:00

135 lines
6.7 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
vars:
selected_projects: >-
{{
(app_projects | dict2items | map(attribute='key') | list)
if (app_project is not defined or app_project | length == 0)
else [app_project]
}}
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(['dev', 'qa', 'prod']) | 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
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_port: "{{ project_def.backend_port | default(app_backend_port) }}"
app_frontend_port: "{{ project_def.frontend_port | default(app_frontend_port) }}"
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(app_backend_install_cmd) }}"
app_backend_migrate_cmd: "{{ project_def.deploy.backend_migrate_cmd | default(app_backend_migrate_cmd) }}"
app_backend_start_cmd: "{{ project_def.deploy.backend_start_cmd | default(app_backend_start_cmd) }}"
app_frontend_install_cmd: "{{ project_def.deploy.frontend_install_cmd | default(app_frontend_install_cmd) }}"
app_frontend_build_cmd: "{{ project_def.deploy.frontend_build_cmd | default(app_frontend_build_cmd) }}"
app_frontend_start_cmd: "{{ project_def.deploy.frontend_start_cmd | default(app_frontend_start_cmd) }}"
app_env_vars: "{{ env_def.env_vars | default({}) }}"
when: app_project != 'pote'