--- # Installs one app (one loop iteration of `app` from playbooks/proxmox.yml) # natively into a Proxmox LXC: merge portable + Proxmox config, fetch secrets # from Vault, provision its database if it declares one, run the app's own # install steps, then manage its systemd unit. # # The Proxmox counterpart to `compose_stack`. Everything up to "Run # app-specific install steps" is deliberately the same shape as that role — # same vars merge, same Vault lookup, same DB provisioning — so config lives # in one place regardless of which platform an app lands on. What differs is # the deployment primitive: a systemd service built from packages/binaries # rather than `docker compose up -d`. # # UNPROVEN: `src/shared/postgres/` and `src/forgejo/` both ship an # `ansible/proxmox/install.yml` now, but neither has been run end to end # against a real LXC. The structure mirrors proven code; the details are not # yet exercised. - name: Set app facts ansible.builtin.set_fact: app_local_dir: "{{ repo_root }}/src/{{ app.src }}" - name: Load portable app variables ansible.builtin.include_vars: file: "{{ app_local_dir }}/common/vars.yml" name: app_common_vars - name: Load Proxmox-specific app variables ansible.builtin.include_vars: file: "{{ app_local_dir }}/ansible/proxmox/vars.yml" name: app_platform_vars - name: Merge app configuration ansible.builtin.set_fact: # Portable values first, Proxmox-specific overrides on top. Available to # the app's install.yml, alongside `vault_secrets` below. app_config: >- {{ (app_common_vars.env_defaults | default({})) | combine(app_platform_vars.env_defaults | default({})) }} - name: Look up app secrets from Vault ansible.builtin.set_fact: vault_secrets: >- {{ lookup('community.hashi_vault.vault_kv2_get', app.vault_path, engine_mount_point=vault_kv_mount, url=vault_addr, auth_method=vault_auth_method, role_id=vault_role_id | default(omit), secret_id=vault_secret_id | default(omit)).secret }} no_log: true - name: Provision application database when: app.db is defined no_log: true block: - name: Look up Postgres superuser credentials from Vault ansible.builtin.set_fact: pg_admin_secrets: >- {{ lookup('community.hashi_vault.vault_kv2_get', app.db.admin_vault_path, engine_mount_point=vault_kv_mount, url=vault_addr, auth_method=vault_auth_method, role_id=vault_role_id | default(omit), secret_id=vault_secret_id | default(omit)).secret }} - name: Ensure application database role exists community.postgresql.postgresql_user: name: "{{ app.db.user }}" password: "{{ vault_secrets[app.db.password_vault_key] }}" login_host: "{{ app.db.provision_host }}" login_port: "{{ app.db.provision_port }}" login_user: "{{ pg_admin_secrets.POSTGRES_SUPERUSER }}" login_password: "{{ pg_admin_secrets.POSTGRES_SUPERUSER_PASSWORD }}" state: present delegate_to: localhost become: false # Role first, then the database with `owner:` — the shape roles/k3s_app # already uses, and not the `postgresql_user` + `priv: ALL` this role was # written with. Two independent reasons it had to change: # # 1. `priv` was deprecated in community.postgresql 3.x and REMOVED in # 4.0.0, so the old call is a hard "Unsupported parameters" failure # on any current collection. requirements.yml asks for >=3.0.0, # which installs 4.x. # 2. On this instance it would have been wrong even if it still worked. # `priv: ALL` grants database-level privileges (CONNECT, CREATE, # TEMPORARY); since Postgres 15 the `public` schema no longer grants # CREATE to PUBLIC, so a role holding all of those still cannot # create a table. The Proxmox cluster is 17 (the Unraid one is 13, # which is why the same code never failed that way there). Making # the app role own the database covers it on both: on 15+ `public` # is owned by `pg_database_owner`, which resolves to whoever owns # the database. # # Ownership also makes a pg_dump restore land correctly, for the reason # spelled out at the same tasks in roles/k3s_app. - name: Ensure application database exists community.postgresql.postgresql_db: name: "{{ app.db.name }}" owner: "{{ app.db.user }}" login_host: "{{ app.db.provision_host }}" login_port: "{{ app.db.provision_port }}" login_user: "{{ pg_admin_secrets.POSTGRES_SUPERUSER }}" login_password: "{{ pg_admin_secrets.POSTGRES_SUPERUSER_PASSWORD }}" state: present delegate_to: localhost become: false # Everything genuinely app-specific — fetching the binary or package, creating # the service user, laying out data directories, rendering the app's own # config file from `app_config` + `vault_secrets`, and installing a systemd # unit — lives with the app, not here. See src//ansible/proxmox/. - name: Run app-specific install steps ansible.builtin.include_tasks: "{{ app_local_dir }}/ansible/proxmox/install.yml" - name: Enable and start the service ansible.builtin.systemd_service: name: "{{ app.service_name | default(app.name) }}" enabled: true state: started daemon_reload: true