- 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
135 lines
6.7 KiB
YAML
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'
|
|
|
|
|