Skip to content

Ansible

Ansible is used to configure the web server to host multiple sites.

  • Directoryansible/ software
    • Directorygroup_vars/all/
      • main.yaml settings
      • vault.yaml encrypted secrets
    • Directoryroles/ ansible code to run tasks
      • Directorygalaxy/ package manager roles
        • Directorypaultibbetts.caddy/ public role to install and manage caddy
      • Directoryinternal/ private roles
        • Directoryserver/ configures server settings
        • Directoryweb/ configures multiple static sites
    • inventory.yaml set up to use Terraform managed resources
    • site.yaml playbook of roles that does the whole setup

Ansible can use the same inventory that Terraform manages by using the Terraform plugin.

This is configured using:

inventory.yaml
plugin: cloud.terraform.terraform_provider
project_path: ../terraform

The whole setup is declared in site.yaml:

site.yaml
---
- hosts: pi
become: true
roles:
- { role: server, tags: [server] }
- { role: firewall, tags: [firewall] }
- { role: web, tags: [web] }
- { role: paultibbetts.caddy, tags: [caddy] }

Ansible sets up the following roles:

This internal role sets up the machine to act as a server.

roles/internal/server/tasks/main.yaml
#SPDX-License-Identifier: MIT-0
---
- name: Debian/Ubuntu only
ansible.builtin.assert:
that: ansible_facts.os_family == "Debian"
fail_msg: "This base role targets Debian/Ubuntu."
- name: Ensure apt cache is fresh
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- ansible.builtin.import_tasks: hostname.yaml
- ansible.builtin.import_tasks: packages.yaml
- ansible.builtin.import_tasks: ssh.yaml
- ansible.builtin.import_tasks: time.yaml
- ansible.builtin.import_tasks: updates.yaml
- ansible.builtin.import_tasks: users.yaml

This internal role sets up the server to host multiple websites.

roles/internal/web/tasks/main.yaml
#SPDX-License-Identifier: MIT-0
---
- name: Prepare user and tools for deployment
ansible.builtin.import_tasks: deploy.yaml
- name: Prepare directories
ansible.builtin.import_tasks: directories.yaml

This role is available on Ansible Galaxy.

It installs a custom build of Caddy, that includes the Cloudflare plugin, and configures it to serve web content.

The role writes the Caddyfile that configures Caddy using a template:

templates/Caddyfile.j2
{
servers {
listener_wrappers {
proxy_protocol {
# https://www.mythic-beasts.com/support/topics/proxy#sec-proxy-details
{% for ip in mythicbeasts_proxy_ipv4 %}
allow {{ ip }}/32
{% endfor %}
{% for ip in mythicbeasts_proxy_ipv6 %}
allow {{ ip }}/128
{% endfor %}
}
tls
}
}
}
(cf_tls) {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
}
{% for site in web_sites %}
{{ site }} {
import cf_tls
root * {{ web_sites_root }}/{{ site }}/current
file_server
encode zstd gzip
log
}
{% endfor %}
www.paultibbetts.uk {
import cf_tls
redir {scheme}://paultibbetts.uk{uri} permanent
}

The variables that configure each role are in ansible/group_vars/all/main.yaml:

ansible/group_vars/all/main.yaml
web_sites_root: "/srv/www"
web_sites:
- "paultibbetts.uk"
- "infra.paultibbetts.uk"
- "dev.paultibbetts.uk"
web_deploy_user: "deploy"
web_deploy_home: "/home/deploy"
web_deploy_ssh_keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK+5ESd3EGl8/Wm3VDkQ4PKTp4uDLrEDXpdgYD1nuKmV"
web_group: "deploy"
web_keep_releases: 3
caddy_caddyfile_template: "{{ playbook_dir }}/templates/Caddyfile.j2"
caddy_install_method: download
caddy_plugins:
- github.com/caddy-dns/cloudflare
caddy_manage_systemd_env_file: true
caddy_systemd_env:
CF_API_TOKEN: "{{ vault_cf_api_token }}"

Ansible does not need any secrets passing for it to work, since it uses SSH to connect to my server, but it does need to know my Cloudflare API token to pass to Caddy for it to use.

I do this using ansible-vault where the Cloudflare API token is encrypted and saved into the codebase and is available for the Caddy role to write it to the correct file for Caddy to be able to use it.

Right now it looks like:

group_vars/all/vault.yaml
$ANSIBLE_VAULT;1.1;AES256
31623761626366616266363435616332613537303665333436366362636333633138616232336431
3766356562366238373063636564353832363231636166660a356136323638613661666435626164
31323861313639306564323037363264653730393763353264616537633966633765306133666334
3339666430396366660a666330316264383034633531323035653161616631636133333062343037
35623834613932333765313861643133366664376164623536363961326230663136333139383965
38636665383961616632366530643263346334363135376662323939326466656636386633633835
626263373230633566303534323761326266

If you have the right vault-pass.txt file, like I do, it can decrypt it and use it in the role.