diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9b2377 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.retry +tests/test.sh diff --git a/.travis.yml b/.travis.yml index c1f9860..352227a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,44 +3,28 @@ services: docker env: - distro: centos7 - init: /usr/lib/systemd/systemd - run_opts: "--privileged --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro" - distro: ubuntu1604 - init: /lib/systemd/systemd - run_opts: "--privileged --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro" - distro: ubuntu1404 - init: /sbin/init - run_opts: "" - distro: ubuntu1204 - init: /sbin/init - run_opts: "" - -before_install: - # Pull container. - - 'docker pull geerlingguy/docker-${distro}-ansible:latest' script: - - container_id=$(mktemp) - # Run container in detached state. - - 'docker run --detach --volume="${PWD}":/etc/ansible/roles/role_under_test:ro ${run_opts} geerlingguy/docker-${distro}-ansible:latest "${init}" > "${container_id}"' + # Configure test script so we can run extra tests after playbook is run. + - export container_id=$(date +%s) + - export cleanup=false - # Ansible syntax check. - - 'docker exec --tty "$(cat ${container_id})" env TERM=xterm ansible-playbook /etc/ansible/roles/role_under_test/tests/test.yml --syntax-check' + # Download test shim. + - wget -O ${PWD}/tests/test.sh https://gist.githubusercontent.com/geerlingguy/73ef1e5ee45d8694570f334be385e181/raw/ + - chmod +x ${PWD}/tests/test.sh - # Test role. - - 'docker exec "$(cat ${container_id})" ansible-playbook /etc/ansible/roles/role_under_test/tests/test.yml' + # Run tests. + - ${PWD}/tests/test.sh - # Test role idempotence. - - idempotence=$(mktemp) - - docker exec "$(cat ${container_id})" ansible-playbook /etc/ansible/roles/role_under_test/tests/test.yml | tee -a ${idempotence} - - > - tail ${idempotence} - | grep -q 'changed=0.*failed=0' - && (echo 'Idempotence test: pass' && exit 0) - || (echo 'Idempotence test: fail' && exit 1) + # Setup test site. + - 'docker exec ${container_id} mkdir -p /var/www/test' + - 'docker exec ${container_id} bash -c "echo Success >| /var/www/test/index.html"' - # Check if nginx is running. - # TODO + # Make sure virtualhost exists. + - 'docker exec --tty ${container_id} env TERM=xterm curl http://test.dev/ | grep "Success"' notifications: webhooks: https://galaxy.ansible.com/api/v1/notifications/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4275cf3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 Jeff Geerling + +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 +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index c6375cb..aa8c3ab 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Build Status](https://travis-ci.org/geerlingguy/ansible-role-nginx.svg?branch=master)](https://travis-ci.org/geerlingguy/ansible-role-nginx) -Installs Nginx on RedHat/CentOS or Debian/Ubuntu Linux, FreeBSD or OpenBSD servers. +Installs Nginx on RedHat/CentOS, Debian/Ubuntu, Archlinux, FreeBSD or OpenBSD servers. -This role installs and configures the latest version of Nginx from the Nginx yum repository (on RedHat-based systems) or via apt (on Debian-based systems) or pkgng (on FreeBSD systems) or pkg_add (on OpenBSD systems). You will likely need to do extra setup work after this role has installed Nginx, like adding your own [virtualhost].conf file inside `/etc/nginx/conf.d/`, describing the location and options to use for your particular website. +This role installs and configures the latest version of Nginx from the Nginx yum repository (on RedHat-based systems), apt (on Debian-based systems), pacman (Archlinux), pkgng (on FreeBSD systems) or pkg_add (on OpenBSD systems). You will likely need to do extra setup work after this role has installed Nginx, like adding your own [virtualhost].conf file inside `/etc/nginx/conf.d/`, describing the location and options to use for your particular website. ## Requirements @@ -16,16 +16,20 @@ Available variables are listed below, along with default values (see `defaults/m nginx_vhosts: [] -A list of vhost definitions (server blocks) for Nginx virtual hosts. If left empty, you will need to supply your own virtual host configuration. See the commented example in `defaults/main.yml` for available server options. If you have a large number of customizations required for your server definition(s), you're likely better off managing the vhost configuration file yourself, leaving this variable set to `[]`. +A list of vhost definitions (server blocks) for Nginx virtual hosts. Each entry will create a separate config file named by `server_name`. If left empty, you will need to supply your own virtual host configuration. See the commented example in `defaults/main.yml` for available server options. If you have a large number of customizations required for your server definition(s), you're likely better off managing the vhost configuration file yourself, leaving this variable set to `[]`. nginx_vhosts: - - listen: "80 default_server" + - listen: "443 ssl http2" server_name: "example.com" + server_name_redirect: "www.example.com" root: "/var/www/example.com" index: "index.php index.html index.htm" error_page: "" access_log: "" error_log: "" + state: "present" + template: "{{ nginx_vhost_template }}" + filename: "example.com.conf" extra_parameters: | location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; @@ -34,18 +38,27 @@ A list of vhost definitions (server blocks) for Nginx virtual hosts. If left emp fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } + ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; + ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; + ssl_protocols TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; An example of a fully-populated nginx_vhosts entry, using a `|` to declare a block of syntax for the `extra_parameters`. Please take note of the indentation in the above block. The first line should be a normal 2-space indent. All other lines should be indented normally relative to that line. In the generated file, the entire block will be 4-space indented. This style will ensure the config file is indented correctly. - nginx_remove_default_vhost: false + - listen: "80" + server_name: "example.com www.example.com" + return: "301 https://example.com$request_uri" + filename: "example.com.80.conf" -Whether to remove the 'default' virtualhost configuration supplied by Nginx. Useful if you want the base `/` URL to be directed at one of your own virtual hosts configured in a separate .conf file. +An example of a secondary vhost which will redirect to the one shown above. + +*Note: The `filename` defaults to the first domain in `server_name`, if you have two vhosts with the same domain, eg. a redirect, you need to manually set the `filename` so the second one doesn't override the first one* - nginx_vhosts_filename: "vhosts.conf" + nginx_remove_default_vhost: false -The filename to use to store vhosts configuration. If you run the role multiple times (e.g. include the role with `with_items`), you can change the name for each run, effectively creating a separate vhosts file per vhost configuration. +Whether to remove the 'default' virtualhost configuration supplied by Nginx. Useful if you want the base `/` URL to be directed at one of your own virtual hosts configured in a separate .conf file. nginx_upstreams: [] @@ -124,6 +137,73 @@ Configures Nginx's [`log_format`](http://nginx.org/en/docs/http/ngx_http_log_mod (For RedHat/CentOS only) Set this to `false` to disable the installation of the `nginx` yum repository. This could be necessary if you want the default OS stable packages, or if you use Satellite. +## Overriding configuration templates + +If you can't customize via variables because an option isn't exposed, you can override the template used to generate the virtualhost configuration files or the `nginx.conf` file. + +```yaml +nginx_conf_template: "nginx.conf.j2" +nginx_vhost_template: "vhost.j2" +``` + +If necessary you can also set the template on a per vhost basis. + +```yaml +nginx_vhosts: + - listen: "80 default_server" + server_name: "site1.example.com" + root: "/var/www/site1.example.com" + index: "index.php index.html index.htm" + template: "{{ playbook_dir }}/templates/site1.example.com.vhost.j2" + - server_name: "site2.example.com" + root: "/var/www/site2.example.com" + index: "index.php index.html index.htm" + template: "{{ playbook_dir }}/templates/site2.example.com.vhost.j2" +``` + +You can either copy and modify the provided template, or extend it with [Jinja2 template inheritance](http://jinja.pocoo.org/docs/2.9/templates/#template-inheritance) and override the specific template block you need to change. + +### Example: Configure gzip in nginx configuration + +Set the `nginx_conf_template` to point to a template file in your playbook directory. + +```yaml +nginx_conf_template: "{{ playbook_dir }}/templates/nginx.conf.j2" +``` + +Create the child template in the path you configured above and extend `geerlingguy.nginx` template file relative to your `playbook.yml`. + +``` +{% extends 'roles/geerlingguy.nginx/templates/nginx.conf.j2' %} + +{% block http_gzip %} + gzip on; + gzip_proxied any; + gzip_static on; + gzip_http_version 1.0; + gzip_disable "MSIE [1-6]\."; + gzip_vary on; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/x-javascript + application/json + application/xml + application/xml+rss + application/xhtml+xml + application/x-font-ttf + application/x-font-opentype + image/svg+xml + image/x-icon; + gzip_buffers 16 8k; + gzip_min_length 512; +{% endblock %} +``` + ## Dependencies None. diff --git a/defaults/main.yml b/defaults/main.yml index cd139d9..38aa3da 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -9,9 +9,12 @@ nginx_yum_repo_enabled: true nginx_ppa_use: false nginx_ppa_version: stable -# The name of the nginx apt/yum package to install. +# The name of the nginx package to install. nginx_package_name: "nginx" +nginx_conf_template: "nginx.conf.j2" +nginx_vhost_template: "vhost.j2" + nginx_worker_processes: "{{ ansible_processor_vcpus | default(ansible_processor_count) }}" nginx_worker_connections: "1024" nginx_multi_accept: "off" @@ -50,19 +53,22 @@ nginx_extra_http_options: "" # proxy_set_header Host $http_host; nginx_remove_default_vhost: false -nginx_vhosts_filename: "vhosts.conf" nginx_vhosts: [] # Example vhost below, showing all available options: -# - listen: "80 default_server" # default: "80 default_server" +# - listen: "80" # default: "80" # server_name: "example.com" # default: N/A # root: "/var/www/example.com" # default: N/A # index: "index.html index.htm" # default: "index.html index.htm" +# filename: "example.com.conf" # Can be used to set the filename of the vhost file. # # # Properties that are only added if defined: +# server_name_redirect: "www.example.com" # default: N/A # error_page: "" # access_log: "" # error_log: "" # extra_parameters: "" # Can be used to add extra config blocks (multiline). +# template: "" # Can be used to override the `nginx_vhost_template` per host. +# state: "absent" # To remove the vhost configuration. nginx_upstreams: [] # - name: myapp1 diff --git a/meta/main.yml b/meta/main.yml index a7f79fc..9418877 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -18,6 +18,9 @@ galaxy_info: - name: Ubuntu versions: - all + - name: Archlinux + versions: + - all - name: FreeBSD versions: - 10.3 diff --git a/tasks/main.yml b/tasks/main.yml index 6c420fe..4ee901a 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -24,13 +24,16 @@ - include: setup-OpenBSD.yml when: ansible_os_family == 'OpenBSD' +- include: setup-Archlinux.yml + when: ansible_os_family == 'Archlinux' + # Vhost configuration. - include: vhosts.yml # Nginx setup. - name: Copy nginx configuration in place. template: - src: nginx.conf.j2 + src: "{{ nginx_conf_template }}" dest: "{{ nginx_conf_file_path }}" owner: root group: "{{ root_group }}" diff --git a/tasks/setup-Archlinux.yml b/tasks/setup-Archlinux.yml new file mode 100644 index 0000000..2aa89ec --- /dev/null +++ b/tasks/setup-Archlinux.yml @@ -0,0 +1,5 @@ +--- +- name: Ensure nginx is installed. + pacman: + name: "{{ nginx_package_name }}" + state: installed diff --git a/tasks/vhosts.yml b/tasks/vhosts.yml index cf27c14..05af037 100644 --- a/tasks/vhosts.yml +++ b/tasks/vhosts.yml @@ -12,17 +12,28 @@ state: directory notify: reload nginx -- name: Add managed vhost config file (if any vhosts are configured). +- name: Add managed vhost config files. template: - src: vhosts.j2 - dest: "{{ nginx_vhost_path }}/{{ nginx_vhosts_filename }}" + src: "{{ item.template|default(nginx_vhost_template) }}" + dest: "{{ nginx_vhost_path }}/{{ item.filename|default(item.server_name.split(' ')[0] ~ '.conf') }}" + force: yes + owner: root + group: root mode: 0644 - when: nginx_vhosts|length > 0 + when: item.state|default('present') != 'absent' + with_items: "{{ nginx_vhosts }}" notify: reload nginx -- name: Remove managed vhost config file (if no vhosts are configured). +- name: Remove managed vhost config files. file: - path: "{{ nginx_vhost_path }}/{{ nginx_vhosts_filename }}" + path: "{{ nginx_vhost_path }}/{{ item.filename|default(item.server_name.split(' ')[0] ~ '.conf') }}" + state: absent + when: item.state|default('present') == 'absent' + with_items: "{{ nginx_vhosts }}" + notify: reload nginx + +- name: Remove legacy vhosts.conf file. + file: + path: "{{ nginx_vhost_path }}/vhosts.conf" state: absent - when: nginx_vhosts|length == 0 notify: reload nginx diff --git a/templates/nginx.conf.j2 b/templates/nginx.conf.j2 index b04995a..7cdec60 100644 --- a/templates/nginx.conf.j2 +++ b/templates/nginx.conf.j2 @@ -3,18 +3,25 @@ user {{ nginx_user }}; error_log {{ nginx_error_log }}; pid {{ nginx_pidfile }}; +{% block worker %} worker_processes {{ nginx_worker_processes }}; +{% endblock %} {% if nginx_extra_conf_options %} {{ nginx_extra_conf_options }} {% endif %} +{% block events %} events { worker_connections {{ nginx_worker_connections }}; multi_accept {{ nginx_multi_accept }}; } +{% endblock %} http { + {% block http_begin %}{% endblock %} + +{% block http_basic %} include {{ nginx_mime_file_path }}; default_type application/octet-stream; @@ -34,16 +41,20 @@ http { keepalive_requests {{ nginx_keepalive_requests }}; server_tokens {{ nginx_server_tokens }}; - #gzip on; - {% if nginx_proxy_cache_path %} proxy_cache_path {{ nginx_proxy_cache_path }}; {% endif %} +{% endblock %} + +{% block http_gzip %} + # gzip on; +{% endblock %} {% if nginx_extra_http_options %} {{ nginx_extra_http_options|indent(4, False) }} {% endif %} +{% block http_upstream %} {% for upstream in nginx_upstreams %} upstream {{ upstream.name }} { {% if upstream.strategy is defined %} @@ -57,9 +68,14 @@ http { {% endif %} } {% endfor %} +{% endblock %} +{% block http_includes %} include {{ nginx_conf_path }}/*.conf; {% if nginx_conf_path != nginx_vhost_path %} include {{ nginx_vhost_path }}/*; {% endif %} +{% endblock %} + + {% block http_end %}{% endblock %} } diff --git a/templates/vhost.j2 b/templates/vhost.j2 new file mode 100644 index 0000000..0feb602 --- /dev/null +++ b/templates/vhost.j2 @@ -0,0 +1,47 @@ +{% block server_redirect %} +{% if item.server_name_redirect is defined %} +server { + listen {{ item.listen | default('80') }}; + server_name {{ item.server_name_redirect }}; + return 301 $scheme://{{ item.server_name.split(' ')[0] }}$request_uri; +} +{% endif %} +{% endblock %} + +server { + {% block server_begin %}{% endblock %} + + {% block server_basic -%} + listen {{ item.listen | default('80') }}; + +{% if item.server_name is defined %} + server_name {{ item.server_name }}; +{% endif %} + +{% if item.root is defined %} + root {{ item.root }}; +{% endif %} + + index {{ item.index | default('index.html index.htm') }}; + +{% if item.error_page is defined %} + error_page {{ item.error_page }}; +{% endif %} +{% if item.access_log is defined %} + access_log {{ item.access_log }}; +{% endif %} +{% if item.error_log is defined %} + error_log {{ item.error_log }} error; +{% endif %} + +{% if item.return is defined %} + return {{ item.return }}; +{% endif %} + {% endblock %} + + {% block server_end %}{% endblock %} + +{% if item.extra_parameters is defined %} + {{ item.extra_parameters|indent(4) }} +{% endif %} +} diff --git a/templates/vhosts.j2 b/templates/vhosts.j2 deleted file mode 100644 index e4e8c05..0000000 --- a/templates/vhosts.j2 +++ /dev/null @@ -1,33 +0,0 @@ -{% for vhost in nginx_vhosts %} -server { - listen {{ vhost.listen | default('80 default_server') }}; - -{% if vhost.server_name is defined %} - server_name {{ vhost.server_name }}; -{% endif %} - -{% if vhost.root is defined %} - root {{ vhost.root }}; -{% endif %} - - index {{ vhost.index | default('index.html index.htm') }}; - -{% if vhost.error_page is defined %} - error_page {{ vhost.error_page }}; -{% endif %} -{% if vhost.access_log is defined %} - access_log {{ vhost.access_log }}; -{% endif %} -{% if vhost.error_log is defined %} - error_log {{ vhost.error_log }} error; -{% endif %} - -{% if vhost.return is defined %} - return {{ vhost.return }}; -{% endif %} - -{% if vhost.extra_parameters is defined %} - {{ vhost.extra_parameters|indent(4) }} -{% endif %} -} -{% endfor %} diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..6fb2117 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,11 @@ +# Ansible Role tests + +To run the test playbook(s) in this directory: + + 1. Install and start Docker. + 1. Download the test shim (see .travis.yml file for the URL) into `tests/test.sh`: + - `wget -O tests/test.sh https://gist.githubusercontent.com/geerlingguy/73ef1e5ee45d8694570f334be385e181/raw/` + 1. Make the test shim executable: `chmod +x tests/test.sh`. + 1. Run (from the role root directory) `distro=[distro] playbook=[playbook] ./tests/test.sh` + +If you don't want the container to be automatically deleted after the test playbook is run, add the following environment variables: `cleanup=false container_id=$(date +%s)` diff --git a/tests/test.yml b/tests/test.yml index 13426a9..9ae8960 100644 --- a/tests/test.yml +++ b/tests/test.yml @@ -3,6 +3,19 @@ vars: nginx_use_ppa: true + nginx_remove_default_vhost: true + nginx_vhosts: + - server_name: "test.dev" + root: "/var/www/test" + + pre_tasks: + - name: Update apt cache. + apt: update_cache=yes cache_valid_time=600 + when: ansible_os_family == 'Debian' + changed_when: false + + - name: Install dependencies. + package: name=curl state=present roles: - role_under_test diff --git a/vars/Archlinux.yml b/vars/Archlinux.yml new file mode 100644 index 0000000..593e100 --- /dev/null +++ b/vars/Archlinux.yml @@ -0,0 +1,9 @@ +--- +root_group: root +nginx_conf_path: /etc/nginx/conf.d +nginx_conf_file_path: /etc/nginx/nginx.conf +nginx_mime_file_path: /etc/nginx/mime.types +nginx_pidfile: /run/nginx.pid +nginx_vhost_path: /etc/nginx/sites-enabled +nginx_default_vhost_path: /etc/nginx/sites-enabled/default +__nginx_user: "http"