feat: add system maintenance automation

This commit is contained in:
2025-11-01 19:56:42 +01:00
parent b76064d94a
commit 2d99a23286
14 changed files with 209 additions and 15 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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 }}"

View File

@@ -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

View File

@@ -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') }}"

View File

@@ -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"

View File

@@ -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') }}"

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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: