--- - name: Deploy Infrastructure Stacks on Production Server hosts: production become: no gather_facts: yes vars: # All deployment variables are now defined in group_vars/production.yml # Variables can be overridden via -e flag if needed vault_file: "{{ playbook_dir }}/../secrets/production.vault.yml" pre_tasks: - name: Verify vault file exists ansible.builtin.stat: path: "{{ vault_file }}" register: vault_stat delegate_to: localhost become: no - name: Load encrypted secrets from vault ansible.builtin.include_vars: file: "{{ vault_file }}" when: vault_stat.stat.exists no_log: yes ignore_errors: yes delegate_to: localhost become: no - name: Verify vault secrets were loaded ansible.builtin.debug: msg: | Vault secrets loaded: - vault_db_password: {{ 'SET (length: ' + (vault_db_password | default('') | string | length | string) + ')' if (vault_db_password | default('') | string | trim) != '' else 'NOT SET or EMPTY' }} - vault_redis_password: {{ 'SET' if (vault_redis_password | default('') | string | trim) != '' else 'NOT SET' }} - vault_app_key: {{ 'SET' if (vault_app_key | default('') | string | trim) != '' else 'NOT SET' }} - vault_docker_registry_password: {{ 'SET (length: ' + (vault_docker_registry_password | default('') | string | length | string) + ')' if (vault_docker_registry_password | default('') | string | trim) != '' else 'NOT SET or EMPTY' }} when: vault_stat.stat.exists no_log: yes - name: Warn if vault file is missing ansible.builtin.debug: msg: "WARNING: Vault file not found at {{ vault_file }}. Some roles may fail if they require vault secrets." when: not vault_stat.stat.exists tasks: - name: Debug - Show variables debug: msg: - "stacks_base_path: {{ stacks_base_path | default('NOT SET') }}" - "deploy_user_home: {{ deploy_user_home | default('NOT SET') }}" when: true # Debugging enabled - name: Check if deployment stacks directory exists stat: path: "{{ stacks_base_path }}" register: stacks_dir - name: Create deployment stacks directory if it doesn't exist file: path: "{{ stacks_base_path }}" state: directory mode: '0755' owner: "{{ ansible_user }}" group: "{{ ansible_user }}" when: not stacks_dir.stat.exists - name: Ensure rsync is installed (required for synchronize) ansible.builtin.apt: name: rsync state: present update_cache: no become: yes - name: Sync infrastructure stacks to server synchronize: src: "{{ playbook_dir }}/../../stacks/" dest: "{{ stacks_base_path }}/" delete: no recursive: yes rsync_opts: - "--chmod=D755,F644" - "--exclude=.git" - "--exclude=*.log" - "--exclude=data/" - "--exclude=volumes/" - "--exclude=acme.json" - "--exclude=*.key" - "--exclude=*.pem" - name: Ensure executable permissions on PostgreSQL backup scripts file: path: "{{ item }}" mode: '0755' loop: - "{{ stacks_base_path }}/postgresql-production/scripts/backup-entrypoint.sh" - "{{ stacks_base_path }}/postgresql-production/scripts/backup.sh" - "{{ stacks_base_path }}/postgresql-production/scripts/restore.sh" - "{{ stacks_base_path }}/postgresql-staging/scripts/backup-entrypoint.sh" - "{{ stacks_base_path }}/postgresql-staging/scripts/backup.sh" - "{{ stacks_base_path }}/postgresql-staging/scripts/restore.sh" ignore_errors: yes - name: Ensure system packages are up to date include_role: name: system when: system_update_packages | bool # Create external networks required by all stacks - name: Create traefik-public network community.docker.docker_network: name: traefik-public driver: bridge state: present - name: Create app-internal network community.docker.docker_network: name: app-internal driver: bridge state: present # 1. Deploy Traefik (Reverse Proxy & SSL) - name: Deploy Traefik stack import_role: name: traefik # 2. Deploy PostgreSQL Production (Database) - name: Deploy PostgreSQL Production stack import_role: name: postgresql-production # 3. Deploy Redis (Cache & Session Store) - name: Deploy Redis stack import_role: name: redis # 4. Deploy Docker Registry (Private Registry) - name: Deploy Docker Registry stack import_role: name: registry # 5. Deploy MinIO (Object Storage) - name: Deploy MinIO stack import_role: name: minio # 6. Deploy Gitea (CRITICAL - Git Server + MySQL) - name: Deploy Gitea stack import_role: name: gitea # 7. Deploy Monitoring (Portainer + Grafana + Prometheus) - name: Deploy Monitoring stack import_role: name: monitoring # 8. Deploy Production Stack - name: Deploy Production Stack import_role: name: application vars: application_stack_src: "{{ playbook_dir | default(role_path + '/..') }}/../../stacks/production" application_stack_dest: "{{ app_stack_path | default(stacks_base_path + '/production') }}" application_compose_suffix: "production.yml" application_service_name: "php" application_env_template: "{{ role_path }}/../../templates/application.env.j2" app_env: "production" # Explicitly pass vault variables to the role vault_docker_registry_password: "{{ vault_docker_registry_password | default('') }}" app_domain: "michaelschiemer.de" app_debug: "false" db_name: "{{ db_name_default }}" db_host: "{{ db_host_default }}" # Verification - name: List all running containers command: > docker ps --format 'table {{ "{{" }}.Names{{ "}}" }}\t{{ "{{" }}.Status{{ "}}" }}\t{{ "{{" }}.Ports{{ "}}" }}' register: docker_ps_output - name: Display running containers debug: msg: "{{ docker_ps_output.stdout_lines }}" - name: Verify Gitea accessibility via HTTPS uri: url: "https://{{ gitea_domain }}" method: GET validate_certs: no status_code: 200 timeout: 10 register: gitea_http_check ignore_errors: yes - name: Display Gitea accessibility status debug: msg: "Gitea HTTPS check: {{ 'SUCCESS' if gitea_http_check.status == 200 else 'FAILED - Status: ' + (gitea_http_check.status|string) }}" # 8. Deploy Production Stack - name: Deploy Production Stack import_role: name: application - name: Display application health status debug: msg: "Application health: {{ application_health_output if application_health_output != '' else 'All services healthy or starting' }}" - name: Display migration result debug: msg: | Migration Result: {{ application_migration_stdout if application_migration_stdout != '' else 'Migration may have failed - check logs with: docker compose -f ' + application_stack_dest + '/docker-compose.yml logs app' }} when: application_stack_changed and application_run_migrations - name: Display application accessibility status debug: msg: >- Application health check: {{ 'SUCCESS (HTTP ' + (application_healthcheck_status | string) + ')' if application_healthcheck_status == 200 else 'FAILED or not ready yet (HTTP ' + (application_healthcheck_status | string) + ')' }} when: application_stack_changed and application_healthcheck_url | length > 0 - name: Summary debug: msg: - "=== Infrastructure Deployment Complete ===" - "Traefik: {{ 'Deployed' if traefik_stack_changed is defined and traefik_stack_changed else 'Already running' }}" - "PostgreSQL: {{ 'Deployed' if postgresql_stack_changed is defined and postgresql_stack_changed else 'Already running' }}" - "Redis: {{ 'Deployed' if redis_stack_changed is defined and redis_stack_changed else 'Already running' }}" - "Docker Registry: {{ 'Deployed' if registry_stack_changed is defined and registry_stack_changed else 'Already running' }}" - "MinIO: {{ 'Deployed' if minio_stack_changed is defined and minio_stack_changed else 'Already running' }}" - "Gitea: {{ 'Deployed' if gitea_stack_changed is defined and gitea_stack_changed else 'Already running' }}" - "Monitoring: {{ 'Deployed' if monitoring_stack_changed is defined and monitoring_stack_changed else 'Already running' }}" - "Application: {{ 'Deployed' if application_stack_changed is defined and application_stack_changed else 'Already running' }}" - "" - "Next Steps:" - "1. Access Gitea at: https://{{ gitea_domain }}" - "2. Complete Gitea setup wizard if first-time deployment" - "3. Navigate to Admin > Actions > Runners to get registration token" - "4. Continue with Phase 1 - Gitea Runner Setup" - "5. Access Application at: https://{{ app_domain }}"