32 Commits

Author SHA1 Message Date
99a8c19aa3 feat(restic): migrate to systemd template units 2025-03-23 13:39:18 +01:00
ed95d4fd3d update(nginx): bump version to 1.27.4 2025-03-22 12:25:00 +01:00
788c4bada0 feat(restic): add scripts for directory backup and snapshot metrics generation 2025-03-08 22:47:58 +01:00
7ecf8778ca feat(lego): add pre- and post-renewal hooks 2025-03-01 22:27:29 +01:00
11fec18afc update(lego): bump version to 4.22.2 2025-02-18 17:24:12 +01:00
b0ba86f78e update(lego): bump version to 4.22.1 2025-02-17 17:56:20 +01:00
bd418a6199 update(nginx): bump version to 1.27.3 2025-01-30 15:36:40 +01:00
60b36db8a7 update(lego): bump version to 4.21.0 2024-12-20 20:58:59 +01:00
9a02652d98 meta!: bump galaxy collection to 0.2.0 and drop deprecated roles 2024-12-01 09:46:44 +01:00
2c1b3cb47e chore(mariadb): role was migrated to finallycoffee.databases collection 2024-12-01 09:37:40 +01:00
114cf13871 chore(elasticsearch): role was migrated to finallycoffee.databases collection 2024-12-01 09:36:27 +01:00
b77c81f754 update(lego): bump version to 4.20.4 2024-11-21 18:39:34 +01:00
bdf1871855 update(lego): bump version to 4.20.2 2024-11-12 17:32:39 +01:00
9454845ea1 meta: bump collection version to 0.1.3 2024-10-29 17:53:42 +01:00
4e8cc9bcf2 meta: deprecate elasticsearch role 2024-10-29 17:53:16 +01:00
3b9d6e19da meta: deprecate mariadb role 2024-10-29 17:49:00 +01:00
c847046720 refactor(mariadb): add state parameter and split container image arguments 2024-10-27 16:04:07 +01:00
d7b7c59f46 update(lego): bump version to 4.19.2 2024-10-23 20:48:59 +02:00
153df81836 feat(lego): Detect lego_architecture automatically 2024-10-18 14:53:59 +02:00
7021ed1a89 meta: bump collection version to 0.1.2, require community.docker@^3.0.0, take issues on codeberg repo mirror 2024-10-05 10:19:15 +02:00
afe72f554e chore(nginx): add deployment_method=podman 2024-10-05 10:16:53 +02:00
c36e95d7eb chore(nginx): add state=absent support 2024-10-05 10:14:35 +02:00
97526aec36 update(nginx): bump version to 1.27.2 2024-10-05 10:01:07 +02:00
fc73fdd1fa fix(docs): typos and formatting 2024-09-21 11:36:21 +02:00
bd43f3963c meta: bump collection version to 0.1.1 2024-09-21 11:11:36 +02:00
1076a9f384 update(mariadb): bump version to 10.11.9 2024-09-21 11:10:09 +02:00
82e69bdda3 update(nginx): bump version to 1.26.2 2024-09-21 11:08:57 +02:00
b9b5c19d38 update(lego): bump version to 4.18.0 2024-09-21 11:07:42 +02:00
b9e4abdf36 meta: require ansible >=2.15.0 2024-09-21 11:06:41 +02:00
aac6891518 meta: update collection version to 0.1.0, add community.docker as dependency 2024-09-21 10:58:40 +02:00
31d025ed75 docs: add READMEs to roles powerdns_tsig_key and dns 2024-09-21 10:57:46 +02:00
1423d2a243 feat(restic): add support for additional env vars, optional init and checking and pre-backup hooks 2024-09-18 18:20:12 +02:00
35 changed files with 491 additions and 346 deletions

View File

@ -5,15 +5,12 @@
This ansible collection provides various roles for installing This ansible collection provides various roles for installing
and configuring basic system utilities like gnupg, ssh etc and configuring basic system utilities like gnupg, ssh etc
- [`elasticsearch`](roles/elasticsearch/README.md): Deploy [elasticsearch](https://www.docker.elastic.co/r/elasticsearch/elasticsearch-oss),
a popular (distributed) search and analytics engine, mostly known by it's
letter "E" in the ELK-stack.
- [`git`](roles/git/README.md): configures git on the target system - [`git`](roles/git/README.md): configures git on the target system
- [`gnupg`](roles/gnupg/README.md): configures gnupg on the target system - [`gnupg`](roles/gnupg/README.md): configures gnupg on the target system
- [`mariadb`](roles/mariadb/README.md): runs [MariaDB Server](https://mariadb.org/), one of the world's most popular open source relational database - [`lego`](roles/lego/README.md): runs [lego (LetsEncrypt Go)](https://github.com/go-acme/lego),
a ACME client written in go, using systemd (timers). Multi-instance capable.
- [`minio`](roles/minio/README.md): Deploy [min.io](https://min.io), an - [`minio`](roles/minio/README.md): Deploy [min.io](https://min.io), an
s3-compatible object storage server, using docker containers. s3-compatible object storage server, using docker containers.
@ -24,6 +21,9 @@ and configuring basic system utilities like gnupg, ssh etc
- [`restic`](roles/restic/README.md): Manage backups using restic - [`restic`](roles/restic/README.md): Manage backups using restic
and persist them to a configurable backend. and persist them to a configurable backend.
- [`powerdns_tsig_key`](roles/powerdns_tsig_key/README.md): Simple ansible role
for generating TSIG keys in PowerDNS.
## License ## License
[CNPLv7+](LICENSE.md): Cooperative Nonviolent Public License [CNPLv7+](LICENSE.md): Cooperative Nonviolent Public License

View File

@ -1,12 +1,20 @@
namespace: finallycoffee namespace: finallycoffee
name: base name: base
version: 0.0.2 version: 0.2.0
readme: README.md readme: README.md
authors: authors:
- transcaffeine <transcaffeine@finally.coffee> - transcaffeine <transcaffeine@finally.coffee>
description: Roles for base services which are common dependencies other services like databases description: Roles for base services which are common dependencies other services like databases
dependencies:
"community.docker": "^3.0.0"
license_file: LICENSE.md license_file: LICENSE.md
build_ignore: build_ignore:
- '*.tar.gz' - '*.tar.gz'
repository: https://git.finally.coffee/finallycoffee/base repository: https://git.finally.coffee/finallycoffee/base
issues: https://git.finally.coffee/finallycoffee/base/issues issues: https://codeberg.org/finallycoffee/ansible-collection-base/issues
tags:
- docker
- lego
- minio
- nginx
- restic

View File

@ -1,3 +1,3 @@
--- ---
requires_ansible: ">=2.12" requires_ansible: ">=2.15"

33
roles/dns/README.md Normal file
View File

@ -0,0 +1,33 @@
# `finallycoffee.base.dns` ansible role
Simple role for wrapping around the
[`famedly.dns.update`](https://github.com/famedly/ansible-collection-dns/blob/main/plugins/modules/update.py)
ansible module.
## Usage
### Example playbook
```yaml
- target: "{{ target_hosts }}"
roles:
- role: finallycoffee.base.dns
vars:
dns_server: "dns.example.org"
dns_zone: "zone.example.org"
dns_records: "{{ dns_records }}"
dns_record_state: exact
dns_tsig_name: "mykeyname"
dns_tsig_algo: "hmac-sha256"
dns_tsig_key: "mykeycontent"
vars:
dns_records:
- type: A
name: gitea
content: "127.0.0.1"
- type: AAAA
name: gitea
content: "fe80::1"
- type: CNAME
name: "_acme_challenge.gitea"
content: "delegated-cname.challenge.example.org"
```

View File

@ -1,22 +0,0 @@
# `finallycoffee.base.elastiscsearch`
A simple ansible role which deploys a single-node elastic container to provide
an easy way to do some indexing.
## Usage
Per default, `/opt/elasticsearch/data` is used to persist data, it is
customizable by using either `elasticsearch_base_path` or `elasticsearch_data_path`.
As elasticsearch be can be quite memory heavy, the maximum amount of allowed RAM
can be configured using `elasticsearch_allocated_ram_mb`, defaulting to 512 (mb).
The cluster name and discovery type can be overridden using
`elasticsearch_config_cluster_name` (default: elastic) and
`elasticsearch_config_discovery_type` (default: single-node), should one
need a multi-node elasticsearch deployment.
Per default, no ports or networks are mapped, and explizit mapping using
either ports (`elasticsearch_container_ports`) or networks
(`elasticsearch_container_networks`) is required in order for other services
to use elastic.

View File

@ -1,35 +0,0 @@
---
elasticsearch_version: 7.17.7
elasticsearch_base_path: /opt/elasticsearch
elasticsearch_data_path: "{{ elasticsearch_base_path }}/data"
elasticsearch_config_cluster_name: elastic
elasticsearch_config_discovery_type: single-node
elasticsearch_config_boostrap_memory_lock: true
elasticsearch_allocated_ram_mb: 512
elasticsearch_container_image_name: docker.elastic.co/elasticsearch/elasticsearch-oss
elasticsearch_container_image_tag: ~
elasticsearch_container_image: >-
{{ elasticsearch_container_image_name }}:{{ elasticsearch_container_image_tag | default(elasticsearch_version, true) }}
elasticsearch_container_name: elasticsearch
elasticsearch_container_env:
"ES_JAVA_OPTS": "-Xms{{ elasticsearch_allocated_ram_mb }}m -Xmx{{ elasticsearch_allocated_ram_mb }}m"
"cluster.name": "{{ elasticsearch_config_cluster_name }}"
"discovery.type": "{{ elasticsearch_config_discovery_type }}"
"bootstrap.memory_lock": "{{ 'true' if elasticsearch_config_boostrap_memory_lock else 'false' }}"
elasticsearch_container_user: ~
elasticsearch_container_ports: ~
elasticsearch_container_labels:
version: "{{ elasticsearch_version }}"
elasticsearch_container_ulimits:
# - "memlock:{{ (1.5 * 1024 * elasticsearch_allocated_ram_mb) | int }}:{{ (1.5 * 1024 * elasticsearch_allocated_ram_mb) | int }}"
- "memlock:-1:-1"
elasticsearch_container_volumes:
- "{{ elasticsearch_data_path }}:/usr/share/elasticsearch/data:z"
elasticsearch_container_networks: ~
elasticsearch_container_purge_networks: ~
elasticsearch_container_restart_policy: unless-stopped

View File

@ -1,32 +0,0 @@
---
- name: Ensure host directories are present
file:
path: "{{ item }}"
state: directory
mode: "0777"
loop:
- "{{ elasticsearch_base_path }}"
- "{{ elasticsearch_data_path }}"
- name: Ensure elastic container image is present
docker_image:
name: "{{ elasticsearch_container_image }}"
state: present
source: pull
force_source: "{{ elasticsearch_container_image_tag|default(false, true)|bool }}"
- name: Ensure elastic container is running
docker_container:
name: "{{ elasticsearch_container_name }}"
image: "{{ elasticsearch_container_image }}"
env: "{{ elasticsearch_container_env | default(omit, True) }}"
user: "{{ elasticsearch_container_user | default(omit, True) }}"
ports: "{{ elasticsearch_container_ports | default(omit, True) }}"
labels: "{{ elasticsearch_container_labels | default(omit, True) }}"
volumes: "{{ elasticsearch_container_volumes }}"
ulimits: "{{ elasticsearch_container_ulimits }}"
networks: "{{ elasticsearch_container_networks | default(omit, True) }}"
purge_networks: "{{ elasticsearch_container_purge_networks | default(omit, True) }}"
restart_policy: "{{ elasticsearch_container_restart_policy }}"
state: started

View File

@ -1,6 +1,6 @@
--- ---
lego_user: "lego" lego_user: "lego"
lego_version: "4.17.4" lego_version: "4.22.2"
lego_instance: default lego_instance: default
lego_base_path: "/opt/lego" lego_base_path: "/opt/lego"
lego_cert_user: "acme-{{ lego_instance }}" lego_cert_user: "acme-{{ lego_instance }}"
@ -58,7 +58,7 @@ lego_systemd_timer_name: "lego-{{ lego_instance }}.timer"
lego_systemd_timer_template: lego.timer.j2 lego_systemd_timer_template: lego.timer.j2
lego_systemd_timer_calendar: "*-*-* *:00/15:00" lego_systemd_timer_calendar: "*-*-* *:00/15:00"
lego_architecture: "amd64" lego_architecture: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}"
lego_os: "linux" lego_os: "linux"
lego_binary_allow_net_bind_service: false lego_binary_allow_net_bind_service: false

View File

@ -1,5 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
LEGO_BINARY=$(/usr/bin/env which lego) LEGO_BINARY=$(/usr/bin/env which lego)
@ -15,6 +14,10 @@ if [[ -n "$LEGO_HTTP_FALLBACK_PORT" ]]; then
fi fi
fi fi
if [[ -n "$LEGO_PRE_RENEWAL_HOOK" ]]; then
$LEGO_PRE_RENEWAL_HOOK
fi
LEGO_COMMAND_ARGS_EXPANDED=$(bash -c "echo $LEGO_COMMAND_ARGS") # This is a bit icky LEGO_COMMAND_ARGS_EXPANDED=$(bash -c "echo $LEGO_COMMAND_ARGS") # This is a bit icky
FILES_IN_DIR=$(find "$LEGO_CERT_STORE_PATH/certificates" -type f | wc -l) FILES_IN_DIR=$(find "$LEGO_CERT_STORE_PATH/certificates" -type f | wc -l)
@ -24,5 +27,9 @@ else
$LEGO_BINARY $LEGO_COMMAND_ARGS_EXPANDED run $LEGO_BINARY $LEGO_COMMAND_ARGS_EXPANDED run
fi fi
find "$LEGO_CERT_STORE_PATH/certificates/" -type f | xargs -I{} -n 1 chmod "$LEGO_CERT_MODE" "{}" find "$LEGO_CERT_STORE_PATH/certificates" -type f | xargs -I{} -n 1 chmod "$LEGO_CERT_MODE" "{}"
find "$LEGO_CERT_STORE_PATH/certificates/" -type f | xargs -I{} -n 1 chown "${LEGO_CERT_USER}:${LEGO_CERT_GROUP}" "{}" find "$LEGO_CERT_STORE_PATH/certificates" -type f | xargs -I{} -n 1 chown "${LEGO_CERT_USER}:${LEGO_CERT_GROUP}" "{}"
if [[ -n "$LEGO_POST_RENEWAL_HOOK" ]]; then
$LEGO_POST_RENEWAL_HOOK
fi

View File

@ -1,19 +0,0 @@
# `finallycoffee.base.mariadb` ansible role
This role deploys a MariaDB instance in a docker container.
## Usage
The role expects the following variables to be populated with values and/or secrets:
```yaml
mariadb_root_password: #mariadb root password
mariadb_database: # name of the database to create
mariadb_username: # name of a user to auto-create and assign permission on the mariadb_database
mariadb_password: # password of the user in mariadb_username
```
## Requirements
- Docker installed
- python-docker present on target system for ansible to be able to talk with the docker API.

View File

@ -1,32 +0,0 @@
---
mariadb_version: "10.11.6"
mariadb_base_path: /var/lib/mariadb
mariadb_data_path: "{{ mariadb_base_path }}/{{ mariadb_version }}"
mariadb_root_password: ~
mariadb_database: ~
mariadb_username: ~
mariadb_password: ~
mariadb_container_base_environment:
MARIADB_ROOT_PASSWORD: "{{ mariadb_root_password }}"
mariadb_container_extra_environment: {}
mariadb_container_name: mariadb
mariadb_container_image_name: docker.io/mariadb
mariadb_container_image_tag: ~
mariadb_container_image: "{{ mariadb_container_image_name }}:{{ mariadb_container_image_tag | default(mariadb_version, true) }}"
mariadb_container_base_volumes:
- "{{ mariadb_data_path }}:{{ mariadb_container_data_path }}:z"
mariadb_container_extra_volumes: []
mariadb_container_base_labels:
version: "{{ mariadb_version }}"
mariadb_container_extra_labels: {}
mariadb_container_restart_policy: "unless-stopped"
mariadb_container_environment: >-2
{{ mariadb_container_base_environment
| combine(mariadb_container_database_environment
if (mariadb_database and mariadb_username and mariadb_password)
else {}, recursive=True)
| combine(mariadb_container_extra_environment) }}

View File

@ -1,20 +0,0 @@
---
- name: Ensure mariaDB container image is present on host
community.docker.docker_image:
name: "{{ mariadb_container_image }}"
state: present
source: pull
- name: Ensure mariaDB {{ mariadb_version }} is running as '{{ mariadb_container_name }}'
community.docker.docker_container:
name: "{{ mariadb_container_name }}"
image: "{{ mariadb_container_image }}"
env: "{{ mariadb_container_environment }}"
ports: "{{ mariadb_container_ports }}"
labels: "{{ mariadb_container_labels }}"
volumes: "{{ mariadb_container_volumes }}"
networks: "{{ mariadb_container_networks | default(omit, true) }}"
etc_hosts: "{{ mariadb_container_etc_hosts | default(omit, true) }}"
purge_networks: "{{ mariadb_container_purge_networks | default(omit, true) }}"
restart_policy: "{{ mariadb_container_restart_policy }}"
state: started

View File

@ -1,10 +0,0 @@
---
mariadb_container_database_environment:
MARIADB_DATABASE: "{{ mariadb_database }}"
MARIADB_USER: "{{ mariadb_username }}"
MARIADB_PASSWORD: "{{ mariadb_password }}"
mariadb_container_data_path: /var/lib/mysql
mariadb_container_volumes: "{{ mariadb_container_base_volumes + mariadb_container_extra_volumes }}"
mariadb_container_labels: "{{ mariadb_container_base_labels | combine(mariadb_container_extra_labels, recursive=True) }}"

View File

@ -26,3 +26,8 @@ For exposing this server to the host and/or internet, the `nginx_container_ports
from host to container), `nginx_container_networks` (docker networking) or `nginx_container_labels` from host to container), `nginx_container_networks` (docker networking) or `nginx_container_labels`
(for label-based routing discovery like traefik) can be used. The options correspond to the arguments (for label-based routing discovery like traefik) can be used. The options correspond to the arguments
of the `community.docker.docker_container` module. of the `community.docker.docker_container` module.
## Deployment methods
Set `nginx_deployment_method` to either `docker` or `podman` to use the respective ansible modules for
creating and managing the container and its image. See all supported methods in `nginx_deployment_methods`.

View File

@ -1,9 +1,10 @@
--- ---
nginx_version: "1.27.4"
nginx_version: "1.25.3"
nginx_flavour: alpine nginx_flavour: alpine
nginx_base_path: /opt/nginx nginx_base_path: /opt/nginx
nginx_config_file: "{{ nginx_base_path }}/nginx.conf" nginx_config_file: "{{ nginx_base_path }}/nginx.conf"
nginx_state: present
nginx_deployment_method: docker
nginx_container_name: nginx nginx_container_name: nginx
nginx_container_image_reference: >- nginx_container_image_reference: >-
@ -26,6 +27,9 @@ nginx_container_image_repository: >-
nginx_container_image_registry: "docker.io" nginx_container_image_registry: "docker.io"
nginx_container_image_name: "nginx" nginx_container_image_name: "nginx"
nginx_container_image_tag: ~ nginx_container_image_tag: ~
nginx_container_image_source: pull
nginx_container_state: >-2
{{ (nginx_state == 'present') | ternary('started', 'absent') }}
nginx_container_restart_policy: "unless-stopped" nginx_container_restart_policy: "unless-stopped"
nginx_container_volumes: nginx_container_volumes:

12
roles/nginx/meta/main.yml Normal file
View File

@ -0,0 +1,12 @@
---
allow_duplicates: true
dependencies: []
galaxy_info:
role_name: nginx
description: Deploy nginx, a webserver
galaxy_tags:
- nginx
- http
- webserver
- docker
- podman

View File

@ -0,0 +1,28 @@
---
- name: Ensure docker container image '{{ nginx_container_image_reference }}' is {{ nginx_state }}
community.docker.docker_image:
name: "{{ nginx_container_image_reference }}"
state: "{{ nginx_state }}"
source: "{{ nginx_container_image_source }}"
force_source: >-2
{{ nginx_container_image_force_source
| default(nginx_container_image_tag | default(false, true)) }}
register: nginx_container_image_info
until: nginx_container_image_info is success
retries: 5
delay: 3
- name: Ensure docker container '{{ nginx_container_name }}' is {{ nginx_container_state }}
community.docker.docker_container:
name: "{{ nginx_container_name }}"
image: "{{ nginx_container_image_reference }}"
env: "{{ nginx_container_env | default(omit, true) }}"
user: "{{ nginx_container_user | default(omit, true) }}"
ports: "{{ nginx_container_ports | default(omit, true) }}"
labels: "{{ nginx_container_labels | default(omit, true) }}"
volumes: "{{ nginx_container_volumes | default(omit, true) }}"
etc_hosts: "{{ nginx_container_etc_hosts | default(omit, true) }}"
networks: "{{ nginx_container_networks | default(omit, true) }}"
purge_networks: "{{ nginx_container_purge_networks | default(omit, true) }}"
restart_policy: "{{ nginx_container_restart_policy }}"
state: "{{ nginx_container_state }}"

View File

@ -0,0 +1,27 @@
---
- name: Ensure container image '{{ nginx_container_image_reference }}' is {{ nginx_state }}
containers.podman.podman_image:
name: "{{ nginx_container_image_reference }}"
state: "{{ nginx_state }}"
pull: "{{ nginx_container_image_source == 'pull' }}"
force: >-2
{{ nginx_container_image_force_source
| default(nginx_container_image_tag | default(false, true)) }}
register: nginx_container_image_info
until: nginx_container_image_info is success
retries: 5
delay: 3
- name: Ensure container '{{ nginx_container_name }}' is {{ nginx_container_state }}
containers.podman.podman_container:
name: "{{ nginx_container_name }}"
image: "{{ nginx_container_image_reference }}"
env: "{{ nginx_container_env | default(omit, true) }}"
user: "{{ nginx_container_user | default(omit, true) }}"
ports: "{{ nginx_container_ports | default(omit, true) }}"
labels: "{{ nginx_container_labels | default(omit, true) }}"
volumes: "{{ nginx_container_volumes | default(omit, true) }}"
etc_hosts: "{{ nginx_container_etc_hosts | default(omit, true) }}"
network: "{{ nginx_container_networks | default(omit, true) }}"
restart_policy: "{{ nginx_container_restart_policy }}"
state: "{{ nginx_container_state }}"

View File

@ -1,10 +1,30 @@
--- ---
- name: Check if state is supported
ansible.builtin.fail:
msg: >-2
Unsupported state '{{ nginx_state }}'. Supported
states are {{ nginx_states | join(', ') }}.
when: nginx_state not in nginx_states
- name: Ensure base path '{{ nginx_base_path }}' exists - name: Check if deployment_method is supported
ansible.builtin.fail:
msg: >-2
Unsupported state '{{ nginx_deployment_method }}'. Supported
states are {{ nginx_deployment_methods | join(', ') }}.
when: nginx_deployment_method not in nginx_deployment_methods
- name: Ensure nginx config file is {{ nginx_state }}
ansible.builtin.file:
path: "{{ nginx_config_file }}"
state: "{{ nginx_state }}"
when: nginx_state == 'absent'
- name: Ensure base path '{{ nginx_base_path }}' is {{ nginx_state }}
ansible.builtin.file: ansible.builtin.file:
path: "{{ nginx_base_path }}" path: "{{ nginx_base_path }}"
state: directory mode: "0755"
mode: 0755 state: >-2
{{ (nginx_state == 'present') | ternary('directory', 'absent') }}
- name: Ensure nginx config file is templated - name: Ensure nginx config file is templated
ansible.builtin.copy: ansible.builtin.copy:
@ -13,25 +33,8 @@
mode: 0640 mode: 0640
notify: notify:
- restart-nginx - restart-nginx
when: nginx_state == 'present'
- name: Ensure docker container image is present - name: Deploy using {{ nginx_deployment_method }}
community.docker.docker_image: ansible.builtin.include_tasks:
name: "{{ nginx_container_image_reference }}" file: "deploy-{{ nginx_deployment_method }}.yml"
state: present
source: pull
force_source: "{{ nginx_container_image_tag is defined and nginx_container_image_tag | string != '' }}"
- name: Ensure docker container '{{ nginx_container_name }}' is running
community.docker.docker_container:
name: "{{ nginx_container_name }}"
image: "{{ nginx_container_image_reference }}"
env: "{{ nginx_container_env | default(omit, true) }}"
user: "{{ nginx_container_user | default(omit, true) }}"
ports: "{{ nginx_container_ports | default(omit, true) }}"
labels: "{{ nginx_container_labels | default(omit, true) }}"
volumes: "{{ nginx_container_volumes | default(omit, true) }}"
etc_hosts: "{{ nginx_container_etc_hosts | default(omit, true) }}"
networks: "{{ nginx_container_networks | default(omit, true) }}"
purge_networks: "{{ nginx_container_purge_networks | default(omit, true) }}"
restart_policy: "{{ nginx_container_restart_policy }}"
state: started

View File

@ -0,0 +1,7 @@
---
nginx_states:
- present
- absent
nginx_deployment_methods:
- docker
- podman

View File

@ -0,0 +1,25 @@
# `finallycoffee.base.powerdns_tsig_key`
Simple ansible role for ensuring a TSIG key is present in a given PowerDNS-
instance.
## Usage
The usage example below assumes `powerdns` is running in a container named `powerdns` (as supplied to `powerdns_tsig_key_container_name`.
```yaml
- hosts: "{{ target_hosts }}"
become: true
roles:
- role: finallycoffee.base.powerdns_tsig_key
vars:
powerdns_tsig_key_name: "nameofmykey"
powerdns_tsig_key_path: "/var/lib/myapp/tsig.key"
powernds_tsig_key_algo: "hmac-sha512"
powerdns_tsig_key_path_owner: "myappuser"
powerdns_tsig_key_path_group: "myappgroup"
powerdns_tsig_key_container_name: 'powerdns'
```
> [!NOTE]
> Support for non-docker deployments is pending.

View File

@ -1,37 +0,0 @@
---
restic_repo_url: ~
restic_repo_password: ~
restic_s3_key_id: ~
restic_s3_access_key: ~
restic_backup_paths: []
restic_backup_stdin_command: ~
restic_backup_stdin_command_filename: ~
restic_policy_keep_all_within: 1d
restic_policy_keep_hourly: 6
restic_policy_keep_daily: 2
restic_policy_keep_weekly: 7
restic_policy_keep_monthly: 4
restic_policy_backup_frequency: hourly
restic_policy:
keep_within: "{{ restic_policy_keep_all_within }}"
hourly: "{{ restic_policy_keep_hourly }}"
daily: "{{ restic_policy_keep_daily }}"
weekly: "{{ restic_policy_keep_weekly }}"
monthly: "{{ restic_policy_keep_monthly }}"
frequency: "{{ restic_policy_backup_frequency }}"
restic_user: root
restic_create_user: false
restic_start_job_on_unit_change: false
restic_job_name: ~
restic_job_description: "Restic backup job for {{ restic_job_name }}"
restic_systemd_unit_naming_scheme: "restic.{{ restic_job_name }}"
restic_systemd_working_directory: /tmp
restic_systemd_syslog_identifier: "restic-{{ restic_job_name }}"
restic_package_name: restic

View File

@ -0,0 +1,52 @@
---
restic_repo_url: ~
restic_repo_password: ~
restic_s3_key_id: ~
restic_s3_access_key: ~
restic_backup_paths: []
restic_backup_stdin_command: ~
restic_backup_stdin_command_filename: ~
restic_backup_generate_metrics_command: >-2
{{ restic_script_generate_snapshot_metrics }}
restic_policy_keep_all_within: 1d
restic_policy_keep_hourly: 12
restic_policy_keep_daily: 7
restic_policy_keep_weekly: 6
restic_policy_keep_monthly: 6
restic_policy_keep_yearly: 5
restic_policy_backup_frequency: hourly
restic_base_environment:
RESTIC_REPOSITORY: "{{ restic_repo_url }}"
RESTIC_PASSWORD: "{{ restic_password }}"
RESTIC_JOBNAME: "{{ restic_job_name | default('unknown') }}"
RESTIC_FORGET_KEEP_WITHIN: "{{ restic_policy_keep_all_within }}"
RESTIC_FORGET_KEEP_HOURLY: "{{ restic_policy_keep_hourly }}"
RESTIC_FORGET_KEEP_DAILY: "{{ restic_policy_keep_daily }}"
RESTIC_FORGET_KEEP_WEEKLY: "{{ restic_policy_keep_weekly }}"
RESTIC_FORGET_KEEP_MONTHLY: "{{ restic_policy_keep_monthly }}"
RESTIC_FORGET_KEEP_YEARLY: "{{ restic_policy_keep_yearly }}"
RESTIC_GENERATE_SNAPSHOT_METRICS_COMMAND: "{{ restic_backup_generate_metrics_command }}"
restic_s3_environment:
AWS_ACCESS_KEY_ID: "{{ restic_s3_key_id }}"
AWS_SECRET_ACCESS_KEY: "{{ restic_s3_access_key }}"
restic_complete_environment: >-
{{
restic_base_environment
| combine((restic_s3_environment | default({}))
if (restic_s3_key_id and restic_s3_access_key) else {})
| combine(restic_environment | default({}))
}}
restic_policy:
keep_within: "{{ restic_policy_keep_all_within }}"
hourly: "{{ restic_policy_keep_hourly }}"
daily: "{{ restic_policy_keep_daily }}"
weekly: "{{ restic_policy_keep_weekly }}"
monthly: "{{ restic_policy_keep_monthly }}"
yearly: "{{ restic_policy_keep_yearly }}"
frequency: "{{ restic_policy_backup_frequency }}"

View File

@ -0,0 +1,15 @@
---
restic_user: root
restic_user_create: false
restic_create_user: "{{ restic_user_create }}"
restic_user_create_home: false
restic_user_system: false
restic_state: present
restic_version: "0.17.3"
restic_job_name: default
restic_job_state: "{{ restic_state }}"
restic_job_directory: "/etc/restic"
restic_package_name: restic
restic_script_generate_snapshot_metrics: "/opt/restic-generate-snapshot-metrics.sh"

View File

@ -0,0 +1,11 @@
---
restic_systemd_job_name: ~
restic_systemd_job_description: "Restic backup job for {{ restic_job_name }}"
restic_systemd_working_directory: /tmp
restic_systemd_install_wanted_by: "basic.target"
restic_systemd_install_default_instance: "default"
restic_systemd_start_job_on_unit_change: false
restic_systemd_service_exec_start: "/opt/restic-backup.sh"

View File

@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ -n ${RESTIC_PRE_BACKUP_HOOK-} ]]; then
/bin/bash -c "$RESTIC_PRE_BACKUP_HOOK"
fi
echo "List existing snapshots or initialize repository"
restic snapshots || restic init
sleep 1;
echo "Attempting to remove lock if present"
restic unlock
sleep 1;
echo "Start backup on ${@:1}"
restic --verbose --retry-lock=${RESTIC_RETRY_LOCK:-5m} backup "${@:1}"
sleep 1;
if [[ -n ${RESTIC_POIST_BACKUP_HOOK-} ]]; then
/bin/bash -c "$RESTIC_POST_BACKUP_HOOK"
fi
echo "Forget and prune old snapshots"
restic forget --prune --retry-lock=${RESTIC_RETRY_LOCK:-5m} \
--keep-within=${RESTIC_FORGET_KEEP_WITHIN:-1d} \
--keep-hourly=${RESTIC_FORGET_KEEP_HOURLY:-6} \
--keep-daily=${RESTIC_FORGET_KEEP_DAILY:-2} \
--keep-weekly=${RESTIC_FORGET_KEEP_WEEKLY:-7} \
--keep-monthly=${RESTIC_FORGET_KEEP_MONTHLY:-4} \
--verbose
sleep 2
echo "Generate snapshot metrics"
if [[ -n ${RESTIC_GENERATE_SNAPSHOT_METRICS_COMMAND-} ]]; then
restic --json snapshots | ${RESTIC_GENERATE_SNAPSHOT_METRICS_COMMAND} \
> /var/lib/node_exporter/restic-snapshots-${RESTIC_JOBNAME:-unknown}.prom-src
sleep 2;
fi
echo "Check repository"
restic check

View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
RESTIC_JSON=$(</dev/stdin)
echo $RESTIC_JSON | jq -r '.[]
| {
"hostname": .hostname,
"username": .username,
"short_id": .short_id,
"time": ((((.time | split(".")[0]) + "Z") | fromdate) - (3600 * (.time | split("+")[1] | split(":")[0] | tonumber + 1))),
"paths": .paths[]
} | "restic_snapshots{hostname=\"\(.hostname)\",username=\"\(.username)\",short_id=\"\(.short_id)\",paths=\"\(.paths)\"} \(.time)"'

View File

@ -0,0 +1,35 @@
---
- name: Check if 'restic_state' is valid
ansible.builtin.fail:
msg: >-2
Unknown value '{{ restic_state }}' for 'restic_state'!
Supported values are {{ restic_states | join(', ') }}
when: restic_state not in restic_states
- name: Ensure 'restic_job_name' is properly populated
ansible.builtin.fail:
msg: >-2
Unsupported restic_job_name '{{ restic_job_name | string }}'!
when:
- not (restic_job_name | string | length > 0)
- name: Ensure either backup_paths or backup_stdin_command is populated
ansible.builtin.fail:
msg: >-2
Setting both `restic_backup_paths` and `restic_backup_stdin_command`
is not supported!
when: restic_backup_paths|length > 0 and restic_backup_stdin_command and false
- name: Ensure a filename for stdin_command backup is given
ansible.builtin.fail:
msg: >-2
`restic_backup_stdin_command` was set but no filename for the resulting
output was supplied in `restic_backup_stdin_command_filename`.
when: restic_backup_stdin_command and not restic_backup_stdin_command_filename
- name: Ensure backup frequency adheres to systemd's OnCalender syntax
command:
cmd: "systemd-analyze calendar {{ restic_policy.frequency }}"
register: systemd_calender_parse_res
failed_when: systemd_calender_parse_res.rc != 0
changed_when: false

View File

@ -0,0 +1,31 @@
---
- name: Ensure systemd timer file for '{{ restic_job_name }}' is {{ restic_state }}'
template:
dest: "/etc/systemd/system/{{ restic_systemd_unit_naming_scheme }}.timer"
src: restic.timer.j2
owner: root
group: root
mode: "0640"
when: restic_state == 'present'
notify:
- reload-systemd
- name: Ensure restic configuration for '{{ restic_job_name }}' is {{ restic_job_state }}
ansible.builtin.template:
src: "restic.conf.j2"
dest: "{{ restic_job_directory }}/{{ restic_job_name }}.conf"
mode: "0640"
when: restic_job_state in ['present', 'masked']
notify:
- trigger-restic
- name: Ensure restic configuration for '{{ restic_job_name }}' is {{ restic_job_state }}
ansible.builtin.file:
path: "{{ restic_job_directory }}/{{ restic_job_name }}.conf"
state: "{{ restic_job_state }}"
when: restic_job_state not in ['present', 'masked']
- name: Flush handlers to ensure systemd knows about '{{ restic_job_name }}'
meta: flush_handlers

View File

@ -0,0 +1,48 @@
---
- name: Ensure restic is installed
block:
- name: Ensure restic is installed via apt
apt:
package: restic
state: latest
when: ansible_os_family == 'Debian'
- name: Ensure restic is installed via dnf
dnf:
name: restic
state: latest
when: ansible_os_family == 'RedHat'
- name: Ensure restic is installed using the auto-detected package-manager
package:
name: "{{ restic_package_name }}"
state: present
when: ansible_os_family not in ['RedHat', 'Debian']
- name: Ensure restic backup scripts are {{ restic_state }}
ansible.builtin.copy:
src: "{{ script.source }}"
dest: "{{ script.destination }}"
mode: "{{ script.mode }}"
loop:
- source: restic-backup.sh
destination: "{{ restic_systemd_service_exec_start }}"
mode: "0510"
- source: restic-snapshot-metrics.sh
destination: "{{ restic_script_generate_snapshot_metrics }}"
mode: "0510"
loop_control:
loop_var: script
label: "{{ script.source }}"
- name: Ensure systemd service file for restic template unit is {{ restic_state }}
ansible.builtin.template:
dest: "/etc/systemd/system/restic@.service"
src: "restic@.service.j2"
owner: root
group: root
mode: "0640"
when: restic_state == 'present'
notify:
- reload-systemd
- trigger-restic

View File

@ -1,77 +1,30 @@
--- ---
- name: Check if role input is valid
ansible.builtin.include_tasks:
file: check.yml
- name: Ensure {{ restic_user }} system user exists - name: Ensure restic is {{ restic_state }}
user: ansible.builtin.include_tasks:
file: install.yml
- name: Ensure restic user '{{ restic_user }}' is {{ restic_state }}
ansible.builtin.user:
name: "{{ restic_user }}" name: "{{ restic_user }}"
state: present state: "{{ restic_state }}"
system: true system: "{{ restic_user_system }}"
create_home: "{{ restic_user_create_home }}"
when: restic_create_user when: restic_create_user
- name: Ensure either backup_paths or backup_stdin_command is populated - name: Ensure restic configuration for job is {{ restic_job_state }}
when: restic_backup_paths|length > 0 and restic_backup_stdin_command and false ansible.builtin.include_tasks:
fail: file: "configure.yml"
msg: "Setting both `restic_backup_paths` and `restic_backup_stdin_command` is not supported"
- name: Ensure a filename for stdin_command backup is given
when: restic_backup_stdin_command and not restic_backup_stdin_command_filename
fail:
msg: "`restic_backup_stdin_command` was set but no filename for the resulting output was supplied in `restic_backup_stdin_command_filename`"
- name: Ensure backup frequency adheres to systemd's OnCalender syntax
command:
cmd: "systemd-analyze calendar {{ restic_policy.frequency }}"
register: systemd_calender_parse_res
failed_when: systemd_calender_parse_res.rc != 0
changed_when: false
- name: Ensure restic is installed
block:
- name: Ensure restic is installed via apt
apt:
package: restic
state: latest
when: ansible_os_family == 'Debian'
- name: Ensure restic is installed via dnf
dnf:
name: restic
state: latest
when: ansible_os_family == 'RedHat'
- name: Ensure restic is installed using the auto-detected package-manager
package:
name: "{{ restic_package_name }}"
state: present
when: ansible_os_family not in ['RedHat', 'Debian']
- name: Ensure systemd service file for '{{ restic_job_name }}' is templated
template:
dest: "/etc/systemd/system/{{ restic_systemd_unit_naming_scheme }}.service"
src: restic.service.j2
owner: root
group: root
mode: 0640
notify:
- reload-systemd
- trigger-restic
- name: Ensure systemd service file for '{{ restic_job_name }}' is templated
template:
dest: "/etc/systemd/system/{{ restic_systemd_unit_naming_scheme }}.timer"
src: restic.timer.j2
owner: root
group: root
mode: 0640
notify:
- reload-systemd
- name: Flush handlers to ensure systemd knows about '{{ restic_job_name }}'
meta: flush_handlers
- name: Ensure systemd timer for '{{ restic_job_name }}' is activated - name: Ensure systemd timer for '{{ restic_job_name }}' is activated
systemd: ansible.builtin.systemd:
name: "{{ restic_systemd_unit_naming_scheme }}.timer" name: "{{ restic_systemd_unit_naming_scheme }}.timer"
enabled: true enabled: true
- name: Ensure systemd timer for '{{ restic_job_name }}' is started - name: Ensure systemd timer for '{{ restic_job_name }}' is {{ restic_job_state }}
systemd: ansible.builtin.systemd:
name: "{{ restic_systemd_unit_naming_scheme }}.timer" name: "{{ restic_systemd_unit_naming_scheme }}.timer"
state: started state: started # TODO: mask a timer?

View File

@ -0,0 +1,3 @@
{% for kv in restic_complete_environment | dict2items %}
{{ kv.key }}={{ kv.value }}
{% endfor %}

View File

@ -1,34 +0,0 @@
[Unit]
Description={{ restic_job_description }}
[Service]
Type=simple
User={{ restic_user }}
WorkingDirectory={{ restic_systemd_working_directory }}
SyslogIdentifier={{ restic_systemd_syslog_identifier }}
Environment=RESTIC_REPOSITORY={{ restic_repo_url }}
Environment=RESTIC_PASSWORD={{ restic_repo_password }}
{% if restic_s3_key_id and restic_s3_access_key %}
Environment=AWS_ACCESS_KEY_ID={{ restic_s3_key_id }}
Environment=AWS_SECRET_ACCESS_KEY={{ restic_s3_access_key }}
{% endif %}
{% if restic_unlock_before_backup | default(false) %}
ExecStartPre=-/bin/sh -c '/usr/bin/restic unlock'
{% endif %}
ExecStartPre=-/bin/sh -c '/usr/bin/restic snapshots || /usr/bin/restic init'
{% if restic_backup_pre_hook | default(false) %}
ExecStart=-{{ restic_backup_pre_hook }}
{% endif %}
{% if restic_backup_stdin_command %}
ExecStart=/bin/sh -c '{{ restic_backup_stdin_command }} | /usr/bin/restic backup --verbose --stdin --stdin-filename {{ restic_backup_stdin_command_filename }}'
{% else %}
ExecStart=/usr/bin/restic --verbose backup {{ restic_backup_paths | join(' ') }}
{% endif %}
ExecStartPost=/usr/bin/restic forget --prune --keep-within={{ restic_policy.keep_within }} --keep-hourly={{ restic_policy.hourly }} --keep-daily={{ restic_policy.daily }} --keep-weekly={{ restic_policy.weekly }} --keep-monthly={{ restic_policy.monthly }}
ExecStartPost=-/usr/bin/restic snapshots
{% if restic_backup_post_hook | default(false) %}
ExecStartPost=-{{ restic_backup_post_hook }}
{% endif %}
ExecStartPost=/usr/bin/restic check

View File

@ -0,0 +1,15 @@
[Unit]
Description={{ restic_systemd_job_description }}
[Service]
Type=simple
EnvironmentFile={{ restic_job_directory }}/%i.conf
User={{ restic_user }}
WorkingDirectory={{ restic_systemd_working_directory }}
SyslogIdentifier={{ restic_systemd_syslog_identifier }}
ExecStart={{ restic_systemd_service_exec_start }}
[Install]
WantedBy={{ restic_systemd_install_wanted_by }}
DefaultInstance={{ restic_systemd_install_default_instance }}

View File

@ -0,0 +1,9 @@
---
restic_states:
- "present"
- "absent"
restic_job_states:
- "present"
- "masked"
- "absent"