--- # Ubuntu/Debian LEMP WordPress installation - name: Install LEMP stack with WordPress (Ubuntu/Debian) hosts: all become: yes pre_tasks: - name: Gather OS facts setup: gather_subset: - '!all' - '!min' - 'distribution' - 'distribution_version' - 'os_family' - name: Load Debian family variables include_vars: "../vars/debian-family.yml" - name: Debug OS information debug: msg: | OS Family: {{ ansible_os_family }} Distribution: {{ ansible_distribution }} Version: {{ ansible_distribution_version }} Supported: Ubuntu/Debian family systems - name: Verify supported OS fail: msg: "This playbook only supports Ubuntu/Debian systems. Detected: {{ ansible_os_family }}" when: ansible_os_family != "Debian" # Environment Detection and Configuration - name: Detect if running in Docker stat: path: /.dockerenv register: docker_env tags: always - name: Check for deployment_environment override set_fact: environment_override: "{{ deployment_environment is defined }}" tags: always - name: Set deployment environment (auto-detect or override) set_fact: deployment_environment: >- {{ deployment_environment | default('docker' if docker_env.stat.exists else 'bare_metal') }} tags: always - name: Set WordPress site URL with smart defaults set_fact: wp_site_url: "{{ wp_site_url | default(_auto_wp_url) }}" vars: _auto_wp_url: >- {{ 'http://localhost:8080' if (deployment_environment == 'docker') else 'http://' + (ansible_default_ipv4.address | default('localhost')) }} tags: always - name: Debug deployment configuration debug: msg: | Environment: {{ deployment_environment }} ({{ 'overridden' if environment_override else 'auto-detected' }}) WordPress URL: {{ wp_site_url }} Docker detected: {{ docker_env.stat.exists }} tags: always vars: # WordPress Configuration wordpress_path: "{{ web_root }}" wordpress_url: "http://{{ ansible_default_ipv4.address | default('localhost') }}" # Database Configuration (use inventory variables or these defaults) default_mysql_root_password: "{{ mysql_root_password | default('secure_root_password_123') }}" default_wordpress_db_name: "{{ wordpress_db_name | default('wordpress_db') }}" default_wordpress_db_user: "{{ wordpress_db_user | default('wordpress_user') }}" default_wordpress_db_password: "{{ wordpress_db_password | default('secure_wp_password_123') }}" # WordPress Admin (use inventory variables directly) # These variables come from inventory/production.yml # wp_admin_user, wp_admin_password, wp_admin_email, wp_site_title # Auto-Update Configuration update_cron_enabled: true tasks: # IMPORTANT: Pre-flight checks - create basic directory structure FIRST - name: Ensure /var/www exists file: path: /var/www state: directory mode: '0755' tags: [preflight] - name: Create web root directory file: path: /var/www/html state: directory owner: www-data group: www-data mode: '0755' tags: [preflight] # Package Installation - name: Update package cache apt: update_cache: yes cache_valid_time: 3600 tags: packages - name: Install gnupg for PPA (required for apt-add-repository) package: name: gnupg state: present tags: packages - name: Install nginx first (required for web root) apt: name: nginx state: present tags: [packages, nginx] - name: Install all required packages package: name: "{{ packages }}" state: present tags: [packages, nginx] - name: Install gnupg for PPA (required for apt-add-repository) package: name: gnupg state: present tags: packages - name: Add PHP PPA for Ubuntu (needed for PHP 8.4) apt_repository: repo: ppa:ondrej/php when: ansible_distribution == "Ubuntu" tags: packages - name: Install all required packages package: name: "{{ packages }}" state: present tags: packages # Service Management - name: Start and enable Nginx service: name: "{{ nginx_service }}" state: started enabled: yes tags: nginx - name: Start and enable MySQL/MariaDB service: name: "{{ mysql_service }}" state: started enabled: yes tags: mysql - name: Start and enable PHP-FPM service: name: "{{ php_fpm_service }}" state: started enabled: yes tags: php # Firewall Configuration - name: Configure firewall (UFW) ufw: rule: allow port: "{{ item }}" proto: tcp loop: - "22" - "80" - "443" tags: firewall # MySQL Security (Ubuntu 24.04 fix for auth_socket) - name: Set MySQL root password (using auth_socket first) mysql_user: name: root password: "{{ mysql_root_password }}" plugin: mysql_native_password login_unix_socket: /var/run/mysqld/mysqld.sock state: present become: true tags: mysql - name: Create MySQL configuration for root template: src: ../templates/my.cnf.j2 dest: /root/.my.cnf mode: '0600' tags: mysql - name: Remove anonymous MySQL users mysql_user: name: "" host_all: yes state: absent tags: mysql - name: Remove MySQL test database mysql_db: name: test state: absent tags: mysql # WordPress Database Setup - name: Create WordPress database mysql_db: name: "{{ wordpress_db_name }}" state: present tags: mysql - name: Create WordPress database user mysql_user: name: "{{ wordpress_db_user }}" password: "{{ wordpress_db_password }}" priv: "{{ wordpress_db_name }}.*:ALL" host: localhost state: present tags: mysql # WordPress Installation - name: Create web root directory file: path: "{{ wordpress_path }}" state: directory owner: www-data group: www-data mode: '0755' tags: wordpress - name: Remove default nginx index file file: path: "{{ wordpress_path }}/index.nginx-debian.html" state: absent tags: wordpress - name: Download WordPress get_url: url: https://wordpress.org/latest.tar.gz dest: /tmp/wordpress.tar.gz mode: '0644' tags: wordpress - name: Extract WordPress unarchive: src: /tmp/wordpress.tar.gz dest: /tmp/ remote_src: yes tags: wordpress - name: Copy WordPress files command: "cp -r /tmp/wordpress/. {{ wordpress_path }}/" args: creates: "{{ wordpress_path }}/wp-config-sample.php" tags: wordpress - name: Set WordPress file permissions file: path: "{{ wordpress_path }}" owner: "{{ nginx_user }}" group: "{{ nginx_user }}" recurse: yes tags: wordpress # SSL Configuration (optional) - name: Generate self-signed SSL certificate (if SSL enabled but no cert provided) command: > openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout {{ ssl_cert_key_path | default('/etc/ssl/private/nginx-selfsigned.key') }} -out {{ ssl_cert_path | default('/etc/ssl/certs/nginx-selfsigned.crt') }} -subj "/C=DE/ST=State/L=City/O=Organization/OU=OrgUnit/CN={{ domain_name }}" when: - ssl_enabled | default(false) - not (ssl_cert_path is defined and ssl_cert_key_path is defined) tags: ssl # Nginx Configuration - name: Configure Nginx for WordPress template: src: >- ../templates/{% if ssl_enabled | default(false) %}wordpress-ssl.nginx.j2{% else %}wordpress.nginx.j2{% endif %} dest: "{{ nginx_sites_available }}/wordpress" notify: restart nginx tags: nginx - name: Enable WordPress site file: src: "{{ nginx_sites_available }}/wordpress" dest: "{{ nginx_sites_enabled }}/wordpress" state: link notify: restart nginx tags: nginx - name: Remove default Nginx site file: path: "{{ nginx_sites_enabled }}/default" state: absent notify: restart nginx tags: nginx # PHP Configuration - name: Configure PHP-FPM pool template: src: ../templates/www.conf.j2 dest: "{{ php_fpm_pool_path }}/www.conf" backup: yes notify: restart php-fpm tags: php - name: Configure PHP settings lineinfile: path: "{{ php_cli_config_path }}/php.ini" regexp: "{{ item.regexp }}" line: "{{ item.line }}" backup: yes loop: - { regexp: '^upload_max_filesize', line: 'upload_max_filesize = 64M' } - { regexp: '^post_max_size', line: 'post_max_size = 64M' } - { regexp: '^memory_limit', line: 'memory_limit = 256M' } - { regexp: '^max_execution_time', line: 'max_execution_time = 300' } notify: restart php-fpm tags: php # WordPress Configuration - name: Configure WordPress template: src: ../templates/wp-config.php.j2 dest: "{{ wordpress_path }}/wp-config.php" owner: www-data group: www-data mode: '0600' tags: wordpress # WP-CLI Installation - name: Download WP-CLI get_url: url: https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar dest: /usr/local/bin/wp mode: '0755' tags: wp-cli - name: Verify WP-CLI installation command: wp --info environment: HOME: "{{ wordpress_path }}" register: wp_cli_info tags: wp-cli - name: Display WP-CLI info debug: var: wp_cli_info.stdout_lines tags: wp-cli # WordPress Core Installation - name: Install WordPress Core command: > wp core install --url="{{ wp_site_url }}" --title="{{ wp_site_title }}" --admin_user="{{ wp_admin_user }}" --admin_password="{{ wp_admin_password }}" --admin_email="{{ wp_admin_email }}" --path="{{ wordpress_path }}" --allow-root environment: HOME: "{{ wordpress_path }}" tags: wp-install - name: Set WordPress file ownership after installation file: path: "{{ wordpress_path }}" owner: "{{ nginx_user }}" group: "{{ nginx_user }}" recurse: yes tags: wp-install - name: Display WordPress installation success debug: msg: | WordPress installation completed! URL: {{ wp_site_url }} Admin User: {{ wp_admin_user }} Admin Password: {{ wp_admin_password }} Admin Email: {{ wp_admin_email }} tags: wp-install # WordPress Auto-Update Setup - name: Create update script directory file: path: /var/backups/wordpress state: directory mode: '0755' tags: update - name: Deploy WordPress update script copy: src: ../scripts/wp-update.sh dest: /usr/local/bin/wp-update.sh mode: '0755' tags: update - name: Configure WordPress auto-update settings command: > wp option update auto_update_core_minor enabled --allow-root environment: HOME: "{{ wordpress_path }}" ignore_errors: yes tags: update - name: Set up cron job for WordPress updates cron: name: wordpress-weekly-update job: "/usr/local/bin/wp-update.sh --minor --no-backup >> /var/log/wp-update.log 2>&1" minute: "0" hour: "4" weekday: "0" user: "root" when: update_cron_enabled | default(true) | bool tags: update # Fail2Ban Security - name: Deploy Fail2Ban configuration template: src: ../templates/fail2ban.j2 dest: /etc/fail2ban/jail.local mode: '0644' notify: restart fail2ban tags: fail2ban - name: Deploy WordPress Fail2Ban filter copy: src: ../templates/fail2ban-wordpress-filter.conf dest: /etc/fail2ban/filter.d/wordpress-login.conf mode: '0644' notify: restart fail2ban tags: fail2ban - name: Ensure Fail2Ban service is running service: name: fail2ban state: started enabled: yes tags: fail2ban - name: Display Fail2Ban status info debug: msg: | Fail2Ban security enabled! Jails: sshd, nginx-http-auth, nginx-botsearch, wordpress-login, recidive Use: fail2ban-client status to see banned IPs tags: fail2ban handlers: - name: restart nginx service: name: "{{ nginx_service }}" state: restarted - name: restart php-fpm service: name: "{{ php_fpm_service }}" state: restarted - name: restart fail2ban service: name: fail2ban state: restarted - name: restart mysql service: name: "{{ mysql_service }}" state: restarted