diff --git a/Makefile b/Makefile index 51696353..197b432a 100644 --- a/Makefile +++ b/Makefile @@ -215,6 +215,10 @@ logs-production: ## Show production logs @echo "📋 Showing production logs..." @cd deployment && make logs-prod-php +logs-staging: ## Show staging-app container logs via SSH + @echo "📋 Showing staging-app logs..." + @ssh -i ~/.ssh/production deploy@94.16.110.151 "cd ~/deployment/stacks/staging && docker compose logs -f staging-app" + # SSL Certificate Management (PHP Framework Integration) ssl-init: ## Initialize Let's Encrypt certificates @echo "🔒 Initializing SSL certificates..." @@ -249,4 +253,4 @@ ssl-backup: ## Backup Let's Encrypt certificates push-staging: ## Pusht den aktuellen Stand nach origin/staging git push origin HEAD:staging -.PHONY: up down build restart logs ps phpinfo deploy setup clean clean-coverage status fix-ssh-perms setup-ssh test test-coverage test-coverage-html test-unit test-framework test-domain test-watch test-parallel test-profile test-filter security-check security-audit-json security-check-prod update-production restart-production deploy-production-quick status-production logs-production ssl-init ssl-init-staging ssl-test ssl-renew ssl-status ssl-backup push-staging +.PHONY: up down build restart logs ps phpinfo deploy setup clean clean-coverage status fix-ssh-perms setup-ssh test test-coverage test-coverage-html test-unit test-framework test-domain test-watch test-parallel test-profile test-filter security-check security-audit-json security-check-prod update-production restart-production deploy-production-quick status-production logs-production logs-staging ssl-init ssl-init-staging ssl-test ssl-renew ssl-status ssl-backup push-staging diff --git a/deployment/ansible/group_vars/production.yml b/deployment/ansible/group_vars/production.yml index 199ba9f3..2e420256 100644 --- a/deployment/ansible/group_vars/production.yml +++ b/deployment/ansible/group_vars/production.yml @@ -2,6 +2,15 @@ # Production Deployment - Centralized Variables # These variables are used across all playbooks +# System Maintenance +system_update_packages: true +system_apt_upgrade: dist +system_enable_unattended_upgrades: true +system_enable_unattended_reboot: false +system_unattended_reboot_time: "02:00" +system_enable_unattended_timer: true +system_enable_docker_prune: false + # Deployment Paths deploy_user_home: "/home/deploy" stacks_base_path: "{{ deploy_user_home }}/deployment/stacks" diff --git a/deployment/ansible/playbooks/deploy-update.yml b/deployment/ansible/playbooks/deploy-update.yml index d69a1968..001aef58 100644 --- a/deployment/ansible/playbooks/deploy-update.yml +++ b/deployment/ansible/playbooks/deploy-update.yml @@ -25,6 +25,11 @@ docker_registry_username: "{{ docker_registry_username | default(vault_docker_registry_username | default(docker_registry_username_default)) }}" docker_registry_password: "{{ docker_registry_password | default(vault_docker_registry_password | default(docker_registry_password_default)) }}" + - name: Ensure system packages are up to date + include_role: + name: system + when: system_update_packages | bool + - name: Verify Docker is running systemd: name: docker diff --git a/deployment/ansible/playbooks/setup-infrastructure.yml b/deployment/ansible/playbooks/setup-infrastructure.yml index 24434df8..46962bda 100644 --- a/deployment/ansible/playbooks/setup-infrastructure.yml +++ b/deployment/ansible/playbooks/setup-infrastructure.yml @@ -26,6 +26,11 @@ msg: "Deployment stacks directory not found at {{ stacks_base_path }}" when: not stacks_dir.stat.exists + - 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: diff --git a/deployment/ansible/playbooks/system-maintenance.yml b/deployment/ansible/playbooks/system-maintenance.yml new file mode 100644 index 00000000..e7445443 --- /dev/null +++ b/deployment/ansible/playbooks/system-maintenance.yml @@ -0,0 +1,11 @@ +--- +- name: Apply system maintenance on production hosts + hosts: production + gather_facts: yes + become: yes + + tasks: + - name: Run system maintenance role + include_role: + name: system + when: system_update_packages | bool diff --git a/deployment/ansible/roles/application/tasks/sync.yml b/deployment/ansible/roles/application/tasks/sync.yml index ea309f29..e0b43564 100644 --- a/deployment/ansible/roles/application/tasks/sync.yml +++ b/deployment/ansible/roles/application/tasks/sync.yml @@ -79,6 +79,14 @@ mode: '0644' when: application_nginx_src.stat.exists +- name: Expose secrets for template rendering + set_fact: + db_password: "{{ application_db_password }}" + redis_password: "{{ application_redis_password }}" + db_username: "{{ db_user | default(db_user_default) }}" + db_name: "{{ db_name | default(db_name_default) }}" + no_log: yes + - name: Render application environment file template: src: "{{ application_env_template }}" @@ -86,9 +94,3 @@ owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0600' - vars: - db_password: "{{ application_db_password }}" - db_user: "{{ db_user | default(db_user_default) }}" - db_name: "{{ db_name | default(db_name_default) }}" - redis_password: "{{ application_redis_password }}" - app_domain: "{{ app_domain }}" diff --git a/deployment/ansible/roles/minio/defaults/main.yml b/deployment/ansible/roles/minio/defaults/main.yml index 6109abb9..030c3240 100644 --- a/deployment/ansible/roles/minio/defaults/main.yml +++ b/deployment/ansible/roles/minio/defaults/main.yml @@ -4,3 +4,4 @@ minio_wait_timeout: "{{ wait_timeout | default(60) }}" minio_wait_interval: 5 minio_env_template: "{{ role_path }}/../../templates/minio.env.j2" minio_vault_file: "{{ role_path }}/../../secrets/production.vault.yml" +minio_healthcheck_enabled: false diff --git a/deployment/ansible/roles/minio/tasks/main.yml b/deployment/ansible/roles/minio/tasks/main.yml index fa9e7230..6e9bf2db 100644 --- a/deployment/ansible/roles/minio/tasks/main.yml +++ b/deployment/ansible/roles/minio/tasks/main.yml @@ -77,14 +77,18 @@ register: minio_health_check ignore_errors: yes changed_when: false - when: not ansible_check_mode + when: + - not ansible_check_mode + - minio_healthcheck_enabled | bool - name: Display MinIO status debug: msg: "MinIO health check: {{ 'SUCCESS' if minio_health_check.status == 200 else 'FAILED - Status: ' + (minio_health_check.status|string) }}" - when: not ansible_check_mode + when: + - not ansible_check_mode + - minio_healthcheck_enabled | bool - name: Record MinIO deployment facts set_fact: minio_stack_changed: "{{ minio_compose_result.changed | default(false) }}" - minio_health_status: "{{ minio_health_check.status | default('unknown') }}" + minio_health_status: "{{ minio_health_check.status | default('disabled' if not minio_healthcheck_enabled else 'unknown') }}" diff --git a/deployment/ansible/roles/registry/defaults/main.yml b/deployment/ansible/roles/registry/defaults/main.yml index 40f41608..c8d253e3 100644 --- a/deployment/ansible/roles/registry/defaults/main.yml +++ b/deployment/ansible/roles/registry/defaults/main.yml @@ -3,3 +3,5 @@ registry_stack_path: "{{ stacks_base_path }}/registry" registry_wait_timeout: "{{ wait_timeout | default(60) }}" registry_wait_interval: 5 registry_vault_file: "{{ role_path }}/../../secrets/production.vault.yml" +registry_healthcheck_enabled: true +registry_healthcheck_url: "http://127.0.0.1:5000/v2/_catalog" diff --git a/deployment/ansible/roles/registry/tasks/main.yml b/deployment/ansible/roles/registry/tasks/main.yml index 1552d895..da6d9350 100644 --- a/deployment/ansible/roles/registry/tasks/main.yml +++ b/deployment/ansible/roles/registry/tasks/main.yml @@ -93,7 +93,7 @@ - name: Verify Registry is accessible uri: - url: "http://127.0.0.1:5000/v2/_catalog" + url: "{{ registry_healthcheck_url }}" user: "{{ registry_username }}" password: "{{ registry_password }}" status_code: 200 @@ -102,14 +102,18 @@ ignore_errors: yes changed_when: false no_log: true - when: not ansible_check_mode + when: + - not ansible_check_mode + - registry_healthcheck_enabled | bool - name: Display Registry status debug: msg: "Registry accessibility: {{ 'SUCCESS' if registry_check.status == 200 else 'FAILED - may need manual check' }}" - when: not ansible_check_mode + when: + - not ansible_check_mode + - registry_healthcheck_enabled | bool - name: Record registry deployment facts set_fact: registry_stack_changed: "{{ registry_compose_result.changed | default(false) }}" - registry_access_status: "{{ registry_check.status | default('unknown') }}" + registry_access_status: "{{ registry_check.status | default('disabled' if not registry_healthcheck_enabled else 'unknown') }}" diff --git a/deployment/ansible/roles/system/defaults/main.yml b/deployment/ansible/roles/system/defaults/main.yml new file mode 100644 index 00000000..27f9355b --- /dev/null +++ b/deployment/ansible/roles/system/defaults/main.yml @@ -0,0 +1,9 @@ +--- +system_update_packages: true +system_apt_cache_valid_time: 3600 +system_apt_upgrade: dist +system_enable_unattended_upgrades: true +system_enable_unattended_reboot: false +system_unattended_reboot_time: "02:00" +system_enable_unattended_timer: true +system_enable_docker_prune: false diff --git a/deployment/ansible/roles/system/tasks/main.yml b/deployment/ansible/roles/system/tasks/main.yml new file mode 100644 index 00000000..641d3cb2 --- /dev/null +++ b/deployment/ansible/roles/system/tasks/main.yml @@ -0,0 +1,130 @@ +--- +- name: Refresh apt cache on Debian-based systems + ansible.builtin.apt: + update_cache: yes + cache_valid_time: "{{ system_apt_cache_valid_time }}" + become: yes + when: + - ansible_os_family == 'Debian' + - system_update_packages | bool + +- name: Upgrade packages on Debian-based systems + ansible.builtin.apt: + upgrade: "{{ system_apt_upgrade }}" + autoremove: yes + become: yes + when: + - ansible_os_family == 'Debian' + - system_update_packages | bool + +- name: Upgrade packages on RedHat-based systems + ansible.builtin.yum: + name: '*' + state: latest + become: yes + when: + - ansible_os_family == 'RedHat' + - system_update_packages | bool + +- name: Warn about unsupported package manager + ansible.builtin.debug: + msg: "System package updates are not implemented for {{ ansible_os_family }}" + changed_when: false + when: + - system_update_packages | bool + - ansible_os_family not in ['Debian', 'RedHat'] + +- name: Install unattended-upgrades packages + ansible.builtin.package: + name: + - unattended-upgrades + - apt-listchanges + state: present + become: yes + when: + - ansible_os_family == 'Debian' + - system_enable_unattended_upgrades | bool + +- name: Configure unattended upgrades periodic execution + ansible.builtin.copy: + dest: /etc/apt/apt.conf.d/20auto-upgrades + owner: root + group: root + mode: '0644' + content: | + APT::Periodic::Update-Package-Lists "1"; + APT::Periodic::Download-Upgradeable-Packages "1"; + APT::Periodic::AutocleanInterval "7"; + APT::Periodic::Unattended-Upgrade "1"; + become: yes + when: + - ansible_os_family == 'Debian' + - system_enable_unattended_upgrades | bool + +- name: Configure unattended upgrade reboot preference + ansible.builtin.lineinfile: + path: /etc/apt/apt.conf.d/50unattended-upgrades + regexp: '^//?\s*Unattended-Upgrade::Automatic-Reboot\s+' + line: 'Unattended-Upgrade::Automatic-Reboot "{{ system_enable_unattended_reboot | ternary("true", "false") }}";' + owner: root + group: root + mode: '0644' + create: yes + become: yes + when: + - ansible_os_family == 'Debian' + - system_enable_unattended_upgrades | bool + +- name: Configure unattended upgrade reboot time + ansible.builtin.lineinfile: + path: /etc/apt/apt.conf.d/50unattended-upgrades + regexp: '^//?\s*Unattended-Upgrade::Automatic-Reboot-Time\s+' + line: 'Unattended-Upgrade::Automatic-Reboot-Time "{{ system_unattended_reboot_time }}";' + owner: root + group: root + mode: '0644' + create: yes + become: yes + when: + - ansible_os_family == 'Debian' + - system_enable_unattended_upgrades | bool + - system_enable_unattended_reboot | bool + +- name: Disable unattended reboot time when automatic reboot is off + ansible.builtin.lineinfile: + path: /etc/apt/apt.conf.d/50unattended-upgrades + regexp: '^Unattended-Upgrade::Automatic-Reboot-Time\s+' + state: absent + owner: root + group: root + mode: '0644' + become: yes + when: + - ansible_os_family == 'Debian' + - system_enable_unattended_upgrades | bool + - not system_enable_unattended_reboot | bool + +- name: Ensure unattended upgrade timers are enabled + ansible.builtin.systemd: + name: "{{ item }}" + enabled: true + state: started + become: yes + loop: + - apt-daily.timer + - apt-daily-upgrade.timer + - unattended-upgrades.service + when: + - ansible_os_family == 'Debian' + - system_enable_unattended_upgrades | bool + - system_enable_unattended_timer | bool + +- name: Prune unused Docker data + community.docker.docker_prune: + containers: true + images: true + networks: true + volumes: false + builder_cache: true + become: yes + when: system_enable_docker_prune | bool diff --git a/deployment/ansible/templates/application.env.j2 b/deployment/ansible/templates/application.env.j2 index 81644d0f..47adf55f 100644 --- a/deployment/ansible/templates/application.env.j2 +++ b/deployment/ansible/templates/application.env.j2 @@ -16,6 +16,10 @@ APP_URL=https://{{ app_domain }} # Using PostgreSQL from postgres stack DB_HOST=postgres DB_PORT={{ db_port | default('5432') }} +DB_DATABASE={{ db_name | default(db_name_default) }} +DB_USERNAME={{ db_user | default(db_user_default) }} +DB_PASSWORD={{ db_password }} +# Legacy variables (kept for backward compatibility) DB_NAME={{ db_name | default(db_name_default) }} DB_USER={{ db_user | default(db_user_default) }} DB_PASS={{ db_password }} @@ -44,4 +48,4 @@ GIT_REPOSITORY_URL={{ git_repository_url | default('') }} GIT_BRANCH={{ git_branch | default('main') }} GIT_TOKEN={{ git_token | default('') }} GIT_USERNAME={{ git_username | default('') }} -GIT_PASSWORD={{ git_password | default('') }} \ No newline at end of file +GIT_PASSWORD={{ git_password | default('') }} diff --git a/deployment/stacks/application/docker-compose.yml b/deployment/stacks/application/docker-compose.yml index a960ec87..c40fe36c 100644 --- a/deployment/stacks/application/docker-compose.yml +++ b/deployment/stacks/application/docker-compose.yml @@ -289,12 +289,16 @@ services: volumes: app-code: name: app-code + external: true app-storage: name: app-storage + external: true app-logs: name: app-logs + external: true redis-data: name: redis-data + external: true networks: traefik-public: