--- # playbooks/docker/deploy_authentik.yml # # Purpose: # Deploy Authentik as a Swarm stack pinned to swarm-manager-1 with persistent # bind mounts under /mnt/homelab/apps/authentik. # # Data protection: # This playbook validates all required Authentik data paths before deploy. # If paths are missing, deployment fails early to avoid creating empty data # roots that could mask or diverge from an existing Authentik installation. # # Usage: # ansible-playbook -i inventory/hosts.ini playbooks/docker/deploy_authentik.yml # # ansible-playbook -i inventory/hosts.ini playbooks/docker/deploy_authentik.yml \ # -e "stack_validate_only=true" # # ansible-playbook -i inventory/hosts.ini playbooks/docker/deploy_authentik.yml \ # -e "authentik_deploy_state=absent" - name: Deploy Authentik Swarm stack hosts: swarm_managers become: true gather_facts: false vars_files: - ../../group_vars/all.yml vars: authentik_deploy_target: "{{ edge_routing.swarm.stack_deploy_target | default(groups['swarm_managers'][0]) }}" tasks: # -------------------------------------------------- # STEP 0: Assert required secrets are present # -------------------------------------------------- - name: Assert vault_authentik_secret_key is defined and non-empty ansible.builtin.assert: that: - vault_authentik_secret_key is defined - vault_authentik_secret_key | trim | length > 0 fail_msg: >- vault_authentik_secret_key is not defined or is empty. Encrypt and store it in group_vars/vault/all.yml with: ansible-vault encrypt_string 'your-random-secret' --name 'vault_authentik_secret_key' when: inventory_hostname == authentik_deploy_target - name: Assert vault_authentik_postgres_password is defined and non-empty ansible.builtin.assert: that: - vault_authentik_postgres_password is defined - vault_authentik_postgres_password | trim | length > 0 fail_msg: >- vault_authentik_postgres_password is not defined or is empty. Encrypt and store it in group_vars/vault/all.yml with: ansible-vault encrypt_string 'your-db-password' --name 'vault_authentik_postgres_password' when: inventory_hostname == authentik_deploy_target - name: Assert Authentik secrets are not placeholders ansible.builtin.assert: that: - vault_authentik_secret_key not in ['change-me', 'changeme', 'your-random-secret'] - vault_authentik_postgres_password not in ['change-me', 'changeme', 'your-db-password'] fail_msg: "Authentik secrets still appear to be placeholders. Set real vault values before deploy." when: inventory_hostname == authentik_deploy_target # -------------------------------------------------- # STEP 1: Assert Swarm manager is active # -------------------------------------------------- - name: Collect Swarm manager state ansible.builtin.command: > docker info --format '{{ "{{" }}.Swarm.LocalNodeState{{ "}}" }}|{{ "{{" }}.Swarm.ControlAvailable{{ "}}" }}' register: _swarm_info changed_when: false when: inventory_hostname == authentik_deploy_target - name: Assert target is an active Swarm manager ansible.builtin.assert: that: - _swarm_info.stdout is search('active') - _swarm_info.stdout is search('true') fail_msg: >- {{ inventory_hostname }} must be an active Swarm manager. Current state: {{ _swarm_info.stdout | default('unknown') }} when: inventory_hostname == authentik_deploy_target # -------------------------------------------------- # STEP 2: Validate pre-existing persistent data paths # -------------------------------------------------- - name: Stat required Authentik bind-mount paths ansible.builtin.stat: path: "{{ item }}" register: _authentik_path_stat loop: - /mnt/homelab/apps/authentik - /mnt/homelab/apps/authentik/data - /mnt/homelab/apps/authentik/data/database - /mnt/homelab/apps/authentik/data/redis - /mnt/homelab/apps/authentik/data/media - /mnt/homelab/apps/authentik/data/config - /mnt/homelab/apps/authentik/data/blueprints when: inventory_hostname == authentik_deploy_target - name: Assert required Authentik paths exist before deploy ansible.builtin.assert: that: - item.stat.exists - item.stat.isdir fail_msg: >- Required Authentik path '{{ item.item }}' is missing on {{ inventory_hostname }}. Create/restore this directory first to avoid accidental fresh bootstrap over existing data. loop: "{{ _authentik_path_stat.results }}" when: inventory_hostname == authentik_deploy_target # -------------------------------------------------- # STEP 3: Deploy Authentik stack # -------------------------------------------------- - name: Deploy Authentik stack ansible.builtin.include_role: name: swarm_stack_deploy vars: stack_name: "authentik" stack_compose_src: "{{ playbook_dir }}/../../templates/stacks/authentik.stack.yml" # authentik_placement_node resolved from group_vars (swarm-manager-2) # Use service-specific state var to avoid self-reference recursion. stack_state: "{{ authentik_deploy_state | default('present') }}" stack_required_external_networks: - proxy-net stack_required_directories: - /mnt/homelab/apps/authentik - /mnt/homelab/apps/authentik/data - /mnt/homelab/apps/authentik/data/database - /mnt/homelab/apps/authentik/data/redis - /mnt/homelab/apps/authentik/data/media - /mnt/homelab/apps/authentik/data/config - /mnt/homelab/apps/authentik/data/blueprints when: inventory_hostname == authentik_deploy_target # -------------------------------------------------- # STEP 4: Wait for service convergence # -------------------------------------------------- - name: Wait for Authentik server service to converge ansible.builtin.command: > docker service ls --filter name=authentik_authentik-server --format '{{ "{{" }}.Replicas{{ "}}" }}' register: _authentik_server_replicas retries: 18 delay: 10 until: _authentik_server_replicas.stdout is search('1/1') changed_when: false when: - inventory_hostname == authentik_deploy_target - authentik_deploy_state | default('present') == 'present' - not ansible_check_mode tags: [verify] - name: Wait for Authentik worker service to converge ansible.builtin.command: > docker service ls --filter name=authentik_authentik-worker --format '{{ "{{" }}.Replicas{{ "}}" }}' register: _authentik_worker_replicas retries: 18 delay: 10 until: _authentik_worker_replicas.stdout is search('1/1') changed_when: false when: - inventory_hostname == authentik_deploy_target - authentik_deploy_state | default('present') == 'present' - not ansible_check_mode tags: [verify] - name: Report Authentik deployment result ansible.builtin.debug: msg: - "================================================" - "Authentik deployment complete." - "================================================" - "Stack : authentik" - "Manager : {{ inventory_hostname }} ({{ ansible_host | default('') }})" - "URL : https://sso.castaldifamily.com" - "Data root : /mnt/homelab/apps/authentik" - "Services : authentik-postgres, authentik-redis, authentik-server, authentik-worker" - "================================================" when: inventory_hostname == authentik_deploy_target tags: [always]