ansible/playbooks/app/configure_app.yml
ilia c3e6caf9e8
All checks were successful
CI / skip-ci-check (push) Successful in 1m18s
CI / lint-and-test (push) Successful in 1m23s
CI / ansible-validation (push) Successful in 3m2s
CI / secret-scanning (push) Successful in 1m19s
CI / dependency-scan (push) Successful in 1m24s
CI / sast-scan (push) Successful in 2m32s
CI / license-check (push) Successful in 1m23s
CI / vault-check (push) Successful in 2m22s
CI / playbook-test (push) Successful in 2m25s
CI / container-scan (push) Successful in 1m51s
CI / sonar-analysis (push) Successful in 2m32s
CI / workflow-summary (push) Successful in 1m17s
refactor-servers-workstations-shell-monitoring (#4)
### Summary

This PR refactors the playbook layout to reduce duplication and make host intent clearer (servers vs workstations), splits monitoring by host type, and restores full Zsh setup for developers while keeping servers aliases-only.

### Key changes

- **New playbooks**
  - `playbooks/servers.yml`: baseline for server-class hosts (no desktop apps)
  - `playbooks/workstations.yml`: baseline for dev/desktop/local + **desktop apps only on `desktop` group**

- **Monitoring split**
  - `roles/monitoring_server`: server monitoring + intrusion prevention (includes `fail2ban`, sysstat)
  - `roles/monitoring_desktop`: desktop-oriented monitoring tooling
  - Updated playbooks to use the correct monitoring role per host type

- **Shell role: server-safe + developer-friendly**
  - `roles/shell` now supports two modes:
    - `shell_mode: minimal` (default): aliases-only, does not overwrite `.zshrc`
    - `shell_mode: full`: installs Oh My Zsh + Powerlevel10k + plugins and deploys a managed `.zshrc`
  - `playbooks/development.yml` and `playbooks/workstations.yml` use `shell_mode: full`
  - `playbooks/servers.yml` remains **aliases-only**

- **Applications**
  - Applications role runs only on `desktop` group (via `workstations.yml`)
  - Removed Brave installs/repo management
  - Added **CopyQ** to desktop apps (`applications_desktop_packages`)

- **Docs + architecture**
  - Added canonical doc tree under `project-docs/` (overview/architecture/standards/workflow/decisions)
  - Consolidated architecture docs: `docs/reference/architecture.md` is now a pointer to `project-docs/architecture.md`
  - Fixed broken doc links by adding the missing referenced pages under `docs/`

### Behavior changes (important)

- Desktop GUI apps install **only** on the `desktop` inventory group (not on servers, not on dev VMs unless they are in `desktop`).
- Dev/workstation Zsh is now provisioned in **full mode** (managed `.zshrc` + p10k).

### How to test (local CI parity)

```bash
make test
npm test
```

Optional dry runs (interactive sudo may be required):

```bash
make check
make check-local
```

### Rollout guidance

- Apply to a single host first:
  - Workstations: `make workstations HOST=<devhost>`
  - Servers: `make servers HOST=<serverhost>`
- Then expand to group runs.

Reviewed-on: #4
2026-01-01 22:11:24 -05:00

136 lines
6.8 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]
}}
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
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) }}"
# Use different variable names to avoid self-referential recursion
app_backend_port: "{{ project_def.backend_port | default(3001) }}"
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_migrate_cmd: "{{ project_def.deploy.backend_migrate_cmd | default('npm run migrate') }}"
app_backend_start_cmd: "{{ project_def.deploy.backend_start_cmd | default('npm 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'