Compare commits

..

55 Commits

Author SHA1 Message Date
uumas
d8bd645a80 Support ssh_pubkeys as list 2025-10-12 19:26:12 +03:00
uumas
6d2d305fd0 caddy: Use firewalld 2025-09-14 03:02:59 +03:00
uumas
90ade1e766 Add readmes 2025-09-14 03:02:48 +03:00
uumas
f2840d79a7 prometheus_node_exporter: Allow listening on all 2025-09-14 03:02:09 +03:00
uumas
217b79b225 Add firewalld role 2025-09-14 03:01:52 +03:00
uumas
37066850a0 compatcheck: support macosx 2025-09-14 02:58:39 +03:00
uumas
7617edfdde borgmatic_config: Initialize repos 2025-09-14 02:58:15 +03:00
uumas
e4c8a2343a borgmatic: ignore unreachable backup target 2025-09-14 02:57:45 +03:00
uumas
9b40f06804 Revert "prometheus_node_exporter: Make listening on localhost possible"
This reverts commit 273da948b5.
2025-07-27 19:42:24 +03:00
uumas
273da948b5 prometheus_node_exporter: Make listening on localhost possible 2025-07-26 23:07:09 +03:00
uumas
7e0538ae20 prometheus_node_exporter: move compatcheck to tasks 2025-07-26 23:06:34 +03:00
uumas
1c9649e8d6 prometheus_node_exporter: rename variable 2025-07-26 23:05:26 +03:00
uumas
648da9266b prometheus_node_exporter: Use systemd_service 2025-07-26 14:34:13 +03:00
uumas
8af49bcc3e vhost: Support regex matching paths 2025-07-19 20:01:34 +03:00
uumas
cb0817fc54 caddy: indent email by 4 spaces 2025-07-19 20:01:09 +03:00
uumas
c0753aeaa2 vhost: Support proxy forward auth 2025-07-13 19:03:02 +03:00
uumas
83569c59ee lint 2025-07-05 15:41:01 +03:00
uumas
6c340c5111 vhost: Add support for custom matchers and specifying response status 2025-07-05 15:40:52 +03:00
uumas
3cd66c54a7 vhost: Allow setting proxy headers 2025-04-05 04:38:14 +03:00
uumas
d9f8733c39 Add support for not passing host header to proxy upstream 2025-04-05 03:58:48 +03:00
uumas
6234c0c459 vhost: Make caddy config use template. Also fix whitespace. 2025-04-05 03:10:27 +03:00
uumas
cebe1eb957 Make compatchecks deduplicatable 2025-04-05 00:44:05 +03:00
uumas
8e27769f5b borgmatic: check mode fixes 2025-03-31 03:50:21 +03:00
uumas
9856bc2087 expand Ubuntu 22 support 2025-03-31 03:49:49 +03:00
uumas
3d4f2773a8 vhost: lint 2025-03-31 03:15:25 +03:00
uumas
0db60e2d60 Add borgmatic 2025-03-31 03:15:14 +03:00
uumas
0deed89c3f vhost: move variable definition from set_fact to vars 2025-03-28 04:18:47 +02:00
uumas
c26c3f8b8e ssh: Don't support x11 forwarding, rename arguments 2025-03-28 04:17:43 +02:00
uumas
3cabd55d0b locale: remove legacy gen_locales variable and arch support 2025-03-28 04:16:59 +02:00
uumas
c2b4820a7a Add compatchecks 2025-03-28 04:15:28 +02:00
uumas
bb30a2024f caddy: move to deb822 repo 2025-03-28 04:14:58 +02:00
uumas
65552ee816 Add deprecation notices 2025-03-28 04:13:59 +02:00
uumas
039c53abe6 Fix apt_repository argspecs 2025-03-27 21:48:24 +02:00
uumas
632a893ca5 Add automatic_updates role 2025-03-27 21:48:12 +02:00
uumas
52862ddfdd locale: Support configuring formats separately 2025-02-15 11:42:20 +02:00
uumas
b983882add v0.5.15 2025-01-27 06:43:38 +02:00
uumas
f94c4b6208 packages: fix include error 2025-01-27 05:35:35 +02:00
uumas
88318942e9 Rename galaxy.yaml and meta/runtime.yaml back to .yml
due to ansible-galaxy requirement
2025-01-27 05:25:56 +02:00
uumas
8988b8a1b5 v0.5.14 2025-01-27 04:58:16 +02:00
uumas
b76fce1e1f lint: .yml -> .yaml 2025-01-27 04:57:37 +02:00
uumas
e2f5e49fe8 ssh: use sshd instead of ssh to make it work on fedora 2025-01-27 04:55:32 +02:00
uumas
d94cc30aa5 compatcheck: Support checking for package manager
Some distros (fedora) have dnf by default but may be atomic also
2025-01-27 04:54:29 +02:00
uumas
34de2fe02b lint: remove empty lines after --- 2025-01-27 04:53:33 +02:00
uumas
8825bba2c6 Merge branch 'master' of git.uumas.fi:uumas/ansible-general 2024-11-13 04:46:05 +02:00
uumas
87adb19fc5 locale: Fix typo in defaults 2024-11-13 04:45:48 +02:00
uumas
a674c1fc48 Merge branch 'master' of git.uumas.fi:uumas/ansible-general 2024-11-09 13:51:54 +02:00
uumas
9eeb4304f5 locale: Fix yml -> yaml in include_vars 2024-11-08 03:36:53 +02:00
uumas
2932426f1f prometheus_node_exporter: lint 2024-11-08 02:49:33 +02:00
uumas
228dde059d locale: Allow disabling locale configuration 2024-11-08 01:54:31 +02:00
uumas
b36cafca1a locale: lint 2024-11-08 01:51:19 +02:00
uumas
e3527bf5fe v0.5.13 2024-11-08 01:14:31 +02:00
uumas
65d5d64222 Add raw_python for installing python on fedora coreos 2024-11-05 15:48:59 +02:00
uumas
20c24f2f1b vhost: lint 2024-11-05 15:48:03 +02:00
uumas
05714898eb vhost: Add support for specifying preserve query independently of
preserve path
2024-11-05 15:47:21 +02:00
uumas
500ad93295 vhost: fix argument spec descriptions 2024-10-16 14:32:05 +03:00
83 changed files with 1236 additions and 211 deletions

View File

@@ -1,5 +1,4 @@
---
Creates users specified in the `users` variable. Syntax:
```

View File

@@ -1,9 +1,8 @@
---
namespace: uumas
name: general
description: General roles
version: 0.5.12
version: 0.5.15
readme: README.md
authors:
- uumas

View File

@@ -1,3 +1,2 @@
---
requires_ansible: ">=2.10"
requires_ansible: ">=2.17.0"

View File

@@ -1,5 +1,4 @@
---
argument_specs:
main:
short_description: Apt repository
@@ -23,10 +22,9 @@ argument_specs:
required: false
default: ''
repo_suite:
description: Suite of the repository. Usually distribution codename.
description: Suite of the repository. Usually distribution codename. Defaults to value of `ansible_distribution_release`.
type: str
required: false
default: "{{ ansible_distribution_release }}"
repo_components:
description: Components of the repository to use
type: list

View File

@@ -1,4 +1,10 @@
---
- name: Deprecation notification
ansible.builtin.debug:
msg: |
The role uumas.general.apt_repository is deprecated and will be removed soon!
Switch to ansible.builtin.deb822_repository.
changed_when: true
- name: Install dependencies
ansible.builtin.apt:

View File

@@ -0,0 +1,2 @@
This role enables automatic package updates.
It currently supports Debian and Ubuntu.

View File

@@ -0,0 +1,8 @@
---
argument_specs:
main:
short_description: Automatic updates
description:
- This role enables automatic package updates.
- It currently supports Debian and Ubuntu.
options: {}

View File

@@ -0,0 +1,9 @@
---
dependencies:
- role: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 11
- name: ubuntu
version_min: 22

View File

@@ -0,0 +1,4 @@
---
- name: Install unatteded-upgrades
ansible.builtin.apt:
name: unattended-upgrades

View File

@@ -0,0 +1 @@
Installs borgmatic

View File

@@ -0,0 +1,22 @@
---
argument_specs:
main:
short_description: Borgmatic
description:
- Installs borgmatic
options:
borgmatic_targets:
description: List of backup targets
type: list
required: true
elements: dict
options:
host:
description: Target hostname
type: str
required: true
directories:
description: Directories on the host where backup repos will be created under
type: list
required: true
elements: str

View File

@@ -0,0 +1,9 @@
---
dependencies:
- role: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 11
- name: ubuntu
version_min: 22

View File

@@ -0,0 +1,47 @@
---
- name: Install borgmatic
ansible.builtin.apt:
name: borgmatic
register: _borgmatic_install
- name: Disable borgmatic global timer
ansible.builtin.systemd_service:
name: borgmatic.timer
state: stopped
enabled: false
when: "not (ansible_check_mode and _borgmatic_install.changed)"
- name: Add systemd drop-in service for borgmatic
ansible.builtin.template:
src: borgmatic@.service.j2
dest: /etc/systemd/system/borgmatic@.service
mode: "0644"
- name: Create borgmatic configurations directory
ansible.builtin.file:
path: /etc/borgmatic.d
state: directory
mode: "0755"
- name: Generate ssh key for borg
community.crypto.openssh_keypair:
type: ed25519
path: "{{ ansible_user_dir }}/.ssh/id_ed25519_borg"
comment: "{{ ansible_user_id }}@{{ ansible_fqdn }} borg"
register: _borgmatic_key
- name: Setup backup targets
ansible.builtin.include_tasks:
file: target.yaml
apply:
delegate_to: "{{ target.host }}"
become: false
loop: "{{ borgmatic_targets }}"
loop_control:
loop_var: target
- name: Add borg target ssh host keys to known hosts
ansible.builtin.known_hosts:
name: "{{ item }}"
key: "{{ item }} ssh-ed25519 {{ hostvars[item].ansible_ssh_host_key_ed25519_public }}"
loop: "{{ borgmatic_targets | map(attribute='host') }}"

View File

@@ -0,0 +1,28 @@
---
- name: Gather facts
ansible.builtin.setup:
delegate_facts: true
ignore_unreachable: true
retries: 3
- name: Add ssh key to authorized_keys
ansible.posix.authorized_key:
user: "{{ hostvars[target.host].ansible_user_id }}"
key: >-
{{
_borgmatic_key.public_key + ' ' + _borgmatic_key.comment
if not (ansible_check_mode and _borgmatic_key.changed)
else 'ssh-ed25519 AAAA'
}}
key_options: >-
command="borg
serve{% for directory in target.directories %}
--restrict-to-path
{{ hostvars[target.host].ansible_user_dir }}/{{ directory }}/{{ ansible_fqdn }}{%- endfor -%}",restrict
- name: Create backup directories
ansible.builtin.file:
path: "{{ hostvars[target.host].ansible_user_dir }}/{{ item }}/{{ ansible_fqdn }}"
state: directory
mode: "0700"
loop: "{{ target.directories }}"

View File

@@ -0,0 +1,63 @@
# {{ ansible_managed }}
[Unit]
Description=borgmatic backup %i
Wants=network-online.target
After=network-online.target
Documentation=https://torsion.org/borgmatic/
[Service]
Type=oneshot
RuntimeDirectory=borgmatic
StateDirectory=borgmatic
# Security settings for systemd running as root, optional but recommended to improve security. You
# can disable individual settings if they cause problems for your use case. For more details, see
# the systemd manual: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
LockPersonality=true
# Certain borgmatic features like Healthchecks integration need MemoryDenyWriteExecute to be off.
# But you can try setting it to "yes" for improved security if you don't use those features.
MemoryDenyWriteExecute=no
NoNewPrivileges=yes
PrivateDevices=yes
PrivateTmp=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
# To restrict write access further, change "ProtectSystem" to "strict" and
# uncomment "ReadWritePaths", "TemporaryFileSystem", "BindPaths" and
# "BindReadOnlyPaths". Then add any local repository paths to the list of
# "ReadWritePaths". This leaves most of the filesystem read-only to borgmatic.
ProtectSystem=full
# ReadWritePaths=-/mnt/my_backup_drive
# This will mount a tmpfs on top of /root and pass through needed paths
# TemporaryFileSystem=/root:ro
# BindPaths=-/root/.cache/borg -/root/.config/borg -/root/.borgmatic
# BindReadOnlyPaths=-/root/.ssh
# May interfere with running external programs within borgmatic hooks.
CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_NET_RAW
# Lower CPU and I/O priority.
Nice=19
CPUSchedulingPolicy=batch
IOSchedulingClass=best-effort
IOSchedulingPriority=7
IOWeight=100
Restart=no
# Prevent rate limiting of borgmatic log events. If you are using an older version of systemd that
# doesn't support this (pre-240 or so), you may have to remove this option.
LogRateLimitIntervalSec=0
ExecStart=systemd-inhibit --who="borgmatic" --what="sleep:shutdown" --why="Prevent interrupting scheduled backup" /usr/bin/borgmatic -c "/etc/borgmatic.d/%I.yaml" --verbosity -2 --syslog-verbosity 1

View File

@@ -0,0 +1 @@
Creates a bormatic configuration in /etc/borgmatic.d/ and creates the repos

View File

@@ -0,0 +1,8 @@
---
borgmatic_config_backup_frequency:
unit: h
amount: 1
borgmatic_config_keep_backups_months: 6
borgmatic_config_targets: "{{ borgmatic_targets }}"

View File

@@ -0,0 +1,13 @@
---
- name: Initialize borgmatic
ansible.builtin.command:
cmd: borgmatic init --encryption repokey
register: _borgmatic_init_out
changed_when: _borgmatic_init_out.stdout | length > 0
- name: Restart borgmatic timer {{ borgmatic_config_name }}
ansible.builtin.systemd_service:
name: "borgmatic@{{ borgmatic_config_name }}.timer"
state: restarted
daemon_reload: true
ignore_errors: "{{ ansible_check_mode }}"

View File

@@ -0,0 +1,65 @@
---
argument_specs:
main:
short_description: Borgmatic config
description:
- Creates a bormatic configuration in /etc/borgmatic.d/ and creates the repos
options:
borgmatic_config_name:
description:
- Name of the borgmatic config.
- Must be unique within the (source) host.
type: str
required: true
borgmatic_config_directories:
description: Directories to backup
type: list
required: true
elements: str
borgmatic_config_encryption_passphrase:
description: Passphrase for borg repo encryption
type: str
required: true
borgmatic_config_targets:
description:
- List of backup targets for this config.
- All backup targets and directories must be listed in borgmatic_targets.
- Defaults to all defined in borgmatic_targets.
type: list
required: false
elements: dict
options:
host:
description: Target hostname
type: str
required: true
directories:
description: Directories on the host where backup repos will be created under
type: list
required: true
elements: str
borgmatic_config_backup_frequency:
description: How often to take backups. Defaults to once per hour.
type: dict
required: false
default:
unit: h
amount: 1
options:
unit:
description: Time unit
type: str
required: true
choices:
- min
- h
- d
amount:
description: Every how many time units to take backpus
type: int
required: true
borgmatic_config_keep_backups_months:
description: How many months to keep backups for
type: int
required: false
default: 6

View File

@@ -0,0 +1,3 @@
---
dependencies:
- borgmatic

View File

@@ -0,0 +1,36 @@
---
- name: Fail if hosts or directories listed in borgmatic_config_targets not in borgmatic_targets.
ansible.builtin.fail:
msg: All backup targets and directories must be listed in borgmatic_targets.
when: >-
borgmatic_config_targets
| items2dict(key_name='host', value_name='directories')
| ansible.builtin.combine(
borgmatic_targets | items2dict(key_name='host', value_name='directories'), list_merge='prepend_rp'
)
| dict2items(key_name='host', value_name='directories')
| difference(borgmatic_targets)
| length != 0
- name: Add borgmatic configuration for {{ borgmatic_config_name }}
ansible.builtin.template:
src: borgmatic.yaml.j2
dest: /etc/borgmatic.d/{{ borgmatic_config_name }}.yaml
mode: "0600"
no_log: true
notify: Initialize borgmatic
- name: Add systemd timer for borgmatic {{ borgmatic_config_name }}
ansible.builtin.template:
src: borgmatic@.timer.j2
dest: /etc/systemd/system/borgmatic@{{ borgmatic_config_name }}.timer
mode: "0644"
register: _borgmatic_config_systemd_timer
notify: Restart borgmatic timer {{ borgmatic_config_name }}
- name: Enable systemd timer for borgmatic {{ borgmatic_config_name }}
ansible.builtin.systemd_service:
name: borgmatic@{{ borgmatic_config_name }}.timer
state: started
enabled: true
when: "not (ansible_check_mode and _borgmatic_config_systemd_timer.changed)"

View File

@@ -0,0 +1,35 @@
# {{ ansible_managed }}
# vim:ft=yaml
source_directories:
{{ borgmatic_config_directories | to_nice_yaml }}
repositories:
{% for target in borgmatic_config_targets %}
{% for directory in target.directories %}
- path: "ssh://{{ hostvars[target.host].ansible_user_id }}@{{ target.host }}/{{ directory }}/{{ ansible_fqdn }}/{{ borgmatic_config_name }}"
{% endfor %}
{% endfor %}
working_directory: "~"
one_file_system: true
exclude_patterns:
- /var/cache
exclude_caches: true
exclude_if_present:
- .nobackup
keep_exclude_tags: true
source_directories_must_exist: true
compression: zstd
encryption_passphrase: "{{ borgmatic_config_encryption_passphrase }}"
ssh_command: ssh -i ~/.ssh/id_ed25519_borg
keep_within: 48H
keep_hourly: 168
keep_daily: 30
keep_weekly: 26
keep_monthly: {{ (borgmatic_config_keep_backups_months / 2) | round(0, 'ceil') | int }}
keep_yearly: {{ (borgmatic_config_keep_backups_months / 12) | round(0, 'ceil') | int }}

View File

@@ -0,0 +1,21 @@
# {{ ansible_managed }}
[Unit]
Description=Run borgmatic backup
[Timer]
{% if borgmatic_config_backup_frequency.unit == "min" %}
OnCalendar=*:0/{{ borgmatic_config_backup_frequency.amount }}
{% elif borgmatic_config_backup_frequency.unit == "h" %}
OnCalendar=0/{{ borgmatic_config_backup_frequency.amount }}:30
{% elif borgmatic_config_backup_frequency.unit == "d" %}
OnCalendar=*-1/{{ borgmatic_config_backup_frequency.amount }} 22:00
{% else %}
{{ dafuq }}
{% endif %}
Persistent=true
RandomizedDelaySec={{ 10 * borgmatic_config_backup_frequency.amount }}{{ _borgmatic_config_previous_time_unit[borgmatic_config_backup_frequency.unit] }}
FixedRandomDelay=true
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,5 @@
---
_borgmatic_config_previous_time_unit:
min: s
h: min
d: h

View File

@@ -1 +1 @@
Installs caddy https server
Installs caddy

View File

@@ -1,5 +1,4 @@
---
- name: Reload caddy
ansible.builtin.systemd:
name: caddy

View File

@@ -1,7 +1,7 @@
---
argument_specs:
main:
short_description: Installs caddy
description: Installs caddy
options:
caddy_admin_email:
description: Email address used for ssl certs

View File

@@ -0,0 +1,10 @@
---
dependencies:
- role: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 12
- name: ubuntu
version_min: 22
- role: uumas.general.firewalld

View File

@@ -1,23 +1,16 @@
---
- name: Ensure host distribution is supported
ansible.builtin.import_role:
name: compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 11
- name: ubuntu
version_min: 20
- name: Ensure legacy caddy apt repository not present
ansible.builtin.file:
path: /etc/apt/sources.list.d/caddy-stable.list
state: absent
- name: Add caddy apt repository
ansible.builtin.include_role:
name: apt_repository
vars:
repo_name: caddy-stable
repo_url: https://dl.cloudsmith.io/public/caddy/stable/deb/debian
repo_key_url: https://dl.cloudsmith.io/public/caddy/stable/gpg.key
repo_suite: any-version
repo_components:
ansible.builtin.deb822_repository:
name: caddy-stable
uris: https://dl.cloudsmith.io/public/caddy/stable/deb/debian
signed_by: https://dl.cloudsmith.io/public/caddy/stable/gpg.key
suites: any-version
components:
- main
when: >
(ansible_distribution == 'Debian' and ansible_distribution_major_version | int == 11) or
@@ -43,8 +36,19 @@
marker: "# {mark} ANSIBLE MANAGED BLOCK general"
block: |
{
email {{ caddy_admin_email }}
email {{ caddy_admin_email }}
}
validate: 'caddy validate --config %s --adapter caddyfile'
backup: true
notify: Reload caddy
- name: Open ports for caddy
ansible.posix.firewalld:
service: "{{ item }}"
state: enabled
permanent: true
immediate: true
loop:
- http
- https
- http3

View File

@@ -0,0 +1,3 @@
Checks that the host is runing a supported os.
Supported distros and versions are defined by the compatcheck_supported_distros variable.
This role is used by other roles to check compatibility.

View File

@@ -22,6 +22,7 @@ argument_specs:
- ubuntu
- fedora
- archlinux
- macosx
version_min:
description: Earliest supported major version. Allows any version if not specified.
type: int
@@ -30,3 +31,16 @@ argument_specs:
description: Last supported major version. Allows any version if not specified.
type: int
required: false
package_managers:
description: >-
List of supported package managers. Defaults to apt for debian and ubuntu,
dnf for fedora, pacman for archlinux, homebrew for macosx
type: list
required: false
elements: str
choices:
- apt
- dnf
- pacman
- atomic_container
- homebrew

View File

@@ -2,14 +2,12 @@
- name: Fail if distribution not supported
ansible.builtin.fail:
msg: "{{ lookup('ansible.builtin.template', 'distroerror.j2').strip() }}"
when: checkfailed
loop:
- "{{ compatcheck_distro | length == 0 }}"
- >
{{
ansible_distribution_major_version != 'n/a' and
compatcheck_distro[0].version_min | default(0) > ansible_distribution_major_version | int
}}
- "{{ compatcheck_distro[0].version_max is defined and compatcheck_distro[0].version_max < ansible_distribution_major_version | int }}"
when: >
_compatcheck_distro | length != 1 or (
ansible_distribution_major_version != 'n/a' and
_compatcheck_distro[0].version_min | default(0) > ansible_distribution_major_version | int
) or
_compatcheck_distro[0].version_max is defined and _compatcheck_distro[0].version_max < ansible_distribution_major_version | int or
ansible_pkg_mgr not in _compatcheck_distro[0].package_managers | default([_compatcheck_default_package_manager])
loop_control:
loop_var: checkfailed

View File

@@ -1,15 +1,18 @@
{%- set distros = [] -%}
{%- for distro in compatcheck_supported_distributions -%}
{% set distro_package_managers = distro.package_managers | default([_compatcheck_default_package_managers[distro.name]]) %}
{% set distro_package_managers_str = ' (' ~ ', '.join(distro_package_managers) ~ ')' %}
{% set distro_name = distro.name | capitalize %}
{%- if distro.version_min is defined -%}
{%- if distro.version_max is defined -%}
{{ distros.append(distro.name | capitalize + ' ' + distro.version_min | string + '-' + distro.version_max | string) }}
{{ distros.append(distro_name ~ ' ' ~ distro.version_min ~ '-' ~ distro.version_max ~ distro_package_managers_str) }}
{%- else -%}
{{ distros.append(distro.name | capitalize + ' ' + distro.version_min | string + '+') }}
{{ distros.append(distro_name ~ ' ' ~ distro.version_min ~ '+' ~ distro_package_managers_str) }}
{%- endif -%}
{%- elif distro.version_max is defined -%}
{{ distros.append(distro.name | capitalize + ' <' + distro.version_max | string) }}
{{ distros.append(distro_name ~ ' <' ~ distro.version_max ~ distro_package_managers_str) }}
{%- else -%}
{{ distros.append(distro.name | capitalize) }}
{{ distros.append(distro_name ~ distro_package_managers_str) }}
{%- endif -%}
{%- endfor -%}
This role only supports {{ distros | join(', ') }} (You are running {{ ansible_distribution }} {{ ansible_distribution_major_version }})
This role only supports {{ distros | join(', ') }} (You are running {{ ansible_distribution }} {{ ansible_distribution_major_version }} ({{ ansible_pkg_mgr }})

View File

@@ -1,2 +1,9 @@
---
compatcheck_distro: "{{ compatcheck_supported_distributions | selectattr('name', 'equalto', ansible_distribution | lower) }}"
_compatcheck_distro: "{{ compatcheck_supported_distributions | selectattr('name', 'equalto', ansible_distribution | lower) }}"
_compatcheck_default_package_managers:
debian: apt
ubuntu: apt
fedora: dnf
archlinux: pacman
macosx: homebrew
_compatcheck_default_package_manager: "{{ _compatcheck_default_package_managers[ansible_distribution | lower] }}"

2
roles/example/README.md Normal file
View File

@@ -0,0 +1,2 @@
This role is just an example.
It pings the host, if example_ping is set to true.

View File

@@ -0,0 +1,12 @@
---
dependencies:
- role: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 8
- name: archlinux
- name: ubuntu
version_min: 16
- name: fedora
version_min: 29

View File

@@ -1,17 +1,4 @@
---
- name: Ensure host distribution is supported
ansible.builtin.import_role:
name: compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 8
- name: archlinux
- name: ubuntu
version_min: 16
- name: fedora
version_min: 29
- name: Ping
ansible.builtin.ping:
when: example_ping

View File

@@ -0,0 +1 @@
Installs firewalld

View File

@@ -0,0 +1,5 @@
---
argument_specs:
main:
description: Installs firewalld
options: {}

View File

@@ -0,0 +1,9 @@
---
dependencies:
- role: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 12
- name: ubuntu
version_min: 22

View File

@@ -0,0 +1,4 @@
---
- name: Install firewalld
ansible.builtin.apt:
name: firewalld

View File

@@ -0,0 +1,12 @@
---
locale_package: []
locale_gen: true
locale_configure: true
locale_gen_locales:
- en_US.UTF-8
- en_GB.UTF-8
locale_lang: "{{ lang | default('en_US.UTF-8') }}"
locale_lc_messages: "{{ locale_lang }}"
locale_formats: "{{ locale_lang }}"

View File

@@ -1,9 +0,0 @@
---
gen_locales:
- en_US.UTF-8
- en_GB.UTF-8
lang: en_US.UTF-8
lc_messages: "{{ lang }}"
language: "en_US:en"

View File

@@ -0,0 +1,11 @@
---
dependencies:
- role: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 10
- name: ubuntu
version_min: 24
- name: fedora
version_min: 39

View File

@@ -1,21 +1,21 @@
---
- name: Include variables for os family {{ ansible_os_family }}
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
ansible.builtin.include_vars: "{{ ansible_os_family }}.yaml"
- name: Install locales package
ansible.builtin.package:
name: "{{ locale_package }}"
when: locale_package is defined
when: locale_package | length > 0
- name: Generate locales
community.general.locale_gen:
name: "{{ item }}"
loop: "{{ gen_locales }}"
when: locale_gen | default('true')
loop: "{{ locale_gen_locales }}"
when: locale_gen
- name: Put default locale config in place
ansible.builtin.template:
src: locale.j2
dest: "{{ locale_config }}"
mode: '0644'
when: locale_configure

View File

@@ -1,6 +1,9 @@
# {{ ansible_managed }}
LANG="{{ lang }}"
LC_MESSAGES="{{ lc_messages }}"
LANGUAGE="{{ language }}"
LANG={{ locale_lang }}
LC_MESSAGES={{ locale_lc_messages }}
LC_NUMERIC={{ locale_formats }}
LC_TIME={{ locale_formats }}
LC_MONETARY={{ locale_formats }}
LC_PAPER={{ locale_formats }}
LC_MEASUREMENT={{ locale_formats }}

View File

@@ -1,3 +0,0 @@
---
locale_config: /etc/locale.conf

View File

@@ -1,4 +1,3 @@
---
locale_package: locales
locale_config: /etc/default/locale

View File

@@ -1,4 +1,3 @@
---
locale_config: /etc/locale.conf
locale_gen: false

View File

@@ -1,5 +1,4 @@
---
install_packages:
- sudo
- vim

View File

@@ -1,5 +1,4 @@
---
- name: Ensure packages defined in install_packages are installed
ansible.builtin.apt:
name: "{{ install_packages }}"

View File

@@ -1,11 +1,17 @@
---
- name: Deprecation notification
ansible.builtin.debug:
msg: |
The role uumas.general.packages is deprecated and will be removed soon!
Just use ansible.builtin.apt or .dnf directly, this does nothing special.
changed_when: true
- name: Include tasks for apt as package manager
ansible.builtin.include_tasks: apt.yml
ansible.builtin.include_tasks: apt.yaml
when: ansible_pkg_mgr == 'apt'
- name: Include tasks for other package manager
ansible.builtin.include_tasks: other.yml
ansible.builtin.include_tasks: other.yaml
when: ansible_pkg_mgr != 'apt'
- name: Ensure packages defined in delete_packages not installed

View File

@@ -1,5 +1,4 @@
---
- name: Ensure packages defined in install_packages are installed
ansible.builtin.package:
name: "{{ install_packages }}"

View File

@@ -0,0 +1,2 @@
---
prometheus_node_exporter_local_network: ""

View File

@@ -1,6 +1,5 @@
---
- name: Restart prometheus-node-exporter
ansible.builtin.systemd:
ansible.builtin.systemd_service:
name: prometheus-node-exporter
state: restarted

View File

@@ -0,0 +1,12 @@
---
argument_specs:
main:
description: Installs and configures prometheus node exporter to listen on local ipv4 address
options:
prometheus_node_exporter_local_network:
description: >-
The local ipv4 network block, listen address is taken from this block.
If empty, listens on 0.0.0.0
type: str
required: false
default: ""

View File

@@ -1,10 +0,0 @@
---
argument_specs:
main:
short_description: Prometheus node exporter
options:
local_network:
description: The local ipv4 network block, listen address is taken from this block
type: str
required: true

View File

@@ -0,0 +1,3 @@
---
dependencies:
- role: uumas.general.firewalld

View File

@@ -1,8 +1,18 @@
---
- name: Compatibility check
ansible.builtin.import_role:
name: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 11
- name: ubuntu
version_min: 22
- name: Install prometheus node exporter
ansible.builtin.apt:
name: prometheus-node-exporter
install_recommends: false
- name: Set prometheus options in /etc/default/prometheus-node-exporter
ansible.builtin.template:

View File

@@ -1 +1 @@
ARGS="--web.listen-address {{ (ansible_all_ipv4_addresses | ansible.utils.ipaddr(local_network))[0] }}:9100 --collector.logind --collector.systemd --collector.processes"
ARGS="--web.listen-address {{ (ansible_all_ipv4_addresses | ansible.utils.ipaddr(prometheus_node_exporter_local_network))[0] if prometheus_node_exporter_local_network | length > 0 else '0.0.0.0' }}:9100 --collector.logind --collector.systemd --collector.processes"

View File

@@ -0,0 +1 @@
Installs python using only the raw module

View File

@@ -0,0 +1,5 @@
---
argument_specs:
main:
description: Installs python using only the raw module
options: {}

View File

@@ -0,0 +1,31 @@
---
- name: Check ansible dependencies
ansible.builtin.raw: which python3
register: _python_test
failed_when: false
changed_when: false
- name: Install ansible dependencies
when: _python_test.rc != 0
block:
- name: Get os release information
ansible.builtin.raw: cat /etc/os-release
changed_when: false
register: _os_release
- name: Parse os release content
ansible.builtin.set_fact:
_os_release_id: >-
{{ (_os_release.stdout_lines
| select('search', '^ID='))[0]
| regex_replace('^ID=', '') }}
_os_release_variant_id: >-
{{ (_os_release.stdout_lines
| select('search', '^VARIANT_ID='))[0]
| default('none')
| regex_replace('^VARIANT_ID=', '') }}
- name: Install ansible dependencies (Fedora CoreOS)
ansible.builtin.raw: rpm-ostree install -y -A python3 python3-libselinux
changed_when: true
when: _os_release_id == 'fedora' and _os_release_variant_id == 'coreos'

View File

@@ -0,0 +1,2 @@
---
ssh_password_auth: false

View File

@@ -1,4 +0,0 @@
---
sshd_x11_forwarding: false
sshd_password_auth: false

View File

@@ -0,0 +1,5 @@
---
- name: Restart sshd
ansible.builtin.systemd_service:
name: sshd.service
state: restarted

View File

@@ -1,6 +0,0 @@
---
- name: Restart ssh
ansible.builtin.systemd:
name: ssh
state: restarted

View File

@@ -1,5 +1,4 @@
---
- name: Ensure sshd config options set correctly
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
@@ -7,8 +6,8 @@
line: "{{ item.key }} {{ item.value }}"
state: present
validate: '/usr/sbin/sshd -t -f %s'
notify: Restart ssh
notify: Restart sshd
with_dict:
PermitRootLogin: 'prohibit-password'
PasswordAuthentication: "{{ 'yes' if sshd_password_auth else 'no' }}"
X11Forwarding: "{{ 'yes' if sshd_x11_forwarding else 'no' }}"
PermitRootLogin: "prohibit-password"
PasswordAuthentication: "{{ 'yes' if ssh_password_auth else 'no' }}"
X11Forwarding: "no"

View File

@@ -0,0 +1,11 @@
---
dependencies:
- role: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 11
- name: ubuntu
version_min: 22
- name: fedora
version_min: 39

View File

@@ -1,5 +1,4 @@
---
- name: Ensure sudo is installed
ansible.builtin.package:
name: sudo
@@ -27,7 +26,8 @@
- name: Set ssh authorized keys for users
ansible.posix.authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_pubkey }}"
key: "{{ item.ssh_pubkeys | default([item.ssh_pubkey]) | join('\n') }}"
exclusive: true
when: item.state | default('present') == 'present'
loop: "{{ users }}"

3
roles/vhost/README.md Normal file
View File

@@ -0,0 +1,3 @@
Sets up a vhost on a web server.
Supports reverse proxies, redirects and simple resonses.
Currently only supports caddy.

View File

@@ -1,5 +1,4 @@
---
vhost_state: present
vhost_type: "{{ vhost_state }}"
vhost_domains: []
@@ -15,9 +14,18 @@ vhost_basicauth_users: {}
vhost_proxy_target_netproto: tcp
vhost_proxy_target_protocol: http
vhost_proxy_target_host: localhost
vhost_proxy_headers: {}
vhost_proxy_delete_headers: []
vhost_proxy_pass_host_header: true
vhost_proxy_auth_socket: ""
vhost_proxy_auth_uri: ""
vhost_proxy_auth_unauthorized_redir: ""
vhost_redirect_type: temporary
vhost_redirect_preserve_path: false
vhost_redirect_preserve_query: "{{ vhost_redirect_preserve_path }}"
vhost_respond_content_type: plain
vhost_respond_status: 200
vhost_matchers: []

View File

@@ -20,16 +20,18 @@ argument_specs:
- present
- absent
vhost_type:
description: Required if vhost_state is present
type: str
required: "{{ vhost_state == 'present' }}"
required: false
choices:
- reverse_proxy
- redirect
- respond
- absent
vhost_domains:
description: Required if vhost_state is present
type: list
required: "{{ vhost_state == 'present' }}"
required: false
elements: str
vhost_web_server:
description: Defines which server software to use for vhost. This role does nothing if set to none.
@@ -94,31 +96,67 @@ argument_specs:
- Port where to proxy requests to.
- Only applicable if vhost_type is reverse_proxy and vhost_proxy_target_netproto is tcp.
type: int
required: "{{ vhost_state == 'present' and vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'tcp' }}"
required: false
vhost_proxy_target_socket:
description:
- Unix socket path to proxy requests to.
- Only applicable if vhost_type is reverse_proxy and vhost_proxy_target_netproto is unix.
type: str
required: "{{ vhost_state == 'present' and vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'unix' }}"
required: false
vhost_proxy_headers:
description: Dict of request headers and their values to set for proxied requests
type: dict
required: false
default: {}
vhost_proxy_delete_headers:
description: List of headers to delete from proxied requests
type: list
elements: str
required: false
default: []
vhost_proxy_pass_host_header:
description: Whether to pass the host header unchanged (true) or change it to the proxy target host (false)
type: bool
required: false
default: true
vhost_proxy_auth_socket:
description: >-
Unix socket path to forward requests to for auhtentication, before
proxying them
type: str
required: false
default: ""
vhost_proxy_auth_uri:
description: >-
The authentication endpoint of the auth host. Required if
proxy_auth_socket is defined. Does nothing otherwise.
type: str
required: false
default: ""
vhost_proxy_auth_unauthorized_redir:
description: >-
Where to redirect requests if authentication service returns 401
unathorized. If not set, returns responses as is.
type: str
required: false
default: ""
vhost_redirect_target:
description: "Only applicable if vhost_type is redirect. Example: https://www.domain.tld/location"
type: str
required: "{{ vhost_state == 'present' and vhost_type == 'redirect' }}"
required: false
vhost_redirect_preserve_path:
description: Whether to keep the original request path
type: bool
required: false
default: false
redirect type:
description: Only applicable if vhost_type is reverse_proxy
vhost_redirect_preserve_query:
description: Whether to keep the original request query string
type: bool
required: false
default: "{{ vhost_redirect_preserve_path }}"
redirect_type:
description: Only applicable if vhost_type is redirect
type: str
required: false
default: temporary
@@ -129,7 +167,7 @@ argument_specs:
vhost_respond_content:
description: Content to respond with. Json content can be set as yaml as long as vhost_respond_content_type is set to json
type: str
required: "{{ vhost_state == 'present' and vhost_type == 'respond' }}"
required: false
vhost_respond_content_type:
description: Type of the respond content
type: str
@@ -138,6 +176,199 @@ argument_specs:
choices:
- plain
- json
vhost_respond_status:
description: Status code of response
type: int
required: false
default: 200
vhost_matchers:
description: >
List of matchers to handle differently from the default for vhost.
A matcher matches if all of its conditions match
type: list
elements: dict
required: false
default: []
options:
name:
description: Name of the matcher used to reference it
type: str
required: true
match_methods:
description: HTTP methods to match against. Matching one method is enough.
type: list
elements: str
choices:
- GET
- HEAD
- OPTIONS
- TRACE
- PUT
- DELETE
- POST
- PATCH
- CONNECT
required: false
default: []
match_headers:
description: >-
Headers to match against.
If the value begins with ^ and end with $, the value is matched as regex.
type: dict
required: false
default: {}
type:
type: str
required: false
default: "{{ vhost_type }}"
choices:
- reverse_proxy
- redirect
- respond
headers:
description: Dict of response headers and their values
type: dict
required: false
default: "{{ vhost_headers }}"
delete_headers:
description: List of response headers to delete
type: list
elements: str
required: false
default: "{{ vhost_delete_headers }}"
basicauth:
description: Whether to require basic auth for the location
type: bool
required: false
default: "{{ vhost_basicauth }}"
basicauth_users:
description: A dict of basic auth users and their password hashes. Required if basicauth is true
type: dict
default: "{{ vhost_basicauth_users }}"
proxy_target_netproto:
description:
- Network protocol to use for proxy requests.
- Only applicable if type is reverse_proxy.
type: str
required: false
default: "{{ vhost_proxy_target_netproto }}"
choices:
- tcp
- unix
proxy_target_protocol:
description:
- Transport protocol (scheme) to use for proxy requests.
- Only applicable if type is reverse_proxy.
type: str
required: false
default: "{{ vhost_proxy_target_protocol }}"
choices:
- http
- https
proxy_target_host:
description: Host where to proxy requests to. Only applicable if type is reverse_proxy
type: str
required: false
default: "{{ vhost_proxy_target_host }}"
proxy_target_port:
description: Port where to proxy requests to. Only applicable if type is reverse_proxy.
type: int
required: false
default: "{{ vhost_proxy_target_port if vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'tcp' else 0 }}"
proxy_target_socket:
description:
- Unix socket path to proxy requests to.
- Only applicable if type is reverse_proxy and proxy_target_netproto is unix.
type: str
required: false
default: "{{ vhost_proxy_target_socket if vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'unix' else '' }}"
proxy_headers:
description: Dict of request headers and their values to set for proxied requests
type: dict
required: false
default: "{{ vhost_proxy_headers }}"
proxy_delete_headers:
description: List of request headers to delete from proxied requests
type: list
elements: str
required: false
default: "{{ vhost_proxy_delete_headers }}"
proxy_pass_host_header:
description: Whether to pass the host header unchanged (true) or change it to the proxy target host (false)
type: bool
required: false
default: "{{ vhost_proxy_pass_host_header }}"
proxy_auth_socket:
description: >-
Unix socket path to forward requests to for auhtentication, before
proxying them
type: str
required: false
default: "{{ vhost_proxy_auth_socket }}"
proxy_auth_uri:
description: >-
The authentication endpoint of the auth host. Required if
proxy_auth_socket is defined. Does nothing otherwise.
type: str
required: false
default: "{{ vhost_proxy_auth_uri }}"
proxy_auth_unauthorized_redir:
description: >-
Where to redirect requests if authentication service returns 401
unathorized. If not set, returns responses as is.
type: str
required: false
default: "{{ vhost_proxy_auth_unauthorized_redir }}"
redirect_target:
description: "Only applicable if vhost_type is redirect. Example: https://www.domain.tld/location"
type: str
required: false
default: "{{ vhost_redirect_target if vhost_type == 'redirect' else '' }}"
redirect_preserve_path:
description: Whether to keep the original request path
type: bool
required: false
default: "{{ vhost_redirect_preserve_path }}"
redirect_preserve_query:
description: Whether to keep the original request query string
type: bool
required: false
default: "{{ vhost_redirect_preserve_query }}"
redirect_type:
description: Only applicable if vhost_type is redirect
type: str
required: false
default: "{{ vhost_redirect_type }}"
choices:
- temporary
- permanent
respond_content:
description: >-
Content to respond with.
Json content can be set as yaml as long as respond_content_type is set to json.
type: str
required: false
default: "{{ vhost_respond_content if vhost_type == 'respond' else '' }}"
respond_content_type:
description: Type of the respond content
type: str
required: false
default: "{{ vhost_respond_content_type }}"
choices:
- plain
- json
respond_status:
description: Status code of response
type: int
required: false
default: "{{ vhost_respond_status }}"
vhost_locations:
description: List of locations to handle differently from the default for vhost
@@ -147,7 +378,9 @@ argument_specs:
elements: dict
options:
path:
description: Path to match. Only supports full paths for now.
description: >-
Path to match.
If the value begins with ^ and end with $, the value is matched as regex.
type: str
required: true
type:
@@ -217,12 +450,43 @@ argument_specs:
type: str
required: false
default: "{{ vhost_proxy_target_socket if vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'unix' else '' }}"
proxy_headers:
description: Dict of request headers and their values to set for proxied requests
type: dict
required: false
default: "{{ vhost_proxy_headers }}"
proxy_delete_headers:
description: List of request headers to delete from proxied requests
type: list
elements: str
required: false
default: "{{ vhost_proxy_delete_headers }}"
proxy_pass_host_header:
description: Whether to pass the host header unchanged (true) or change it to the proxy target host (false)
type: bool
required: false
default: "{{ vhost_proxy_pass_host_header }}"
proxy_auth_socket:
description: >-
Unix socket path to forward requests to for auhtentication, before
proxying them
type: str
required: false
default: "{{ vhost_proxy_auth_socket }}"
proxy_auth_uri:
description: >-
The authentication endpoint of the auth host. Required if
proxy_auth_socket is defined. Does nothing otherwise.
type: str
required: false
default: "{{ vhost_proxy_auth_uri }}"
proxy_auth_unauthorized_redir:
description: >-
Where to redirect requests if authentication service returns 401
unathorized. If not set, returns responses as is.
type: str
required: false
default: "{{ vhost_proxy_auth_unauthorized_redir }}"
redirect_target:
description: "Only applicable if vhost_type is redirect. Example: https://www.domain.tld/location"
@@ -234,8 +498,13 @@ argument_specs:
type: bool
required: false
default: "{{ vhost_redirect_preserve_path }}"
redirect_preserve_query:
description: Whether to keep the original request query string
type: bool
required: false
default: "{{ vhost_redirect_preserve_query }}"
redirect_type:
description: Only applicable if vhost_type is reverse_proxy
description: Only applicable if vhost_type is redirect
type: str
required: false
default: "{{ vhost_redirect_type }}"
@@ -244,7 +513,9 @@ argument_specs:
- permanent
respond_content:
description: Content to respond with. Json content can be set as yaml as long as respond_content_type is set to json
description: >-
Content to respond with.
Json content can be set as yaml as long as respond_content_type is set to json.
type: str
required: false
default: "{{ vhost_respond_content if vhost_type == 'respond' else '' }}"
@@ -256,3 +527,156 @@ argument_specs:
choices:
- plain
- json
respond_status:
description: Status code of response
type: int
required: false
default: "{{ vhost_respond_status }}"
matchers:
description: >
List of matchers to handle differently from the default for vhost.
A matcher matches if all of its conditions match.
Options without a specified default will default to location's corresponding option.
type: list
elements: dict
required: false
default: "{{ vhost_matchers }}"
options:
name:
description: Name of the matcher used to reference it
type: str
required: true
match_methods:
description: HTTP methods to match against. Matching one method is enough.
type: list
elements: str
choices:
- GET
- HEAD
- OPTIONS
- TRACE
- PUT
- DELETE
- POST
- PATCH
- CONNECT
required: false
default: []
match_headers:
description: >-
Headers to match against.
If the value begins with ^ and end with $, the value is matched as regex.
type: dict
required: false
default: {}
type:
type: str
required: false
choices:
- reverse_proxy
- redirect
- respond
headers:
description: Dict of response headers and their values
type: dict
required: false
delete_headers:
description: List of response headers to delete
type: list
elements: str
required: false
basicauth:
description: Whether to require basic auth for the location
type: bool
required: false
basicauth_users:
description: A dict of basic auth users and their password hashes. Required if basicauth is true
type: dict
proxy_target_netproto:
description:
- Network protocol to use for proxy requests.
- Only applicable if type is reverse_proxy.
type: str
required: false
choices:
- tcp
- unix
proxy_target_protocol:
description:
- Transport protocol (scheme) to use for proxy requests.
- Only applicable if type is reverse_proxy.
type: str
required: false
choices:
- http
- https
proxy_target_host:
description: Host where to proxy requests to. Only applicable if type is reverse_proxy
type: str
required: false
proxy_target_port:
description: Port where to proxy requests to. Only applicable if type is reverse_proxy.
type: int
required: false
proxy_target_socket:
description:
- Unix socket path to proxy requests to.
- Only applicable if type is reverse_proxy and proxy_target_netproto is unix.
type: str
required: false
proxy_headers:
description: Dict of request headers and their values to set for proxied requests
type: dict
required: false
proxy_delete_headers:
description: List of request headers to delete from proxied requests
type: list
elements: str
required: false
proxy_pass_host_header:
description: Whether to pass the host header unchanged (true) or change it to the proxy target host (false)
type: bool
required: false
redirect_target:
description: "Only applicable if vhost_type is redirect. Example: https://www.domain.tld/location"
type: str
required: false
redirect_preserve_path:
description: Whether to keep the original request path
type: bool
required: false
redirect_preserve_query:
description: Whether to keep the original request query string
type: bool
required: false
redirect_type:
description: Only applicable if vhost_type is redirect
type: str
required: false
choices:
- temporary
- permanent
respond_content:
description: >-
Content to respond with.
Json content can be set as yaml as long as respond_content_type is set to json.
type: str
required: false
respond_content_type:
description: Type of the respond content
type: str
required: false
choices:
- plain
- json
respond_status:
description: Status code of response
type: int
required: false

View File

@@ -1,5 +1,4 @@
---
dependencies:
- role: caddy
when: vhost_web_server == 'caddy'

View File

@@ -1,57 +1,9 @@
---
- name: Add caddy vhost config
ansible.builtin.blockinfile:
path: /etc/caddy/Caddyfile
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ vhost_id }}"
block: |
{{ vhost_domains | join(' ') }} {
{% for location in vhost_locations_all %}
handle {{ location.path }} {
{% for header in location.delete_headers %}
header -{{ header }}
{% endfor %}
{% for header in location.headers | dict2items %}
header {{ header.key }} `{{ header.value }}`
{% endfor %}
{% if location.basicauth %}
basicauth {
{% for user in location.basicauth_users | dict2items %}
{{ user.key }} {{ user.value }}
{% endfor %}
}
{% endif %}
{% if location.type == 'reverse_proxy' %}
reverse_proxy {
{% if location.proxy_target_netproto == 'tcp' %}
to tcp/{{ location.proxy_target_host }}:{{ location.proxy_target_port }}
{% else %}
to unix/{{ location.proxy_target_socket }}
{% endif %}
{% if location.proxy_target_protocol == 'https' %}
transport http {
tls
{% if location.proxy_target_host == 'localhost' %}
tls_insecure_skip_verify
{% endif %}
}
{% endif %}
}
{% for header in location.proxy_delete_headers %}
request_header -{{ header }}
{% endfor %}
{% elif location.type == 'redirect' %}
redir {{ location.redirect_target }}{{ '{uri}' if location.redirect_preserve_path }} {{ location.redirect_type }}
{% elif location.type == 'respond' %}
{% if location.respond_content_type == 'json' %}
respond `{{ location.respond_content | to_json }}`
{% else %}
respond `{{ location.respond_content }}`
{% endif %}
{% endif %}
}
{% endfor %}
}
block: "{{ lookup('ansible.builtin.template', 'Caddyfile_block.j2') }}"
validate: 'caddy validate --config %s --adapter caddyfile'
backup: true
state: "{{ vhost_state }}"

View File

@@ -1,5 +1,4 @@
---
- name: Fail if vhost_redirect_target is a relative path and vhost_redirect_preserve_path is true
ansible.builtin.fail:
msg: vhost_redirect_target must be an absolute url or absolute path if vhost_redirect_preserve_path is true
@@ -15,39 +14,6 @@
- vhost_redirect_preserve_path
- vhost_redirect_target.endswith('/')
- name: Reset vhost_locations_all
ansible.builtin.set_fact:
vhost_locations_all: []
- name: Set vhost_locations_all reverse proxies
ansible.builtin.set_fact:
vhost_locations_all: >
{{ vhost_locations_all + [{
'path': item.path,
'type': item.type | default(vhost_type),
'headers': item.headers | default(vhost_headers),
'delete_headers': item.delete_headers | default(vhost_delete_headers),
'basicauth': item.basicauth | default(vhost_basicauth),
'basicauth_users': item.basicauth_users | default(vhost_basicauth_users),
'proxy_target_netproto': item.proxy_target_netproto | default(vhost_proxy_target_netproto),
'proxy_target_protocol': item.proxy_target_protocol | default(vhost_proxy_target_protocol),
'proxy_target_host': item.proxy_target_host | default(vhost_proxy_target_host),
'proxy_target_port': item.proxy_target_port | default(vhost_proxy_target_port if
vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'tcp' else ''),
'proxy_target_socket': item.proxy_target_socket | default(vhost_proxy_target_socket if
vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'unix' else ''),
'proxy_delete_headers': item.proxy_delete_headers | default(vhost_proxy_delete_headers),
'redirect_target': item.redirect_target | default(vhost_redirect_target if vhost_type == 'redirect' else ''),
'redirect_preserve_path': item.redirect_preserve_path | default(vhost_redirect_preserve_path),
'redirect_type': item.redirect_type | default(vhost_redirect_type),
'respond_content': item.respond_content | default(vhost_respond_content if vhost_type == 'respond' else ''),
'respond_content_type': item.respond_content_type | default(vhost_respond_content_type)
}] }}
loop: "{{ vhost_locations + [{'path': ''}] }}"
- name: "Setup {{ vhost_id + ' vhost on ' + vhost_web_server }}"
ansible.builtin.include_tasks: "{{ vhost_web_server }}.yaml"
when: vhost_web_server != 'none'

View File

@@ -0,0 +1,85 @@
#jinja2: lstrip_blocks: True
{{ vhost_domains | join(' ') }} {
{% for location in _vhost_locations_complete %}
{% if location.path != '' %}
@{{ location.path }} path{{ '_regexp' if location.path.startswith('^') and location.path.endswith('$') else '' }} {{ location.path }}
handle @{{ location.path }} {
{% else %}
handle {
{% endif %}
{% for matcher in location.matchers %}
{% if matcher.name != '' %}
@{{ matcher.name }} {
{% if matcher.match_methods | length > 0 %}
method {{ matcher.match_methods | join(' ') }}
{% endif %}
{% for header in matcher.match_headers | dict2items %}
header{{ '_regexp' if header.value.startswith('^') and header.value.endswith('$') else '' }} {{ header.key }} {{ header.value }}
{% endfor %}
}
{% endif %}
handle{{ ' @' ~ matcher.name if matcher.name != '' else '' }} {
{% for header in matcher.delete_headers %}
header -{{ header }}
{% endfor %}
{% for header in matcher.headers | dict2items %}
header {{ header.key }} `{{ header.value }}`
{% endfor %}
{% if matcher.basicauth %}
basicauth {
{% for user in matcher.basicauth_users | dict2items %}
{{ user.key }} {{ user.value }}
{% endfor %}
}
{% endif %}
{% if matcher.type == 'reverse_proxy' %}
{% if matcher.proxy_auth_socket | length > 0 %}
forward_auth {
to unix//{{ matcher.proxy_auth_socket }}
uri {{ matcher.proxy_auth_uri }}
{% if matcher.proxy_auth_unauthorized_redir | length > 0 %}
@unauthorized status 401
handle_response @unauthorized {
redir * {{ matcher.proxy_auth_unauthorized_redir }}
}
{% endif %}
}
{% endif %}
reverse_proxy {
{% if matcher.proxy_target_netproto == 'tcp' %}
to tcp/{{ matcher.proxy_target_host }}:{{ matcher.proxy_target_port }}
{% else %}
to unix/{{ matcher.proxy_target_socket }}
{% endif %}
{% if matcher.proxy_target_protocol == 'https' %}
transport http {
tls
{% if matcher.proxy_target_host == 'localhost' %}
tls_insecure_skip_verify
{% endif %}
}
{% endif %}
{% for header in matcher.proxy_delete_headers %}
header_up -{{ header }}
{% endfor %}
{% for header in matcher.proxy_headers | dict2items %}
header_up {{ header.key }} `{{ header.value }}`
{% endfor %}
{% if (not matcher.proxy_pass_host_header) and ('host' not in matcher.proxy_headers | map('lower')) %}
header_up Host {upstream_hostport}
{% endif %}
}
{% elif matcher.type == 'redirect' %}
redir * {{ matcher.redirect_target }}{{ '{path}' if matcher.redirect_preserve_path }}{{ '?{query}' if matcher.redirect_preserve_query }} {{ matcher.redirect_type }}
{% elif matcher.type == 'respond' %}
{% if matcher.respond_content_type == 'json' %}
respond `{{ matcher.respond_content | to_json }}`
{% else %}
respond `{{ matcher.respond_content }}` {{ matcher.respond_status }}
{% endif %}
{% endif %}
}
{% endfor %}
}
{% endfor %}
}

View File

@@ -0,0 +1,74 @@
---
_vhost_matcher_defaults:
match_headers: {}
match_method: []
_vhost_matchers: >-
{{
vhost_matchers
| map('combine', _vhost_matcher_defaults)
| zip(vhost_matchers)
| map('combine')
}}
_vhost_location_defaults:
type: "{{ vhost_type }}"
headers: "{{ vhost_headers }}"
delete_headers: "{{ vhost_delete_headers }}"
basicauth: "{{ vhost_basicauth }}"
basicauth_users: "{{ vhost_basicauth_users }}"
proxy_target_netproto: "{{ vhost_proxy_target_netproto }}"
proxy_target_protocol: "{{ vhost_proxy_target_protocol }}"
proxy_target_host: "{{ vhost_proxy_target_host }}"
proxy_target_port: "{{ vhost_proxy_target_port if
vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'tcp' else '' }}"
proxy_target_socket: "{{ vhost_proxy_target_socket if
vhost_type == 'reverse_proxy' and vhost_proxy_target_netproto == 'unix' else '' }}"
proxy_headers: "{{ vhost_proxy_headers }}"
proxy_delete_headers: "{{ vhost_proxy_delete_headers }}"
proxy_pass_host_header: "{{ vhost_proxy_pass_host_header }}"
proxy_auth_socket: "{{ vhost_proxy_auth_socket }}"
proxy_auth_uri: "{{ vhost_proxy_auth_uri }}"
proxy_auth_unauthorized_redir: "{{ vhost_proxy_auth_unauthorized_redir }}"
redirect_target: "{{ vhost_redirect_target if vhost_type == 'redirect' else '' }}"
redirect_preserve_path: "{{ vhost_redirect_preserve_path }}"
redirect_preserve_query: "{{ vhost_redirect_preserve_query }}"
redirect_type: "{{ vhost_redirect_type }}"
respond_content: "{{ vhost_respond_content if vhost_type == 'respond' else '' }}"
respond_content_type: "{{ vhost_respond_content_type }}"
respond_status: "{{ vhost_respond_status }}"
matchers: "{{ _vhost_matchers }}"
_vhost_locations: "{{ vhost_locations + [{'path': ''}] }}"
_vhost_locations_withdefaults: >-
{{
_vhost_locations
| map('combine', _vhost_location_defaults)
| zip(
_vhost_locations
)
| map('combine')
| map('combine', {'matchers': [{'name': ''}]}, list_merge='append')
}}
_vhost_locations_complete: >-
{{
_vhost_locations_withdefaults |
sort(attribute='path') |
zip(
_vhost_locations_withdefaults |
subelements('matchers') |
map('combine') |
groupby('path') |
map('last') |
map('community.general.remove_keys', ['matchers', 'path']) |
map('community.general.dict_kv', 'matchers')
) |
map('combine') |
reverse
}}