diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 5ab3ba4cdf..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,80 +0,0 @@ -version: 2.1 - -orbs: - trellis: - executors: - python-2: - docker: - - image: 'circleci/python:2-stretch' - python-3: - docker: - - image: 'circleci/python:3-stretch' - - jobs: - syntax-check: - parameters: - ansible-version: - type: string - python-version: - type: enum - enum: ['3', '2'] - executor: python-<< parameters.python-version >> - steps: - - run: python --version - - checkout - - restore_cache: - keys: - - ansible-v1-<< parameters.python-version >>-<< parameters.ansible-version >>-{{ checksum "galaxy.yml" }} - - run: - name: Install Python dependencies in a venv - command: | - virtualenv venv - . venv/bin/activate - pip install ansible<< parameters.ansible-version >> - ansible --version - - run: - name: Install Galaxy roles - command: | - . venv/bin/activate - ansible-galaxy install -r galaxy.yml - - save_cache: - key: ansible-v1-<< parameters.python-version >>-<< parameters.ansible-version >>-{{ checksum "galaxy.yml" }} - paths: - - venv - - vendor - - run: - name: Check Playbook syntax - command: | - . venv/bin/activate - ansible-playbook --syntax-check -e env=development deploy.yml - ansible-playbook --syntax-check -e env=development dev.yml - ansible-playbook --syntax-check -e env=development server.yml - -workflows: - syntax-check: - jobs: - - trellis/syntax-check: - name: syntax-check-python-3-ansible-latest - python-version: '3' - ansible-version: '' - - trellis/syntax-check: - name: syntax-check-python-3-ansible-2.8 - python-version: '3' - ansible-version: ~=2.8.0 - - trellis/syntax-check: - name: syntax-check-python-3-ansible-2.7 - python-version: '3' - ansible-version: ~=2.7.0 - - - trellis/syntax-check: - name: syntax-check-python-2-ansible-latest - python-version: '2' - ansible-version: '' - - trellis/syntax-check: - name: syntax-check-python-2-ansible-2.8 - python-version: '2' - ansible-version: ~=2.8.0 - - trellis/syntax-check: - name: syntax-check-python-2-ansible-2.7 - python-version: '2' - ansible-version: ~=2.7.0 diff --git a/.gitattributes b/.gitattributes index 41c1519e9f..aacb20e852 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ -/.circleci export-ignore /.gitattributes export-ignore +/.github export-ignore diff --git a/.github/actions/setup-step-ca/action.yml b/.github/actions/setup-step-ca/action.yml new file mode 100644 index 0000000000..1f95236d7f --- /dev/null +++ b/.github/actions/setup-step-ca/action.yml @@ -0,0 +1,42 @@ +name: Setup Step CA ACME server +description: Installs and runs an ACME compatible server via step-ca +inputs: + path: + description: 'step-ca path' + required: false + default: /root/.step +runs: + using: composite + steps: + - name: Set STEP_CA_PATH env + run: echo STEP_CA_PATH=${{ inputs.path }} >> $GITHUB_ENV + shell: bash + - name: Download packages + run: | + wget -q https://dl.step.sm/gh-release/cli/docs-ca-install/v0.18.1/step-cli_0.18.1_amd64.deb + wget -q https://dl.step.sm/gh-release/certificates/docs-ca-install/v0.18.1/step-ca_0.18.1_amd64.deb + shell: bash + - name: Install packages + run: | + sudo dpkg -i step-cli_0.18.1_amd64.deb + sudo dpkg -i step-ca_0.18.1_amd64.deb + shell: bash + - name: Create password file + run: | + sudo mkdir $STEP_CA_PATH && sudo touch $STEP_CA_PATH/password.txt + echo $(openssl rand -hex 12) | sudo tee $STEP_CA_PATH/password.txt + shell: bash + - name: Initialize + run: | + sudo step ca init --name trellis-local-ca --dns 127.0.0.1 --address :8443 --provisioner admin --password-file $STEP_CA_PATH/password.txt --provisioner-password-file $STEP_CA_PATH/password.txt + sudo step ca provisioner add acme --type ACME + shell: bash + - name: Install certificate to system + run: | + sudo step certificate install $STEP_CA_PATH/certs/root_ca.crt + shell: bash + - name: Run service + run: | + sudo cp .github/files/step-ca.service /etc/systemd/system/step-ca.service + sudo systemctl start step-ca + shell: bash diff --git a/.github/files/inventory b/.github/files/inventory new file mode 100644 index 0000000000..87d5779a91 --- /dev/null +++ b/.github/files/inventory @@ -0,0 +1,4 @@ +[production] +localhost ansible_connection=local +[web] +localhost ansible_connection=local diff --git a/.github/files/step-ca.service b/.github/files/step-ca.service new file mode 100644 index 0000000000..e62cc2bc5d --- /dev/null +++ b/.github/files/step-ca.service @@ -0,0 +1,15 @@ +[Unit] +Description=step-ca service +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=always +RestartSec=1 +Environment=STEPPATH=/root/.step +WorkingDirectory=/root/.step +ExecStart=/usr/bin/step-ca config/ca.json --password-file password.txt + +[Install] +WantedBy=multi-user.target diff --git a/.github/files/vault.yml b/.github/files/vault.yml new file mode 100644 index 0000000000..0dd232f46a --- /dev/null +++ b/.github/files/vault.yml @@ -0,0 +1,36 @@ +# Documentation: https://roots.io/trellis/docs/vault/ +vault_mysql_root_password: productionpw + +# Documentation: https://roots.io/trellis/docs/security/ +vault_users: + - name: "{{ admin_user }}" + password: example_password + salt: "generateme" + +# Variables to accompany `group_vars/production/wordpress_sites.yml` +# Note: the site name (`example.com`) must match up with the site name in the above file. +vault_wordpress_sites: + example.com: + env: + db_password: example_dbpassword + # Generate your keys here: https://roots.io/salts.html + auth_key: "generateme" + secure_auth_key: "generateme" + logged_in_key: "generateme" + nonce_key: "generateme" + auth_salt: "generateme" + secure_auth_salt: "generateme" + logged_in_salt: "generateme" + nonce_salt: "generateme" + example-https.com: + env: + db_password: example_dbpassword + # Generate your keys here: https://roots.io/salts.html + auth_key: "generateme" + secure_auth_key: "generateme" + logged_in_key: "generateme" + nonce_key: "generateme" + auth_salt: "generateme" + secure_auth_salt: "generateme" + logged_in_salt: "generateme" + nonce_salt: "generateme" diff --git a/.github/files/wordpress_sites.yml b/.github/files/wordpress_sites.yml new file mode 100644 index 0000000000..8fa390b191 --- /dev/null +++ b/.github/files/wordpress_sites.yml @@ -0,0 +1,34 @@ +letsencrypt_contact_emails: + - admin@example.com + +wordpress_sites: + example.com: + site_hosts: + - canonical: example.com + redirects: + - www.example.com + local_path: ../site + repo: git@github.com:roots/bedrock.git + branch: master + multisite: + enabled: false + ssl: + enabled: false + provider: letsencrypt + cache: + enabled: true + example-https.com: + site_hosts: + - canonical: example-https.com + redirects: + - www.example-https.com + local_path: ../site + repo: git@github.com:roots/bedrock.git + branch: master + multisite: + enabled: false + ssl: + enabled: true + provider: letsencrypt + cache: + enabled: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..67ab61288b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: ci + +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.x'] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + cache: 'pip' + - run: pip install -r requirements.txt + - uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-galaxy-${{ hashFiles('galaxy.yml') }} + - run: ansible-galaxy install -r galaxy.yml + - name: Check playbook syntax + run: | + ansible-playbook --syntax-check -e env=development deploy.yml + ansible-playbook --syntax-check -e env=development dev.yml + ansible-playbook --syntax-check -e env=development server.yml + ansible-playbook --syntax-check -e env=development rollback.yml + ansible-playbook --syntax-check -e xdebug_tunnel_inventory_host=1 xdebug-tunnel.yml diff --git a/.github/workflows/discourse.yml b/.github/workflows/discourse.yml new file mode 100644 index 0000000000..0205abbd1f --- /dev/null +++ b/.github/workflows/discourse.yml @@ -0,0 +1,17 @@ +name: Post release topic on Discourse + +on: + release: + types: [published] + +jobs: + post: + runs-on: ubuntu-latest + steps: + - uses: roots/discourse-topic-github-release-action@main + with: + discourse-api-key: ${{ secrets.DISCOURSE_RELEASES_API_KEY }} + discourse-base-url: ${{ secrets.DISCOURSE_BASE_URL }} + discourse-author-username: swalkinshaw + discourse-category: 12 + discourse-tags: releases diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000000..9741423727 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,67 @@ +name: Integration + +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: mkdir $HOME/.ssh + - name: Remove and cleanup mysql + run: | + sudo apt-get remove --purge mysql* + sudo apt-get autoremove + sudo apt-get autoclean + sudo rm -rf /etc/apparmor.d/abstractions/mysql /etc/apparmor.d/cache/usr.sbin.mysqld /etc/mysql /var/lib/mysql /var/log/mysql* /var/log/upstart/mysql.log* /var/run/mysqld ~/.mysql_history + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: '3.9' + - uses: ./.github/actions/setup-step-ca + - uses: roots/setup-trellis-cli@v1 + with: + ansible-vault-password: 'fake' + auto-init: false + galaxy-install: false + trellis-directory: '.' + - name: Create new Trellis project + run: trellis new --name example.com --host www.example.com --trellis-version ${{ github.sha }} ./example.com + - name: Update configs + run: | + sudo echo "127.0.0.1 www.example.com example.com www.example-https.com example-https.com" | sudo tee -a /etc/hosts + cp ../../.github/files/inventory hosts/production + cp ../../.github/files/wordpress_sites.yml group_vars/production/wordpress_sites.yml + cp ../../.github/files/vault.yml group_vars/production/vault.yml + working-directory: example.com/trellis + - run: trellis exec ansible-playbook --version + working-directory: example.com/trellis + - name: Provision + run: trellis provision --extra-vars "web_user=runner letsencrypt_ca=https://127.0.0.1:8443/acme/acme" production + working-directory: example.com + - name: Deploy non-https site + run: trellis deploy --extra-vars "web_user=runner project_git_repo=https://github.com/roots/bedrock.git" production example.com + working-directory: example.com + - name: Install WordPress + run: | + wp core install --url="http://example.com" --title="Example.com" --admin_user="admin" --admin_password="password" --admin_email="admin@example.com" + working-directory: /srv/www/example.com/current + - name: Verify install + run: curl -s http://example.com | grep "Example" + - name: Deploy https site + run: trellis deploy --extra-vars "web_user=runner project_git_repo=https://github.com/roots/bedrock.git" production example-https.com + working-directory: example.com + - name: Install WordPress + run: | + wp core install --url="http://example-https.com" --title="Example HTTPS" --admin_user="admin" --admin_password="password" --admin_email="admin@example.com" + working-directory: /srv/www/example-https.com/current + - name: Verify install + run: curl -s https://example-https.com | grep "<title>Example HTTPS" diff --git a/CHANGELOG.md b/CHANGELOG.md index 84e59ca325..dbb85e0e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,111 @@ +### HEAD +* Add built-in fail2ban filters ([#1375](https://github.com/roots/trellis/pull/1375)) +* Support Ansible >= 2.10 (tested up to 5.4.0) ([#1373](https://github.com/roots/trellis/pull/1373)) +* Remove Python 2 support ([#1361](https://github.com/roots/trellis/pull/1361)) + +### 1.14.0: February 16th, 2022 +* Fix #1026 - Preserve nested path for copied folders between deploys ([#1364](https://github.com/roots/trellis/pull/1364)) +* Fix #1354 - Ensure correct PHP version is set ([#1365](https://github.com/roots/trellis/pull/1365)) +* Create mysql my.cnf credentials file earlier ([#1360](https://github.com/roots/trellis/pull/1360)) +* Remove bin scripts (trellis-cli should be used instead) ([#1352](https://github.com/roots/trellis/pull/1352)) +* Update `wp_cli_version` to `2.6.0` ([#1358](https://github.com/roots/trellis/pull/1358)) +* Deploy hook build example: update Sage build command ([#1356](https://github.com/roots/trellis/pull/1356)) + +### 1.13.0: January 21st, 2022 +* Fix #1354 - Prevent apt from installing recommended packages for php ([#1355](https://github.com/roots/trellis/pull/1355)) +* Update default ssh key paths (include ed25519 keys) ([#1348](https://github.com/roots/trellis/pull/1348)) +* Use trellis-cli for Vagrant galaxy install when available ([#1349](https://github.com/roots/trellis/pull/1349)) +* Fix #970 - Improve git clone failure error ([#1351](https://github.com/roots/trellis/pull/1351)) + +### 1.12.0: January 3rd, 2022 +* Improve support for adding public SSH keys ([#1344](https://github.com/roots/trellis/pull/1344)) +* Update default Vagrant IP to 192.168.56.5 ([#1341](https://github.com/roots/trellis/pull/1341)) +* Remove old WP customizer frame options hack ([#1338](https://github.com/roots/trellis/pull/1338)) +* Fix #1319 - Improve how ssh_args are loaded ([#1337](https://github.com/roots/trellis/pull/1337)) +* Fix #1331 - Improve passlib instructions([#1336](https://github.com/roots/trellis/pull/1336)) + +### 1.11.0: December 10th, 2021 +* Bump minimum ansible version to `2.10.0` and add `ansible-base` to requirements ([#1334](https://github.com/roots/trellis/pull/1334)) +* Fix Ansible `2.10.16` - set default for `ansible_ssh_extra_args` ([#1333](https://github.com/roots/trellis/pull/1333)) +* Set max supported Vagrant version to `< 2.2.19` ([#1332](https://github.com/roots/trellis/pull/1332)) +* Bump `vagrant_ansible_version` to `2.10.7` ([#1329](https://github.com/roots/trellis/pull/1329)) +* Remove Nginx `ssl_dhparam` directive and Diffie-Hellman params group ([#1326](https://github.com/roots/trellis/pull/1326)) +* Add PHP 8.1 support ([#1325](https://github.com/roots/trellis/pull/1325)) + +### 1.10.0: November 28th, 2021 +* Default to PHP 8.0 ([#1322](https://github.com/roots/trellis/pull/1322)) +* Add GitHub SSH ed25519 key to known hosts ([#1324](https://github.com/roots/trellis/pull/1324)) +* Enable pipelining for local Ansible connections ([#1323](https://github.com/roots/trellis/pull/1323)) + +### 1.9.1: November 11th, 2021 +* Update MariaDB mirror source ([#1320](https://github.com/roots/trellis/pull/1320)) +* Remove explicit arch deb options for MariaDB (improves ARM support) ([#1318](https://github.com/roots/trellis/pull/1318)) + +### 1.9.0: October 27th, 2021 +* Bump max tested Ansible version to `2.10.7` ([#1317](https://github.com/roots/trellis/pull/1317)) +* Fix display color output in logs ([#1316](https://github.com/roots/trellis/pull/1316)) +* Define `composer_authentications` default ([#1315](https://github.com/roots/trellis/pull/1315)) +* Fix #1311 - Remove explicit permission for site directory ([#1314](https://github.com/roots/trellis/pull/1314)) +* Fix #1277 - Disable PHP CLI memory limit ([#1278](https://github.com/roots/trellis/pull/1278)) +* Fix #1285 - Improve handling of WP-CLI failed verification ([#1295](https://github.com/roots/trellis/pull/1295)) +* Fix #1284 - Update logrotate postrotate Nginx command ([#1293](https://github.com/roots/trellis/pull/1293)) +* Replace php-gd with php-imagick ([#1292](https://github.com/roots/trellis/pull/1292)) +* Improve handling of PHP versions and support PHP 8.0 (default is still 7.4) ([#1284](https://github.com/roots/trellis/pull/1284)) + +### 1.8.0: February 12th, 2021 +* Set permissions on all file related tasks ([#1270](https://github.com/roots/trellis/pull/1270)) +* Use Python 3 for `ansible_local` Vagrant provisioner ([#1269](https://github.com/roots/trellis/pull/1269)) +* Bump `vagrant_ansible_version` to `2.9.10` ([#1268](https://github.com/roots/trellis/pull/1268)) +* Migrate to Xdebug 3 ([#1260](https://github.com/roots/trellis/pull/1260)) + +### 1.7.1: January 20th, 2021 +* Improved repo connection failure message on deploys ([#1265](https://github.com/roots/trellis/pull/1265)) +* Fix #1263 - Remove deprecated PHP option `track_errors` ([#1264](https://github.com/roots/trellis/pull/1264)) +* Validate that `letsencrypt_contact_emails` is a list ([#1250](https://github.com/roots/trellis/pull/1250)) +* Add config for PHP CLI ([#1261](https://github.com/roots/trellis/pull/1261)) +* Fix security issue with empty password ([#1256](https://github.com/roots/trellis/pull/1256)) + +### 1.7.0: November 9th, 2020 +* Officially support Ubuntu 20.04 (and default Vagrant to it) ([#1197](https://github.com/roots/trellis/pull/1197)) + +### 1.6.0: November 5th, 2020 +* Remove prestissimo for Composer 2.0 support ([#1247](https://github.com/roots/trellis/pull/1247)) +* Allow WP cron intervals to be configurable ([#1222](https://github.com/roots/trellis/pull/1222)) +* Remove default Vagrant SMB credentials ([#1215](https://github.com/roots/trellis/pull/1215)) +* Fix usage of `ANSIBLE_CONFIG` env var ([#1217](https://github.com/roots/trellis/pull/1217)) +* Update MariaDB package to 10.5 ([#1212](https://github.com/roots/trellis/pull/1212)) +* Switch to official Nginx Ubuntu package ([#1208](https://github.com/roots/trellis/pull/1208)) + +### 1.5.0: August 5th, 2020 +* Improve Nginx reloading for failed Let's Encrypt certificates ([#1207](https://github.com/roots/trellis/pull/1207)) +* Add support for Lets Encrypt contact emails ([#1206](https://github.com/roots/trellis/pull/1206)) +* Support branch variable for deploys ([#1204](https://github.com/roots/trellis/pull/1204)) +* Removes ID from Lets Encrypt bundled certificate and make filename stable ([#834](https://github.com/roots/trellis/pull/834)) +* Make Fail2ban settings extensible ([#1177](https://github.com/roots/trellis/pull/1177)) +* Improve ip_whitelist in development ([#1183](https://github.com/roots/trellis/pull/1183)) +* Support Ansible 2.9 ([#1169](https://github.com/roots/trellis/pull/1169)) +* [BREAKING] Remove `nginx_includes_deprecated` feature ([#1173](https://github.com/roots/trellis/pull/1173)) +* Bump Ansible version_tested_max to 2.8.10 ([#1167](https://github.com/roots/trellis/pull/1167)) +* Bump Ansible requirement to 2.8.0 ([#1147](https://github.com/roots/trellis/pull/1147)) +* Update CircleCI Config ([#1184](https://github.com/roots/trellis/pull/1184)) + +### 1.4.0: April 2nd, 2020 +* Update PHP to 7.4 ([#1164](https://github.com/roots/trellis/pull/1164)) +* Update `wp_cli_version` to 2.4.0 ([#1131](https://github.com/roots/trellis/pull/1131)) +* Fix `subjectAltName` for self-signed certificates ([#1128](https://github.com/roots/trellis/pull/1128)) +* `composer install` without `--no-scripts` during deploy ([#1133](https://github.com/roots/trellis/pull/1133)) +* Allow `composer install` with `--classmap-authoritative` during deploy ([#1132](https://github.com/roots/trellis/pull/1132)) +* Use modern SSL config for Nginx ([#1127](https://github.com/roots/trellis/pull/1127)) +* Fix `DEPLOY_UNFINISHED` not being copied over to `release` folder ([#1145](https://github.com/roots/trellis/pull/1145)) +* Deploy: Remove untracked files from project folder ([#1146](https://github.com/roots/trellis/pull/1146)) +* Nginx: Block `composer/installed.json` ([#1150](https://github.com/roots/trellis/pull/1150)) +* Run `git clean` after checking `git clone` is successful ([#1151](https://github.com/roots/trellis/pull/1151)) +* Lint: Fix: `[206] Variables should have spaces before and after: {{ var_name }}` ([#1152](https://github.com/roots/trellis/pull/1152)) +* Lint: Fix: `[306] Shells that use pipes should set the pipefail option ([#1153](https://github.com/roots/trellis/pull/1153)) +* Lint: Fix `[301] Commands should not change things if nothing needs doing ([#1139](https://github.com/roots/trellis/pull/1139)) +* Void rolled back releases ([#1148](https://github.com/roots/trellis/pull/1148)) +* Add `WP_DEBUG_LOG` to `.env` on deploy ([#1160](https://github.com/roots/trellis/pull/1160)) + ### 1.3.0: December 7th, 2019 * Add `git_sha` and `release_version` to `.env` on deploy ([#1124](https://github.com/roots/trellis/pull/1124)) * Lower self-signed certificate expiry time for macOS Cataline support ([#1120](https://github.com/roots/trellis/pull/1120)) diff --git a/LICENSE.md b/LICENSE.md index ee859d0f58..1b5cd27da6 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) Roots +Copyright (c) Roots Software Foundation LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 447222f7da..eca8889534 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,155 @@ -# Trellis -[![Release](https://img.shields.io/github/release/roots/trellis.svg?style=flat-square)](https://github.com/roots/trellis/releases) -[![Build Status](https://img.shields.io/circleci/build/gh/roots/trellis?style=flat-square)](https://circleci.com/gh/roots/trellis) -[![Follow Roots](https://img.shields.io/twitter/follow/rootswp.svg?style=flat-square&color=1da1f2)](https://twitter.com/rootswp) +<p align="center"> + <a href="https://roots.io/trellis/"> + <img alt="Trellis" src="https://cdn.roots.io/app/uploads/logo-trellis.svg" height="100"> + </a> +</p> + +<p align="center"> + <a href="LICENSE.md"> + <img alt="MIT License" src="https://img.shields.io/github/license/roots/trellis?color=%23525ddc&style=flat-square" /> + </a> + + <a href="https://github.com/roots/trellis/releases"> + <img alt="Release" src="https://img.shields.io/github/release/roots/trellis.svg?style=flat-square" /> + </a> + + <a href="https://github.com/roots/trellis/actions"> + <img alt="Build Status" src="https://img.shields.io/github/workflow/status/roots/trellis/ci?style=flat-square" /> + </a> + + <a href="https://twitter.com/rootswp"> + <img alt="Follow Roots" src="https://img.shields.io/twitter/follow/rootswp.svg?style=flat-square&color=1da1f2" /> + </a> +</p> + +<p align="center"> + <strong>Ansible-powered LEMP stack for WordPress</strong> +</p> + +<p align="center"> + <a href="https://roots.io/"><strong><code>Website</code></strong></a>    <a href="https://docs.roots.io/trellis/master/installation/"><strong><code>Documentation</code></strong></a>    <a href="https://github.com/roots/trellis/releases"><strong><code>Releases</code></strong></a>    <a href="https://discourse.roots.io/"><strong><code>Support</code></strong></a> +</p> + +## Sponsors + +**Trellis** is an open source project and completely free to use. + +However, the amount of effort needed to maintain and develop new features and products within the Roots ecosystem is not sustainable without proper financial backing. If you have the capability, please consider [sponsoring Roots](https://github.com/sponsors/roots). + +<p align="center"><a href="https://github.com/sponsors/roots"><img height="32" src="https://img.shields.io/badge/sponsor%20roots-525ddc?logo=github&logoColor=ffffff&message=" alt="Sponsor Roots"></a></p> + +<div align="center"> +<a href="https://k-m.com/"><img src="https://cdn.roots.io/app/uploads/km-digital.svg" alt="KM Digital" width="148" height="111"></a> <a href="https://carrot.com/"><img src="https://cdn.roots.io/app/uploads/carrot.svg" alt="Carrot" width="148" height="111"></a> <a href="https://www.c21redwood.com/"><img src="https://cdn.roots.io/app/uploads/c21redwood.svg" alt="C21 Redwood Realty" width="148" height="111"></a> <a href="https://wordpress.com/"><img src="https://cdn.roots.io/app/uploads/wordpress.svg" alt="WordPress.com" width="148" height="111"></a> <a href="https://pantheon.io/"><img src="https://cdn.roots.io/app/uploads/pantheon.svg" alt="Pantheon" width="148" height="111"></a> +</div> + +## Overview Ansible playbooks for setting up a LEMP stack for WordPress. - Local development environment with Vagrant - High-performance production servers - Zero-downtime deploys for your [Bedrock](https://roots.io/bedrock/)-based WordPress sites +- [trellis-cli](https://github.com/roots/trellis-cli) for easier management ## What's included Trellis will configure a server with the following and more: -* Ubuntu 18.04 Bionic LTS -* Nginx (with optional FastCGI micro-caching) -* PHP 7.3 -* MariaDB (a drop-in MySQL replacement) -* SSL support (scores an A+ on the [Qualys SSL Labs Test](https://www.ssllabs.com/ssltest/)) -* Let's Encrypt for free SSL certificates -* HTTP/2 support (requires SSL) -* Composer -* WP-CLI -* sSMTP (mail delivery) -* MailHog -* Memcached -* Fail2ban and ferm - -## Documentation - -Full documentation is available at [https://roots.io/trellis/docs/](https://roots.io/trellis/docs/). +- Ubuntu 20.04 Focal LTS +- Nginx (with optional FastCGI micro-caching) +- PHP 8.0 +- MariaDB (a drop-in MySQL replacement) +- SSL support (scores an A+ on the [Qualys SSL Labs Test](https://www.ssllabs.com/ssltest/)) +- Let's Encrypt for free SSL certificates +- HTTP/2 support (requires SSL) +- Composer +- WP-CLI +- sSMTP (mail delivery) +- MailHog +- Memcached +- Fail2ban and ferm ## Requirements -Make sure all dependencies have been installed before moving on: - -* [Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx) -* [Virtualbox](https://www.virtualbox.org/wiki/Downloads) >= 4.3.10 -* [Vagrant](https://www.vagrantup.com/downloads.html) >= 2.1.0 - -**Windows user?** [Read the Windows getting started docs](https://roots.io/getting-started/docs/windows-development-environment-trellis/) for slightly different installation instructions. +See the full [installation](https://docs.roots.io/trellis/master/installation/#installation) docs for requirements. ## Installation -The recommended directory structure for a Trellis project looks like: +Create a new project: -```shell -example.com/ # → Root folder for the project -├── trellis/ # → Your clone of this repository -└── site/ # → A Bedrock-based WordPress site - └── web/ - ├── app/ # → WordPress content directory (themes, plugins, etc.) - └── wp/ # → WordPress core (don't touch!) +```bash +$ trellis new example.com ``` -See a complete working example in the [roots-example-project.com repo](https://github.com/roots/roots-example-project.com). +## Local development setup + +1. Review the automatically created site in `group_vars/development/wordpress_sites.yml` +2. Customize settings if necessary -1. Create a new project directory: -```plain -$ mkdir example.com && cd example.com -``` -2. Install Trellis: -```plain -$ git clone --depth=1 git@github.com:roots/trellis.git && rm -rf trellis/.git -``` -3. Install Bedrock into the `site` directory: -```plain -$ composer create-project roots/bedrock site +Start the Vagrant virtual machine: + +```bash +$ trellis up ``` -## Local development setup +[Read the local development docs](https://docs.roots.io/trellis/master/local-development/#wordpress-installation) for more information. -1. Configure your WordPress sites in `group_vars/development/wordpress_sites.yml` and in `group_vars/development/vault.yml` -2. Ensure you're in the trellis directory: `cd trellis` -3. Run `vagrant up` +## Remote server setup (staging/production) -[Read the local development docs](https://roots.io/trellis/docs/local-development-setup/) for more information. +A base Ubuntu 18.04 (Bionic) or Ubuntu 20.04 (Focal LTS) server is required for setting up remote servers. -## Remote server setup (staging/production) +1. Configure your WordPress sites in `group_vars/<environment>/wordpress_sites.yml` and in `group_vars/<environment>/vault.yml` (see the [Vault docs](https://docs.roots.io/trellis/master/vault/) for how to encrypt files containing passwords) +2. Add your server IP/hostnames to `hosts/<environment>` +3. Specify public SSH keys for `users` in `group_vars/all/users.yml` (see the [SSH Keys docs](https://docs.roots.io/trellis/master/ssh-keys/)) -For remote servers, installing Ansible locally is an additional requirement. See the [docs](https://roots.io/trellis/docs/remote-server-setup/#requirements) for more information. +Provision the server: -A base Ubuntu 18.04 (Bionic) server is required for setting up remote servers. OS X users must have [passlib](http://pythonhosted.org/passlib/install.html#installation-instructions) installed. +```bash +$ trellis provision production +``` -1. Configure your WordPress sites in `group_vars/<environment>/wordpress_sites.yml` and in `group_vars/<environment>/vault.yml` (see the [Vault docs](https://roots.io/trellis/docs/vault/) for how to encrypt files containing passwords) -2. Add your server IP/hostnames to `hosts/<environment>` -3. Specify public SSH keys for `users` in `group_vars/all/users.yml` (see the [SSH Keys docs](https://roots.io/trellis/docs/ssh-keys/)) -4. Run `ansible-playbook server.yml -e env=<environment>` to provision the server +Or take advantage of its [Digital Ocean](https://roots.io/r/digitalocean) support to create a Droplet _and_ provision it in a single command: + +```bash +$ trellis droplet create production +``` -[Read the remote server docs](https://roots.io/trellis/docs/remote-server-setup/) for more information. +[Read the remote server docs](https://docs.roots.io/trellis/master/remote-server-setup/) for more information. ## Deploying to remote servers 1. Add the `repo` (Git URL) of your Bedrock WordPress project in the corresponding `group_vars/<environment>/wordpress_sites.yml` file -2. Set the `branch` you want to deploy -3. Run `./bin/deploy.sh <environment> <site name>` -4. To rollback a deploy, run `ansible-playbook rollback.yml -e "site=<site name> env=<environment>"` +2. Set the `branch` you want to deploy (defaults to `master`) + +Deploy a site: -[Read the deploys docs](https://roots.io/trellis/docs/deploys/) for more information. +```bash +$ trellis deploy <environment> <site> +``` + +Rollback a deploy: -## Contributing +```bash +$ trellis rollback <environment> <site> +``` -Contributions are welcome from everyone. We have [contributing guidelines](https://github.com/roots/guidelines/blob/master/CONTRIBUTING.md) to help you get started. +[Read the deploys docs](https://roots.io/docs/trellis/master/deployments/) for more information. -## Trellis sponsors +## Migrating existing projects to trellis-cli: -Help support our open-source development efforts by [becoming a patron](https://www.patreon.com/rootsdev). +Assuming you're using the standard project structure, you just need to make the +project trellis-cli compatible by initializing it: -<a href="https://kinsta.com/?kaid=OFDHAJIXUDIV"><img src="https://cdn.roots.io/app/uploads/kinsta.svg" alt="Kinsta" width="200" height="150"></a> <a href="https://k-m.com/"><img src="https://cdn.roots.io/app/uploads/km-digital.svg" alt="KM Digital" width="200" height="150"></a> <a href="https://scaledynamix.com/"><img src="https://cdn.roots.io/app/uploads/scale-dynamix.svg" alt="Scale Dynamix" width="200" height="150"></a> +```bash +$ trellis init +``` ## Community Keep track of development and community news. -* Participate on the [Roots Discourse](https://discourse.roots.io/) -* Follow [@rootswp on Twitter](https://twitter.com/rootswp) -* Read and subscribe to the [Roots Blog](https://roots.io/blog/) -* Subscribe to the [Roots Newsletter](https://roots.io/subscribe/) -* Listen to the [Roots Radio podcast](https://roots.io/podcast/) +- Join us on Discord by [sponsoring us on GitHub](https://github.com/sponsors/roots) +- Participate on the [Roots Discourse](https://discourse.roots.io/) +- Follow [@rootswp on Twitter](https://twitter.com/rootswp) +- Read and subscribe to the [Roots Blog](https://roots.io/blog/) +- Subscribe to the [Roots Newsletter](https://roots.io/subscribe/) diff --git a/Vagrantfile b/Vagrantfile index d59cdb3b5a..1b7484cf47 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -16,7 +16,11 @@ ensure_plugins(vconfig.fetch('vagrant_plugins')) if vconfig.fetch('vagrant_insta trellis_config = Trellis::Config.new(root_path: ANSIBLE_PATH) -Vagrant.require_version '>= 2.1.0' +if Vagrant::Util::Platform.darwin? + Vagrant.require_version '>= 2.1.0', '< 2.2.19' +else + Vagrant.require_version '>= 2.1.0' +end Vagrant.configure('2') do |config| config.vm.box = vconfig.fetch('vagrant_box') @@ -64,14 +68,12 @@ Vagrant.configure('2') do |config| fail_with_message "vagrant-hostmanager missing, please install the plugin with this command:\nvagrant plugin install vagrant-hostmanager\n\nOr install landrush for multisite subdomains:\nvagrant plugin install landrush" end - bin_path = File.join(ANSIBLE_PATH_ON_VM, 'bin') - vagrant_mount_type = vconfig.fetch('vagrant_mount_type') extra_options = if vagrant_mount_type == 'smb' { - smb_username: vconfig.fetch('vagrant_smb_username', 'vagrant'), - smb_password: vconfig.fetch('vagrant_smb_password', 'vagrant'), + smb_username: vconfig.fetch('vagrant_smb_username', nil), + smb_password: vconfig.fetch('vagrant_smb_password', nil), } else {} @@ -84,7 +86,6 @@ Vagrant.configure('2') do |config| end config.vm.synced_folder ANSIBLE_PATH, ANSIBLE_PATH_ON_VM, mount_options: mount_options(vagrant_mount_type, dmode: 755, fmode: 644), type: vagrant_mount_type, **extra_options - config.vm.synced_folder File.join(ANSIBLE_PATH, 'bin'), bin_path, mount_options: mount_options(vagrant_mount_type, dmode: 755, fmode: 755), type: vagrant_mount_type, **extra_options elsif !Vagrant.has_plugin?('vagrant-bindfs') fail_with_message "vagrant-bindfs missing, please install the plugin with this command:\nvagrant plugin install vagrant-bindfs" else @@ -95,7 +96,6 @@ Vagrant.configure('2') do |config| config.vm.synced_folder ANSIBLE_PATH, '/ansible-nfs', type: 'nfs' config.bindfs.bind_folder '/ansible-nfs', ANSIBLE_PATH_ON_VM, o: 'nonempty', p: '0644,a+D' - config.bindfs.bind_folder bin_path, bin_path, perms: '0755' end vconfig.fetch('vagrant_synced_folders', []).each do |folder| @@ -117,23 +117,10 @@ Vagrant.configure('2') do |config| provisioner = local_provisioning? ? :ansible_local : :ansible provisioning_path = local_provisioning? ? ANSIBLE_PATH_ON_VM : ANSIBLE_PATH - # Fix for https://github.com/hashicorp/vagrant/issues/10914 - if local_provisioning? - config.vm.provision 'shell', inline: <<~SHELL - sudo apt-get update -y -qq && - sudo dpkg-reconfigure libc6 && - export DEBIAN_FRONTEND=noninteractive && - sudo -E apt-get -q --option \"Dpkg::Options::=--force-confold\" --assume-yes install libssl1.1 - SHELL - end - config.vm.provision provisioner do |ansible| if local_provisioning? ansible.install_mode = 'pip' - if Vagrant::VERSION >= '2.2.5' - # Fix for https://github.com/hashicorp/vagrant/issues/10950 - ansible.pip_install_cmd = 'curl https://bootstrap.pypa.io/get-pip.py | sudo python' - end + ansible.pip_install_cmd = 'sudo apt-get install -y -qq python3-pip' ansible.provisioning_path = provisioning_path ansible.version = vconfig.fetch('vagrant_ansible_version') end @@ -143,6 +130,10 @@ Vagrant.configure('2') do |config| ansible.galaxy_role_file = File.join(provisioning_path, 'galaxy.yml') unless vconfig.fetch('vagrant_skip_galaxy') || ENV['SKIP_GALAXY'] ansible.galaxy_roles_path = File.join(provisioning_path, 'vendor/roles') + if which('trellis') + ansible.galaxy_command = 'trellis galaxy install' + end + ansible.groups = { 'web' => ['default'], 'development' => ['default'] diff --git a/ansible.cfg b/ansible.cfg index 1321fe3630..e6467d2586 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -8,8 +8,8 @@ inventory = hosts nocows = 1 roles_path = vendor/roles vars_plugins = ~/.ansible/plugins/vars:/usr/share/ansible/plugins/vars:lib/trellis/plugins/vars +pipelining = True [ssh_connection] -ssh_args = -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o HostKeyAlgorithms=ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-ed25519,ssh-rsa -pipelining = True +ssh_args = -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s retries = 1 diff --git a/bin/deploy.sh b/bin/deploy.sh deleted file mode 100755 index 0b346e8e34..0000000000 --- a/bin/deploy.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -shopt -s nullglob - -ENVIRONMENTS=( hosts/* ) -ENVIRONMENTS=( "${ENVIRONMENTS[@]##*/}" ) - -show_usage() { - echo "Usage: deploy <environment> <site name> [options] - -<environment> is the environment to deploy to ("staging", "production", etc) -<site name> is the WordPress site to deploy (name defined in "wordpress_sites") -[options] is any number of parameters that will be passed to ansible-playbook - -Available environments: -`( IFS=$'\n'; echo "${ENVIRONMENTS[*]}" )` - -Examples: - deploy staging example.com - deploy production example.com - deploy staging example.com -vv -T 60 -" -} - -[[ $# -lt 2 ]] && { show_usage; exit 127; } - -for arg -do - [[ $arg = -h ]] && { show_usage; exit 0; } -done - -ENV="$1"; shift -SITE="$1"; shift -EXTRA_PARAMS=$@ -DEPLOY_CMD="ansible-playbook deploy.yml -e env=$ENV -e site=$SITE $EXTRA_PARAMS" -HOSTS_FILE="hosts/$ENV" - -if [[ ! -e $HOSTS_FILE ]]; then - echo "Error: $ENV is not a valid environment ($HOSTS_FILE does not exist)." - echo - echo "Available environments:" - ( IFS=$'\n'; echo "${ENVIRONMENTS[*]}" ) - exit 1 -fi - -$DEPLOY_CMD diff --git a/bin/xdebug-tunnel.sh b/bin/xdebug-tunnel.sh deleted file mode 100755 index e60e4d48bb..0000000000 --- a/bin/xdebug-tunnel.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -show_usage() { - echo " -Usage: bin/xdebug-tunnel.sh <action> <host> - -<action> can be 'open' or 'close' -<host> is the hostname, IP, or inventory alias in your \`hosts/<environment>\` file. - -Examples: - To open a tunnel: - bin/xdebug-tunnel.sh open 12.34.56.78 - - To close a tunnel: - bin/xdebug-tunnel.sh close 12.34.56.78 -" -} - -ENABLE_TCP_FORWARDING= -if [[ $1 == "open" ]]; then - REMOTE_ENABLE=1 - ENABLE_TCP_FORWARDING="-e sshd_allow_tcp_forwarding=yes" -elif [[ $1 == "close" ]]; then - REMOTE_ENABLE=0 -else - >&2 echo "The provided <action> argument '${1}' is not acceptable." - show_usage - exit 1 -fi - -if [[ -z $2 ]]; then - >&2 echo "The <host> argument is required." - show_usage - exit 1 -fi - -XDEBUG_ENABLE="-e xdebug_remote_enable=${REMOTE_ENABLE}" -SSH_HOST="-e xdebug_tunnel_inventory_host=$2" - -if [[ -n $DEBUG ]]; then - PARAMS="$PARAMS ${VERBOSITY:--vvvv}" -fi - -ansible-playbook xdebug-tunnel.yml $XDEBUG_ENABLE $ENABLE_TCP_FORWARDING $SSH_HOST $PARAMS diff --git a/deploy-hooks/build-after.yml b/deploy-hooks/build-after.yml new file mode 100644 index 0000000000..9cce3a2ac3 --- /dev/null +++ b/deploy-hooks/build-after.yml @@ -0,0 +1,11 @@ +# Placeholder `deploy_build_after` hook +# +# ⚠️ This example assumes your theme is using Sage 10 +# +# Uncomment the lines below if you are using Sage 10 +# +# --- +# - name: Run Acorn optimize +# command: wp acorn optimize +# args: +# chdir: "{{ deploy_helper.new_release_path }}" diff --git a/deploy-hooks/build-before.yml b/deploy-hooks/build-before.yml index e6893432ba..78003bd92c 100644 --- a/deploy-hooks/build-before.yml +++ b/deploy-hooks/build-before.yml @@ -1,10 +1,10 @@ # Placeholder `deploy_build_before` hook for building theme assets on the # host machine and then copying the files to the remote server # -# ⚠️ This example assumes your theme is using Sage 9 -# An example for themes built with Sage 8 can be found at: https://git.io/vdgUt +# ⚠️ This example assumes your theme is using Sage 10 # -# Uncomment the lines below and replace `sage` with your theme folder +# Uncomment the lines below if you are using Sage 10 +# and replace `sage` with your theme folder # # --- # - name: Install npm dependencies @@ -14,19 +14,19 @@ # chdir: "{{ project_local_path }}/web/app/themes/sage" # # - name: Install Composer dependencies -# command: composer install --no-ansi --no-dev --no-interaction --no-progress --optimize-autoloader --no-scripts +# command: composer install --no-ansi --no-dev --no-interaction --no-progress --optimize-autoloader --no-scripts --classmap-authoritative # args: # chdir: "{{ deploy_helper.new_release_path }}/web/app/themes/sage" # # - name: Compile assets for production -# command: yarn build:production +# command: yarn build # delegate_to: localhost # args: # chdir: "{{ project_local_path }}/web/app/themes/sage" # # - name: Copy production assets # synchronize: -# src: "{{ project_local_path }}/web/app/themes/sage/dist" +# src: "{{ project_local_path }}/web/app/themes/sage/public" # dest: "{{ deploy_helper.new_release_path }}/web/app/themes/sage" # group: no # owner: no diff --git a/dev.yml b/dev.yml index 8df24a21d7..d84cf893da 100644 --- a/dev.yml +++ b/dev.yml @@ -1,12 +1,5 @@ --- -- name: Set ansible_python_interpreter - hosts: web:&development - gather_facts: false - become: yes - roles: - - { role: python_interpreter, tags: [always] } - -- name: "WordPress Server: Install LEMP Stack with PHP 7.3 and MariaDB MySQL" +- name: "WordPress Server: Install LEMP Stack with PHP and MariaDB MySQL" hosts: web:&development become: yes remote_user: vagrant @@ -27,7 +20,6 @@ - { role: logrotate, tags: [logrotate] } - { role: princexml, tags: [princexml] } - { role: epubcheck, tags: [epubcheck] } - - { role: kindlegen, tags: [kindlegen] } - { role: saxon-he, tags: [saxon-he] } - { role: composer, tags: [composer] } - { role: wp-cli, tags: [wp-cli] } diff --git a/galaxy.yml b/galaxy.yml index 387e4b4a12..788cd63b1b 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,10 +1,10 @@ - name: composer src: geerlingguy.composer - version: 1.7.6 + version: 1.9.0 - name: ntp src: geerlingguy.ntp - version: 1.6.4 + version: 2.3.1 - name: logrotate src: nickhammond.logrotate @@ -12,15 +12,11 @@ - name: swapfile src: oefenweb.swapfile - version: v2.0.22 + version: v2.0.32 - name: mailhog src: geerlingguy.mailhog - version: 2.1.4 - -- name: princexml - src: pressbooks.princexml - version: 12.5.0 + version: 2.3.0 - src: geerlingguy.redis - version: 1.6.0 + version: 1.7.0 diff --git a/group_vars/all/helpers.yml b/group_vars/all/helpers.yml index 20a70ecf80..1e56f5c083 100644 --- a/group_vars/all/helpers.yml +++ b/group_vars/all/helpers.yml @@ -8,6 +8,7 @@ wordpress_env_defaults: wp_home: "{{ ssl_enabled | ternary('https', 'http') }}://{{ site_hosts_canonical | first }}" wp_siteurl: "{{ ssl_enabled | ternary('https', 'http') }}://{{ site_hosts_canonical | first }}/wp" domain_current_site: "{{ site_hosts_canonical | first }}" + wp_debug_log: "{{ www_root }}/{{ item.key }}/logs/debug.log" site_env: "{{ wordpress_env_defaults | combine(vault_wordpress_env_defaults | default({}), item.value.env | default({}), vault_wordpress_sites[item.key].env) }}" site_hosts_canonical: "{{ item.value.site_hosts | map(attribute='canonical') | list }}" diff --git a/group_vars/all/known_hosts.yml b/group_vars/all/known_hosts.yml index b8af937ca5..1db951997f 100644 --- a/group_vars/all/known_hosts.yml +++ b/group_vars/all/known_hosts.yml @@ -6,6 +6,8 @@ known_hosts: - name: github.com key: github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== + - name: github.com + key: github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl - name: bitbucket.org key: bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw== - name: gitlab.com diff --git a/group_vars/all/logrotate.yml b/group_vars/all/logrotate.yml index c6d24962c6..ba1ba124fa 100644 --- a/group_vars/all/logrotate.yml +++ b/group_vars/all/logrotate.yml @@ -17,4 +17,4 @@ logrotate_scripts: if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ run-parts /etc/logrotate.d/httpd-prerotate; \ fi \ - postrotate: service nginx rotate + postrotate: service nginx reload >/dev/null 2>&1 diff --git a/group_vars/all/main.yml b/group_vars/all/main.yml index 6c26f24cf2..9b8d168ee6 100644 --- a/group_vars/all/main.yml +++ b/group_vars/all/main.yml @@ -1,12 +1,9 @@ -composer_keep_updated: true -composer_version: "1.10.20" -composer_version_branch: '' -composer_global_packages: - - { name: hirak/prestissimo } apt_cache_valid_time: 3600 apt_package_state: present apt_security_package_state: latest apt_dev_package_state: latest +composer_keep_updated: true +php_version: "7.4" apt_packages_custom: libxml2-utils: "{{ apt_package_state }}" default-jdk: "{{ apt_package_state }}" diff --git a/group_vars/all/security.yml b/group_vars/all/security.yml index 2d9df3d3b9..bac2bbb098 100644 --- a/group_vars/all/security.yml +++ b/group_vars/all/security.yml @@ -1,3 +1,5 @@ +# Documentation: https://roots.io/trellis/docs/security/ + ferm_input_list: - type: dport_accept dport: [http, https] @@ -10,8 +12,25 @@ ferm_input_list: seconds: 300 hits: 20 -# Documentation: https://roots.io/trellis/docs/security/ + +# Enable built-in fail2ban services or add your own custom ones +fail2ban_services_custom: + - name: wordpress_xmlrpc + filter: wordpress-xmlrpc + enabled: "false" + port: http,https + logpath: "{{ www_root }}/**/logs/access.log" + - name: wordpress_wp_login + filter: wordpress-wp-login + enabled: "false" + port: http,https + logpath: "{{ www_root }}/**/logs/access.log" + # If sshd_permit_root_login: false, admin_user must be in 'users' (`group_vars/all/users.yml`) with sudo group # and in 'vault_users' (`group_vars/staging/vault.yml`, `group_vars/production/vault.yml`) sshd_permit_root_login: true sshd_password_authentication: false + +ip_whitelist: + - 127.0.0.0/8 + - "{{ ipify_public_ip | default('') }}" diff --git a/group_vars/all/users.yml b/group_vars/all/users.yml index 2290fea7de..4d38ac23d2 100644 --- a/group_vars/all/users.yml +++ b/group_vars/all/users.yml @@ -7,16 +7,18 @@ users: groups: - "{{ web_group }}" keys: - - "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" + - "{{ lookup('file', '~/.ssh/id_rsa.pub', errors='ignore') }}" + - "{{ lookup('file', '~/.ssh/id_ed25519.pub', errors='ignore') }}" # - https://github.com/username.keys - name: "{{ admin_user }}" groups: - sudo keys: - - "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" + - "{{ lookup('file', '~/.ssh/id_rsa.pub', errors='ignore') }}" + - "{{ lookup('file', '~/.ssh/id_ed25519.pub', errors='ignore') }}" # - https://github.com/username.keys web_user: web web_group: www-data web_sudoers: - - "/usr/sbin/service php7.3-fpm *" + - "/usr/sbin/service php{{ php_version }}-fpm *" diff --git a/group_vars/development/main.yml b/group_vars/development/main.yml index 8d7c15913c..1a3d9f3bd2 100644 --- a/group_vars/development/main.yml +++ b/group_vars/development/main.yml @@ -1,5 +1,4 @@ acme_tiny_challenges_directory: "{{ www_root }}/letsencrypt" env: development -ferm_enabled: false mysql_root_password: "{{ vault_mysql_root_password }}" # Define this variable in group_vars/development/vault.yml web_user: vagrant diff --git a/group_vars/development/php.yml b/group_vars/development/php.yml index 8df88dc533..c8490ede4b 100644 --- a/group_vars/development/php.yml +++ b/group_vars/development/php.yml @@ -5,6 +5,9 @@ php_track_errors: 'On' php_mysqlnd_collect_memory_statistics: 'On' php_opcache_enable: 0 +xdebug_mode: 'debug' +xdebug_start_with_request: 'yes' +xdebug_discover_client_host: 1 xdebug_remote_enable: 1 xdebug_remote_connect_back: 1 xdebug_remote_autostart: 1 diff --git a/group_vars/development/security.yml b/group_vars/development/security.yml new file mode 100644 index 0000000000..fe079a2f7e --- /dev/null +++ b/group_vars/development/security.yml @@ -0,0 +1,6 @@ +ferm_enabled: false +ip_whitelist: + - 127.0.0.0/8 + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 diff --git a/group_vars/production/wordpress_sites.yml b/group_vars/production/wordpress_sites.yml index 62e95f430c..e22a685929 100644 --- a/group_vars/production/wordpress_sites.yml +++ b/group_vars/production/wordpress_sites.yml @@ -3,13 +3,13 @@ # Define accompanying passwords/secrets in group_vars/production/vault.yml wordpress_sites: - example.com: + pressbooks.test: site_hosts: - - canonical: example.com + - canonical: pressbooks.test redirects: - - www.example.com + - www.pressbooks.test local_path: ../site # path targeting local Bedrock site directory (relative to Ansible root) - repo: git@github.com:example/example.com.git # replace with your Git repo URL + repo: git@github.com:example/pressbooks.test.git # replace with your Git repo URL repo_subtree_path: site # relative path to your Bedrock/WP directory in your repo branch: master multisite: @@ -21,6 +21,6 @@ wordpress_sites: cache: enabled: false env: - domain_current_site: example.com - wp_home: https://example.com - wp_siteurl: https://example.com/wp + domain_current_site: pressbooks.test + wp_home: https://pressbooks.test + wp_siteurl: https://pressbooks.test/wp diff --git a/hosts/development b/hosts/development index 4af294aca7..694b39e540 100644 --- a/hosts/development +++ b/hosts/development @@ -1,41 +1,8 @@ -# This file is only used for Windows hosts. -# -# Windows -# ------------------------------------------------------------- -# If you want to run `dev.yml` manually you can SSH into the VM -# to the directory with the `dev.yml` playbook and run: - -# `ansible-playbook dev.yml` -# -# Non-Windows -# ------------------------------------------------------------- -# If you want to run `dev.yml` manually via the `ansible-playbook` -# command (vs. `vagrant up` or `vagrant provision`), you might be -# inclined to define your development host information in this file. -# We recommend instead that you use the `-i` (inventory) option with -# your `ansible-playbook` command to specify the custom inventory file -# Vagrant has created for the VM. Vagrant's custom inventory -# includes necessary non-standard SSH connection information. -# -# Here is an example command: -# -# `ansible-playbook dev.yml -i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory` -# -# The `.vagrant` directory above is usually in the same directory as -# your `Vagrantfile`. If not, you will need to adjust this path in the -# command. -# -# Why run `dev.yml` manually? -# ------------------------------------------------------------- -# One reason you may want to run `dev.yml` via the `ansible-playbook` -# command is for the convenience of adding Ansible options via the -# command line (e.g., `--tags`, `--skip-tags`, or `-vvvv`). In contrast, -# the commands `vagrant up` and `vagrant provision` would only run the -# `dev.yml` playbook with such options if you were edit the options -# into the Vagrantfile's `config.vm.provision` section. +# Add each host to the [development] group and to a "type" group such as [web] or [db]. +# List each machine only once per [group], even if it will host multiple sites. [development] -192.168.50.5 ansible_connection=local +192.168.56.5 [web] -192.168.50.5 ansible_connection=local +192.168.56.5 diff --git a/lib/trellis/__init__.py b/lib/trellis/__init__.py deleted file mode 100644 index 980f84a225..0000000000 --- a/lib/trellis/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type diff --git a/lib/trellis/plugins/callback/output.py b/lib/trellis/plugins/callback/output.py index 9bb2cbede6..61935552a0 100644 --- a/lib/trellis/plugins/callback/output.py +++ b/lib/trellis/plugins/callback/output.py @@ -1,16 +1,21 @@ -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - import os.path import sys +DOCUMENTATION = ''' + callback: output + type: stdout + short_description: Custom output for Trellis + extends_documentation_fragment: + - default_callback +''' + from ansible.plugins.callback.default import CallbackModule as CallbackModule_default try: from trellis.utils import output as output except ImportError: - ansible_path = os.getenv('ANSIBLE_CONFIG', os.getcwd()) + ansible_config_path = os.getenv('ANSIBLE_CONFIG') + ansible_path = os.path.dirname(ansible_config_path) if ansible_config_path else os.getcwd() if sys.path.append(os.path.join(ansible_path, 'lib')) in sys.path: raise sys.path.append(sys.path.append(os.path.join(ansible_path, 'lib'))) from trellis.utils import output as output diff --git a/lib/trellis/plugins/callback/vars.py b/lib/trellis/plugins/callback/vars.py index 338d8f9597..2ffbbf9854 100644 --- a/lib/trellis/plugins/callback/vars.py +++ b/lib/trellis/plugins/callback/vars.py @@ -1,8 +1,6 @@ -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - import re import sys +import os from __main__ import cli from ansible.module_utils.six import iteritems @@ -13,6 +11,8 @@ from ansible.plugins.callback import CallbackBase from ansible.template import Templar from ansible.utils.unsafe_proxy import wrap_var +from ansible import context +from ansible.plugins.loader import connection_loader class CallbackModule(CallbackBase): @@ -23,13 +23,7 @@ class CallbackModule(CallbackBase): def __init__(self): super(CallbackModule, self).__init__() - - # handle Ansible 2.7 and 2.8 cases by normalizing each into a dict - try: - from ansible import context - self._options = context.CLIARGS - except ImportError: - self._options = vars(cli.options) if cli else {} + self._options = context.CLIARGS def raw_triage(self, key_string, item, patterns): # process dict values @@ -99,6 +93,9 @@ def darwin_without_passlib(self): return True def v2_playbook_on_play_start(self, play): + play_context = PlayContext(play=play) + connection = connection_loader.get('ssh', play_context, os.devnull) + env = play.get_variable_manager().get_vars(play=play).get('env', '') env_group = next((group for key,group in iteritems(play.get_variable_manager()._inventory.groups) if key == env), False) if env_group: @@ -107,7 +104,7 @@ def v2_playbook_on_play_start(self, play): for host in play.get_variable_manager()._inventory.list_hosts(play.hosts[0]): hostvars = play.get_variable_manager().get_vars(play=play, host=host) self.raw_vars(play, host, hostvars) - host.vars['ssh_args_default'] = PlayContext(play=play)._ssh_args.default + host.vars['ssh_args_default'] = connection.get_option('ssh_args') host.vars['cli_options'] = self.cli_options() host.vars['cli_ask_pass'] = self._options.get('ask_pass', False) host.vars['cli_ask_become_pass'] = self._options.get('become_ask_pass', False) diff --git a/lib/trellis/plugins/filter/filters.py b/lib/trellis/plugins/filter/filters.py index 6c8eed5eb7..8b4f624232 100644 --- a/lib/trellis/plugins/filter/filters.py +++ b/lib/trellis/plugins/filter/filters.py @@ -1,7 +1,3 @@ -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function, unicode_literals) -__metaclass__ = type - import types from ansible import errors diff --git a/lib/trellis/plugins/vars/version.py b/lib/trellis/plugins/vars/version.py index 93c325ab34..e05b94e800 100644 --- a/lib/trellis/plugins/vars/version.py +++ b/lib/trellis/plugins/vars/version.py @@ -1,12 +1,8 @@ -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - from ansible import __version__ from ansible.errors import AnsibleError from distutils.version import LooseVersion from operator import eq, ge, gt -from sys import version_info +from platform import python_version, python_version_tuple try: from __main__ import display @@ -14,26 +10,22 @@ from ansible.utils.display import Display display = Display() -version_requirement = '2.7.12' -version_tested_max = '2.8.4' -python3_required_version = '2.5.3' +version_requirement = '2.10.0' +version_tested_max = '5.4.0' -if version_info[0] == 3 and not ge(LooseVersion(__version__), LooseVersion(python3_required_version)): - raise AnsibleError(('Ansible >= {} is required when using Python 3.\n' - 'Either downgrade to Python 2 or update your Ansible version to {}.').format(python3_required_version, python3_required_version)) +if python_version_tuple()[0] == '2': + raise AnsibleError(('Trellis no longer supports Python 2 (you are using version {}).' + ' Python 2 reached end of life in 2020 and is unmaintained.\n' + 'Python 3 is required as of Trellis version v1.15.0.').format(python_version())) if not ge(LooseVersion(__version__), LooseVersion(version_requirement)): raise AnsibleError(('Trellis no longer supports Ansible {}.\n' 'Please upgrade to Ansible {} or higher.').format(__version__, version_requirement)) elif gt(LooseVersion(__version__), LooseVersion(version_tested_max)): - display.warning(u'You Ansible version is {} but this version of Trellis has only been tested for ' + display.warning(u'Your Ansible version is {} but this version of Trellis has only been tested for ' u'compatability with Ansible {} -> {}. It is advisable to check for Trellis updates or ' u'downgrade your Ansible version.'.format(__version__, version_requirement, version_tested_max)) -if eq(LooseVersion(__version__), LooseVersion('2.5.0')): - display.warning(u'You Ansible version is {}. Consider upgrading your Ansible version to avoid ' - u'erroneous warnings such as `Removed restricted key from module data...`'.format(__version__)) - # Import BaseVarsPlugin after Ansible version check. # Otherwise import error for Ansible versions older than 2.4 would prevent display of version check message. from ansible.plugins.vars import BaseVarsPlugin diff --git a/lib/trellis/utils/__init__.py b/lib/trellis/utils/__init__.py deleted file mode 100644 index 980f84a225..0000000000 --- a/lib/trellis/utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type diff --git a/lib/trellis/utils/output.py b/lib/trellis/utils/output.py index 89b073b9c7..4ae252df67 100644 --- a/lib/trellis/utils/output.py +++ b/lib/trellis/utils/output.py @@ -1,5 +1,4 @@ # Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) __metaclass__ = type import os.path @@ -9,12 +8,12 @@ from ansible import __version__ from ansible.module_utils._text import to_text -from ansible.module_utils.six import string_types def system(vagrant_version=None): # Get most recent Trellis CHANGELOG entry changelog_msg = '' - ansible_path = os.getenv('ANSIBLE_CONFIG', os.getcwd()) + ansible_config_path = os.getenv('ANSIBLE_CONFIG') + ansible_path = os.path.dirname(ansible_config_path) if ansible_config_path else os.getcwd() changelog = os.path.join(ansible_path, 'CHANGELOG.md') if os.path.isfile(changelog): @@ -54,7 +53,7 @@ def replace_item_with_key(obj, result): ) if should_replace: - if 'key' in result._result[item]: + if type(result._result[item]) is dict and 'key' in result._result[item]: result._result[item] = result._result[item]['key'] elif type(result._result[item]) is dict: subitem = '_ansible_item_label' if '_ansible_item_label' in result._result[item] else 'item' @@ -90,7 +89,7 @@ def display(obj, result): # Must pass unicode strings to Display.display() to prevent UnicodeError tracebacks if isinstance(msg, list): msg = '\n'.join([to_text(x) for x in msg]) - elif not isinstance(msg, string_types): + elif not isinstance(msg, str): msg = to_text(msg) # Wrap text @@ -101,17 +100,17 @@ def display(obj, result): hr = '-' * int(wrap_width*.67) if obj.task_failed and first: - display(system(obj.vagrant_version), 'bright gray') - display(hr, 'bright gray') + display(system(obj.vagrant_version), 'bright gray', screen_only=True) + display(hr, 'bright gray', screen_only=True) if msg == '': if obj.task_failed and not first: - display(hr, 'bright gray') + display(hr, 'bright gray', screen_only=True) else: return else: if not first: - display(hr, 'bright gray') + display(hr, 'bright gray', screen_only=True) display(msg, 'red' if obj.task_failed else 'bright purple') def display_host(obj, result): diff --git a/lib/trellis/vagrant.rb b/lib/trellis/vagrant.rb index fa60ad0112..d34f216009 100644 --- a/lib/trellis/vagrant.rb +++ b/lib/trellis/vagrant.rb @@ -1,5 +1,5 @@ # Set Ansible paths relative to Ansible directory -ENV['ANSIBLE_CONFIG'] = ANSIBLE_PATH +ENV['ANSIBLE_CONFIG'] = File.join(ANSIBLE_PATH, 'ansible.cfg') ENV['ANSIBLE_CALLBACK_PLUGINS'] = "~/.ansible/plugins/callback:/usr/share/ansible/plugins/callback:#{File.join(ANSIBLE_PATH, 'lib/trellis/plugins/callback')}" ENV['ANSIBLE_FILTER_PLUGINS'] = "~/.ansible/plugins/filter:/usr/share/ansible/plugins/filter:#{File.join(ANSIBLE_PATH, 'lib/trellis/plugins/filter')}" ENV['ANSIBLE_LIBRARY'] = "~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules:#{File.join(ANSIBLE_PATH, 'lib/trellis/modules')}" diff --git a/public_keys/.gitkeep b/public_keys/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/requirements.txt b/requirements.txt index cd72552dd7..5955db3396 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -ansible>=2.7.12,<=2.8.4 +ansible>=2.10.0 passlib diff --git a/roles/acceptance-test/tasks/main.yml b/roles/acceptance-test/tasks/main.yml index 73515895dc..86bd0cded0 100644 --- a/roles/acceptance-test/tasks/main.yml +++ b/roles/acceptance-test/tasks/main.yml @@ -31,24 +31,18 @@ login_user: "root" login_password: "devpw" -- name: Add Chrome deb to target - copy: - src: "google-chrome-stable_current_amd64.deb" - dest: "/tmp/google-chrome-stable_current_amd64.deb" +- name: Install Chromium + apt: + name: chromium-browser + state: present -- name: Install a Google Chrome .deb package +- name: Install Chromium Chrome Driver apt: - deb: "/tmp/google-chrome-stable_current_amd64.deb" + name: chromium-chromedriver + state: present - name: Check that the chromedriver exists stat: path: /usr/bin/chromedriver register: chromedriver_present -- name: Add chromedriver - copy: - src: "chromedriver" - dest: "/usr/bin/chromedriver" - owner: root - group: root - mode: '0777' diff --git a/roles/bash-for-pressbooks-dev/tasks/main.yml b/roles/bash-for-pressbooks-dev/tasks/main.yml index 27f4cc9a98..88d22baf0e 100644 --- a/roles/bash-for-pressbooks-dev/tasks/main.yml +++ b/roles/bash-for-pressbooks-dev/tasks/main.yml @@ -1,15 +1,19 @@ -- name: Add hh PPA +- name: Add hstr PPA apt_repository: repo: "ppa:ultradvorka/ppa" update_cache: yes -- name: Install hh + when: ansible_architecture == "x86_64" + +- name: Install hstr apt: - name: hh + name: hstr state: present + when: ansible_architecture == "x86_64" + - name: Checkout git aware prompt become_user: vagrant git: - repo: git://github.com/jimeh/git-aware-prompt.git + repo: https://github.com/jimeh/git-aware-prompt.git dest: ~/.bash/git-aware-prompt - name: Create .bash_aliases file with_dict: "{{ wordpress_sites }}" @@ -19,4 +23,4 @@ dest: ~/.bash_aliases vars: pathToLogs: "{{ www_root }}/{{ item.key }}/logs" - pathToCurrent: "{{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}" \ No newline at end of file + pathToCurrent: "{{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}" diff --git a/roles/common/defaults/main.yml b/roles/common/defaults/main.yml index 1dcdd38d15..2ba0cb8252 100644 --- a/roles/common/defaults/main.yml +++ b/roles/common/defaults/main.yml @@ -20,28 +20,21 @@ site_keys_by_env_pair: "[ {% endfor %} ]" -_apt_packages_default: +apt_packages_default: build-essential: "{{ apt_package_state }}" curl: "{{ apt_package_state }}" dbus: "{{ apt_package_state }}" + ghostscript: "{{ apt_package_state }}" git: "{{ apt_package_state }}" + imagemagick: "{{ apt_package_state }}" + libgs-dev: "{{ apt_package_state }}" libnss-myhostname: "{{ apt_package_state }}" - python: "{{ apt_package_state }}" + python3: "{{ apt_package_state }}" + python3-software-properties: "{{ apt_package_state }}" + python3-mysqldb: "{{ apt_package_state }}" + python3-pycurl: "{{ apt_package_state }}" unzip: "{{ apt_package_state }}" -apt_packages_python: - '2': - python-software-properties: "{{ apt_package_state }}" - python-mysqldb: "{{ apt_package_state }}" - python-pycurl: "{{ apt_package_state }}" - '3': - python3-software-properties: "{{ apt_package_state }}" - python3-mysqldb: "{{ apt_package_state }}" - python3-pycurl: "{{ apt_package_state }}" - -python_major_version: "{{ ansible_python_version[0] }}" -apt_packages_default: "{{ _apt_packages_default | combine(apt_packages_python[python_major_version]) }}" - apt_packages_custom: {} apt_packages: "{{ apt_packages_default | combine(apt_packages_custom) }}" diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml index ad0488c90b..8035cb64b4 100644 --- a/roles/common/handlers/main.yml +++ b/roles/common/handlers/main.yml @@ -9,7 +9,7 @@ - name: reload php-fpm service: - name: php7.3-fpm + name: php{{ php_version }}-fpm state: reloaded - import_tasks: reload_nginx.yml diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index aeff82462d..eaa2ba0aad 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -34,6 +34,18 @@ when: item.value.site_hosts | rejectattr('canonical', 'defined') | list | count tags: [letsencrypt, wordpress] +- name: Import PHP version specific vars + include_vars: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - '{{ php_version }}.yml' + - '7.4.yml' + paths: + - "{{ playbook_dir }}/roles/php/vars/" + + tags: [php, memcached] + - name: Verify dict format for apt package component variables fail: msg: "{{ lookup('template', 'package_vars_wrong_format_msg.j2') }}" @@ -61,13 +73,15 @@ memcached_packages: "{{ memcached_packages }}" php_extensions: "{{ php_extensions }}" sshd_packages: "{{ sshd_packages }}" - package_vars_wrong_format: "[{% for k,v in package_vars.items() | list if v | type_debug != 'dict' %}'{{ k }}',{% endfor %}]" + package_vars_wrong_format: "[{% for k,v in package_vars.items() if v | type_debug != 'dict' %}'{{ k }}',{% endfor %}]" tags: [sshd, memcached, php] - name: Validate Ubuntu version debug: msg: | - Trellis is built for Ubuntu 18.04 Bionic as of https://github.com/roots/trellis/pull/992 + Ubuntu 18.04 Bionic is the minimum supported version of Ubuntu in Trellis 1.0+ (as of https://github.com/roots/trellis/pull/992) + + 20.04 Focal is the recommend version for Trellis 1.7+ (as of https://github.com/roots/trellis/pull/1197) Your Ubuntu version is {{ ansible_distribution_version }} {{ ansible_distribution_release }} @@ -77,16 +91,27 @@ Development via Vagrant: `vagrant destroy && vagrant up` - Staging/Production: Create a new server with Ubuntu 18.04 and provision - when: ansible_distribution_release != 'bionic' + Staging/Production: Create a new server with Ubuntu 20.04 and provision + when: ansible_distribution_version is version('18.04', '<') - name: Check whether passlib is needed fail: msg: | - Ansible on OS X requires python passlib module to create user password hashes + Ansible on macOS requires Python's passlib module to create user password hashes + + If you're seeing this error message, you likely didn't use trellis-cli to create your project. + We highly recommend installing and using trellis-cli to manage your Trellis projects. + + See https://github.com/roots/trellis-cli for more documentation. + + For existing projects, you can run `trellis init` which will manage the dependencies automatically and fix this problem + as long as you use the `trellis` commands (like `trellis provision`) afterwards. + + To fix this manually, use pip to install the package: pip install passlib + + If pip is not installed, you'll have to install it first. + See https://stackoverflow.com/questions/17271319/how-do-i-install-pip-on-macos-or-os-x for many options. - sudo easy_install pip - pip install passlib when: env != 'development' and darwin_without_passlib | default(false) run_once: true @@ -147,13 +172,14 @@ generate_ssh_key: yes when: env == 'development' -- name: Retrieve SSH client IP - block: - - ipify_facts: +- block: + - name: Retrieve SSH client IP + ipify_facts: delegate_to: localhost become: no when: env != 'development' and ssh_client_ip_lookup | default(true) tags: [fail2ban, ferm] rescue: - - fail: + - name: Fail when unable to retrieve SSH client IP + fail: msg: "External IP resolution failed. Check that your DNS servers are working. Try to disable DNSCrypt if you are using it." diff --git a/roles/common/tasks/reload_nginx.yml b/roles/common/tasks/reload_nginx.yml index 952a6082e0..9d30718aa9 100644 --- a/roles/common/tasks/reload_nginx.yml +++ b/roles/common/tasks/reload_nginx.yml @@ -2,6 +2,7 @@ - name: reload nginx command: nginx -t notify: "{{ (role_path | basename == 'common') | ternary('perform nginx reload', omit) }}" + changed_when: true - name: perform nginx reload service: diff --git a/roles/connection/tasks/main.yml b/roles/connection/tasks/main.yml index 0675e77bf4..8acdef9606 100644 --- a/roles/connection/tasks/main.yml +++ b/roles/connection/tasks/main.yml @@ -12,13 +12,14 @@ register: preferred_host_key_algorithms when: - dynamic_host_key_algorithms | default(true) - - ansible_ssh_extra_args | default('') == '' + - not ansible_ssh_extra_args | default(None) - not (ansible_host_known or ssh_config_host_known) - name: Check whether Ansible can connect as {{ dynamic_user | default(true) | ternary('root', web_user) }} - local_action: | - command ansible {{ inventory_hostname }} -m raw -a whoami + command: | + ansible {{ inventory_hostname }} -m raw -a whoami -u {{ dynamic_user | default(true) | ternary('root', web_user) }} {{ cli_options | default('') }} -vvvv + delegate_to: localhost environment: ANSIBLE_SSH_ARGS: "{{ ssh_args_default }} {{ ansible_ssh_extra_args | default('') }}" failed_when: false diff --git a/roles/deploy/defaults/main.yml b/roles/deploy/defaults/main.yml index 8e04496fff..329075b9de 100644 --- a/roles/deploy/defaults/main.yml +++ b/roles/deploy/defaults/main.yml @@ -2,7 +2,7 @@ # - you must set a repository (no default) project_git_repo: "{{ project.repo }}" # - you can set the git ref to deploy (can be a branch, tag or commit hash) -project_version: "{{ project.branch | default('master') }}" +project_version: "{{ branch is defined | ternary(branch, project.branch) | default('master') }}" # The source_path is used to fetch the tags from git, or synchronise via rsync. This way # you do not have to download/sync the entire project on every deploy @@ -20,6 +20,7 @@ project_templates: - name: .env config src: roles/deploy/templates/env.j2 dest: .env + mode: '0600' # The shared_children is a list of all files/folders in your project that need to be linked to a path in `/shared`. # For example a sessions directory or an uploads folder. They are created if they don't exist, with the type @@ -28,10 +29,10 @@ project_templates: # project_shared_children: # - path: app/sessions # src: sessions -# mode: '0755' // <- optional, must be quoted, defaults to `'0755'` if `directory` or `'0644'` if `file` -# type: directory // <- optional, defaults to `directory`, options: `directory` or `file` +# mode: '0755' // <- optional, use an octal number starting with 0 or quote it, defaults to `'0755'` if `directory` or `'0644'` if `file` +# type: directory // <- optional, defaults to `directory`, options: `directory` or `file` project_shared_children: - - path: web/app/uploads + - path: "{{ project_public_path }}/{{ project_upload_path }}" src: uploads # The project_environment is a list of environment variables that can be used in hooks @@ -48,10 +49,17 @@ project_current_path: "{{ project.current_path | default('current') }}" # Whether to run `wp core update-db` at end of each deploy update_db_on_deploy: true +# Most scripts are used in development instead of remote servers. Use with caution. +composer_no_scripts: true +# Whether to autoload classes from classmap only. +composer_classmap_authoritative: true + # Helpers project: "{{ wordpress_sites[site] }}" project_root: "{{ www_root }}/{{ site }}" project_local_path: "{{ (lookup('env', 'USER') == 'vagrant') | ternary(project_root + '/' + project_current_path, project.local_path) }}" +project_public_path: "{{ project.public_path | default('web') }}" +project_upload_path: "{{ project.upload_path | default('app/uploads') }}" # Deploy hooks @@ -61,6 +69,7 @@ deploy_build_before: deploy_build_after: - "{{ playbook_dir }}/roles/deploy/hooks/build-after.yml" + - "{{ playbook_dir }}/deploy-hooks/build-after.yml" # - "{{ playbook_dir }}/deploy-hooks/sites/{{ site }}-build-after.yml" deploy_finalize_before: diff --git a/roles/deploy/files/tmp_multisite_constants.php b/roles/deploy/files/tmp_multisite_constants.php deleted file mode 100644 index e468cc8bfc..0000000000 --- a/roles/deploy/files/tmp_multisite_constants.php +++ /dev/null @@ -1,7 +0,0 @@ -<?php -error_reporting(E_ALL & ~E_NOTICE); -define('MULTISITE', false); -define('SUBDOMAIN_INSTALL', false); -define('WPMU_PLUGIN_DIR', null); -define('WP_PLUGIN_DIR', null); -define('WP_USE_THEMES', false); diff --git a/roles/deploy/hooks/build-after.yml b/roles/deploy/hooks/build-after.yml index c637d12882..4c088f6452 100644 --- a/roles/deploy/hooks/build-after.yml +++ b/roles/deploy/hooks/build-after.yml @@ -20,12 +20,13 @@ - composer_authentication.hostname is defined and composer_authentication.hostname != "" - composer_authentication.username is defined and composer_authentication.username != "" - composer_authentication.password is defined and composer_authentication.password != "" - loop: "{{ composer_authentications }}" + loop: "{{ composer_authentications | default([]) }}" loop_control: loop_var: composer_authentication label: "{{ composer_authentication.hostname }}" - name: Install Composer dependencies composer: - no_scripts: yes + no_scripts: "{{ composer_no_scripts }}" + classmap_authoritative: "{{ composer_classmap_authoritative }}" working_dir: "{{ deploy_helper.new_release_path }}" diff --git a/roles/deploy/hooks/finalize-after.yml b/roles/deploy/hooks/finalize-after.yml index f7c2a1f756..1d20d3b456 100644 --- a/roles/deploy/hooks/finalize-after.yml +++ b/roles/deploy/hooks/finalize-after.yml @@ -34,6 +34,6 @@ when: wp_installed.rc == 0 - name: Reload php-fpm - shell: sudo service php7.3-fpm reload + shell: sudo service php{{ php_version }}-fpm reload args: warn: false diff --git a/roles/deploy/hooks/finalize-before.yml b/roles/deploy/hooks/finalize-before.yml index 4c46ed35ea..b9fb5fd981 100644 --- a/roles/deploy/hooks/finalize-before.yml +++ b/roles/deploy/hooks/finalize-before.yml @@ -1,16 +1,45 @@ --- -- name: Create file with multisite constants defined as false - copy: - src: "tmp_multisite_constants.php" - dest: "{{ deploy_helper.shared_path }}/tmp_multisite_constants.php" +- name: Clean up unused, temporary PHP file with multisite constants that had been used for WordPress Installed checks. + file: + state: absent + path: "{{ deploy_helper.shared_path }}/tmp_multisite_constants.php" + +- name: WordPress Installed (non-multisite)? + block: + - name: "Invoke 'wp core is-installed' command" + command: wp core is-installed --skip-plugins --skip-themes + args: + chdir: "{{ deploy_helper.new_release_path }}" + register: wp_installed_singlesite + changed_when: false + failed_when: wp_installed_singlesite.stderr | length > 0 or wp_installed_singlesite.rc > 1 + + - name: Set "WordPress installed?" result variable (from non-multisite) + set_fact: + wp_installed: "{{ wp_installed_singlesite }}" + when: + - not project.multisite.enabled | default(false) + +- name: WordPress Installed (multisite)? + block: + - name: Set variables used in "WordPress Installed (multisite)?" check + set_fact: + multisite_non_setup_db_error: "WordPress database error Table '{{ site_env.db_name }}.wp_blogs' doesn't exist" + + - name: "Invoke 'wp core is-installed' command" + command: wp core is-installed --skip-plugins --skip-themes + args: + chdir: "{{ deploy_helper.new_release_path }}" + register: wp_installed_multisite + changed_when: false + failed_when: (wp_installed_multisite.stderr | length > 0 and wp_installed_multisite.stderr is not match(multisite_non_setup_db_error)) or wp_installed_multisite.rc > 1 + + - name: Set "WordPress installed?" result variable (from multisite) + set_fact: + wp_installed: "{{ wp_installed_multisite }}" + when: + - project.multisite.enabled | default(false) -- name: WordPress Installed? - command: wp core is-installed --skip-plugins --skip-themes --require={{ deploy_helper.shared_path }}/tmp_multisite_constants.php - args: - chdir: "{{ deploy_helper.new_release_path }}" - register: wp_installed - changed_when: false - failed_when: wp_installed.stderr | default("") != "" or wp_installed.rc > 1 - name: Get WP theme template and stylesheet roots shell: > diff --git a/roles/deploy/tasks/build.yml b/roles/deploy/tasks/build.yml index 0151fc0d8c..aa0e043e69 100644 --- a/roles/deploy/tasks/build.yml +++ b/roles/deploy/tasks/build.yml @@ -1,8 +1,16 @@ --- -- include_tasks: "{{ include_path }}" +- name: Check if deploy_build_before scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_build_before_paths with_items: "{{ deploy_build_before | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_build_before_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-build-before - name: Copy project templates @@ -19,12 +27,24 @@ with_items: "{{ project.project_copy_folders | default(project_copy_folders) }}" - name: Copy project folders - command: cp -rp {{ deploy_helper.current_path }}/{{ item.item }} {{ deploy_helper.new_release_path }} + copy: + src: "{{ deploy_helper.current_path }}/{{ item.item }}/" + dest: "{{ deploy_helper.new_release_path }}/{{ item.item }}" + remote_src: true + mode: 'preserve' with_items: "{{ project_folder_paths.results }}" when: item.stat.exists -- include_tasks: "{{ include_path }}" +- name: Check if deploy_build_after scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_build_after_paths with_items: "{{ deploy_build_after | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_build_after_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-build-after diff --git a/roles/deploy/tasks/finalize.yml b/roles/deploy/tasks/finalize.yml index 7ec6e32944..824c44f3e5 100644 --- a/roles/deploy/tasks/finalize.yml +++ b/roles/deploy/tasks/finalize.yml @@ -1,8 +1,16 @@ --- -- include_tasks: "{{ include_path }}" +- name: Check if deploy_finalize_before scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_finalize_before_paths with_items: "{{ deploy_finalize_before | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_finalize_before_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-finalize-before - name: Finalize the deploy @@ -13,10 +21,18 @@ state: finalize keep_releases: "{{ project.deploy_keep_releases | default(deploy_keep_releases | default(omit)) }}" -- include_tasks: "{{ include_path }}" +- name: Check if deploy_finalize_after scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_finalize_after_paths with_items: "{{ deploy_finalize_after | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_finalize_after_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-finalize-after - debug: diff --git a/roles/deploy/tasks/initialize.yml b/roles/deploy/tasks/initialize.yml index a78fefab90..5b1d48cc16 100644 --- a/roles/deploy/tasks/initialize.yml +++ b/roles/deploy/tasks/initialize.yml @@ -1,8 +1,16 @@ --- -- include_tasks: "{{ include_path }}" +- name: Check if deploy_initialize_before scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_initialize_before_paths with_items: "{{ deploy_initialize_before | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_initialize_before_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-initialize-before - name: Initialize @@ -11,8 +19,16 @@ path: "{{ project_root }}" state: present -- include_tasks: "{{ include_path }}" +- name: Check if deploy_initialize_after scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_initialize_after_paths with_items: "{{ deploy_initialize_after | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_initialize_after_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-initialize-after diff --git a/roles/deploy/tasks/main.yml b/roles/deploy/tasks/main.yml index 2e6b5c27f7..5478676a7b 100644 --- a/roles/deploy/tasks/main.yml +++ b/roles/deploy/tasks/main.yml @@ -1,8 +1,16 @@ --- -- include_tasks: "{{ include_path }}" +- name: Check if deploy_before scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_before_paths with_items: "{{ deploy_before | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_before_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-before - import_tasks: initialize.yml @@ -12,8 +20,16 @@ - import_tasks: share.yml - import_tasks: finalize.yml -- include_tasks: "{{ include_path }}" +- name: Check if deploy_after scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_after_paths with_items: "{{ deploy_after | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_after_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-after diff --git a/roles/deploy/tasks/prepare.yml b/roles/deploy/tasks/prepare.yml index 9181b43f51..2567d7986e 100644 --- a/roles/deploy/tasks/prepare.yml +++ b/roles/deploy/tasks/prepare.yml @@ -1,15 +1,18 @@ --- -- include_tasks: "{{ include_path }}" +- name: Check if deploy_prepare_before scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_prepare_before_paths with_items: "{{ deploy_prepare_before | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_prepare_before_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-prepare-before -- name: write unfinished file - file: - path: "{{ project_source_path }}/{{ deploy_helper.unfinished_filename }}" - state: touch - - name: Check for project repo subtree stat: path: "{{ project_source_path }}/{{ project.repo_subtree_path }}" @@ -24,22 +27,43 @@ - name: Create new release dir file: path: "{{ deploy_helper.new_release_path }}" + mode: '0755' state: directory - name: Run git archive to populate new build dir - shell: git archive {{ project_version }} | tar xf - -C {{ deploy_helper.new_release_path }} + shell: | + set -eo pipefail + git archive {{ project_version }} | tar xf - -C {{ deploy_helper.new_release_path }} args: chdir: "{{ project_source_path }}" + executable: /bin/bash when: project.repo_subtree_path is not defined - name: Run git archive with subdirectory to populate new build dir - shell: git archive {{ project_version }} {{ project.repo_subtree_path }} | tar -x --strip-components {{ project.repo_subtree_path.split('/') | count }} -f - -C {{ deploy_helper.new_release_path }} + shell: | + set -eo pipefail + git archive {{ project_version }} {{ project.repo_subtree_path }} | tar -x --strip-components {{ project.repo_subtree_path.split('/') | count }} -f - -C {{ deploy_helper.new_release_path }} args: chdir: "{{ project_source_path }}" + executable: /bin/bash when: project.repo_subtree_path is defined -- include_tasks: "{{ include_path }}" +- name: write unfinished file + file: + path: "{{ deploy_helper.new_release_path }}/{{ deploy_helper.unfinished_filename }}" + mode: '0744' + state: touch + +- name: Check if deploy_prepare_after scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_prepare_after_paths with_items: "{{ deploy_prepare_after | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_prepare_after_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-prepare-after diff --git a/roles/deploy/tasks/share.yml b/roles/deploy/tasks/share.yml index 294c05d4b9..12da9836c2 100644 --- a/roles/deploy/tasks/share.yml +++ b/roles/deploy/tasks/share.yml @@ -1,8 +1,16 @@ --- -- include_tasks: "{{ include_path }}" +- name: Check if deploy_share_before scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_share_before_paths with_items: "{{ deploy_share_before | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_share_before_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-share-before - name: Ensure shared sources are present -- directories @@ -32,6 +40,7 @@ - name: Ensure parent directories for shared paths are present file: path: "{{ deploy_helper.new_release_path }}/{{ item.path | dirname }}" + mode: '0777' state: directory with_items: "{{ project.project_shared_children | default(project_shared_children) }}" @@ -48,8 +57,16 @@ state: link with_items: "{{ project.project_shared_children | default(project_shared_children) }}" -- include_tasks: "{{ include_path }}" +- name: Check if deploy_share_after scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_share_after_paths with_items: "{{ deploy_share_after | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_share_after_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-share-after diff --git a/roles/deploy/tasks/update.yml b/roles/deploy/tasks/update.yml index 4170f658db..92ac949b00 100644 --- a/roles/deploy/tasks/update.yml +++ b/roles/deploy/tasks/update.yml @@ -1,8 +1,16 @@ --- -- include_tasks: "{{ include_path }}" +- name: Check if deploy_update_before scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_update_before_paths with_items: "{{ deploy_update_before | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_update_before_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-update-before - name: Add known_hosts @@ -27,14 +35,32 @@ - name: Failed connection to remote repo fail: msg: | - Git repo {{ project.repo }} cannot be accessed. Please verify the repository exists and you have SSH forwarding set up correctly. + Git repo {{ project.repo }} on branch {{ project_version }} cannot be accessed. Please verify the repository/branch are correct and you have SSH forwarding set up correctly. More info: > https://roots.io/trellis/docs/deploys/#ssh-keys > https://roots.io/trellis/docs/ssh-keys/#cloning-remote-repo-using-ssh-agent-forwarding + + Error: + {{ git_clone.stderr }} when: git_clone is failed -- include_tasks: "{{ include_path }}" +- name: Remove untracked files from project folder + command: git clean -fdx + args: + chdir: "{{ project_source_path }}" + register: git_clean + changed_when: not not(git_clean.stdout) + +- name: Check if deploy_update_after scripts exist + stat: + path: "{{ item }}" + delegate_to: localhost + register: deploy_update_after_paths with_items: "{{ deploy_update_after | default([]) }}" + +- include_tasks: "{{ include_path.item }}" + with_items: "{{ deploy_update_after_paths.results }}" loop_control: loop_var: include_path + when: include_path.stat.exists tags: deploy-update-after diff --git a/roles/deploy/vars/main.yml b/roles/deploy/vars/main.yml index aea2f70a6e..1a7612f9bb 100644 --- a/roles/deploy/vars/main.yml +++ b/roles/deploy/vars/main.yml @@ -7,5 +7,9 @@ wordpress_env_defaults: wp_home: "{{ project.ssl.enabled | default(false) | ternary('https', 'http') }}://{{ project.site_hosts | map(attribute='canonical') | first }}" wp_siteurl: "{{ project.ssl.enabled | default(false) | ternary('https', 'http') }}://{{ project.site_hosts | map(attribute='canonical') | first }}/wp" domain_current_site: "{{ project.site_hosts | map(attribute='canonical') | first }}" + git_sha: "{{ git_clone.after }}" + release_version: "{{ deploy_helper.new_release }}" + wp_debug_log: "{{ project_root }}/logs/debug.log" + wp_post_revisions: true site_env: "{{ wordpress_env_defaults | combine(vault_wordpress_env_defaults | default({}), project.env | default({}), vault_wordpress_sites[site].env) }}" diff --git a/roles/epubcheck/defaults/main.yml b/roles/epubcheck/defaults/main.yml deleted file mode 100644 index 4b7cd15dc8..0000000000 --- a/roles/epubcheck/defaults/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -epubcheck_release_url: https://github.com/w3c/epubcheck/releases/download/v4.1.1/epubcheck-4.1.1.zip diff --git a/roles/epubcheck/tasks/main.yml b/roles/epubcheck/tasks/main.yml index bd1ce24e01..8a6c13783a 100644 --- a/roles/epubcheck/tasks/main.yml +++ b/roles/epubcheck/tasks/main.yml @@ -1,13 +1,4 @@ ---- -- name: Download epubcheck - unarchive: - src: "{{ epubcheck_release_url }}" - dest: /tmp - remote_src: yes - -- name: Sync epubcheck into place - synchronize: - src: /tmp/{{ epubcheck_release_url[:-4] | basename }}/ - dest: /opt/epubcheck - delete: yes - delegate_to: "{{ inventory_hostname }}" +- name: Install EpubCheck + apt: + name: epubcheck + state: present diff --git a/roles/fail2ban/README.md b/roles/fail2ban/README.md index a3aa07b68e..16b707fc8a 100644 --- a/roles/fail2ban/README.md +++ b/roles/fail2ban/README.md @@ -1,19 +1,17 @@ -## What is ansible-fail2ban? +## What is this role? -It is an [ansible](http://www.ansible.com/home) role to install and configure fail2ban. +This role installs and configures [Fail2ban](https://github.com/fail2ban/fail2ban). -### What problem does it solve and why is it useful? - -Security is important and fail2ban is an excellent tool to harden your server with minimal or even no configuration. +Fail2ban is an excellent tool to harden your server with minimal configuration. ## Role variables -Below is a list of default values along with a description of what they do. +Below is a list of available variables, their description and their default value within Trellis. -``` +```yaml # Which log level should it be output as? -# Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG. Default: ERROR -fail2ban_loglevel: WARNING +# Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG. +fail2ban_loglevel: INFO # Where should log outputs be sent to? # SYSLOG, STDERR, STDOUT, file @@ -56,10 +54,25 @@ fail2ban_chain: INPUT # action_, action_mw, action_mwl fail2ban_action: action_ -# What services should fail2ban monitor? -# You can define fail2ban_services as an empty string to not monitor anything. +# Trellis by default only monitors SSH connections +# For available parameters, see fail2ban_services_custom below. +fail2ban_services_default: + - name: ssh + port: ssh + filter: sshd + logpath: /var/log/auth.log + +# In which folder did you place custom filters? +# Filters MUST have .conf.j2 extension to copied to the servers. +fail2ban_filter_templates_path: fail2ban_filters +``` + +The following list variable is available for custom services (to be set up in `group_vars`): + +```yaml +# Which additional services should fail2ban monitor? # You can define multiple services as a standard yaml list. -fail2ban_services: +fail2ban_services_custom: # The name of the service # REQUIRED. - name: ssh @@ -77,11 +90,11 @@ fail2ban_services: # OPTIONAL: Defaults to the protocol listed above. protocol: tcp - # What filter should it use? + # Which filter should it use? # REQUIRED. filter: sshd - # Which log path should it monitor? + # Which log file should it monitor? # REQUIRED. logpath: /var/log/auth.log @@ -96,24 +109,29 @@ fail2ban_services: # How should the ban be applied? # OPTIONAL: Defaults to the banaction listed above. banaction: iptables-multiport + ``` -## Example playbook +## Custom Settings -Let's say you want to edit a few values, you can do this by opening `group_vars/all` and then add the following: +To add services, you might add the following to `group_vars/all/security.yml`, e.g.: -``` -fail2ban_services: - - name: ssh - port: ssh - filter: sshd +```yaml +fail2ban_services_custom: + - name: wordpress + filter: wordpress logpath: /var/log/auth.log - - name: postfix - port: smtp,ssmtp - filter: postfix - logpath: /var/log/mail.log + maxretry: 2 ``` +To add the corresponding filter, add it to the folder specified in `fail2ban_filter_templates_path`, i.e. `fail2ban_filters` per default (next to the `group_vars` folder). The filter configuration must be of `.conf.j2` extension for Trellis to recognize it. + +Filters might be provided by plugins as `.conf` files: it is then enough to simply append the file name with `.j2`. It is not required to modify these provided filters, but you may customize them to your liking. + +To develop custom filters, refer to the Fail2ban wiki: [How Fail2ban works](https://github.com/fail2ban/fail2ban/wiki/How-fail2ban-works) and [How to ban something…](https://github.com/fail2ban/fail2ban/wiki/How-to-ban-something-other-as-host-(IP-address),-like-user-or-mail,-etc.) for simple filter rules or [Developing Filters](https://fail2ban.readthedocs.io/en/latest/filters.html) for complex setups. + +If you need to edit the default services, copy the `fail2ban_services_default` list from `roles/fail2ban/defaults/main.yml` to `group_vars/all/security.yml` and edit as needed. + ## Attribution Many thanks to [nickjj](https://github.com/nickjj/) for creating the [original version](https://github.com/nickjj/ansible-fail2ban/) of this role. diff --git a/roles/fail2ban/defaults/main.yml b/roles/fail2ban/defaults/main.yml index 473ae8a0d5..598172cfbd 100644 --- a/roles/fail2ban/defaults/main.yml +++ b/roles/fail2ban/defaults/main.yml @@ -5,7 +5,7 @@ fail2ban_loglevel: INFO fail2ban_logtarget: /var/log/fail2ban.log fail2ban_socket: /var/run/fail2ban/fail2ban.sock -fail2ban_ignoreip: 127.0.0.1/8 {{ ip_whitelist | join(' ') }} +fail2ban_ignoreip: "{{ ip_whitelist | join(' ') }}" fail2ban_bantime: 600 fail2ban_maxretry: 6 @@ -19,8 +19,13 @@ fail2ban_chain: INPUT fail2ban_action: action_ -fail2ban_services: +fail2ban_services_default: - name: ssh port: ssh filter: sshd logpath: /var/log/auth.log + +fail2ban_services_custom: [] +fail2ban_services: "{{ fail2ban_services_default + fail2ban_services_custom }}" + +fail2ban_filter_templates_path: fail2ban_filters diff --git a/roles/fail2ban/tasks/main.yml b/roles/fail2ban/tasks/main.yml index dcc6adc5ab..2a900a2a87 100644 --- a/roles/fail2ban/tasks/main.yml +++ b/roles/fail2ban/tasks/main.yml @@ -11,12 +11,37 @@ template: src: "{{ item }}.j2" dest: /etc/fail2ban/{{ item }} + mode: '0644' with_items: - jail.local - fail2ban.local notify: - restart fail2ban +- name: build list of fail2ban filter templates + find: + paths: + - "{{ playbook_dir }}/roles/fail2ban/templates/filters" + - "{{ fail2ban_filter_templates_path }}" + pattern: "*.conf.j2" + become: no + delegate_to: localhost + register: fail2ban_filter_templates + +- name: ensure configuration directory exists + file: + path: /etc/fail2ban/filter.d/ + state: directory + mode: '0755' + +- name: template fail2ban filters + template: + src: "{{ item }}" + dest: "/etc/fail2ban/filter.d/{{ item | basename | regex_replace('.j2$', '') }}" + mode: '0644' + with_items: "{{ fail2ban_filter_templates.files | map(attribute='path') | list | sort(True) }}" + notify: restart fail2ban + - name: ensure fail2ban starts on a fresh reboot service: name: fail2ban diff --git a/roles/fail2ban/templates/filters/wordpress-wp-login.conf.j2 b/roles/fail2ban/templates/filters/wordpress-wp-login.conf.j2 new file mode 100644 index 0000000000..d0f9271098 --- /dev/null +++ b/roles/fail2ban/templates/filters/wordpress-wp-login.conf.j2 @@ -0,0 +1,2 @@ +[Definition] +failregex = ^<HOST> .* "POST .*wp-login\.php diff --git a/roles/fail2ban/templates/filters/wordpress-xmlrpc.conf.j2 b/roles/fail2ban/templates/filters/wordpress-xmlrpc.conf.j2 new file mode 100644 index 0000000000..6d8547146f --- /dev/null +++ b/roles/fail2ban/templates/filters/wordpress-xmlrpc.conf.j2 @@ -0,0 +1,2 @@ +[Definition] +failregex = ^<HOST> .* "POST .*xmlrpc\.php diff --git a/roles/ferm/handlers/main.yml b/roles/ferm/handlers/main.yml index 16985eb8f5..2af34d8500 100644 --- a/roles/ferm/handlers/main.yml +++ b/roles/ferm/handlers/main.yml @@ -1,4 +1,4 @@ --- - name: restart ferm service: name=ferm state=restarted - when: ferm_enabled \ No newline at end of file + when: ferm_enabled | bool diff --git a/roles/ferm/tasks/main.yml b/roles/ferm/tasks/main.yml index 63b0b0a4d0..2848cb488a 100644 --- a/roles/ferm/tasks/main.yml +++ b/roles/ferm/tasks/main.yml @@ -19,7 +19,7 @@ file: path: "{{ item }}" state: directory - mode: 0750 + mode: '0750' with_items: - /etc/ferm/ferm.d - /etc/ferm/filter-input.d @@ -28,6 +28,7 @@ template: src: "{{ item }}.j2" dest: /{{ item }} + mode: '0644' with_items: - etc/default/ferm - etc/ferm/ferm.conf @@ -55,6 +56,7 @@ {% else %} dest=/etc/ferm/filter-input.d/{{ item.weight | default('50') }}_{{ item.type }}_{{ item.dport[0] }}.conf {% endif %} + mode=0644 with_flattened: - "{{ ferm_input_list }}" - "{{ ferm_input_group_list }}" @@ -65,7 +67,7 @@ - name: ensure iptables rules are enabled command: ferm --slow /etc/ferm/ferm.conf changed_when: false - when: ferm_enabled + when: ferm_enabled | bool - name: ensure iptables rules are disabled command: ferm --flush /etc/ferm/ferm.conf diff --git a/roles/letsencrypt/defaults/main.yml b/roles/letsencrypt/defaults/main.yml index 4b9d9b26b8..f900239dbb 100644 --- a/roles/letsencrypt/defaults/main.yml +++ b/roles/letsencrypt/defaults/main.yml @@ -1,5 +1,5 @@ sites_using_letsencrypt: "[{% for name, site in wordpress_sites.items() | list if site.ssl.enabled and site.ssl.provider | default('manual') == 'letsencrypt' %}'{{ name }}',{% endfor %}]" -site_uses_letsencrypt: ssl_enabled and item.value.ssl.provider | default('manual') == 'letsencrypt' +site_uses_letsencrypt: "{{ (ssl_enabled and item.value.ssl.provider | default('manual') == 'letsencrypt') | bool }}" missing_hosts: "{{ site_hosts | difference((current_hosts.results | selectattr('item.key', 'equalto', item.key) | selectattr('stdout_lines', 'defined') | sum(attribute='stdout_lines', start=[]) | map('trim') | list | join(' ')).split(' ')) }}" letsencrypt_cert_ids: "{ {% for item in (generate_cert_ids | default({'results':[{'skipped':True}]})).results if item is not skipped %}'{{ item.item.key }}':'{{ item.stdout }}', {% endfor %} }" @@ -28,6 +28,10 @@ letsencrypt_ca: 'https://acme-v02.api.letsencrypt.org' letsencrypt_account_key: '{{ acme_tiny_data_directory }}/account.key' +letsencrypt_intermediate_cert_path: /etc/ssl/certs/lets-encrypt-x3-cross-signed.pem +letsencrypt_intermediate_cert_url: 'https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem' +letsencrypt_intermediate_cert_sha256sum: 'e446c5e9dbef9d09ac9f7027c034602492437a05ff6c40011d7235fca639c79a' + letsencrypt_keys_dir: "{{ nginx_ssl_path }}/letsencrypt" letsencrypt_certs_dir: "{{ nginx_ssl_path }}/letsencrypt" diff --git a/roles/letsencrypt/library/test_challenges.py b/roles/letsencrypt/library/test_challenges.py index d7f4a8cc2c..8d5899e745 100644 --- a/roles/letsencrypt/library/test_challenges.py +++ b/roles/letsencrypt/library/test_challenges.py @@ -1,13 +1,8 @@ -#!/usr/bin/python +#!/usr/bin/python3 # -*- coding: utf-8 -*- import socket - -try: - from httplib import HTTPConnection, HTTPException -except ImportError: - # Python 3 - from http.client import HTTPConnection, HTTPException +from http.client import HTTPConnection, HTTPException DOCUMENTATION = ''' --- diff --git a/roles/letsencrypt/tasks/certificates.yml b/roles/letsencrypt/tasks/certificates.yml index 480d910253..110af4219e 100644 --- a/roles/letsencrypt/tasks/certificates.yml +++ b/roles/letsencrypt/tasks/certificates.yml @@ -9,20 +9,23 @@ - name: Ensure correct permissions on private keys file: path: "{{ letsencrypt_keys_dir }}/{{ item.key }}.key" - mode: 0600 + mode: '0600' when: site_uses_letsencrypt with_dict: "{{ wordpress_sites }}" - name: Generate Lets Encrypt certificate IDs shell: | + set -eo pipefail echo "{{ [site_hosts | join(' '), letsencrypt_ca, acme_tiny_commit] | join('\n') }}" | cat {{ letsencrypt_account_key }} {{ letsencrypt_keys_dir }}/{{ item.key }}.key - | md5sum | cut -c -7 + args: + executable: /bin/bash register: generate_cert_ids changed_when: false when: site_uses_letsencrypt with_dict: "{{ wordpress_sites }}" - tags: [wordpress, wordpress-setup, nginx-includes, nginx-sites] + tags: [wordpress, wordpress-setup, wordpress-setup-nginx, nginx-includes] - name: Generate CSRs shell: "openssl req -new -sha256 -key '{{ letsencrypt_keys_dir }}/{{ item.key }}.key' -subj '/' -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf '[SAN]\nsubjectAltName=DNS:{{ site_hosts | join(',DNS:') }}')) > {{ acme_tiny_data_directory }}/csrs/{{ item.key }}-{{ letsencrypt_cert_ids[item.key] }}.csr" @@ -36,7 +39,8 @@ template: src: renew-certs.py dest: "{{ acme_tiny_data_directory }}/renew-certs.py" - mode: 0700 + mode: '0700' + tags: [wordpress, wordpress-setup, wordpress-setup-nginx, nginx-includes] - name: Generate the certificates command: ./renew-certs.py @@ -45,3 +49,4 @@ register: generate_certs changed_when: generate_certs.stdout is defined and 'Created' in generate_certs.stdout notify: reload nginx + tags: [wordpress, wordpress-setup, wordpress-setup-nginx, nginx-includes] diff --git a/roles/letsencrypt/tasks/main.yml b/roles/letsencrypt/tasks/main.yml index 27c4b86ac7..b65a534087 100644 --- a/roles/letsencrypt/tasks/main.yml +++ b/roles/letsencrypt/tasks/main.yml @@ -8,8 +8,8 @@ cron_file: letsencrypt-certificate-renewal name: letsencrypt certificate renewal user: root - job: cd {{ acme_tiny_data_directory }} && ./renew-certs.py && /usr/sbin/service nginx reload + job: cd {{ acme_tiny_data_directory }} && ./renew-certs.py ; /usr/sbin/service nginx reload day: "{{ letsencrypt_cronjob_daysofmonth }}" - hour: 4 - minute: 30 + hour: "4" + minute: "30" state: present diff --git a/roles/letsencrypt/tasks/nginx.yml b/roles/letsencrypt/tasks/nginx.yml index c0578e4933..877ed09c92 100644 --- a/roles/letsencrypt/tasks/nginx.yml +++ b/roles/letsencrypt/tasks/nginx.yml @@ -3,6 +3,7 @@ template: src: acme-challenge-location.conf.j2 dest: "{{ nginx_path }}/acme-challenge-location.conf" + mode: '0644' - name: Get list of hosts in current Nginx conf shell: | @@ -17,6 +18,7 @@ template: src: nginx-challenge-site.conf.j2 dest: "{{ nginx_path }}/sites-available/letsencrypt-{{ item.key }}.conf" + mode: '0644' register: challenge_site_confs when: - site_uses_letsencrypt @@ -39,10 +41,10 @@ when: challenge_site_confs is changed or challenge_sites_enabled is changed - name: Create test Acme Challenge file - shell: touch {{ acme_tiny_challenges_directory }}/ping.txt - args: - creates: "{{ acme_tiny_challenges_directory }}/ping.txt" - warn: false + file: + path: "{{ acme_tiny_challenges_directory }}/ping.txt" + state: touch + mode: '0644' - name: Test Acme Challenges test_challenges: diff --git a/roles/letsencrypt/tasks/setup.yml b/roles/letsencrypt/tasks/setup.yml index 6d3cd762aa..c23ba5918d 100644 --- a/roles/letsencrypt/tasks/setup.yml +++ b/roles/letsencrypt/tasks/setup.yml @@ -1,4 +1,26 @@ --- +- name: Fail if letsencrypt_contact_emails is not defined + fail: + msg: > + Error: the required `letsencrypt_contact_emails` variable is not defined or invalid. + + + Please define it in `groups_vars/all/main.yml` with at least one email (as a list/array, *not* a string): + + letsencrypt_contact_emails: + - changeme@example.com + + The contact email is used by Let's Encrypt to send expiry notices when a certificate is coming up for renewal. + + + See https://letsencrypt.org/docs/expiration-emails/ for more information. + + + Since Trellis attempts to renew certificates after {{ letsencrypt_min_renewal_age }} days ({{ 90 - letsencrypt_min_renewal_age }} days before renewal), + getting an expiry notice email means something has gone wrong giving you enough notice to fix the problem. + + when: (letsencrypt_contact_emails is not defined) or (letsencrypt_contact_emails is string) + - name: Create directories and set permissions file: mode: "{{ item.mode | default(omit) }}" @@ -24,12 +46,14 @@ copy: src: "{{ letsencrypt_account_key_source_file }}" dest: "{{ letsencrypt_account_key }}" + mode: '0700' when: letsencrypt_account_key_source_file is defined - name: Copy Lets Encrypt account key source contents copy: content: "{{ letsencrypt_account_key_source_content | trim }}" dest: "{{ letsencrypt_account_key }}" + mode: '0700' when: letsencrypt_account_key_source_content is defined - name: Generate a new account key diff --git a/roles/letsencrypt/templates/renew-certs.py b/roles/letsencrypt/templates/renew-certs.py index 6d4b5f0304..b13ed8efa6 100644 --- a/roles/letsencrypt/templates/renew-certs.py +++ b/roles/letsencrypt/templates/renew-certs.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys @@ -10,38 +10,52 @@ letsencrypt_cert_ids = {{ letsencrypt_cert_ids }} for site in {{ sites_using_letsencrypt }}: - bundled_cert_path = os.path.join('{{ letsencrypt_certs_dir }}', site + '-' + letsencrypt_cert_ids[site] + '-bundled.cert') + csr_path = os.path.join('{{ acme_tiny_data_directory }}', 'csrs', '{}-{}.csr'.format(site, letsencrypt_cert_ids[site])) + bundled_cert_path = os.path.join('{{ letsencrypt_certs_dir }}', '{}-bundled.cert'.format(site)) + bundled_hashed_cert_path = os.path.join('{{ letsencrypt_certs_dir }}', '{}-{}-bundled.cert'.format(site, letsencrypt_cert_ids[site])) - if os.access(bundled_cert_path, os.F_OK): - stat = os.stat(bundled_cert_path) - print('Certificate file ' + bundled_cert_path + ' already exists') + # Generate or update root cert if needed + if not os.access(csr_path, os.F_OK): + failed = True + print('The required CSR file {} does not exist. This could happen if you changed site_hosts and have ' + 'not yet rerun the letsencrypt role. Create the CSR file by re-provisioning (running the Trellis ' + 'server.yml playbook) with `--tags letsencrypt`'.format(csr_path), file=sys.stderr) + continue - if time.time() - stat.st_mtime < {{ letsencrypt_min_renewal_age }} * 86400: - print(' The certificate is younger than {{ letsencrypt_min_renewal_age }} days. Not creating a new certificate.\n') - continue + elif os.access(bundled_hashed_cert_path, os.F_OK) and time.time() - os.stat(bundled_hashed_cert_path).st_mtime < {{ letsencrypt_min_renewal_age }} * 86400: + print('Certificate file {} already exists and is younger than {{ letsencrypt_min_renewal_age }} days. ' + 'Not creating a new certificate.'.format(bundled_hashed_cert_path)) - print('Generating certificate for ' + site) + else: + cmd = ('/usr/bin/env python3 {{ acme_tiny_software_directory }}/acme_tiny.py ' + '--quiet ' + '--ca {{ letsencrypt_ca }} ' + '--account-key {{ letsencrypt_account_key }} ' + '--csr {} ' + '--contact {{ letsencrypt_contact_emails | map('regex_replace', '(^.*$)', 'mailto:\\1') | join (' ') }} ' + '--acme-dir {{ acme_tiny_challenges_directory }}' + ).format(csr_path) + + try: + new_bundled_cert = check_output(cmd, stderr=STDOUT, shell=True, universal_newlines=True) + except CalledProcessError as e: + failed = True + print('Error while generating certificate for {}\n{}'.format(site, e.output), file=sys.stderr) + continue + else: + with open(bundled_hashed_cert_path, 'w') as bundled_hashed_cert_file: + bundled_hashed_cert_file.write(new_bundled_cert) + with open(bundled_cert_path, 'w') as bundled_cert_file: + bundled_cert_file.write(new_bundled_cert) - cmd = ( - '/usr/bin/env python {{ acme_tiny_software_directory }}/acme_tiny.py ' - '--quiet ' - '--ca {{ letsencrypt_ca }} ' - '--account-key {{ letsencrypt_account_key }} ' - '--csr {{ acme_tiny_data_directory }}/csrs/{0}-{1}.csr ' - '--acme-dir {{ acme_tiny_challenges_directory }}' - ).format(site, letsencrypt_cert_ids[site]) + if not os.access(bundled_cert_path, os.F_OK): + with open(bundled_hashed_cert_path, 'rb') as bundled_hashed_cert_file: + bundled_hashed_cert = bundled_hashed_cert_file.read() - try: - cert = check_output(cmd, stderr=STDOUT, shell=True) - except CalledProcessError as e: - failed = True - print('Error while generating certificate for ' + site) - print(e.output) - else: - with open(bundled_cert_path, 'w') as cert_file: - cert_file.write(cert) + with open(bundled_cert_path, 'w') as bundled_cert_file: + bundled_cert_file.write(bundled_hashed_cert) + print('Created bundled certificate {}'.format(bundled_cert_path)) - print('Created certificate for ' + site) if failed: sys.exit(1) diff --git a/roles/local-development/tasks/main.yml b/roles/local-development/tasks/main.yml index c4275bd270..7495220d06 100644 --- a/roles/local-development/tasks/main.yml +++ b/roles/local-development/tasks/main.yml @@ -77,7 +77,7 @@ login_password: "{{ mysql_root_password }}" with_dict: "{{ wordpress_sites }}" - name: Install WP-CLI login helper - command: wp login install --activate + command: wp login install --activate --yes args: chdir: "{{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}/" with_dict: "{{ wordpress_sites }}" diff --git a/roles/mariadb/defaults/main.yml b/roles/mariadb/defaults/main.yml index 89f7b57ea0..d337299bfb 100644 --- a/roles/mariadb/defaults/main.yml +++ b/roles/mariadb/defaults/main.yml @@ -1,6 +1,6 @@ mariadb_keyserver: "hkp://keyserver.ubuntu.com:80" mariadb_keyserver_id: "0xF1656F24C74CD1D8" -mariadb_ppa: "deb [arch=amd64] http://nyc2.mirrors.digitalocean.com/mariadb/repo/10.2/ubuntu {{ ansible_distribution_release }} main" +mariadb_ppa: "deb http://mariadb.mirror.globo.tech/repo/10.5/ubuntu {{ ansible_distribution_release }} main" mariadb_client_package: mariadb-client mariadb_server_package: mariadb-server diff --git a/roles/mariadb/tasks/main.yml b/roles/mariadb/tasks/main.yml index f4e21e561e..0da81f6f95 100644 --- a/roles/mariadb/tasks/main.yml +++ b/roles/mariadb/tasks/main.yml @@ -29,9 +29,18 @@ dest: /etc/mysql/conf.d owner: root group: root - when: mysql_binary_logging_disabled + mode: '0644' + when: mysql_binary_logging_disabled | bool notify: restart mysql server + - name: Copy .my.cnf file with root password credentials. + template: + src: my.cnf.j2 + dest: ~/.my.cnf + owner: root + group: root + mode: '0600' + - name: Set root user password mysql_user: name: root @@ -39,25 +48,19 @@ password: "{{ mysql_root_password }}" check_implicit_admin: yes state: present + no_log: true with_items: - "{{ inventory_hostname }}" - 127.0.0.1 - ::1 - localhost - - name: Copy .my.cnf file with root password credentials. - template: - src: my.cnf.j2 - dest: ~/.my.cnf - owner: root - group: root - mode: 0600 - - name: Delete anonymous MySQL server users mysql_user: user: "" host: "{{ item }}" state: absent + no_log: true with_items: - localhost - "{{ inventory_hostname }}" @@ -67,5 +70,6 @@ mysql_db: name: test state: absent + no_log: true when: not sites_using_remote_db | count diff --git a/roles/memcached/defaults/main.yml b/roles/memcached/defaults/main.yml index 8bdd3e663a..0e9ba87761 100644 --- a/roles/memcached/defaults/main.yml +++ b/roles/memcached/defaults/main.yml @@ -8,7 +8,6 @@ memcached_port_udp: 0 memcached_packages_default: memcached: "{{ apt_package_state }}" - php-memcached: "{{ apt_package_state }}" memcached_packages_custom: {} -memcached_packages: "{{ memcached_packages_default | combine(memcached_packages_custom) }}" +memcached_packages: "{{ memcached_packages_default | combine(php_memcached_packages, memcached_packages_custom) }}" diff --git a/roles/memcached/tasks/main.yml b/roles/memcached/tasks/main.yml index dc62343926..04bec4ffb4 100644 --- a/roles/memcached/tasks/main.yml +++ b/roles/memcached/tasks/main.yml @@ -10,12 +10,13 @@ template: src: memcached.conf.j2 dest: /etc/memcached.conf + mode: '0644' notify: restart memcached - name: Set the max open file descriptors sysctl: name: fs.file-max - value: "{{ memcached_fs_file_max }}" + value: "{{ memcached_fs_file_max | string }}" state: present - name: Start the memcached service diff --git a/roles/nginx/defaults/main.yml b/roles/nginx/defaults/main.yml index c82941bcc7..69433b33fb 100644 --- a/roles/nginx/defaults/main.yml +++ b/roles/nginx/defaults/main.yml @@ -1,5 +1,7 @@ --- -nginx_ppa: "ppa:nginx/mainline" +nginx_keyserver: "https://nginx.org/keys/nginx_signing.key" +nginx_keyserver_id: "ABF5BD827BD9BF62" +nginx_ppa: "deb http://nginx.org/packages/mainline/ubuntu {{ ansible_distribution_release }} nginx" nginx_package: nginx nginx_conf: nginx.conf.j2 nginx_path: /etc/nginx diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml index 3f0202792c..676679ce1b 100644 --- a/roles/nginx/tasks/main.yml +++ b/roles/nginx/tasks/main.yml @@ -1,4 +1,9 @@ --- +- name: Add Nginx APT key + apt_key: + keyserver: "{{ nginx_keyserver }}" + id: "{{ nginx_keyserver_id }}" + - name: Add Nginx PPA apt_repository: repo: "{{ nginx_ppa }}" @@ -10,31 +15,33 @@ state: "{{ nginx_package_state | default(apt_package_state) }}" cache_valid_time: "{{ apt_cache_valid_time }}" +- name: Ensure site directories exist + file: + path: "{{ nginx_path }}/{{ item }}" + state: directory + mode: '0755' + with_items: + - sites-available + - sites-enabled + - name: Create SSL directory file: - mode: 0700 + mode: '0700' path: "{{ nginx_path }}/ssl" state: directory -- name: Generate strong unique Diffie-Hellman group. - command: openssl dhparam -out dhparams.pem 2048 - args: - chdir: "{{ nginx_path }}/ssl" - creates: "{{ nginx_path }}/ssl/dhparams.pem" - when: sites_use_ssl - notify: reload nginx - tags: [diffie-hellman, letsencrypt, wordpress, wordpress-setup, nginx-includes, nginx-sites] - - name: Copy h5bp configs copy: src: templates/h5bp dest: "{{ nginx_path }}" + mode: '0755' notify: reload nginx - name: Create nginx.conf template: src: "{{ nginx_conf }}" dest: "{{ nginx_path }}/nginx.conf" + mode: '0644' notify: reload nginx tags: nginx-includes diff --git a/roles/nginx/templates/h5bp/directive-only/ssl-stapling.conf b/roles/nginx/templates/h5bp/directive-only/ssl-stapling.conf index d15bf972ba..95cc175ce4 100644 --- a/roles/nginx/templates/h5bp/directive-only/ssl-stapling.conf +++ b/roles/nginx/templates/h5bp/directive-only/ssl-stapling.conf @@ -1,9 +1,34 @@ -# OCSP stapling... +# ---------------------------------------------------------------------- +# | Online Certificate Status Protocol stapling | +# ---------------------------------------------------------------------- + +# OCSP is a lightweight, only one record to help clients verify the validity of +# the server certificate. +# OCSP stapling allows the server to send its cached OCSP record during the TLS +# handshake, without the need of 3rd party OCSP responder. +# +# https://wiki.mozilla.org/Security/Server_Side_TLS#OCSP_Stapling +# https://tools.ietf.org/html/rfc6066#section-8 +# https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_stapling +# +# (1) Use Cloudflare 1.1.1.1 DNS resolver +# https://developers.cloudflare.com/1.1.1.1/setting-up-1.1.1.1/ +# +# (2) Use Google 8.8.8.8 DNS resolver +# https://developers.google.com/speed/public-dns/docs/using +# +# (3) Use Dyn DNS resolver +# https://help.dyn.com/internet-guide-setup/ + ssl_stapling on; ssl_stapling_verify on; -#trusted cert must be made up of your intermediate certificate followed by root certificate -#ssl_trusted_certificate /path/to/ca.crt; - -resolver 8.8.8.8 8.8.4.4 216.146.35.35 216.146.36.36 valid=60s; +resolver + # (1) + 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] + # (2) + 8.8.8.8 8.8.4.4 [2001:4860:4860::8888] [2001:4860:4860::8844] + # (3) + # 216.146.35.35 216.146.36.36 + valid=60s; resolver_timeout 2s; diff --git a/roles/nginx/templates/h5bp/directive-only/ssl.conf b/roles/nginx/templates/h5bp/directive-only/ssl.conf index cf5cfaa6cc..20d98766b3 100644 --- a/roles/nginx/templates/h5bp/directive-only/ssl.conf +++ b/roles/nginx/templates/h5bp/directive-only/ssl.conf @@ -1,10 +1,6 @@ -# Protect against the BEAST and POODLE attacks by not using SSLv3 at all. If you need to support older browsers (IE6) you may need to add -# SSLv3 to the list of protocols below. -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - -# Ciphers set to best allow protection from Beast, while providing forwarding secrecy, as defined by Mozilla (Intermediate Set) - https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx -ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; -ssl_prefer_server_ciphers on; +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers EECDH+CHACHA20:EECDH+AES; +ssl_prefer_server_ciphers off; # Optimize SSL by caching session parameters for 10 minutes. This cuts down on the number of expensive SSL handshakes. # The handshake is the most CPU-intensive operation, and by default it is re-negotiated on every new/parallel connection. @@ -25,7 +21,7 @@ ssl_session_timeout 24h; # # Note that you'll have to define and rotate the keys securely by yourself. In absence # of such infrastructure, consider turning off session tickets: -#ssl_session_tickets off; +ssl_session_tickets off; # Use a higher keepalive timeout to reduce the need for repeated handshakes keepalive_timeout 300s; # up from 75 secs default diff --git a/roles/nginx/templates/h5bp/mime.types b/roles/nginx/templates/h5bp/mime.types index 7c3b1e7386..972fd08958 100644 --- a/roles/nginx/templates/h5bp/mime.types +++ b/roles/nginx/templates/h5bp/mime.types @@ -14,7 +14,7 @@ types { # Normalize to standard type. # https://tools.ietf.org/html/rfc4329#section-7.2 - application/javascript js; + application/javascript js mjs; # Manifest files diff --git a/roles/php/defaults/main.yml b/roles/php/defaults/main.yml index f59a03b044..c6ef36e940 100644 --- a/roles/php/defaults/main.yml +++ b/roles/php/defaults/main.yml @@ -2,18 +2,19 @@ disable_default_pool: true memcached_sessions: false php_extensions_default: - php7.3-cli: "{{ apt_package_state }}" - php7.3-common: "{{ apt_package_state }}" - php7.3-curl: "{{ apt_package_state }}" - php7.3-dev: "{{ apt_package_state }}" - php7.3-fpm: "{{ apt_package_state }}" - php7.3-gd: "{{ apt_package_state }}" - php7.3-mbstring: "{{ apt_package_state }}" - php7.3-mysql: "{{ apt_package_state }}" - php7.3-opcache: "{{ apt_package_state }}" - php7.3-xml: "{{ apt_package_state }}" - php7.3-xmlrpc: "{{ apt_package_state }}" - php7.3-zip: "{{ apt_package_state }}" + php7.4-cli: "{{ apt_package_state }}" + php7.4-common: "{{ apt_package_state }}" + php7.4-curl: "{{ apt_package_state }}" + php7.4-dev: "{{ apt_package_state }}" + php7.4-fpm: "{{ apt_package_state }}" + php7.4-gd: "{{ apt_package_state }}" + php7.4-mbstring: "{{ apt_package_state }}" + php7.4-mysql: "{{ apt_package_state }}" + php7.4-opcache: "{{ apt_package_state }}" + php7.4-redis: "{{ apt_package_state }}" + php7.4-xml: "{{ apt_package_state }}" + php7.4-xmlrpc: "{{ apt_package_state }}" + php7.4-zip: "{{ apt_package_state }}" php_extensions_custom: {} php_extensions: "{{ php_extensions_default | combine(php_extensions_custom) }}" @@ -25,6 +26,7 @@ php_max_execution_time: 120 php_max_input_time: 300 php_max_input_vars: 1000 php_memory_limit: 96M +php_cli_memory_limit: -1 php_mysqlnd_collect_memory_statistics: 'Off' php_post_max_size: 25M php_sendmail_path: /usr/sbin/ssmtp -t @@ -32,7 +34,6 @@ php_session_save_path: /tmp php_session_cookie_httponly: 'On' php_session_cookie_secure: 'Off' php_upload_max_filesize: 25M -php_track_errors: 'Off' php_timezone: '{{ ntp_timezone }}' php_output_buffering: 'Off' diff --git a/roles/php/tasks/main.yml b/roles/php/tasks/main.yml index ceaf843c05..eaec4b4f16 100644 --- a/roles/php/tasks/main.yml +++ b/roles/php/tasks/main.yml @@ -1,38 +1,62 @@ --- -- name: Add PHP 7.3 PPA +- name: Add PHP PPA apt_repository: repo: "ppa:ondrej/php" update_cache: yes -- name: Install PHP 7.3 +- name: Install PHP and extensions apt: name: "{{ item.key }}" state: "{{ item.value }}" cache_valid_time: "{{ apt_cache_valid_time }}" + install_recommends: no with_dict: "{{ php_extensions }}" -- name: Start php7.3-fpm service +- name: Ensure correct PHP version selected + community.general.alternatives: + name: php + path: /usr/bin/php{{ php_version }} + +- name: Start php fpm service service: - name: php7.3-fpm + name: "php{{ php_version }}-fpm" state: started enabled: true -- name: Check for existing php7.2-fpm service - stat: - path: /etc/init.d/php7.2-fpm - register: php72_status +- name: Find existing php fpm services + find: + paths: /etc/init.d + patterns: "^php((?!{{ php_version }})(\\d\\.\\d))-fpm$" + use_regex: true + register: old_php_fpm_services -- name: Stop php7.2-fpm service if it exists +- name: Stop old php-fpm services service: - name: php7.2-fpm + name: "{{ item.path | basename }}" state: stopped enabled: false - register: service_stopped - when: php72_status.stat.exists + loop: "{{ old_php_fpm_services.files }}" + loop_control: + label: "{{ item.path | basename }}" notify: reload php-fpm -- name: PHP configuration file +- name: Copy PHP-FPM configuration file template: - src: php.ini.j2 - dest: /etc/php/7.3/fpm/php.ini + src: php-fpm.ini.j2 + dest: /etc/php/{{ php_version }}/fpm/php.ini + mode: '0644' + notify: reload php-fpm + +- name: Copy PHP CLI configuration file + template: + src: php-cli.ini.j2 + dest: /etc/php/{{ php_version }}/cli/php.ini + mode: '0644' + +- name: Change ImageMagick policy.xml to allow for PDFs + replace: + path: /etc/ImageMagick-6/policy.xml + regexp: '<policy domain="coder" rights="none" pattern="PDF" />' + replace: '<policy domain="coder" rights="read" pattern="PDF" />' + backup: no notify: reload php-fpm diff --git a/roles/php/templates/php-cli.ini.j2 b/roles/php/templates/php-cli.ini.j2 new file mode 100644 index 0000000000..44360aa80d --- /dev/null +++ b/roles/php/templates/php-cli.ini.j2 @@ -0,0 +1,20 @@ +; {{ ansible_managed }} + +[PHP] +error_reporting = {{ php_error_reporting }} +sendmail_path = {{ php_sendmail_path }} +expose_php = Off +date.timezone = {{ php_timezone }} +memory_limit = {{ php_cli_memory_limit }} + +[mysqlnd] +mysqlnd.collect_memory_statistics = {{ php_mysqlnd_collect_memory_statistics }} + +[opcache] +opcache.enable = {{ php_opcache_enable }} +opcache.enable_cli = {{ php_opcache_enable_cli }} +opcache.memory_consumption = {{ php_opcache_memory_consumption }} +opcache.interned_strings_buffer = {{ php_opcache_interned_strings_buffer }} +opcache.max_accelerated_files = {{ php_opcache_max_accelerated_files }} +opcache.revalidate_freq = {{ php_opcache_revalidate_freq }} +opcache.fast_shutdown = {{ php_opcache_fast_shutdown }} diff --git a/roles/php/templates/php.ini.j2 b/roles/php/templates/php-fpm.ini.j2 similarity index 97% rename from roles/php/templates/php.ini.j2 rename to roles/php/templates/php-fpm.ini.j2 index 84fe207c2e..8ff098f656 100644 --- a/roles/php/templates/php.ini.j2 +++ b/roles/php/templates/php-fpm.ini.j2 @@ -13,7 +13,6 @@ sendmail_path = {{ php_sendmail_path }} session.save_path = {{ php_session_save_path }} session.cookie_httponly = {{ php_session_cookie_httponly }} session.cookie_secure = {{ php_session_cookie_secure }} -track_errors = {{ php_track_errors }} upload_max_filesize = {{ php_upload_max_filesize }} expose_php = Off date.timezone = {{ php_timezone }} diff --git a/roles/php/vars/7.4.yml b/roles/php/vars/7.4.yml new file mode 100644 index 0000000000..562b7468c2 --- /dev/null +++ b/roles/php/vars/7.4.yml @@ -0,0 +1,19 @@ +php_extensions_default: + php7.4-bcmath: "{{ apt_package_state }}" + php7.4-cli: "{{ apt_package_state }}" + php7.4-curl: "{{ apt_package_state }}" + php7.4-dev: "{{ apt_package_state }}" + php7.4-gd: "{{ apt_package_state }}" + php7.4-fpm: "{{ apt_package_state }}" + php7.4-imagick: "{{ apt_package_state }}" + php7.4-intl: "{{ apt_package_state }}" + php7.4-mbstring: "{{ apt_package_state }}" + php7.4-mysql: "{{ apt_package_state }}" + php7.4-xml: "{{ apt_package_state }}" + php7.4-xmlrpc: "{{ apt_package_state }}" + php7.4-zip: "{{ apt_package_state }}" + +php_memcached_packages: + php7.4-memcached: "{{ apt_package_state }}" + +php_xdebug_package: php7.4-xdebug diff --git a/roles/php/vars/8.0.yml b/roles/php/vars/8.0.yml new file mode 100644 index 0000000000..02eb376120 --- /dev/null +++ b/roles/php/vars/8.0.yml @@ -0,0 +1,18 @@ +php_extensions_default: + php8.0-bcmath: "{{ apt_package_state }}" + php8.0-cli: "{{ apt_package_state }}" + php8.0-curl: "{{ apt_package_state }}" + php8.0-dev: "{{ apt_package_state }}" + php8.0-fpm: "{{ apt_package_state }}" + php8.0-imagick: "{{ apt_package_state }}" + php8.0-intl: "{{ apt_package_state }}" + php8.0-mbstring: "{{ apt_package_state }}" + php8.0-mysql: "{{ apt_package_state }}" + php8.0-xml: "{{ apt_package_state }}" + php8.0-xmlrpc: "{{ apt_package_state }}" + php8.0-zip: "{{ apt_package_state }}" + +php_memcached_packages: + php8.0-memcached: "{{ apt_package_state }}" + +php_xdebug_package: php8.0-xdebug diff --git a/roles/php/vars/8.1.yml b/roles/php/vars/8.1.yml new file mode 100644 index 0000000000..1b15ae2d4e --- /dev/null +++ b/roles/php/vars/8.1.yml @@ -0,0 +1,18 @@ +php_extensions_default: + php8.1-bcmath: "{{ apt_package_state }}" + php8.1-cli: "{{ apt_package_state }}" + php8.1-curl: "{{ apt_package_state }}" + php8.1-dev: "{{ apt_package_state }}" + php8.1-fpm: "{{ apt_package_state }}" + php8.1-imagick: "{{ apt_package_state }}" + php8.1-intl: "{{ apt_package_state }}" + php8.1-mbstring: "{{ apt_package_state }}" + php8.1-mysql: "{{ apt_package_state }}" + php8.1-xml: "{{ apt_package_state }}" + php8.1-xmlrpc: "{{ apt_package_state }}" + php8.1-zip: "{{ apt_package_state }}" + +php_memcached_packages: + php8.1-memcached: "{{ apt_package_state }}" + +php_xdebug_package: php8.1-xdebug diff --git a/roles/princexml/.travis.yml b/roles/princexml/.travis.yml new file mode 100644 index 0000000000..24e14eb011 --- /dev/null +++ b/roles/princexml/.travis.yml @@ -0,0 +1,30 @@ +--- +dist: xenial +language: python +python: "2.7" + +# Use the new container infrastructure +sudo: false + +# Install ansible +addons: + apt: + packages: + - python-pip + +install: + # Install ansible + - pip install ansible + + # Check ansible version + - ansible --version + + # Create ansible.cfg with correct roles_path + - printf '[defaults]\nroles_path=../' >ansible.cfg + +script: + # Basic role syntax check + - ansible-playbook tests/test.yml -i tests/inventory --syntax-check + +notifications: + webhooks: https://galaxy.ansible.com/api/v1/notifications/ \ No newline at end of file diff --git a/roles/princexml/README.md b/roles/princexml/README.md new file mode 100644 index 0000000000..ed45eab366 --- /dev/null +++ b/roles/princexml/README.md @@ -0,0 +1,48 @@ +PrinceXML +========= + +[![Build Status](https://img.shields.io/travis/pressbooks/ansible-role-princexml.svg?style=flat-square)](https://travis-ci.org/pressbooks/ansible-role-princexml) [![GitHub release](https://img.shields.io/github/release/pressbooks/ansible-role-princexml.svg?style=flat-square)](https://github.com/pressbooks/ansible-role-princexml/releases/latest) + +Installs [PrinceXML](https://princexml.com) on Ubuntu 16.04 or 18.04. + +Requirements +------------ + +Optionally, a PrinceXML `license.dat` file. + +Role Variables +-------------- + +Available variables are listed below, along with default values (see defaults/main.yml): + + # PrinceXML package URI for Ubuntu 16.04 Xenial. Defaults to latest stable 64-bit. + prince_package_uri_ubuntu_xenial: https://www.princexml.com/download/prince_12.5-1_ubuntu16.04_amd64.deb + + # PrinceXML package URI for Ubuntu 18.04 Bionic. Defaults to latest stable 64-bit. + prince_package_uri_ubuntu_bionic: https://www.princexml.com/download/prince_12.5-1_ubuntu18.04_amd64.deb + + # The local path to your PrinceXML `license.dat` file (optional). + prince_license: "" + + +Dependencies +------------ + +None. + +Example Playbook +---------------- + + - hosts: servers + roles: + - pressbooks.princexml + +License +------- + +MIT + +Author Information +------------------ + +This role was created in 2016 by [Ned Zimmerman](https://github.com/greatislander) for [Pressbooks](https://pressbooks.org). diff --git a/roles/princexml/defaults/main.yml b/roles/princexml/defaults/main.yml new file mode 100644 index 0000000000..b32becf192 --- /dev/null +++ b/roles/princexml/defaults/main.yml @@ -0,0 +1,5 @@ +--- +prince_package_uri_ubuntu_xenial: https://www.princexml.com/download/prince_12.5-1_ubuntu16.04_amd64.deb +prince_package_uri_ubuntu_bionic: https://www.princexml.com/download/prince_12.5-1_ubuntu18.04_amd64.deb +prince_package_uri_ubuntu_focal: https://www.princexml.com/download/prince_14.2-1_ubuntu20.04_amd64.deb +prince_license: "" diff --git a/roles/princexml/meta/.galaxy_install_info b/roles/princexml/meta/.galaxy_install_info new file mode 100644 index 0000000000..fbada44785 --- /dev/null +++ b/roles/princexml/meta/.galaxy_install_info @@ -0,0 +1,2 @@ +install_date: Wed Oct 13 20:58:21 2021 +version: 12.5.0 diff --git a/roles/princexml/meta/main.yml b/roles/princexml/meta/main.yml new file mode 100644 index 0000000000..8ba54e17b8 --- /dev/null +++ b/roles/princexml/meta/main.yml @@ -0,0 +1,176 @@ +galaxy_info: + author: greatislander + description: Installs PrinceXML. + company: Pressbooks (Book Oven Inc.) + license: MIT + min_ansible_version: 2.5 + platforms: + #- name: OpenBSD + # versions: + # - all + # - 5.6 + # - 5.7 + # - 5.8 + # - 5.9 + # - 6.0 + #- name: Fedora + # versions: + # - all + # - 16 + # - 17 + # - 18 + # - 19 + # - 20 + # - 21 + # - 22 + # - 23 + # - 24 + #- name: DellOS + # versions: + # - all + # - 10 + # - 6 + # - 9 + #- name: MacOSX + # versions: + # - all + # - 10.10 + # - 10.11 + # - 10.12 + # - 10.7 + # - 10.8 + # - 10.9 + #- name: Junos + # versions: + # - all + # - any + #- name: GenericBSD + # versions: + # - all + # - any + #- name: Void Linux + # versions: + # - all + # - any + #- name: GenericLinux + # versions: + # - all + # - any + #- name: NXOS + # versions: + # - all + # - any + #- name: IOS + # versions: + # - all + # - any + #- name: Amazon + # versions: + # - all + # - 2013.03 + # - 2013.09 + # - 2016.03 + #- name: ArchLinux + # versions: + # - all + # - any + #- name: FreeBSD + # versions: + # - all + # - 10.0 + # - 10.1 + # - 10.2 + # - 10.3 + # - 8.0 + # - 8.1 + # - 8.2 + # - 8.3 + # - 8.4 + # - 9.0 + # - 9.1 + # - 9.1 + # - 9.2 + # - 9.3 + - name: Ubuntu + versions: + # - all + # - lucid + # - maverick + # - natty + # - oneiric + # - precise + # - quantal + # - raring + # - saucy + # - trusty + # - utopic + # - vivid + # - wily + - xenial + - bionic + #- name: Debian + # versions: + # - all + # - etch + # - jessie + # - lenny + # - sid + # - squeeze + # - stretch + # - wheezy + #- name: EL + # versions: + # - all + # - 5 + # - 6 + # - 7 + #- name: Windows + # versions: + # - all + # - 2012R2 + #- name: SmartOS + # versions: + # - all + # - any + #- name: opensuse + # versions: + # - all + # - 12.1 + # - 12.2 + # - 12.3 + # - 13.1 + # - 13.2 + #- name: SLES + # versions: + # - all + # - 10SP3 + # - 10SP4 + # - 11 + # - 11SP1 + # - 11SP2 + # - 11SP3 + # - 11SP4 + # - 12 + # - 12SP1 + #- name: GenericUNIX + # versions: + # - all + # - any + #- name: Solaris + # versions: + # - all + # - 10 + # - 11.0 + # - 11.1 + # - 11.2 + # - 11.3 + #- name: eos + # versions: + # - all + # - Any + galaxy_tags: + - eprdctn + - publishing + - authoring + - xhtml + - pdf diff --git a/roles/princexml/tasks/amd64.yml b/roles/princexml/tasks/amd64.yml new file mode 100644 index 0000000000..c77983df8e --- /dev/null +++ b/roles/princexml/tasks/amd64.yml @@ -0,0 +1,35 @@ +--- +- name: 'Check for supported environment' + fail: msg="PrinceXML can only be installed on Ubuntu Xenial, Bionic or Focal." + when: (ansible_distribution|string != 'Ubuntu') or + (ansible_distribution_release|string != 'xenial' and ansible_distribution_release|string != 'bionic' and ansible_distribution_release|string != 'focal') + +- name: Determine PrinceXML version + set_fact: + prince_package_uri: "prince_package_uri_ubuntu_{{ ansible_distribution_release|string }}" + +- name: Install PrinceXML + apt: + deb: "{{ lookup('vars', prince_package_uri) }}" + +- name: Upload PrinceXML license + copy: + src: "{{ prince_license }}" + dest: /usr/lib/prince/license/license.dat.new + owner: root + group: root + mode: "u=rw,g=r,o=r" + when: prince_license != "" + +- name: Check for existing PrinceXML license + stat: path=/usr/lib/prince/license/license.dat + register: existing_license + when: prince_license != "" + +- name: Backup old PrinceXML license + command: mv /usr/lib/prince/license/license.dat /usr/lib/prince/license/license.dat.{{ lookup('pipe', 'date +%Y%m%d') }}.old + when: prince_license != "" and existing_license.stat.exists + +- name: Install new PrinceXML license + command: mv /usr/lib/prince/license/license.dat.new /usr/lib/prince/license/license.dat + when: prince_license != "" diff --git a/roles/princexml/tasks/arm64.yml b/roles/princexml/tasks/arm64.yml new file mode 100644 index 0000000000..7ebd8451e7 --- /dev/null +++ b/roles/princexml/tasks/arm64.yml @@ -0,0 +1,3 @@ +--- +- name: 'Skip PrinceXML for now until there is an arm .deb' + action: command echo "Arm detected. Skipping PrinceXML" diff --git a/roles/princexml/tasks/main.yml b/roles/princexml/tasks/main.yml new file mode 100644 index 0000000000..e51a84945b --- /dev/null +++ b/roles/princexml/tasks/main.yml @@ -0,0 +1,9 @@ +--- + - name: AMD64/x86_64 + import_tasks: amd64.yml + when: ansible_architecture == "x86_64" + + - name: ARM64 + import_tasks: arm64.yml + when: ansible_architecture == "aarch64" + diff --git a/roles/princexml/tests/inventory b/roles/princexml/tests/inventory new file mode 100644 index 0000000000..d18580b3c3 --- /dev/null +++ b/roles/princexml/tests/inventory @@ -0,0 +1 @@ +localhost \ No newline at end of file diff --git a/roles/princexml/tests/test.yml b/roles/princexml/tests/test.yml new file mode 100644 index 0000000000..ae87d45c38 --- /dev/null +++ b/roles/princexml/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - ansible-role-princexml diff --git a/roles/python_interpreter/tasks/main.yml b/roles/python_interpreter/tasks/main.yml deleted file mode 100644 index 8935abe901..0000000000 --- a/roles/python_interpreter/tasks/main.yml +++ /dev/null @@ -1,11 +0,0 @@ -- block: - - name: Get Ubuntu release - raw: lsb_release -cs - register: ubuntu_release - changed_when: false - - name: Set ansible_python_interpreter for Ubuntu 18.04 Bionic - set_fact: - ansible_python_interpreter: python3 - when: ubuntu_release.stdout | trim == 'bionic' - when: ansible_python_interpreter is not defined - tags: always diff --git a/roles/redis/tasks/main.yml b/roles/redis/tasks/main.yml index 8c039c0c64..4e7bc44c3c 100644 --- a/roles/redis/tasks/main.yml +++ b/roles/redis/tasks/main.yml @@ -22,7 +22,7 @@ - name: Load Disable THP service into systemd command: "/bin/systemctl daemon-reload" - when: disable_thp_service_added | changed + when: disable_thp_service_added.changed - name: Enable and start Disable THP service service: diff --git a/roles/rollback/tasks/main.yml b/roles/rollback/tasks/main.yml index 4185f2729b..6ceb07dc30 100644 --- a/roles/rollback/tasks/main.yml +++ b/roles/rollback/tasks/main.yml @@ -1,22 +1,33 @@ --- +- name: Get real path of current symlinked release + command: "readlink {{ project_current_path }}" + args: + chdir: "{{ project_root }}" + register: current_release_readlink_result + changed_when: false + +- name: Clean up old and failed releases + deploy_helper: + state: clean + path: "{{ project_root }}" + current_path: "{{ project_current_path }}" + release: "{{ current_release_readlink_result.stdout }}" + keep_releases: "{{ project.deploy_keep_releases | default(deploy_keep_releases | default(omit)) }}" + - import_tasks: user-release.yml when: release is defined - import_tasks: prior-release.yml when: release is not defined -- name: Check whether target release was from a successful deploy - stat: - path: "{{ new_release_path }}/DEPLOY_UNFINISHED" - register: target - -- name: Fail if target release was from failed deploy - fail: - msg: "Cannot switch to release at {{ new_release_path }}. It is from an unfinished deploy. You may manually specify a different release using --extra-vars='release=12345678901234'." - when: target.stat.exists | default(False) - - name: Link 'current' directory to target release file: path: "{{ project_root }}/{{ project_current_path }}" src: "{{ new_release_path }}" state: link + +- name: Write unfinished file to old symlinked release + file: + path: "{{ current_release_readlink_result.stdout }}/DEPLOY_UNFINISHED" + state: touch + mode: '0644' diff --git a/roles/rollback/tasks/prior-release.yml b/roles/rollback/tasks/prior-release.yml index 1788aeebe8..25a51ca884 100644 --- a/roles/rollback/tasks/prior-release.yml +++ b/roles/rollback/tasks/prior-release.yml @@ -1,9 +1,13 @@ --- - name: Get list position of current symlinked release - shell: "ls releases | grep -n $(basename $(readlink {{ project_current_path }})) | cut -f1 -d:" + shell: | + set -eo pipefail + ls releases | grep -n $(basename $(readlink {{ project_current_path }})) | cut -f1 -d: args: chdir: "{{ project_root }}" + executable: /bin/bash register: current_release_position + changed_when: false - name: Fail if current release is the oldest available release fail: @@ -15,6 +19,7 @@ args: chdir: "{{ project_root }}" register: releases + changed_when: false - name: Create new_release_path variable set_fact: diff --git a/roles/rollback/tasks/user-release.yml b/roles/rollback/tasks/user-release.yml index 6e356e1bc0..d360cccda3 100644 --- a/roles/rollback/tasks/user-release.yml +++ b/roles/rollback/tasks/user-release.yml @@ -9,11 +9,12 @@ args: chdir: "{{ project_root }}" register: current_release + changed_when: false - name: Fail if user-specified release doesn't exist or is already active fail: msg: "Cannot switch to release {{ release }}. Either it does not exist or it is the active release." - when: specified.stat.isdir | default(False) == False or current_release.stdout_lines[0] == release + when: not (specified.stat.isdir | default(False)) or current_release.stdout_lines[0] == release - name: Create new_release_path variable set_fact: diff --git a/roles/sshd/tasks/main.yml b/roles/sshd/tasks/main.yml index acabc23938..152b4f7fd3 100644 --- a/roles/sshd/tasks/main.yml +++ b/roles/sshd/tasks/main.yml @@ -11,7 +11,7 @@ template: src: "{{ sshd_config }}" dest: /etc/ssh/sshd_config - mode: 0600 + mode: '0600' validate: '/usr/sbin/sshd -T -f %s' notify: restart ssh @@ -19,7 +19,7 @@ template: src: "{{ ssh_config }}" dest: /etc/ssh/ssh_config - mode: 0644 + mode: '0644' - name: Remove Diffie-Hellman moduli of size < 2000 lineinfile: diff --git a/roles/ssmtp/tasks/main.yml b/roles/ssmtp/tasks/main.yml index 9d62055d4d..c0e78ba3ec 100644 --- a/roles/ssmtp/tasks/main.yml +++ b/roles/ssmtp/tasks/main.yml @@ -9,8 +9,10 @@ template: src: ssmtp.conf.j2 dest: /etc/ssmtp/ssmtp.conf + mode: '0644' - name: ssmtp revaliases configuration template: src: revaliases.j2 dest: /etc/ssmtp/revaliases + mode: '0644' diff --git a/roles/users/tasks/main.yml b/roles/users/tasks/main.yml index 8c1403c0be..09b84830df 100644 --- a/roles/users/tasks/main.yml +++ b/roles/users/tasks/main.yml @@ -33,7 +33,7 @@ name: "{{ item.name }}" group: "{{ item.groups[0] }}" groups: "{{ item.groups | join(',') }}" - password: '{% for user in vault_users | default([]) if user.name == item.name and user.password is defined %}{{ user.password | password_hash("sha512", (user.salt | default(""))[:16] | regex_replace("[^\.\/a-zA-Z0-9]", "x")) }}{% else %}{{ None }}{% endfor %}' + password: '{% for user in vault_users | default([]) if user.name == item.name and user.password is defined %}{{ user.password | password_hash("sha512", (user.salt | default(""))[:16] | regex_replace("[^\.\/a-zA-Z0-9]", "x")) }}{% else %}{{ "!" }}{% endfor %}' state: present shell: /bin/bash update_password: "{{ item.update_password | default('always') }}" @@ -43,13 +43,13 @@ template: src: sudoers.d.j2 dest: "/etc/sudoers.d/{{ web_user }}-services" - mode: 0440 + mode: '0440' owner: root group: root validate: "/usr/sbin/visudo -cf %s" - when: web_sudoers + when: web_sudoers[0] is defined -- name: Add SSH keys +- name: Add user SSH keys authorized_key: user: "{{ item.0.name }}" key: "{{ item.1 }}" @@ -57,8 +57,15 @@ - "{{ users | default([]) }}" - keys +- name: Add deploy SSH keys + authorized_key: + user: "{{ web_user }}" + key: "{{ lookup('file', item) }}" + with_fileglob: 'public_keys/*.pub' + - name: Check whether Ansible can connect as admin_user - local_action: command ansible {{ inventory_hostname }} -m ping -u {{ admin_user }} {{ cli_options | default('') }} + command: ansible {{ inventory_hostname }} -m ping -u {{ admin_user }} {{ cli_options | default('') }} + delegate_to: localhost failed_when: false changed_when: false become: no diff --git a/roles/wordpress-install/tasks/composer-authentications.yml b/roles/wordpress-install/tasks/composer-authentications.yml index 9d0a8368d3..2b2e8649e2 100644 --- a/roles/wordpress-install/tasks/composer-authentications.yml +++ b/roles/wordpress-install/tasks/composer-authentications.yml @@ -7,10 +7,10 @@ no_log: true changed_when: false when: - - composer_authentication.hostname is defined and composer_authentication.hostname != "" - - composer_authentication.username is defined and composer_authentication.username != "" - - composer_authentication.password is defined and composer_authentication.password != "" - loop: "{{ composer_authentications }}" + - not (not composer_authentication.hostname) + - not (not composer_authentication.username) + - not (not composer_authentication.password) + loop: "{{ composer_authentications | default([]) }}" loop_control: loop_var: composer_authentication label: "{{ composer_authentication.hostname }}" diff --git a/roles/wordpress-install/tasks/directories.yml b/roles/wordpress-install/tasks/directories.yml index 093d3212a6..591255688b 100644 --- a/roles/wordpress-install/tasks/directories.yml +++ b/roles/wordpress-install/tasks/directories.yml @@ -1,10 +1,10 @@ --- - name: Create web root of sites file: - path: "{{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}/web" + path: "{{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}/{{ item.value.public_path | default('web') }}" owner: "{{ web_user }}" group: "{{ web_group }}" - mode: 0755 + mode: '0755' state: directory with_dict: "{{ wordpress_sites }}" @@ -13,7 +13,7 @@ path: "{{ www_root }}/{{ item.key }}/shared" owner: "{{ web_user }}" group: "{{ web_group }}" - mode: 0755 + mode: '0755' state: directory with_dict: "{{ wordpress_sites }}" diff --git a/roles/wordpress-install/tasks/dotenv.yml b/roles/wordpress-install/tasks/dotenv.yml new file mode 100644 index 0000000000..5397096992 --- /dev/null +++ b/roles/wordpress-install/tasks/dotenv.yml @@ -0,0 +1,17 @@ +--- +- name: Create .env file + template: + src: "env.j2" + dest: "/tmp/{{ item.key }}.env" + mode: '0644' + owner: "{{ web_user }}" + group: "{{ web_group }}" + with_dict: "{{ wordpress_sites }}" + +- name: Copy .env file into web root + synchronize: + src: "/tmp/{{ item.key }}.env" + dest: "{{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}/.env" + checksum: true + with_dict: "{{ wordpress_sites }}" + delegate_to: "{{ inventory_hostname }}" diff --git a/roles/wordpress-install/tasks/main.yml b/roles/wordpress-install/tasks/main.yml index fd6fbb223b..bdebc1d3f4 100644 --- a/roles/wordpress-install/tasks/main.yml +++ b/roles/wordpress-install/tasks/main.yml @@ -2,19 +2,8 @@ - import_tasks: directories.yml tags: wordpress-install-directories -- name: Create .env file - template: - src: "env.j2" - dest: "/tmp/{{ item.key }}.env" - owner: "{{ web_user }}" - group: "{{ web_group }}" - with_dict: "{{ wordpress_sites }}" - -- name: Copy .env file into web root - command: rsync -ac --info=NAME /tmp/{{ item.key }}.env {{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}/.env - with_dict: "{{ wordpress_sites }}" - register: env_file - changed_when: env_file.stdout == item.key + '.env' +- import_tasks: dotenv.yml + tags: dotenv - name: Add known_hosts known_hosts: diff --git a/roles/wordpress-setup/defaults/main.yml b/roles/wordpress-setup/defaults/main.yml index 5b2e1768ee..4684d17a67 100644 --- a/roles/wordpress-setup/defaults/main.yml +++ b/roles/wordpress-setup/defaults/main.yml @@ -5,7 +5,7 @@ nginx_ssl_path: "{{ nginx_path }}/ssl" ssl_default_site: no_default: site_hosts: - - canonical: example.com + - canonical: request.is.invalid ssl: enabled: true provider: self-signed @@ -34,8 +34,7 @@ nginx_cache_background_update: "on" # Nginx includes nginx_includes_templates_path: nginx-includes -nginx_includes_deprecated: roles/wordpress-setup/templates/includes.d -nginx_includes_pattern: "^({{ nginx_includes_templates_path | regex_escape }}|{{ nginx_includes_deprecated | regex_escape }})/(.*)\\.j2$" +nginx_includes_pattern: "^({{ nginx_includes_templates_path | regex_escape }})/(.*)\\.j2$" nginx_includes_d_cleanup: true # h5bp helpers @@ -50,6 +49,11 @@ h5bp_cross_domain_fonts_enabled: "{{ h5bp.cross_domain_fonts | default(true) }}" h5bp_expires_enabled: "{{ h5bp.expires | default(false) }}" h5bp_protect_system_files_enabled: "{{ h5bp.protect_system_files | default(true) }}" +# X-Robots-Tag Header helpers +not_prod: "{{ env != 'production' }}" +robots_tag_header: "{{ item.value.robots_tag_header | default({}) }}" +robots_tag_header_enabled: "{{ robots_tag_header.enabled | default(not_prod) }}" + # PHP FPM php_fpm_pm_max_children: 10 php_fpm_pm_start_servers: 1 diff --git a/roles/wordpress-setup/tasks/database.yml b/roles/wordpress-setup/tasks/database.yml index 61c74a5475..ad10a40026 100644 --- a/roles/wordpress-setup/tasks/database.yml +++ b/roles/wordpress-setup/tasks/database.yml @@ -7,6 +7,7 @@ login_host: "{{ site_env.db_host }}" login_user: "{{ mysql_root_user }}" login_password: "{{ mysql_root_password }}" + no_log: true with_dict: "{{ wordpress_sites }}" - name: Create/assign database user to db and grant permissions @@ -20,6 +21,7 @@ login_host: "{{ site_env.db_host }}" login_user: "{{ mysql_root_user }}" login_password: "{{ mysql_root_password }}" + no_log: true with_dict: "{{ wordpress_sites }}" when: site_uses_local_db and item.value.db_create | default(true) diff --git a/roles/wordpress-setup/tasks/main.yml b/roles/wordpress-setup/tasks/main.yml index 59e3dc60f4..ac1fd8a735 100644 --- a/roles/wordpress-setup/tasks/main.yml +++ b/roles/wordpress-setup/tasks/main.yml @@ -11,7 +11,7 @@ path: "{{ www_root }}" owner: "{{ web_user }}" group: "{{ web_group }}" - mode: 0755 + mode: '0755' state: directory - name: Create logs folder of sites @@ -19,20 +19,21 @@ path: "{{ www_root }}/{{ item.key }}/logs" owner: "{{ web_user }}" group: "{{ web_group }}" - mode: 0755 + mode: '0755' state: directory with_dict: "{{ wordpress_sites }}" - name: Create WordPress php-fpm configuration file template: src: php-fpm.conf.j2 - dest: /etc/php/7.3/fpm/pool.d/wordpress.conf + dest: /etc/php/{{ php_version }}/fpm/pool.d/wordpress.conf + mode: '0644' notify: reload php-fpm - name: Disable default PHP-FPM pool - command: mv /etc/php/7.3/fpm/pool.d/www.conf /etc/php/7.3/fpm/pool.d/www.disabled + command: mv /etc/php/{{ php_version }}/fpm/pool.d/www.conf /etc/php/{{ php_version }}/fpm/pool.d/www.disabled args: - creates: /etc/php/7.3/fpm/pool.d/www.disabled + creates: /etc/php/{{ php_version }}/fpm/pool.d/www.disabled when: disable_default_pool | default(true) notify: reload php-fpm @@ -45,7 +46,7 @@ - name: Setup WP system cron cron: name: "{{ item.key }} WordPress cron" - minute: "*/15" + minute: "{{ item.value.cron_interval | default('*/15') }}" user: "{{ web_user }}" job: "cd {{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }} && wp cron event run --due-now > /dev/null 2>&1" cron_file: "wordpress-{{ item.key | replace('.', '_') }}" @@ -55,7 +56,7 @@ - name: Setup WP Multisite system cron cron: name: "{{ item.key }} WordPress network cron" - minute: "*/30" + minute: "{{ item.value.cron_interval_multisite | default('*/30') }}" user: "{{ web_user }}" job: "cd {{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }} && wp site list --field=url | xargs -n1 -I \\% wp --url=\\% cron event run --due-now > /dev/null 2>&1" cron_file: "wordpress-multisite-{{ item.key | replace('.', '_') }}" diff --git a/roles/wordpress-setup/tasks/nginx-client-cert.yml b/roles/wordpress-setup/tasks/nginx-client-cert.yml index 49d810eee5..69f7026a22 100644 --- a/roles/wordpress-setup/tasks/nginx-client-cert.yml +++ b/roles/wordpress-setup/tasks/nginx-client-cert.yml @@ -3,6 +3,6 @@ get_url: url: "{{ item.value.ssl.client_cert_url }}" dest: "{{ nginx_ssl_path }}/client-{{ (item.value.ssl.client_cert_url | hash('md5'))[:7] }}.crt" - mode: 0640 + mode: '0640' with_dict: "{{ wordpress_sites }}" when: ssl_enabled and item.value.ssl.client_cert_url is defined diff --git a/roles/wordpress-setup/tasks/nginx-includes.yml b/roles/wordpress-setup/tasks/nginx-includes.yml index 960a82fdf1..a18e2e1851 100644 --- a/roles/wordpress-setup/tasks/nginx-includes.yml +++ b/roles/wordpress-setup/tasks/nginx-includes.yml @@ -3,23 +3,17 @@ find: paths: - "{{ nginx_includes_templates_path }}" - - "{{ nginx_includes_deprecated }}" pattern: "*.conf.j2" recurse: yes become: no delegate_to: localhost register: nginx_includes_templates -- name: Warn about deprecated Nginx includes directory - debug: - msg: "[DEPRECATION WARNING]: The `{{ nginx_includes_deprecated }}` directory for Trellis Nginx includes templates is deprecated and will no longer function beginning with Trellis 1.0. Please move these templates to a directory named `{{ nginx_includes_templates_path }}` in the root of this project. For more information, see https://roots.io/trellis/docs/nginx-includes/" - when: True in nginx_includes_templates.files | map(attribute='path') | map('search', nginx_includes_deprecated | regex_escape) | list - - name: Create includes.d directories file: path: "{{ nginx_path }}/includes.d/{{ item }}" state: directory - mode: 0755 + mode: '0755' with_items: "{{ nginx_includes_templates.files | map(attribute='path') | map('regex_replace', nginx_includes_pattern, '\\2') | map('dirname') | unique | list | sort @@ -30,6 +24,7 @@ template: src: "{{ item }}" dest: "{{ nginx_path }}/includes.d/{{ item | regex_replace(nginx_includes_pattern, '\\2') }}" + mode: '0644' with_items: "{{ nginx_includes_templates.files | map(attribute='path') | list | sort(True) }}" notify: reload nginx @@ -39,7 +34,7 @@ pattern: "*.conf" recurse: yes register: nginx_includes_existing - when: nginx_includes_d_cleanup + when: nginx_includes_d_cleanup | bool - name: Remove unmanaged files from includes.d file: diff --git a/roles/wordpress-setup/tasks/nginx.yml b/roles/wordpress-setup/tasks/nginx.yml index ffb9fcb11f..ebeb80c49b 100644 --- a/roles/wordpress-setup/tasks/nginx.yml +++ b/roles/wordpress-setup/tasks/nginx.yml @@ -3,7 +3,7 @@ copy: src: "{{ item.value.ssl.cert }}" dest: "{{ nginx_ssl_path }}/{{ item.value.ssl.cert | basename }}" - mode: 0640 + mode: '0640' with_dict: "{{ wordpress_sites }}" when: ssl_enabled and item.value.ssl.cert is defined notify: reload nginx @@ -12,7 +12,7 @@ copy: src: "{{ item.value.ssl.key }}" dest: "{{ nginx_ssl_path }}/{{ item.value.ssl.key | basename }}" - mode: 0600 + mode: '0600' with_dict: "{{ wordpress_sites }}" when: ssl_enabled and item.value.ssl.key is defined notify: reload nginx @@ -23,17 +23,28 @@ template: src: "{{ item.src }}" dest: "{{ nginx_path }}/sites-available/{{ item.src | basename | regex_replace('.j2$', '') }}" + mode: '0644' with_items: "{{ nginx_sites_confs }}" when: item.enabled | default(true) notify: reload nginx tags: nginx-sites -- name: Enable or disable Nginx sites +- name: Enable Nginx sites file: path: "{{ nginx_path }}/sites-enabled/{{ item.src | basename | regex_replace('.j2$', '') }}" src: "{{ nginx_path }}/sites-available/{{ item.src | basename | regex_replace('.j2$', '') }}" - state: "{{ item.enabled | default(true) | ternary('link', 'absent') }}" + state: link force: yes + when: item.enabled | default(true) + with_items: "{{ nginx_sites_confs }}" + notify: reload nginx + tags: nginx-sites + +- name: Disable Nginx sites + file: + path: "{{ nginx_path }}/sites-enabled/{{ item.src | basename | regex_replace('.j2$', '') }}" + state: absent + when: not(item.enabled | default(true)) with_items: "{{ nginx_sites_confs }}" notify: reload nginx tags: nginx-sites @@ -42,12 +53,14 @@ template: src: "{{ playbook_dir }}/roles/letsencrypt/templates/acme-challenge-location.conf.j2" dest: "{{ nginx_path }}/acme-challenge-location.conf" + mode: '0644' notify: reload nginx - name: Create WordPress configuration for Nginx template: src: "{{ item.value.nginx_wordpress_site_conf | default(nginx_wordpress_site_conf) }}" dest: "{{ nginx_path }}/sites-available/{{ item.key }}.conf" + mode: '0644' with_dict: "{{ wordpress_sites }}" notify: reload nginx tags: nginx-includes diff --git a/roles/wordpress-setup/tasks/self-signed-certificate.yml b/roles/wordpress-setup/tasks/self-signed-certificate.yml index 64e355fce2..fea0d9cc99 100644 --- a/roles/wordpress-setup/tasks/self-signed-certificate.yml +++ b/roles/wordpress-setup/tasks/self-signed-certificate.yml @@ -3,31 +3,31 @@ file: path: "{{ nginx_ssl_path }}/self-signed-openssl-configs/" state: directory - mode: "0755" + mode: '0755' - name: Template openssl configs template: src: self-signed-openssl-config.j2 dest: "{{ nginx_ssl_path }}/self-signed-openssl-configs/{{ item.key }}.cnf" + mode: '0644' with_dict: "{{ wordpress_sites | combine(ssl_default_site) }}" when: - - sites_use_ssl - - ssl_enabled + - sites_use_ssl | bool + - ssl_enabled | bool - item.value.ssl.provider | default('manual') == 'self-signed' - name: Generate self-signed certificates - shell: "openssl req -new -newkey rsa:2048 \ + command: "openssl req -new -newkey rsa:2048 \ -days 3650 -nodes -x509 -sha256 \ -extensions req_ext -config {{ nginx_ssl_path }}/self-signed-openssl-configs/{{ item.key }}.cnf \ -keyout {{ item.key | quote }}.key -out {{ item.key | quote }}.cert" args: - executable: "/bin/bash" chdir: "{{ nginx_ssl_path }}" creates: "{{ item.key }}.*" with_dict: "{{ wordpress_sites | combine(ssl_default_site) }}" when: - - sites_use_ssl - - ssl_enabled + - sites_use_ssl | bool + - ssl_enabled | bool - item.value.ssl.provider | default('manual') == 'self-signed' notify: reload nginx diff --git a/roles/wordpress-setup/templates/self-signed-openssl-config.j2 b/roles/wordpress-setup/templates/self-signed-openssl-config.j2 index 8babe10d98..9ba1054aef 100644 --- a/roles/wordpress-setup/templates/self-signed-openssl-config.j2 +++ b/roles/wordpress-setup/templates/self-signed-openssl-config.j2 @@ -4,4 +4,4 @@ distinguished_name = req_dn [req_dn] commonName = {{ item.value.site_hosts[0].canonical }} [req_ext] -subjectAltName = {{ site_hosts | union(multisite_subdomains_wildcards) | map('regex_replace', '(.*)', 'DNS:\\1') | join(',') }} +subjectAltName = {{ site_hosts | union(multisite_subdomains_wildcards) | map('regex_replace', '(^.*$)', 'DNS:\\1') | join(',') }} diff --git a/roles/wordpress-setup/templates/ssl.no-default.conf.j2 b/roles/wordpress-setup/templates/ssl.no-default.conf.j2 index a29b113dbd..479979c710 100644 --- a/roles/wordpress-setup/templates/ssl.no-default.conf.j2 +++ b/roles/wordpress-setup/templates/ssl.no-default.conf.j2 @@ -12,7 +12,6 @@ server { listen 443 ssl default_server deferred; include h5bp/directive-only/ssl.conf; - ssl_dhparam /etc/nginx/ssl/dhparams.pem; ssl_certificate {{ nginx_path }}/ssl/no_default.cert; ssl_trusted_certificate {{ nginx_path }}/ssl/no_default.cert; diff --git a/roles/wordpress-setup/templates/wordpress-site.conf.j2 b/roles/wordpress-setup/templates/wordpress-site.conf.j2 index c951c49340..2096c6d45b 100644 --- a/roles/wordpress-setup/templates/wordpress-site.conf.j2 +++ b/roles/wordpress-setup/templates/wordpress-site.conf.j2 @@ -15,7 +15,7 @@ server { {% endblock %} {% block server_basic -%} - root {{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}/web; + root {{ www_root }}/{{ item.key }}/{{ item.value.current_path | default('current') }}/{{ item.value.public_path | default('web') }}; index index.php index.htm index.html; add_header Fastcgi-Cache $upstream_cache_status; @@ -48,6 +48,7 @@ server { {% endif -%} {% endblock -%} + {% block multisite_rewrites -%} {% if item.value.multisite.enabled | default(false) -%} # Multisite rewrites @@ -74,7 +75,6 @@ server { include h5bp/directive-only/ssl-stapling.conf; {% endif -%} - ssl_dhparam /etc/nginx/ssl/dhparams.pem; ssl_buffer_size 1400; # 1400 bytes to fit in one MTU {% if item.value.ssl.provider | default('manual') != 'self-signed' -%} @@ -91,7 +91,7 @@ server { ssl_certificate_key {{ nginx_path }}/ssl/{{ item.value.ssl.key | basename }}; {% elif item.value.ssl.provider | default('manual') == 'letsencrypt' -%} - ssl_certificate {{ nginx_path }}/ssl/letsencrypt/{{ item.key }}-{{ letsencrypt_cert_ids[item.key] }}-bundled.cert; + ssl_certificate {{ nginx_path }}/ssl/letsencrypt/{{ item.key }}-bundled.cert; ssl_certificate_key {{ nginx_path }}/ssl/letsencrypt/{{ item.key }}.key; {% elif item.value.ssl.provider | default('manual') == 'self-signed' -%} @@ -120,7 +120,7 @@ server { deny all; } {% endblock %} - + {% block blade_twig_templates -%} # Prevent Blade and Twig templates from being accessed directly. location ~* \.(blade\.php|twig)$ { @@ -128,6 +128,40 @@ server { } {% endblock %} + {% block dependency_managers -%} + # composer + location ~* composer\.(json|lock)$ { + deny all; + } + + location ~* composer/installed\.json$ { + deny all; + } + + location ~* auth\.json$ { + deny all; + } + + # npm + location ~* package(-lock)?\.json$ { + deny all; + } + + # yarn + location ~* yarn\.lock$ { + deny all; + } + + # bundler + location ~* Gemfile(\.lock)?$ { + deny all; + } + + location ~* gems\.(rb|locked)?$ { + deny all; + } + {% endblock %} + {% block location_primary -%} location / { try_files $uri $uri/ /index.php?$args; @@ -172,13 +206,14 @@ server { {% block embed_security -%} {% if item.value.nginx_embed_security | default(nginx_embed_security | default(true)) -%} add_header Content-Security-Policy "frame-ancestors 'self'" always; + add_header X-Frame-Options SAMEORIGIN always; + {% endif -%} + {% endblock -%} - # Conditional X-Frame-Options until https://core.trac.wordpress.org/ticket/40020 is resolved - set $x_frame_options SAMEORIGIN; - if ($arg_customize_changeset_uuid) { - set $x_frame_options ""; - } - add_header X-Frame-Options $x_frame_options always; + {% block robots_tag_header -%} + {% if robots_tag_header_enabled -%} + # Prevent search engines from indexing non-production environments + add_header X-Robots-Tag "noindex, nofollow" always; {% endif -%} {% endblock -%} diff --git a/roles/wp-cli/defaults/main.yml b/roles/wp-cli/defaults/main.yml index 6abe35ebe1..b27b963495 100644 --- a/roles/wp-cli/defaults/main.yml +++ b/roles/wp-cli/defaults/main.yml @@ -1,5 +1,5 @@ gpg2_package: gnupg2 -wp_cli_version: 2.5.0 +wp_cli_version: 2.6.0 wp_cli_bin_path: /usr/bin/wp wp_cli_phar_url: "https://github.com/wp-cli/wp-cli/releases/download/v{{ wp_cli_version }}/wp-cli-{{ wp_cli_version }}.phar" wp_cli_phar_asc_url: "https://github.com/wp-cli/wp-cli/releases/download/v{{ wp_cli_version }}/wp-cli-{{ wp_cli_version }}.phar.asc" diff --git a/roles/wp-cli/tasks/main.yml b/roles/wp-cli/tasks/main.yml index 675596e141..dab02c6ea8 100644 --- a/roles/wp-cli/tasks/main.yml +++ b/roles/wp-cli/tasks/main.yml @@ -19,10 +19,24 @@ copy: src: "{{ wp_cli_pgp_public_key }}" dest: /tmp/wp-cli.pgp.gpg + mode: '0744' -- name: Verify WP-CLI Phar Signature - command: gpg2 --lock-never --no-default-keyring --keyring /tmp/wp-cli.pgp.gpg --verify /tmp/wp-cli-{{ wp_cli_version }}.phar.asc /tmp/wp-cli-{{ wp_cli_version }}.phar - changed_when: false +- name: Verify WP-CLI + block: + - name: Check GPG signature + command: gpg2 --lock-never --no-default-keyring --keyring /tmp/wp-cli.pgp.gpg --verify /tmp/wp-cli-{{ wp_cli_version }}.phar.asc /tmp/wp-cli-{{ wp_cli_version }}.phar + changed_when: false + rescue: + - name: Delete invalid WP-CLI Phar file + file: + path: "{{ item }}" + state: absent + with_items: + - "/tmp/wp-cli-{{ wp_cli_version }}.phar" + - "/tmp/wp-cli-{{ wp_cli_version }}.phar.asc" + - name: Fail verification + fail: + msg: "WP-CLI Phar signature could not be verified. Please try again." - name: Install WP-CLI command: rsync -c --chmod=0755 --info=name /tmp/wp-cli-{{ wp_cli_version }}.phar {{ wp_cli_bin_path }} @@ -46,6 +60,7 @@ - name: Install WP-CLI packages command: wp package install {{ item }} + become: true become_user: "{{ web_user }}" register: wp_cli_packages_installed changed_when: diff --git a/roles/xdebug-tunnel/defaults/main.yml b/roles/xdebug-tunnel/defaults/main.yml index 764f6a43fa..3623b4ac55 100644 --- a/roles/xdebug-tunnel/defaults/main.yml +++ b/roles/xdebug-tunnel/defaults/main.yml @@ -1,6 +1,6 @@ -xdebug_tunnel_remote_port: 9000 +xdebug_tunnel_remote_port: 9003 xdebug_tunnel_host: localhost -xdebug_tunnel_local_port: 9000 +xdebug_tunnel_local_port: 9003 xdebug_tunnel_control_socket: /tmp/trellis-xdebug-{{ xdebug_tunnel_inventory_host }} xdebug_tunnel_control_identity: "{{ ansible_user_id }}" diff --git a/roles/xdebug-tunnel/tasks/main.yml b/roles/xdebug-tunnel/tasks/main.yml index 86a84909c0..84ec403faf 100644 --- a/roles/xdebug-tunnel/tasks/main.yml +++ b/roles/xdebug-tunnel/tasks/main.yml @@ -2,7 +2,7 @@ - name: Create or close Xdebug SSH tunnel command: | {% if xdebug_remote_enable | bool %} - ssh -M -S '{{ xdebug_tunnel_control_socket }}' -fnNT -R {{ xdebug_tunnel_port_mapping }} {{ xdebug_tunnel_user_at_host}} '{{ xdebug_tunnel_control_identity }}' + ssh -M -S '{{ xdebug_tunnel_control_socket }}' -fnNT -R {{ xdebug_tunnel_port_mapping }} {{ xdebug_tunnel_user_at_host }} '{{ xdebug_tunnel_control_identity }}' {% else %} ssh -S '{{ xdebug_tunnel_control_socket }}' -O exit '{{ xdebug_tunnel_control_identity }}' {% endif %} @@ -10,6 +10,7 @@ become: no register: xdebug_tunnel ignore_errors: true + changed_when: true - name: Interpret and present Xdebug SSH tunnel errors fail: diff --git a/roles/xdebug/defaults/main.yml b/roles/xdebug/defaults/main.yml index 3efa2ccfb2..cb7be83bf7 100644 --- a/roles/xdebug/defaults/main.yml +++ b/roles/xdebug/defaults/main.yml @@ -1,6 +1,14 @@ -php_xdebug_package: php-xdebug +# XDebug Generic +xdebug_output_dir: /tmp +xdebug_trigger_value: # XDebug Remote Debugging +xdebug_mode: 'off' +xdebug_start_with_request: 'no' +xdebug_discover_client_host: 0 +xdebug_client_host: localhost +xdebug_client_port: 9003 +xdebug_log: /tmp/xdebug.log xdebug_remote_enable: 0 xdebug_remote_connect_back: 0 xdebug_remote_autostart: 0 @@ -8,7 +16,6 @@ xdebug_remote_host: localhost xdebug_remote_port: 9000 xdebug_remote_log: /tmp/xdebug.log xdebug_idekey: XDEBUG -xdebug_extended_info: 1 xdebug_max_nesting_level: 200 # XDebug Display Settings @@ -21,24 +28,13 @@ xdebug_var_display_max_depth: 3 # XDebug Function/Stack Traces xdebug_collect_assignments: 0 -xdebug_collect_includes: 1 -xdebug_collect_params: 0 xdebug_collect_return: 0 -xdebug_collect_vars: 0 xdebug_show_exception_trace: 0 xdebug_show_local_vars: 0 -xdebug_show_mem_delta: 0 -xdebug_trace_enable_trigger: 0 -xdebug_trace_enable_trigger_value: xdebug_trace_format: 0 xdebug_trace_options: 0 -xdebug_trace_output_dir: /tmp xdebug_trace_output_name: trace.%c # XDebug Profiler xdebug_profiler_append: 0 -xdebug_profiler_enable: 0 -xdebug_profiler_enable_trigger: 0 -xdebug_profiler_enable_trigger_value: -xdebug_profiler_output_dir: /tmp xdebug_profiler_output_name: cachegrind.out.%p diff --git a/roles/xdebug/tasks/main.yml b/roles/xdebug/tasks/main.yml index deadc88c50..2b52f58420 100644 --- a/roles/xdebug/tasks/main.yml +++ b/roles/xdebug/tasks/main.yml @@ -1,34 +1,25 @@ --- -- block: - - name: Install Xdebug - apt: - name: "{{ php_xdebug_package }}" - state: "{{ php_xdebug_package_state | default(apt_dev_package_state) }}" - cache_valid_time: "{{ apt_cache_valid_time }}" +- name: Install Xdebug + apt: + name: "{{ php_xdebug_package }}" + state: "{{ php_xdebug_package_state | default(apt_dev_package_state) }}" + cache_valid_time: "{{ apt_cache_valid_time }}" - - name: Template the Xdebug configuration file - template: - src: xdebug.ini.j2 - dest: /etc/php/7.3/mods-available/xdebug.ini - notify: reload php-fpm - - - name: Ensure 20-xdebug.ini is present - file: - src: /etc/php/7.3/mods-available/xdebug.ini - dest: /etc/php/7.3/fpm/conf.d/20-xdebug.ini - state: link - notify: reload php-fpm - - when: xdebug_remote_enable | bool +- name: Template the Xdebug configuration file + template: + src: xdebug.ini.j2 + dest: /etc/php/{{ php_version }}/mods-available/xdebug.ini + mode: '0644' + notify: reload php-fpm -- name: Disable Xdebug +- name: Ensure 20-xdebug.ini is present file: - path: /etc/php/7.3/fpm/conf.d/20-xdebug.ini - state: absent - when: not xdebug_remote_enable | bool + src: /etc/php/{{ php_version }}/mods-available/xdebug.ini + dest: /etc/php/{{ php_version }}/fpm/conf.d/20-xdebug.ini + state: link notify: reload php-fpm - name: Disable Xdebug CLI file: - path: /etc/php/7.3/cli/conf.d/20-xdebug.ini + path: /etc/php/{{ php_version }}/cli/conf.d/20-xdebug.ini state: absent diff --git a/roles/xdebug/templates/xdebug.ini.j2 b/roles/xdebug/templates/xdebug.ini.j2 index 3490bc1d9b..a4c2644c58 100644 --- a/roles/xdebug/templates/xdebug.ini.j2 +++ b/roles/xdebug/templates/xdebug.ini.j2 @@ -3,7 +3,17 @@ [XDebug] zend_extension=xdebug.so +; Generic +xdebug.output_dir={{ xdebug_output_dir }} +xdebug.trigger_value={{ xdebug_trigger_value }} + ; Remote Debugging +xdebug.mode={{ xdebug_mode }} +xdebug.start_with_request={{ xdebug_start_with_request }} +xdebug.discover_client_host={{ xdebug_discover_client_host }} +xdebug.client_host={{ xdebug_client_host }} +xdebug.client_port={{ xdebug_client_port }} +xdebug.log={{ xdebug_log }} xdebug.remote_enable={{ xdebug_remote_enable }} xdebug.remote_connect_back={{ xdebug_remote_connect_back }} xdebug.remote_autostart={{ xdebug_remote_autostart }} @@ -12,7 +22,6 @@ xdebug.remote_port={{ xdebug_remote_port }} xdebug.remote_handler=dbgp xdebug.remote_log={{ xdebug_remote_log }} xdebug.idekey={{ xdebug_idekey }} -xdebug.extended_info={{ xdebug_extended_info }} xdebug.max_nesting_level={{ xdebug_max_nesting_level }} ; Display Settings @@ -25,24 +34,13 @@ xdebug.var_display_max_depth={{ xdebug_var_display_max_depth }} ; Function/Stack Traces xdebug.collect_assignments={{ xdebug_collect_assignments }} -xdebug.collect_includes={{ xdebug_collect_includes }} -xdebug.collect_params={{ xdebug_collect_params }} xdebug.collect_return={{ xdebug_collect_return }} -xdebug.collect_vars={{ xdebug_collect_vars }} xdebug.show_exception_trace={{ xdebug_show_exception_trace }} xdebug.show_local_vars={{ xdebug_show_local_vars }} -xdebug.show_mem_delta={{ xdebug_show_mem_delta }} -xdebug.trace_enable_trigger={{ xdebug_trace_enable_trigger }} -xdebug.trace_enable_trigger_value={{ xdebug_trace_enable_trigger_value }} xdebug.trace_format={{ xdebug_trace_format }} xdebug.trace_options={{ xdebug_trace_options }} -xdebug.trace_output_dir={{ xdebug_trace_output_dir }} xdebug.trace_output_name={{ xdebug_trace_output_name }} ; Profiler xdebug.profiler_append={{ xdebug_profiler_append }} -xdebug.profiler_enable={{ xdebug_profiler_enable }} -xdebug.profiler_enable_trigger={{ xdebug_profiler_enable_trigger }} -xdebug.profiler_enable_trigger_value={{ xdebug_profiler_enable_trigger_value }} -xdebug.profiler_output_dir={{ xdebug_profiler_output_dir }} xdebug.profiler_output_name={{ xdebug_profiler_output_name }} diff --git a/server.yml b/server.yml index e15d0e1f73..77f7ef0926 100644 --- a/server.yml +++ b/server.yml @@ -9,14 +9,7 @@ roles: - { role: connection, tags: [connection, always] } -- name: Set ansible_python_interpreter - hosts: web:&{{ env }} - gather_facts: false - become: yes - roles: - - { role: python_interpreter, tags: [always] } - -- name: WordPress Server - Install LEMP Stack with PHP 7.3 and MariaDB MySQL +- name: WordPress Server - Install LEMP Stack with PHP and MariaDB MySQL hosts: web:&{{ env }} become: yes roles: diff --git a/vagrant.default.yml b/vagrant.default.yml index 64b48f7dfe..85df0b0622 100644 --- a/vagrant.default.yml +++ b/vagrant.default.yml @@ -1,10 +1,14 @@ --- -vagrant_ip: '192.168.50.5' +vagrant_ip: '192.168.56.5' vagrant_cpus: 1 vagrant_memory: 1024 # in MB -vagrant_box: 'bento/ubuntu-18.04' -vagrant_box_version: '>= 201807.12.0' -vagrant_ansible_version: '2.7.12' +vagrant_box: 'bento/ubuntu-20.04' +vagrant_box_version: '>= 202012.23.0' +## Uncomment below for use on Apple M1/Arm hardware, and comment out the two lines above this +#vagrant_box: 'jeffnoxon/ubuntu-20.04-arm64' +#vagrant_box_version: '>= 1.0.0' +#vagrant_ansible_python_interpreter: '/usr/bin/python3' +vagrant_ansible_version: '2.10.7' vagrant_skip_galaxy: false vagrant_mount_type: 'nfs' diff --git a/xdebug-tunnel.yml b/xdebug-tunnel.yml index 2c8a975438..014c1f0d41 100644 --- a/xdebug-tunnel.yml +++ b/xdebug-tunnel.yml @@ -15,5 +15,5 @@ handlers: - name: reload php-fpm service: - name: php7.3-fpm + name: php{{ php_version }}-fpm state: reloaded