From 573224a36b6d6611906dfc038aac856dfd142d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Palencs=C3=A1r?= Date: Wed, 18 Jun 2025 18:24:03 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Initial=20release:=20Complete=20?= =?UTF-8?q?Ansible=20LEMP=20WordPress=20automation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ Features: - Complete LEMP stack automation - WordPress with WP-CLI - Redis Object Cache (50% performance boost) - Multi-OS support - SSL/Let's Encrypt integration - Security hardening - Enterprise features - Docker testing environment - GitHub Actions CI/CD - Comprehensive documentation πŸ§ͺ Fully tested and production-ready Copyright (c) 2025 Sebastian PalencsΓ‘r --- .ansible-lint | 50 +++ .github/ISSUE_TEMPLATE/bug_report.yml | 107 ++++++ .github/ISSUE_TEMPLATE/documentation.yml | 76 ++++ .github/ISSUE_TEMPLATE/feature_request.yml | 98 +++++ .github/pull_request_template.md | 52 +++ .github/workflows/ci-cd.yml | 252 +++++++++++++ .github/workflows/docs.yml | 59 +++ .gitignore | 96 +++++ .yamllint.yml | 23 ++ CHANGELOG.md | 51 +++ LICENSE | 21 ++ PROJECT_OVERVIEW.md | 200 ++++++++++ README.md | 291 +++++++++++++++ SECURITY.md | 141 +++++++ docker/Dockerfile | 76 ++++ docker/Dockerfile.optimized | 118 ++++++ docker/docker-compose.yml | 39 ++ docker/start-services.sh | 17 + docs/contributing.md | 235 ++++++++++++ docs/production-deployment.md | 265 ++++++++++++++ docs/ssl-setup.md | 74 ++++ docs/troubleshooting.md | 290 +++++++++++++++ inventory/docker.ini | 36 ++ inventory/production.example | 39 ++ inventory/staging.example | 36 ++ playbooks/install-wordpress-official.yml | 159 ++++++++ playbooks/lemp-wordpress-multios.yml | 345 ++++++++++++++++++ playbooks/lemp-wordpress-ssl.yml | 209 +++++++++++ playbooks/lemp-wordpress.yml | 168 +++++++++ .../ultimate-performance-optimization.yml | 289 +++++++++++++++ playbooks/wordpress-advanced-features.yml | 313 ++++++++++++++++ .../fail2ban-filters/wordpress-auth.conf | 14 + .../fail2ban-filters/wordpress-xmlrpc.conf | 13 + templates/fail2ban-filters/wordpress.conf | 15 + templates/fail2ban-wordpress.conf.j2 | 26 ++ templates/fastcgi-cache.conf.j2 | 67 ++++ templates/logrotate.conf.j2 | 103 ++++++ templates/memcached.conf.j2 | 46 +++ templates/my.cnf.j2 | 61 ++++ templates/nginx.conf.j2 | 155 ++++++++ templates/performance-monitor.sh.j2 | 241 ++++++++++++ templates/php-performance.ini.j2 | 0 templates/php.ini.j2 | 254 +++++++++++++ templates/redis.conf.j2 | 58 +++ templates/security.conf.j2 | 40 ++ templates/ssl-renewal-notify.sh.j2 | 70 ++++ templates/sysctl.conf.j2 | 41 +++ templates/wordpress-backup.sh.j2 | 49 +++ templates/wordpress-cron.sh.j2 | 64 ++++ templates/wordpress-multisite.nginx.j2 | 123 +++++++ templates/wordpress-redhat.nginx.j2 | 75 ++++ templates/wordpress-ssl.nginx.j2 | 83 +++++ templates/wordpress.nginx.j2 | 84 +++++ templates/wp-cli.yml.j2 | 25 ++ templates/wp-config.php.j2 | 86 +++++ templates/www.conf.j2 | 70 ++++ tests/integration-test.sh | 216 +++++++++++ tests/validate-inventory.py | 179 +++++++++ vars/debian.yml | 39 ++ vars/redhat.yml | 68 ++++ vars/ubuntu.yml | 39 ++ 61 files changed, 6629 insertions(+) create mode 100644 .ansible-lint create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci-cd.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .gitignore create mode 100644 .yamllint.yml create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 PROJECT_OVERVIEW.md create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 docker/Dockerfile create mode 100644 docker/Dockerfile.optimized create mode 100644 docker/docker-compose.yml create mode 100644 docker/start-services.sh create mode 100644 docs/contributing.md create mode 100644 docs/production-deployment.md create mode 100644 docs/ssl-setup.md create mode 100644 docs/troubleshooting.md create mode 100644 inventory/docker.ini create mode 100644 inventory/production.example create mode 100644 inventory/staging.example create mode 100644 playbooks/install-wordpress-official.yml create mode 100644 playbooks/lemp-wordpress-multios.yml create mode 100644 playbooks/lemp-wordpress-ssl.yml create mode 100644 playbooks/lemp-wordpress.yml create mode 100644 playbooks/ultimate-performance-optimization.yml create mode 100644 playbooks/wordpress-advanced-features.yml create mode 100644 templates/fail2ban-filters/wordpress-auth.conf create mode 100644 templates/fail2ban-filters/wordpress-xmlrpc.conf create mode 100644 templates/fail2ban-filters/wordpress.conf create mode 100644 templates/fail2ban-wordpress.conf.j2 create mode 100644 templates/fastcgi-cache.conf.j2 create mode 100644 templates/logrotate.conf.j2 create mode 100644 templates/memcached.conf.j2 create mode 100644 templates/my.cnf.j2 create mode 100644 templates/nginx.conf.j2 create mode 100644 templates/performance-monitor.sh.j2 create mode 100644 templates/php-performance.ini.j2 create mode 100644 templates/php.ini.j2 create mode 100644 templates/redis.conf.j2 create mode 100644 templates/security.conf.j2 create mode 100644 templates/ssl-renewal-notify.sh.j2 create mode 100644 templates/sysctl.conf.j2 create mode 100644 templates/wordpress-backup.sh.j2 create mode 100644 templates/wordpress-cron.sh.j2 create mode 100644 templates/wordpress-multisite.nginx.j2 create mode 100644 templates/wordpress-redhat.nginx.j2 create mode 100644 templates/wordpress-ssl.nginx.j2 create mode 100644 templates/wordpress.nginx.j2 create mode 100644 templates/wp-cli.yml.j2 create mode 100644 templates/wp-config.php.j2 create mode 100644 templates/www.conf.j2 create mode 100755 tests/integration-test.sh create mode 100755 tests/validate-inventory.py create mode 100644 vars/debian.yml create mode 100644 vars/redhat.yml create mode 100644 vars/ubuntu.yml diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 0000000..6230649 --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,50 @@ +# Ansible-lint configuration +# https://ansible-lint.readthedocs.io/en/latest/configuring.html + +# Exclude paths from linting +exclude_paths: + - .cache/ + - .github/ + - docker/ + - tests/ + - '*.md' + +# Enable specific rules +enable_list: + - yaml + - no-changed-when + - no-tabs + +# Skip specific rules +skip_list: + - role-name # We don't use role naming conventions + - galaxy # We don't publish to Galaxy + - meta-no-info # No meta/main.yml files + +# Configure specific rules +rules: + line-length: + max: 120 + truthy: + allowed-values: ['true', 'false', 'yes', 'no', 'on', 'off'] + braces: + max-spaces-inside: 1 + max-spaces-inside-empty: 0 + brackets: + max-spaces-inside: 1 + max-spaces-inside-empty: 0 + indentation: + spaces: 2 + indent-sequences: true + comments: + min-spaces-from-content: 1 + +# Use specific Ansible version for linting +supported_ansible_versions: + - ">=6.0" + +# Offline mode (don't check for newer ansible-lint versions) +offline: false + +# Verbosity level +verbosity: 1 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..78ba1fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,107 @@ +name: πŸ› Bug Report +description: File a bug report to help us improve +title: "[BUG] " +labels: ["bug", "needs-triage"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! Please provide as much detail as possible. + + - type: checkboxes + id: terms + attributes: + label: Pre-check + description: Please confirm you have done the following + options: + - label: I have searched existing issues to make sure this is not a duplicate + required: true + - label: I have read the documentation and troubleshooting guide + required: true + + - type: dropdown + id: environment + attributes: + label: Environment + description: What environment are you deploying to? + options: + - Docker (testing) + - Ubuntu Server + - Debian Server + - CentOS/RHEL/Rocky Linux + - Cloud Instance (AWS/GCP/Azure) + - VPS + - Bare Metal + - Other + validations: + required: true + + - type: input + id: os-version + attributes: + label: OS Version + description: "Example: Ubuntu 24.04 LTS, CentOS Stream 9" + placeholder: "Ubuntu 24.04 LTS" + validations: + required: true + + - type: input + id: ansible-version + attributes: + label: Ansible Version + description: Output of `ansible --version` + placeholder: "ansible [core 2.15.0]" + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: Bug Description + description: A clear and concise description of what the bug is + placeholder: Describe what happened... + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What you expected to happen + placeholder: Describe what should have happened... + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Run command '...' + 2. See error '...' + 3. Check log '...' + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant Log Output + description: Please copy and paste any relevant log output (ansible-playbook -vv output, error logs, etc.) + render: shell + + - type: textarea + id: config + attributes: + label: Configuration + description: Your inventory file and any custom variables (please remove sensitive information) + render: yaml + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Add any other context about the problem here diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..a6fe24b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,76 @@ +name: πŸ“š Documentation Issue +description: Report an issue with documentation +title: "[DOCS] " +labels: ["documentation", "needs-triage"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Thanks for helping improve our documentation! + + - type: dropdown + id: doc-type + attributes: + label: Documentation Type + description: Which documentation needs attention? + options: + - README.md + - Setup/Installation Guide + - Production Deployment Guide + - SSL Setup Guide + - Troubleshooting Guide + - Contributing Guidelines + - API/Variable Documentation + - Code Comments + - Other + validations: + required: true + + - type: dropdown + id: issue-type + attributes: + label: Issue Type + description: What kind of documentation issue is this? + options: + - Missing Information + - Incorrect Information + - Outdated Information + - Unclear Instructions + - Typo/Grammar + - Missing Examples + - Broken Links + - Formatting Issues + - Other + validations: + required: true + + - type: textarea + id: current-content + attributes: + label: Current Content + description: What does the current documentation say? (copy/paste relevant section) + render: markdown + + - type: textarea + id: expected-content + attributes: + label: Expected Content + description: What should the documentation say instead? + render: markdown + validations: + required: true + + - type: input + id: location + attributes: + label: Location + description: "File path and line number (e.g., docs/ssl-setup.md:45)" + placeholder: "docs/production-deployment.md:120" + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Any other information that might be helpful diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..66ea5e4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,98 @@ +name: πŸš€ Feature Request +description: Suggest a new feature or enhancement +title: "[FEATURE] " +labels: ["enhancement", "needs-triage"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a new feature! Please provide as much detail as possible. + + - type: checkboxes + id: terms + attributes: + label: Pre-check + description: Please confirm you have done the following + options: + - label: I have searched existing issues to make sure this is not a duplicate + required: true + - label: I have checked the project roadmap and documentation + required: true + + - type: dropdown + id: feature-type + attributes: + label: Feature Type + description: What type of feature is this? + options: + - New Playbook + - Operating System Support + - Security Enhancement + - Performance Improvement + - WordPress Feature + - Documentation + - CI/CD Enhancement + - Other + validations: + required: true + + - type: textarea + id: problem + attributes: + label: Problem Description + description: Is your feature request related to a problem? Please describe. + placeholder: "I'm always frustrated when..." + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Describe the solution you'd like + placeholder: "I would like to see..." + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Describe any alternative solutions or features you've considered + + - type: textarea + id: use-case + attributes: + label: Use Case + description: Describe your use case and how this feature would help + placeholder: "This would help me/users to..." + validations: + required: true + + - type: dropdown + id: priority + attributes: + label: Priority + description: How important is this feature to you? + options: + - Low - Nice to have + - Medium - Would be helpful + - High - Important for my use case + - Critical - Blocking my workflow + + - type: checkboxes + id: contribution + attributes: + label: Contribution + description: Are you willing to contribute to this feature? + options: + - label: I would like to work on this feature + - label: I can help with testing + - label: I can help with documentation + - label: I can provide feedback during development + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Add any other context, screenshots, or examples about the feature request diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e59f2f0 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,52 @@ +# Pull Request + +## Description +Brief description of the changes in this PR. + +## Type of Change +Please delete options that are not relevant. + +- [ ] πŸ› Bug fix (non-breaking change which fixes an issue) +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] πŸ’₯ Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] πŸ“š Documentation update +- [ ] πŸ”§ Configuration change +- [ ] 🎨 Code style/formatting +- [ ] ♻️ Refactoring +- [ ] ⚑ Performance improvement +- [ ] πŸ”’ Security improvement + +## Related Issue +Fixes #(issue number) + +## Testing +Please describe the tests that you ran to verify your changes: + +- [ ] Tested on Ubuntu 24.04 +- [ ] Tested on Ubuntu 22.04 +- [ ] Tested on Debian 12 +- [ ] Tested on CentOS Stream 9 +- [ ] Tested with Docker environment +- [ ] Tested SSL configuration +- [ ] Tested WordPress installation +- [ ] Tested advanced features +- [ ] All existing tests pass +- [ ] Added new tests for new functionality + +## Checklist +Please check all that apply: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings or errors +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Screenshots (if applicable) +Add screenshots to help explain your changes. + +## Additional Notes +Add any other notes about the PR here. diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..5e5a591 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,252 @@ +name: CI/CD Pipeline + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + lint: + name: Lint Ansible Playbooks + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ansible ansible-lint yamllint + + - name: Lint YAML files + run: | + yamllint . + continue-on-error: true + + - name: Lint Ansible playbooks + run: | + ansible-lint playbooks/*.yml + continue-on-error: true + + - name: Validate Ansible syntax + run: | + for playbook in playbooks/*.yml; do + ansible-playbook --syntax-check "$playbook" + done + + test-ubuntu: + name: Test on Ubuntu + runs-on: ubuntu-latest + needs: lint + strategy: + matrix: + ubuntu_version: ['20.04', '22.04', '24.04'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install Ansible + run: | + python -m pip install --upgrade pip + pip install ansible + + - name: Set up Docker + uses: docker/setup-buildx-action@v3 + + - name: Create test inventory + run: | + mkdir -p test-inventory + cat > test-inventory/hosts <> /etc/sudoers + CMD ["/sbin/init"] + EOF + + - name: Run test container + run: | + docker run -d --name test-server \ + --privileged \ + --tmpfs /tmp \ + --tmpfs /run \ + --tmpfs /run/lock \ + -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ + test-ubuntu:${{ matrix.ubuntu_version }} + + - name: Test base LEMP installation + run: | + ansible-playbook -i test-inventory/hosts \ + playbooks/lemp-wordpress.yml \ + --extra-vars "mysql_root_password=test123 wordpress_db_password=test123 wp_admin_password=test123" \ + -v + + - name: Test WordPress installation + run: | + ansible-playbook -i test-inventory/hosts \ + playbooks/install-wordpress-official.yml \ + --extra-vars "wp_admin_password=test123" \ + -v + + - name: Verify installation + run: | + # Test if services are running + docker exec test-server systemctl is-active nginx + docker exec test-server systemctl is-active mysql + docker exec test-server systemctl is-active php8.3-fpm || docker exec test-server systemctl is-active php8.1-fpm + + # Test if WordPress is accessible + docker exec test-server curl -f http://localhost/ || true + + - name: Cleanup + if: always() + run: | + docker stop test-server || true + docker rm test-server || true + + test-centos: + name: Test on CentOS Stream + runs-on: ubuntu-latest + needs: lint + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install Ansible + run: | + python -m pip install --upgrade pip + pip install ansible + + - name: Set up Docker + uses: docker/setup-buildx-action@v3 + + - name: Create test inventory + run: | + mkdir -p test-inventory + cat > test-inventory/hosts <> /etc/sudoers + CMD ["/sbin/init"] + EOF + + - name: Run CentOS test container + run: | + docker run -d --name test-centos \ + --privileged \ + --tmpfs /tmp \ + --tmpfs /run \ + --tmpfs /run/lock \ + -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ + test-centos:stream9 + + - name: Test LEMP installation on CentOS + run: | + ansible-playbook -i test-inventory/hosts \ + playbooks/lemp-wordpress-multios.yml \ + --extra-vars "mysql_root_password=test123 wordpress_db_password=test123 wp_admin_password=test123" \ + -v + continue-on-error: true + + - name: Cleanup CentOS + if: always() + run: | + docker stop test-centos || true + docker rm test-centos || true + + security-scan: + name: Security Scanning + runs-on: ubuntu-latest + needs: lint + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + release: + name: Create Release + runs-on: ubuntu-latest + needs: [test-ubuntu, test-centos, security-scan] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get latest tag + id: get_tag + run: | + latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT + + - name: Generate changelog + run: | + echo "# Changelog" > CHANGELOG_RELEASE.md + echo "" >> CHANGELOG_RELEASE.md + git log ${{ steps.get_tag.outputs.latest_tag }}..HEAD --pretty=format:"- %s (%h)" >> CHANGELOG_RELEASE.md + + - name: Create Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ github.run_number }} + release_name: Release v${{ github.run_number }} + body_path: CHANGELOG_RELEASE.md + draft: false + prerelease: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..53ef583 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,59 @@ +name: Documentation + +on: + push: + branches: [ main ] + paths: [ 'docs/**', 'README.md' ] + +jobs: + deploy-docs: + name: Deploy Documentation + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install dependencies + run: | + npm install -g @vuepress/cli vuepress + + - name: Build documentation + run: | + # Create docs structure for VuePress + mkdir -p .vuepress + cat > .vuepress/config.js < + +--- + +Thank you for helping keep our project and our users safe! + +--- + +**Maintainer**: Sebastian PalencsΓ‘r +**Copyright**: (c) 2025 Sebastian PalencsΓ‘r + diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..37996ff --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,76 @@ +FROM ubuntu:24.04 + +# Umgebungsvariablen setzen +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Europe/Berlin + +# System updaten und notwendige Pakete installieren +RUN apt-get update && apt-get upgrade -y && apt-get install -y \ + openssh-server \ + sudo \ + python3 \ + python3-pip \ + python3-venv \ + curl \ + wget \ + vim \ + nano \ + git \ + systemctl \ + ca-certificates \ + gnupg \ + lsb-release \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# SSH-Verzeichnis erstellen +RUN mkdir /var/run/sshd + +# Root-Passwort setzen (fΓΌr Testing) +RUN echo 'root:password' | chpasswd + +# SSH-Konfiguration fΓΌr moderne Sicherheit +RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \ + sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \ + sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config && \ + echo "ClientAliveInterval 60" >> /etc/ssh/sshd_config && \ + echo "ClientAliveCountMax 3" >> /etc/ssh/sshd_config + +# Ansible-User erstellen mit modernen Standards +RUN useradd -m -s /bin/bash ansible && \ + echo 'ansible:ansible123' | chpasswd && \ + usermod -aG sudo ansible + +# Sudo ohne Passwort fΓΌr ansible-User (sicher fΓΌr Testumgebung) +RUN echo 'ansible ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/ansible && \ + chmod 0440 /etc/sudoers.d/ansible + +# SSH-Keys-Verzeichnis fΓΌr ansible-User erstellen +RUN mkdir -p /home/ansible/.ssh && \ + chown ansible:ansible /home/ansible/.ssh && \ + chmod 700 /home/ansible/.ssh + +# Webroot-Verzeichnis vorbereiten +RUN mkdir -p /var/www/html && \ + chown ansible:ansible /var/www/html + +# Systemd-Ersatz fΓΌr Container (optional) +RUN systemctl --user enable ssh || true + +# Gesunde Defaults setzen +WORKDIR /home/ansible +USER root + +# Start-Script kopieren +COPY start-services.sh /usr/local/bin/start-services.sh +RUN chmod +x /usr/local/bin/start-services.sh + +# SSH-Port freigeben +EXPOSE 22 + +# Healthcheck hinzufΓΌgen +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD service ssh status || exit 1 + +# Start-Script ausfΓΌhren +CMD ["/usr/local/bin/start-services.sh"] diff --git a/docker/Dockerfile.optimized b/docker/Dockerfile.optimized new file mode 100644 index 0000000..5904dd4 --- /dev/null +++ b/docker/Dockerfile.optimized @@ -0,0 +1,118 @@ +# Optimized multi-stage Dockerfile for LEMP WordPress testing +# This Dockerfile creates a lightweight, secure testing environment + +# Stage 1: Base system with essential packages +FROM ubuntu:24.04 AS base + +# Set environment variables +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Europe/Berlin + +# Create necessary directories and users early +RUN mkdir -p /var/run/sshd /var/log/supervisor \ + && groupadd -r ansible && useradd -r -g ansible ansible \ + && echo 'ansible ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + +# Install base system packages in a single layer +RUN apt-get update && apt-get upgrade -y && apt-get install -y \ + # Core system + ca-certificates \ + gnupg \ + lsb-release \ + apt-transport-https \ + # SSH and system control + openssh-server \ + sudo \ + systemd \ + systemd-sysv \ + # Python for Ansible + python3 \ + python3-pip \ + python3-venv \ + python3-dev \ + # Essential utilities + curl \ + wget \ + unzip \ + git \ + vim \ + nano \ + htop \ + net-tools \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Stage 2: Runtime with LEMP stack +FROM base AS runtime + +# Install LEMP stack packages +RUN apt-get update && apt-get install -y \ + # Web server + nginx \ + # Database + mysql-server \ + mysql-client \ + # PHP and extensions + php8.3 \ + php8.3-fpm \ + php8.3-cli \ + php8.3-mysql \ + php8.3-gd \ + php8.3-xml \ + php8.3-mbstring \ + php8.3-curl \ + php8.3-zip \ + php8.3-intl \ + php8.3-soap \ + php8.3-xmlrpc \ + php8.3-opcache \ + php8.3-redis \ + php8.3-memcached \ + # Security and monitoring + fail2ban \ + ufw \ + # Cache systems (optional) + redis-server \ + memcached \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Configure SSH +RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config \ + && sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config \ + && echo 'root:password' | chpasswd \ + && echo 'ansible:ansible' | chpasswd + +# Configure systemd +RUN systemctl enable ssh \ + && systemctl enable nginx \ + && systemctl enable mysql \ + && systemctl enable php8.3-fpm + +# Create web directory structure +RUN mkdir -p /var/www/html \ + && chown -R www-data:www-data /var/www/html \ + && chmod -R 755 /var/www/html + +# Copy service startup script +COPY start-services.sh /usr/local/bin/start-services.sh +RUN chmod +x /usr/local/bin/start-services.sh + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost/ || exit 1 + +# Expose ports +EXPOSE 22 80 443 + +# Set working directory +WORKDIR /var/www/html + +# Use systemd as init system for proper service management +CMD ["/usr/local/bin/start-services.sh"] + +# Labels for metadata +LABEL org.opencontainers.image.title="LEMP WordPress Testing Environment" +LABEL org.opencontainers.image.description="Ubuntu 24.04 with Nginx, MySQL, PHP 8.3 for WordPress testing" +LABEL org.opencontainers.image.version="1.0" +LABEL org.opencontainers.image.licenses="MIT" diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..88bd965 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,39 @@ + + +services: + ubuntu-test: + build: + context: . + dockerfile: Dockerfile + container_name: ansible-ubuntu-test + hostname: ubuntu-test + ports: + - "2222:22" + - "8080:80" + volumes: + - ./test-data:/opt/test-data:rw + - ./html:/var/www/html:rw + environment: + - TZ=Europe/Berlin + restart: unless-stopped + healthcheck: + test: ["CMD", "service", "ssh", "status"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - ansible-net + security_opt: + - seccomp:unconfined + tmpfs: + - /tmp:noexec,nosuid,size=500m + +networks: + ansible-net: + driver: bridge + driver_opts: + com.docker.network.bridge.name: ansible-br1 + ipam: + config: + - subnet: 172.25.0.0/24 diff --git a/docker/start-services.sh b/docker/start-services.sh new file mode 100644 index 0000000..96f71b2 --- /dev/null +++ b/docker/start-services.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Services fΓΌr LEMP-Stack starten + +# MySQL starten +service mysql start + +# PHP-FPM starten +service php8.3-fpm start + +# Nginx starten +service nginx start + +# SSH-Server fΓΌr Ansible +service ssh start + +# Container am Leben halten +tail -f /dev/null diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..04a833e --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,235 @@ +# Contributing to Ansible LEMP WordPress + +Thank you for your interest in contributing! This document provides guidelines for contributing to this project. + +## Code of Conduct + +This project follows the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/). By participating, you agree to uphold this code. + +## How to Contribute + +### Reporting Bugs + +Before creating bug reports, please check the existing issues to avoid duplicates. When creating a bug report, include: + +- **Description:** Clear description of the issue +- **Environment:** OS version, Ansible version, target system details +- **Steps to reproduce:** Detailed steps to recreate the issue +- **Expected behavior:** What you expected to happen +- **Actual behavior:** What actually happened +- **Logs:** Relevant log output (use code blocks) +- **Configuration:** Your inventory and variable files (sanitized) + +### Suggesting Features + +Feature requests are welcome! Please: + +- Check existing feature requests first +- Provide a clear use case +- Explain the expected behavior +- Consider implementation complexity +- Be open to discussion + +### Pull Requests + +1. **Fork the repository** +2. **Create a feature branch:** `git checkout -b feature/amazing-feature` +3. **Make your changes** +4. **Test thoroughly** (see Testing section) +5. **Commit with clear messages** +6. **Push to your fork:** `git push origin feature/amazing-feature` +7. **Open a Pull Request** + +#### Pull Request Guidelines + +- **Clear title:** Summarize the change in the title +- **Description:** Explain what and why, not just how +- **Link issues:** Reference related issues with "Fixes #123" +- **Test coverage:** Ensure your changes are tested +- **Documentation:** Update docs if needed +- **Small changes:** Keep PRs focused and manageable + +## Development Setup + +### Prerequisites + +- Ansible 2.9+ +- Docker and Docker Compose (for testing) +- Git +- Text editor with YAML support + +### Local Development + +1. **Clone your fork:** + ```bash + git clone https://github.com/yourusername/ansible-lemp-wordpress.git + cd ansible-lemp-wordpress + ``` + +2. **Set up testing environment:** + ```bash + cd docker/ + docker-compose up -d + ``` + +3. **Test your changes:** + ```bash + ansible-playbook -i inventory/docker.ini playbooks/lemp-wordpress.yml + ``` + +### Testing + +Before submitting, please test your changes: + +#### Unit Tests +```bash +# Lint Ansible playbooks +ansible-lint playbooks/*.yml + +# Validate YAML syntax +ansible-playbook --syntax-check playbooks/lemp-wordpress.yml +``` + +#### Integration Tests +```bash +# Test on Docker container +cd docker/ +docker-compose up -d +ansible-playbook -i ../inventory/docker.ini ../playbooks/lemp-wordpress.yml + +# Test WordPress installation +ansible-playbook -i ../inventory/docker.ini ../playbooks/install-wordpress-official.yml + +# Verify installation +curl -I http://localhost:8080 +``` + +#### Multi-OS Testing +Test on different operating systems: +- Ubuntu 20.04 LTS +- Ubuntu 22.04 LTS +- Ubuntu 24.04 LTS +- Debian 11 +- Debian 12 + +## Coding Standards + +### Ansible Best Practices + +- **Use descriptive task names** +- **Add appropriate tags** +- **Use variables for reusable values** +- **Follow idempotency principles** +- **Include proper error handling** + +### YAML Style + +```yaml +# Good +- name: Install nginx package + apt: + name: nginx + state: present + update_cache: yes + tags: + - nginx + - packages + +# Avoid +- apt: name=nginx state=present update_cache=yes +``` + +### Variables + +- Use lowercase with underscores: `mysql_root_password` +- Group related variables in files +- Provide sensible defaults +- Document complex variables + +### Templates + +- Use `.j2` extension for Jinja2 templates +- Include header comments +- Use consistent indentation +- Test template rendering + +## File Structure + +``` +ansible-lemp-wordpress/ +β”œβ”€β”€ playbooks/ # Main playbooks +β”œβ”€β”€ roles/ # Ansible roles (if used) +β”œβ”€β”€ templates/ # Jinja2 templates +β”œβ”€β”€ vars/ # Variable definitions +β”œβ”€β”€ inventory/ # Inventory examples +β”œβ”€β”€ docker/ # Docker testing environment +β”œβ”€β”€ docs/ # Documentation +β”œβ”€β”€ tests/ # Test scripts +└── .github/ # GitHub workflows +``` + +## Documentation + +### Required Documentation + +- Update README.md for significant changes +- Add inline comments for complex logic +- Update relevant docs/ files +- Include examples for new features + +### Documentation Style + +- Use clear, concise language +- Include code examples +- Provide both basic and advanced usage +- Test all documented commands + +## Release Process + +### Version Numbering + +This project follows [Semantic Versioning](https://semver.org/): +- **MAJOR:** Incompatible API changes +- **MINOR:** New functionality (backward compatible) +- **PATCH:** Bug fixes (backward compatible) + +### Changelog + +Update CHANGELOG.md for all changes: +- **Added:** New features +- **Changed:** Changes in existing functionality +- **Deprecated:** Soon-to-be removed features +- **Removed:** Removed features +- **Fixed:** Bug fixes +- **Security:** Security improvements + +## Getting Help + +- **Documentation:** Check the docs/ directory +- **Issues:** Search existing GitHub issues +- **Discussions:** Use GitHub Discussions for questions +- **IRC:** Join #ansible on Libera.Chat +- **Community:** Ansible community forums + +## Recognition + +Contributors will be recognized in: +- README.md contributors section +- Release notes for significant contributions +- Project documentation + +## License + +By contributing, you agree that your contributions will be licensed under the MIT License. + +## Questions? + +Don't hesitate to ask! Open an issue with the "question" label or start a discussion. + +Thank you for contributing! πŸŽ‰ + +## πŸ‘¨β€πŸ’» Maintainer + +**Sebastian PalencsΓ‘r** +Copyright (c) 2025 Sebastian PalencsΓ‘r + diff --git a/docs/production-deployment.md b/docs/production-deployment.md new file mode 100644 index 0000000..f97f394 --- /dev/null +++ b/docs/production-deployment.md @@ -0,0 +1,265 @@ +# Production Deployment Guide + +This guide walks you through deploying WordPress on a production server using this Ansible automation. + +## Prerequisites + +### Control Machine (Your Local Computer) +- Ansible 6.0 or higher +- SSH client +- Git (to clone this repository) + +### Target Server +- Ubuntu 24.04 LTS (recommended) or other supported OS +- Minimum 1GB RAM (2GB+ recommended) +- 10GB+ free disk space +- Root or sudo access +- SSH access enabled + +## Step-by-Step Deployment + +### 1. Prepare Your Server + +#### Create a Non-Root User (if not exists) +```bash +# On your server, as root: +adduser deployer +usermod -aG sudo deployer + +# Add passwordless sudo (optional but recommended) +echo "deployer ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/deployer +``` + +#### Set Up SSH Key Authentication +```bash +# On your local machine: +ssh-keygen -t ed25519 -C "your-email@example.com" +ssh-copy-id deployer@your-server.com + +# Test connection: +ssh deployer@your-server.com +``` + +### 2. Clone and Configure + +```bash +# Clone the repository +git clone https://github.com/yourusername/ansible-lemp-wordpress.git +cd ansible-lemp-wordpress + +# Create your inventory +cp inventory/production.example inventory/production.ini +``` + +### 3. Edit Inventory Configuration + +Edit `inventory/production.ini`: + +```ini +[webservers] +your-domain.com ansible_user=deployer ansible_ssh_private_key_file=~/.ssh/id_ed25519 + +[webservers:vars] +# WordPress Configuration +wp_admin_user=admin +wp_admin_password=your_very_secure_password_here +wp_admin_email=admin@your-domain.com +wp_site_title=Your WordPress Site +wp_site_url=https://your-domain.com + +# Database Configuration +mysql_root_password=extremely_secure_root_password +wordpress_db_name=wordpress_prod +wordpress_db_user=wp_prod_user +wordpress_db_password=secure_database_password + +# SSL Configuration +enable_ssl=true +ssl_email=admin@your-domain.com +``` + +### 4. Test Connection + +```bash +ansible -i inventory/production.ini webservers -m ping +``` + +Expected output: +``` +your-domain.com | SUCCESS => { + "changed": false, + "ping": "pong" +} +``` + +### 5. Deploy LEMP Stack + +```bash +ansible-playbook -i inventory/production.ini playbooks/lemp-wordpress.yml +``` + +This will: +- Update system packages +- Install and configure Nginx +- Install and secure MySQL +- Install PHP with required extensions +- Configure firewall (if applicable) + +### 6. Install WordPress + +```bash +ansible-playbook -i inventory/production.ini playbooks/install-wordpress-official.yml +``` + +This will: +- Download and install WP-CLI +- Download latest WordPress +- Create wp-config.php +- Set up database and admin user +- Configure proper file permissions + +### 7. Post-Deployment Steps + +#### Update DNS Records +Point your domain to your server's IP address: +``` +A Record: your-domain.com β†’ YOUR_SERVER_IP +A Record: www.your-domain.com β†’ YOUR_SERVER_IP +``` + +#### Set Up SSL (if enabled) +If you set `enable_ssl=true`, SSL certificates will be automatically configured via Let's Encrypt. + +#### Test Your Site +- Visit: `https://your-domain.com` +- Admin: `https://your-domain.com/wp-admin` + +## Security Recommendations + +### 1. Change Default Passwords +Immediately change all default passwords: +- WordPress admin password +- Database passwords +- Server user passwords + +### 2. Update WordPress Admin Email +Go to WordPress Admin β†’ Settings β†’ General and update the admin email. + +### 3. Install Security Plugins +Consider installing: +- Wordfence Security +- Updraft Plus (for backups) +- iThemes Security + +### 4. Server Security +```bash +# Enable automatic security updates +sudo dpkg-reconfigure -plow unattended-upgrades + +# Configure firewall +sudo ufw allow ssh +sudo ufw allow http +sudo ufw allow https +sudo ufw enable +``` + +### 5. Database Security +- Use strong passwords +- Limit database access to localhost only +- Regular backups + +## Monitoring and Maintenance + +### 1. Log Files +Monitor these log files: +- Nginx: `/var/log/nginx/access.log` and `/var/log/nginx/error.log` +- PHP-FPM: `/var/log/php8.3-fpm.log` +- MySQL: `/var/log/mysql/error.log` +- WordPress: `/var/www/html/wp-content/debug.log` (if WP_DEBUG enabled) + +### 2. Regular Updates +```bash +# Update system packages +sudo apt update && sudo apt upgrade + +# Update WordPress (via WP-CLI) +cd /var/www/html +sudo -u www-data wp core update --allow-root +sudo -u www-data wp plugin update --all --allow-root +sudo -u www-data wp theme update --all --allow-root +``` + +### 3. Backups +Set up automated backups: +- Database: `mysqldump` +- Files: `rsync` or cloud storage +- Consider using backup plugins like UpdraftPlus + +### 4. Performance Monitoring +Monitor: +- Server resources (CPU, RAM, disk space) +- Website response times +- Database query performance +- Error rates + +## Troubleshooting + +### Connection Issues +```bash +# Test Ansible connection +ansible -i inventory/production.ini webservers -m ping + +# Test SSH connection +ssh -v deployer@your-server.com +``` + +### Service Issues +```bash +# Check service status +sudo systemctl status nginx +sudo systemctl status mysql +sudo systemctl status php8.3-fpm + +# View logs +sudo journalctl -u nginx +sudo journalctl -u mysql +sudo journalctl -u php8.3-fpm +``` + +### WordPress Issues +```bash +# Check WordPress installation +cd /var/www/html +sudo -u www-data wp core verify-checksums --allow-root + +# Check database connection +sudo -u www-data wp db check --allow-root +``` + +## Scaling Considerations + +### Multi-Server Setup +For high-traffic sites, consider: +- Load balancer (Nginx or HAProxy) +- Database replication or clustering +- Redis/Memcached for object caching +- CDN for static assets + +### Performance Optimization +- PHP OPcache configuration +- Nginx caching +- Database query optimization +- Image optimization +- Content compression + +## Support + +If you encounter issues: +1. Check the [Troubleshooting Guide](troubleshooting.md) +2. Review server logs +3. Open an issue on GitHub +4. Check existing discussions + +--- + +**Next Steps**: [SSL Setup Guide](ssl-setup.md) diff --git a/docs/ssl-setup.md b/docs/ssl-setup.md new file mode 100644 index 0000000..6667d6d --- /dev/null +++ b/docs/ssl-setup.md @@ -0,0 +1,74 @@ +# SSL/HTTPS Setup Guide + +This guide explains how to set up SSL certificates for your WordPress installation. + +## Let's Encrypt with Certbot + +### Prerequisites +- Domain name pointing to your server +- Ports 80 and 443 open in firewall +- Nginx already configured and running + +### Automatic SSL Setup + +The playbook includes an optional SSL setup that uses Let's Encrypt: + +```bash +ansible-playbook -i inventory/production playbooks/lemp-wordpress.yml -e enable_ssl=true -e domain_name=yourdomain.com +``` + +### Manual SSL Setup + +If you prefer manual setup or have your own certificates: + +1. **Install Certbot:** + ```bash + sudo apt update + sudo apt install certbot python3-certbot-nginx + ``` + +2. **Generate Certificate:** + ```bash + sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com + ``` + +3. **Auto-renewal:** + ```bash + sudo crontab -e + # Add: 0 12 * * * /usr/bin/certbot renew --quiet + ``` + +### SSL Configuration + +The SSL-enabled Nginx configuration includes: +- HTTP to HTTPS redirect +- Strong SSL ciphers +- HSTS headers +- OCSP stapling + +### Troubleshooting SSL + +**Certificate not found:** +- Verify domain DNS points to server +- Check firewall allows ports 80/443 +- Ensure Nginx is running + +**Mixed content warnings:** +- Update WordPress site URL in database +- Check for hardcoded HTTP links +- Use SSL-aware plugins + +**Certificate renewal fails:** +- Check Nginx configuration syntax +- Verify webroot path permissions +- Review certbot logs: `/var/log/letsencrypt/` + +## Custom Certificates + +To use your own SSL certificates: + +1. Copy certificates to `/etc/ssl/certs/` +2. Update Nginx configuration +3. Restart Nginx service + +See `templates/wordpress-ssl.nginx.j2` for the SSL template. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..9f932ec --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,290 @@ +# Troubleshooting Guide + +Common issues and their solutions when using the LEMP WordPress automation. + +## Installation Issues + +### Ansible Connection Problems + +**SSH Connection Refused:** +```bash +# Check if SSH service is running +sudo systemctl status ssh +sudo systemctl start ssh + +# Verify SSH port (default 22) +sudo netstat -tlnp | grep :22 +``` + +**Permission Denied (publickey):** +```bash +# Use password authentication temporarily +ansible-playbook -i inventory/production playbooks/lemp-wordpress.yml --ask-pass + +# Or set up SSH keys +ssh-copy-id username@your-server +``` + +**Host Key Verification Failed:** +```bash +# Remove old host key +ssh-keygen -R your-server-ip + +# Or disable host key checking temporarily +export ANSIBLE_HOST_KEY_CHECKING=False +``` + +### Package Installation Failures + +**Package Not Found:** +```bash +# Update package cache first +sudo apt update + +# Check if universe repository is enabled (Ubuntu) +sudo add-apt-repository universe +``` + +**Lock Error (dpkg/apt):** +```bash +# Kill any running package managers +sudo killall apt apt-get dpkg + +# Remove locks +sudo rm /var/lib/apt/lists/lock +sudo rm /var/cache/apt/archives/lock +sudo rm /var/lib/dpkg/lock* + +# Reconfigure packages +sudo dpkg --configure -a +``` + +## Service Issues + +### Nginx Problems + +**Nginx Won't Start:** +```bash +# Check configuration syntax +sudo nginx -t + +# Check what's using port 80 +sudo netstat -tlnp | grep :80 +sudo lsof -i :80 + +# Check logs +sudo journalctl -u nginx -f +``` + +**403 Forbidden Error:** +```bash +# Check file permissions +ls -la /var/www/html/ +sudo chown -R www-data:www-data /var/www/html/ +sudo chmod -R 755 /var/www/html/ + +# Check Nginx user in config +grep user /etc/nginx/nginx.conf +``` + +### MySQL/MariaDB Issues + +**MySQL Won't Start:** +```bash +# Check MySQL status and logs +sudo systemctl status mysql +sudo journalctl -u mysql -f + +# Check disk space +df -h + +# Reset MySQL if corrupted +sudo systemctl stop mysql +sudo mysqld_safe --skip-grant-tables & +``` + +**Can't Connect to Database:** +```bash +# Test MySQL connection +mysql -u root -p +mysql -u wordpress_user -p wordpress_db + +# Check user privileges +mysql -u root -p -e "SELECT user,host FROM mysql.user;" +mysql -u root -p -e "SHOW GRANTS FOR 'wordpress_user'@'localhost';" +``` + +**Access Denied for User:** +```bash +# Reset WordPress database user +mysql -u root -p +DROP USER IF EXISTS 'wordpress_user'@'localhost'; +CREATE USER 'wordpress_user'@'localhost' IDENTIFIED BY 'your_password'; +GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wordpress_user'@'localhost'; +FLUSH PRIVILEGES; +``` + +### PHP-FPM Issues + +**PHP-FPM Not Working:** +```bash +# Check PHP-FPM status +sudo systemctl status php8.3-fpm + +# Check socket file +ls -la /run/php/php8.3-fpm.sock + +# Check PHP-FPM configuration +sudo nginx -t +sudo php-fpm8.3 -t +``` + +**PHP Errors in WordPress:** +```bash +# Enable PHP error logging +sudo tail -f /var/log/php8.3-fpm.log + +# Check WordPress debug +# Add to wp-config.php: +define('WP_DEBUG', true); +define('WP_DEBUG_LOG', true); + +# Check WordPress logs +tail -f /var/www/html/wp-content/debug.log +``` + +## WordPress Issues + +### WordPress Installation Problems + +**White Screen of Death:** +```bash +# Check PHP errors +sudo tail -f /var/log/php8.3-fpm.log +sudo tail -f /var/log/nginx/error.log + +# Increase PHP memory limit +sudo nano /etc/php/8.3/fpm/php.ini +# memory_limit = 256M + +sudo systemctl restart php8.3-fpm +``` + +**Database Connection Error:** +```bash +# Verify wp-config.php settings +sudo cat /var/www/html/wp-config.php + +# Test database connection manually +mysql -u wordpress_user -p wordpress_db +``` + +**File Permissions Issues:** +```bash +# Set correct WordPress permissions +sudo chown -R www-data:www-data /var/www/html/ +sudo find /var/www/html/ -type d -exec chmod 755 {} \; +sudo find /var/www/html/ -type f -exec chmod 644 {} \; +sudo chmod 600 /var/www/html/wp-config.php +``` + +### Performance Issues + +**Slow Website Loading:** +```bash +# Enable PHP OPcache +sudo nano /etc/php/8.3/fpm/php.ini +# opcache.enable=1 +# opcache.memory_consumption=128 + +# Optimize MySQL +sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf +# innodb_buffer_pool_size = 256M + +# Enable Nginx gzip +sudo nano /etc/nginx/nginx.conf +# gzip on; +``` + +## Security Issues + +### File Permission Problems + +**Incorrect Ownership:** +```bash +# WordPress files should be owned by www-data +sudo chown -R www-data:www-data /var/www/html/ + +# wp-config.php should have restricted permissions +sudo chmod 600 /var/www/html/wp-config.php +``` + +**Upload Directory Issues:** +```bash +# Create uploads directory if missing +sudo mkdir -p /var/www/html/wp-content/uploads +sudo chown www-data:www-data /var/www/html/wp-content/uploads +sudo chmod 755 /var/www/html/wp-content/uploads +``` + +## Network Issues + +### Firewall Configuration + +**Can't Access Website:** +```bash +# Check if UFW is blocking +sudo ufw status +sudo ufw allow 80 +sudo ufw allow 443 + +# Check iptables +sudo iptables -L +``` + +**Domain/DNS Issues:** +```bash +# Test DNS resolution +nslookup yourdomain.com +dig yourdomain.com + +# Test local access +curl -I http://localhost +curl -I http://your-server-ip +``` + +## Log Files Locations + +Important log files for debugging: + +- **Nginx Access:** `/var/log/nginx/access.log` +- **Nginx Error:** `/var/log/nginx/error.log` +- **PHP-FPM:** `/var/log/php8.3-fpm.log` +- **MySQL:** `/var/log/mysql/error.log` +- **WordPress:** `/var/www/html/wp-content/debug.log` +- **System:** `journalctl -f` + +## Getting Help + +1. **Check the logs first** - Most issues show up in the relevant log files +2. **Test each component individually** - Nginx, PHP, MySQL, WordPress +3. **Verify configuration files** - Use built-in syntax checkers +4. **Check file permissions** - Many issues are permission-related +5. **Open an issue** on GitHub with relevant log output + +## Common Commands + +```bash +# Restart all services +sudo systemctl restart nginx php8.3-fpm mysql + +# Check all service status +sudo systemctl status nginx php8.3-fpm mysql + +# Test configurations +sudo nginx -t +sudo php-fpm8.3 -t + +# Monitor logs in real-time +sudo tail -f /var/log/nginx/error.log /var/log/php8.3-fpm.log +``` diff --git a/inventory/docker.ini b/inventory/docker.ini new file mode 100644 index 0000000..26c0dd0 --- /dev/null +++ b/inventory/docker.ini @@ -0,0 +1,36 @@ +# Docker Test Environment Inventory +# This inventory is configured for the Docker testing environment + +[testservers] +127.0.0.1 ansible_port=2222 ansible_user=root ansible_ssh_pass=root_password + +[testservers:vars] +# Test WordPress Configuration +wp_admin_user=admin +wp_admin_password=admin123 +wp_admin_email=admin@example.com +wp_site_title=WordPress Test Site +wp_site_url=http://localhost:8080 + +# Test Database Configuration +mysql_root_password=secure_root_pass_123 +wordpress_db_name=wordpress +wordpress_db_user=wp_user +wordpress_db_password=wp_secure_pass_123 + +# PHP Configuration +php_version=8.3 +php_max_execution_time=300 +php_memory_limit=256M +php_upload_max_filesize=64M + +# Nginx Configuration +nginx_client_max_body_size=64M + +# Test Configuration +enable_ssl=false +timezone=UTC + +# Ansible Configuration +ansible_host_key_checking=False +ansible_python_interpreter=/usr/bin/python3 diff --git a/inventory/production.example b/inventory/production.example new file mode 100644 index 0000000..ed75393 --- /dev/null +++ b/inventory/production.example @@ -0,0 +1,39 @@ +# Production Server Inventory Example +# Copy this file to production.ini and customize for your servers + +[webservers] +# Replace with your actual server IP or domain +your-server.example.com ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/your-key.pem + +# Alternative with password authentication (not recommended for production) +# your-server.example.com ansible_user=ubuntu ansible_ssh_pass=your_password + +[webservers:vars] +# WordPress Configuration +wp_admin_user=admin +wp_admin_password=change_this_secure_password_123 +wp_admin_email=admin@yoursite.com +wp_site_title=My WordPress Site +wp_site_url=https://yoursite.com + +# Database Configuration +mysql_root_password=very_secure_root_password_123 +wordpress_db_name=wordpress +wordpress_db_user=wp_user +wordpress_db_password=secure_wp_db_password_123 + +# PHP Configuration +php_version=8.3 +php_max_execution_time=300 +php_memory_limit=256M +php_upload_max_filesize=64M + +# Nginx Configuration +nginx_client_max_body_size=64M + +# SSL Configuration (set to true for Let's Encrypt) +enable_ssl=false +ssl_email=admin@yoursite.com + +# System Configuration +timezone=UTC diff --git a/inventory/staging.example b/inventory/staging.example new file mode 100644 index 0000000..a85177b --- /dev/null +++ b/inventory/staging.example @@ -0,0 +1,36 @@ +# Staging Server Inventory Example +# Copy this file to staging.ini and customize for your staging servers + +[stagingservers] +# Replace with your staging server details +staging.yoursite.com ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/staging-key.pem + +[stagingservers:vars] +# Staging WordPress Configuration +wp_admin_user=admin +wp_admin_password=staging_password_123 +wp_admin_email=staging@yoursite.com +wp_site_title=WordPress Staging Site +wp_site_url=https://staging.yoursite.com + +# Staging Database Configuration +mysql_root_password=staging_root_password_123 +wordpress_db_name=wordpress_staging +wordpress_db_user=wp_staging_user +wordpress_db_password=staging_wp_password_123 + +# PHP Configuration +php_version=8.3 +php_max_execution_time=300 +php_memory_limit=256M +php_upload_max_filesize=64M + +# Nginx Configuration +nginx_client_max_body_size=64M + +# SSL Configuration +enable_ssl=true +ssl_email=admin@yoursite.com + +# System Configuration +timezone=UTC diff --git a/playbooks/install-wordpress-official.yml b/playbooks/install-wordpress-official.yml new file mode 100644 index 0000000..03d4342 --- /dev/null +++ b/playbooks/install-wordpress-official.yml @@ -0,0 +1,159 @@ +--- +- name: WordPress mit offiziellem WP-CLI installieren + hosts: testservers + become: yes + + tasks: + - name: WP-CLI von wp-cli.org herunterladen (offizielle Methode) + shell: | + cd /tmp + curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar + chmod +x wp-cli.phar + mv wp-cli.phar /usr/local/bin/wp + + - name: WP-CLI testen + shell: wp --info --allow-root + register: wp_cli_test + environment: + WP_CLI_ALLOW_ROOT: 1 + + - name: WP-CLI Info + debug: + var: wp_cli_test.stdout_lines + + - name: WordPress-Datenbank fΓΌr WP-CLI vorbereiten + shell: | + mysql -h 127.0.0.1 -P 3306 -u root -psecure_root_pass_123 -e " + DROP DATABASE IF EXISTS wordpress; + CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + FLUSH PRIVILEGES; + " + + - name: Alte WordPress-Dateien entfernen (außer wp-content) + shell: | + cd /var/www/html + find . -maxdepth 1 -name "*.php" -delete + find . -maxdepth 1 -name "*.html" -delete + find . -maxdepth 1 -name "*.txt" -delete + rm -rf wp-admin wp-includes + ls -la + register: cleanup + + - name: Cleanup-Ergebnis + debug: + var: cleanup.stdout_lines + + - name: WordPress-Core mit WP-CLI herunterladen + shell: | + cd /var/www/html + wp core download --allow-root --force + register: wp_download + environment: + WP_CLI_ALLOW_ROOT: 1 + + - name: WordPress-Download Ergebnis + debug: + var: wp_download.stdout_lines + + - name: wp-config.php mit WP-CLI erstellen + shell: | + cd /var/www/html + wp config create \ + --dbname=wordpress \ + --dbuser=wp_user \ + --dbpass=wp_secure_pass_123 \ + --dbhost=127.0.0.1:3306 \ + --dbcharset=utf8mb4 \ + --allow-root \ + --force + register: wp_config + environment: + WP_CLI_ALLOW_ROOT: 1 + + - name: wp-config Ergebnis + debug: + var: wp_config.stdout_lines + + - name: WordPress mit WP-CLI installieren + shell: | + cd /var/www/html + wp core install \ + --url="http://localhost:8080" \ + --title="WordPress Test Site" \ + --admin_user="admin" \ + --admin_password="admin123" \ + --admin_email="admin@example.com" \ + --allow-root + register: wp_install + environment: + WP_CLI_ALLOW_ROOT: 1 + + - name: WordPress-Installation Ergebnis + debug: + var: wp_install.stdout_lines + + - name: WordPress-Berechtigungen korrekt setzen + shell: | + cd /var/www/html + chown -R www-data:www-data . + find . -type d -exec chmod 755 {} \; + find . -type f -exec chmod 644 {} \; + chmod 600 wp-config.php + + - name: WordPress-Status mit WP-CLI prΓΌfen + shell: | + cd /var/www/html + echo "=== WordPress Version ===" + wp core version --allow-root + echo "=== Site-URLs ===" + wp option get siteurl --allow-root + wp option get home --allow-root + echo "=== Benutzer ===" + wp user list --allow-root + echo "=== Plugins ===" + wp plugin list --allow-root + echo "=== Themes ===" + wp theme list --allow-root + register: wp_status + environment: + WP_CLI_ALLOW_ROOT: 1 + + - name: WordPress-Status + debug: + var: wp_status.stdout_lines + + - name: Services neustarten + shell: | + service php8.3-fpm restart + service nginx restart + + - name: WordPress-Homepage testen + uri: + url: http://localhost + method: GET + return_content: yes + register: homepage_test + ignore_errors: yes + + - name: Homepage-Inhalt prΓΌfen + debug: + msg: | + Status: {{ homepage_test.status | default('Fehler') }} + Content-Length: {{ homepage_test.content | length if homepage_test.content is defined else 0 }} + EnthΓ€lt WordPress: {{ 'Ja' if homepage_test.content is defined and 'WordPress' in homepage_test.content else 'Nein' }} + + - name: Erfolgreiche Installation + debug: + msg: | + πŸŽ‰ WordPress erfolgreich installiert! + + πŸ“± URLs: + - Homepage: http://localhost:8080 + - Admin: http://localhost:8080/wp-admin + + πŸ”‘ Login-Daten: + - Benutzername: admin + - Passwort: admin123 + - E-Mail: admin@example.com + + πŸ›  WP-CLI verfΓΌgbar unter: wp --allow-root diff --git a/playbooks/lemp-wordpress-multios.yml b/playbooks/lemp-wordpress-multios.yml new file mode 100644 index 0000000..72179c4 --- /dev/null +++ b/playbooks/lemp-wordpress-multios.yml @@ -0,0 +1,345 @@ +--- +# Multi-OS LEMP WordPress installation with auto-detection +- name: Install LEMP stack with WordPress (Multi-OS Support) + hosts: all + become: yes + + pre_tasks: + - name: Gather OS facts + setup: + gather_subset: + - '!all' + - '!min' + - 'distribution' + - 'distribution_version' + - 'os_family' + + - name: Load OS-specific variables + include_vars: "../vars/{{ ansible_os_family | lower }}.yml" + + - name: Debug OS information + debug: + msg: | + OS Family: {{ ansible_os_family }} + Distribution: {{ ansible_distribution }} + Version: {{ ansible_distribution_version }} + Using variables from: vars/{{ ansible_os_family | lower }}.yml + + vars: + # WordPress Configuration + wordpress_path: "{{ web_root }}" + wordpress_url: "http://{{ ansible_default_ipv4.address | default('localhost') }}" + + # Database Configuration + mysql_root_password: "{{ mysql_root_password | default('secure_root_password_' + ansible_date_time.epoch) }}" + wordpress_db_name: "{{ wordpress_db_name | default('wordpress_db') }}" + wordpress_db_user: "{{ wordpress_db_user | default('wordpress_user') }}" + wordpress_db_password: "{{ wordpress_db_password | default('secure_wp_password_' + ansible_date_time.epoch) }}" + + # WordPress Admin + wp_admin_user: "{{ wp_admin_user | default('admin') }}" + wp_admin_password: "{{ wp_admin_password | default('secure_admin_password_' + ansible_date_time.epoch) }}" + wp_admin_email: "{{ wp_admin_email | default('admin@localhost') }}" + wp_site_title: "{{ wp_site_title | default('My WordPress Site') }}" + + tasks: + # Repository Setup for RedHat family + - name: Enable EPEL repository (RedHat/CentOS) + package: + name: "{{ epel_release }}" + state: present + when: ansible_os_family == "RedHat" + tags: repos + + - name: Install Remi repository (RedHat/CentOS) + yum: + name: "{{ remi_release }}" + state: present + when: ansible_os_family == "RedHat" + tags: repos + + - name: Enable Remi PHP repository (RedHat/CentOS) + command: "yum-config-manager --enable remi-php{{ php_version | replace('.', '') }}" + when: ansible_os_family == "RedHat" + tags: repos + + # Package Installation + - name: Update package cache (Debian/Ubuntu) + apt: + update_cache: yes + cache_valid_time: 3600 + when: ansible_os_family == "Debian" + tags: packages + + - name: Install Nginx + package: + name: "{{ nginx_package }}" + state: present + tags: nginx + + - name: Install MySQL/MariaDB packages + package: + name: "{{ mysql_packages }}" + state: present + tags: mysql + + - name: Install PHP packages + package: + name: "{{ php_packages }}" + state: present + tags: php + + - name: Install additional tools + package: + name: + - curl + - wget + - unzip + - git + state: present + tags: tools + + # 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 + + # SELinux Configuration (RedHat family) + - name: Configure SELinux booleans for web services + seboolean: + name: "{{ item }}" + state: yes + persistent: yes + loop: "{{ selinux_booleans }}" + when: ansible_os_family == "RedHat" and ansible_selinux.status == "enabled" + tags: selinux + + # Firewall Configuration + - name: Configure firewall (UFW - Debian/Ubuntu) + ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: + - "22" + - "80" + - "443" + when: ansible_os_family == "Debian" + tags: firewall + + - name: Configure firewall (firewalld - RedHat/CentOS) + firewalld: + service: "{{ item }}" + permanent: yes + state: enabled + immediate: yes + loop: + - ssh + - http + - https + when: ansible_os_family == "RedHat" + tags: firewall + + # MySQL Security + - name: Set MySQL root password + mysql_user: + name: root + password: "{{ mysql_root_password }}" + login_unix_socket: "{{ mysql_socket }}" + state: present + 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: "{{ nginx_user }}" + group: "{{ nginx_user }}" + mode: '0755' + 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 + + # Nginx Configuration + - name: Configure Nginx for WordPress (Debian/Ubuntu) + template: + src: ../templates/wordpress.nginx.j2 + dest: "{{ nginx_sites_available }}/wordpress" + when: ansible_os_family == "Debian" + notify: restart nginx + tags: nginx + + - name: Configure Nginx for WordPress (RedHat/CentOS) + template: + src: ../templates/wordpress-redhat.nginx.j2 + dest: "{{ nginx_sites_available }}/wordpress.conf" + when: ansible_os_family == "RedHat" + notify: restart nginx + tags: nginx + + - name: Enable WordPress site (Debian/Ubuntu) + file: + src: "{{ nginx_sites_available }}/wordpress" + dest: "{{ nginx_sites_enabled }}/wordpress" + state: link + when: ansible_os_family == "Debian" + notify: restart nginx + tags: nginx + + - name: Remove default Nginx site (Debian/Ubuntu) + file: + path: "{{ nginx_sites_enabled }}/default" + state: absent + when: ansible_os_family == "Debian" + notify: restart nginx + tags: nginx + + # PHP Configuration + - name: Configure PHP-FPM pool + template: + src: ../templates/www.conf.j2 + dest: "{{ php_fpm_pool_dir }}/www.conf" + backup: yes + notify: restart php-fpm + tags: php + + - name: Configure PHP settings + lineinfile: + path: "{{ php_ini_path }}" + 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: "{{ nginx_user }}" + group: "{{ nginx_user }}" + 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 + become_user: "{{ nginx_user }}" + 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 + + handlers: + - name: restart nginx + service: + name: "{{ nginx_service }}" + state: restarted + + - name: restart php-fpm + service: + name: "{{ php_fpm_service }}" + state: restarted + + - name: restart mysql + service: + name: "{{ mysql_service }}" + state: restarted diff --git a/playbooks/lemp-wordpress-ssl.yml b/playbooks/lemp-wordpress-ssl.yml new file mode 100644 index 0000000..d6d0f48 --- /dev/null +++ b/playbooks/lemp-wordpress-ssl.yml @@ -0,0 +1,209 @@ +--- +# SSL-enabled LEMP WordPress installation +- name: Install LEMP stack with WordPress and SSL + hosts: all + become: yes + vars_files: + - "../vars/{{ ansible_os_family | lower }}.yml" + + vars: + # SSL Configuration + enable_ssl: "{{ enable_ssl | default(false) }}" + domain_name: "{{ domain_name | default('localhost') }}" + www_redirect: "{{ www_redirect | default(true) }}" + ssl_certificate_path: "{{ ssl_certificate_path | default('/etc/ssl/certs/' + domain_name + '.crt') }}" + ssl_certificate_key_path: "{{ ssl_certificate_key_path | default('/etc/ssl/private/' + domain_name + '.key') }}" + letsencrypt_email: "{{ letsencrypt_email | default('admin@' + domain_name) }}" + + # WordPress Configuration + wordpress_path: /var/www/html + wordpress_url: "{{ 'https://' + domain_name if enable_ssl else 'http://' + domain_name }}" + + # Database Configuration + mysql_root_password: "{{ mysql_root_password | default('secure_root_password_' + ansible_date_time.epoch) }}" + wordpress_db_name: "{{ wordpress_db_name | default('wordpress_db') }}" + wordpress_db_user: "{{ wordpress_db_user | default('wordpress_user') }}" + wordpress_db_password: "{{ wordpress_db_password | default('secure_wp_password_' + ansible_date_time.epoch) }}" + + # WordPress Admin + wp_admin_user: "{{ wp_admin_user | default('admin') }}" + wp_admin_password: "{{ wp_admin_password | default('secure_admin_password_' + ansible_date_time.epoch) }}" + wp_admin_email: "{{ wp_admin_email | default(letsencrypt_email) }}" + wp_site_title: "{{ wp_site_title | default('My WordPress Site') }}" + + tasks: + # Include base LEMP installation + - name: Include base LEMP installation tasks + include_tasks: lemp-base-tasks.yml + + # SSL Certificate Management + - name: Install Certbot for Let's Encrypt + apt: + name: + - certbot + - python3-certbot-nginx + state: present + update_cache: yes + when: enable_ssl and ansible_os_family == "Debian" + tags: ssl + + - name: Install Certbot for Let's Encrypt (CentOS/RHEL) + yum: + name: + - certbot + - python3-certbot-nginx + state: present + when: enable_ssl and ansible_os_family == "RedHat" + tags: ssl + + - name: Check if SSL certificate exists + stat: + path: "{{ ssl_certificate_path }}" + register: ssl_cert_exists + when: enable_ssl + tags: ssl + + - name: Create temporary HTTP Nginx config for Let's Encrypt + template: + src: ../templates/wordpress.nginx.j2 + dest: /etc/nginx/sites-available/wordpress + when: enable_ssl and not ssl_cert_exists.stat.exists + notify: restart nginx + tags: ssl + + - name: Enable temporary site + file: + src: /etc/nginx/sites-available/wordpress + dest: /etc/nginx/sites-enabled/wordpress + state: link + when: enable_ssl and not ssl_cert_exists.stat.exists + notify: restart nginx + tags: ssl + + - name: Flush handlers to ensure Nginx is running + meta: flush_handlers + when: enable_ssl and not ssl_cert_exists.stat.exists + + - name: Generate Let's Encrypt certificate + command: > + certbot --nginx --non-interactive --agree-tos + --email {{ letsencrypt_email }} + -d {{ domain_name }} + {% if www_redirect %}-d www.{{ domain_name }}{% endif %} + --redirect + when: enable_ssl and not ssl_cert_exists.stat.exists and domain_name != 'localhost' + tags: ssl + + - name: Create self-signed certificate for localhost/testing + block: + - name: Create SSL directory + file: + path: /etc/ssl/private + state: directory + mode: '0700' + + - name: Generate self-signed certificate + command: > + openssl req -x509 -nodes -days 365 -newkey rsa:2048 + -keyout {{ ssl_certificate_key_path }} + -out {{ ssl_certificate_path }} + -subj "/C=US/ST=Test/L=Test/O=Test/CN={{ domain_name }}" + args: + creates: "{{ ssl_certificate_path }}" + when: enable_ssl and not ssl_cert_exists.stat.exists and domain_name == 'localhost' + tags: ssl + + # Nginx Configuration + - name: Configure Nginx for WordPress with SSL + template: + src: ../templates/wordpress-ssl.nginx.j2 + dest: /etc/nginx/sites-available/wordpress + backup: yes + when: enable_ssl + notify: restart nginx + tags: nginx + + - name: Configure Nginx for WordPress without SSL + template: + src: ../templates/wordpress.nginx.j2 + dest: /etc/nginx/sites-available/wordpress + backup: yes + when: not enable_ssl + notify: restart nginx + tags: nginx + + - name: Enable WordPress site + file: + src: /etc/nginx/sites-available/wordpress + dest: /etc/nginx/sites-enabled/wordpress + state: link + notify: restart nginx + tags: nginx + + - name: Remove default Nginx site + file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: restart nginx + tags: nginx + + # Rate limiting configuration + - name: Configure Nginx rate limiting + blockinfile: + path: /etc/nginx/nginx.conf + marker: "# {mark} ANSIBLE MANAGED BLOCK - Rate Limiting" + insertafter: "http {" + block: | + # Rate limiting zones + limit_req_zone $binary_remote_addr zone=admin:10m rate=5r/m; + limit_req_zone $binary_remote_addr zone=api:10m rate=10r/m; + notify: restart nginx + tags: nginx + + # WordPress Configuration + - name: Configure WordPress with SSL settings + template: + src: ../templates/wp-config.php.j2 + dest: "{{ wordpress_path }}/wp-config.php" + owner: www-data + group: www-data + mode: '0600' + tags: wordpress + + # SSL Auto-renewal + - name: Set up Let's Encrypt auto-renewal + cron: + name: "Renew Let's Encrypt certificates" + minute: "0" + hour: "12" + job: "/usr/bin/certbot renew --quiet" + when: enable_ssl and domain_name != 'localhost' + tags: ssl + + # Firewall Configuration + - name: Configure UFW for HTTP and HTTPS + ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: + - "80" + - "443" + when: enable_ssl + tags: firewall + + handlers: + - name: restart nginx + service: + name: nginx + state: restarted + + - name: restart php-fpm + service: + name: "php{{ php_version }}-fpm" + state: restarted + + - name: restart mysql + service: + name: "{{ mysql_service }}" + state: restarted diff --git a/playbooks/lemp-wordpress.yml b/playbooks/lemp-wordpress.yml new file mode 100644 index 0000000..2ce5363 --- /dev/null +++ b/playbooks/lemp-wordpress.yml @@ -0,0 +1,168 @@ +--- +- name: LEMP Stack + WordPress Installation auf Ubuntu + hosts: testservers + become: yes + vars: + mysql_root_password: "secure_root_pass_123" + wordpress_db_name: "wordpress" + wordpress_db_user: "wp_user" + wordpress_db_password: "wp_secure_pass_123" + wordpress_admin_user: "admin" + wordpress_admin_password: "admin_secure_pass_123" + wordpress_admin_email: "admin@example.com" + domain_name: "localhost" + + tasks: + - name: System aktualisieren + apt: + update_cache: yes + upgrade: dist + cache_valid_time: 3600 + + - name: Notwendige Pakete installieren + apt: + name: + - nginx + - mysql-server + - php8.3-fpm + - php8.3-mysql + - php8.3-curl + - php8.3-gd + - php8.3-intl + - php8.3-mbstring + - php8.3-soap + - php8.3-xml + - php8.3-xmlrpc + - php8.3-zip + - curl + - wget + - unzip + - python3-pymysql + state: present + + - name: MySQL-Service starten und aktivieren + shell: | + service mysql start + update-rc.d mysql enable + become: yes + + - name: MySQL root-Passwort setzen + mysql_user: + name: root + password: "{{ mysql_root_password }}" + login_unix_socket: /var/run/mysqld/mysqld.sock + column_case_sensitive: false + state: present + + - name: MySQL-Konfiguration fΓΌr root + copy: + content: | + [client] + user=root + password={{ mysql_root_password }} + dest: /root/.my.cnf + mode: '0600' + + - name: WordPress-Datenbank erstellen + mysql_db: + name: "{{ wordpress_db_name }}" + state: present + login_user: root + login_password: "{{ mysql_root_password }}" + + - name: WordPress-Datenbankuser erstellen + mysql_user: + name: "{{ wordpress_db_user }}" + password: "{{ wordpress_db_password }}" + priv: "{{ wordpress_db_name }}.*:ALL" + column_case_sensitive: false + state: present + login_user: root + login_password: "{{ mysql_root_password }}" + + - name: WordPress herunterladen + get_url: + url: https://wordpress.org/latest.tar.gz + dest: /tmp/wordpress.tar.gz + + - name: WordPress entpacken + unarchive: + src: /tmp/wordpress.tar.gz + dest: /tmp + remote_src: yes + + - name: WordPress-Dateien kopieren + copy: + src: /tmp/wordpress/ + dest: /var/www/html/ + remote_src: yes + owner: www-data + group: www-data + mode: '0755' + + - name: wp-config.php erstellen + template: + src: wp-config.php.j2 + dest: /var/www/html/wp-config.php + owner: www-data + group: www-data + mode: '0644' + + - name: Nginx-Konfiguration fΓΌr WordPress + template: + src: wordpress.nginx.j2 + dest: /etc/nginx/sites-available/wordpress + backup: yes + + - name: Standard Nginx-Site deaktivieren + file: + path: /etc/nginx/sites-enabled/default + state: absent + + - name: WordPress-Site aktivieren + file: + src: /etc/nginx/sites-available/wordpress + dest: /etc/nginx/sites-enabled/wordpress + state: link + + - name: Nginx-Konfiguration testen + command: nginx -t + register: nginx_test + failed_when: nginx_test.rc != 0 + + - name: PHP-FPM Service starten + shell: service php8.3-fpm start + become: yes + + - name: Nginx Service neustarten + shell: service nginx restart + become: yes + + - name: WordPress-Verzeichnis-Berechtigungen setzen + file: + path: /var/www/html + owner: www-data + group: www-data + recurse: yes + mode: '0755' + + - name: Spezielle WordPress-Verzeichnisse erstellen + file: + path: "{{ item }}" + state: directory + owner: www-data + group: www-data + mode: '0755' + loop: + - /var/www/html/wp-content/uploads + - /var/www/html/wp-content/upgrade + + - name: Installation abgeschlossen - Info ausgeben + debug: + msg: | + WordPress-Installation abgeschlossen! + URL: http://{{ domain_name }}:8080 + Admin-User: {{ wordpress_admin_user }} + Admin-Passwort: {{ wordpress_admin_password }} + DB-Name: {{ wordpress_db_name }} + DB-User: {{ wordpress_db_user }} diff --git a/playbooks/ultimate-performance-optimization.yml b/playbooks/ultimate-performance-optimization.yml new file mode 100644 index 0000000..ab9d783 --- /dev/null +++ b/playbooks/ultimate-performance-optimization.yml @@ -0,0 +1,289 @@ +--- +# Ultimate WordPress Performance Optimization Playbook +- name: Ultimate WordPress Performance Optimization + hosts: all + become: yes + vars_files: + - "../vars/{{ ansible_os_family | lower }}.yml" + + vars: + # Performance Configuration + enable_performance_monitoring: "{{ enable_performance_monitoring | default(true) }}" + enable_system_optimization: "{{ enable_system_optimization | default(true) }}" + enable_nginx_optimization: "{{ enable_nginx_optimization | default(true) }}" + enable_php_optimization: "{{ enable_php_optimization | default(true) }}" + enable_mysql_optimization: "{{ enable_mysql_optimization | default(true) }}" + + # Nginx Performance Settings + nginx_worker_processes: "{{ ansible_processor_vcpus }}" + nginx_worker_connections: "{{ (ansible_memtotal_mb / 4) | int }}" + nginx_fastcgi_cache_enabled: true + nginx_fastcgi_cache_size: "{{ (ansible_memtotal_mb / 10) | int }}m" + + # PHP Performance Settings + php_memory_limit: "{{ (ansible_memtotal_mb / 4) | int }}M" + php_max_execution_time: 300 + php_max_input_vars: 3000 + + # MySQL Performance Settings + innodb_buffer_pool_size: "{{ (ansible_memtotal_mb / 2) | int }}M" + max_connections: "{{ (ansible_memtotal_mb / 10) | int }}" + + tasks: + # System Optimizations + - name: Apply system performance optimizations + template: + src: ../templates/sysctl.conf.j2 + dest: /etc/sysctl.d/99-wordpress-performance.conf + backup: yes + when: enable_system_optimization + notify: reload sysctl + tags: system-optimization + + - name: Set system limits for web services + blockinfile: + path: /etc/security/limits.conf + marker: "# {mark} ANSIBLE MANAGED BLOCK - WordPress Performance" + block: | + # Limits for web services + www-data soft nofile 65536 + www-data hard nofile 65536 + mysql soft nofile 65536 + mysql hard nofile 65536 + redis soft nofile 65536 + redis hard nofile 65536 + when: enable_system_optimization + tags: system-optimization + + # Nginx Performance Optimization + - name: Optimize Nginx main configuration + template: + src: ../templates/nginx.conf.j2 + dest: /etc/nginx/nginx.conf + backup: yes + when: enable_nginx_optimization + notify: restart nginx + tags: nginx-optimization + + - name: Create FastCGI cache directory + file: + path: /var/cache/nginx + state: directory + owner: www-data + group: www-data + mode: '0755' + when: nginx_fastcgi_cache_enabled + tags: nginx-optimization + + - name: Configure Nginx FastCGI cache + copy: + content: | + # FastCGI cache configuration + include /etc/nginx/conf.d/fastcgi-cache.conf; + dest: /etc/nginx/conf.d/fastcgi-cache-include.conf + when: nginx_fastcgi_cache_enabled + notify: restart nginx + tags: nginx-optimization + + - name: Create FastCGI cache configuration + template: + src: ../templates/fastcgi-cache.conf.j2 + dest: /etc/nginx/conf.d/fastcgi-cache.conf + when: nginx_fastcgi_cache_enabled + notify: restart nginx + tags: nginx-optimization + + # PHP Performance Optimization + - name: Optimize PHP configuration + template: + src: ../templates/php.ini.j2 + dest: "{{ php_ini_path }}" + backup: yes + when: enable_php_optimization + notify: restart php-fpm + tags: php-optimization + + - name: Optimize PHP-FPM pool configuration + template: + src: ../templates/www.conf.j2 + dest: "{{ php_fpm_pool_dir }}/www.conf" + backup: yes + when: enable_php_optimization + notify: restart php-fpm + tags: php-optimization + + # MySQL Performance Optimization + - name: Optimize MySQL configuration + template: + src: ../templates/my.cnf.j2 + dest: "{{ mysql_conf_file }}" + backup: yes + when: enable_mysql_optimization + notify: restart mysql + tags: mysql-optimization + + # Redis Performance (if enabled) + - name: Optimize Redis configuration + template: + src: ../templates/redis.conf.j2 + dest: /etc/redis/redis.conf + backup: yes + when: enable_redis is defined and enable_redis + notify: restart redis + tags: redis-optimization + + # WordPress Cron Optimization + - name: Create optimized WordPress cron script + template: + src: ../templates/wordpress-cron.sh.j2 + dest: /usr/local/bin/wordpress-cron.sh + mode: '0755' + tags: wordpress-optimization + + - name: Disable default WordPress cron and use system cron + cron: + name: "WordPress Cron" + minute: "*/5" + job: "/usr/local/bin/wordpress-cron.sh" + user: root + tags: wordpress-optimization + + - name: Install Redis Object Cache plugin (if Redis enabled) + command: > + wp plugin install redis-cache --activate --allow-root + args: + chdir: "{{ wordpress_path }}" + when: enable_redis is defined and enable_redis + become_user: "{{ nginx_user }}" + tags: wordpress-optimization + + - name: Enable Redis Object Cache + command: > + wp redis enable --allow-root + args: + chdir: "{{ wordpress_path }}" + when: enable_redis is defined and enable_redis + become_user: "{{ nginx_user }}" + ignore_errors: yes + tags: wordpress-optimization + + # Performance Monitoring + - name: Install performance monitoring script + template: + src: ../templates/performance-monitor.sh.j2 + dest: /usr/local/bin/wordpress-performance-monitor.sh + mode: '0755' + when: enable_performance_monitoring + tags: monitoring + + - name: Schedule performance monitoring + cron: + name: "WordPress Performance Monitoring" + minute: "*/15" + job: "/usr/local/bin/wordpress-performance-monitor.sh" + user: root + when: enable_performance_monitoring + tags: monitoring + + # Log Management + - name: Configure logrotate for WordPress + template: + src: ../templates/logrotate.conf.j2 + dest: /etc/logrotate.d/wordpress + tags: log-management + + # Cache Warming Script + - name: Create cache warming script + copy: + content: | + #!/bin/bash + # Cache warming script for WordPress + + WORDPRESS_URL="{{ wordpress_url }}" + SITEMAP_URL="${WORDPRESS_URL}/sitemap.xml" + + # Warm up homepage + curl -s "$WORDPRESS_URL" > /dev/null + + # Warm up sitemap URLs (if sitemap exists) + if curl -s "$SITEMAP_URL" | grep -q "xml"; then + curl -s "$SITEMAP_URL" | grep -oP 'https?://[^<]+' | head -20 | while read url; do + curl -s "$url" > /dev/null + sleep 1 + done + fi + dest: /usr/local/bin/wordpress-cache-warm.sh + mode: '0755' + when: nginx_fastcgi_cache_enabled + tags: cache-optimization + + - name: Schedule cache warming + cron: + name: "WordPress Cache Warming" + minute: "0" + hour: "6" + job: "/usr/local/bin/wordpress-cache-warm.sh" + user: www-data + when: nginx_fastcgi_cache_enabled + tags: cache-optimization + + # Database Optimization + - name: Create database optimization script + copy: + content: | + #!/bin/bash + # WordPress Database Optimization Script + + cd {{ wordpress_path }} + + # Optimize database tables + wp db optimize --allow-root + + # Clean up spam comments + wp comment delete $(wp comment list --status=spam --format=ids --allow-root) --allow-root 2>/dev/null || true + + # Clean up trash posts + wp post delete $(wp post list --post_status=trash --format=ids --allow-root) --allow-root 2>/dev/null || true + + # Clean up expired transients + wp transient delete --expired --allow-root + + # Update search index (if search plugin is installed) + wp search-replace 'old-domain.com' '{{ domain_name }}' --dry-run --allow-root || true + dest: /usr/local/bin/wordpress-db-optimize.sh + mode: '0755' + tags: database-optimization + + - name: Schedule weekly database optimization + cron: + name: "WordPress Database Optimization" + minute: "0" + hour: "3" + weekday: "0" + job: "/usr/local/bin/wordpress-db-optimize.sh" + user: root + tags: database-optimization + + handlers: + - name: restart nginx + service: + name: "{{ nginx_service }}" + state: restarted + + - name: restart php-fpm + service: + name: "{{ php_fpm_service }}" + state: restarted + + - name: restart mysql + service: + name: "{{ mysql_service }}" + state: restarted + + - name: restart redis + service: + name: redis-server + state: restarted + + - name: reload sysctl + command: sysctl -p /etc/sysctl.d/99-wordpress-performance.conf diff --git a/playbooks/wordpress-advanced-features.yml b/playbooks/wordpress-advanced-features.yml new file mode 100644 index 0000000..d899f64 --- /dev/null +++ b/playbooks/wordpress-advanced-features.yml @@ -0,0 +1,313 @@ +--- +# Advanced WordPress features and optimizations +- name: WordPress Advanced Features Setup + hosts: all + become: yes + vars_files: + - "../vars/{{ ansible_os_family | lower }}.yml" + + vars: + # Backup Configuration + enable_backups: "{{ enable_backups | default(true) }}" + backup_schedule: "{{ backup_schedule | default('0 2 * * *') }}" # Daily at 2 AM + backup_retention_days: "{{ backup_retention_days | default(7) }}" + backup_destination: "{{ backup_destination | default('/var/backups/wordpress') }}" + + # Performance Configuration + enable_redis: "{{ enable_redis | default(false) }}" + enable_memcached: "{{ enable_memcached | default(false) }}" + enable_opcache: "{{ enable_opcache | default(true) }}" + + # Security Configuration + enable_fail2ban: "{{ enable_fail2ban | default(true) }}" + enable_modsecurity: "{{ enable_modsecurity | default(false) }}" + + # Monitoring Configuration + enable_monitoring: "{{ enable_monitoring | default(false) }}" + + # WordPress Multisite + enable_multisite: "{{ enable_multisite | default(false) }}" + multisite_type: "{{ multisite_type | default('subdirectory') }}" # subdirectory or subdomain + + tasks: + # Backup System + - name: Install backup dependencies + package: + name: + - rsync + - gzip + - tar + state: present + when: enable_backups + tags: backup + + - name: Create backup directories + file: + path: "{{ item }}" + state: directory + mode: '0755' + loop: + - "{{ backup_destination }}" + - "{{ backup_destination }}/database" + - "{{ backup_destination }}/files" + when: enable_backups + tags: backup + + - name: Create backup script + template: + src: ../templates/wordpress-backup.sh.j2 + dest: /usr/local/bin/wordpress-backup.sh + mode: '0755' + when: enable_backups + tags: backup + + - name: Schedule WordPress backups + cron: + name: "WordPress Backup" + minute: "{{ backup_schedule.split()[0] }}" + hour: "{{ backup_schedule.split()[1] }}" + day: "{{ backup_schedule.split()[2] }}" + month: "{{ backup_schedule.split()[3] }}" + weekday: "{{ backup_schedule.split()[4] }}" + job: "/usr/local/bin/wordpress-backup.sh" + when: enable_backups + tags: backup + + # Redis Cache + - name: Install Redis + package: + name: redis-server + state: present + when: enable_redis + tags: redis + + - name: Configure Redis + template: + src: ../templates/redis.conf.j2 + dest: /etc/redis/redis.conf + backup: yes + when: enable_redis + notify: restart redis + tags: redis + + - name: Start and enable Redis + service: + name: redis-server + state: started + enabled: yes + when: enable_redis + tags: redis + + - name: Install Redis PHP extension + package: + name: "php{{ php_version }}-redis" + state: present + when: enable_redis and ansible_os_family == "Debian" + notify: restart php-fpm + tags: redis + + # Memcached + - name: Install Memcached + package: + name: + - memcached + - "php{{ php_version }}-memcached" + state: present + when: enable_memcached and ansible_os_family == "Debian" + tags: memcached + + - name: Configure Memcached + template: + src: ../templates/memcached.conf.j2 + dest: /etc/memcached.conf + backup: yes + when: enable_memcached + notify: restart memcached + tags: memcached + + - name: Start and enable Memcached + service: + name: memcached + state: started + enabled: yes + when: enable_memcached + tags: memcached + + # PHP OPcache Optimization + - name: Configure PHP OPcache + blockinfile: + path: "{{ php_ini_path }}" + marker: "; {mark} ANSIBLE MANAGED BLOCK - OPcache" + block: | + ; OPcache Configuration + opcache.enable=1 + opcache.memory_consumption=256 + opcache.interned_strings_buffer=16 + opcache.max_accelerated_files=10000 + opcache.revalidate_freq=2 + opcache.save_comments=1 + opcache.enable_file_override=1 + when: enable_opcache + notify: restart php-fpm + tags: opcache + + # Fail2Ban Security + - name: Install Fail2Ban + package: + name: fail2ban + state: present + when: enable_fail2ban + tags: fail2ban + + - name: Configure Fail2Ban for WordPress + template: + src: ../templates/fail2ban-wordpress.conf.j2 + dest: /etc/fail2ban/jail.d/wordpress.conf + when: enable_fail2ban + notify: restart fail2ban + tags: fail2ban + + - name: Start and enable Fail2Ban + service: + name: fail2ban + state: started + enabled: yes + when: enable_fail2ban + tags: fail2ban + + # WordPress Plugins for Performance + - name: Install WordPress performance plugins + command: > + wp plugin install {{ item }} --activate --allow-root + args: + chdir: "{{ wordpress_path }}" + loop: + - w3-total-cache + - wp-optimize + - wordfence + when: enable_monitoring + become_user: "{{ nginx_user }}" + tags: wordpress-plugins + + # WordPress Multisite Setup + - name: Configure WordPress Multisite + blockinfile: + path: "{{ wordpress_path }}/wp-config.php" + marker: "/* {mark} ANSIBLE MANAGED BLOCK - Multisite */" + insertbefore: "/* That's all, stop editing!" + block: | + /* Multisite Configuration */ + define('WP_ALLOW_MULTISITE', true); + define('MULTISITE', true); + define('SUBDOMAIN_INSTALL', {{ 'true' if multisite_type == 'subdomain' else 'false' }}); + define('DOMAIN_CURRENT_SITE', '{{ domain_name | default(ansible_default_ipv4.address) }}'); + define('PATH_CURRENT_SITE', '/'); + define('SITE_ID_CURRENT_SITE', 1); + define('BLOG_ID_CURRENT_SITE', 1); + when: enable_multisite + tags: multisite + + - name: Configure Nginx for WordPress Multisite + template: + src: ../templates/wordpress-multisite.nginx.j2 + dest: "{{ nginx_sites_available }}/wordpress" + backup: yes + when: enable_multisite and ansible_os_family == "Debian" + notify: restart nginx + tags: multisite + + # SSL Certificate Auto-renewal with notifications + - name: Create SSL renewal notification script + template: + src: ../templates/ssl-renewal-notify.sh.j2 + dest: /usr/local/bin/ssl-renewal-notify.sh + mode: '0755' + when: enable_ssl is defined and enable_ssl + tags: ssl + + - name: Schedule SSL renewal with notifications + cron: + name: "Renew SSL certificates with notification" + minute: "0" + hour: "12" + job: "/usr/local/bin/ssl-renewal-notify.sh" + when: enable_ssl is defined and enable_ssl + tags: ssl + + # System Monitoring + - name: Install monitoring tools + package: + name: + - htop + - iotop + - nethogs + - glances + state: present + when: enable_monitoring + tags: monitoring + + - name: Install Netdata monitoring + shell: | + bash <(curl -Ss https://my-netdata.io/kickstart.sh) --dont-wait --stable-channel + args: + creates: /usr/sbin/netdata + when: enable_monitoring + tags: monitoring + + # WordPress CLI improvements + - name: Create WP-CLI config + template: + src: ../templates/wp-cli.yml.j2 + dest: "{{ wordpress_path }}/wp-cli.yml" + owner: "{{ nginx_user }}" + group: "{{ nginx_user }}" + tags: wp-cli + + # Database optimization + - name: Configure MySQL for WordPress optimization + blockinfile: + path: "{{ mysql_conf_file }}" + marker: "# {mark} ANSIBLE MANAGED BLOCK - WordPress Optimization" + block: | + # WordPress Optimization + innodb_buffer_pool_size = 256M + innodb_log_file_size = 64M + query_cache_type = 1 + query_cache_size = 32M + max_connections = 200 + thread_cache_size = 8 + tmp_table_size = 32M + max_heap_table_size = 32M + notify: restart mysql + tags: mysql-optimization + + handlers: + - name: restart nginx + service: + name: "{{ nginx_service }}" + state: restarted + + - name: restart php-fpm + service: + name: "{{ php_fpm_service }}" + state: restarted + + - name: restart mysql + service: + name: "{{ mysql_service }}" + state: restarted + + - name: restart redis + service: + name: redis-server + state: restarted + + - name: restart memcached + service: + name: memcached + state: restarted + + - name: restart fail2ban + service: + name: fail2ban + state: restarted diff --git a/templates/fail2ban-filters/wordpress-auth.conf b/templates/fail2ban-filters/wordpress-auth.conf new file mode 100644 index 0000000..158a036 --- /dev/null +++ b/templates/fail2ban-filters/wordpress-auth.conf @@ -0,0 +1,14 @@ +# WordPress login bruteforce filter +# Generated by Ansible + +[Definition] + +# Detect WordPress login failures +failregex = ^ .* "POST /wp-login\.php + ^ .* "POST /wp-admin/ + ^ .* "GET /wp-login\.php.*action=lostpassword + +ignoreregex = + +[Init] +maxlines = 1 diff --git a/templates/fail2ban-filters/wordpress-xmlrpc.conf b/templates/fail2ban-filters/wordpress-xmlrpc.conf new file mode 100644 index 0000000..a395d41 --- /dev/null +++ b/templates/fail2ban-filters/wordpress-xmlrpc.conf @@ -0,0 +1,13 @@ +# WordPress XMLRPC attack filter +# Generated by Ansible + +[Definition] + +# Detect XMLRPC attacks +failregex = ^ .* "POST /xmlrpc\.php + ^ .* "GET /xmlrpc\.php + +ignoreregex = + +[Init] +maxlines = 1 diff --git a/templates/fail2ban-filters/wordpress.conf b/templates/fail2ban-filters/wordpress.conf new file mode 100644 index 0000000..4db10f8 --- /dev/null +++ b/templates/fail2ban-filters/wordpress.conf @@ -0,0 +1,15 @@ +# WordPress generic attack filter +# Generated by Ansible + +[Definition] + +# Detect various WordPress attacks +failregex = ^ .* "(GET|POST) .*/wp-.*\.php.*" (4\d\d|5\d\d) + ^ .* "(GET|POST) .*" 40[134] + ^ .* "GET .*(/\..*|.*\.sql|.*\.zip|.*backup.*)" + ^ .* "(GET|POST) .*/wp-content/.*\.php" + +ignoreregex = + +[Init] +maxlines = 1 diff --git a/templates/fail2ban-wordpress.conf.j2 b/templates/fail2ban-wordpress.conf.j2 new file mode 100644 index 0000000..890263c --- /dev/null +++ b/templates/fail2ban-wordpress.conf.j2 @@ -0,0 +1,26 @@ +[wordpress] +enabled = true +port = http,https +filter = wordpress +logpath = {{ log_dir }}/nginx/*access*.log +maxretry = 3 +bantime = 3600 +findtime = 600 + +[wordpress-auth] +enabled = true +port = http,https +filter = wordpress-auth +logpath = {{ log_dir }}/nginx/*access*.log +maxretry = 5 +bantime = 1800 +findtime = 600 + +[wordpress-xmlrpc] +enabled = true +port = http,https +filter = wordpress-xmlrpc +logpath = {{ log_dir }}/nginx/*access*.log +maxretry = 3 +bantime = 3600 +findtime = 600 diff --git a/templates/fastcgi-cache.conf.j2 b/templates/fastcgi-cache.conf.j2 new file mode 100644 index 0000000..40e3e50 --- /dev/null +++ b/templates/fastcgi-cache.conf.j2 @@ -0,0 +1,67 @@ +# FastCGI Cache configuration for WordPress +# Include this in your WordPress server block + +# Cache zones +set $skip_cache 0; + +# POST requests and urls with a query string should always go to PHP +if ($request_method = POST) { + set $skip_cache 1; +} + +if ($query_string != "") { + set $skip_cache 1; +} + +# Don't cache uris containing the following segments +if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") { + set $skip_cache 1; +} + +# Don't use the cache for logged in users or recent commenters +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") { + set $skip_cache 1; +} + +# Cache configuration +location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:{{ php_fpm_socket }}; + + {% if nginx_fastcgi_cache_enabled | default(true) %} + # FastCGI cache settings + fastcgi_cache wordpress; + fastcgi_cache_valid 200 301 302 {{ nginx_cache_valid_time | default('60m') }}; + fastcgi_cache_valid 404 {{ nginx_cache_404_time | default('1m') }}; + fastcgi_cache_bypass $skip_cache; + fastcgi_no_cache $skip_cache; + fastcgi_cache_lock on; + fastcgi_cache_lock_timeout 5s; + fastcgi_cache_revalidate on; + fastcgi_cache_background_update on; + + # Cache headers + add_header X-FastCGI-Cache $upstream_cache_status; + {% endif %} + + # Security headers + {% if enable_ssl is defined and enable_ssl %} + fastcgi_param HTTPS on; + {% endif %} + + # Performance parameters + fastcgi_buffer_size {{ nginx_fastcgi_buffer_size | default('128k') }}; + fastcgi_buffers {{ nginx_fastcgi_buffers | default('4 256k') }}; + fastcgi_busy_buffers_size {{ nginx_fastcgi_busy_buffers_size | default('256k') }}; + fastcgi_temp_file_write_size {{ nginx_fastcgi_temp_file_write_size | default('256k') }}; + fastcgi_read_timeout {{ nginx_fastcgi_read_timeout | default('300') }}; + fastcgi_connect_timeout {{ nginx_fastcgi_connect_timeout | default('60') }}; + fastcgi_send_timeout {{ nginx_fastcgi_send_timeout | default('180') }}; +} + +# Cache purge endpoint (for logged in users) +location ~ /purge(/.*) { + allow 127.0.0.1; + deny all; + fastcgi_cache_purge wordpress "$scheme$request_method$host$1"; +} diff --git a/templates/logrotate.conf.j2 b/templates/logrotate.conf.j2 new file mode 100644 index 0000000..48a9675 --- /dev/null +++ b/templates/logrotate.conf.j2 @@ -0,0 +1,103 @@ +# Logrotate configuration for WordPress/LEMP stack +# Generated by Ansible + +# Nginx logs +/var/log/nginx/*.log { + daily + missingok + rotate {{ logrotate_nginx_rotate | default('52') }} + compress + delaycompress + notifempty + create 0644 www-data adm + sharedscripts + prerotate + if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ + run-parts /etc/logrotate.d/httpd-prerotate; \ + fi + endscript + postrotate + if [ -f /var/run/nginx.pid ]; then + kill -USR1 `cat /var/run/nginx.pid` + fi + endscript +} + +# PHP-FPM logs +/var/log/php{{ php_version }}-fpm/*.log { + weekly + missingok + rotate {{ logrotate_php_rotate | default('12') }} + compress + delaycompress + notifempty + create 0644 www-data www-data + postrotate + /usr/lib/php/php{{ php_version }}-fpm-reopenlogs + endscript +} + +# MySQL/MariaDB logs +/var/log/mysql/*.log { + daily + missingok + rotate {{ logrotate_mysql_rotate | default('30') }} + compress + delaycompress + notifempty + create 0640 mysql adm + sharedscripts + postrotate + if test -x /usr/bin/mysqladmin && \ + /usr/bin/mysqladmin ping &>/dev/null + then + /usr/bin/mysqladmin flush-logs + fi + endscript +} + +# WordPress debug logs +{{ wordpress_path }}/wp-content/debug.log { + weekly + missingok + rotate {{ logrotate_wordpress_rotate | default('4') }} + compress + delaycompress + notifempty + create 0644 www-data www-data + copytruncate +} + +# Redis logs (if enabled) +{% if enable_redis is defined and enable_redis %} +/var/log/redis/*.log { + weekly + missingok + rotate {{ logrotate_redis_rotate | default('12') }} + compress + delaycompress + notifempty + create 0640 redis redis + postrotate + if [ -f /var/run/redis/redis-server.pid ]; then + kill -USR1 `cat /var/run/redis/redis-server.pid` + fi + endscript +} +{% endif %} + +# Fail2ban logs (if enabled) +{% if enable_fail2ban is defined and enable_fail2ban %} +/var/log/fail2ban.log { + weekly + missingok + rotate {{ logrotate_fail2ban_rotate | default('12') }} + compress + delaycompress + notifempty + create 0600 root root + postrotate + /usr/bin/fail2ban-client flushlogs >/dev/null 2>&1 || true + endscript +} +{% endif %} diff --git a/templates/memcached.conf.j2 b/templates/memcached.conf.j2 new file mode 100644 index 0000000..1f12bb8 --- /dev/null +++ b/templates/memcached.conf.j2 @@ -0,0 +1,46 @@ +# Memcached configuration +# Generated by Ansible + +# Run memcached as a daemon +-d + +# Log memcached's output to /var/log/memcached +logfile /var/log/memcached.log + +# Be verbose +# -v + +# Be even more verbose (print client commands as well) +# -vv + +# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default +# Note that the daemon will grow to this size, but does not start out holding this much +# memory +-m {{ memcached_memory | default('64') }} + +# Default connection port is 11211 +-p 11211 + +# Run the daemon as root. The start-memcached will default to running as root if no +# -u command is present in this config file +-u memcache + +# Specify which IP address to listen on. The default is to listen on all IP addresses +# This parameter is one of the only security measures that memcached has, so make sure +# it's listening on a firewalled interface. +-l 127.0.0.1 + +# Limit the number of simultaneous incoming connections. The daemon default is 1024 +-c {{ memcached_connections | default('1024') }} + +# Lock down all paged memory. Consult with the README and homepage before you do this +# -k + +# Return error when memory is exhausted (rather than removing items) +# -M + +# Maximize core file limit +# -r + +# Use a pidfile +-P /var/run/memcached/memcached.pid diff --git a/templates/my.cnf.j2 b/templates/my.cnf.j2 new file mode 100644 index 0000000..a03cd58 --- /dev/null +++ b/templates/my.cnf.j2 @@ -0,0 +1,61 @@ +[client] +user = root +password = {{ mysql_root_password }} +socket = {{ mysql_socket }} + +[mysql] +default-character-set = utf8mb4 + +[mysqld] +# Basic Settings +user = mysql +pid-file = {{ mysql_pid_file }} +socket = {{ mysql_socket }} +port = 3306 +basedir = /usr +datadir = /var/lib/mysql +tmpdir = /tmp +lc-messages-dir = /usr/share/mysql +skip-external-locking + +# Character Set +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci +init-connect = 'SET NAMES utf8mb4' + +# Fine Tuning +key_buffer_size = 16M +max_allowed_packet = 64M +thread_stack = 192K +thread_cache_size = 8 + +# Query Cache Configuration +query_cache_type = 1 +query_cache_limit = 2M +query_cache_size = 32M + +# Logging and Replication +log_error = {{ mysql_log_error }} +expire_logs_days = 10 +max_binlog_size = 100M + +# InnoDB Settings +innodb_buffer_pool_size = {{ innodb_buffer_pool_size | default('256M') }} +innodb_log_file_size = 64M +innodb_file_per_table = 1 +innodb_open_files = 400 +innodb_io_capacity = 400 +innodb_flush_method = O_DIRECT + +# WordPress Optimization +max_connections = {{ max_connections | default('200') }} +tmp_table_size = 32M +max_heap_table_size = 32M +table_open_cache = 2000 +join_buffer_size = 256K +sort_buffer_size = 1M +read_buffer_size = 128K +read_rnd_buffer_size = 256K + +# Security +bind-address = 127.0.0.1 diff --git a/templates/nginx.conf.j2 b/templates/nginx.conf.j2 new file mode 100644 index 0000000..5601fb3 --- /dev/null +++ b/templates/nginx.conf.j2 @@ -0,0 +1,155 @@ +# Nginx main configuration for WordPress optimization +# Generated by Ansible + +user {{ nginx_user }}; +worker_processes {{ nginx_worker_processes | default('auto') }}; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +# Worker settings +worker_rlimit_nofile {{ nginx_worker_rlimit_nofile | default('65535') }}; + +events { + worker_connections {{ nginx_worker_connections | default('1024') }}; + use epoll; + multi_accept on; +} + +http { + # Basic Settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout {{ nginx_keepalive_timeout | default('65') }}; + types_hash_max_size 2048; + server_tokens off; + + # File upload settings + client_max_body_size {{ nginx_client_max_body_size | default('64M') }}; + client_body_buffer_size {{ nginx_client_body_buffer_size | default('128k') }}; + client_header_buffer_size {{ nginx_client_header_buffer_size | default('1k') }}; + large_client_header_buffers {{ nginx_large_client_header_buffers | default('2 1k') }}; + + # Timeouts + client_body_timeout {{ nginx_client_body_timeout | default('12') }}; + client_header_timeout {{ nginx_client_header_timeout | default('12') }}; + send_timeout {{ nginx_send_timeout | default('10') }}; + + # MIME types + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging Settings + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '$request_time $upstream_response_time'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + # Gzip Settings + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level {{ nginx_gzip_comp_level | default('6') }}; + gzip_types + application/atom+xml + application/geo+json + application/javascript + application/x-javascript + application/json + application/ld+json + application/manifest+json + application/rdf+xml + application/rss+xml + application/xhtml+xml + application/xml + font/eot + font/otf + font/ttf + image/svg+xml + text/css + text/javascript + text/plain + text/xml; + + # Brotli compression (if available) + {% if nginx_brotli_enabled | default(false) %} + brotli on; + brotli_comp_level 6; + brotli_types + application/atom+xml + application/javascript + application/json + application/rss+xml + application/vnd.ms-fontobject + application/x-font-opentype + application/x-font-truetype + application/x-font-ttf + application/x-javascript + application/xhtml+xml + application/xml + font/eot + font/opentype + font/otf + font/truetype + image/svg+xml + image/vnd.microsoft.icon + image/x-icon + image/x-win-bitmap + text/css + text/javascript + text/plain + text/xml; + {% endif %} + + # Rate Limiting Zones + limit_req_zone $binary_remote_addr zone=login:10m rate={{ nginx_login_rate_limit | default('1r/m') }}; + limit_req_zone $binary_remote_addr zone=admin:10m rate={{ nginx_admin_rate_limit | default('5r/m') }}; + limit_req_zone $binary_remote_addr zone=api:10m rate={{ nginx_api_rate_limit | default('10r/m') }}; + limit_req_zone $binary_remote_addr zone=search:10m rate={{ nginx_search_rate_limit | default('3r/m') }}; + + # Connection limiting + limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m; + limit_conn_zone $server_name zone=conn_limit_per_server:10m; + + # FastCGI Cache Settings + {% if nginx_fastcgi_cache_enabled | default(true) %} + fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=wordpress:{{ nginx_fastcgi_cache_size | default('100m') }} + max_size={{ nginx_fastcgi_cache_max_size | default('1g') }} + inactive={{ nginx_fastcgi_cache_inactive | default('60m') }} + use_temp_path=off; + fastcgi_cache_key "$scheme$request_method$host$request_uri"; + {% endif %} + + # SSL Settings + {% if enable_ssl is defined and enable_ssl %} + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_session_cache shared:SSL:50m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + # OCSP stapling + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + {% endif %} + + # Security Headers (include in server blocks) + map $sent_http_content_type $expires { + default off; + text/html epoch; + text/css max; + application/javascript max; + ~image/ max; + ~font/ max; + } + + # Include additional configurations + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/templates/performance-monitor.sh.j2 b/templates/performance-monitor.sh.j2 new file mode 100644 index 0000000..8a307d3 --- /dev/null +++ b/templates/performance-monitor.sh.j2 @@ -0,0 +1,241 @@ +#!/bin/bash +# WordPress Performance Monitoring Script +# Generated by Ansible + +set -e + +# Configuration +WORDPRESS_PATH="{{ wordpress_path }}" +LOG_FILE="/var/log/wordpress-performance.log" +ALERT_EMAIL="{{ performance_alert_email | default('admin@localhost') }}" +THRESHOLDS_FILE="/etc/wordpress-performance-thresholds.conf" + +# Default thresholds +CPU_THRESHOLD={{ cpu_threshold | default('80') }} +MEMORY_THRESHOLD={{ memory_threshold | default('85') }} +DISK_THRESHOLD={{ disk_threshold | default('90') }} +LOAD_THRESHOLD={{ load_threshold | default('5.0') }} +RESPONSE_TIME_THRESHOLD={{ response_time_threshold | default('2.0') }} + +# Load custom thresholds if available +if [[ -f "$THRESHOLDS_FILE" ]]; then + source "$THRESHOLDS_FILE" +fi + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Functions +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" | tee -a "$LOG_FILE" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_FILE" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" +} + +send_alert() { + local subject="$1" + local message="$2" + + if command -v mail >/dev/null 2>&1; then + echo "$message" | mail -s "$subject" "$ALERT_EMAIL" + fi + + # Log the alert + log_error "ALERT: $subject - $message" +} + +check_system_resources() { + log_info "Checking system resources..." + + # CPU usage + CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | awk -F'%' '{print $1}') + if (( $(echo "$CPU_USAGE > $CPU_THRESHOLD" | bc -l) )); then + send_alert "High CPU Usage" "CPU usage is ${CPU_USAGE}% (threshold: ${CPU_THRESHOLD}%)" + fi + + # Memory usage + MEMORY_USAGE=$(free | grep Mem | awk '{printf("%.1f", ($3/$2) * 100.0)}') + if (( $(echo "$MEMORY_USAGE > $MEMORY_THRESHOLD" | bc -l) )); then + send_alert "High Memory Usage" "Memory usage is ${MEMORY_USAGE}% (threshold: ${MEMORY_THRESHOLD}%)" + fi + + # Disk usage + DISK_USAGE=$(df {{ wordpress_path }} | tail -1 | awk '{print $5}' | sed 's/%//') + if (( DISK_USAGE > DISK_THRESHOLD )); then + send_alert "High Disk Usage" "Disk usage is ${DISK_USAGE}% (threshold: ${DISK_THRESHOLD}%)" + fi + + # Load average + LOAD_AVG=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//') + if (( $(echo "$LOAD_AVG > $LOAD_THRESHOLD" | bc -l) )); then + send_alert "High Load Average" "Load average is ${LOAD_AVG} (threshold: ${LOAD_THRESHOLD})" + fi + + log_info "System resources: CPU: ${CPU_USAGE}%, Memory: ${MEMORY_USAGE}%, Disk: ${DISK_USAGE}%, Load: ${LOAD_AVG}" +} + +check_services() { + log_info "Checking services status..." + + # Check Nginx + if ! systemctl is-active --quiet nginx; then + send_alert "Nginx Service Down" "Nginx service is not running" + fi + + # Check PHP-FPM + if ! systemctl is-active --quiet php{{ php_version }}-fpm; then + send_alert "PHP-FPM Service Down" "PHP-FPM service is not running" + fi + + # Check MySQL/MariaDB + if ! systemctl is-active --quiet mysql && ! systemctl is-active --quiet mariadb; then + send_alert "Database Service Down" "MySQL/MariaDB service is not running" + fi + + # Check Redis (if enabled) + {% if enable_redis is defined and enable_redis %} + if ! systemctl is-active --quiet redis-server; then + log_warn "Redis service is not running" + fi + {% endif %} + + log_info "All critical services are running" +} + +check_website_performance() { + log_info "Checking website performance..." + + # Test response time + RESPONSE_TIME=$(curl -o /dev/null -s -w '%{time_total}\n' http://localhost/ || echo "999") + + if (( $(echo "$RESPONSE_TIME > $RESPONSE_TIME_THRESHOLD" | bc -l) )); then + send_alert "Slow Website Response" "Website response time is ${RESPONSE_TIME}s (threshold: ${RESPONSE_TIME_THRESHOLD}s)" + fi + + # Test HTTP status + HTTP_STATUS=$(curl -o /dev/null -s -w '%{http_code}\n' http://localhost/ || echo "000") + + if [[ "$HTTP_STATUS" != "200" ]]; then + send_alert "Website HTTP Error" "Website returned HTTP status $HTTP_STATUS" + fi + + log_info "Website performance: Response time: ${RESPONSE_TIME}s, HTTP status: $HTTP_STATUS" +} + +check_database_performance() { + log_info "Checking database performance..." + + # Check MySQL connections + if command -v mysql >/dev/null 2>&1; then + MYSQL_CONNECTIONS=$(mysql -e "SHOW STATUS LIKE 'Threads_connected';" | tail -1 | awk '{print $2}' 2>/dev/null || echo "0") + MYSQL_MAX_CONNECTIONS=$(mysql -e "SHOW VARIABLES LIKE 'max_connections';" | tail -1 | awk '{print $2}' 2>/dev/null || echo "100") + + CONNECTION_PERCENTAGE=$(( (MYSQL_CONNECTIONS * 100) / MYSQL_MAX_CONNECTIONS )) + + if (( CONNECTION_PERCENTAGE > 80 )); then + send_alert "High Database Connections" "MySQL connections: ${MYSQL_CONNECTIONS}/${MYSQL_MAX_CONNECTIONS} (${CONNECTION_PERCENTAGE}%)" + fi + + log_info "Database connections: ${MYSQL_CONNECTIONS}/${MYSQL_MAX_CONNECTIONS} (${CONNECTION_PERCENTAGE}%)" + fi +} + +check_log_errors() { + log_info "Checking for recent errors..." + + # Check Nginx error log + NGINX_ERRORS=$(tail -n 100 /var/log/nginx/error.log | grep -c "$(date '+%Y/%m/%d')" || echo "0") + if (( NGINX_ERRORS > 10 )); then + log_warn "Found $NGINX_ERRORS Nginx errors today" + fi + + # Check PHP error log + if [[ -f /var/log/php{{ php_version }}-fpm.log ]]; then + PHP_ERRORS=$(tail -n 100 /var/log/php{{ php_version }}-fpm.log | grep -c "$(date '+%d-%b-%Y')" || echo "0") + if (( PHP_ERRORS > 10 )); then + log_warn "Found $PHP_ERRORS PHP-FPM errors today" + fi + fi + + # Check WordPress debug log + if [[ -f "$WORDPRESS_PATH/wp-content/debug.log" ]]; then + WP_ERRORS=$(tail -n 100 "$WORDPRESS_PATH/wp-content/debug.log" | grep -c "$(date '+%d-%b-%Y')" || echo "0") + if (( WP_ERRORS > 5 )); then + log_warn "Found $WP_ERRORS WordPress errors today" + fi + fi +} + +generate_performance_report() { + log_info "Generating performance report..." + + cat > /tmp/wordpress-performance-report.txt </dev/null | awk '{print $1}' || echo "N/A") +{% endif %} +{% if enable_redis is defined and enable_redis %} +- Redis Memory Usage: $(redis-cli info memory | grep used_memory_human | cut -d: -f2 | tr -d '\r' || echo "N/A") +{% endif %} + +EOF + + # Optionally email the report + if [[ "${SEND_DAILY_REPORT:-false}" == "true" ]]; then + mail -s "WordPress Performance Report - $(date +%Y-%m-%d)" "$ALERT_EMAIL" < /tmp/wordpress-performance-report.txt + fi +} + +main() { + log_info "Starting WordPress performance monitoring..." + + check_system_resources + check_services + check_website_performance + check_database_performance + check_log_errors + generate_performance_report + + log_info "Performance monitoring completed" +} + +# Run the monitoring +main "$@" diff --git a/templates/php-performance.ini.j2 b/templates/php-performance.ini.j2 new file mode 100644 index 0000000..e69de29 diff --git a/templates/php.ini.j2 b/templates/php.ini.j2 new file mode 100644 index 0000000..bd389dc --- /dev/null +++ b/templates/php.ini.j2 @@ -0,0 +1,254 @@ +# PHP configuration optimizations for WordPress +# Generated by Ansible + +[PHP] +; Engine Settings +engine = On +short_open_tag = Off +precision = 14 +output_buffering = 4096 +zlib.output_compression = Off +implicit_flush = Off +unserialize_callback_func = +serialize_precision = -1 +disable_functions = {{ php_disable_functions | default('exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source') }} +disable_classes = +zend.enable_gc = On +zend.exception_ignore_args = On + +; Resource Limits +max_execution_time = {{ php_max_execution_time | default('300') }} +max_input_time = {{ php_max_input_time | default('300') }} +max_input_nesting_level = {{ php_max_input_nesting_level | default('64') }} +max_input_vars = {{ php_max_input_vars | default('3000') }} +memory_limit = {{ php_memory_limit | default('256M') }} + +; Error handling and logging +error_reporting = {{ php_error_reporting | default('E_ALL & ~E_DEPRECATED & ~E_STRICT') }} +display_errors = {{ php_display_errors | default('Off') }} +display_startup_errors = {{ php_display_startup_errors | default('Off') }} +log_errors = {{ php_log_errors | default('On') }} +log_errors_max_len = {{ php_log_errors_max_len | default('1024') }} +ignore_repeated_errors = {{ php_ignore_repeated_errors | default('Off') }} +ignore_repeated_source = {{ php_ignore_repeated_source | default('Off') }} +report_memleaks = {{ php_report_memleaks | default('On') }} +track_errors = {{ php_track_errors | default('Off') }} +error_log = {{ php_error_log | default('/var/log/php_errors.log') }} + +; Data Handling +variables_order = "GPCS" +request_order = "GP" +register_argc_argv = Off +auto_globals_jit = On +post_max_size = {{ php_post_max_size | default('64M') }} +auto_prepend_file = +auto_append_file = +default_mimetype = "text/html" +default_charset = "UTF-8" + +; File Uploads +file_uploads = {{ php_file_uploads | default('On') }} +upload_tmp_dir = {{ php_upload_tmp_dir | default('/tmp') }} +upload_max_filesize = {{ php_upload_max_filesize | default('64M') }} +max_file_uploads = {{ php_max_file_uploads | default('20') }} + +; Fopen wrappers +allow_url_fopen = {{ php_allow_url_fopen | default('On') }} +allow_url_include = {{ php_allow_url_include | default('Off') }} +auto_detect_line_endings = Off + +; Dynamic Extensions +extension_dir = "/usr/lib/php/{{ php_version }}" + +; Module Settings + +[CLI Server] +cli_server.color = On + +[Date] +date.timezone = {{ php_timezone | default('UTC') }} + +[filter] +filter.default = unsafe_raw +filter.default_flags = + +[iconv] +iconv.input_encoding = +iconv.internal_encoding = +iconv.output_encoding = + +[imap] +imap.enable_insecure_rsh = 0 + +[intl] +intl.default_locale = +intl.error_level = 0 +intl.use_exceptions = 0 + +[sqlite3] +sqlite3.extension_dir = + +[Pcre] +pcre.backtrack_limit = {{ php_pcre_backtrack_limit | default('1000000') }} +pcre.recursion_limit = {{ php_pcre_recursion_limit | default('100000') }} +pcre.jit = {{ php_pcre_jit | default('1') }} + +[Pdo] +pdo_mysql.cache_size = 2000 +pdo_mysql.default_socket = + +[Pdo_mysql] +pdo_mysql.default_socket = + +[Phar] +phar.readonly = {{ php_phar_readonly | default('On') }} +phar.require_hash = {{ php_phar_require_hash | default('On') }} + +[mail function] +SMTP = {{ php_smtp_server | default('localhost') }} +smtp_port = {{ php_smtp_port | default('25') }} +sendmail_path = {{ php_sendmail_path | default('/usr/sbin/sendmail -t -i') }} +mail.add_x_header = {{ php_mail_add_x_header | default('Off') }} + +[ODBC] +odbc.allow_persistent = On +odbc.check_persistent = On +odbc.max_persistent = -1 +odbc.max_links = -1 +odbc.defaultlrl = 4096 +odbc.defaultbinmode = 1 + +[MySQLi] +mysqli.max_persistent = -1 +mysqli.allow_persistent = On +mysqli.max_links = -1 +mysqli.cache_size = 2000 +mysqli.default_port = 3306 +mysqli.default_socket = +mysqli.default_host = +mysqli.default_user = +mysqli.default_pw = +mysqli.reconnect = Off + +[mysqlnd] +mysqlnd.collect_statistics = On +mysqlnd.collect_memory_statistics = Off + +[OPcache] +{% if enable_opcache | default(true) %} +opcache.enable = {{ opcache_enable | default('1') }} +opcache.enable_cli = {{ opcache_enable_cli | default('0') }} +opcache.memory_consumption = {{ opcache_memory_consumption | default('256') }} +opcache.interned_strings_buffer = {{ opcache_interned_strings_buffer | default('16') }} +opcache.max_accelerated_files = {{ opcache_max_accelerated_files | default('10000') }} +opcache.max_wasted_percentage = {{ opcache_max_wasted_percentage | default('5') }} +opcache.use_cwd = {{ opcache_use_cwd | default('1') }} +opcache.validate_timestamps = {{ opcache_validate_timestamps | default('1') }} +opcache.revalidate_freq = {{ opcache_revalidate_freq | default('2') }} +opcache.revalidate_path = {{ opcache_revalidate_path | default('0') }} +opcache.save_comments = {{ opcache_save_comments | default('1') }} +opcache.enable_file_override = {{ opcache_enable_file_override | default('0') }} +opcache.optimization_level = {{ opcache_optimization_level | default('0x7FFFBFFF') }} +opcache.dups_fix = {{ opcache_dups_fix | default('0') }} +opcache.blacklist_filename = {{ opcache_blacklist_filename | default('') }} +opcache.max_file_size = {{ opcache_max_file_size | default('0') }} +opcache.consistency_checks = {{ opcache_consistency_checks | default('0') }} +opcache.force_restart_timeout = {{ opcache_force_restart_timeout | default('180') }} +opcache.error_log = {{ opcache_error_log | default('') }} +opcache.log_verbosity_level = {{ opcache_log_verbosity_level | default('1') }} +opcache.preferred_memory_model = {{ opcache_preferred_memory_model | default('') }} +opcache.protect_memory = {{ opcache_protect_memory | default('0') }} +opcache.restrict_api = {{ opcache_restrict_api | default('') }} +opcache.mmap_base = {{ opcache_mmap_base | default('') }} +opcache.file_cache = {{ opcache_file_cache | default('') }} +opcache.file_cache_only = {{ opcache_file_cache_only | default('0') }} +opcache.file_cache_consistency_checks = {{ opcache_file_cache_consistency_checks | default('1') }} +opcache.file_cache_fallback = {{ opcache_file_cache_fallback | default('1') }} +opcache.huge_code_pages = {{ opcache_huge_code_pages | default('0') }} +opcache.validate_permission = {{ opcache_validate_permission | default('0') }} +opcache.validate_root = {{ opcache_validate_root | default('0') }} +opcache.preload = {{ opcache_preload | default('') }} +opcache.preload_user = {{ opcache_preload_user | default('') }} +{% endif %} + +[curl] +curl.cainfo = + +[openssl] +openssl.cafile = +openssl.capath = + +[ffi] +ffi.enable = "preload" + +[Session] +session.save_handler = {{ php_session_save_handler | default('files') }} +session.save_path = {{ php_session_save_path | default('/var/lib/php/sessions') }} +session.use_strict_mode = {{ php_session_use_strict_mode | default('0') }} +session.use_cookies = {{ php_session_use_cookies | default('1') }} +session.use_only_cookies = {{ php_session_use_only_cookies | default('1') }} +session.name = {{ php_session_name | default('PHPSESSID') }} +session.auto_start = {{ php_session_auto_start | default('0') }} +session.cookie_lifetime = {{ php_session_cookie_lifetime | default('0') }} +session.cookie_path = {{ php_session_cookie_path | default('/') }} +session.cookie_domain = {{ php_session_cookie_domain | default('') }} +session.cookie_httponly = {{ php_session_cookie_httponly | default('1') }} +session.cookie_samesite = {{ php_session_cookie_samesite | default('') }} +session.serialize_handler = {{ php_session_serialize_handler | default('php') }} +session.gc_probability = {{ php_session_gc_probability | default('1') }} +session.gc_divisor = {{ php_session_gc_divisor | default('1000') }} +session.gc_maxlifetime = {{ php_session_gc_maxlifetime | default('1440') }} +session.referer_check = {{ php_session_referer_check | default('') }} +session.cache_limiter = {{ php_session_cache_limiter | default('nocache') }} +session.cache_expire = {{ php_session_cache_expire | default('180') }} +session.use_trans_sid = {{ php_session_use_trans_sid | default('0') }} +session.sid_length = {{ php_session_sid_length | default('26') }} +session.trans_sid_tags = {{ php_session_trans_sid_tags | default('a=href,area=href,frame=src,form=') }} +session.sid_bits_per_character = {{ php_session_sid_bits_per_character | default('5') }} + +[Assertion] +zend.assertions = {{ php_zend_assertions | default('-1') }} +assert.active = {{ php_assert_active | default('On') }} +assert.exception = {{ php_assert_exception | default('On') }} + +[COM] +com.typelib_file = +com.allow_dcom = Off +com.autoregister_typelib = Off +com.autoregister_casesensitive = Off +com.autoregister_verbose = Off + +[mbstring] +mbstring.language = {{ php_mbstring_language | default('neutral') }} +mbstring.internal_encoding = {{ php_mbstring_internal_encoding | default('') }} +mbstring.http_input = {{ php_mbstring_http_input | default('') }} +mbstring.http_output = {{ php_mbstring_http_output | default('') }} +mbstring.encoding_translation = {{ php_mbstring_encoding_translation | default('Off') }} +mbstring.detect_order = {{ php_mbstring_detect_order | default('auto') }} +mbstring.substitute_character = {{ php_mbstring_substitute_character | default('none') }} + +[gd] +gd.jpeg_ignore_warning = {{ php_gd_jpeg_ignore_warning | default('1') }} + +[exif] +exif.encode_unicode = {{ php_exif_encode_unicode | default('ISO-8859-15') }} +exif.decode_unicode_motorola = {{ php_exif_decode_unicode_motorola | default('UCS-2BE') }} +exif.decode_unicode_intel = {{ php_exif_decode_unicode_intel | default('UCS-2LE') }} +exif.encode_jis = {{ php_exif_encode_jis | default('') }} +exif.decode_jis_motorola = {{ php_exif_decode_jis_motorola | default('JIS') }} +exif.decode_jis_intel = {{ php_exif_decode_jis_intel | default('JIS') }} + +[Tidy] +tidy.clean_output = {{ php_tidy_clean_output | default('Off') }} + +[soap] +soap.wsdl_cache_enabled = {{ php_soap_wsdl_cache_enabled | default('1') }} +soap.wsdl_cache_dir = {{ php_soap_wsdl_cache_dir | default('/tmp') }} +soap.wsdl_cache_ttl = {{ php_soap_wsdl_cache_ttl | default('86400') }} +soap.wsdl_cache_limit = {{ php_soap_wsdl_cache_limit | default('5') }} + +[sysvshm] +sysvshm.init_mem = {{ php_sysvshm_init_mem | default('10000') }} + +[ldap] +ldap.max_links = {{ php_ldap_max_links | default('-1') }} diff --git a/templates/redis.conf.j2 b/templates/redis.conf.j2 new file mode 100644 index 0000000..4bea24d --- /dev/null +++ b/templates/redis.conf.j2 @@ -0,0 +1,58 @@ +# Redis configuration file for WordPress +# Generated by Ansible + +# Network +bind 127.0.0.1 +port 6379 +tcp-backlog 511 +timeout 0 +tcp-keepalive 300 + +# General +daemonize yes +supervised no +pidfile /var/run/redis/redis-server.pid +loglevel notice +logfile /var/log/redis/redis-server.log +databases 16 + +# Memory Management +maxmemory {{ redis_maxmemory | default('128mb') }} +maxmemory-policy allkeys-lru +maxmemory-samples 5 + +# Persistence +save 900 1 +save 300 10 +save 60 10000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir /var/lib/redis + +# Security +requirepass {{ redis_password | default('') }} + +# Slow Log +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +# Advanced config +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 +list-max-ziplist-size -2 +list-compress-depth 0 +set-max-intset-entries 512 +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 +hll-sparse-max-bytes 3000 + +# Client output buffer limits +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# WordPress optimizations +hz 10 +dynamic-hz yes diff --git a/templates/security.conf.j2 b/templates/security.conf.j2 new file mode 100644 index 0000000..654c7cb --- /dev/null +++ b/templates/security.conf.j2 @@ -0,0 +1,40 @@ +# Security headers configuration for Nginx +# Generated by Ansible + +# Security Headers +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Content-Type-Options "nosniff" always; +add_header X-XSS-Protection "1; mode=block" always; +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; + +# Content Security Policy (CSP) +add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wordpress.org *.wp.com *.gravatar.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data: *.wordpress.org *.wp.com *.gravatar.com; connect-src 'self'; frame-src 'self' *.youtube.com *.vimeo.com; object-src 'none';" always; + +# HSTS (HTTP Strict Transport Security) +{% if enable_ssl is defined and enable_ssl %} +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; +{% endif %} + +# Hide Nginx version +server_tokens off; + +# Limit request size +client_max_body_size 64M; +client_body_buffer_size 128k; + +# Rate limiting zones (defined in main nginx.conf) +# limit_req_zone $binary_remote_addr zone=login:10m rate=1r/m; +# limit_req_zone $binary_remote_addr zone=admin:10m rate=5r/m; +# limit_req_zone $binary_remote_addr zone=api:10m rate=10r/m; + +# Security-related timeouts +client_body_timeout 10s; +client_header_timeout 10s; +keepalive_timeout 65s; +send_timeout 10s; + +# Buffer overflow protection +client_body_buffer_size 1k; +client_header_buffer_size 1k; +large_client_header_buffers 2 1k; diff --git a/templates/ssl-renewal-notify.sh.j2 b/templates/ssl-renewal-notify.sh.j2 new file mode 100644 index 0000000..de1ed17 --- /dev/null +++ b/templates/ssl-renewal-notify.sh.j2 @@ -0,0 +1,70 @@ +#!/bin/bash +# SSL Certificate Renewal Notification Script +# Generated by Ansible + +set -e + +DOMAIN="{{ domain_name }}" +EMAIL="{{ letsencrypt_email }}" +LOG_FILE="/var/log/ssl-renewal.log" +WEBHOOK_URL="{{ slack_webhook_url | default('') }}" + +# Log function +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +# Function to send notification +send_notification() { + local status="$1" + local message="$2" + + if [[ -n "$WEBHOOK_URL" ]]; then + curl -X POST -H 'Content-type: application/json' \ + --data "{\"text\":\"SSL Renewal $status for $DOMAIN: $message\"}" \ + "$WEBHOOK_URL" || true + fi + + # Send email if available + if command -v mail >/dev/null 2>&1; then + echo "$message" | mail -s "SSL Renewal $status - $DOMAIN" "$EMAIL" || true + fi +} + +log "Starting SSL certificate renewal check for $DOMAIN" + +# Check certificate expiry +EXPIRY_DATE=$(openssl x509 -in {{ ssl_certificate_path }} -noout -enddate 2>/dev/null | cut -d= -f2) +EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s) +CURRENT_EPOCH=$(date +%s) +DAYS_UNTIL_EXPIRY=$(( (EXPIRY_EPOCH - CURRENT_EPOCH) / 86400 )) + +log "Certificate expires in $DAYS_UNTIL_EXPIRY days" + +# Run certbot renewal +if certbot renew --quiet --no-self-upgrade; then + NEW_EXPIRY_DATE=$(openssl x509 -in {{ ssl_certificate_path }} -noout -enddate 2>/dev/null | cut -d= -f2) + NEW_EXPIRY_EPOCH=$(date -d "$NEW_EXPIRY_DATE" +%s) + NEW_DAYS_UNTIL_EXPIRY=$(( (NEW_EXPIRY_EPOCH - CURRENT_EPOCH) / 86400 )) + + if [[ $NEW_DAYS_UNTIL_EXPIRY -gt $DAYS_UNTIL_EXPIRY ]]; then + log "Certificate renewed successfully. New expiry: $NEW_EXPIRY_DATE" + send_notification "SUCCESS" "Certificate renewed successfully. Valid until $NEW_EXPIRY_DATE" + + # Reload Nginx + if systemctl reload nginx; then + log "Nginx reloaded successfully" + else + log "ERROR: Failed to reload Nginx" + send_notification "WARNING" "Certificate renewed but Nginx reload failed" + fi + else + log "Certificate was not renewed (not due for renewal)" + fi +else + log "ERROR: Certificate renewal failed" + send_notification "FAILED" "Certificate renewal failed. Please check server logs." + exit 1 +fi + +log "SSL renewal check completed" diff --git a/templates/sysctl.conf.j2 b/templates/sysctl.conf.j2 new file mode 100644 index 0000000..dc8a646 --- /dev/null +++ b/templates/sysctl.conf.j2 @@ -0,0 +1,41 @@ +# System optimization configuration for WordPress/LEMP stack +# Generated by Ansible + +# Network optimizations +net.core.rmem_default = {{ net_core_rmem_default | default('262144') }} +net.core.rmem_max = {{ net_core_rmem_max | default('16777216') }} +net.core.wmem_default = {{ net_core_wmem_default | default('262144') }} +net.core.wmem_max = {{ net_core_wmem_max | default('16777216') }} +net.core.netdev_max_backlog = {{ net_core_netdev_max_backlog | default('5000') }} +net.core.somaxconn = {{ net_core_somaxconn | default('1024') }} + +# TCP optimizations +net.ipv4.tcp_window_scaling = {{ net_ipv4_tcp_window_scaling | default('1') }} +net.ipv4.tcp_congestion_control = {{ net_ipv4_tcp_congestion_control | default('bbr') }} +net.ipv4.tcp_rmem = {{ net_ipv4_tcp_rmem | default('4096 87380 16777216') }} +net.ipv4.tcp_wmem = {{ net_ipv4_tcp_wmem | default('4096 65536 16777216') }} +net.ipv4.tcp_max_syn_backlog = {{ net_ipv4_tcp_max_syn_backlog | default('8192') }} +net.ipv4.tcp_slow_start_after_idle = {{ net_ipv4_tcp_slow_start_after_idle | default('0') }} +net.ipv4.tcp_tw_reuse = {{ net_ipv4_tcp_tw_reuse | default('1') }} + +# Memory management +vm.swappiness = {{ vm_swappiness | default('10') }} +vm.dirty_ratio = {{ vm_dirty_ratio | default('15') }} +vm.dirty_background_ratio = {{ vm_dirty_background_ratio | default('5') }} +vm.vfs_cache_pressure = {{ vm_vfs_cache_pressure | default('50') }} +vm.min_free_kbytes = {{ vm_min_free_kbytes | default('65536') }} + +# File system optimizations +fs.file-max = {{ fs_file_max | default('1000000') }} +fs.inotify.max_user_watches = {{ fs_inotify_max_user_watches | default('524288') }} + +# Security optimizations +net.ipv4.conf.all.rp_filter = {{ net_ipv4_conf_all_rp_filter | default('1') }} +net.ipv4.conf.default.rp_filter = {{ net_ipv4_conf_default_rp_filter | default('1') }} +net.ipv4.icmp_echo_ignore_broadcasts = {{ net_ipv4_icmp_echo_ignore_broadcasts | default('1') }} +net.ipv4.icmp_ignore_bogus_error_responses = {{ net_ipv4_icmp_ignore_bogus_error_responses | default('1') }} +net.ipv4.conf.all.log_martians = {{ net_ipv4_conf_all_log_martians | default('1') }} +net.ipv4.conf.default.log_martians = {{ net_ipv4_conf_default_log_martians | default('1') }} + +# Process limits +kernel.pid_max = {{ kernel_pid_max | default('4194304') }} diff --git a/templates/wordpress-backup.sh.j2 b/templates/wordpress-backup.sh.j2 new file mode 100644 index 0000000..918d237 --- /dev/null +++ b/templates/wordpress-backup.sh.j2 @@ -0,0 +1,49 @@ +#!/bin/bash +# WordPress Backup Script +# Generated by Ansible + +set -e + +# Configuration +BACKUP_DIR="{{ backup_destination }}" +WP_PATH="{{ wordpress_path }}" +DB_NAME="{{ wordpress_db_name }}" +DB_USER="{{ wordpress_db_user }}" +DB_PASS="{{ wordpress_db_password }}" +RETENTION_DAYS="{{ backup_retention_days }}" +DATE=$(date +%Y%m%d_%H%M%S) + +# Create backup directories +mkdir -p "$BACKUP_DIR/database" "$BACKUP_DIR/files" + +# Log function +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$BACKUP_DIR/backup.log" +} + +log "Starting WordPress backup..." + +# Backup WordPress files +log "Backing up WordPress files..." +tar -czf "$BACKUP_DIR/files/wordpress_files_$DATE.tar.gz" -C "$(dirname $WP_PATH)" "$(basename $WP_PATH)" + +# Backup database +log "Backing up database..." +mysqldump -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" | gzip > "$BACKUP_DIR/database/wordpress_db_$DATE.sql.gz" + +# Cleanup old backups +log "Cleaning up old backups (older than $RETENTION_DAYS days)..." +find "$BACKUP_DIR/files" -name "wordpress_files_*.tar.gz" -mtime +$RETENTION_DAYS -delete +find "$BACKUP_DIR/database" -name "wordpress_db_*.sql.gz" -mtime +$RETENTION_DAYS -delete + +# Calculate backup sizes +FILES_SIZE=$(du -sh "$BACKUP_DIR/files/wordpress_files_$DATE.tar.gz" | cut -f1) +DB_SIZE=$(du -sh "$BACKUP_DIR/database/wordpress_db_$DATE.sql.gz" | cut -f1) + +log "Backup completed successfully!" +log "Files backup size: $FILES_SIZE" +log "Database backup size: $DB_SIZE" +log "Backup location: $BACKUP_DIR" + +# Optional: Send notification (requires mail setup) +# echo "WordPress backup completed on $(hostname) at $(date)" | mail -s "WordPress Backup Success" admin@example.com diff --git a/templates/wordpress-cron.sh.j2 b/templates/wordpress-cron.sh.j2 new file mode 100644 index 0000000..e288b80 --- /dev/null +++ b/templates/wordpress-cron.sh.j2 @@ -0,0 +1,64 @@ +#!/bin/bash +# WordPress Cron Optimization Script +# Generated by Ansible + +set -e + +# Configuration +WORDPRESS_PATH="{{ wordpress_path }}" +WP_CLI_PATH="/usr/local/bin/wp" +LOG_FILE="/var/log/wordpress-cron.log" +LOCK_FILE="/tmp/wordpress-cron.lock" + +# Function to log messages +log_message() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE" +} + +# Check if another cron is running +if [[ -f "$LOCK_FILE" ]]; then + if ps -p "$(cat $LOCK_FILE)" > /dev/null 2>&1; then + log_message "WordPress cron is already running (PID: $(cat $LOCK_FILE))" + exit 0 + else + rm -f "$LOCK_FILE" + fi +fi + +# Create lock file +echo $$ > "$LOCK_FILE" + +# Function to cleanup on exit +cleanup() { + rm -f "$LOCK_FILE" +} +trap cleanup EXIT + +log_message "Starting WordPress cron execution" + +# Change to WordPress directory +cd "$WORDPRESS_PATH" + +# Execute WordPress cron +if [[ -x "$WP_CLI_PATH" ]]; then + # Use WP-CLI for better control + if sudo -u {{ nginx_user }} "$WP_CLI_PATH" cron event run --due-now --quiet; then + log_message "WordPress cron executed successfully via WP-CLI" + else + log_message "ERROR: WordPress cron execution failed via WP-CLI" + exit 1 + fi +else + # Fallback to HTTP request + if curl -s "{{ wordpress_url }}/wp-cron.php" > /dev/null; then + log_message "WordPress cron executed successfully via HTTP" + else + log_message "ERROR: WordPress cron execution failed via HTTP" + exit 1 + fi +fi + +# Clean up old cron logs (keep last 30 days) +find /var/log/ -name "wordpress-cron.log*" -mtime +30 -delete 2>/dev/null || true + +log_message "WordPress cron execution completed" diff --git a/templates/wordpress-multisite.nginx.j2 b/templates/wordpress-multisite.nginx.j2 new file mode 100644 index 0000000..1368412 --- /dev/null +++ b/templates/wordpress-multisite.nginx.j2 @@ -0,0 +1,123 @@ +{% if multisite_type == 'subdomain' %} +# WordPress Multisite - Subdomain Configuration +map $http_host $blogid { + default -999; + + # Primary site + ~^{{ domain_name | regex_escape }}$ 0; + + # Subdomains + ~^([a-zA-Z0-9-]+)\.{{ domain_name | regex_escape }}$ -999; +} +{% endif %} + +server { + listen 80; + server_name {{ domain_name }}{% if multisite_type == 'subdomain' %} *.{{ domain_name }}{% endif %}; + + {% if enable_ssl is defined and enable_ssl %} + # Redirect HTTP to HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {{ domain_name }}{% if multisite_type == 'subdomain' %} *.{{ domain_name }}{% endif %}; + + # SSL Configuration + ssl_certificate {{ ssl_certificate_path }}; + ssl_certificate_key {{ ssl_certificate_key_path }}; + include /etc/nginx/snippets/ssl-params.conf; + {% endif %} + + root {{ wordpress_path }}; + index index.php index.html index.htm; + + # WordPress Multisite specific rules + {% if multisite_type == 'subdirectory' %} + # Subdirectory multisite + if (!-e $request_filename) { + rewrite /wp-admin$ $scheme://$host$uri/ permanent; + rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last; + rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last; + } + {% endif %} + + location / { + try_files $uri $uri/ /index.php?$args; + } + + # WordPress uploads for multisite + {% if multisite_type == 'subdirectory' %} + location ~ ^/[_0-9a-zA-Z-]+/files/(.+) { + try_files /wp-content/blogs.dir/$blogid/files/$1 /wp-includes/ms-files.php?file=$1; + access_log off; log_not_found off; expires max; + } + {% endif %} + + # Handle uploads for subdomain multisite + {% if multisite_type == 'subdomain' %} + location ~ ^/files/(.+) { + try_files /wp-content/blogs.dir/$blogid/files/$1 /wp-includes/ms-files.php?file=$1; + access_log off; log_not_found off; expires max; + } + {% endif %} + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:{{ php_fpm_socket }}; + {% if enable_ssl is defined and enable_ssl %} + fastcgi_param HTTPS on; + {% endif %} + + # WordPress multisite environment variables + fastcgi_param WP_MULTISITE 1; + {% if multisite_type == 'subdomain' %} + fastcgi_param SUBDOMAIN_INSTALL 1; + {% else %} + fastcgi_param SUBDOMAIN_INSTALL 0; + {% endif %} + } + + # WordPress security rules + location ~* ^/(wp-config\.php|wp-config-sample\.php|readme\.html|license\.txt)$ { + deny all; + access_log off; + log_not_found off; + } + + location ~ /\.ht { + deny all; + access_log off; + log_not_found off; + } + + # WordPress uploads security + location ~* /(?:uploads|files)/.*\.php$ { + deny all; + access_log off; + log_not_found off; + } + + # Static files caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + # WordPress REST API protection + location ~ ^/wp-json/ { + # Rate limiting for API + limit_req zone=api burst=10 nodelay; + + try_files $uri $uri/ /index.php?$args; + } + + # Block xmlrpc.php + location = /xmlrpc.php { + deny all; + access_log off; + log_not_found off; + } +} diff --git a/templates/wordpress-redhat.nginx.j2 b/templates/wordpress-redhat.nginx.j2 new file mode 100644 index 0000000..9cc0f81 --- /dev/null +++ b/templates/wordpress-redhat.nginx.j2 @@ -0,0 +1,75 @@ +server { + listen 80; + server_name {{ domain_name | default('_') }}; + + root {{ wordpress_path }}; + index index.php index.html index.htm; + + # Logging + access_log {{ log_dir }}/nginx/wordpress_access.log; + error_log {{ log_dir }}/nginx/wordpress_error.log; + + # WordPress specific rules + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + fastcgi_pass unix:{{ php_fpm_socket }}; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + + # Security + fastcgi_hide_header X-Powered-By; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + # WordPress uploads + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + # WordPress security + location ~* ^/(wp-config\.php|wp-config-sample\.php|readme\.html|license\.txt)$ { + deny all; + access_log off; + log_not_found off; + } + + # Deny access to any files with a .php extension in the uploads directory + location ~* /(?:uploads|files)/.*\.php$ { + deny all; + access_log off; + log_not_found off; + } + + # WordPress REST API and XMLRPC protection + location = /xmlrpc.php { + deny all; + access_log off; + log_not_found off; + } + + # Block access to wp-includes folders and files + location ~* ^/wp-includes/.*.php$ { + deny all; + access_log off; + log_not_found off; + } + + # Block wp-content/uploads php files + location ~* ^/wp-content/uploads/.*.php$ { + deny all; + access_log off; + log_not_found off; + } +} diff --git a/templates/wordpress-ssl.nginx.j2 b/templates/wordpress-ssl.nginx.j2 new file mode 100644 index 0000000..aa476c1 --- /dev/null +++ b/templates/wordpress-ssl.nginx.j2 @@ -0,0 +1,83 @@ +server { + listen 80; + server_name {{ domain_name }}{% if www_redirect %} www.{{ domain_name }}{% endif %}; + + # Redirect HTTP to HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {{ domain_name }}{% if www_redirect %} www.{{ domain_name }}{% endif %}; + + root {{ wordpress_path }}; + index index.php index.html index.htm; + + # SSL Configuration + ssl_certificate {{ ssl_certificate_path }}; + ssl_certificate_key {{ ssl_certificate_key_path }}; + + # Modern SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # HSTS (HTTP Strict Transport Security) + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + + # Security headers + add_header X-Frame-Options DENY always; + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # OCSP Stapling + ssl_stapling on; + ssl_stapling_verify on; + ssl_trusted_certificate {{ ssl_certificate_path }}; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + + # WordPress specific rules + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:{{ php_fpm_socket }}; + fastcgi_param HTTPS on; + } + + location ~ /\.ht { + deny all; + } + + # WordPress uploads + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # WordPress security + location ~ ^/(wp-admin|wp-login\.php) { + # Rate limiting for admin + limit_req zone=admin burst=5 nodelay; + + include snippets/fastcgi-php.conf; + fastcgi_pass unix:{{ php_fpm_socket }}; + fastcgi_param HTTPS on; + } + + # Deny access to sensitive files + location ~* ^/(wp-config\.php|wp-config-sample\.php|readme\.html|license\.txt)$ { + deny all; + } + + # Deny access to any files with a .php extension in the uploads directory + location ~* /(?:uploads|files)/.*\.php$ { + deny all; + } +} diff --git a/templates/wordpress.nginx.j2 b/templates/wordpress.nginx.j2 new file mode 100644 index 0000000..91346c8 --- /dev/null +++ b/templates/wordpress.nginx.j2 @@ -0,0 +1,84 @@ +server { + listen 80; + server_name {{ domain_name }}; + root /var/www/html; + index index.php index.html index.htm; + + # Sicherheits-Headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always; + + # Gzip-Kompression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/javascript application/xml+rss application/json; + + # WordPress-spezifische Regeln + location / { + try_files $uri $uri/ /index.php?$args; + } + + # PHP-Dateien verarbeiten + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + # WordPress-Admin-Bereich optimieren + location ~ ^/wp-admin/(.*)$ { + try_files $uri $uri/ /wp-admin/index.php?$args; + } + + # Statische Dateien cachen + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + # WordPress-Uploads-Verzeichnis + location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf)$ { + expires 1y; + add_header Cache-Control "public"; + } + + # Sensible Dateien blockieren + location ~* /(?:uploads|files)/.*\.php$ { + deny all; + } + + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + location ~ ~$ { + access_log off; + log_not_found off; + deny all; + } + + # wp-config.php schΓΌtzen + location ~ wp-config.php { + deny all; + } + + # xmlrpc.php begrenzen (gegen Brute-Force) + location = /xmlrpc.php { + deny all; + access_log off; + log_not_found off; + } + + # Logs + access_log /var/log/nginx/wordpress_access.log; + error_log /var/log/nginx/wordpress_error.log; +} diff --git a/templates/wp-cli.yml.j2 b/templates/wp-cli.yml.j2 new file mode 100644 index 0000000..05df9ec --- /dev/null +++ b/templates/wp-cli.yml.j2 @@ -0,0 +1,25 @@ +# WP-CLI Configuration +path: {{ wordpress_path }} +url: {{ wordpress_url | default('http://localhost') }} +user: {{ nginx_user }} +color: auto +debug: false +quiet: false + +# Core settings +core config: + dbname: {{ wordpress_db_name }} + dbuser: {{ wordpress_db_user }} + dbpass: {{ wordpress_db_password }} + dbhost: localhost + +# Apache/Nginx integration +apache_modules: + - mod_rewrite + +# Custom commands and aliases +aliases: + st: status + co: config + pl: plugin + th: theme diff --git a/templates/wp-config.php.j2 b/templates/wp-config.php.j2 new file mode 100644 index 0000000..f8d42b0 --- /dev/null +++ b/templates/wp-config.php.j2 @@ -0,0 +1,86 @@ +/dev/null; then + log_info "βœ“ $service is running" + return 0 + else + log_error "βœ— $service is not running" + return 1 + fi +} + +test_http_response() { + local url=$1 + local expected_code=${2:-200} + + log_info "Testing HTTP response for $url..." + + local response_code + response_code=$(curl -s -o /dev/null -w "%{http_code}" "$url" || echo "000") + + if [[ "$response_code" == "$expected_code" ]]; then + log_info "βœ“ HTTP $response_code response received" + return 0 + else + log_error "βœ— Expected HTTP $expected_code, got $response_code" + return 1 + fi +} + +test_wordpress_installation() { + local url="http://$TEST_DOMAIN:$TEST_PORT" + + log_info "Testing WordPress installation..." + + # Test homepage + if curl -s "$url" | grep -q "WordPress\|wp-content" >/dev/null; then + log_info "βœ“ WordPress homepage is accessible" + else + log_error "βœ— WordPress homepage not accessible or not WordPress" + return 1 + fi + + # Test admin page + if test_http_response "$url/wp-admin/" 302; then + log_info "βœ“ WordPress admin redirects properly" + else + log_error "βœ— WordPress admin not accessible" + return 1 + fi + + # Test WP-CLI + if ansible all -i "$INVENTORY_FILE" -m shell -a "cd /var/www/html && wp core version" &>/dev/null; then + log_info "βœ“ WP-CLI is working" + else + log_error "βœ— WP-CLI is not working" + return 1 + fi +} + +test_database_connection() { + log_info "Testing database connection..." + + if ansible all -i "$INVENTORY_FILE" -m mysql_db -a "name=wordpress_db state=present" &>/dev/null; then + log_info "βœ“ Database connection working" + return 0 + else + log_error "βœ— Database connection failed" + return 1 + fi +} + +test_php_functionality() { + log_info "Testing PHP functionality..." + + # Create test PHP file + local test_script='' + + if ansible all -i "$INVENTORY_FILE" -m copy -a "content='$test_script' dest=/var/www/html/test-php.php" &>/dev/null; then + if test_http_response "http://$TEST_DOMAIN:$TEST_PORT/test-php.php"; then + log_info "βœ“ PHP is working" + # Cleanup + ansible all -i "$INVENTORY_FILE" -m file -a "path=/var/www/html/test-php.php state=absent" &>/dev/null + return 0 + else + log_error "βœ— PHP test failed" + return 1 + fi + else + log_error "βœ— Could not create PHP test file" + return 1 + fi +} + +test_ssl_configuration() { + local url="https://$TEST_DOMAIN" + + log_info "Testing SSL configuration (if enabled)..." + + if curl -k -s "$url" >/dev/null 2>&1; then + log_info "βœ“ SSL/HTTPS is working" + + # Test SSL certificate + if echo | openssl s_client -connect "$TEST_DOMAIN:443" 2>/dev/null | openssl x509 -noout -dates >/dev/null 2>&1; then + log_info "βœ“ SSL certificate is valid" + else + log_warn "! SSL certificate validation failed (might be self-signed)" + fi + else + log_warn "! SSL/HTTPS not configured or not accessible" + fi +} + +run_security_tests() { + log_info "Running security tests..." + + # Test for sensitive file exposure + local sensitive_files=("wp-config.php" ".htaccess" "readme.html" "license.txt") + + for file in "${sensitive_files[@]}"; do + if test_http_response "http://$TEST_DOMAIN:$TEST_PORT/$file" 403; then + log_info "βœ“ $file is properly protected" + else + log_warn "! $file might be exposed" + fi + done + + # Test XMLRPC blocking + if test_http_response "http://$TEST_DOMAIN:$TEST_PORT/xmlrpc.php" 403; then + log_info "βœ“ XMLRPC is properly blocked" + else + log_warn "! XMLRPC might be accessible" + fi +} + +main() { + log_info "Starting LEMP WordPress integration tests..." + echo "=====================================================" + + local failed_tests=0 + + # Service tests + for service in nginx mysql php*-fpm; do + if ! test_service "$service"; then + ((failed_tests++)) + fi + done + + # HTTP and WordPress tests + if ! test_http_response "http://$TEST_DOMAIN:$TEST_PORT"; then + ((failed_tests++)) + fi + + if ! test_wordpress_installation; then + ((failed_tests++)) + fi + + if ! test_database_connection; then + ((failed_tests++)) + fi + + if ! test_php_functionality; then + ((failed_tests++)) + fi + + # Optional tests + test_ssl_configuration + run_security_tests + + echo "=====================================================" + + if [[ $failed_tests -eq 0 ]]; then + log_info "All critical tests passed! βœ“" + exit 0 + else + log_error "$failed_tests critical test(s) failed! βœ—" + exit 1 + fi +} + +# Run tests +main "$@" diff --git a/tests/validate-inventory.py b/tests/validate-inventory.py new file mode 100755 index 0000000..1d4ed97 --- /dev/null +++ b/tests/validate-inventory.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +""" +Ansible inventory validation script +Validates inventory files for common issues and best practices +""" + +import argparse +import configparser +import ipaddress +import os +import re +import sys +from pathlib import Path + + +class InventoryValidator: + def __init__(self, inventory_file): + self.inventory_file = Path(inventory_file) + self.errors = [] + self.warnings = [] + + def validate(self): + """Run all validation checks""" + if not self.inventory_file.exists(): + self.errors.append(f"Inventory file {self.inventory_file} does not exist") + return False + + try: + config = configparser.ConfigParser(allow_no_value=True) + config.read(self.inventory_file) + + self._validate_groups(config) + self._validate_hosts(config) + self._validate_variables(config) + self._validate_security(config) + + except configparser.Error as e: + self.errors.append(f"INI parsing error: {e}") + + return len(self.errors) == 0 + + def _validate_groups(self, config): + """Validate group definitions""" + required_groups = ['wordpress_servers'] + + for group in required_groups: + if group not in config.sections(): + self.errors.append(f"Required group '{group}' not found") + + # Check for empty groups + for section in config.sections(): + if not config.options(section): + self.warnings.append(f"Group '{section}' is empty") + + def _validate_hosts(self, config): + """Validate host definitions""" + host_pattern = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9\.-]*[a-zA-Z0-9]$') + + for section in config.sections(): + if ':vars' in section: + continue + + for option in config.options(section): + host_line = f"{option} {config.get(section, option) or ''}" + host_name = option + + # Validate hostname format + if not host_pattern.match(host_name) and not self._is_valid_ip(host_name): + self.warnings.append(f"Host '{host_name}' has unusual format") + + # Check for common connection variables + if 'ansible_host=' in host_line: + ansible_host = self._extract_variable(host_line, 'ansible_host') + if ansible_host and not self._is_valid_ip(ansible_host) and not self._is_valid_hostname(ansible_host): + self.warnings.append(f"ansible_host '{ansible_host}' for host '{host_name}' looks invalid") + + # Check for insecure practices + if 'ansible_ssh_pass=' in host_line: + self.warnings.append(f"Host '{host_name}' uses password authentication (consider SSH keys)") + + if 'ansible_become_pass=' in host_line: + self.warnings.append(f"Host '{host_name}' has become password in inventory (consider vault)") + + def _validate_variables(self, config): + """Validate variable definitions""" + wordpress_vars = [ + 'mysql_root_password', + 'wordpress_db_password', + 'wp_admin_password' + ] + + for section in config.sections(): + if ':vars' not in section: + continue + + for option in config.options(section): + value = config.get(section, option) + + # Check for hardcoded passwords + if any(var in option for var in wordpress_vars): + if value and len(value) < 8: + self.warnings.append(f"Variable '{option}' has weak password") + if value and value in ['password', 'admin', '123456']: + self.errors.append(f"Variable '{option}' has insecure default value") + + def _validate_security(self, config): + """Validate security-related settings""" + security_checks = { + 'ansible_host_key_checking': 'false', + 'ansible_ssh_common_args': '-o StrictHostKeyChecking=no' + } + + for section in config.sections(): + for option in config.options(section): + value = config.get(section, option) or '' + + for check, insecure_value in security_checks.items(): + if check in value and insecure_value in value.lower(): + self.warnings.append(f"Insecure setting detected: {check}={insecure_value}") + + def _is_valid_ip(self, ip_str): + """Check if string is a valid IP address""" + try: + ipaddress.ip_address(ip_str) + return True + except ValueError: + return False + + def _is_valid_hostname(self, hostname): + """Check if string is a valid hostname""" + if len(hostname) > 255: + return False + hostname = hostname.rstrip('.') + allowed = re.compile(r'^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?$') + return all(allowed.match(part) for part in hostname.split('.')) + + def _extract_variable(self, line, var_name): + """Extract variable value from inventory line""" + pattern = fr'{var_name}=([^\s]+)' + match = re.search(pattern, line) + return match.group(1) if match else None + + def print_results(self): + """Print validation results""" + if self.errors: + print("❌ ERRORS:") + for error in self.errors: + print(f" - {error}") + + if self.warnings: + print("⚠️ WARNINGS:") + for warning in self.warnings: + print(f" - {warning}") + + if not self.errors and not self.warnings: + print("βœ… Inventory validation passed!") + + return len(self.errors) == 0 + + +def main(): + parser = argparse.ArgumentParser(description='Validate Ansible inventory files') + parser.add_argument('inventory', help='Path to inventory file') + parser.add_argument('--strict', action='store_true', help='Treat warnings as errors') + + args = parser.parse_args() + + validator = InventoryValidator(args.inventory) + is_valid = validator.validate() + validator.print_results() + + if args.strict and validator.warnings: + is_valid = False + + sys.exit(0 if is_valid else 1) + + +if __name__ == '__main__': + main() diff --git a/vars/debian.yml b/vars/debian.yml new file mode 100644 index 0000000..c7f6ef5 --- /dev/null +++ b/vars/debian.yml @@ -0,0 +1,39 @@ +# Debian-specific variables +--- +# Package names for Debian (similar to Ubuntu but may have version differences) +packages: + - nginx + - mariadb-server + - php8.2-fpm + - php8.2-mysql + - php8.2-curl + - php8.2-gd + - php8.2-intl + - php8.2-mbstring + - php8.2-soap + - php8.2-xml + - php8.2-xmlrpc + - php8.2-zip + - python3-pymysql + - unzip + +# Service names +mysql_service: mariadb +nginx_service: nginx +php_fpm_service: php8.2-fpm + +# Package manager +package_manager: apt + +# PHP paths +php_fpm_config_path: /etc/php/8.2/fpm +php_cli_config_path: /etc/php/8.2/cli +php_fpm_pool_path: /etc/php/8.2/fpm/pool.d + +# Nginx paths +nginx_config_path: /etc/nginx +nginx_sites_available: /etc/nginx/sites-available +nginx_sites_enabled: /etc/nginx/sites-enabled + +# MySQL paths +mysql_config_path: /etc/mysql diff --git a/vars/redhat.yml b/vars/redhat.yml new file mode 100644 index 0000000..aea0f01 --- /dev/null +++ b/vars/redhat.yml @@ -0,0 +1,68 @@ +--- +# CentOS/RHEL/Rocky Linux specific variables +ansible_python_interpreter: /usr/bin/python3 + +# Package names +nginx_package: nginx +mysql_packages: + - mariadb-server + - mariadb + - python3-PyMySQL +php_packages: + - php + - php-fpm + - php-mysql + - php-gd + - php-xml + - php-mbstring + - php-curl + - php-zip + - php-intl + - php-soap + - php-xmlrpc + - php-opcache + +# Service names +nginx_service: nginx +mysql_service: mariadb +php_fpm_service: php-fpm + +# PHP Configuration +php_version: "8.1" # Default PHP version for CentOS 9 +php_fpm_socket: /run/php-fpm/www.sock +php_fpm_pool_dir: /etc/php-fpm.d +php_ini_path: /etc/php.ini +php_fpm_conf: /etc/php-fpm.d/www.conf + +# Nginx Configuration +nginx_conf_dir: /etc/nginx +nginx_sites_available: /etc/nginx/conf.d +nginx_sites_enabled: /etc/nginx/conf.d +nginx_user: nginx + +# MySQL Configuration +mysql_conf_dir: /etc/mysql +mysql_conf_file: /etc/my.cnf.d/mysql-server.cnf +mysql_socket: /var/lib/mysql/mysql.sock +mysql_pid_file: /var/run/mariadb/mariadb.pid +mysql_log_error: /var/log/mariadb/mariadb.log + +# System paths +web_root: /var/www/html +log_dir: /var/log + +# Package manager +package_manager: yum + +# Additional repositories needed +epel_release: epel-release +remi_release: "https://rpms.remirepo.net/enterprise/remi-release-{{ ansible_distribution_major_version }}.rpm" + +# SELinux booleans for web services +selinux_booleans: + - httpd_can_network_connect + - httpd_execmem + - httpd_unified + +# Firewall service +firewall_service: firewalld diff --git a/vars/ubuntu.yml b/vars/ubuntu.yml new file mode 100644 index 0000000..aecdff9 --- /dev/null +++ b/vars/ubuntu.yml @@ -0,0 +1,39 @@ +# Ubuntu-specific variables +--- +# Package names for Ubuntu +packages: + - nginx + - mysql-server + - php8.3-fpm + - php8.3-mysql + - php8.3-curl + - php8.3-gd + - php8.3-intl + - php8.3-mbstring + - php8.3-soap + - php8.3-xml + - php8.3-xmlrpc + - php8.3-zip + - python3-pymysql + - unzip + +# Service names +mysql_service: mysql +nginx_service: nginx +php_fpm_service: php8.3-fpm + +# Package manager +package_manager: apt + +# PHP paths +php_fpm_config_path: /etc/php/8.3/fpm +php_cli_config_path: /etc/php/8.3/cli +php_fpm_pool_path: /etc/php/8.3/fpm/pool.d + +# Nginx paths +nginx_config_path: /etc/nginx +nginx_sites_available: /etc/nginx/sites-available +nginx_sites_enabled: /etc/nginx/sites-enabled + +# MySQL paths +mysql_config_path: /etc/mysql