Compare commits
	
		
			58 Commits
		
	
	
		
			0.0.2
			...
			transcaffe
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1024921a74 | |||
| 62263726fa | |||
| ae887a1936 | |||
| 1e57ae1ec0 | |||
| 90dae1311f | |||
| 3733c0b7ab | |||
| 877c5a137a | |||
| 11d4b397ef | |||
| 76e89db5c6 | |||
| 4bddc95161 | |||
| ed95d4fd3d | |||
| 788c4bada0 | |||
| 7ecf8778ca | |||
| 11fec18afc | |||
| b0ba86f78e | |||
| bd418a6199 | |||
| 60b36db8a7 | |||
| 9a02652d98 | |||
| 2c1b3cb47e | |||
| 114cf13871 | |||
| b77c81f754 | |||
| bdf1871855 | |||
| 9454845ea1 | |||
| 4e8cc9bcf2 | |||
| 3b9d6e19da | |||
| c847046720 | |||
| d7b7c59f46 | |||
| 153df81836 | |||
| 7021ed1a89 | |||
| afe72f554e | |||
| c36e95d7eb | |||
| 97526aec36 | |||
| fc73fdd1fa | |||
| bd43f3963c | |||
| 1076a9f384 | |||
| 82e69bdda3 | |||
| b9b5c19d38 | |||
| b9e4abdf36 | |||
| aac6891518 | |||
| 31d025ed75 | |||
| 1423d2a243 | |||
| 967ebab4c1 | |||
| 5f4fbd492c | |||
| 96f5da9bf6 | |||
| 2aaa529585 | |||
| 8941b9357a | |||
| 04b5837fd8 | |||
| 4837172f64 | |||
| ab7cca0947 | |||
| 960d95a924 | |||
| eab7b7e915 | |||
| e7886d8c98 | |||
| 13d40341a0 | |||
| 12b98487a5 | |||
| 2e6cb0a4d5 | |||
| 52d25942b4 | |||
| af17bea1e1 | |||
| 52bf02e084 | 
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -5,15 +5,12 @@ | ||||
| This ansible collection provides various roles for installing | ||||
| 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 | ||||
|  | ||||
| - [`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 | ||||
|   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 | ||||
|   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 | ||||
|  | ||||
| [CNPLv7+](LICENSE.md): Cooperative Nonviolent Public License | ||||
|   | ||||
							
								
								
									
										12
									
								
								galaxy.yml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								galaxy.yml
									
									
									
									
									
								
							| @@ -1,12 +1,20 @@ | ||||
| namespace: finallycoffee | ||||
| name: base | ||||
| version: 0.0.2 | ||||
| version: 0.2.1 | ||||
| readme: README.md | ||||
| authors: | ||||
| - transcaffeine <transcaffeine@finally.coffee> | ||||
| description: Roles for base services which are common dependencies other services like databases | ||||
| dependencies: | ||||
|   "community.docker": "^4.2.0" | ||||
| license_file: LICENSE.md | ||||
| build_ignore: | ||||
| - '*.tar.gz' | ||||
| 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 | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| --- | ||||
|  | ||||
| requires_ansible: ">=2.12" | ||||
| requires_ansible: ">=2.15" | ||||
|   | ||||
							
								
								
									
										7
									
								
								playbooks/user.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								playbooks/user.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| - name: Configure user accounts | ||||
|   hosts: "{{ user_hosts | default('all', true) }}" | ||||
|   become: "{{ user_role_become | default(false, true) }}" | ||||
|   gather_facts: "{{ user_role_gather_facts | default(false, true) }}" | ||||
|   roles: | ||||
|     - role: finallycoffee.base.user | ||||
							
								
								
									
										33
									
								
								roles/dns/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								roles/dns/README.md
									
									
									
									
									
										Normal 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" | ||||
| ``` | ||||
							
								
								
									
										2
									
								
								roles/dns/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								roles/dns/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| --- | ||||
| dns_record_state: present | ||||
							
								
								
									
										11
									
								
								roles/dns/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								roles/dns/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| --- | ||||
|  | ||||
| - name: Ensure DNS records in '{{ dns_zone }}' are up to date | ||||
|   famedly.dns.update: | ||||
|     primary_master: "{{ dns_server }}" | ||||
|     zone: "{{ dns_zone }}" | ||||
|     tsig_name: "{{ dns_tsig_name }}" | ||||
|     tsig_algo: "{{ dns_tsig_algo }}" | ||||
|     tsig_key: "{{ dns_tsig_key }}" | ||||
|     rr_set: "{{ dns_records }}" | ||||
|     state: "{{ dns_record_state }}" | ||||
| @@ -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. | ||||
| @@ -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 | ||||
| @@ -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 | ||||
							
								
								
									
										46
									
								
								roles/lego/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								roles/lego/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| # `finallycoffee.base.lego` ansible role | ||||
|  | ||||
| This role can be used to retrieve ACME certificates on the target host. It uses `lego` for that, and with systemd template units provides an easy way to configure and monitor the status for each certificate. | ||||
|  | ||||
| ## Requirements | ||||
|  | ||||
| - `systemd` | ||||
| - write access to /tmp to unpack the lego release tarball during installation | ||||
| - write access to /opt/lego (or whatever `lego_base_path` is set to) for configuration and certificate data | ||||
| - `become` privileges of the `ansible_user` on the target | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| ### Required configuration | ||||
|  | ||||
| - `lego_instance` - used for allowing multiple lego jobs to run with systemd template units. recommended to be set to the CN / first SAN of the certificate. | ||||
| - `lego_cert_domains` - list of FQDNs to request a certificate for | ||||
| - `lego_acme_account_email` - when using letsencrypt, a contact email is mandatory | ||||
|  | ||||
| ### Proxies / Registries | ||||
|  | ||||
| The role ensure `lego` is downloaded from the github release page. If you are behind a proxy or use a registry like Nexus3, set `lego_release_archive_server`. | ||||
|  | ||||
| ### ACME server | ||||
|  | ||||
| Per default, the Letsencrypt Staging ACME server is configured. Set `lego_acme_server_url` from `lego_letsencrypt_server_urls.{qa,prod}` or configure your own ACME v2 server directly. | ||||
|  | ||||
| ### Certificate | ||||
|  | ||||
| To set for which domains to request a certificate for, set them as a list of SANs in `lego_cert_domains`. The default key type is EC256 and can be overridden using `lego_cert_key_type`. | ||||
|  | ||||
| Set the type of challenge in `lego_acme_challenge_type` (to either `http` or `dns`), and `lego_acme_challenge_provider` to, for example, `rfc2136` for DNS challenges using the DNSUPDATE mechanism. If your challenge needs additional data, set that in `lego_command_config` as a dictionary analog to `lego_base_command_config` (see [defaults](defaults/main.yml)). | ||||
|  | ||||
| ## Trivia | ||||
|  | ||||
| ### Architecture | ||||
|  | ||||
| By default, the lego distribution for `linux` on `amd64` is downloaded. If your target needs a different architecture or target OS, adjust this in `lego_os` and `lego_architecture`, cross-checking with the [lego GitHub release page](https://github.com/go-acme/lego/releases/tag/v4.17.4) for upstream availability. | ||||
|  | ||||
| ### User management | ||||
|  | ||||
| The role will attempt to create user+group for each seperate lego instance for data isolation (i.e. to avoid leaking a TSIG key from one lego instance to other services). The user and group are of the form `acme-{{ lego_instance }}`. Beware that changing this in `lego_cert_{user,group}` also requires `lego_systemd_{user,group}` to be adjusted! | ||||
|  | ||||
| ### Binding to ports < 1024 (HTTP-01 challenge) | ||||
|  | ||||
| Set `lego_binary_allow_net_bind_service: true` to allow the lego binary to bind to ports in the 'privileged' (< 1024) port range. | ||||
							
								
								
									
										71
									
								
								roles/lego/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								roles/lego/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| --- | ||||
| lego_user: "lego" | ||||
| lego_version: "4.23.0" | ||||
| lego_instance: default | ||||
| lego_base_path: "/opt/lego" | ||||
| lego_cert_user: "acme-{{ lego_instance }}" | ||||
| lego_cert_group: "{{ lego_cert_user }}" | ||||
| lego_cert_mode: "0640" # rw-r----- | ||||
| lego_systemd_user: "acme-%i" | ||||
| lego_systemd_group: "{{ lego_systemd_user }}" | ||||
| lego_instance_base_path: "{{ lego_base_path }}/instances" | ||||
| lego_instance_path: "{{ lego_instance_base_path }}/{{ lego_instance }}" | ||||
|  | ||||
| lego_cert_domains: [] | ||||
| lego_cert_key_type: ec256 | ||||
| lego_cert_days_to_renew: 30 | ||||
| lego_acme_account_email: ~ | ||||
| lego_acme_challenge_type: http | ||||
| lego_acme_challenge_provider: ~ | ||||
| lego_letsencrypt_server_urls: | ||||
|   qa: "https://acme-staging-v02.api.letsencrypt.org/directory" | ||||
|   prod: "https://acme-v02.api.letsencrypt.org/directory" | ||||
| lego_acme_server_url: "{{ lego_letsencrypt_server_urls.qa }}" | ||||
|  | ||||
| lego_base_environment: | ||||
|   LEGO_CERT_USER: "{{ lego_cert_user }}" | ||||
|   LEGO_CERT_GROUP: "{{ lego_cert_group }}" | ||||
|   LEGO_CERT_MODE: "{{ lego_cert_mode }}" | ||||
|   LEGO_CERT_STORE_PATH: "{{ lego_instance_path }}" | ||||
|   LEGO_CERT_DAYS_TO_RENEW: "{{ lego_cert_days_to_renew }}" | ||||
|   LEGO_KEY_TYPE: "{{ lego_cert_key_type }}" | ||||
|   LEGO_ACME_CHALLENGE_TYPE: "{{ lego_acme_challenge_type }}" | ||||
|   LEGO_ACME_SERVER: "{{ lego_acme_server_url }}" | ||||
|   LEGO_COMMAND_ARGS: "{{ lego_command_args }}" | ||||
|  | ||||
| lego_base_command_config: | ||||
|   server: "{{ lego_acme_server_url }}" | ||||
|   accept_tos: true | ||||
|   email: "{{ lego_acme_account_email }}" | ||||
|   path: "{{ lego_instance_path }}" | ||||
|   key_type: "{{ lego_cert_key_type }}" | ||||
|  | ||||
| lego_acme_challenge_config: >- | ||||
|   {{ {lego_acme_challenge_type: lego_acme_challenge_provider} }} | ||||
|  | ||||
| lego_systemd_unit_path: "/etc/systemd/system" | ||||
| lego_systemd_template_unit_name: "lego@.service" | ||||
| lego_systemd_template_unit_file: "{{ lego_systemd_template_unit_name }}.j2" | ||||
| lego_systemd_service_name: "lego@{{ lego_instance }}.service" | ||||
| lego_systemd_environment: >- | ||||
|   {{ lego_base_environment | combine(lego_environment | default({})) }} | ||||
| lego_full_command_config: >- | ||||
|   {{ lego_base_command_config | ||||
|   | combine(lego_acme_challenge_config) | ||||
|   | combine(lego_command_config | default({})) }} | ||||
|  | ||||
| lego_systemd_timer_name: "lego-{{ lego_instance }}.timer" | ||||
| lego_systemd_timer_template: lego.timer.j2 | ||||
| lego_systemd_timer_calendar: "*-*-* *:00/15:00" | ||||
|  | ||||
| lego_architecture: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}" | ||||
| lego_os: "linux" | ||||
| lego_binary_allow_net_bind_service: false | ||||
|  | ||||
| lego_release_archive_server: "https://github.com" | ||||
| lego_release_archive_filename: >- | ||||
|   lego_v{{ lego_version }}_{{ lego_os }}_{{ lego_architecture }}.tar.gz | ||||
| lego_release_archive_url: >- | ||||
|   {{ lego_release_archive_server }}/go-acme/lego/releases/download/v{{ lego_version }}/{{ lego_release_archive_filename }} | ||||
| lego_release_archive_file_path: "/tmp/{{ lego_release_archive_filename }}" | ||||
| lego_release_archive_path: "/tmp/lego_v{{ lego_version }}_{{ lego_os }}_{{ lego_architecture }}" | ||||
							
								
								
									
										35
									
								
								roles/lego/files/lego_run.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								roles/lego/files/lego_run.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| #!/usr/bin/env bash | ||||
| set -euo pipefail | ||||
|  | ||||
| LEGO_BINARY=$(/usr/bin/env which lego) | ||||
|  | ||||
| if [[ -n "${LEGO_HTTP_FALLBACK_PORT:-}" ]]; then | ||||
|   if ! nc_binary="$(type -p 'nc')" || [[ -z $nc_binary ]]; then | ||||
|     echo "nc not found (in PATH), exiting" | ||||
|     exit 1 | ||||
|   fi | ||||
|   nc -z 127.0.0.1 $LEGO_HTTP_PORT; | ||||
|   if [[ $? -eq 0 ]]; then | ||||
|       LEGO_HTTP_PORT=$LEGO_HTTP_FALLBACK_PORT | ||||
|   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 | ||||
|  | ||||
| FILES_IN_DIR=$(find "$LEGO_CERT_STORE_PATH/certificates" -type f | wc -l) | ||||
| if [[ $FILES_IN_DIR -gt 2 ]]; then | ||||
|         $LEGO_BINARY $LEGO_COMMAND_ARGS_EXPANDED renew --days=$LEGO_CERT_DAYS_TO_RENEW | ||||
| else | ||||
|         $LEGO_BINARY $LEGO_COMMAND_ARGS_EXPANDED run | ||||
| 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 chown "${LEGO_CERT_USER}:${LEGO_CERT_GROUP}" "{}" | ||||
|  | ||||
| if [[ -n "${LEGO_POST_RENEWAL_HOOK:-}" ]]; then | ||||
|   $LEGO_POST_RENEWAL_HOOK | ||||
| fi | ||||
							
								
								
									
										5
									
								
								roles/lego/handlers/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								roles/lego/handlers/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| --- | ||||
| - name: Ensure systemd daemon is reloaded | ||||
|   ansible.builtin.systemd: | ||||
|     daemon_reload: true | ||||
|   listen: systemd_reload | ||||
							
								
								
									
										165
									
								
								roles/lego/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								roles/lego/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| --- | ||||
| - name: Ensure lego cert group is created | ||||
|   ansible.builtin.group: | ||||
|     name: "{{ lego_cert_group }}" | ||||
|     state: present | ||||
|     system: true | ||||
|  | ||||
| - name: Ensure lego cert user is created | ||||
|   ansible.builtin.user: | ||||
|     name: "{{ lego_cert_user }}" | ||||
|     state: present | ||||
|     system: true | ||||
|     create_home: false | ||||
|     groups: | ||||
|       - "{{ lego_cert_group }}" | ||||
|     append: true | ||||
|  | ||||
| - name: Ensure lego user is created | ||||
|   ansible.builtin.user: | ||||
|     name: "{{ lego_user }}" | ||||
|     state: present | ||||
|     system: true | ||||
|     create_home: false | ||||
|     groups: | ||||
|       - "{{ lego_cert_group }}" | ||||
|     append: true | ||||
|  | ||||
| - name: Check if lego is present | ||||
|   ansible.builtin.command: | ||||
|     cmd: which lego | ||||
|   changed_when: false | ||||
|   failed_when: false | ||||
|   register: lego_binary_info | ||||
|   check_mode: false | ||||
|  | ||||
| - name: Check which version of lego is present | ||||
|   ansible.builtin.command: | ||||
|     cmd: "lego --version" | ||||
|   changed_when: false | ||||
|   failed_when: false | ||||
|   register: lego_binary_version_info | ||||
|   when: lego_binary_info.rc == 0 | ||||
|   check_mode: false | ||||
|  | ||||
| - name: Ensure lego is installed | ||||
|   when: (lego_binary_info.rc != 0) or (lego_version not in lego_binary_version_info.stdout) | ||||
|   block: | ||||
|     - name: Download lego from source | ||||
|       ansible.builtin.get_url: | ||||
|         url: "{{ lego_release_archive_url }}" | ||||
|         url_username: "{{ lego_release_archive_url_username | default(omit) }}" | ||||
|         url_password: "{{ lego_release_archive_url_password | default(omit) }}" | ||||
|         dest: "{{ lego_release_archive_file_path }}" | ||||
|  | ||||
|     - name: Create folder to uncompress into | ||||
|       ansible.builtin.file: | ||||
|         dest: "{{ lego_release_archive_path }}" | ||||
|         state: directory | ||||
|  | ||||
|     - name: Uncompress lego source archive | ||||
|       ansible.builtin.unarchive: | ||||
|         src: "{{ lego_release_archive_file_path }}" | ||||
|         dest: "{{ lego_release_archive_path }}" | ||||
|         remote_src: true | ||||
|       ignore_errors: "{{ ansible_check_mode }}" | ||||
|  | ||||
|     - name: Ensure lego binary is present in PATH | ||||
|       ansible.builtin.copy: | ||||
|         src: "{{ lego_release_archive_path }}/lego" | ||||
|         dest: "/usr/local/bin/lego" | ||||
|         mode: "u+rwx,g+rx,o+rx" | ||||
|         remote_src: true | ||||
|       ignore_errors: "{{ ansible_check_mode }}" | ||||
|  | ||||
|     - name: Ensure intermediate data is gone | ||||
|       ansible.builtin.file: | ||||
|         path: "{{ item }}" | ||||
|         state: absent | ||||
|       loop: | ||||
|         - "{{ lego_release_archive_path }}" | ||||
|         - "{{ lego_release_archive_file_path }}" | ||||
|  | ||||
|     - name: Ensure lego is allowed to bind to ports < 1024 | ||||
|       community.general.capabilities: | ||||
|         path: "/usr/local/bin/lego" | ||||
|         capability: "cap_net_bind_service+ep" | ||||
|         state: present | ||||
|       when: lego_binary_allow_net_bind_service | ||||
|  | ||||
| - name: Ensure lego base path exists | ||||
|   ansible.builtin.file: | ||||
|     path: "{{ lego_base_path }}" | ||||
|     state: directory | ||||
|     mode: "0755" | ||||
|  | ||||
| - name: Ensure template unit file is present | ||||
|   ansible.builtin.template: | ||||
|     src: "{{ lego_systemd_template_unit_file }}" | ||||
|     dest: "{{ lego_systemd_unit_path }}/{{ lego_systemd_template_unit_name }}" | ||||
|   notify: | ||||
|     - systemd_reload | ||||
|  | ||||
| - name: Ensure env file is templated | ||||
|   ansible.builtin.copy: | ||||
|     content: |+ | ||||
|       {% for entry in lego_systemd_environment | dict2items %} | ||||
|       {{ entry.key }}={{ entry.value }} | ||||
|       {% endfor %} | ||||
|     dest: "{{ lego_base_path }}/{{ lego_instance }}.conf" | ||||
|  | ||||
| - name: Ensure timer unit is templated | ||||
|   ansible.builtin.template: | ||||
|     src: "{{ lego_systemd_timer_template }}" | ||||
|     dest: "{{ lego_systemd_unit_path }}/{{ lego_systemd_timer_name }}" | ||||
|   notify: | ||||
|     - systemd_reload | ||||
|  | ||||
| - name: Ensure handling script is templated | ||||
|   ansible.builtin.copy: | ||||
|     src: "lego_run.sh" | ||||
|     dest: "{{ lego_base_path }}/run.sh" | ||||
|     mode: "0755" | ||||
|  | ||||
| - name: Ensure per-instance base path is created | ||||
|   ansible.builtin.file: | ||||
|     path: "{{ lego_instance_path }}" | ||||
|     state: directory | ||||
|     owner: "{{ lego_cert_user }}" | ||||
|     group: "{{ lego_cert_group }}" | ||||
|     mode: "0755" | ||||
|  | ||||
| - name: Ensure per-instance sub folders are created with correct permissions | ||||
|   ansible.builtin.file: | ||||
|     path: "{{ item.path }}" | ||||
|     state: directory | ||||
|     owner: "{{ item.owner | default(lego_cert_user) }}" | ||||
|     group: "{{ item.group | default(lego_cert_group) }}" | ||||
|     mode: "{{ item.mode }}" | ||||
|   loop: | ||||
|     - path: "{{ lego_instance_path }}/secrets" | ||||
|       mode: "0750" | ||||
|     - path: "{{ lego_instance_path }}/accounts" | ||||
|       mode: "0770" | ||||
|     - path: "{{ lego_instance_path }}/certificates" | ||||
|       mode: "0775" | ||||
|   loop_control: | ||||
|     label: "{{ item.path }}" | ||||
|  | ||||
| - name: Ensure systemd daemon is reloaded | ||||
|   meta: flush_handlers | ||||
|  | ||||
| - name: Ensure systemd timer is enabled | ||||
|   ansible.builtin.systemd_service: | ||||
|     name: "{{ lego_systemd_timer_name }}" | ||||
|     enabled: true | ||||
|  | ||||
| - name: Ensure systemd timer is started | ||||
|   ansible.builtin.systemd_service: | ||||
|     name: "{{ lego_systemd_timer_name }}" | ||||
|     state: "started" | ||||
|  | ||||
| - name: Ensure systemd service is started once to obtain the certificate | ||||
|   ansible.builtin.systemd_service: | ||||
|     name: "{{ lego_systemd_service_name }}" | ||||
|     state: "started" | ||||
							
								
								
									
										9
									
								
								roles/lego/templates/lego.timer.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								roles/lego/templates/lego.timer.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| [Unit] | ||||
| Description=Run lego@{{ lego_instance}}.service | ||||
|  | ||||
| [Timer] | ||||
| OnCalendar={{ lego_systemd_timer_calendar }} | ||||
| Unit=lego@{{ lego_instance }}.service | ||||
|  | ||||
| [Install] | ||||
| WantedBy=timers.target | ||||
							
								
								
									
										14
									
								
								roles/lego/templates/lego@.service.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								roles/lego/templates/lego@.service.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| [Unit] | ||||
| Description=Run lego (letsencrypt client in go) | ||||
|  | ||||
| [Service] | ||||
| Type=oneshot | ||||
| EnvironmentFile={{ lego_base_path }}/%i.conf | ||||
| User={{ lego_systemd_user }} | ||||
| Group={{ lego_systemd_group }} | ||||
| ExecStart={{ lego_base_path }}/run.sh | ||||
| AmbientCapabilities=CAP_NET_BIND_SERVICE | ||||
|  | ||||
| [Install] | ||||
| WantedBy=basic.target | ||||
| DefaultInstance=default | ||||
							
								
								
									
										16
									
								
								roles/lego/vars/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								roles/lego/vars/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| --- | ||||
|  | ||||
| lego_domain_command_args: >- | ||||
|   {% for domain in lego_cert_domains %} | ||||
|    --domains={{ domain }} | ||||
|   {%- endfor %} | ||||
|  | ||||
| lego_config_command_args: >- | ||||
|   {% for key in lego_full_command_config %} | ||||
|    --{{ key | replace("_", "-") }} | ||||
|   {%- if lego_full_command_config[key] != None and lego_full_command_config[key] != '' -%} | ||||
|   ={{ lego_full_command_config[key] }} | ||||
|   {%- endif -%} | ||||
|   {%- endfor -%} | ||||
|  | ||||
| lego_command_args: "{{ lego_domain_command_args }} {{ lego_config_command_args }}" | ||||
| @@ -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. | ||||
| @@ -1,32 +0,0 @@ | ||||
| --- | ||||
|  | ||||
| mariadb_version: "10.6.11" | ||||
| 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) }} | ||||
| @@ -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 | ||||
| @@ -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) }}" | ||||
| @@ -1,17 +1,7 @@ | ||||
| --- | ||||
| 
 | ||||
| minio_user: ~ | ||||
| minio_data_path: /opt/minio | ||||
| 
 | ||||
| minio_create_user: false | ||||
| minio_manage_host_filesystem: false | ||||
| 
 | ||||
| minio_root_username: root | ||||
| minio_root_password: ~ | ||||
| 
 | ||||
| minio_container_name: minio | ||||
| minio_container_image_name: docker.io/minio/minio | ||||
| minio_container_image_tag: latest | ||||
| minio_container_image_name: "docker.io/minio/minio" | ||||
| minio_container_image_tag: "RELEASE.2025-04-08T15-41-24Z" | ||||
| minio_container_image: "{{ minio_container_image_name }}:{{ minio_container_image_tag }}" | ||||
| minio_container_networks: [] | ||||
| minio_container_ports: [] | ||||
| @@ -30,9 +20,12 @@ minio_container_labels: {} | ||||
| minio_container_command: | ||||
|   - "server" | ||||
|   - "{{ minio_container_data_path }}" | ||||
|   - "--console-address \":{{ minio_container_listen_port_console }}\"" | ||||
|   - "--console-address" | ||||
|   - ":{{ minio_container_listen_port_console }}" | ||||
| minio_container_restart_policy: "unless-stopped" | ||||
| minio_container_image_force_source: "{{ (minio_container_image_tag == 'latest')|bool }}" | ||||
| minio_container_state: >-2 | ||||
|   {{ (minio_state == 'present') | ternary('started', 'absent') }} | ||||
| 
 | ||||
| minio_container_listen_port_api: 9000 | ||||
| minio_container_listen_port_console: 8900 | ||||
							
								
								
									
										12
									
								
								roles/minio/defaults/main/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								roles/minio/defaults/main/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| --- | ||||
| minio_user: ~ | ||||
| minio_data_path: /opt/minio | ||||
|  | ||||
| minio_create_user: false | ||||
| minio_manage_host_filesystem: false | ||||
|  | ||||
| minio_root_username: root | ||||
| minio_root_password: ~ | ||||
|  | ||||
| minio_state: present | ||||
| minio_deployment_method: docker | ||||
							
								
								
									
										29
									
								
								roles/minio/tasks/deploy-docker.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								roles/minio/tasks/deploy-docker.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| --- | ||||
| - name: Ensure filesystem mounts ({{ minio_data_path }}) for container volumes are present | ||||
|   ansible.builtin.file: | ||||
|     path: "{{ minio_data_path }}" | ||||
|     state: directory | ||||
|     user: "{{ minio_user|default(omit, True) }}" | ||||
|     group: "{{ minio_user|default(omit, True) }}" | ||||
|   when: minio_manage_host_filesystem | ||||
|  | ||||
| - name: Ensure container image '{{ minio_container_image }}' is {{ minio_state }} | ||||
|   community.docker.docker_image: | ||||
|     name: "{{ minio_container_image }}" | ||||
|     state: "{{ minio_state }}" | ||||
|     source: pull | ||||
|     force_source: "{{ minio_container_image_force_source }}" | ||||
|  | ||||
| - name: Ensure container '{{ minio_container_name }}' is {{ minio_container_state }} | ||||
|   community.docker.docker_container: | ||||
|     name: "{{ minio_container_name }}" | ||||
|     image: "{{ minio_container_image }}" | ||||
|     volumes: "{{ minio_container_volumes }}" | ||||
|     env: "{{ minio_container_env }}" | ||||
|     labels: "{{ minio_container_labels }}" | ||||
|     networks: "{{ minio_container_networks }}" | ||||
|     ports: "{{ minio_container_ports }}" | ||||
|     user: "{{ minio_user|default(omit, True) }}" | ||||
|     command: "{{ minio_container_command }}" | ||||
|     restart_policy: "{{ minio_container_restart_policy }}" | ||||
|     state: "{{ minio_container_state }}" | ||||
| @@ -1,37 +1,25 @@ | ||||
| --- | ||||
| - name: Ensure 'minio_state' is valid | ||||
|   ansible.builtin.fail: | ||||
|     msg: >- | ||||
|       Unsupported state '{{ minio_state }}'! | ||||
|       Supported states are {{ minio_states | join(', ') }}. | ||||
|   when: minio_state not in minio_states | ||||
|  | ||||
| - name: Ensure minio run user is present | ||||
|   user: | ||||
| - name: Ensure 'minio_deployment_method' is valid | ||||
|   ansible.builtin.fail: | ||||
|     msg: >- | ||||
|       Unsupported state '{{ minio_deployment_method }}'! | ||||
|       Supported states are {{ minio_deployment_methods | join(', ') }}. | ||||
|   when: minio_deployment_method not in minio_deployment_methods | ||||
|  | ||||
| - name: Ensure minio run user is {{ minio_state }} | ||||
|   ansible.builtin.user: | ||||
|     name: "{{ minio_user }}" | ||||
|     state: present | ||||
|     system: yes | ||||
|     state: "{{ minio_state }}" | ||||
|     system: true | ||||
|   when: minio_create_user | ||||
|  | ||||
| - name: Ensure filesystem mounts ({{ minio_data_path }}) for container volumes are present | ||||
|   file: | ||||
|     path: "{{ minio_data_path }}" | ||||
|     state: directory | ||||
|     user: "{{ minio_user|default(omit, True) }}" | ||||
|     group: "{{ minio_user|default(omit, True) }}" | ||||
|   when: minio_manage_host_filesystem | ||||
|  | ||||
| - name: Ensure container image for minio is present | ||||
|   community.docker.docker_image: | ||||
|     name: "{{ minio_container_image }}" | ||||
|     state: present | ||||
|     source: pull | ||||
|     force_source: "{{ minio_container_image_force_source }}" | ||||
|  | ||||
| - name: Ensure container {{ minio_container_name }} is running | ||||
|   docker_container: | ||||
|     name: "{{ minio_container_name }}" | ||||
|     image: "{{ minio_container_image }}" | ||||
|     volumes: "{{ minio_container_volumes }}" | ||||
|     env: "{{ minio_container_env }}" | ||||
|     labels: "{{ minio_container_labels }}" | ||||
|     networks: "{{ minio_container_networks }}" | ||||
|     ports: "{{ minio_container_ports }}" | ||||
|     user: "{{ minio_user|default(omit, True) }}" | ||||
|     command: "{{ minio_container_command }}" | ||||
|     restart_policy: "{{ minio_container_restart_policy }}" | ||||
|     state: started | ||||
| - name: Deploy minio using {{ minio_deployment_method }} | ||||
|   ansible.builtin.include_tasks: | ||||
|     file: "deploy-{{ minio_deployment_method }}.yml" | ||||
|   | ||||
| @@ -1,5 +1,9 @@ | ||||
| --- | ||||
|  | ||||
| minio_states: | ||||
|   - present | ||||
|   - absent | ||||
| minio_deployment_methods: | ||||
|   - docker | ||||
| minio_container_volumes: "{{ minio_container_base_volumes + minio_container_extra_volumes }}" | ||||
|  | ||||
| minio_container_env: "{{ minio_container_base_env | combine(minio_container_extra_env) }}" | ||||
|   | ||||
| @@ -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` | ||||
| (for label-based routing discovery like traefik) can be used. The options correspond to the arguments | ||||
| 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`. | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| --- | ||||
|  | ||||
| nginx_version: "1.25.1" | ||||
| nginx_version: "1.27.5" | ||||
| nginx_flavour: alpine | ||||
| nginx_base_path: /opt/nginx | ||||
| nginx_config_file: "{{ nginx_base_path }}/nginx.conf" | ||||
| nginx_state: present | ||||
| nginx_deployment_method: docker | ||||
|  | ||||
| nginx_container_name: nginx | ||||
| nginx_container_image_reference: >- | ||||
| @@ -26,6 +27,9 @@ nginx_container_image_repository: >- | ||||
| nginx_container_image_registry: "docker.io" | ||||
| nginx_container_image_name: "nginx" | ||||
| 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_volumes: | ||||
|   | ||||
							
								
								
									
										12
									
								
								roles/nginx/meta/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								roles/nginx/meta/main.yml
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										28
									
								
								roles/nginx/tasks/deploy-docker.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								roles/nginx/tasks/deploy-docker.yml
									
									
									
									
									
										Normal 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 }}" | ||||
							
								
								
									
										27
									
								
								roles/nginx/tasks/deploy-podman.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								roles/nginx/tasks/deploy-podman.yml
									
									
									
									
									
										Normal 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 }}" | ||||
| @@ -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: | ||||
|     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 | ||||
|   ansible.builtin.copy: | ||||
| @@ -13,25 +33,8 @@ | ||||
|     mode: 0640 | ||||
|   notify: | ||||
|     - restart-nginx | ||||
|   when: nginx_state == 'present' | ||||
|  | ||||
| - name: Ensure docker container image is present | ||||
|   community.docker.docker_image: | ||||
|     name: "{{ nginx_container_image_reference }}" | ||||
|     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 | ||||
| - name: Deploy using {{ nginx_deployment_method }} | ||||
|   ansible.builtin.include_tasks: | ||||
|     file: "deploy-{{ nginx_deployment_method }}.yml" | ||||
|   | ||||
							
								
								
									
										7
									
								
								roles/nginx/vars/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								roles/nginx/vars/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| nginx_states: | ||||
|   - present | ||||
|   - absent | ||||
| nginx_deployment_methods: | ||||
|   - docker | ||||
|   - podman | ||||
							
								
								
									
										25
									
								
								roles/powerdns_tsig_key/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								roles/powerdns_tsig_key/README.md
									
									
									
									
									
										Normal 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. | ||||
							
								
								
									
										2
									
								
								roles/powerdns_tsig_key/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								roles/powerdns_tsig_key/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| --- | ||||
| powerdns_tsig_key_container_name: powerdns | ||||
							
								
								
									
										104
									
								
								roles/powerdns_tsig_key/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								roles/powerdns_tsig_key/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| --- | ||||
| - name: Ensure unix group '{{ powerdns_tsig_key_path_group }}' exists | ||||
|   ansible.builtin.group: | ||||
|     name: "{{ powerdns_tsig_key_path_group }}" | ||||
|     state: "present" | ||||
|     system: true | ||||
|   register: powerdns_tsig_key_path_group_info | ||||
|   when: powerdns_tsig_key_path_group is defined | ||||
|  | ||||
| - name: Ensure unix user '{{ powerdns_tsig_key_path_owner }}' exists | ||||
|   ansible.builtin.user: | ||||
|     name: "{{ powerdns_tsig_key_path_owner }}" | ||||
|     state: "present" | ||||
|     system: true | ||||
|     create_home: false | ||||
|     groups: "{{ powerdns_tsig_key_path_group is defined | ternary([powerdns_tsig_key_path_group], omit) }}" | ||||
|     append: "{{ powerdns_tsig_key_path_group is defined | ternary(true, omit) }}" | ||||
|   register: powerdns_tsig_key_path_owner_info | ||||
|   when: powerdns_tsig_key_path_owner is defined | ||||
|  | ||||
| - name: Check if TSIG key is already present | ||||
|   ansible.builtin.stat: | ||||
|     path: "{{ powerdns_tsig_key_path }}" | ||||
|   register: powerdns_tsig_key_info | ||||
|  | ||||
| - name: Ensure TSIG key directory is present | ||||
|   ansible.builtin.file: | ||||
|     path: "{{ powerdns_tsig_key_path | dirname }}" | ||||
|     state: directory | ||||
|     owner: "{{ powerdns_tsig_key_path_owner | default(omit) }}" | ||||
|     group: "{{ powerdns_tsig_key_path_group | default(omit) }}" | ||||
|     mode: "u+rwX,g+rX" | ||||
|     recurse: true | ||||
|  | ||||
| - name: Ensure a TSIG key is configured and persisted | ||||
|   when: >- | ||||
|     not powerdns_tsig_key_info.stat.exists | ||||
|     or powerdns_tsig_key_info.stat.size == 0 | ||||
|   block: | ||||
|     - name: Ensure TSIG key is not already present | ||||
|       community.docker.docker_container_exec: | ||||
|         container: "{{ powerdns_tsig_key_container_name }}" | ||||
|         command: "pdnsutil list-tsig-keys" | ||||
|       delegate_to: "{{ powerdns_tsig_key_hostname }}" | ||||
|       register: powerdns_tsig_key_powerdns_info | ||||
|       changed_when: false | ||||
|       check_mode: false | ||||
|       become: true | ||||
|  | ||||
|     - name: Ensure TSIG key is generated in powerdns | ||||
|       community.docker.docker_container_exec: | ||||
|         container: "{{ powerdns_tsig_key_container_name }}" | ||||
|         command: "pdnsutil generate-tsig-key '{{ powerdns_tsig_key_name }}' '{{ powerdns_tsig_key_algo }}'" | ||||
|       when: >- | ||||
|         (powerdns_tsig_key_name ~ '. ' ~ powerdns_tsig_key_algo ~ '. ') | ||||
|         not in powerdns_tsig_key_powerdns_info.stdout | ||||
|       delegate_to: "{{ powerdns_tsig_key_hostname }}" | ||||
|       register: powerdns_tsig_key_powerdns_generated_tsig_key | ||||
|       throttle: 1 | ||||
|       become: true | ||||
|  | ||||
|     - name: Ensure PowerDNS is restarted | ||||
|       community.docker.docker_container: | ||||
|         name: "{{ powerdns_tsig_key_container_name }}" | ||||
|         state: started | ||||
|         restart: true | ||||
|       when: >- | ||||
|         (powerdns_tsig_key_name ~ '. ' ~ powerdns_tsig_key_algo ~ '. ') | ||||
|         not in powerdns_tsig_key_powerdns_info.stdout | ||||
|       delegate_to: "{{ powerdns_tsig_key_hostname }}" | ||||
|       throttle: 1 | ||||
|       become: true | ||||
|  | ||||
|     - name: Extract TSIG key into variable | ||||
|       ansible.builtin.set_fact: | ||||
|         powerdns_tsig_key_key: >- | ||||
|           {{ | ||||
|             (powerdns_tsig_key_powerdns_generated_tsig_key.stdout | trim | split(' ') | list | last) | ||||
|             if (powerdns_tsig_key_name ~ '. ' ~ powerdns_tsig_key_algo ~ '. ') | ||||
|             not in powerdns_tsig_key_powerdns_info.stdout | ||||
|             else (powerdns_generated_tsig_key | trim | split(' ') | list | last) | ||||
|           }} | ||||
|       vars: | ||||
|         powerdns_generated_tsig_key: >- | ||||
|           {% for line in powerdns_tsig_key_powerdns_info.stdout_lines %} | ||||
|           {% if powerdns_tsig_key_name in line %} | ||||
|           {{ line }} | ||||
|           {% endif %} | ||||
|           {% endfor %} | ||||
|  | ||||
|     - name: Ensure TSIG key is persisted into {{ powerdns_tsig_key_path }} | ||||
|       ansible.builtin.copy: | ||||
|         content: "{{ powerdns_tsig_key_key }}" | ||||
|         dest: "{{ powerdns_tsig_key_path }}" | ||||
|         owner: "{{ powerdns_tsig_key_path_owner | default(omit) }}" | ||||
|         group: "{{ powerdns_tsig_key_path_group | default(omit) }}" | ||||
|         mode: "0640" | ||||
|  | ||||
| - name: Ensure TSIG key permissions on {{ powerdns_tsig_key_path }} are correct | ||||
|   ansible.builtin.file: | ||||
|     path: "{{ powerdns_tsig_key_path }}" | ||||
|     owner: "{{ powerdns_tsig_key_path_owner | default(omit) }}" | ||||
|     group: "{{ powerdns_tsig_key_path_group | default(omit) }}" | ||||
|     mode: "u+rwX,g+rwX" | ||||
| @@ -10,18 +10,41 @@ 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_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_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_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 | ||||
|       if (restic_s3_key_id and restic_s3_access_key) else {}) | default({})) | ||||
|     | 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 }}" | ||||
|  | ||||
| restic_user: root | ||||
|   | ||||
							
								
								
									
										37
									
								
								roles/restic/files/restic-backup-directories.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								roles/restic/files/restic-backup-directories.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #!/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 attempt to initialize/unlock repository" | ||||
| restic snapshots || restic init || restic unlock | ||||
| sleep 1; | ||||
|  | ||||
| echo "Attempting to remove lock if present" | ||||
| restic unlock | ||||
| sleep 2 | ||||
|  | ||||
| echo "Start backup on ${@:1}" | ||||
| restic --verbose --retry-lock=${RESTIC_RETRY_LOCK:-5m} backup "${@:1}" | ||||
| sleep 2 | ||||
|  | ||||
| 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" | ||||
| restic --json snapshots | /opt/restic-generate-snapshot-metrics.sh \ | ||||
|     > /var/lib/node_exporter/restic-snapshots-${RESTIC_JOBNAME:-unknown}.prom-src | ||||
| sleep 2 | ||||
|  | ||||
| echo "Check repository" | ||||
| restic check | ||||
							
								
								
									
										12
									
								
								roles/restic/files/restic-generate-snapshot-metrics.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								roles/restic/files/restic-generate-snapshot-metrics.sh
									
									
									
									
									
										Normal 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)"' | ||||
| @@ -8,7 +8,7 @@ | ||||
|   when: restic_create_user | ||||
|  | ||||
| - name: Ensure either backup_paths or backup_stdin_command is populated | ||||
|   when: restic_backup_paths|length > 0 and restic_backup_stdin_command | ||||
|   when: restic_backup_paths|length > 0 and restic_backup_stdin_command and false | ||||
|   fail: | ||||
|     msg: "Setting both `restic_backup_paths` and `restic_backup_stdin_command` is not supported" | ||||
|  | ||||
|   | ||||
| @@ -2,27 +2,50 @@ | ||||
| Description={{ restic_job_description }} | ||||
|  | ||||
| [Service] | ||||
| Type=oneshot | ||||
| 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 %} | ||||
| {% for kv in restic_complete_environment | dict2items %} | ||||
| Environment={{ kv.key }}={{ kv.value }} | ||||
| {% endfor %} | ||||
|  | ||||
| {% if restic_init | default(true) %} | ||||
| ExecStartPre=-/bin/sh -c '/usr/bin/restic snapshots || /usr/bin/restic init' | ||||
| {% 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 | ||||
| ExecStartPost=/usr/bin/restic check | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
| {% if restic_unlock_before_backup | default(false) %} | ||||
| ExecStartPre=-/bin/sh -c 'sleep 3 && /usr/bin/restic unlock' | ||||
| {% endif %} | ||||
| {% if restic_backup_pre_hook | default(false) %} | ||||
| ExecStartPre=-{{ restic_backup_pre_hook }} | ||||
| {% endif %} | ||||
| {% if restic_backup_stdin_command %} | ||||
| ExecStart=/bin/sh -c '{{ restic_backup_stdin_command }} | /usr/bin/restic backup \ | ||||
| 	--retry-lock {{ restic_retry_lock | default('5m') }} \ | ||||
| 	--verbose --stdin \ | ||||
| 	--stdin-filename {{ restic_backup_stdin_command_filename }}' | ||||
| {% else %} | ||||
| ExecStart=/opt/restic-backup-directories.sh {{ restic_backup_paths | join(' ') }} | ||||
| {% endif %} | ||||
| {% if restic_forget_prune | default(true) %} | ||||
| ExecStartPost=/usr/bin/restic forget --prune \ | ||||
| 	--retry-lock {{ restic_retry_lock | default('5m') }} \ | ||||
| 	--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 }} \ | ||||
| 	--keep-yearly={{ restic_policy.yearly }} | ||||
| {% endif %} | ||||
| {% if restic_list_snapshots | default(true) %} | ||||
| ExecStartPost=-/usr/bin/restic snapshots --retry-lock {{ restic_retry_lock | default('5m') }} | ||||
| {% endif %} | ||||
| {% if restic_backup_post_hook | default(false) %} | ||||
| ExecStartPost=-{{ restic_backup_post_hook }} | ||||
| {% endif %} | ||||
| {% if restic_check | default(true) %} | ||||
| ExecStartPost=/usr/bin/restic check --retry-lock {{ restic_retry_lock | default('5m') }} | ||||
| {% endif %} | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| [Unit] | ||||
| Description=Run {{ restic_job_name }} | ||||
| Description=Run {{ restic_timer_description | default(restic_job_name) }} | ||||
|  | ||||
| [Timer] | ||||
| OnCalendar={{ restic_policy.frequency }} | ||||
| Persistent=True | ||||
| Unit={{ restic_systemd_unit_naming_scheme }}.service | ||||
|  | ||||
| [Install] | ||||
|   | ||||
							
								
								
									
										23
									
								
								roles/user/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								roles/user/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| # `finallycoffee.base.user` ansible role | ||||
|  | ||||
| Provision and manage user accounts on the remote host. Supports setting user | ||||
| home, gecos (display name) and shell. | ||||
|  | ||||
| Warning: if the users' home exists and is changed, the role will attempt to | ||||
| move the home directory. Set `move_home` to false on the user to disable this | ||||
| behaviour. | ||||
|  | ||||
| ## Examples | ||||
| ```yaml | ||||
| - hosts: all | ||||
|   roles: | ||||
|     - role: finallycoffee.base.user | ||||
|   vars: | ||||
|     users: | ||||
|       - name: root | ||||
|       - name: alice | ||||
|       - name: bob | ||||
|         state: present | ||||
|       - name: eve | ||||
|         state: absent | ||||
| ``` | ||||
							
								
								
									
										2
									
								
								roles/user/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								roles/user/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| --- | ||||
| users: [] | ||||
							
								
								
									
										41
									
								
								roles/user/tasks/configure-user.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								roles/user/tasks/configure-user.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| --- | ||||
| - name: Ensure user '{{ user.name }}' is {{ user_state }} | ||||
|   ansible.builtin.user: | ||||
|     name: "{{ user.name }}" | ||||
|     state: "{{ user_state }}" | ||||
|     system: "{{ user.system | default(false, true) }}" | ||||
|     shell: "{{ user.shell | default(omit, true) }}" | ||||
|     home: "{{ user.home | default(omit, true) }}" | ||||
|     create_home: "{{ user.create_home | default(true, true) }}" | ||||
|     move_home: "{{ user.move_home | default(true, true) }}" | ||||
|     skeleton: >-2 | ||||
|       {{ (user.create_home | default(true, true) and 'skeleton' in user) | ||||
|       | ternary(user.skeleton | default(''), omit) }} | ||||
|     comment: "{{ user.comment | default(user.gecos | default(omit, true), true) }}" | ||||
|   vars: | ||||
|     user_state: "{{ user.state | default('present', false) }}" | ||||
|  | ||||
| - name: Ensure SSH authorized keys for '{{ user.name }}' are {{ user_state }} | ||||
|   vars: | ||||
|     user_state: "{{ user.state | default('present', false) }}" | ||||
|   when: | ||||
|     - user_state == 'present' | ||||
|     - user.authorized_keys | default([]) | length > 0 | ||||
|   block: | ||||
|     - name: Ensure .ssh directory for user '{{ user.name }}' exists | ||||
|       ansible.builtin.file: | ||||
|         path: "{{ user.home | default('/home/' + user.name) + '/.ssh' }}" | ||||
|         state: "directory" | ||||
|         owner: "{{ user.name }}" | ||||
|         group: "{{ user.name }}" | ||||
|         mode: "0700" | ||||
|     - name: Ensure key is up to date | ||||
|       ansible.posix.authorized_key: | ||||
|         user: "{{ user.name }}" | ||||
|         state: "{{ key.state | default('present', true) }}" | ||||
|         key: "{{ key.type }} {{ key.key }}" | ||||
|         comment: "{{ user.name }}-{{ key.comment }}" | ||||
|       loop: "{{ user.authorized_keys }}" | ||||
|       loop_control: | ||||
|         loop_var: key | ||||
|         label: "{{ user.name }}-{{ key.comment }}" | ||||
							
								
								
									
										8
									
								
								roles/user/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								roles/user/tasks/main.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| --- | ||||
| - name: Ensure users are configured | ||||
|   ansible.builtin.include_tasks: | ||||
|     file: "configure-user.yml" | ||||
|   loop: "{{ users }}" | ||||
|   loop_control: | ||||
|     loop_var: user | ||||
|     label: "{{ user.name }}" | ||||
		Reference in New Issue
	
	Block a user