Compare commits

...

47 Commits

Author SHA1 Message Date
uumas
db651723b2 Add pinp support and make windmill use it 2026-03-26 03:04:44 +02:00
uumas
1d180106d6 service: Use saner defaults for additional containers 2026-03-25 19:29:30 +02:00
uumas
9eaa306aa4 lint 2026-03-25 19:28:40 +02:00
uumas
f8e67b12d7 windmill: fix entrypoint script 2026-03-24 19:56:09 +02:00
uumas
5814267d66 Add windmill 2026-03-18 00:31:50 +02:00
uumas
defd2517ea service: Add postgres url to secrets 2026-03-18 00:30:25 +02:00
uumas
615c4013c1 Use caddy instead of socat for http proxying 2026-03-15 22:30:36 +02:00
uumas
77768e5483 small fixes 2026-03-15 22:30:16 +02:00
uumas
162972810f example: fix postgres examples 2026-03-15 21:56:16 +02:00
uumas
8595e261c9 nextcloud: Make HArP optional and opt-in 2026-03-15 00:43:24 +02:00
uumas
70c5ed7ea0 service: Make oauth2-proxy depend on its socket 2026-03-14 23:50:01 +02:00
uumas
3554de82c0 service: Make oauth2-proxy aware it's running behing reverse proxy 2026-03-14 23:33:34 +02:00
uumas
f64ea2cbe3 container: Allow custom ip addresses for more than one network 2026-03-14 23:33:04 +02:00
uumas
ca29ffb271 network: Delete network on stop 2026-03-14 22:11:42 +02:00
uumas
dac44638e6 service: Don't use different networks for additional containers 2026-03-12 03:08:42 +02:00
uumas
489b8eaade service: Use native socket for oauth2 proxy 2026-03-12 03:08:36 +02:00
uumas
956f8ed6ce Use uumas.general.systemd_socket role 2026-03-12 01:54:43 +02:00
uumas
63e6f938bb Add vscode configuration 2026-03-12 00:45:48 +02:00
uumas
61c0724801 Add nextcloud role 2026-03-12 00:45:25 +02:00
uumas
ea2a2c3652 Add forgejo role 2026-03-12 00:42:54 +02:00
uumas
31cf49b004 service: Improve additional container support 2026-03-12 00:42:00 +02:00
uumas
9e3e1496f0 service: Split container network namespaces 2026-03-12 00:40:54 +02:00
uumas
190527e877 naming and documentation fixes 2026-03-12 00:38:42 +02:00
uumas
fb39f1bfc8 service: Don't require postgres tag specified 2026-03-12 00:36:11 +02:00
uumas
69ae1687b7 service: Add support for mongodb 2026-03-12 00:35:42 +02:00
uumas
efc7bf5434 service: Imrove native sockets 2026-03-12 00:34:25 +02:00
uumas
294b931d19 service: Support publishing arbitary ports through sockets 2026-03-12 00:32:10 +02:00
uumas
470b60f988 service: Support postgres >= 18 and postgres upgrades 2026-03-12 00:26:35 +02:00
uumas
c673aae8dc synapse: Use simple database hostname 2026-03-12 00:23:22 +02:00
uumas
4a68ab25e1 .yml -> .yaml 2026-03-12 00:22:52 +02:00
uumas
bf4ced4a9b service: Support cap_add 2026-03-12 00:20:19 +02:00
uumas
5a3bb96fc2 container: Support cap_add 2026-03-12 00:17:18 +02:00
uumas
447d4e59ad container: Stop container if process is oomkilled 2026-03-12 00:16:56 +02:00
uumas
f6af1d3472 service: Make supporting containers accessible using simple hostnames 2026-03-12 00:16:11 +02:00
uumas
ff1badbf03 container: Support setting container hostname 2026-03-12 00:12:03 +02:00
uumas
f721641fc6 Use systemd handlers from uumas.general.systemd 2026-03-12 00:10:31 +02:00
uumas
fce8804653 Make socat its own role 2026-03-12 00:06:37 +02:00
uumas
accd5ece14 small improvements 2026-02-13 02:00:55 +02:00
uumas
58ff2f6217 service: Add support for native sockets for http 2026-02-13 02:00:44 +02:00
uumas
4079b69338 Add gitignore for release archives 2026-02-12 20:05:09 +02:00
uumas
c7e26555b7 v0.1.1 2026-02-12 20:04:38 +02:00
uumas
07925caa95 Rename runtime.yaml to runtime.yml 2026-02-12 20:02:12 +02:00
uumas
de707b4e71 service: Add support for mariadb 2026-02-12 20:01:52 +02:00
uumas
0ee8e9b254 Add support for container devices 2026-01-24 17:12:57 +02:00
uumas
b030d671b5 service: Add support for mounting entire copied directory 2026-01-09 17:24:01 +02:00
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
74 changed files with 1502 additions and 233 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
uumas-podman-*.tar.gz

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"*.yaml": "ansible"
}
}

View File

@@ -3,7 +3,7 @@ namespace: uumas
name: podman name: podman
description: Roles for installing services in podman containers description: Roles for installing services in podman containers
readme: README.md readme: README.md
version: 0.1.0 version: 0.1.1
repository: "https://git.uumas.fi/uumas/ansible-podman" repository: "https://git.uumas.fi/uumas/ansible-podman"
license_file: LICENSE license_file: LICENSE
authors: authors:

View File

@@ -0,0 +1,4 @@
---
caddy_socket_proxy_target_container: "{{ caddy_socket_proxy_service_name }}"
caddy_socket_proxy_container_ip: ""
caddy_socket_proxy_auto_update: true

View File

@@ -0,0 +1,30 @@
---
argument_specs:
main:
description: >-
Sets up a caddy container and a systemd socket unit, forwarding traffic from it to
target container
options:
caddy_socket_proxy_service_name:
description: Name of the caddy service, used for systemd unit and container naming
type: str
required: true
caddy_socket_proxy_target_container:
description: Name of the container to forward traffic to
type: str
required: false
default: "{{ caddy_socket_proxy_service_name }}"
caddy_socket_proxy_target_http_port:
description: Port on the target container to forward traffic to
type: int
required: true
caddy_socket_proxy_container_ip:
description: IP address to assign to the caddy container.
type: str
required: false
default: ""
caddy_socket_proxy_auto_update:
description: Whether to automatically update the caddy container
type: bool
required: false
default: true

View File

@@ -0,0 +1,45 @@
---
- name: Create caddy socket proxy mount directories for {{ caddy_socket_proxy_service_name }}
ansible.builtin.file:
path: "{{ item.key }}"
state: directory
mode: "{{ item.value }}"
with_dict:
"/srv/{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy/": "0755"
"/srv/{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy/mounts": "0700"
"/srv/{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy/mounts/caddy": "0755"
- name: Configure caddy socket proxy for {{ caddy_socket_proxy_service_name }}
ansible.builtin.template:
src: Caddyfile.j2
dest: "/srv/{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy/mounts/caddy/Caddyfile"
mode: "0644"
notify: Restart container service {{ caddy_socket_proxy_service_name }}-caddy-socket-proxy
- name: Caddy socket proxy socket for {{ caddy_socket_proxy_service_name }}
ansible.builtin.import_role:
name: uumas.general.systemd_socket
vars:
systemd_socket_name: "{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy"
systemd_socket_requires:
- "{{ caddy_socket_proxy_target_container }}.service"
- name: Caddy container for {{ caddy_socket_proxy_service_name }}
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy"
container_image: "docker.io/library/caddy:2-alpine"
container_mounts:
- type: bind
source: "/srv/{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy/mounts/caddy"
destination: /etc/caddy
readonly: true
container_networks:
- name: "{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy"
ip: "{{ caddy_socket_proxy_container_ip }}"
container_requires:
- "{{ caddy_socket_proxy_service_name }}-caddy-socket-proxy.socket"
- "{{ caddy_socket_proxy_target_container }}.service"
container_auto_start: false
container_auto_update: "{{ caddy_socket_proxy_auto_update }}"

View File

@@ -0,0 +1,12 @@
# {{ ansible_managed }}
{
servers {
trusted_proxies_unix
}
}
http:// {
bind fd/3
reverse_proxy {{ caddy_socket_proxy_service_name }}:{{ service_container_http_port }}
}
}

View File

@@ -1,13 +1,16 @@
--- ---
container_command: [] container_command: []
container_entrypoint: ""
container_user: "" container_user: ""
container_mounts: [] container_mounts: []
container_devices: []
container_publish_ports: [] container_publish_ports: []
container_networks: [] container_networks: []
container_ip: "" container_hostname: ""
container_secrets: [] container_secrets: []
container_env: {} container_env: {}
container_auto_start: true container_auto_start: true
container_auto_update: true container_auto_update: true
container_requires: [] container_requires: []
container_wants: [] container_wants: []
container_add_capabilities: []

View File

@@ -1,7 +1,6 @@
--- ---
- name: "Restart container service {{ container_name }}" - name: Restart container service {{ container_name }}
ansible.builtin.systemd_service: ansible.builtin.set_fact:
name: "{{ container_name }}.service" systemd_restart_units: "{{ systemd_restart_units + [container_name ~ '.service'] }}" # noqa: var-naming[no-role-prefix]
state: restarted changed_when: true
daemon_reload: true notify: Apply systemd unit restarts
ignore_errors: '{{ ansible_check_mode }}'

View File

@@ -13,6 +13,11 @@ argument_specs:
required: false required: false
default: [] default: []
elements: str elements: str
container_entrypoint:
description: Entrypoint to use for the container
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
@@ -20,7 +25,7 @@ argument_specs:
default: "" default: ""
container_image: container_image:
description: "The image to run in the container, in FQIN format (registry/imagename:tag)" description: "The image to run in the container, in FQIN format (registry/image_name:tag)"
type: str type: str
required: true required: true
@@ -87,6 +92,21 @@ argument_specs:
required: false required: false
default: [] default: []
container_devices:
description: List of devices to be added inside the container.
type: list
required: false
default: []
elements: dict
options:
source:
description: Device path on host
type: str
required: true
destination:
description: Device path inside the container. Defaults to same as host.
type: str
required: false
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>)"
type: list type: list
@@ -98,12 +118,23 @@ argument_specs:
type: list type: list
required: false required: false
default: [] default: []
elements: str elements: dict
container_ip: options:
description: IPv4 address for the container in the first network defined in container_networks name:
description: Network name
type: str
required: true
ip:
description: Container IPv4 address in the network
type: str
required: false
default: ""
container_hostname:
description: Hostname to set inside the container. Available to other containers on the same network.
type: str type: str
required: false required: false
default: "" default: ""
container_secrets: container_secrets:
description: A list of secrets available to the container as file or environment variable description: A list of secrets available to the container as file or environment variable
type: list type: list
@@ -147,6 +178,13 @@ argument_specs:
required: false required: false
default: {} default: {}
container_add_capabilities:
description: List of capabilities to add to the container
type: list
required: false
default: []
elements: str
container_requires: container_requires:
description: > description: >
List of systemd units (like other containers) this one depends on. List of systemd units (like other containers) this one depends on.

View File

@@ -13,14 +13,13 @@
ansible.builtin.include_role: ansible.builtin.include_role:
name: network name: network
vars: vars:
network_name: "{{ network }}" network_name: "{{ network.name }}"
network_subnet: "{{ _container_network_subnet if network_index == 0 else '' }}" network_subnet: "{{ _container_network_subnet }}"
network_range: "{{ _container_network_range if network_index == 0 else '' }}" network_range: "{{ _container_network_range }}"
when: network_created_networks is not defined or network not in network_created_networks when: network_created_networks is not defined or network.name 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:
@@ -45,14 +44,20 @@
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_with_ip }}" device: "{{ _container_devices }}"
network: "{{ _container_networks }}"
hostname: "{{ container_hostname or omit }}"
publish: "{{ container_publish_ports }}" publish: "{{ container_publish_ports }}"
secrets: "{{ _container_secrets }}" secrets: "{{ _container_secrets }}"
env: "{{ container_env }}" env: "{{ container_env }}"
cap_add: "{{ container_add_capabilities }}"
label: "{{ _container_labels if _container_labels | length > 0 else omit }}" 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:
- Reload systemd daemon
- Restart container service {{ container_name }}

View File

@@ -1,33 +1,6 @@
--- ---
_container_image: "{{ container_image | replace('/', '_') ~ '.image' }}" _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') }}"
@@ -49,6 +22,25 @@ _container_mounts: >-
| zip(_container_volume_mount_sources) | map('combine') | zip(_container_volume_mount_sources) | map('combine')
}} }}
_container_devices_withdefaults: >-
{{
container_devices
| map(attribute='source')
| map('community.general.dict_kv', 'destination')
| zip(container_devices)
| map('combine')
}}
_container_devices: >-
{{
_container_devices_withdefaults
| map(attribute='source')
| zip(
_container_devices_withdefaults
| map(attribute='destination')
)
| map('join', ':')
}}
_container_secrets: >- _container_secrets: >-
{{ {{
container_secrets container_secrets
@@ -91,6 +83,7 @@ _container_quadlet_unit_options: |
{% endfor %} {% endfor %}
[Service] [Service]
SuccessExitStatus=0 143 SuccessExitStatus=0 143
OOMPolicy=stop
_container_quadlet_auto_start_options: | _container_quadlet_auto_start_options: |
[Service] [Service]
Restart=always Restart=always

View File

@@ -0,0 +1,27 @@
---
_container_networks: >-
{{
container_networks
| map(attribute='name')
| map('regex_replace', '$', '.network')
| zip(container_networks | map(attribute='ip', default=''))
| map('reject', 'equalto', '')
| map('join', ':ip=')
}}
_container_network_subnet: >-
{{ network.ip | ansible.utils.ipsubnet(24) if network.ip | default('') | length > 0 else '' }}
_container_network_subnet_ranges: >-
{{
[
_container_network_subnet | ansible.utils.ipsubnet(25, 0),
_container_network_subnet | ansible.utils.ipsubnet(25, 1)
] if network.ip | default('') | length > 0 else []
}}
_container_network_range: >-
{{
_container_network_subnet_ranges |
reject('ansible.utils.supernet_of', network.ip) |
first
if network.ip | default('') | length > 0 else ''
}}

View File

@@ -1,5 +1,5 @@
--- ---
- name: Hello world container - name: Hello world service
ansible.builtin.import_role: ansible.builtin.import_role:
name: service name: service
vars: vars:
@@ -19,7 +19,6 @@
service_container_http_port: 8080 service_container_http_port: 8080
service_domains: "{{ example_domains }}" service_domains: "{{ example_domains }}"
service_database_type: postgres service_database_type: postgres
service_postgres_tag: 16-alpine
service_container_publish_ports: service_container_publish_ports:
- "127.0.0.1:8080:8080" - "127.0.0.1:8080:8080"
- "0.0.0.0:4443:8043" - "0.0.0.0:4443:8043"
@@ -27,6 +26,9 @@
- network-online.target - network-online.target
service_container_env: service_container_env:
TZ: "Etc/UTC" TZ: "Etc/UTC"
DB_HOST: postgres
DB_USER: hello-world
DB_PASSWORD__FILE: /run/secrets/postgres
service_additional_containers: service_additional_containers:
- name: worker - name: worker
# image: "docker.io/library/hello-world:latest" # image: "docker.io/library/hello-world:latest"

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

@@ -0,0 +1 @@
Installs and configures forgejo inside podman

View File

@@ -0,0 +1,6 @@
---
forgejo_require_signin_view: false
forgejo_enable_internal_signin: true
forgejo_smtp_user: ""
forgejo_smtp_password: ""

View File

@@ -0,0 +1,45 @@
---
argument_specs:
main:
description: "Installs and configures forgejo inside podman"
options:
forgejo_tag:
description: Forgejo version to use. Can be major (x), minor (x.y) or patch (x.y.z). Major version recommended.
type: str
required: true
forgejo_domain:
description: Domain forgejo should listen on
type: str
required: true
forgejo_secret_key:
description: A long secret key for forgejo to encrypt secrets with. Must never change.
type: str
required: true
forgejo_smtp_server:
description: Smtp server for forgejo
type: str
required: true
forgejo_smtp_from:
description: Address to send email from
type: str
required: true
forgejo_smtp_user:
description: Smtp user to authenticate as
type: str
required: false
default: ""
forgejo_smtp_password:
description: Smtp password to authenticate with
type: str
required: false
default: ""
forgejo_require_signin_view:
description: Whether to require signing in to view public repositories
type: bool
required: false
default: false
forgejo_enable_internal_signin:
description: Whether to enable signing in using local username/password
type: bool
required: false
default: true

View File

@@ -0,0 +1,81 @@
---
- name: Ensure netcat-openbsd is installed for ssh shell
ansible.builtin.apt:
name: netcat-openbsd
- name: Create git system user on host for forgejo ssh
ansible.builtin.user:
name: git
group: git
system: true
home: /srv/forgejo/git
generate_ssh_key: true
ssh_key_type: ed25519
shell: /srv/forgejo/git/ssh-shell
register: _forgejo_git_user
- name: Add git user's own ssh key to its authorized keys
ansible.posix.authorized_key:
user: git
key: "{{ _forgejo_git_user.ssh_public_key }}"
- name: Install ssh forwarding shell for forgejo
ansible.builtin.template:
src: ssh-shell.j2
dest: /srv/forgejo/git/ssh-shell
mode: "0755"
- name: Forgejo service
ansible.builtin.import_role:
name: service
vars:
service_name: forgejo
service_container_image: codeberg.org/forgejo/forgejo:{{ forgejo_tag }}
service_container_mounts:
- type: volume
source: data
destination: /data
- type: bind
source: /etc/localtime
destination: /etc/localtime
readonly: true
- type: bind
source: /srv/forgejo/git/.ssh
destination: /data/git/.ssh
service_container_secrets:
- name: secret-key
value: "{{ forgejo_secret_key }}"
service_domains:
- "{{ forgejo_domain }}"
service_database_type: postgres
service_postgres_tag: 18-alpine
service_container_publish_ports:
- name: ssh
type: socket
container_port: 22
service_container_env:
USER_UID: "{{ _forgejo_git_user.uid }}"
USER_GID: "{{ _forgejo_git_user.group }}"
FORGEJO__security__SECRET_KEY_URI: file:/run/secrets/secret-key
FORGEJO__database__DB_TYPE: postgres
FORGEJO__database__USER: forgejo
FORGEJO__database__NAME: forgejo
FORGEJO__database__HOST: postgres
FORGEJO__database__PASSWD__FILE: /run/secrets/postgres
FORGEJO__server__PROTOCOL: http+unix
FORGEJO__server__HTTP_ADDR: /run/forgejo.sock
FORGEJO__server__DOMAIN: "{{ forgejo_domain }}"
FORGEJO__server__ROOT_URL: https://{{ forgejo_domain }}
FORGEJO__server__SSH_ALLOW_UNEXPECTED_AUTHORIZED_KEYS: "true"
FORGEJO__mailer__ENABLED: "true"
FORGEJO__mailer__PROTOCOL: smtp
FORGEJO__mailer__SMTP_ADDR: "{{ forgejo_smtp_server }}"
FORGEJO__mailer__SMTP_PORT: "587"
FORGEJO__mailer__FROM: "{{ forgejo_smtp_from }}"
FORGEJO__mailer__USER: "{{ forgejo_smtp_user }}"
FORGEJO__mailer__PASSWD: "{{ forgejo_smtp_password }}"
FORGEJO__service__DISABLE_REGISTRATION: "true"
FORGEJO__service__REQUIRE_SIGNIN_VIEW: "{{ 'true' if forgejo_require_signin_view else 'false' }}"
FORGEJO__service__ENABLE_INTERNAL_SIGNIN: "{{ 'true' if forgejo_enable_internal_signin else 'false' }}"
FORGEJO__oauth2_client__ENABLE_AUTO_REGISTRATION: "true"
FORGEJO__openid__ENABLE_OPENID_SIGNIN: "false"

View File

@@ -0,0 +1,4 @@
#!/bin/bash
# {{ ansible_managed }}
shift
SHELL=/bin/bash ssh -o "ProxyCommand nc -U /run/forgejo-ssh-socat.sock" -o StrictHostKeyChecking=no git@forgejo "SSH_ORIGINAL_COMMAND=\"$SSH_ORIGINAL_COMMAND\" $@"

View File

@@ -17,7 +17,7 @@
service_container_additional_networks: "{{ grafana_additional_networks }}" service_container_additional_networks: "{{ grafana_additional_networks }}"
service_container_env: service_container_env:
GF_DATABASE_TYPE: postgres GF_DATABASE_TYPE: postgres
GF_DATABASE_HOST: grafana-postgres:5432 GF_DATABASE_HOST: postgres:5432
GF_DATABASE_NAME: grafana GF_DATABASE_NAME: grafana
GF_DATABASE_USER: grafana GF_DATABASE_USER: grafana
GF_DATABASE_PASSWORD__FILE: /run/secrets/postgres GF_DATABASE_PASSWORD__FILE: /run/secrets/postgres

View File

@@ -1,7 +1,6 @@
--- ---
- name: Restart network service {{ network_name }} - name: Restart network service {{ network_name }}
ansible.builtin.systemd_service: ansible.builtin.set_fact:
name: "{{ network_name }}-network.service" systemd_restart_units: "{{ systemd_restart_units + [network_name ~ '-network.service'] }}" # noqa: var-naming[no-role-prefix]
state: restarted changed_when: true
daemon_reload: true notify: Apply systemd unit restarts
ignore_errors: "{{ ansible_check_mode }}"

View File

@@ -7,12 +7,15 @@
driver: "{{ network_driver }}" driver: "{{ network_driver }}"
subnet: "{{ network_subnet if network_subnet | length > 0 else omit }}" subnet: "{{ network_subnet if network_subnet | length > 0 else omit }}"
ip_range: "{{ network_range if network_range | length > 0 else omit }}" ip_range: "{{ network_range if network_range | length > 0 else omit }}"
quadlet_options: >- opt:
{{ parent: "{{ ansible_facts.default_ipv4.interface if network_driver == 'macvlan' else omit }}"
['Options=parent=' ~ ansible_default_ipv4.interface] quadlet_options:
if network_driver == 'macvlan' else [] - |-
}} [Service]
notify: Restart network service {{ network_name }} ExecStopPost=/usr/bin/podman network rm {{ network_name }}
notify:
- Reload systemd daemon
- Restart network service {{ network_name }}
- name: Add network to created networks variable - name: Add network to created networks variable
ansible.builtin.set_fact: ansible.builtin.set_fact:

View File

@@ -0,0 +1 @@
Sets up a nextcloud podman container, including HaRP using podman in podman

View File

@@ -0,0 +1,3 @@
---
nextcloud_tag: stable
nextcloud_install_harp: false

View File

@@ -0,0 +1,29 @@
---
- name: Unregister AppAPI daemon
containers.podman.podman_container_exec:
name: nextcloud
argv:
- /var/www/html/occ
- app_api:daemon:unregister
- harp
register: _nextcloud_appapi_unregister
changed_when: _nextcloud_appapi_unregister.rc == 0
failed_when: _nextcloud_appapi_unregister.rc not in [0, 1]
listen: Restart container service nextcloud-harp
- name: Register AppAPI daemon
containers.podman.podman_container_exec:
name: nextcloud
argv:
- /bin/sh
- -c
- >-
/var/www/html/occ app_api:daemon:register
--harp
--harp_frp_address=harp:8782
--harp_shared_key "$(cat /run/secrets/harp-shared-key)"
--net host
--set-default
--
harp HaRP docker-install http harp:8780 https://{{ nextcloud_domains[0] }}
listen: Restart container service nextcloud-harp

View File

@@ -0,0 +1,24 @@
---
argument_specs:
main:
description: "Sets up a nextcloud podman container, including HaRP using podman in podman"
options:
nextcloud_domains:
description: A list of domains nextcloud should listen on
type: list
required: true
elements: str
nextcloud_admin_password:
description: Password of the initial admin user
type: str
required: true
nextcloud_tag:
description: Nextcloud version to use
type: str
required: false
default: stable
nextcloud_install_harp:
description: Whether to install HaRP for nextcloud
type: bool
required: false
default: false

View File

@@ -0,0 +1,31 @@
---
- name: Nextcloud service
ansible.builtin.import_role:
name: service
vars:
service_name: nextcloud
service_container_image: docker.io/library/nextcloud:{{ nextcloud_tag }}
service_container_http_port: 80
service_domains: "{{ nextcloud_domains }}"
service_database_type: postgres
service_redis: true
service_container_mounts:
- type: volume
source: data
destination: /var/www/html
service_container_secrets:
- name: admin-password
value: "{{ nextcloud_admin_password }}"
- name: harp-shared-key
service_container_env:
POSTGRES_HOST: postgres
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD_FILE: /run/secrets/postgres
REDIS_HOST: redis
TRUSTED_PROXIES: 10.0.0.0/8
NEXTCLOUD_TRUSTED_DOMAINS: "{{ nextcloud_domains | join(' ') }}"
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD_FILE: /run/secrets/admin-password
service_additional_containers: "{{ _nextcloud_additional_containers }}"
service_vhost_locations: "{{ _nextcloud_vhost_locations }}"

View File

@@ -0,0 +1,9 @@
[containers]
ipcns = "host"
cgroupns = "host"
cgroups = "disabled"
log_driver = "k8s-file"
[engine]
cgroup_manager = "cgroupfs"
events_logger = "file"
runtime = "crun"

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# {{ ansible_managed }}
_term() {
echo "Received SIGTERM, stopping all containers"
kill "$child"
}
podman system service -t 0 &
podman run \
--rm \
-v /run/secrets/harp-shared-key:/run/secrets/harp-shared-key:ro \
-e HP_SHARED_KEY_FILE=/run/secrets/harp-shared-key \
-e NC_INSTANCE_URL="https://{{ nextcloud_domains[0] }}" \
-e HP_TRUSTED_PROXY_IPS="10.0.0.0/8" \
-v /tmp/storage-run-1000/podman/podman.sock:/var/run/docker.sock \
-v /certs:/certs \
--name harp \
--network host \
ghcr.io/nextcloud/nextcloud-appapi-harp:release &
child=$!
trap _term SIGTERM
wait

View File

@@ -0,0 +1,45 @@
---
_nextcloud_cron_container:
name: cron
entrypoint: /cron.sh
_nextcloud_harp_container:
name: harp
add_capabilities:
- CAP_SYS_ADMIN
image: quay.io/podman/stable:latest
user: podman
entrypoint: /entrypoint.sh
devices:
- source: /dev/fuse
mounts:
- type: template
source: containers.conf.j2
destination: /etc/containers/containers.conf
- type: template
source: harp_entrypoint.sh.j2
destination: /entrypoint.sh
mode: "0755"
- type: volume
source: harp-certs
destination: /certs
- type: volume
source: harp-containers
destination: /home/podman/.local/share/containers
env: {}
secrets:
- name: harp-shared-key
publish_ports:
- name: harp
type: socket
container_port: 8780
_nextcloud_additional_containers: >-
{{
[_nextcloud_cron_container]
+ ([_nextcloud_harp_container] if nextcloud_install_harp else [])
}}
_nextcloud_harp_vhost_locations:
- path: /exapps/*
proxy_target_socket: /run/nextcloud-harp-socat.sock
_nextcloud_vhost_locations: >-
{{ _nextcloud_harp_vhost_locations if nextcloud_install_harp else [] }}

View File

@@ -1,4 +0,0 @@
---
- name: Reload systemd daemon
ansible.builtin.systemd_service:
daemon_reload: true

View File

@@ -1,5 +1,6 @@
--- ---
dependencies: dependencies:
- role: uumas.general.systemd
- role: uumas.general.compatcheck - role: uumas.general.compatcheck
vars: vars:
compatcheck_supported_distributions: compatcheck_supported_distributions:

View File

@@ -10,6 +10,7 @@
name: podman-auto-update.timer name: podman-auto-update.timer
state: started state: started
enabled: true enabled: true
ignore_errors: "{{ ansible_check_mode }}"
- name: Login to registries - name: Login to registries
containers.podman.podman_login: containers.podman.podman_login:

View File

@@ -1,5 +1,6 @@
--- ---
service_container_command: [] service_container_command: []
service_container_entrypoint: ""
service_domains: [] service_domains: []
service_container_http_port: 0 service_container_http_port: 0
@@ -12,14 +13,18 @@ service_container_additional_networks: []
service_container_user: "" service_container_user: ""
service_container_publish_ports: [] service_container_publish_ports: []
service_container_mounts: [] service_container_mounts: []
service_container_devices: []
service_container_secrets: [] service_container_secrets: []
service_container_env: {} service_container_env: {}
service_container_add_capabilities: []
service_container_pinp: false
service_database_type: none service_database_type: none
service_database_additional_networks: [] service_database_additional_networks: []
service_database_secret_type: mount service_database_secret_type: mount
service_database_secret_target: "{{ service_database_type }}" service_database_secret_target: "{{ service_database_type }}"
service_postgres_image: docker.io/library/postgres service_postgres_image: docker.io/pgautoupgrade/pgautoupgrade
service_postgres_tag: alpine
service_redis: false service_redis: false
service_additional_containers: [] service_additional_containers: []

View File

@@ -1,14 +1,6 @@
--- ---
- name: Restart socat socket for {{ service_name }} - name: Restart socket for {{ service_name }}
ansible.builtin.systemd_service: ansible.builtin.set_fact:
name: "{{ service_name }}-socat.socket" systemd_restart_units: "{{ systemd_restart_units + [servive_name ~ '.socket'] }}" # noqa: var-naming[no-role-prefix]
state: restarted changed_when: true
daemon_reload: true notify: Apply systemd unit restarts
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

@@ -14,6 +14,11 @@ argument_specs:
required: false required: false
default: [] default: []
elements: str 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
@@ -23,8 +28,8 @@ argument_specs:
elements: str elements: str
service_container_http_port: service_container_http_port:
description: description:
- Port inside the container where http requests will be proxied to. - Port inside the container where http requests are proxied to.
- Required if service_domains is not empty. - If set to 0, http requests are proxied to /run/<service name>.sock inside the container
type: int type: int
required: false required: false
default: 0 default: 0
@@ -63,7 +68,7 @@ argument_specs:
required: false 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/image_name:tag)."
type: str type: str
required: true required: true
@@ -86,11 +91,44 @@ argument_specs:
default: [] default: []
elements: str elements: str
service_container_publish_ports: service_container_publish_ports:
description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)" description: A list of ports to publish outside the container
type: list type: list
required: false required: false
default: [] default: []
elements: str elements: dict
options:
name:
description:
- Name of the port.
- If type is socket, the socket will be created at /run/<service name>-<port name>.sock on the host.
- If type is not socket, this is just informative.
type: str
required: true
container_port:
description: Container port to publish
type: int
required: true
type:
description: Whether to publish as a port or socket
type: str
required: false
default: port
choices:
- socket
- port
host_address:
description:
- IP or hostname to listen on on the host
- Ignored if type is socket
type: str
required: false
default: 0.0.0.0
host_port:
description:
- Port to listen on on the host
- Required if type is port, ignored otherwise
type: int
required: false
service_container_mounts: service_container_mounts:
description: List of bind mounts or volumes to be mounted inside the service container(s). description: List of bind mounts or volumes to be mounted inside the service container(s).
type: list type: list
@@ -106,12 +144,14 @@ argument_specs:
- volume - volume
- bind - bind
- template - template
- copy
source: source:
description: description:
- Mount source. - Mount source.
- If mount type is volume, name of the volume. - If mount type is volume, name of the volume.
- If mount type is bind, host path to bind mount inside the container. - If mount type is bind, host path to bind mount inside the container.
- If mount type is template, the name of the template file, must end in .j2 - If mount type is template, the name of the template file, must end in .j2
- If mount type is copy, name of the file or directory to copy. Directory name must end in /.
type: str type: str
required: true required: true
destination: destination:
@@ -121,7 +161,7 @@ argument_specs:
readonly: readonly:
description: description:
- If true, volume will be mounted as read only inside the container. - If true, volume will be mounted as read only inside the container.
- Defaults to false for volume and bind, true for template - Defaults to false for volume and bind, true for template and copy
type: bool type: bool
required: false required: false
user: user:
@@ -135,10 +175,11 @@ argument_specs:
required: false required: false
default: "" default: ""
mode: mode:
description: Templated file permissions description:
- Templated file or copied directory/file permissions.
- Defaults to 0644 for files, 0755 for directories
type: str type: str
required: false required: false
default: "0644"
volume_device: volume_device:
description: >- description: >-
The path of a device which is mounted for the volume. The path of a device which is mounted for the volume.
@@ -161,6 +202,21 @@ argument_specs:
elements: str elements: str
required: false required: false
default: [] default: []
service_container_devices:
description: List of devices to be added inside the service main container.
type: list
required: false
default: []
elements: dict
options:
source:
description: Device path on host
type: str
required: true
destination:
description: Device path inside the container. Defaults to same as host.
type: str
required: false
service_container_secrets: service_container_secrets:
description: description:
- > - >
@@ -214,20 +270,45 @@ argument_specs:
required: false required: false
default: {} default: {}
service_container_add_capabilities:
description: List of capabilities to add to the service container
type: list
required: false
default: []
elements: str
service_container_pinp:
description:
- If true, runs the container with podman in podman
- This starts a podman service inside the outer container
- The podman socket is exposed to the inner container at /var/run/docker.sock
- >-
This allows the container to manage other containers, which are run inside the
same outer container
- >-
The inner containers use host networking, so they share the network namespace
with the outer container and each other.
- This support is experimental and may not work with all images or configurations.
type: bool
required: false
default: false
service_database_type: service_database_type:
description: description:
- Database type to set up. - Database type to set up.
- > - >
It will be run in a 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 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_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
choices: choices:
- postgres - postgres
- mariadb
- mongo
- none - none
required: false required: false
default: none default: none
@@ -261,14 +342,13 @@ argument_specs:
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 - Ignored if database type is not postgres.
- If a custom postgres image is specified, see that image documentation for supported tags. - If a custom postgres image is specified, see that image documentation for supported tags.
type: str type: str
required: false required: false
service_redis: service_redis:
description: >- description: >-
Whether to install redis in a container accessible to the service at host Whether to install redis in a container accessible to the service at host redis.
{{ service_name }}-redis.
type: bool type: bool
required: false required: false
default: false default: false
@@ -276,9 +356,10 @@ argument_specs:
service_additional_containers: service_additional_containers:
description: description:
- List of additional containers for the service. - List of additional containers for the service.
- > - >-
Will inherit most options from main service container. All options can be overridden If image is not specified, will use service container image and
per-container. inherit most options from main service container.
- All options can be overridden per-container.
type: list type: list
required: false required: false
default: [] default: []
@@ -298,17 +379,31 @@ argument_specs:
type: str type: str
required: false required: false
default: "{{ service_container_image }}" default: "{{ service_container_image }}"
user:
description:
- The UID to run as inside the container.
- Defaults to <service_container_user> if same image, "" otherwise.
type: str
required: false
command: command:
description: Command to start the container with. description:
- Command to start the container with.
- Defaults to <service_container_command> if same image, [] otherwise.
type: list type: list
required: false required: false
default: "[]"
elements: str elements: str
entrypoint:
description:
- Entrypoint to use in the container
- Defaults to <service_container_entrypoint> if same image, "" otherwise.
type: str
required: false
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 container.
- Defaults to <service_container_mounts> if same image, [] otherwise.
type: list type: list
required: false required: false
default: "{{ service_container_mounts }}"
elements: dict elements: dict
options: options:
type: type:
@@ -337,6 +432,22 @@ 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
user:
description: Volume owner uid. Only applicable if mount type is volume.
type: str
required: false
default: ""
group:
description: Volume owner gid. Only applicable if mount type is volume.
type: str
required: false
default: ""
mode:
description:
- Templated file or copied directory/file permissions.
- Defaults to 0644 for files, 0755 for directories
type: str
required: false
volume_device: volume_device:
description: >- description: >-
The path of a device which is mounted for the volume. The path of a device which is mounted for the volume.
@@ -359,17 +470,76 @@ argument_specs:
elements: str elements: str
required: false required: false
default: [] default: []
devices:
description:
- List of devices to be added inside the container.
- Defaults to <service_container_devices> if same image, [] otherwise.
type: list
required: false
elements: dict
options:
source:
description: Device path on host
type: str
required: true
destination:
description: Device path inside the container. Defaults to same as host.
type: str
required: false
publish_ports: publish_ports:
description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)" description: A list of ports to publish outside the container
type: list type: list
required: false required: false
default: [] default: []
elements: str elements: dict
options:
name:
description:
- Name of the port.
- >-
If type is socket, the socket will be created at
/run/<service name>-<additional container name>-<port name>.sock on the host.
- If type is not socket, this is just informative.
type: str
required: true
container_port:
description: Container port to publish
type: int
required: true
type:
description: Whether to publish as a port or socket
type: str
required: false
default: port
choices:
- socket
- port
host_address:
description:
- IP or hostname to listen on on the host
- Ignored if type is socket
type: str
required: false
default: 0.0.0.0
host_port:
description:
- Port to listen on on the host
- Required if type is port, ignored otherwise
type: int
required: false
env: env:
description: A dict of environment variables for the container description:
- A dict of environment variables for the container
- Defaults to <service_container_env> if same image, {} otherwise.
type: dict type: dict
required: false required: false
default: {} add_capabilities:
description:
- List of capabilities to add to the container
- Defaults to <service_container_add_capabilities> if same image, [] otherwise.
type: list
required: false
elements: str
secrets: secrets:
description: description:
- > - >
@@ -379,9 +549,9 @@ argument_specs:
A dict of secrets and their values (including autogenerated values) is available as 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 `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. container doesn't support reading the secret from file or environment variable.
- Defaults to <service_container_secrets> if same image, [] otherwise.
type: list type: list
required: false required: false
default: []
elements: dict elements: dict
options: options:
name: name:
@@ -417,6 +587,21 @@ argument_specs:
the name of the environment variable. Defaults to secret name. the name of the environment variable. Defaults to secret name.
type: str type: str
required: false required: false
pinp:
description:
- If true, runs the container with podman in podman
- This starts a podman service inside the outer container
- The podman socket is exposed to the inner container at /var/run/docker.sock
- >-
This allows the container to manage other containers, which are run inside the
same outer container
- >-
The inner containers use host networking, so they share the network namespace
with the outer container and each other.
- This support is experimental and may not work with all images or configurations.
type: bool
required: false
default: 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

@@ -1,22 +1,31 @@
--- ---
- name: Additional container {{ container ~ ' for ' ~ service_name }} - name: Additional containers for {{ service_name }}
ansible.builtin.include_role: ansible.builtin.include_role:
name: container name: container
vars: vars:
container_name: "{{ _service_additional_container.name }}" container_name: "{{ _service_additional_container.name }}"
container_image: "{{ _service_additional_container.image | default(service_container_image) }}" container_image: "{{ _service_additional_container_final.image }}"
container_command: "{{ _service_additional_container.command | default([]) }}" container_entrypoint: "{{ _service_additional_container_final.entrypoint }}"
container_user: "{{ service_container_user }}" container_command: "{{ _service_additional_container_final.command }}"
container_mounts: "{{ _service_additional_container_mounts }}" container_user: "{{ _service_additional_container_final.user }}"
container_publish_ports: "{{ _service_additional_container.publish_ports | default([]) }}" container_mounts: "{{ _service_additional_container_final.mounts }}"
container_networks: "{{ _service_container_networks }}" container_devices: "{{ _service_additional_container.devices }}"
container_ip: "{{ _service_additional_container_ip }}" container_publish_ports: "{{ _service_additional_container_publish_ports }}"
container_secrets: "{{ _service_additional_container.secrets | default(_service_container_secrets) }}" container_networks: "{{ _service_additional_container_networks }}"
container_env: "{{ _service_additional_container.env | default(service_container_env) }}" container_hostname: "{{ _service_additional_container.name | regex_replace('^' ~ service_name ~ '-', '') }}"
container_secrets: "{{ _service_additional_container_secrets }}"
container_env: "{{ _service_additional_container_final.env }}"
container_add_capabilities: "{{ _service_additional_container.add_capabilities }}"
container_requires: "{{ _service_container_requires }}" container_requires: "{{ _service_container_requires }}"
container_wants: "{{ service_wants }}" container_wants: "{{ _service_additional_container_wants }}"
container_auto_update: "{{ service_auto_update }}" container_auto_update: "{{ service_auto_update }}"
loop: "{{ _service_additional_containers }}" loop: "{{ _service_additional_containers }}"
loop_control: loop_control:
loop_var: _service_additional_container loop_var: _service_additional_container
index_var: _service_additional_container_index index_var: _service_additional_container_index
- name: Socat sockets for additional containers of {{ service_name }}
ansible.builtin.include_tasks: additional_socat.yaml
loop: "{{ _service_additional_containers }}"
loop_control:
loop_var: _service_additional_container

View File

@@ -0,0 +1,12 @@
---
- name: Socat for socket published ports of {{ service_name }}
ansible.builtin.include_role:
name: socat
loop: "{{ _service_additional_container_publish_socket_ports }}"
loop_control:
loop_var: publish_port
vars:
socat_service_name: "{{ service_name }}-{{ publish_port.name }}"
socat_target_container: "{{ _service_additional_container.name }}"
socat_target_http_port: "{{ publish_port.container_port }}"
socat_auto_update: "{{ service_auto_update }}"

View File

@@ -1,22 +1,20 @@
--- ---
- name: Include variables for database {{ service_database_type }}
ansible.builtin.include_vars:
file: database/{{ service_database_type }}.yaml
- name: Database container for {{ service_name }} - name: Database container for {{ service_name }}
ansible.builtin.import_role: ansible.builtin.import_role:
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: "{{ service_postgres_image }}:{{ service_postgres_tag }}" container_image: "{{ _service_database_image }}"
container_mounts: container_mounts:
- type: volume - type: volume
source: "{{ _service_database_name }}" source: "{{ _service_database_name }}"
destination: /var/lib/postgresql/data destination: "{{ _service_database_mount_destination }}"
container_networks: "{{ _service_database_networks }}" container_networks: "{{ _service_database_networks }}"
container_ip: >- container_hostname: "{{ service_database_type }}"
{{ service_container_ip | ansible.utils.ipmath(1) if _service_static_ip else '' }} container_secrets: "{{ _service_database_secrets }}"
container_secrets: container_env: "{{ _service_database_env }}"
- name: "{{ _service_database_name }}"
target: "{{ service_database_type }}"
container_env:
POSTGRES_USER: "{{ service_name | replace('-', '_') }}"
POSTGRES_PASSWORD_FILE: "/run/secrets/{{ service_database_type }}"
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

@@ -0,0 +1,61 @@
---
- 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) }}"
- name: Copy files for copy mounts
ansible.builtin.copy:
src: "{{ item[0].source }}"
dest: "{{ item[1] }}"
mode: "{{ item[0].mode | default('0644') }}"
directory_mode: "0755"
notify: Restart container service {{ service_name }}
loop: "{{ _service_all_copy_mounts | zip(_service_all_copy_mount_host_files) }}"
- name: Template entrypoint for pinp
ansible.builtin.template:
src: "pinp-entrypoint.sh.j2"
dest: "{{ _service_host_directory }}/mounts/pinp-entrypoint.sh"
mode: "0755"
vars:
pinp_inner_name: "{{ service_name }}"
pinp_inner_image: "{{ service_container_image }}"
pinp_inner_mounts: "{{ _service_container_pinp_inner_mounts }}"
pinp_inner_env: "{{ service_container_env }}"
when: service_container_pinp
- name: Template entrypoint for pinp of additional containers
ansible.builtin.template:
src: "pinp-entrypoint.sh.j2"
dest: "{{ _service_host_directory }}/mounts/{{ _service_additional_container.name }}-pinp-entrypoint.sh"
mode: "0755"
loop: "{{ _service_additional_containers | selectattr('pinp') }}"
loop_control:
loop_var: _service_additional_container
vars:
pinp_inner_name: "{{ _service_additional_container.name }}"
pinp_inner_image: "{{ _service_additional_container.image }}"
pinp_inner_mounts: "{{ _service_additional_container_pinp_inner_mounts }}"
pinp_inner_env: "{{ _service_additional_container.env }}"

View File

@@ -14,40 +14,68 @@
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: Template mounts for {{ service_name }} - name: Host mounts for {{ service_name }}
ansible.builtin.include_tasks: templates.yaml ansible.builtin.include_tasks: host_mounts.yaml
when: _service_template_mounts | length > 0 when: >-
(_service_all_template_mounts + _service_all_copy_mounts) | length > 0
or service_container_pinp
or (_service_additional_containers | selectattr('pinp') | length > 0)
- name: Additional containers for {{ service_name }} - name: Additional containers for {{ service_name }}
ansible.builtin.include_tasks: additional.yaml ansible.builtin.include_tasks: additional.yaml
when: _service_additional_containers | length > 0 when: _service_additional_containers | length > 0
- name: Native socket for {{ service_name }}
ansible.builtin.include_role:
name: uumas.general.systemd_socket
vars:
systemd_socket_name: "{{ service_name }}"
systemd_socket_requires:
- "{{ service_name }}.service"
when: _service_native_socket
- name: Main container for {{ service_name }} - name: Main container for {{ service_name }}
ansible.builtin.import_role: ansible.builtin.import_role:
name: container name: container
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_command: "{{ _service_container.command }}"
container_mounts: "{{ _service_container_mounts }}" container_user: "{{ _service_container.user }}"
container_publish_ports: "{{ service_container_publish_ports }}" container_mounts: "{{ _service_container.mounts }}"
container_devices: "{{ service_container_devices }}"
container_publish_ports: "{{ _service_container_publish_ports }}"
container_networks: "{{ _service_container_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_add_capabilities: "{{ service_container_add_capabilities }}"
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 }} - name: Caddy socket proxy for http of {{ service_name }}
ansible.builtin.include_tasks: socat.yaml ansible.builtin.include_role:
name: caddy_socket_proxy
when: service_container_http_port > 0 when: service_container_http_port > 0
vars: vars:
socat_service_name: "{{ service_name }}" caddy_socket_proxy_service_name: "{{ service_name }}"
socat_target_http_port: "{{ service_container_http_port }}" caddy_socket_proxy_target_http_port: "{{ service_container_http_port }}"
socat_container_ip: >- caddy_socket_proxy_container_ip: >-
{{ service_container_ip | ansible.utils.ipmath(3) if _service_static_ip else '' }} {{ service_container_ip | ansible.utils.ipmath(257) if _service_static_ip else '' }}
caddy_socket_proxy_auto_update: "{{ service_auto_update }}"
- name: Socat for socket published ports of {{ service_name }}
ansible.builtin.include_role:
name: socat
loop: "{{ _service_container_publish_socket_ports }}"
loop_control:
loop_var: publish_port
vars:
socat_service_name: "{{ service_name }}-{{ publish_port.name }}"
socat_target_container: "{{ service_name }}"
socat_target_http_port: "{{ publish_port.container_port }}"
socat_auto_update: "{{ service_auto_update }}"
- name: Reverse proxy for {{ service_name }} - name: Reverse proxy for {{ service_name }}
ansible.builtin.include_tasks: proxy.yaml ansible.builtin.include_tasks: proxy.yaml

View File

@@ -11,7 +11,7 @@
- --cookie-secret-file - --cookie-secret-file
- /run/secrets/cookie-secret - /run/secrets/cookie-secret
container_networks: container_networks:
- "{{ service_name }}-oauth2-proxy" - name: "{{ service_name }}-oauth2-proxy"
container_secrets: container_secrets:
- name: "{{ service_name }}-oauth2-proxy-cookie-secret" - name: "{{ service_name }}-oauth2-proxy-cookie-secret"
length: 32 length: 32
@@ -20,18 +20,20 @@
value: "{{ service_oauth2_proxy_client_secret }}" value: "{{ service_oauth2_proxy_client_secret }}"
target: client-secret target: client-secret
container_env: container_env:
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180 OAUTH2_PROXY_HTTP_ADDRESS: fd:3
OAUTH2_PROXY_PROVIDER: oidc OAUTH2_PROXY_PROVIDER: oidc
OAUTH2_PROXY_OIDC_ISSUER_URL: "{{ service_oauth2_proxy_issuer_url }}" OAUTH2_PROXY_OIDC_ISSUER_URL: "{{ service_oauth2_proxy_issuer_url }}"
OAUTH2_PROXY_CLIENT_ID: "{{ service_oauth2_proxy_client_id }}" OAUTH2_PROXY_CLIENT_ID: "{{ service_oauth2_proxy_client_id }}"
OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256 OAUTH2_PROXY_CODE_CHALLENGE_METHOD: S256
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true" OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"
OAUTH2_PROXY_EMAIL_DOMAINS: "*" OAUTH2_PROXY_EMAIL_DOMAINS: "*"
OAUTH2_PROXY_REVERSE_PROXY: "true"
container_requires:
- "{{ service_name }}-oauth2-proxy.socket"
container_auto_update: "{{ service_auto_update }}" container_auto_update: "{{ service_auto_update }}"
- name: Socat for OAuth2 Proxy for {{ service_name }} - name: Socket for OAuth2 Proxy for {{ service_name }}
ansible.builtin.import_tasks: socat.yaml ansible.builtin.import_role:
name: uumas.general.systemd_socket
vars: vars:
socat_service_name: "{{ service_name }}-oauth2-proxy" systemd_socket_name: "{{ service_name }}-oauth2-proxy"
socat_target_http_port: 4180
socat_container_ip: ""

View File

@@ -11,7 +11,7 @@
vhost_id: "{{ service_name }}" vhost_id: "{{ service_name }}"
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: "{{ _service_socket_path }}"
vhost_proxy_headers: "{{ _service_proxy_headers }}" vhost_proxy_headers: "{{ _service_proxy_headers }}"
vhost_proxy_auth_socket: "{{ _service_oauth2_socket }}" vhost_proxy_auth_socket: "{{ _service_oauth2_socket }}"
vhost_proxy_auth_uri: /oauth2/auth vhost_proxy_auth_uri: /oauth2/auth

View File

@@ -6,7 +6,7 @@
container_name: "{{ service_name }}-redis" container_name: "{{ service_name }}-redis"
container_image: docker.io/valkey/valkey:alpine container_image: docker.io/valkey/valkey:alpine
container_networks: container_networks:
- "{{ service_name }}" - name: "{{ service_name }}"
container_ip: >- ip: "{{ service_container_ip | ansible.utils.ipmath(2) if _service_static_ip else '' }}"
{{ service_container_ip | ansible.utils.ipmath(2) if _service_static_ip else '' }} container_hostname: redis
container_auto_update: "{{ service_auto_update }}" container_auto_update: "{{ service_auto_update }}"

View File

@@ -1,27 +0,0 @@
---
- 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,16 +1,22 @@
--- ---
- 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."
when: service_container_user is not string when: service_container_user is not string
- name: Fail if service_database_type is postgres but service_postgres_tag is not set
ansible.builtin.fail:
msg: "service_postgres_tag needs to be set when database type is postgres"
when: "service_database_type == 'postgres' and service_postgres_tag is not defined"
- name: Fail if template mount source doesn't end in .j2 - name: Fail if template mount source doesn't end in .j2
ansible.builtin.fail: ansible.builtin.fail:
msg: "Template mount source file name needs to end in .j2. The file {{ item.source }} of {{ service_name }} doesn't." msg: "Template mount source file name needs to end in .j2. The file {{ item.source }} of {{ service_name }} doesn't."
when: "item.source | split('.') | last != 'j2'" when: "item.source | split('.') | last != 'j2'"
loop: "{{ _service_template_mounts }}" loop: "{{ _service_template_mounts }}"
- name: Fail if copy mount source doesn't end with /
ansible.builtin.fail:
msg: "Copy mount source name must end with /. The file {{ item.source }} of {{ service_name }} doesn't"
when: "not item.source.endswith('/')"
loop: "{{ _service_copy_mounts }}"

View File

@@ -0,0 +1,26 @@
#!/bin/bash
# {{ ansible_managed }}
_term() {
echo "Received SIGTERM, stopping all containers"
kill "$child"
}
podman system service -t 0 &
podman run \
--rm \
-v /run/secrets:/run/secrets:ro \
{% for key, value in pinp_inner_env.items() %}
-e {{ key }}={{ value }} \
{% endfor %}
-v /tmp/storage-run-1000/podman/podman.sock:/var/run/docker.sock \
{% for mount in pinp_inner_mounts %}
--mount type={{ mount.type }},source={{ mount.source }},destination={{ mount.destination }}{% if mount.readonly | default(false) %},readonly{% endif %} \
{% endfor %}
--name {{ pinp_inner_name }} \
--network host \
{{ pinp_inner_image }} &
child=$!
trap _term SIGTERM
wait "$!"

View File

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

View File

@@ -0,0 +1,9 @@
---
_service_database_image: docker.io/library/mariadb:lts
_service_database_mount_destination: /var/lib/mysql
_service_database_authenticated: true
_service_database_env:
MARIADB_RANDOM_ROOT_PASSWORD: "1"
MARIADB_USER: "{{ service_name | replace('-', '_') }}"
MARIADB_DATABASE: "{{ service_name | replace('-', '_') }}"
MARIADB_PASSWORD_FILE: "/run/secrets/{{ service_database_type }}"

View File

@@ -0,0 +1,6 @@
---
_service_database_image: docker.io/library/mongo:latest
_service_database_mount_destination: /data/db
_service_database_authenticated: false
_service_database_env:
MONGO_INITDB_DATABASE: "{{ service_name | replace('-', '_') }}"

View File

@@ -0,0 +1,14 @@
---
_service_database_image: "{{ service_postgres_image }}:{{ service_postgres_tag }}"
_service_database_mount_destination: >-
{{
'/var/lib/postgresql/data'
if (service_postgres_tag | split('-') | length > 1)
and (service_postgres_tag | split('-') | first) is version('18', '<')
else '/var/lib/postgresql'
}}
_service_database_authenticated: true
_service_database_env:
POSTGRES_USER: "{{ service_name | replace('-', '_') }}"
POSTGRES_PASSWORD_FILE: "/run/secrets/{{ service_database_type }}"
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"

View File

@@ -1,26 +1,156 @@
--- ---
_service_additional_containers_with_default_image: >-
{{
([{ 'image': service_container_image }] * service_additional_containers | length)
| zip(service_additional_containers)
| map('combine')
}}
_service_additional_container_same_image_defaults:
user: "{{ service_container_user }}"
command: "{{ service_container_command }}"
entrypoint: "{{ service_container_entrypoint }}"
devices: "{{ service_container_devices }}"
env: "{{ service_container_env }}"
add_capabilities: "{{ service_container_add_capabilities }}"
pinp: false
_service_additional_container_different_image_defaults:
user: ""
command: []
entrypoint: ""
mounts: []
devices: []
publish_ports: []
env: {}
add_capabilities: []
secrets: []
pinp: false
_service_additional_same_image_containers: >-
{{
_service_additional_containers_with_default_image
| selectattr('image', '==', service_container_image)
}}
_service_additional_different_image_containers: >-
{{
_service_additional_containers_with_default_image
| selectattr('image', '!=', service_container_image)
}}
_service_additional_containers: >- _service_additional_containers: >-
{{ {{
service_additional_containers (
| zip( (
service_additional_containers (
| map(attribute='name') [_service_additional_container_same_image_defaults] *
| map('regex_replace', '^', service_name ~ '-') (_service_additional_same_image_containers | length)
| map('community.general.dict_kv', 'name')
) )
| map('combine') | zip(_service_additional_same_image_containers)
| map('combine')
) +
(
(
[_service_additional_container_different_image_defaults] *
(_service_additional_different_image_containers | length)
)
| zip(_service_additional_different_image_containers)
| map('combine')
)
)
| zip(
(
_service_additional_same_image_containers +
_service_additional_different_image_containers
)
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('community.general.dict_kv', 'name')
)
| map('combine')
}} }}
_service_additional_container_ip: >-
_service_additional_container_wants: >-
{{ {{
service_container_ip | service_wants
ansible.utils.ipmath(20 + _service_additional_container_index) + _service_additional_container_publish_socket_ports
if _service_static_ip else '' | map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('regex_replace', '$', '-socat.socket')
}} }}
_service_additional_container_networks: >-
{{
[{
'name': service_name,
'ip':
service_container_ip | ansible.utils.ipmath(20 + _service_additional_container_index)
if _service_static_ip else ''
}]
+ (
service_container_additional_networks
+ (
_service_additional_container_publish_socket_ports
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('regex_replace', '$', '-socat')
)
) | map('community.general.dict_kv', 'name')
}}
_service_additional_container_secrets: >-
{{
(
_service_additional_container.secrets
| map(attribute='name')
| map('community.general.dict_kv', 'target')
| zip(
_service_additional_container.secrets,
_service_additional_container.secrets
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('community.general.dict_kv', 'name')
)
| map('combine')
) if _service_additional_container.secrets is defined
else _service_container_secrets
}}
_service_additional_container_publish_ports_with_defaults: >-
{{
([{ 'type': 'port', 'host_address': '0.0.0.0' }] * _service_additional_container.publish_ports | length)
| zip(_service_additional_container.publish_ports)
| map('combine')
}}
_service_additional_container_publish_socket_ports: >-
{{
_service_additional_container_publish_ports_with_defaults | selectattr('type', '==', 'socket')
if _service_additional_container.publish_ports is defined
else
[]
}}
_service_additional_container_publish_port_ports: >-
{{
_service_additional_container_publish_ports_with_defaults | selectattr('type', '==', 'port')
if _service_additional_container.publish_ports is defined
else
[]
}}
_service_additional_container_publish_ports: >-
{{
_service_additional_container_publish_port_ports | map(attribute='host_address') |
zip(
_service_additional_container_publish_port_ports | map(attribute='host_port'),
_service_additional_container_publish_port_ports | map(attribute='container_port')
) | map('join', ':')
}}
_service_additional_volume_mounts: "{{ _service_additional_container.mounts | selectattr('type', '==', 'volume') }}" _service_additional_volume_mounts: "{{ _service_additional_container.mounts | selectattr('type', '==', 'volume') }}"
_service_additional_template_mounts: "{{ _service_additional_container.mounts | selectattr('type', '==', 'template') }}" _service_additional_template_mounts: "{{ _service_additional_container.mounts | selectattr('type', '==', 'template') }}"
_service_additional_copy_mounts: "{{ _service_additional_container.mounts | selectattr('type', '==', 'copy') }}"
_service_additional_host_directory: "/srv/{{ service_name }}" _service_additional_host_directory: "/srv/{{ service_name }}"
_service_additional_container_volume_mounts: >- _service_additional_container_volume_mounts: >-
@@ -50,13 +180,85 @@ _service_additional_container_template_mounts: >-
) | ) |
map('combine') map('combine')
}} }}
_service_additional_container_copy_mounts: >-
{{
([{'readonly': true}] * _service_additional_copy_mounts | length) |
zip(
_service_additional_copy_mounts |
community.general.remove_keys(['mode']),
_service_additional_copy_mounts |
map(attribute='source') |
map('regex_replace', '\/$', '') |
map('regex_replace', '^', _service_host_directory ~ '/mounts/') |
map('community.general.dict_kv', 'source'),
([{'type': 'bind'}] * _service_additional_copy_mounts | length)
) |
map('combine')
}}
_service_additional_container_mounts: >- _service_additional_container_mounts: >-
{{ {{
_service_additional_container_volume_mounts + _service_additional_container_volume_mounts +
_service_additional_container_bind_mounts + _service_additional_container_bind_mounts +
_service_additional_container_template_mounts _service_additional_container_template_mounts +
_service_additional_container_copy_mounts
if _service_additional_container.mounts is defined if _service_additional_container.mounts is defined
else else
_service_container_mounts _service_container_mounts
}} }}
_service_additional_plain_container:
image: "{{ _service_additional_container.image }}"
entrypoint: "{{ _service_additional_container.entrypoint }}"
command: "{{ _service_additional_container.command }}"
user: "{{ _service_additional_container.user }}"
env: "{{ _service_additional_container.env }}"
mounts: "{{ _service_additional_container_mounts }}"
_service_additional_pinp_container_mounts:
- type: bind
source: "{{ _service_host_directory }}/mounts/{{ _service_additional_container.name }}-entrypoint.sh"
destination: /entrypoint.sh
readonly: true
- type: volume
source: "{{ _service_additional_container.name }}-containers"
destination: /home/podman/.local/share/containers
_service_additional_pinp_container:
image: quay.io/podman/stable:latest
entrypoint: /entrypoint.sh
command: []
user: podman
env: {}
mounts: >-
{{
_service_additional_pinp_container_mounts
+ (
_service_additional_container_mounts
| zip(
_service_additional_container_mounts
| map(attribute='source')
| map('replace', '/', '_')
| map('regex_replace', '^', '/mounts/')
| map('community.general.dict_kv', 'destination')
)
| map('combine')
)
}}
_service_additional_container_final: >-
{{ _service_additional_pinp_container if _service_additional_container.pinp else _service_additional_plain_container }}
_service_additional_container_pinp_inner_mounts: >-
{{
_service_additional_container_mounts
| zip(
_service_additional_container_mounts
| map(attribute='source')
| map('replace', '/', '_')
| map('regex_replace', '^', '/mounts/')
| map('community.general.dict_kv', 'source')
)
| map('combine')
}}

View File

@@ -3,6 +3,14 @@ _service_setup_database: "{{ service_database_type != 'none' }}"
_service_database_name: "{{ service_name }}-{{ service_database_type }}" _service_database_name: "{{ service_name }}-{{ service_database_type }}"
_service_database_networks: >- _service_database_networks: >-
{{ {{
[service_name] + [{
service_database_additional_networks 'name': service_name,
'ip': service_container_ip | ansible.utils.ipmath(1) if _service_static_ip else ''
}]
+ service_database_additional_networks | map('community.general.dict_kv', 'name')
}} }}
_service_database_secret:
name: "{{ _service_database_name }}"
target: "{{ service_database_type }}"
_service_database_secrets: "{{ [_service_database_secret] if _service_database_authenticated else [] }}"

View File

@@ -1,18 +1,44 @@
--- ---
_service_container_networks: "{{ [service_name] + service_container_additional_networks }}" _service_container_networks: >-
{{
[{
'name': service_name,
'ip': service_container_ip
}]
+ (
[{
'name': service_name ~ '-caddy-socket-proxy',
'ip': service_container_ip | ansible.utils.ipmath(256) if _service_static_ip else ''
}] if service_container_http_port > 0 else []
)
+ (
service_container_additional_networks
+ (
_service_container_publish_socket_ports
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('regex_replace', '$', '-socat')
)
) | map('community.general.dict_kv', 'name')
}}
_service_static_ip: "{{ service_container_ip | length > 0 }}" _service_static_ip: "{{ service_container_ip | length > 0 }}"
_service_container_requires: >- _service_container_requires: >-
{{ {{
service_requires service_requires
+ ([_service_database_name + '.service'] if _service_setup_database else []) + ([_service_database_name ~ '.service'] if _service_setup_database else [])
+ ([service_name + '-redis.service'] if service_redis else []) + ([service_name ~ '-redis.service'] if service_redis else [])
+ ([service_name ~ '.socket'] if _service_native_socket else [])
}} }}
_service_container_wants: >- _service_container_wants: >-
{{ {{
service_wants service_wants
+ ([service_name + '-socat.socket'] if service_container_http_port > 0 else []) + ([service_name + '-caddy-socket-proxy.socket'] if service_container_http_port > 0 else [])
+ ([service_name + '-oauth2-proxy-socat.socket'] if _service_oauth2_proxy else []) + ([service_name + '-oauth2-proxy.socket'] if _service_oauth2_proxy else [])
+ _service_container_publish_socket_ports
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('regex_replace', '$', '-socat.socket')
+ _service_additional_containers + _service_additional_containers
| map(attribute='name') | map(attribute='name')
| map('regex_replace', '$', '.service') | map('regex_replace', '$', '.service')

View File

@@ -1,6 +1,12 @@
--- ---
_service_container_socket_mount:
type: bind
source: /run/{{ service_name }}.sock
destination: /run/{{ service_name }}.sock
_service_volume_mounts: "{{ service_container_mounts | selectattr('type', '==', 'volume') }}" _service_volume_mounts: "{{ service_container_mounts | selectattr('type', '==', 'volume') }}"
_service_template_mounts: "{{ service_container_mounts | selectattr('type', '==', 'template') }}" _service_template_mounts: "{{ service_container_mounts | selectattr('type', '==', 'template') }}"
_service_copy_mounts: "{{ service_container_mounts | selectattr('type', '==', 'copy') }}"
_service_host_directory: "/srv/{{ service_name }}" _service_host_directory: "/srv/{{ service_name }}"
_service_container_volume_mounts: >- _service_container_volume_mounts: >-
@@ -14,7 +20,11 @@ _service_container_volume_mounts: >-
) | ) |
map('combine') map('combine')
}} }}
_service_container_bind_mounts: "{{ service_container_mounts | selectattr('type', '==', 'bind') }}" _service_container_bind_mounts: >-
{{
service_container_mounts | selectattr('type', '==', 'bind') +
([ _service_container_socket_mount ] if _service_native_socket else [])
}}
_service_container_template_mounts: >- _service_container_template_mounts: >-
{{ {{
([{'readonly': true}] * _service_template_mounts | length) | ([{'readonly': true}] * _service_template_mounts | length) |
@@ -30,12 +40,28 @@ _service_container_template_mounts: >-
) | ) |
map('combine') map('combine')
}} }}
_service_container_copy_mounts: >-
{{
([{'readonly': true}] * _service_copy_mounts | length) |
zip(
_service_copy_mounts |
community.general.remove_keys(['mode']),
_service_copy_mounts |
map(attribute='source') |
map('regex_replace', '\/$', '') |
map('regex_replace', '^', _service_host_directory ~ '/mounts/') |
map('community.general.dict_kv', 'source'),
([{'type': 'bind'}] * _service_copy_mounts | length)
) |
map('combine')
}}
_service_container_mounts: >- _service_container_mounts: >-
{{ {{
_service_container_volume_mounts + _service_container_volume_mounts +
_service_container_bind_mounts + _service_container_bind_mounts +
_service_container_template_mounts _service_container_template_mounts +
_service_container_copy_mounts
}} }}
@@ -45,7 +71,7 @@ _service_all_template_mounts: >-
_service_template_mounts + _service_template_mounts +
( (
_service_additional_containers | _service_additional_containers |
map(attribute='mounts') | map(attribute='mounts', default=[]) |
flatten flatten
) )
) | ) |
@@ -67,3 +93,23 @@ _service_all_template_mount_host_files: >-
map('regex_replace', '\.j2$', '') | map('regex_replace', '\.j2$', '') |
map('regex_replace', '^', _service_host_directory ~ '/mounts/') map('regex_replace', '^', _service_host_directory ~ '/mounts/')
}} }}
_service_all_copy_mounts: >-
{{
(
_service_copy_mounts +
(
_service_additional_containers |
map(attribute='mounts', default=[]) |
flatten
)
) |
selectattr('type', '==', 'copy') |
unique
}}
_service_all_copy_mount_host_files: >-
{{
_service_all_copy_mounts |
map(attribute='source') |
map('regex_replace', '^', _service_host_directory ~ '/mounts/')
}}

View File

@@ -0,0 +1,55 @@
---
_service_plain_container:
image: "{{ service_container_image }}"
entrypoint: "{{ service_container_entrypoint }}"
command: "{{ service_container_command }}"
user: "{{ service_container_user }}"
env: "{{ service_container_env }}"
mounts: "{{ _service_container_mounts }}"
_service_pinp_container_mounts:
- type: bind
source: "{{ _service_host_directory }}/mounts/entrypoint.sh"
destination: /entrypoint.sh
readonly: true
- type: volume
source: "containers"
destination: /home/podman/.local/share/containers
_service_pinp_container:
image: quay.io/podman/stable:latest
entrypoint: /entrypoint.sh
command: []
user: podman
env: {}
mounts: >-
{{
_service_pinp_container_mounts
+ (
_service_container_mounts
| zip(
_service_container_mounts
| map(attribute='source')
| map('replace', '/', '_')
| map('regex_replace', '^', '/mounts/')
| map('community.general.dict_kv', 'destination')
)
| map('combine')
)
}}
_service_container: >-
{{ _service_pinp_container if service_container_pinp else _service_plain_container }}
_service_container_pinp_inner_mounts: >-
{{
_service_container_mounts
| zip(
_service_container_mounts
| map(attribute='source')
| map('replace', '/', '_')
| map('regex_replace', '^', '/mounts/')
| map('community.general.dict_kv', 'source')
)
| map('combine')
}}

View File

@@ -1,11 +1,16 @@
--- ---
_service_native_socket: "{{ service_domains | length > 0 and service_container_http_port == 0 }}"
_service_socket_path: >-
/run/{{ service_name ~ ('-caddy-socket-proxy' if not _service_native_socket else '' ) }}.sock
_service_replacement_host_header: _service_replacement_host_header:
Host: "{{ service_name }}:{{ service_container_http_port }}" Host: "{{ service_name }}:{{ service_container_http_port }}"
_service_proxy_headers: "{{ _service_replacement_host_header if not service_proxy_pass_host_header else {} }}" _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_proxy: "{{ service_proxy_auth_type == 'oauth2-proxy' }}"
_service_oauth2_socket: >- _service_oauth2_socket: >-
{{ '/run/' ~ service_name ~ '-oauth2-proxy-socat.sock' if _service_oauth2_proxy else '' }} {{ '/run/' ~ service_name ~ '-oauth2-proxy.sock' if _service_oauth2_proxy else '' }}
_service_oauth2_proxy_location: _service_oauth2_proxy_location:
path: /oauth2/* path: /oauth2/*
proxy_target_socket: "{{ _service_oauth2_socket }}" proxy_target_socket: "{{ _service_oauth2_socket }}"

View File

@@ -0,0 +1,21 @@
---
_service_container_publish_ports_with_defaults: >-
{{
([{ 'type': 'port', 'host_address': '0.0.0.0' }] * service_container_publish_ports | length)
| zip(service_container_publish_ports)
| map('combine')
}}
_service_container_publish_socket_ports: >-
{{ _service_container_publish_ports_with_defaults | selectattr('type', '==', 'socket') }}
_service_container_publish_port_ports: >-
{{ _service_container_publish_ports_with_defaults | selectattr('type', '==', 'port') }}
_service_container_publish_ports: >-
{{
_service_container_publish_port_ports | map(attribute='host_address') |
zip(
_service_container_publish_port_ports | map(attribute='host_port'),
_service_container_publish_port_ports | map(attribute='container_port')
) | map('join', ':')
}}

View File

@@ -19,4 +19,17 @@ _service_container_secrets: >-
'target': service_database_secret_target 'target': service_database_secret_target
}] if _service_setup_database else [] }] if _service_setup_database else []
) )
+ (
[{
'name': _service_database_name ~ '-url',
'value':
'postgres://'
~ service_name | replace('-', '_')
~ ':' ~ service_podman_secrets[service_name ~ '-postgres']
~ '@postgres/' ~ service_name | replace('-', '_')
~ '?sslmode=disable',
'type': service_database_secret_type,
'target': service_database_secret_target ~ '-url'
}] if service_podman_secrets[service_name ~ '-postgres'] is defined else []
)
}} }}

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

@@ -0,0 +1 @@
Sets up a socat container along with a systemd socket unit to forward traffic to it

View File

@@ -0,0 +1,4 @@
---
socat_target_container: "{{ socat_service_name }}"
socat_container_ip: ""
socat_auto_update: true

View File

@@ -0,0 +1,28 @@
---
argument_specs:
main:
description: Sets up a socat container along with a systemd socket unit to forward traffic to it
options:
socat_service_name:
description: Name of the socat service, used for systemd unit and container naming
type: str
required: true
socat_target_container:
description: Name of the container to forward traffic to
type: str
required: false
default: "{{ socat_service_name }}"
socat_target_http_port:
description: Port on the target container to forward traffic to
type: int
required: true
socat_container_ip:
description: IP address to assign to the socat container.
type: str
required: false
default: ""
socat_auto_update:
description: Whether to automatically update the socat container
type: bool
required: false
default: true

View File

@@ -1,10 +1,11 @@
--- ---
- name: Socat socket for {{ socat_service_name }} - name: Socat socket for {{ socat_service_name }}
ansible.builtin.template: ansible.builtin.import_role:
src: socat.socket.j2 name: uumas.general.systemd_socket
dest: /etc/systemd/system/{{ socat_service_name }}-socat.socket vars:
mode: "0644" systemd_socket_name: "{{ socat_service_name }}-socat"
notify: Restart socat socket for {{ socat_service_name }} systemd_socket_requires:
- "{{ socat_target_container }}.service"
- name: Socat container for {{ socat_service_name }} - name: Socat container for {{ socat_service_name }}
ansible.builtin.import_role: ansible.builtin.import_role:
@@ -14,13 +15,13 @@
container_image: "docker.io/alpine/socat:latest" container_image: "docker.io/alpine/socat:latest"
container_command: container_command:
- "ACCEPT-FD:3,fork" - "ACCEPT-FD:3,fork"
- "TCP:{{ socat_service_name }}:{{ socat_target_http_port }}" - "TCP:{{ socat_target_container }}:{{ socat_target_http_port }}"
container_user: nobody container_user: nobody
container_networks: container_networks:
- "{{ socat_service_name }}" - name: "{{ socat_service_name }}-socat"
container_ip: "{{ socat_container_ip }}" ip: "{{ socat_container_ip }}"
container_requires: container_requires:
- "{{ socat_service_name }}-socat.socket" - "{{ socat_service_name }}-socat.socket"
- "{{ socat_service_name }}.service" - "{{ socat_target_container }}.service"
container_auto_start: false container_auto_start: false
container_auto_update: "{{ service_auto_update }}" container_auto_update: "{{ socat_auto_update }}"

View File

@@ -82,7 +82,7 @@
vhost_domains: vhost_domains:
- "{{ synapse_external_domain }}:8448" - "{{ synapse_external_domain }}:8448"
vhost_proxy_target_netproto: unix vhost_proxy_target_netproto: unix
vhost_proxy_target_socket: "/run/synapse-socat.sock" vhost_proxy_target_socket: "/run/synapse-caddy-socket-proxy.sock"
- name: Open port for synapse federation - name: Open port for synapse federation
ansible.posix.firewalld: ansible.posix.firewalld:

View File

@@ -27,7 +27,7 @@ listeners:
database: database:
name: psycopg2 name: psycopg2
args: args:
host: synapse-postgres host: postgres
user: synapse user: synapse
password: "{{ service_podman_secrets['synapse-postgres'] }}" password: "{{ service_podman_secrets['synapse-postgres'] }}"
dbname: synapse dbname: synapse

View File

@@ -1,7 +1,6 @@
--- ---
- name: "Restart volume service {{ volume_name }}" - name: "Restart volume service {{ volume_name }}"
ansible.builtin.systemd_service: ansible.builtin.set_fact:
name: "{{ volume_name }}-volume.service" systemd_restart_units: "{{ systemd_restart_units + [volume_name ~ '-volume.service'] }}" # noqa: var-naming[no-role-prefix]
state: restarted changed_when: true
daemon_reload: true notify: Apply systemd unit restarts
ignore_errors: "{{ ansible_check_mode }}"

View File

@@ -9,4 +9,6 @@
state: quadlet state: quadlet
quadlet_file_mode: "0644" quadlet_file_mode: "0644"
quadlet_options: "{{ _volume_quadlet_options }}" quadlet_options: "{{ _volume_quadlet_options }}"
notify: Restart volume service {{ volume_name }} notify:
- Reload systemd daemon
- Restart volume service {{ volume_name }}

View File

@@ -0,0 +1,9 @@
---
argument_specs:
main:
description: Installs windmill with worker in podman in podman
options:
windmill_domain:
description: The domain to use for windmill
type: str
required: true

View File

@@ -0,0 +1,62 @@
---
- name: Windmill service
ansible.builtin.import_role:
name: service
vars:
service_name: windmill
service_container_image: ghcr.io/windmill-labs/windmill:main
service_container_mounts:
- type: volume
source: worker-logs
destination: /tmp/windmill/logs
service_container_http_port: 8000
service_domains:
- "{{ windmill_domain }}"
service_database_type: postgres
service_container_env:
DATABASE_URL_FILE: /run/secrets/postgres-url
MODE: server
service_additional_containers:
- name: worker
pinp: true
mounts:
- type: volume
source: worker-logs
destination: /tmp/windmill/logs
- type: volume
source: worker-dependency-cache
destination: /tmp/windmill/cache
env:
DATABASE_URL_FILE: /run/secrets/postgres-url
MODE: worker
WORKER_GROUP: default
ENABLE_UNSHARE_PID: "true"
UNSHARE_ISOLATION_FLAGS: "--user --map-root-user --pid --fork"
- name: worker-native
env:
DATABASE_URL_FILE: /run/secrets/postgres-url
MODE: worker
WORKER_TYPE: native
NATIVE_MODE: "true"
NUM_WORKERS: "8"
SLEEP_QUEUE: "200"
- name: lsp
image: ghcr.io/windmill-labs/windmill-extra:latest
secrets: []
mounts:
- type: volume
source: lsp-cache
destination: /puls/.cache
publish_ports:
- name: lsp
type: socket
container_port: 3001
env:
ENABLE_LSP: "true"
ENABLE_MULTIPLAYER: "false"
ENABLE_DEBUGGER: "false"
WINDMILL_BASE_URL: http://windmill:8000
service_vhost_locations:
- path: /ws/*
proxy_target_socket: /run/windmill-lsp-socat.sock