Compare commits

..

50 Commits

Author SHA1 Message Date
uumas
b2540e2bd3 service: Validation, set default for mounts in additional containers 2025-11-26 22:26:12 +02:00
uumas
47088fd1a0 Allow setting container entrypoint 2025-11-26 22:25:54 +02:00
uumas
abf3859af7 container: rename task 2025-10-06 16:18:16 +03:00
uumas
bdec55ffc7 Use auth file instead of creds in quadlet files 2025-10-06 16:17:27 +03:00
uumas
2712cf2865 service: Support setting templated file mode 2025-09-16 12:37:20 +03:00
uumas
c5fb7f599c podman: Login to registries 2025-09-16 12:36:58 +03:00
uumas
597faa3fd5 service: Set static ips for other containers too 2025-09-15 12:34:57 +03:00
uumas
16babfd5ed service: Have service depend on oauth2-proxy socat socket if set 2025-09-15 12:34:21 +03:00
uumas
d3542993dd container: Set network ip range when using a static container ip 2025-09-15 12:32:22 +03:00
uumas
a93c26864d network: Support setting ip range 2025-09-15 12:31:40 +03:00
uumas
b333bbebbd Add prometheus role 2025-09-14 03:21:33 +03:00
uumas
fea49be8d1 Use service-specific oauth2-proxy instances 2025-09-14 03:10:20 +03:00
uumas
aaca377811 service: Support static ip for service container 2025-09-14 03:09:28 +03:00
uumas
0b73582f36 container: Support static ip for container 2025-09-14 03:08:24 +03:00
uumas
ad50e05ee9 network: Support static subnet 2025-09-14 03:07:28 +03:00
uumas
12f32f5824 network: Support macvlan driver 2025-09-14 03:07:03 +03:00
uumas
586f98bc9f synapse: Use federation port 8448 2025-09-14 03:05:06 +03:00
uumas
a29908b507 podman: Ensure auto update timer is enabled 2025-09-14 03:04:22 +03:00
uumas
c96997a4ec lint 2025-09-13 17:36:05 +03:00
uumas
014edb08ac service: fix template mounts for additional containers 2025-08-28 11:02:35 +03:00
uumas
d260e28625 synapse: Use regex for mas-proxied paths 2025-07-19 20:03:12 +03:00
uumas
39b35b30a9 grafana: Support additional networks 2025-07-13 19:09:44 +03:00
uumas
6baab11851 service: Support proxy forward auth using OAuth2 Proxy 2025-07-13 19:09:08 +03:00
uumas
543a34f60d Add oauth2_proxy role 2025-07-13 19:08:10 +03:00
uumas
4e4f824958 service: Support additional networks for database 2025-07-13 19:05:37 +03:00
uumas
a8a7dfc688 container: Restart container when secret changed 2025-07-11 20:44:39 +03:00
uumas
f52ba4eced service: Allow socat without reverse proxy config 2025-07-11 20:41:04 +03:00
uumas
303d3a384a Add grafana role 2025-07-10 00:56:23 +03:00
uumas
d6083ec2be image: Add readme 2025-07-10 00:54:18 +03:00
uumas
4d3a5933c0 service: Better organize vars 2025-07-05 16:52:10 +03:00
uumas
8b55af2d06 synapse: Fix signing key path 2025-07-05 16:37:00 +03:00
uumas
e38c283825 service: Fix additional container mounts to use service prefix 2025-07-05 16:36:33 +03:00
uumas
2182b821f4 service: Use properly prefixed loop var for additional containers 2025-07-05 16:35:57 +03:00
uumas
6437c78a94 service: Move mount variable definitions form set_fact to vars 2025-07-05 16:33:29 +03:00
uumas
c31fbf0833 service: Support specifying container command 2025-07-05 16:24:26 +03:00
uumas
84036653fe container: Set exit code 143 as success
Some applications return 143 when exiting due to SIGTERM
2025-07-05 16:20:34 +03:00
uumas
a862606df2 volume: Support device-based volumes
Also support those options in container and service roles
2025-07-05 16:19:14 +03:00
uumas
faa68bfe83 service: Support setting type and target for db password secret 2025-06-28 13:46:07 +03:00
uumas
a10bf366e6 service: Allow custom postgres image 2025-06-27 00:06:28 +03:00
uumas
78860da6a4 service: Add support for redis 2025-06-27 00:06:28 +03:00
uumas
aa9eabf19c service: Actually do something with service_additional_containers 2025-06-27 00:06:23 +03:00
uumas
2e14434c9f service: Set secret target to secret name
This is a breaking change as it was previously prefixed with service
name
2025-06-27 00:05:51 +03:00
uumas
8f29c2815e container: Allow setting secret target 2025-06-26 00:51:06 +03:00
uumas
60529c18cd Revert "container: shell quote environment variables"
This reverts commit 61aa99bcd1.
2025-06-20 13:13:55 +03:00
uumas
7d8b1cb258 container: Add support for specifying secret type (untested) 2025-06-17 09:08:59 +03:00
uumas
3ac6b98a30 Add image role, support logging in to registries 2025-04-10 19:27:16 +03:00
uumas
093e7846ad service: Option to not pass host header to container service 2025-04-05 04:38:57 +03:00
uumas
6acdcd6dac Make compatcheck deduplicatable 2025-04-05 00:35:22 +03:00
uumas
bdac6ee513 network: Avoid duplicate runs 2025-04-05 00:25:11 +03:00
uumas
56d86d964c podman: remove tags 2025-04-05 00:24:49 +03:00
58 changed files with 1633 additions and 177 deletions

View File

@@ -1,9 +1,11 @@
--- ---
container_command: [] container_command: []
container_entrypoint: ""
container_user: "" container_user: ""
container_mounts: [] container_mounts: []
container_publish_ports: [] container_publish_ports: []
container_networks: [] container_networks: []
container_ip: ""
container_secrets: [] container_secrets: []
container_env: {} container_env: {}
container_auto_start: true container_auto_start: true

View File

@@ -7,22 +7,28 @@ argument_specs:
description: Name of the container. Must be unique within a host. description: Name of the container. Must be unique within a host.
type: str type: str
required: true required: true
container_image:
description: "The image to run in the container, in FQIN format (registry/imagename:tag)"
type: str
required: true
container_command: container_command:
description: Command to start the container with. description: Command to start the container with.
type: list type: list
required: false required: false
default: [] default: []
elements: str elements: str
container_entrypoint:
description: Entrypoint to use for the continaer
type: str
required: false
default: ""
container_user: container_user:
description: The UID to run as inside the container description: The UID to run as inside the container
type: str type: str
required: false required: false
default: "" default: ""
container_image:
description: "The image to run in the container, in FQIN format (registry/imagename:tag)"
type: str
required: true
container_mounts: container_mounts:
description: List of bind mounts or volumes to be mounted inside the container. description: List of bind mounts or volumes to be mounted inside the container.
type: list type: list
@@ -31,7 +37,7 @@ argument_specs:
elements: dict elements: dict
options: options:
type: type:
description: Type of volume description: Type of mount
type: str type: str
required: true required: true
choices: choices:
@@ -63,6 +69,28 @@ argument_specs:
type: str type: str
required: false required: false
default: "" default: ""
volume_device:
description: >-
The path of a device which is mounted for the volume.
Only applicable if mount type is volume.
type: str
required: false
default: ""
volume_type:
description: >-
The filesystem type of device as used by the mount commands -t option
Only applicable if mount type is volume.
type: str
required: false
default: ""
volume_mount_options:
description: >-
The mount options to use for a filesystem as used by the mount command -o option
Only applicable if mount type is volume.
type: list
elements: str
required: false
default: []
container_publish_ports: container_publish_ports:
description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)" description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)"
@@ -76,8 +104,13 @@ argument_specs:
required: false required: false
default: [] default: []
elements: str elements: str
container_ip:
description: IPv4 address for the container in the first network defined in container_networks
type: str
required: false
default: ""
container_secrets: container_secrets:
description: A list of secrets available to the container in /run/secrets/<secret name> description: A list of secrets available to the container as file or environment variable
type: list type: list
required: false required: false
default: [] default: []
@@ -97,7 +130,21 @@ argument_specs:
description: Length of randomly generated string description: Length of randomly generated string
type: int type: int
required: false required: false
defalut: 128 default: 128
type:
description: How the secret will be exposed to the container
type: str
choices:
- mount
- env
default: mount
target:
description: >
Where the secret will be available inside the container. If type is mount, this is
either a full file path or a filename under /run/secrets. If type is env, this is
the name of the environment variable. Defaults to secret name.
type: str
required: false
container_env: container_env:
description: A dict of environment variables for the container description: A dict of environment variables for the container

View File

@@ -2,14 +2,25 @@
- name: Validate inputs - name: Validate inputs
ansible.builtin.import_tasks: validation.yaml ansible.builtin.import_tasks: validation.yaml
- name: Create image for container {{ container_name }}
ansible.builtin.include_role:
name: image
vars:
image_name: "{{ container_image }}"
when: image_created_images is not defined or container_image not in image_created_images
- name: Create networks for container {{ container_name }} - name: Create networks for container {{ container_name }}
ansible.builtin.include_role: ansible.builtin.include_role:
name: network name: network
vars: vars:
network_name: "{{ network }}" network_name: "{{ network }}"
network_subnet: "{{ _container_network_subnet if network_index == 0 else '' }}"
network_range: "{{ _container_network_range if network_index == 0 else '' }}"
when: network_created_networks is not defined or network not in network_created_networks
loop: "{{ container_networks }}" loop: "{{ container_networks }}"
loop_control: loop_control:
loop_var: network loop_var: network
index_var: network_index
- name: Create volumes for container {{ container_name }} - name: Create volumes for container {{ container_name }}
ansible.builtin.include_role: ansible.builtin.include_role:
@@ -18,6 +29,9 @@
volume_name: "{{ volume.source }}" volume_name: "{{ volume.source }}"
volume_uid: "{{ volume.user | default('') }}" volume_uid: "{{ volume.user | default('') }}"
volume_gid: "{{ volume.group | default('') }}" volume_gid: "{{ volume.group | default('') }}"
volume_type: "{{ volume.volume_type | default('') }}"
volume_device: "{{ volume.volume_device | default('') }}"
volume_mount_options: "{{ volume.volume_mount_options | default([]) }}"
loop: "{{ _container_volumes }}" loop: "{{ _container_volumes }}"
loop_control: loop_control:
loop_var: volume loop_var: volume
@@ -26,18 +40,20 @@
ansible.builtin.include_tasks: secrets.yaml ansible.builtin.include_tasks: secrets.yaml
when: container_secrets | length > 0 when: container_secrets | length > 0
- name: Create container service {{ container_name }} - name: Create container {{ container_name }}
containers.podman.podman_container: containers.podman.podman_container:
image: "{{ container_image }}" image: "{{ _container_image }}"
name: "{{ container_name }}" name: "{{ container_name }}"
command: "{{ container_command or omit }}" command: "{{ container_command or omit }}"
entrypoint: "{{ container_entrypoint or omit }}"
user: "{{ container_user or omit }}" user: "{{ container_user or omit }}"
mount: "{{ _container_mounts | map('items') | map('map', 'join', '=') | map('join', ',') }}" mount: "{{ _container_mounts | map('items') | map('map', 'join', '=') | map('join', ',') }}"
network: "{{ container_networks | map('regex_replace', '$', '.network') }}" network: "{{ _container_networks_with_ip }}"
publish: "{{ container_publish_ports }}" publish: "{{ container_publish_ports }}"
secrets: "{{ container_secrets | map(attribute='name') }}" secrets: "{{ _container_secrets }}"
env: "{{ container_env.keys() | zip(container_env.values() | map('quote')) | community.general.dict }}" env: "{{ container_env }}"
label: "{{ _container_labels if _container_labels | length > 0 else omit }}"
state: quadlet state: quadlet
quadlet_file_mode: "0600" quadlet_file_mode: "0600"
quadlet_options: "{{ _container_quadlet_options }}" quadlet_options: "{{ _container_quadlet_options }}"
notify: "Restart container service {{ container_name }}" notify: Restart container service {{ container_name }}

View File

@@ -6,3 +6,4 @@
skip_existing: "{{ item.value is not defined }}" skip_existing: "{{ item.value is not defined }}"
no_log: true no_log: true
loop: "{{ container_secrets }}" loop: "{{ container_secrets }}"
notify: Restart container service {{ container_name }}

View File

@@ -1,4 +1,33 @@
--- ---
_container_image: "{{ container_image | replace('/', '_') ~ '.image' }}"
_container_networks: "{{ container_networks | map('regex_replace', '$', '.network') }}"
_container_networks_with_ip: >-
{{
[
_container_networks[0] ~ (
':ip=' ~ container_ip if container_ip | length > 0 else ''
)
]
+ _container_networks[1:]
}}
_container_network_subnet: >-
{{ container_ip | ansible.utils.ipsubnet(24) if container_ip | length > 0 else '' }}
_container_network_subnet_ranges: >-
{{
[
_container_network_subnet | ansible.utils.ipsubnet(25, 0),
_container_network_subnet | ansible.utils.ipsubnet(25, 1)
] if container_ip | length > 0 else ''
}}
_container_network_range: >-
{{
_container_network_subnet_ranges |
reject('ansible.utils.supernet_of', container_ip) |
first
if container_ip | length > 0 else ''
}}
_container_volumes: "{{ container_mounts | selectattr('type', '==', 'volume') }}" _container_volumes: "{{ container_mounts | selectattr('type', '==', 'volume') }}"
_container_mount_sources: "{{ container_mounts | map(attribute='source') }}" _container_mount_sources: "{{ container_mounts | map(attribute='source') }}"
@@ -16,10 +45,38 @@ _container_mounts: >-
{{ {{
container_mounts | selectattr('type', '!=', 'volume') + container_mounts | selectattr('type', '!=', 'volume') +
container_mounts | selectattr('type', '==', 'volume') container_mounts | selectattr('type', '==', 'volume')
| community.general.remove_keys(['user', 'group']) | community.general.keep_keys(['type', 'source', 'destination', 'readonly'])
| zip(_container_volume_mount_sources) | map('combine') | zip(_container_volume_mount_sources) | map('combine')
}} }}
_container_secrets: >-
{{
container_secrets
| map(attribute='name')
| zip(
container_secrets
| map(attribute='type', default='mount')
| map('regex_replace', '^', 'type='),
container_secrets
| map(attribute='name')
| map('community.general.dict_kv', 'target')
| zip(container_secrets)
| map('combine')
| map(attribute='target')
| map('regex_replace', '^', 'target=')
)
| map('join', ',')
}}
_container_labels: >-
{{
{'io.containers.autoupdate.authfile': '/etc/containers/auth.json'}
if container_auto_update and
container_image.split('/')[0] in
podman_registry_accounts | map(attribute='registry')
else {}
}}
_container_quadlet_unit_options: | _container_quadlet_unit_options: |
[Unit] [Unit]
Description=Container {{ container_name }} Description=Container {{ container_name }}
@@ -32,6 +89,8 @@ _container_quadlet_unit_options: |
{% for want in container_wants %} {% for want in container_wants %}
Wants={{ want }} Wants={{ want }}
{% endfor %} {% endfor %}
[Service]
SuccessExitStatus=0 143
_container_quadlet_auto_start_options: | _container_quadlet_auto_start_options: |
[Service] [Service]
Restart=always Restart=always

1
roles/grafana/README.md Normal file
View File

@@ -0,0 +1 @@
Installs and configures grafana

View File

@@ -0,0 +1,15 @@
---
grafana_additional_networks: []
grafana_oauth_name: ""
grafana_oauth_client_id: ""
grafana_oauth_auth_url: ""
grafana_oauth_token_url: ""
grafana_oauth_api_url: ""
grafana_oauth_scopes:
- openid
- profile
- email
grafana_oauth_role_attribute_path: ""
grafana_oauth_allow_sign_up: true
grafana_oauth_auto_login: true

View File

@@ -0,0 +1,80 @@
---
argument_specs:
main:
description: Installs and configures grafana
options:
grafana_domain:
description: The domain grafana should be available on
type: str
required: true
grafana_additional_networks:
description: >-
A list of additional podman networks for the grafana container (in
addition to grafana network).
type: list
required: false
default: []
elements: str
grafana_oauth_name:
description: >-
Name that refers to the generic OAuth2 authentication from the Grafana
user interface. Required to enable OAuth authentication.
type: str
required: false
default: ""
grafana_oauth_client_id:
description: >-
Client ID provided by your OAuth2 app. Required if OAuth is enabled.
type: str
required: false
default: ""
grafana_oauth_auth_url:
description: Authorization endpoint of your OAuth2 provider. Required if OAuth is enabled.
type: str
required: false
default: ""
grafana_oauth_token_url:
description: Endpoint used to obtain the OAuth2 access token.
type: str
required: false
default: ""
grafana_oauth_api_url:
description: Endpoint used to obtain user information compatible with OpenID UserInfo.
type: str
required: false
default: ""
grafana_oauth_scopes:
description: List of OAuth2 scopes.
type: list
required: false
elements: str
default:
- openid
- profile
- email
grafana_oauth_role_attribute_path:
description: >-
JMESPath expression to use for Grafana role lookup. Grafana will first
evaluate the expression using the OAuth2 ID token. If no role is found,
the expression will be evaluated using the user information obtained
from the UserInfo endpoint. The result of the evaluation should be
a valid Grafana role (Viewer, Editor, Admin or GrafanaAdmin).
type: str
required: false
default: ""
grafana_oauth_allow_sign_up:
description: >-
Controls Grafana user creation through the generic OAuth2 login. Only
existing Grafana users can log in with generic OAuth if set to false.
type: bool
required: false
default: true
grafana_oauth_auto_login:
description: >-
Whether to enable users to bypass the login screen and automatically
log in. This setting is ignored if you configure multiple auth
providers to use auto-login.
type: bool
required: false
default: true

View File

@@ -0,0 +1,40 @@
---
- name: Grafana
ansible.builtin.import_role:
name: service
vars:
service_name: grafana
service_container_image: "docker.io/grafana/grafana:latest"
service_container_mounts:
- type: volume
source: data
destination: /var/lib/grafana
service_container_http_port: 3000
service_domains:
- "{{ grafana_domain }}"
service_database_type: postgres
service_postgres_tag: 17-alpine
service_container_additional_networks: "{{ grafana_additional_networks }}"
service_container_env:
GF_DATABASE_TYPE: postgres
GF_DATABASE_HOST: grafana-postgres:5432
GF_DATABASE_NAME: grafana
GF_DATABASE_USER: grafana
GF_DATABASE_PASSWORD__FILE: /run/secrets/postgres
GF_SERVER_DOMAIN: "{{ grafana_domain }}"
GF_SERVER_ROOT_URL: "https://{{ grafana_domain }}"
GF_AUTH_GENERIC_OAUTH_ENABLED: "{{ 'true' if grafana_oauth_name | length > 0 else 'false' }}"
GF_AUTH_GENERIC_OAUTH_NAME: "{{ grafana_oauth_name }}"
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "{{ grafana_oauth_client_id }}"
GF_AUTH_GENERIC_OAUTH_AUTH_URL: "{{ grafana_oauth_auth_url }}"
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "{{ grafana_oauth_token_url }}"
GF_AUTH_GENERIC_OAUTH_API_URL: "{{ grafana_oauth_userinfo_url }}"
GF_AUTH_GENERIC_OAUTH_SCOPES: "{{ grafana_oauth_scopes | join(' ') }}"
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: "{{ 'true' if grafana_oauth_allow_sign_up else 'false' }}"
GF_AUTH_GENERIC_OAUTH_AUTO_LOGIN: "{{ 'true' if grafana_oauth_auto_login else 'false' }}"
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "{{ grafana_oauth_role_attribute_path }}"
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_STRICT: "true"
GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN: "true"
GF_AUTH_GENERIC_OAUTH_USE_PKCE: "true"
GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH: preferred_username

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

@@ -0,0 +1,2 @@
Sets up podman image with systemd unit (quadlet)
The image unit filename is `image_name` with / replaced by _

View File

@@ -0,0 +1,11 @@
---
argument_specs:
main:
description:
- Sets up podman image with systemd unit (quadlet)
- The image unit filename is `image_name` with / replaced by _
options:
image_name:
description: "The image FQIN (format registry/imagename:tag)"
type: str
required: true

View File

@@ -0,0 +1,3 @@
---
dependencies:
- role: podman

View File

@@ -0,0 +1,19 @@
---
- name: Set variables for use by other roles
ansible.builtin.set_fact:
image_created_images: "{{ image_created_images | default([]) + [image_name] }}"
- name: Create container image service {{ image_name }}
containers.podman.podman_image:
name: "{{ image_name }}"
state: quadlet
quadlet_filename: "{{ image_name | replace('/', '_') }}"
quadlet_file_mode: "0600"
quadlet_options: >-
{{
['AuthFile=/etc/containers/auth.json']
if image_name.split('/')[0] in
podman_registry_accounts | map(attribute='registry')
else []
}}
notify: Reload systemd daemon

View File

@@ -0,0 +1,4 @@
---
network_driver: bridge
network_subnet: ""
network_range: ""

View File

@@ -0,0 +1,7 @@
---
- name: Restart network service {{ network_name }}
ansible.builtin.systemd_service:
name: "{{ network_name }}-network.service"
state: restarted
daemon_reload: true
ignore_errors: "{{ ansible_check_mode }}"

View File

@@ -7,3 +7,21 @@ argument_specs:
description: Name of the network. Must be unique within a host. description: Name of the network. Must be unique within a host.
type: str type: str
required: true required: true
network_driver:
description: Driver to manage the network
type: str
required: false
default: bridge
choices:
- bridge
- macvlan
network_subnet:
description: Subnet for the network
type: str
required: false
default: ""
network_range:
description: Range to allocate ip addresses from
type: str
required: false
default: ""

View File

@@ -1,7 +1,19 @@
--- ---
- name: "Create container network service {{ network_name }}" - name: Create container network service {{ network_name }}
containers.podman.podman_network: containers.podman.podman_network:
name: "{{ network_name }}" name: "{{ network_name }}"
state: quadlet state: quadlet
quadlet_file_mode: "0644" quadlet_file_mode: "0644"
notify: Reload systemd daemon driver: "{{ network_driver }}"
subnet: "{{ network_subnet if network_subnet | length > 0 else omit }}"
ip_range: "{{ network_range if network_range | length > 0 else omit }}"
quadlet_options: >-
{{
['Options=parent=' ~ ansible_default_ipv4.interface]
if network_driver == 'macvlan' else []
}}
notify: Restart network service {{ network_name }}
- name: Add network to created networks variable
ansible.builtin.set_fact:
network_created_networks: "{{ network_created_networks | default([]) + [network_name] }}"

View File

@@ -0,0 +1,2 @@
---
podman_registry_accounts: []

View File

@@ -2,4 +2,23 @@
argument_specs: argument_specs:
main: main:
description: Installs podman description: Installs podman
options: {} options:
podman_registry_accounts:
description: Dict of accounts for container repositories
type: list
required: false
default: []
elements: dict
options:
registry:
description: Registry server to login to
type: str
required: true
username:
description: Username
type: str
required: true
password:
description: Password / token
type: str
required: true

View File

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

View File

@@ -1,18 +1,21 @@
--- ---
- name: Ensure host distribution is supported
ansible.builtin.import_role:
name: uumas.general.compatcheck
vars:
compatcheck_supported_distributions:
- name: debian
version_min: 13
- name: ubuntu
version_min: 24
tags: podman
- name: Install podman - name: Install podman
ansible.builtin.apt: ansible.builtin.apt:
name: name:
- podman - podman
- aardvark-dns - aardvark-dns
tags: podman
- name: Ensure podman auto update timer is enabled
ansible.builtin.systemd_service:
name: podman-auto-update.timer
state: started
enabled: true
- name: Login to registries
containers.podman.podman_login:
registry: "{{ item.registry }}"
username: "{{ item.username }}"
password: "{{ item.password }}"
authfile: /etc/containers/auth.json
loop: "{{ podman_registry_accounts }}"
no_log: true

View File

@@ -0,0 +1 @@
Installs and configures prometheus

View File

@@ -0,0 +1,4 @@
---
prometheus_additional_networks: []
prometheus_ping_hosts: []

View File

@@ -0,0 +1,35 @@
---
argument_specs:
main:
description: Installs and configures prometheus
options:
prometheus_additional_networks:
description: >-
A list of additional podman networks for the prometheus container (in
addition to prometheus network).
type: list
required: false
default: []
elements: str
prometheus_ping_hosts:
description: List of hosts to ping
type: list
required: false
default: []
elements: dict
options:
name:
description: Hostname to ping
type: str
required: true
type:
description: >-
Type of host. Monitored hosts are pinged to check if they are up.
Wan hosts are pinged to check if prometheus has internet access.
type: str
required: false
default: monitored
choices:
- monitored
- wan

View File

@@ -0,0 +1,28 @@
---
- name: Prometheus
ansible.builtin.import_role:
name: service
vars:
service_name: prometheus
service_container_image: "docker.io/prom/prometheus:latest"
service_container_mounts:
- type: template
source: prometheus.yml.j2
destination: /etc/prometheus/prometheus.yml
- type: volume
source: data
destination: /prometheus
- type: template
source: alerting/node-exporter.yaml.j2
destination: /etc/prometheus/alerting/node-exporter.yaml
- type: template
source: alerting/blackbox-exporter.yaml.j2
destination: /etc/prometheus/alerting/blackbox-exporter.yaml
service_container_additional_networks: "{{ prometheus_additional_networks }}"
service_additional_containers:
- name: blackbox-exporter
image: docker.io/prom/blackbox-exporter:latest
mounts:
- type: template
source: blackbox_exporter.yml.j2
destination: /etc/blackbox_exporter/config.yml

View File

@@ -0,0 +1,97 @@
{% raw %}
groups:
- name: BlackboxExporter
rules:
- alert: BlackboxAllWanProbesFailed
expr: 'sum by (host_type) (probe_success{host_type="wan"})==0'
for: 5s
labels:
severity: critical
annotations:
summary: Lost internet access
descrtiption: Failed to contact any wan probes
- alert: BlackboxProbeFailed
expr: 'probe_success == 0'
for: 0m
labels:
severity: critical
annotations:
summary: Blackbox probe failed (instance {{ $labels.instance }})
description: "Probe failed\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: BlackboxConfigurationReloadFailure
expr: 'blackbox_exporter_config_last_reload_successful != 1'
for: 0m
labels:
severity: warning
annotations:
summary: Blackbox configuration reload failure (instance {{ $labels.instance }})
description: "Blackbox configuration reload failure\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: BlackboxSlowProbe
expr: 'avg_over_time(probe_duration_seconds[1m]) > 1'
for: 1m
labels:
severity: warning
annotations:
summary: Blackbox slow probe (instance {{ $labels.instance }})
description: "Blackbox probe took more than 1s to complete\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: BlackboxProbeHttpFailure
expr: 'probe_http_status_code <= 199 OR probe_http_status_code >= 400'
for: 0m
labels:
severity: critical
annotations:
summary: Blackbox probe HTTP failure (instance {{ $labels.instance }})
description: "HTTP status code is not 200-399\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: BlackboxSslCertificateWillExpireSoon
expr: '3 <= round((last_over_time(probe_ssl_earliest_cert_expiry[10m]) - time()) / 86400, 0.1) < 20'
for: 0m
labels:
severity: warning
annotations:
summary: Blackbox SSL certificate will expire soon (instance {{ $labels.instance }})
description: "SSL certificate expires in less than 20 days\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: BlackboxSslCertificateWillExpireSoon
expr: '0 <= round((last_over_time(probe_ssl_earliest_cert_expiry[10m]) - time()) / 86400, 0.1) < 3'
for: 0m
labels:
severity: critical
annotations:
summary: Blackbox SSL certificate will expire soon (instance {{ $labels.instance }})
description: "SSL certificate expires in less than 3 days\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: BlackboxSslCertificateExpired
expr: 'round((last_over_time(probe_ssl_earliest_cert_expiry[10m]) - time()) / 86400, 0.1) < 0'
for: 0m
labels:
severity: critical
annotations:
summary: Blackbox SSL certificate expired (instance {{ $labels.instance }})
description: "SSL certificate has expired already\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: BlackboxProbeSlowHttp
expr: 'avg_over_time(probe_http_duration_seconds[1m]) > 1'
for: 1m
labels:
severity: warning
annotations:
summary: Blackbox probe slow HTTP (instance {{ $labels.instance }})
description: "HTTP request took more than 1s\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: BlackboxProbeSlowPing
expr: 'avg_over_time(probe_icmp_duration_seconds[1m]) > 1'
for: 1m
labels:
severity: warning
annotations:
summary: Blackbox probe slow ping (instance {{ $labels.instance }})
description: "Blackbox ping took more than 1s\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
{% endraw %}

View File

@@ -0,0 +1,322 @@
{% raw %}
groups:
- name: NodeExporter
rules:
- alert: HostOutOfMemory
expr: '(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < .10)'
for: 2m
labels:
severity: warning
annotations:
summary: Host out of memory (instance {{ $labels.instance }})
description: "Node memory is filling up (< 10% left)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostMemoryUnderMemoryPressure
expr: '(rate(node_vmstat_pgmajfault[5m]) > 1000)'
for: 0m
labels:
severity: warning
annotations:
summary: Host memory under memory pressure (instance {{ $labels.instance }})
description: "The node is under heavy memory pressure. High rate of loading memory pages from disk.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostMemoryIsUnderutilized
expr: 'min_over_time(node_memory_MemFree_bytes[1w]) > node_memory_MemTotal_bytes * .8'
for: 0m
labels:
severity: info
annotations:
summary: Host Memory is underutilized (instance {{ $labels.instance }})
description: "Node memory usage is < 20% for 1 week. Consider reducing memory space. (instance {{ $labels.instance }})\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostUnusualNetworkThroughputIn
expr: '((rate(node_network_receive_bytes_total[5m]) / on(instance, device) node_network_speed_bytes) > .80)'
for: 0m
labels:
severity: warning
annotations:
summary: Host unusual network throughput in (instance {{ $labels.instance }})
description: "Host receive bandwidth is high (>80%).\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostUnusualNetworkThroughputOut
expr: '((rate(node_network_transmit_bytes_total[5m]) / on(instance, device) node_network_speed_bytes) > .80)'
for: 0m
labels:
severity: warning
annotations:
summary: Host unusual network throughput out (instance {{ $labels.instance }})
description: "Host transmit bandwidth is high (>80%)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostUnusualDiskReadRate
expr: '(rate(node_disk_io_time_seconds_total[5m]) > .80)'
for: 0m
labels:
severity: warning
annotations:
summary: Host unusual disk read rate (instance {{ $labels.instance }})
description: "Disk is too busy (IO wait > 80%)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostOutOfDiskSpace
expr: '(node_filesystem_avail_bytes{fstype!~"^(fuse.*|tmpfs|cifs|nfs)"} / node_filesystem_size_bytes < .10 and on (instance, device, mountpoint) node_filesystem_readonly == 0)'
for: 2m
labels:
severity: critical
annotations:
summary: Host out of disk space (instance {{ $labels.instance }})
description: "Disk is almost full (< 10% left)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostDiskMayFillIn24Hours
expr: 'predict_linear(node_filesystem_avail_bytes{fstype!~"^(fuse.*|tmpfs|cifs|nfs)"}[3h], 86400) <= 0 and node_filesystem_avail_bytes > 0'
for: 2m
labels:
severity: warning
annotations:
summary: Host disk may fill in 24 hours (instance {{ $labels.instance }})
description: "Filesystem will likely run out of space within the next 24 hours.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostOutOfInodes
expr: '(node_filesystem_files_free / node_filesystem_files < .10 and ON (instance, device, mountpoint) node_filesystem_readonly == 0)'
for: 2m
labels:
severity: critical
annotations:
summary: Host out of inodes (instance {{ $labels.instance }})
description: "Disk is almost running out of available inodes (< 10% left)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostFilesystemDeviceError
expr: 'node_filesystem_device_error{fstype!~"^(fuse.*|tmpfs|cifs|nfs)"} == 1'
for: 2m
labels:
severity: critical
annotations:
summary: Host filesystem device error (instance {{ $labels.instance }})
description: "Error stat-ing the {{ $labels.mountpoint }} filesystem\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostInodesMayFillIn24Hours
expr: 'predict_linear(node_filesystem_files_free{fstype!~"^(fuse.*|tmpfs|cifs|nfs)"}[1h], 86400) <= 0 and node_filesystem_files_free > 0'
for: 2m
labels:
severity: warning
annotations:
summary: Host inodes may fill in 24 hours (instance {{ $labels.instance }})
description: "Filesystem will likely run out of inodes within the next 24 hours at current write rate\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostUnusualDiskReadLatency
expr: '(rate(node_disk_read_time_seconds_total[1m]) / rate(node_disk_reads_completed_total[1m]) > 0.1 and rate(node_disk_reads_completed_total[1m]) > 0)'
for: 2m
labels:
severity: warning
annotations:
summary: Host unusual disk read latency (instance {{ $labels.instance }})
description: "Disk latency is growing (read operations > 100ms)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostUnusualDiskWriteLatency
expr: '(rate(node_disk_write_time_seconds_total[1m]) / rate(node_disk_writes_completed_total[1m]) > 0.1 and rate(node_disk_writes_completed_total[1m]) > 0)'
for: 2m
labels:
severity: warning
annotations:
summary: Host unusual disk write latency (instance {{ $labels.instance }})
description: "Disk latency is growing (write operations > 100ms)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostHighCpuLoad
expr: '1 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) > .80'
for: 10m
labels:
severity: warning
annotations:
summary: Host high CPU load (instance {{ $labels.instance }})
description: "CPU load is > 80%\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostCpuIsUnderutilized
expr: '(min by (instance) (rate(node_cpu_seconds_total{mode="idle"}[1h]))) > 0.8'
for: 1w
labels:
severity: info
annotations:
summary: Host CPU is underutilized (instance {{ $labels.instance }})
description: "CPU load has been < 20% for 1 week. Consider reducing the number of CPUs.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostCpuStealNoisyNeighbor
expr: 'avg by(instance) (rate(node_cpu_seconds_total{mode="steal"}[5m])) * 100 > 10'
for: 0m
labels:
severity: warning
annotations:
summary: Host CPU steal noisy neighbor (instance {{ $labels.instance }})
description: "CPU steal is > 10%. A noisy neighbor is killing VM performances or a spot instance may be out of credit.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostCpuHighIowait
expr: 'avg by (instance) (rate(node_cpu_seconds_total{mode="iowait"}[5m])) > .10'
for: 0m
labels:
severity: warning
annotations:
summary: Host CPU high iowait (instance {{ $labels.instance }})
description: "CPU iowait > 10%. Your CPU is idling waiting for storage to respond.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostUnusualDiskIo
expr: 'rate(node_disk_io_time_seconds_total[5m]) > 0.8'
for: 5m
labels:
severity: warning
annotations:
summary: Host unusual disk IO (instance {{ $labels.instance }})
description: "Disk usage >80%. Check storage for issues or increase IOPS capabilities. Check storage for issues.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostContextSwitchingHigh
expr: '(rate(node_context_switches_total[15m])/count without(mode,cpu) (node_cpu_seconds_total{mode="idle"})) / (rate(node_context_switches_total[1d])/count without(mode,cpu) (node_cpu_seconds_total{mode="idle"})) > 2'
for: 0m
labels:
severity: warning
annotations:
summary: Host context switching high (instance {{ $labels.instance }})
description: "Context switching is growing on the node (twice the daily average during the last 15m)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostSwapIsFillingUp
expr: '((1 - (node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes)) * 100 > 80)'
for: 2m
labels:
severity: warning
annotations:
summary: Host swap is filling up (instance {{ $labels.instance }})
description: "Swap is filling up (>80%)\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostSystemdServiceCrashed
expr: '(node_systemd_unit_state{state="failed"} == 1)'
for: 0m
labels:
severity: warning
annotations:
summary: Host systemd service crashed (instance {{ $labels.instance }})
description: "systemd service crashed\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostPhysicalComponentTooHot
expr: 'node_hwmon_temp_celsius > node_hwmon_temp_max_celsius'
for: 5m
labels:
severity: warning
annotations:
summary: Host physical component too hot (instance {{ $labels.instance }})
description: "Physical hardware component too hot\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostNodeOvertemperatureAlarm
expr: '((node_hwmon_temp_crit_alarm_celsius == 1) or (node_hwmon_temp_alarm == 1))'
for: 0m
labels:
severity: critical
annotations:
summary: Host node overtemperature alarm (instance {{ $labels.instance }})
description: "Physical node temperature alarm triggered\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostSoftwareRaidInsufficientDrives
expr: '((node_md_disks_required - on(device, instance) node_md_disks{state="active"}) > 0)'
for: 0m
labels:
severity: critical
annotations:
summary: Host software RAID insufficient drives (instance {{ $labels.instance }})
description: "MD RAID array {{ $labels.device }} on {{ $labels.instance }} has insufficient drives remaining.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostSoftwareRaidDiskFailure
expr: '(node_md_disks{state="failed"} > 0)'
for: 2m
labels:
severity: warning
annotations:
summary: Host software RAID disk failure (instance {{ $labels.instance }})
description: "MD RAID array {{ $labels.device }} on {{ $labels.instance }} needs attention.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostKernelVersionDeviations
expr: 'changes(node_uname_info[1h]) > 0'
for: 0m
labels:
severity: info
annotations:
summary: Host kernel version deviations (instance {{ $labels.instance }})
description: "Kernel version for {{ $labels.instance }} has changed.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostOomKillDetected
expr: '(increase(node_vmstat_oom_kill[1m]) > 0)'
for: 0m
labels:
severity: warning
annotations:
summary: Host OOM kill detected (instance {{ $labels.instance }})
description: "OOM kill detected\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostEdacCorrectableErrorsDetected
expr: '(increase(node_edac_correctable_errors_total[1m]) > 0)'
for: 0m
labels:
severity: info
annotations:
summary: Host EDAC Correctable Errors detected (instance {{ $labels.instance }})
description: "Host {{ $labels.instance }} has had {{ printf \"%.0f\" $value }} correctable memory errors reported by EDAC in the last 5 minutes.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostEdacUncorrectableErrorsDetected
expr: '(node_edac_uncorrectable_errors_total > 0)'
for: 0m
labels:
severity: warning
annotations:
summary: Host EDAC Uncorrectable Errors detected (instance {{ $labels.instance }})
description: "Host {{ $labels.instance }} has had {{ printf \"%.0f\" $value }} uncorrectable memory errors reported by EDAC in the last 5 minutes.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostNetworkReceiveErrors
expr: '(rate(node_network_receive_errs_total[2m]) / rate(node_network_receive_packets_total[2m]) > 0.01)'
for: 2m
labels:
severity: warning
annotations:
summary: Host Network Receive Errors (instance {{ $labels.instance }})
description: "Host {{ $labels.instance }} interface {{ $labels.device }} has encountered {{ printf \"%.0f\" $value }} receive errors in the last two minutes.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostNetworkTransmitErrors
expr: '(rate(node_network_transmit_errs_total[2m]) / rate(node_network_transmit_packets_total[2m]) > 0.01)'
for: 2m
labels:
severity: warning
annotations:
summary: Host Network Transmit Errors (instance {{ $labels.instance }})
description: "Host {{ $labels.instance }} interface {{ $labels.device }} has encountered {{ printf \"%.0f\" $value }} transmit errors in the last two minutes.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostNetworkBondDegraded
expr: '((node_bonding_active - node_bonding_slaves) != 0)'
for: 2m
labels:
severity: warning
annotations:
summary: Host Network Bond Degraded (instance {{ $labels.instance }})
description: "Bond \"{{ $labels.device }}\" degraded on \"{{ $labels.instance }}\".\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostConntrackLimit
expr: '(node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8)'
for: 5m
labels:
severity: warning
annotations:
summary: Host conntrack limit (instance {{ $labels.instance }})
description: "The number of conntrack is approaching limit\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostClockSkew
expr: '((node_timex_offset_seconds > 0.05 and deriv(node_timex_offset_seconds[5m]) >= 0) or (node_timex_offset_seconds < -0.05 and deriv(node_timex_offset_seconds[5m]) <= 0))'
for: 10m
labels:
severity: warning
annotations:
summary: Host clock skew (instance {{ $labels.instance }})
description: "Clock skew detected. Clock is out of sync. Ensure NTP is configured correctly on this host.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
- alert: HostClockNotSynchronising
expr: '(min_over_time(node_timex_sync_status[1m]) == 0 and node_timex_maxerror_seconds >= 16)'
for: 2m
labels:
severity: warning
annotations:
summary: Host clock not synchronising (instance {{ $labels.instance }})
description: "Clock not synchronising. Ensure NTP is configured on this host.\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
{% endraw %}

View File

@@ -0,0 +1,5 @@
---
modules:
icmp:
prober: icmp
timeout: 5s

View File

@@ -0,0 +1,55 @@
---
# {{ ansible_managed }}
global:
scrape_interval: 10s
evaluation_interval: 10s
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
- "/etc/prometheus/recording/*.yaml"
- "/etc/prometheus/alerting/*.yaml"
scrape_configs:
- job_name: prometheus
static_configs:
- targets:
- localhost:9090
- job_name: blackbox
static_configs:
- targets:
- blackbox-exporter:9115
- job_name: node
static_configs:
- targets:
- host.containers.internal:9100
{% if prometheus_ping_hosts | length > 0 %}
- job_name: "icmp"
metrics_path: "/probe"
params:
module: ["icmp"]
static_configs:
- targets:
{% for host in prometheus_ping_hosts %}
- "{{ host.name }}::{{ host.type | default('monitored') }}"
{% endfor %}
relabel_configs:
- source_labels:
- __address__
regex: '(.+)::(.+)'
target_label: __param_target
replacement: '${1}'
- source_labels:
- __address__
regex: '(.+)::(.+)'
target_label: host_type
replacement: '${2}'
- source_labels:
- __param_target
target_label: instance
- target_label: __address__
replacement: blackbox_exporter:9115
{%- endif %}

View File

@@ -1,7 +1,14 @@
--- ---
service_domains: [] service_container_command: []
service_vhost_locations: [] service_container_entrypoint: ""
service_domains: []
service_container_http_port: 0
service_vhost_locations: []
service_proxy_pass_host_header: true
service_proxy_auth_type: none
service_container_ip: ""
service_container_additional_networks: [] service_container_additional_networks: []
service_container_user: "" service_container_user: ""
service_container_publish_ports: [] service_container_publish_ports: []
@@ -10,6 +17,11 @@ service_container_secrets: []
service_container_env: {} service_container_env: {}
service_database_type: none service_database_type: none
service_database_additional_networks: []
service_database_secret_type: mount
service_database_secret_target: "{{ service_database_type }}"
service_postgres_image: docker.io/library/postgres
service_redis: false
service_additional_containers: [] service_additional_containers: []

View File

@@ -1,7 +1,14 @@
--- ---
- name: "Restart socat socket for {{ service_name }}" - name: Restart socat socket for {{ service_name }}
ansible.builtin.systemd_service: ansible.builtin.systemd_service:
name: "{{ service_name }}-socat.socket" name: "{{ service_name }}-socat.socket"
state: restarted state: restarted
daemon_reload: true daemon_reload: true
ignore_errors: '{{ ansible_check_mode }}' ignore_errors: '{{ ansible_check_mode }}'
- name: Restart socat socket for {{ service_name ~ '-oauth2-proxy' }}
ansible.builtin.systemd_service:
name: "{{ service_name }}-oauth2-proxy-socat.socket"
state: restarted
daemon_reload: true
ignore_errors: '{{ ansible_check_mode }}'

View File

@@ -8,6 +8,18 @@ argument_specs:
type: str type: str
required: true required: true
service_container_command:
description: Command to start the service container with.
type: list
required: false
default: []
elements: str
service_container_entrypoint:
description: Entrypoint to use in the service container
type: str
required: false
default: ""
service_domains: service_domains:
description: A list of domains which should be proxied to the main service container description: A list of domains which should be proxied to the main service container
type: list type: list
@@ -20,22 +32,60 @@ argument_specs:
- Required if service_domains is not empty. - Required if service_domains is not empty.
type: int type: int
required: false required: false
default: 0
service_proxy_pass_host_header:
description: Passed to vhost role as vhost_proxy_pass_header
type: bool
required: false
default: true
service_proxy_auth_type:
description: >-
Set to oauth2-proxy to use OAuth2 Proxy for vhost authentication.
type: str
required: false
default: none
choices:
- none
- oauth2-proxy
service_vhost_locations: service_vhost_locations:
description: Passed to vhost role as vhost_locations description: Passed to vhost role as vhost_locations
type: list
required: false required: false
default: [] default: []
service_oauth2_proxy_issuer_url:
description: >-
OpenID Connect issuer URL. Required if service_proxy_auth_type is oauth2-proxy.
type: str
required: false
oauth2_proxy_client_id:
description: OAuth client ID. Required if service_proxy_auth_type is oauth2-proxy.
type: str
required: false
oauth2_proxy_client_secret:
description: OAuth client secret. Required if service_proxy_auth_type is oauth2-proxy.
type: str
required: false
service_container_image: service_container_image:
description: "The image to run in the service container(s), in FQIN format (registry/imagename:tag)." description: "The image to run in the service container(s), in FQIN format (registry/imagename:tag)."
type: str type: str
required: true required: true
service_container_user: service_container_user:
description: The UID to run as inside the container description: The UID to run as inside the container
type: str type: str
required: false required: false
default: "" default: ""
service_container_ip:
description: Static ip for the container in it's network
type: str
required: false
default: ""
service_container_additional_networks: service_container_additional_networks:
description: A list of additional podman networks for the service container (in addition to service name network). description: >-
A list of additional podman networks for the service container (in
addition to service name network).
type: list type: list
required: false required: false
default: [] default: []
@@ -54,7 +104,7 @@ argument_specs:
elements: dict elements: dict
options: options:
type: type:
description: Type of volume description: Type of mount
type: str type: str
required: true required: true
choices: choices:
@@ -89,12 +139,41 @@ argument_specs:
type: str type: str
required: false required: false
default: "" default: ""
mode:
description: Templated file permissions
type: str
required: false
default: "0644"
volume_device:
description: >-
The path of a device which is mounted for the volume.
Only applicable if mount type is volume.
type: str
required: false
default: ""
volume_type:
description: >-
The filesystem type of device as used by the mount commands -t option
Only applicable if mount type is volume.
type: str
required: false
default: ""
volume_mount_options:
description: >-
The mount options to use for a filesystem as used by the mount command -o option
Only applicable if mount type is volume.
type: list
elements: str
required: false
default: []
service_container_secrets: service_container_secrets:
description: description:
- A list of secrets available to the service container in /run/secrets/<service name>-<secret name>
- > - >
A dict of secrets and their values (including autogenerated values) is available as `service_podman_secrets` for use A list of secrets available to the service container as file or environment variable
in tepmlates or environment variables. This should only be used if the container doesn't support reading the secret from file - >
A dict of secrets and their values (including autogenerated values) is available as
`service_podman_secrets` for use in templates. This should only be used if the
container doesn't support reading the secret from file or environment variable.
type: list type: list
required: false required: false
default: [] default: []
@@ -106,8 +185,12 @@ argument_specs:
required: true required: true
value: value:
description: description:
- Value of the secret. Defaults to a 128-character random string containing alphanumeric characters. - >
- If the value is not explicitly set, it will not be changed if the secret already exists. Value of the secret. Defaults to a 128-character random string containing
alphanumeric characters.
- >
If the value is not explicitly set, it will not be changed if the secret
already exists.
type: str type: str
required: false required: false
length: length:
@@ -115,6 +198,21 @@ argument_specs:
type: int type: int
required: false required: false
default: 128 default: 128
type:
description: How the secret will be exposed to the container
type: str
choices:
- mount
- env
default: mount
target:
description: >
Where the secret will be available inside the container. If type is mount, this is
either a full file path or a filename under /run/secrets. If type is env, this is
the name of the environment variable. Defaults to secret name.
type: str
required: false
service_container_env: service_container_env:
description: A dict of environment variables for the service container(s) description: A dict of environment variables for the service container(s)
type: dict type: dict
@@ -125,33 +223,67 @@ argument_specs:
description: description:
- Database type to set up. - Database type to set up.
- > - >
It will be run in a docker container accessible to the service at It will be run in a container accessible to the service at
host {{ service_name }}-{{ service_database_type }} on the default port. host {{ service_name }}-{{ service_database_type }} on the default port.
- The database user will be {{ service_name }} - The database user will be {{ service_name }}
- The password will be accessible as secret at /run/secrets/{{ service_name }}-{{ service_database_type }} - The password will be accessible as secret at /run/secrets/{{ service_database_type }}
- > - >
The password will also be available as the The password will also be available as the
service_podman_secrets['{{ service_name }}-{{ service_database_type }}'] variable. service_podman_secrets['{{ service_name }}-{{ service_database_type }}'] variable.
type: str type: str
required: false
choices: choices:
- postgres - postgres
- none - none
required: false
default: none default: none
service_database_additional_networks:
description: >-
A list of additional podman networks for the database container (in
addition to service name network).
type: list
required: false
default: []
elements: str
service_database_secret_type:
description: Secret type for database secret for service container
type: str
choices:
- mount
- env
required: false
default: mount
service_database_secret_target:
description: Secret target for database secret for service container.
type: str
required: false
default: "{{ service_database_type }}"
service_postgres_image:
description: Postgresql image to use.
type: str
required: false
default: docker.io/library/postgres
service_postgres_tag: service_postgres_tag:
description: description:
- Postgresql version to use. - Postgresql version to use.
- Can be debian (n) or alpine-based (n-alpine), where n can be major version like 14 or minor like 14.13. - Can be debian (n) or alpine-based (n-alpine), where n can be major version like 14 or minor like 14.13.
- Required if service_database_type is postgres, does nothing otherwise - Required if service_database_type is postgres, does nothing otherwise
- If a custom postgres image is specified, see that image documentation for supported tags.
type: str type: str
required: false required: false
service_redis:
description: >-
Whether to install redis in a container accessible to the service at host
{{ service_name }}-redis.
type: bool
required: false
default: false
service_additional_containers: service_additional_containers:
description: description:
- List of additional containers for the sercice. - List of additional containers for the service.
- > - >
Will inherit most options from main service container, except for publish_ports. Will inherit most options from main service container. All options can be overridden
All options can be overridden per-container. per-container.
type: list type: list
required: false required: false
default: [] default: []
@@ -171,6 +303,17 @@ argument_specs:
type: str type: str
required: false required: false
default: "{{ service_container_image }}" default: "{{ service_container_image }}"
command:
description: Command to start the container with.
type: list
required: false
default: "[]"
elements: str
entrypoint:
description: Entrypoint to use in the container
type: str
required: false
default: ""
mounts: mounts:
description: List of bind mounts or volumes to be mounted inside the main service container. description: List of bind mounts or volumes to be mounted inside the main service container.
type: list type: list
@@ -179,7 +322,7 @@ argument_specs:
elements: dict elements: dict
options: options:
type: type:
description: Type of volume description: Type of mount
type: str type: str
required: true required: true
choices: choices:
@@ -204,6 +347,28 @@ argument_specs:
- Defaults to false for volume and bind, true for template - Defaults to false for volume and bind, true for template
type: bool type: bool
required: false required: false
volume_device:
description: >-
The path of a device which is mounted for the volume.
Only applicable if mount type is volume.
type: str
required: false
default: ""
volume_type:
description: >-
The filesystem type of device as used by the mount commands -t option
Only applicable if mount type is volume.
type: str
required: false
default: ""
volume_mount_options:
description: >-
The mount options to use for a filesystem as used by the mount command -o option
Only applicable if mount type is volume.
type: list
elements: str
required: false
default: []
publish_ports: publish_ports:
description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)" description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)"
type: list type: list
@@ -215,6 +380,53 @@ argument_specs:
type: dict type: dict
required: false required: false
default: {} default: {}
secrets:
description:
- >
A list of secrets available to the service container as file or environment
variable
- >
A dict of secrets and their values (including autogenerated values) is available as
`service_podman_secrets` for use in templates. This should only be used if the
container doesn't support reading the secret from file or environment variable.
type: list
required: false
default: []
elements: dict
options:
name:
description: Name of the secret
type: str
required: true
value:
description:
- >
Value of the secret. Defaults to a 128-character random string containing
alphanumeric characters.
- >
If the value is not explicitly set, it will not be changed if the secret
already exists.
type: str
required: false
length:
description: Length of randomly generated string
type: int
required: false
default: 128
type:
description: How the secret will be exposed to the container
type: str
choices:
- mount
- env
default: mount
target:
description: >
Where the secret will be available inside the container. If type is mount, this is
either a full file path or a filename under /run/secrets. If type is env, this is
the name of the environment variable. Defaults to secret name.
type: str
required: false
service_requires: service_requires:
description: List of systemd units this service container depends on. description: List of systemd units this service container depends on.

View File

@@ -0,0 +1,23 @@
---
- name: Additional container {{ container ~ ' for ' ~ service_name }}
ansible.builtin.include_role:
name: container
vars:
container_name: "{{ _service_additional_container.name }}"
container_image: "{{ _service_additional_container.image | default(service_container_image) }}"
container_command: "{{ _service_additional_container.command | default([]) }}"
container_entrypoint: "{{ _service_additional_container.entrypoint | default('') }}"
container_user: "{{ service_container_user }}"
container_mounts: "{{ _service_additional_container_mounts }}"
container_publish_ports: "{{ _service_additional_container.publish_ports | default([]) }}"
container_networks: "{{ _service_container_networks }}"
container_ip: "{{ _service_additional_container_ip }}"
container_secrets: "{{ _service_additional_container.secrets | default(_service_container_secrets) }}"
container_env: "{{ _service_additional_container.env | default(service_container_env) }}"
container_requires: "{{ _service_container_requires }}"
container_wants: "{{ service_wants }}"
container_auto_update: "{{ service_auto_update }}"
loop: "{{ _service_additional_containers }}"
loop_control:
loop_var: _service_additional_container
index_var: _service_additional_container_index

View File

@@ -4,17 +4,19 @@
name: container name: container
vars: vars:
container_name: "{{ service_name }}-{{ service_database_type }}" # This doesn't use _service_database_name to allow container role handlers to work container_name: "{{ service_name }}-{{ service_database_type }}" # This doesn't use _service_database_name to allow container role handlers to work
container_image: "docker.io/library/postgres:{{ service_postgres_tag }}" container_image: "{{ service_postgres_image }}:{{ service_postgres_tag }}"
container_mounts: container_mounts:
- type: volume - type: volume
source: "{{ _service_database_name }}" source: "{{ _service_database_name }}"
destination: /var/lib/postgresql/data destination: /var/lib/postgresql/data
container_networks: container_networks: "{{ _service_database_networks }}"
- "{{ service_name }}" container_ip: >-
{{ service_container_ip | ansible.utils.ipmath(1) if _service_static_ip else '' }}
container_secrets: container_secrets:
- name: "{{ _service_database_name }}" - name: "{{ _service_database_name }}"
target: "{{ service_database_type }}"
container_env: container_env:
POSTGRES_USER: "{{ service_name | replace('-', '_') }}" POSTGRES_USER: "{{ service_name | replace('-', '_') }}"
POSTGRES_PASSWORD_FILE: "/run/secrets/{{ _service_database_name }}" POSTGRES_PASSWORD_FILE: "/run/secrets/{{ service_database_type }}"
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
container_auto_update: "{{ service_auto_update }}" container_auto_update: "{{ service_auto_update }}"

View File

@@ -2,21 +2,25 @@
- name: Validate inputs - name: Validate inputs
ansible.builtin.import_tasks: validation.yaml ansible.builtin.import_tasks: validation.yaml
- name: Initialize variables - name: Database for {{ service_name }}
ansible.builtin.set_fact:
_service_container_mounts: []
- name: Databse for {{ service_name }}
ansible.builtin.include_tasks: database.yaml ansible.builtin.include_tasks: database.yaml
when: _service_setup_database when: _service_setup_database
- name: Redis for {{ service_name }}
ansible.builtin.include_tasks: redis.yaml
when: service_redis
- name: Secrets for {{ service_name }} - name: Secrets for {{ service_name }}
ansible.builtin.include_tasks: secrets.yaml ansible.builtin.include_tasks: secrets.yaml
when: _service_container_secrets | length > 0 when: _service_container_secrets | length > 0
- name: Mounts for {{ service_name }} - name: Template mounts for {{ service_name }}
ansible.builtin.include_tasks: mounts.yaml ansible.builtin.include_tasks: templates.yaml
when: service_container_mounts | length > 0 when: _service_template_mounts | length > 0
- name: Additional containers for {{ service_name }}
ansible.builtin.include_tasks: additional.yaml
when: _service_additional_containers | length > 0
- name: Main container for {{ service_name }} - name: Main container for {{ service_name }}
ansible.builtin.import_role: ansible.builtin.import_role:
@@ -24,16 +28,28 @@
vars: vars:
container_name: "{{ service_name }}" container_name: "{{ service_name }}"
container_image: "{{ service_container_image }}" container_image: "{{ service_container_image }}"
container_command: "{{ service_container_command }}"
container_entrypoint: "{{ service_container_entrypoint }}"
container_user: "{{ service_container_user }}" container_user: "{{ service_container_user }}"
container_mounts: "{{ _service_container_mounts }}" container_mounts: "{{ _service_container_mounts }}"
container_publish_ports: "{{ service_container_publish_ports }}" container_publish_ports: "{{ service_container_publish_ports }}"
container_networks: "{{ [service_name] + service_container_additional_networks }}" container_networks: "{{ _service_container_networks }}"
container_ip: "{{ service_container_ip }}"
container_secrets: "{{ _service_container_secrets }}" container_secrets: "{{ _service_container_secrets }}"
container_env: "{{ service_container_env }}" container_env: "{{ service_container_env }}"
container_requires: "{{ _service_container_requires }}" container_requires: "{{ _service_container_requires }}"
container_wants: "{{ _service_container_wants }}" container_wants: "{{ _service_container_wants }}"
container_auto_update: "{{ service_auto_update }}" container_auto_update: "{{ service_auto_update }}"
- name: Socat for {{ service_name }}
ansible.builtin.include_tasks: socat.yaml
when: service_container_http_port > 0
vars:
socat_service_name: "{{ service_name }}"
socat_target_http_port: "{{ service_container_http_port }}"
socat_container_ip: >-
{{ service_container_ip | ansible.utils.ipmath(3) if _service_static_ip else '' }}
- name: Reverse proxy for {{ service_name }} - name: Reverse proxy for {{ service_name }}
ansible.builtin.include_tasks: proxy.yaml ansible.builtin.include_tasks: proxy.yaml
when: service_domains | length > 0 when: service_domains | length > 0

View File

@@ -1,32 +0,0 @@
---
- name: Set container named mounts
ansible.builtin.set_fact:
_service_container_mounts: >
{{ _service_container_mounts +
[mount | combine({'source': service_name + '-' + mount.source})] }}
when: mount.type == 'volume'
- name: Set container named mounts
ansible.builtin.set_fact:
_service_container_mounts: "{{ _service_container_mounts + [mount] }}"
when: mount.type == 'bind'
- name: Template mounts
when: mount.type == 'template'
block:
- name: Set template host path
ansible.builtin.set_fact:
_service_template_host_path: "{{ _service_host_directory }}/mounts/{{ (mount.source | split('.'))[0:-1] | join('.') }}" # Strip .j2 extension
- name: Template files for template mounts
ansible.builtin.template:
src: "{{ mount.source }}"
dest: "{{ _service_template_host_path }}"
mode: "0644"
notify: "Restart container service {{ service_name }}"
- name: Set container template mounts
ansible.builtin.set_fact:
_service_container_mounts: >
{{ _service_container_mounts +
[{'readonly': true} | combine(mount) | combine({'type': 'bind', 'source': _service_template_host_path})] }}

View File

@@ -1,21 +0,0 @@
---
- name: Create template mount directories under /srv
when: _service_template_mounts | length > 0
block:
- name: Create directory {{ _service_host_directory }}
ansible.builtin.file:
path: "{{ _service_host_directory }}"
state: directory
mode: "0755"
- name: Create directory {{ _service_host_directory + '/mounts' }}
ansible.builtin.file:
path: "{{ _service_host_directory }}/mounts"
state: directory
mode: "0700"
- name: Set mount definitions for {{ service_name }}
ansible.builtin.include_tasks: mount.yaml
loop: "{{ service_container_mounts }}"
loop_control:
loop_var: mount

View File

@@ -0,0 +1,37 @@
---
- name: OAuth2 Proxy container for {{ service_name }}
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ service_name }}-oauth2-proxy"
container_image: "quay.io/oauth2-proxy/oauth2-proxy:latest-alpine"
container_command:
- --client-secret-file
- /run/secrets/client-secret
- --cookie-secret-file
- /run/secrets/cookie-secret
container_networks:
- "{{ service_name }}-oauth2-proxy"
container_secrets:
- name: "{{ service_name }}-oauth2-proxy-cookie-secret"
length: 32
target: cookie-secret
- name: "{{ service_name }}-oauth2-proxy-client-secret"
value: "{{ service_oauth2_proxy_client_secret }}"
target: client-secret
container_env:
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
OAUTH2_PROXY_PROVIDER: oidc
OAUTH2_PROXY_OIDC_ISSUER_URL: "{{ service_oauth2_proxy_issuer_url }}"
OAUTH2_PROXY_CLIENT_ID: "{{ service_oauth2_proxy_client_id }}"
OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"
OAUTH2_PROXY_EMAIL_DOMAINS: "*"
container_auto_update: "{{ service_auto_update }}"
- name: Socat for OAuth2 Proxy for {{ service_name }}
ansible.builtin.import_tasks: socat.yaml
vars:
socat_service_name: "{{ service_name }}-oauth2-proxy"
socat_target_http_port: 4180
socat_container_ip: ""

View File

@@ -1,28 +1,7 @@
--- ---
- name: Socat socket for {{ service_name }} - name: OAuth2 proxy for {{ service_name }}
ansible.builtin.template: ansible.builtin.include_tasks: oauth2_proxy.yaml
src: socat.socket.j2 when: _service_oauth2_proxy
dest: /etc/systemd/system/{{ service_name }}-socat.socket
mode: "0644"
notify: Restart socat socket for {{ service_name }}
- name: Socat container for {{ service_name }}
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ service_name }}-socat"
container_image: "docker.io/alpine/socat:latest"
container_command:
- "ACCEPT-FD:3,fork"
- "TCP:{{ service_name }}:{{ service_container_http_port }}"
container_user: nobody
container_networks:
- "{{ service_name }}"
container_requires:
- "{{ service_name }}-socat.socket"
- "{{ service_name }}.service"
container_auto_start: false
container_auto_update: "{{ service_auto_update }}"
- name: Reverse proxy for {{ service_name }} - name: Reverse proxy for {{ service_name }}
ansible.builtin.import_role: ansible.builtin.import_role:
@@ -33,4 +12,8 @@
vhost_domains: "{{ service_domains }}" vhost_domains: "{{ service_domains }}"
vhost_proxy_target_netproto: unix vhost_proxy_target_netproto: unix
vhost_proxy_target_socket: "/run/{{ service_name }}-socat.sock" vhost_proxy_target_socket: "/run/{{ service_name }}-socat.sock"
vhost_locations: "{{ service_vhost_locations }}" vhost_proxy_headers: "{{ _service_proxy_headers }}"
vhost_proxy_auth_socket: "{{ _service_oauth2_socket }}"
vhost_proxy_auth_uri: /oauth2/auth
vhost_proxy_auth_unauthorized_redir: "/oauth2/sign_in?rd={scheme}://{host}{uri}"
vhost_locations: "{{ _service_vhost_locations }}"

View File

@@ -0,0 +1,12 @@
---
- name: Redis container for {{ service_name }}
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ service_name }}-redis"
container_image: docker.io/valkey/valkey:alpine
container_networks:
- "{{ service_name }}"
container_ip: >-
{{ service_container_ip | ansible.utils.ipmath(2) if _service_static_ip else '' }}
container_auto_update: "{{ service_auto_update }}"

View File

@@ -0,0 +1,26 @@
---
- name: Socat socket for {{ socat_service_name }}
ansible.builtin.template:
src: socat.socket.j2
dest: /etc/systemd/system/{{ socat_service_name }}-socat.socket
mode: "0644"
notify: Restart socat socket for {{ socat_service_name }}
- name: Socat container for {{ socat_service_name }}
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ socat_service_name }}-socat"
container_image: "docker.io/alpine/socat:latest"
container_command:
- "ACCEPT-FD:3,fork"
- "TCP:{{ socat_service_name }}:{{ socat_target_http_port }}"
container_user: nobody
container_networks:
- "{{ socat_service_name }}"
container_ip: "{{ socat_container_ip }}"
container_requires:
- "{{ socat_service_name }}-socat.socket"
- "{{ socat_service_name }}.service"
container_auto_start: false
container_auto_update: "{{ service_auto_update }}"

View File

@@ -0,0 +1,27 @@
---
- name: Create directory {{ _service_host_directory }}
ansible.builtin.file:
path: "{{ _service_host_directory }}"
state: directory
mode: "0755"
- name: Create directory {{ _service_host_directory + '/mounts' }}
ansible.builtin.file:
path: "{{ _service_host_directory }}/mounts"
state: directory
mode: "0700"
- name: Create service template mount directories
ansible.builtin.file:
path: "{{ _service_host_directory }}/mounts/{{ item }}"
state: directory
mode: "0700"
loop: "{{ _service_all_template_mount_directories }}"
- name: Template files for template mounts
ansible.builtin.template:
src: "{{ item[0].source }}"
dest: "{{ item[1] }}"
mode: "{{ item[0].mode | default('0644') }}"
notify: Restart container service {{ service_name }}
loop: "{{ _service_all_template_mounts | zip(_service_all_template_mount_host_files) }}"

View File

@@ -1,4 +1,9 @@
--- ---
- name: Fail if service_name is empty
ansible.builtin.fail:
msg: service_name must not be empty
when: service_name | length == 0
- name: Fail if service_container_user is not string - name: Fail if service_container_user is not string
ansible.builtin.fail: ansible.builtin.fail:
msg: "service_container_user must be a string, not int." msg: "service_container_user must be a string, not int."

View File

@@ -1,6 +1,6 @@
# {{ ansible_managed }} # {{ ansible_managed }}
[Unit] [Unit]
Description={{ service_name }} socat socket Description={{ socat_service_name }} socat socket
[Socket] [Socket]
ListenStream=/run/{{ service_name }}-socat.sock ListenStream=/run/{{ socat_service_name }}-socat.sock

View File

@@ -1,21 +0,0 @@
---
_service_template_mounts: "{{ service_container_mounts | selectattr('type', '==', 'template') | list }}"
_service_host_directory: "/srv/{{ service_name }}"
_service_setup_database: "{{ service_database_type != 'none' }}"
_service_database_name: "{{ service_name }}-{{ service_database_type }}"
_service_container_secrets: >
{{
service_container_secrets
| zip(service_container_secrets
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('community.general.dict_kv', 'name')
)
| map('combine')
+ ([{'name': _service_database_name }] if _service_setup_database else [])
}}
_service_container_requires: "{{ service_requires + ([_service_database_name + '.service'] if _service_setup_database else []) }}"
_service_container_wants: "{{ service_wants + ([service_name + '-socat.socket'] if service_domains | length > 0 else []) }}"

View File

@@ -0,0 +1,62 @@
---
_service_additional_containers: >-
{{
service_additional_containers
| zip(
service_additional_containers
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('community.general.dict_kv', 'name')
)
| map('combine')
}}
_service_additional_container_ip: >-
{{
service_container_ip |
ansible.utils.ipmath(20 + _service_additional_container_index)
if _service_static_ip else ''
}}
_service_additional_volume_mounts: "{{ _service_additional_container.mounts | selectattr('type', '==', 'volume') }}"
_service_additional_template_mounts: "{{ _service_additional_container.mounts | selectattr('type', '==', 'template') }}"
_service_additional_host_directory: "/srv/{{ service_name }}"
_service_additional_container_volume_mounts: >-
{{
_service_additional_volume_mounts |
zip(
_service_additional_volume_mounts |
map(attribute='source') |
map('regex_replace', '^', service_name ~ '-') |
map('community.general.dict_kv', 'source')
) |
map('combine')
}}
_service_additional_container_bind_mounts: "{{ _service_additional_container.mounts | selectattr('type', '==', 'bind') }}"
_service_additional_container_template_mounts: >-
{{
([{'readonly': true}] * _service_additional_template_mounts | length) |
zip(
_service_additional_template_mounts |
community.general.remove_keys(['mode']),
_service_additional_template_mounts |
map(attribute='source') |
map('regex_replace', '\.j2$', '') |
map('regex_replace', '^', _service_host_directory ~ '/mounts/') |
map('community.general.dict_kv', 'source'),
([{'type': 'bind'}] * _service_additional_template_mounts | length)
) |
map('combine')
}}
_service_additional_container_mounts: >-
{{
_service_additional_container_volume_mounts +
_service_additional_container_bind_mounts +
_service_additional_container_template_mounts
if _service_additional_container.mounts is defined
else
_service_container_mounts
}}

View File

@@ -0,0 +1,8 @@
---
_service_setup_database: "{{ service_database_type != 'none' }}"
_service_database_name: "{{ service_name }}-{{ service_database_type }}"
_service_database_networks: >-
{{
[service_name] +
service_database_additional_networks
}}

View File

@@ -0,0 +1,19 @@
---
_service_container_networks: "{{ [service_name] + service_container_additional_networks }}"
_service_static_ip: "{{ service_container_ip | length > 0 }}"
_service_container_requires: >-
{{
service_requires
+ ([_service_database_name + '.service'] if _service_setup_database else [])
+ ([service_name + '-redis.service'] if service_redis else [])
}}
_service_container_wants: >-
{{
service_wants
+ ([service_name + '-socat.socket'] if service_container_http_port > 0 else [])
+ ([service_name + '-oauth2-proxy-socat.socket'] if _service_oauth2_proxy else [])
+ _service_additional_containers
| map(attribute='name')
| map('regex_replace', '$', '.service')
}}

View File

@@ -0,0 +1,69 @@
---
_service_volume_mounts: "{{ service_container_mounts | selectattr('type', '==', 'volume') }}"
_service_template_mounts: "{{ service_container_mounts | selectattr('type', '==', 'template') }}"
_service_host_directory: "/srv/{{ service_name }}"
_service_container_volume_mounts: >-
{{
_service_volume_mounts |
zip(
_service_volume_mounts |
map(attribute='source') |
map('regex_replace', '^', service_name ~ '-') |
map('community.general.dict_kv', 'source')
) |
map('combine')
}}
_service_container_bind_mounts: "{{ service_container_mounts | selectattr('type', '==', 'bind') }}"
_service_container_template_mounts: >-
{{
([{'readonly': true}] * _service_template_mounts | length) |
zip(
_service_template_mounts |
community.general.remove_keys(['mode']),
_service_template_mounts |
map(attribute='source') |
map('regex_replace', '\.j2$', '') |
map('regex_replace', '^', _service_host_directory ~ '/mounts/') |
map('community.general.dict_kv', 'source'),
([{'type': 'bind'}] * _service_template_mounts | length)
) |
map('combine')
}}
_service_container_mounts: >-
{{
_service_container_volume_mounts +
_service_container_bind_mounts +
_service_container_template_mounts
}}
_service_all_template_mounts: >-
{{
(
_service_template_mounts +
(
_service_additional_containers |
map(attribute='mounts', default=[]) |
flatten
)
) |
selectattr('type', '==', 'template') |
unique
}}
_service_all_template_mount_directories: >-
{{
_service_all_template_mounts |
map(attribute='source') |
map('dirname') |
unique |
select('!=', '')
}}
_service_all_template_mount_host_files: >-
{{
_service_all_template_mounts |
map(attribute='source') |
map('regex_replace', '\.j2$', '') |
map('regex_replace', '^', _service_host_directory ~ '/mounts/')
}}

View File

@@ -0,0 +1,18 @@
---
_service_replacement_host_header:
Host: "{{ service_name }}:{{ service_container_http_port }}"
_service_proxy_headers: "{{ _service_replacement_host_header if not service_proxy_pass_host_header else {} }}"
_service_oauth2_proxy: "{{ service_proxy_auth_type == 'oauth2-proxy' }}"
_service_oauth2_socket: >-
{{ '/run/' ~ service_name ~ '-oauth2-proxy-socat.sock' if _service_oauth2_proxy else '' }}
_service_oauth2_proxy_location:
path: /oauth2/*
proxy_target_socket: "{{ _service_oauth2_socket }}"
proxy_auth_socket: ""
_service_vhost_locations: >-
{{
service_vhost_locations +
([_service_oauth2_proxy_location] if _service_oauth2_proxy else [])
}}

View File

@@ -0,0 +1,22 @@
---
_service_container_secrets: >-
{{
service_container_secrets
| map(attribute='name')
| map('community.general.dict_kv', 'target')
| zip(
service_container_secrets,
service_container_secrets
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('community.general.dict_kv', 'name')
)
| map('combine')
+ (
[{
'name': _service_database_name,
'type': service_database_secret_type,
'target': service_database_secret_target
}] if _service_setup_database else []
)
}}

View File

@@ -43,17 +43,7 @@
service_container_http_port: 8008 service_container_http_port: 8008
service_domains: "{{ [synapse_external_domain] }}" service_domains: "{{ [synapse_external_domain] }}"
service_vhost_locations: service_vhost_locations:
- path: /_matrix/client/*/login - path: ^/_matrix/client/.*/(login|logout|refresh).*$
proxy_target_socket: /run/matrix-authentication-service-socat.sock
- path: /_matrix/client/*/logout
proxy_target_socket: /run/matrix-authentication-service-socat.sock
- path: /_matrix/client/*/refresh
proxy_target_socket: /run/matrix-authentication-service-socat.sock
- path: /_matrix/client/*/login/*
proxy_target_socket: /run/matrix-authentication-service-socat.sock
- path: /_matrix/client/*/logout/*
proxy_target_socket: /run/matrix-authentication-service-socat.sock
- path: /_matrix/client/*/refresh/*
proxy_target_socket: /run/matrix-authentication-service-socat.sock proxy_target_socket: /run/matrix-authentication-service-socat.sock
service_wants: service_wants:
- matrix-authentication-service.service - matrix-authentication-service.service
@@ -82,3 +72,21 @@
matrix_authentication_service_upstream_oauth2_scope: "{{ synapse_oidc_provider_scopes | join(' ') }}" matrix_authentication_service_upstream_oauth2_scope: "{{ synapse_oidc_provider_scopes | join(' ') }}"
matrix_authentication_service_upstream_oauth2_claims_imports: "{{ synapse_oidc_provider_mas_claims_imports }}" matrix_authentication_service_upstream_oauth2_claims_imports: "{{ synapse_oidc_provider_mas_claims_imports }}"
matrix_authentication_service_upstream_oauth2_human_name: "{{ synapse_oidc_provider_name }}" matrix_authentication_service_upstream_oauth2_human_name: "{{ synapse_oidc_provider_name }}"
- name: Reverse proxy synapse federation
ansible.builtin.import_role:
name: uumas.general.vhost
vars:
vhost_type: reverse_proxy
vhost_id: synapse-federation
vhost_domains:
- "{{ synapse_external_domain }}:8448"
vhost_proxy_target_netproto: unix
vhost_proxy_target_socket: "/run/synapse-socat.sock"
- name: Open port for synapse federation
ansible.posix.firewalld:
service: matrix
state: enabled
permanent: true
immediate: true

View File

@@ -2,7 +2,7 @@
# vim:ft=yaml # vim:ft=yaml
# {{ ansible_managed }} # {{ ansible_managed }}
signing_key_path: /run/secrets/synapse-signing-key signing_key_path: /run/secrets/signing-key
media_store_path: /data/media media_store_path: /data/media
log_config: /data/log.yaml log_config: /data/log.yaml

View File

@@ -1,3 +1,6 @@
--- ---
volume_uid: "" volume_uid: ""
volume_gid: "" volume_gid: ""
volume_type: ""
volume_device: ""
volume_mount_options: []

View File

@@ -0,0 +1,7 @@
---
- name: "Restart volume service {{ volume_name }}"
ansible.builtin.systemd_service:
name: "{{ volume_name }}-volume.service"
state: restarted
daemon_reload: true
ignore_errors: "{{ ansible_check_mode }}"

View File

@@ -17,3 +17,19 @@ argument_specs:
type: str type: str
required: false required: false
default: "" default: ""
volume_device:
description: The path of a device which is mounted for the volume.
type: str
required: false
default: ""
volume_type:
description: The filesystem type of device as used by the mount commands -t option
type: str
required: false
default: ""
volume_mount_options:
description: The mount options to use for a filesystem as used by the mount command -o option
type: list
elements: str
required: false
default: []

View File

@@ -5,7 +5,8 @@
- name: Create container volume service {{ volume_name }} - name: Create container volume service {{ volume_name }}
containers.podman.podman_volume: containers.podman.podman_volume:
name: "{{ volume_name }}" name: "{{ volume_name }}"
options: "{{ volume_options }}" options: "{{ _volume_options }}"
state: quadlet state: quadlet
quadlet_file_mode: "0644" quadlet_file_mode: "0644"
notify: Reload systemd daemon quadlet_options: "{{ _volume_quadlet_options }}"
notify: Restart volume service {{ volume_name }}

View File

@@ -1,6 +1,24 @@
--- ---
volume_mount_options_incl_empty: _volume_mount_options_incl_empty:
- "{{ 'uid=' ~ volume_uid if volume_uid | length > 0 else '' }}" - "{{ 'uid=' ~ volume_uid if volume_uid | length > 0 else '' }}"
- "{{ 'gid=' ~ volume_gid if volume_gid | length > 0 else '' }}" - "{{ 'gid=' ~ volume_gid if volume_gid | length > 0 else '' }}"
volume_mount_options: "{{ volume_mount_options_incl_empty | select('!=', '') | list }}" _volume_mount_options: >-
volume_options: "{{ ['o=' ~ volume_mount_options | join(',')] if volume_mount_options | length > 0 else [] }}" {{
_volume_mount_options_incl_empty
| select('!=', '')
+ volume_mount_options
}}
_volume_options: >-
{{
(['o=' ~ _volume_mount_options | join(',')] if _volume_mount_options | length > 0 else [])
+ (['type=' ~ volume_type] if volume_type | length > 0 else [])
+ (['device=' ~ volume_device] if volume_device | length > 0 else [])
}}
_volume_device_quadlet_options: |
[Service]
ExecStartPost=/usr/bin/podman volume mount {{ volume_name }}
ExecStop=/usr/bin/podman volume unmount {{ volume_name }}
ExecStop=/usr/bin/podman volume rm {{ volume_name }}
_volume_quadlet_options: >-
{{ [_volume_device_quadlet_options] if volume_device | length > 0 else [] }}