diff --git a/.gitignore b/.gitignore index e942f6e..780d380 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,19 @@ +# ansible + /.ansible -/.dev /inventory + +# custom stacks + +/custom + +/roles/karo-compose/defaults/main/* +!/roles/karo-compose/defaults/main/main.yml + +/roles/karo-compose/templates/* +!/roles/karo-compose/templates/.gitkeep + +# misc + +/.dev /cspell.json diff --git a/ansible.cfg b/ansible.cfg index e8077ef..4ad1c0d 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -3,7 +3,7 @@ inventory = inventory/hosts.ini interpreter_python = /usr/bin/python3 vault_password_file = /run/user/1000/karo/ansible/vault_pass local_tmp = /run/user/1000/karo/ansible/tmp -editor = micro -backup false +editor = micro -backup false -scrollbar true -statusline false [privilege_escalation] become_ask_pass = false diff --git a/justfile b/justfile index 9fd8797..4b49ce3 100644 --- a/justfile +++ b/justfile @@ -9,10 +9,16 @@ help: @{{ just_executable() }} --list --unsorted --list-prefix " - " --justfile "{{ justfile() }}" +# (Internal use) Reusable confirmation statement +[confirm("proceed? (y/N)")] +_confirm: + @echo + # preseed # Host preseed.cfg +[group('Debian install')] @preseed platform='server': # check user input for platform [ "{{platform}}" = "server" ] || [ "{{platform}}" = "desktop" ] || { echo "platform must be 'server' or 'desktop'" >&2; exit 1; } @@ -38,6 +44,7 @@ _host-preseed platform: # server # Setup a system +[group('System setup')] @install hostname='': _check-password ansible-playbook run.yml --tags install --limit "{{hostname}}" @@ -45,6 +52,7 @@ _host-preseed platform: # compose # Deploy/remove stacks +[group('System setup')] [arg("stack", long, short="s")] compose action hostname='' stack='all': _check-password #!/bin/bash @@ -56,6 +64,8 @@ compose action hostname='' stack='all': _check-password else echo "action must be 'up' or 'down'" >&2; exit 1; fi + # manage symlinks + just _custom-symlinks # run user action ANSIBLE_DISPLAY_SKIPPED_HOSTS=false ansible-playbook run.yml \ --extra-vars "karo_compose_justfile_stack={{stack}}" \ @@ -69,6 +79,7 @@ compose action hostname='' stack='all': _check-password password := "/run/user/1000/karo/ansible/vault_pass" # Manage a vault +[group('Ansible vault')] vault hostname: #!/bin/bash # check password file exists @@ -91,6 +102,7 @@ _check-password: @[ -e "{{password}}" ] || micro -backup false -mkparents true "{{password}}" # Set password +[group('Ansible vault')] password: @micro -backup false -mkparents true "{{password}}" @@ -98,8 +110,76 @@ password: # wireguard # Generate key pair -wireguard: +_wireguard: @priv="$(wg genkey)"; \ pub="$(wg pubkey <<<"$priv")"; \ printf 'Private key: %s\n' "$priv"; \ printf 'Public key: %s\n' "$pub" + + +# custom + +# Manage custom repos +[group('Custom repos')] +custom action username: + #!/usr/bin/env bash + set -euo pipefail + case "{{action}}" in + add) + echo "adding karo-custom {{username}}" + just _custom-add {{username}} + ;; + remove) + echo "removing karo-custom {{username}}" + just _custom-remove {{username}} + ;; + *) + echo "action must be 'add' or 'remove'" >&2; exit 1; + ;; + esac + +repo_name := "karo-custom" + +# (Internal use) Add new karo-custom repo +_custom-add username: + #!/usr/bin/env bash + set -euo pipefail + # clone karo-custom git repo + git clone git@github.com:{{username}}/{{repo_name}}.git custom/{{username}} + # check karo-custom repo dir not empty + if [ -z "$(find custom/{{username}}/karo-compose -mindepth 1 -maxdepth 1)" ]; then + exit 0 + else + # manage symlinks + just _custom-symlinks + # list stack groups for username + echo "new karo-compose stack groups:" + ls -1 roles/karo-compose/templates | grep {{username}} | awk '{print "- " $0}' + fi + +# (Internal use) Remove existing karo-custom repo +@_custom-remove username: + # check username dir exists + test -d "custom/{{username}}" + # confirm removal + just _confirm + # remove custom dir + rm -rf "custom/{{username}}" + # manage symlinks + just _custom-symlinks + +# (Internal use) Manage symbolic links for custom files +_custom-symlinks: + #!/usr/bin/env bash + set -euo pipefail + # clear existing symlinks + find roles/karo-compose/defaults/main/ -mindepth 1 -maxdepth 1 ! -name "main.yml" -delete + find roles/karo-compose/templates/ -mindepth 1 -maxdepth 1 ! -name ".gitkeep" -delete + # check custom dir not empty + if [ -z "$(find custom -mindepth 1 -maxdepth 1)" ]; then + exit 0 + else + # symlink custom files + ln -sr custom/*/karo-compose/defaults/main/*/ roles/karo-compose/defaults/main/ + ln -sr custom/*/karo-compose/templates/*/ roles/karo-compose/templates/ + fi diff --git a/roles/karo-compose/defaults/main.yml b/roles/karo-compose/defaults/main.yml deleted file mode 100644 index e4ece33..0000000 --- a/roles/karo-compose/defaults/main.yml +++ /dev/null @@ -1,264 +0,0 @@ -# SPDX-FileCopyrightText: 2025 - 2026 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - ---- - -karo_compose_justfile_stack: all - -# docker will start stacks in this order -# docker will stop stacks in reverse order -karo_compose_stacks: - core: - - traefik - - pocketid - extra: - - proxy - - whoami - - godns - - gluetun - - qbittorrent - - prowlarr - - radarr - - sonarr - - seerr - - jellyfin - - ntfy - - glance - -karo_compose_restart_policy: unless-stopped -karo_compose_timezone: "Europe/London" - -karo_compose_root_domain: example.com - -# oidc - -karo_compose_oidc_subdomain: auth -karo_compose_oidc_domain: "{{ karo_compose_oidc_subdomain }}.{{ karo_compose_root_domain }}" # e.g. auth.example.com -karo_compose_oidc_url: "https://{{ karo_compose_oidc_domain }}" # e.g. https://auth.example.com - -karo_compose_oidc_authorization_path: "/authorize" -karo_compose_oidc_authorization_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_authorization_path }}" - -karo_compose_oidc_discovery_path: "/.well-known/openid-configuration" -karo_compose_oidc_discovery_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_discovery_path }}" - -karo_compose_oidc_token_path: "/api/oidc/token" -karo_compose_oidc_token_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_token_path }}" - -karo_compose_oidc_userinfo_path: "/api/oidc/userinfo" -karo_compose_oidc_userinfo_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_userinfo_path }}" - -karo_compose_oidc_logout_path: "/api/oidc/end-session" -karo_compose_oidc_logout_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_logout_path }}" - -karo_compose_oidc_certificate_path: "/.well-known/jwks.json" -karo_compose_oidc_certificate_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_certificate_path }}" - -karo_compose_oidc_provider_name: "PocketID" - -karo_compose_oidc_admin_group: "admin" - -# core stacks -# --- - -# pocketid - -karo_compose_pocketid_enabled: true -karo_compose_pocketid_image: ghcr.io/pocket-id/pocket-id -karo_compose_pocketid_version: v2.6.2@sha256:1cc51e5b36fa7ec31368d3efb24ba362be6dce20f3cc0a2852843527bd5110f0 -karo_compose_pocketid_log_level: info # debug, info, warn, error - -karo_compose_pocketid_secrets: - pocketid_encryption_key: "" # `openssl rand -base64 32` - pocketid_maxmind_license_key: "" - -# traefik - -karo_compose_traefik_enabled: true -karo_compose_traefik_image: docker.io/traefik -karo_compose_traefik_version: v3.6.15@sha256:8cb20d16e01a53d8d7f7696ac2f1af7d200d5c9984d226ce2299731d9eab6d6c -karo_compose_traefik_dashboard_domain: "traefik.{{ karo_compose_root_domain }}" -karo_compose_traefik_dashboard_forward_auth_enabled: true -karo_compose_traefik_log_level: debug # trace, debug, info, warn, error, fatal, panic - -karo_compose_traefik_dashboard_enabled: true - -karo_compose_traefik_custom_domains_raw: | - # - main: "*.int.{{ karo_compose_root_domain }}" - -karo_compose_traefik_acme_staging_enabled: true - -karo_compose_traefik_acme_ca_server_url: "https://acme-v02.api.letsencrypt.org/directory" # "https://acme.zerossl.com/v2/DV90" -karo_compose_traefik_acme_staging_ca_server_url: "https://acme-staging-v02.api.letsencrypt.org/directory" - -karo_compose_traefik_acme_email: cert@example.com - -karo_compose_traefik_secrets: - traefik_acme_zone_api_token: "" - traefik_acme_dns_api_token: "" - tinyauth_oidc_client_secret: "" - -# traefik tinyauth - -karo_compose_traefik_tinyauth_image: ghcr.io/steveiliop56/tinyauth -karo_compose_traefik_tinyauth_version: v5.0.7-distroless@sha256:e31ae2bac566cfe4687a8a54121a884ce25c4869995075f8e7e3d352bcd59241 -karo_compose_traefik_tinyauth_domain: "tinyauth.{{ karo_compose_root_domain }}" -karo_compose_traefik_tinyauth_log_level: info # trace, debug, info, warn, error, fatal, panic - -karo_compose_traefik_tinyauth_oidc_client_id: "" - -# traefik cetusguard - -karo_compose_traefik_cetusguard_image: docker.io/hectorm/cetusguard -karo_compose_traefik_cetusguard_version: v1.1.3@sha256:b3dc5d2042869dd714c75985d94bb8c259df53b325ec6b1a2b57bcc3eac17689 -karo_compose_traefik_cetusguard_log_level: 7 # 0-7 (min to max verbosity) - -# extra stacks -# --- - -# glance - -karo_compose_glance_enabled: false -karo_compose_glance_image: docker.io/glanceapp/glance -karo_compose_glance_version: v0.8.4@sha256:6df86a7e8868d1eda21f35205134b1962c422957e42a0c44d4717c8e8f741b1a -karo_compose_glance_domain: "glance.{{ karo_compose_root_domain }}" -karo_compose_glance_forward_auth_enabled: true - -karo_compose_glance_config_raw: | - # glance config - -# gluetun - -karo_compose_gluetun_enabled: false -karo_compose_gluetun_image: docker.io/qmcgaw/gluetun -karo_compose_gluetun_version: v3.41.1@sha256:1a5bf4b4820a879cdf8d93d7ef0d2d963af56670c9ebff8981860b6804ebc8ab -karo_compose_gluetun_log_level: info # debug, info, warning, error - -karo_compose_gluetun_firewall_vpn_input_ports: "" - -karo_compose_gluetun_secrets: - gluetun_wireguard_config_raw: | - # wireguard config - -# godns - -karo_compose_godns_enabled: false -karo_compose_godns_image: docker.io/timothyye/godns -karo_compose_godns_version: v3.4.1@sha256:d9a043164b256ab9d7ab03a7619bfe2292a2ab2b6e5e3151e818edb63249b1de -karo_compose_godns_debug_info_enabled: false - -karo_compose_godns_subdomain: home - -karo_compose_godns_secrets: - godns_dns_api_token: "" - -# jellyfin - -karo_compose_jellyfin_enabled: false -karo_compose_jellyfin_image: docker.io/jellyfin/jellyfin -karo_compose_jellyfin_version: 10.11.8@sha256:1694ff069f0c9dafb283c36765175606866769f5d72f2ed56b6a0f1be922fc37 -karo_compose_jellyfin_domain: "jellyfin.{{ karo_compose_root_domain }}" - -karo_compose_jellyfin_movies_media_path: "" # e.g. /media/drive1/data/media/movies -karo_compose_jellyfin_series_media_path: "" # e.g. /media/drive1/data/media/series - -# ntfy - -karo_compose_ntfy_enabled: false -karo_compose_ntfy_image: docker.io/binwiederhier/ntfy -karo_compose_ntfy_version: v2.22.0@sha256:d8b3408c75d5ae78e03339af6103e7e6195e56573eea6f1ad34bb4dc4f44f340 -karo_compose_ntfy_domain: "ntfy.{{ karo_compose_root_domain }}" -karo_compose_ntfy_log_level: info # trace, debug, info, warn, error - -karo_compose_ntfy_auth_users: "" # https://docs.ntfy.sh/config/#users-via-the-config -karo_compose_ntfy_auth_access: "" # https://docs.ntfy.sh/config/#acl-entries-via-the-config -karo_compose_ntfy_auth_tokens: "" # https://docs.ntfy.sh/config/#tokens-via-the-config - -# prowlarr - -karo_compose_prowlarr_enabled: false -karo_compose_prowlarr_image: docker.io/linuxserver/prowlarr -karo_compose_prowlarr_version: 2.3.5.5327-ls144@sha256:b4204e18666179472225935b443a99cf6c66dcb7bbc2d35034427a3851f13135 -karo_compose_prowlarr_domain: "prowlarr.{{ karo_compose_root_domain }}" -karo_compose_prowlarr_forward_auth_enabled: true - -# proxy - -karo_compose_proxy_client_enabled: false -karo_compose_proxy_server_enabled: false - -karo_compose_proxy_enabled: "{{ karo_compose_proxy_server_enabled }}" - -karo_compose_proxy_client_wireguard_ipv4: 10.25.0.2 -karo_compose_proxy_server_wireguard_ipv4: 10.25.0.1 - -karo_compose_proxy_server_wireguard_port: 52525 - -# proxy haproxy - -karo_compose_proxy_haproxy_image: docker.io/haproxy -karo_compose_proxy_haproxy_version: 3.2.17-alpine@sha256:0c32ec9e64a3bbacb2a1a28afbc3d46032d905bf74ccb745b45488ff41ee9a6b - -karo_compose_proxy_haproxy_config_raw: | - # haproxy config - -# qbittorrent - -karo_compose_qbittorrent_enabled: false -karo_compose_qbittorrent_image: docker.io/qbittorrentofficial/qbittorrent-nox -karo_compose_qbittorrent_version: 5.1.4-1@sha256:41888f4e05646ace98aeaff01a7bbec714e2e6082ab273fd4af0d1210352ea2c -karo_compose_qbittorrent_domain: "qbittorrent.{{ karo_compose_root_domain }}" - -karo_compose_qbittorrent_downloads_path: "" # e.g. /media/drive1/data/torrents - -karo_compose_qbittorrent_webui_enabled: true - -karo_compose_qbittorrent_secrets: - qui_session_secret: "" # `openssl rand -hex 16` - qui_oidc_client_secret: "" - -# qbittorrent qui - -karo_compose_qbittorrent_qui_image: ghcr.io/autobrr/qui -karo_compose_qbittorrent_qui_version: v1.17.0@sha256:fb3832e68f66b056e1b049d16c40732661e7b73999bc642d4b11469a3ebbabd3 -karo_compose_qbittorrent_qui_domain: "qui.{{ karo_compose_root_domain }}" -karo_compose_qbittorrent_qui_log_level: info # trace, debug, info, warn, error - -karo_compose_qbittorrent_qui_oidc_client_id: "" - -# radarr - -karo_compose_radarr_enabled: false -karo_compose_radarr_image: docker.io/linuxserver/radarr -karo_compose_radarr_version: 6.1.1.10360-ls300@sha256:b01097ad2d948c9f5eca39eb60bb529e2e55b0738c4bf7db09383bef0abab59d -karo_compose_radarr_domain: "radarr.{{ karo_compose_root_domain }}" -karo_compose_radarr_forward_auth_enabled: true - -karo_compose_radarr_data_path: "" # e.g. /media/drive1/data - -# seerr - -karo_compose_seerr_enabled: false -karo_compose_seerr_image: ghcr.io/seerr-team/seerr -karo_compose_seerr_version: preview-new-oidc@sha256:a14a9c326881fdce70b00df3fc095f5b5b128f5552677f677cd3788e757fb4e6 -karo_compose_seerr_domain: "seerr.{{ karo_compose_root_domain }}" -karo_compose_seerr_log_level: info # debug, info, warn, error - -# sonarr - -karo_compose_sonarr_enabled: false -karo_compose_sonarr_image: docker.io/linuxserver/sonarr -karo_compose_sonarr_version: 4.0.17.2952-ls309@sha256:3580aec3802c915f0f819a88d5099abce61734b925732b8393d176b5dc561020 -karo_compose_sonarr_domain: "sonarr.{{ karo_compose_root_domain }}" -karo_compose_sonarr_forward_auth_enabled: true - -karo_compose_sonarr_data_path: "" # e.g. /media/drive1/data - -# whoami - -karo_compose_whoami_enabled: false -karo_compose_whoami_image: docker.io/traefik/whoami -karo_compose_whoami_version: v1.11.0@sha256:200689790a0a0ea48ca45992e0450bc26ccab5307375b41c84dfc4f2475937ab -karo_compose_whoami_domain: "whoami.{{ karo_compose_root_domain }}" -karo_compose_whoami_forward_auth_enabled: false diff --git a/roles/karo-compose/defaults/main/main.yml b/roles/karo-compose/defaults/main/main.yml new file mode 100644 index 0000000..ee89610 --- /dev/null +++ b/roles/karo-compose/defaults/main/main.yml @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 - 2026 hazzuk +# +# SPDX-License-Identifier: AGPL-3.0-only + +--- + +# stacks + +# docker will start stacks in this order +# docker will stop stacks in reverse order +# karo_compose_stack_groups: +# - hazzuk_core +# - hazzuk_extra + +# common + +karo_compose_restart_policy: unless-stopped + +karo_compose_timezone: "Europe/London" + +karo_compose_root_domain: example.com + +# auth + +karo_compose_oidc_provider_name: PocketID + +karo_compose_oidc_admin_group: admin + +karo_compose_oidc_subdomain: auth + +karo_compose_oidc_domain: "{{ karo_compose_oidc_subdomain }}.{{ karo_compose_root_domain }}" # e.g. auth.example.com + +karo_compose_oidc_url: "https://{{ karo_compose_oidc_domain }}" # e.g. https://auth.example.com + +karo_compose_oidc_authorization_path: "/authorize" +karo_compose_oidc_authorization_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_authorization_path }}" + +karo_compose_oidc_discovery_path: "/.well-known/openid-configuration" +karo_compose_oidc_discovery_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_discovery_path }}" + +karo_compose_oidc_token_path: "/api/oidc/token" +karo_compose_oidc_token_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_token_path }}" + +karo_compose_oidc_userinfo_path: "/api/oidc/userinfo" +karo_compose_oidc_userinfo_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_userinfo_path }}" + +karo_compose_oidc_logout_path: "/api/oidc/end-session" +karo_compose_oidc_logout_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_logout_path }}" + +karo_compose_oidc_certificate_path: "/.well-known/jwks.json" +karo_compose_oidc_certificate_url: "{{ karo_compose_oidc_url }}{{ karo_compose_oidc_certificate_path }}" + +# internal + +karo_compose_justfile_stack: all diff --git a/roles/karo-compose/tasks/deploy.yml b/roles/karo-compose/tasks/deploy.yml index e608884..e17971f 100644 --- a/roles/karo-compose/tasks/deploy.yml +++ b/roles/karo-compose/tasks/deploy.yml @@ -4,18 +4,22 @@ --- -- name: "Create compose stack directory for {{ stack }}" +- name: "Create compose stack directory for {{ stack.name }}" ansible.builtin.file: - path: "/srv/docker/{{ group }}/{{ stack }}" + path: "/srv/docker/{{ stack.path }}" mode: "0774" state: directory -- name: "Deploy compose templates for {{ stack }}" +- name: "Merge compose variables for {{ stack.name }}" + ansible.builtin.set_fact: + compose: "{{ stack_defaults | combine(stack_vars, recursive=true) }}" + +- name: "Deploy compose templates for {{ stack.name }}" ansible.builtin.template: src: "{{ template.src }}" - dest: "/srv/docker/{{ group }}/{{ stack }}/{{ template.path | splitext | first }}" + dest: "/srv/docker/{{ stack.path }}/{{ template.path | splitext | first }}" mode: "0644" + loop: "{{ query('filetree', '../templates/' ~ stack.path) }}" loop_control: loop_var: template label: "{{ template.path }}" - with_community.general.filetree: ../templates/{{ group }}/{{ stack }}/ diff --git a/roles/karo-compose/tasks/down.yml b/roles/karo-compose/tasks/down.yml index bf4a3e7..b832307 100644 --- a/roles/karo-compose/tasks/down.yml +++ b/roles/karo-compose/tasks/down.yml @@ -4,8 +4,8 @@ --- -- name: Down compose stack {{ stack }} +- name: Down compose stack {{ stack.name }} community.docker.docker_compose_v2: docker_host: unix:///run/user/1001/docker.sock - project_src: "/srv/docker/{{ group }}/{{ stack }}" + project_src: "/srv/docker/{{ stack.path }}" state: absent # docker compose down diff --git a/roles/karo-compose/tasks/main.yml b/roles/karo-compose/tasks/main.yml index 672e822..a77e555 100644 --- a/roles/karo-compose/tasks/main.yml +++ b/roles/karo-compose/tasks/main.yml @@ -6,7 +6,9 @@ --- -- name: Check docker directory status +# Setup + +- name: Register docker directory status ansible.builtin.stat: path: /srv/docker register: docker_dir @@ -17,28 +19,58 @@ fail_msg: "Docker directory not present, run `just install` to setup Docker." quiet: true -- name: Define expanded stack list - ansible.builtin.set_fact: - karo_compose_stacks_expanded: >- - {{ karo_compose_stacks_expanded | default([]) + [ - { - 'group': item.0.key, - 'name': item.1 - } - ] }} - loop: "{{ karo_compose_stacks | dict2items | subelements('value') }}" +- name: Assert stack group names + ansible.builtin.assert: + that: + - stack_group_lookup is not false + - stack_group_lookup | type_debug == "list" + fail_msg: "Invalid stack group '{{ stack_group }}' in 'karo_compose_stack_groups'." + quiet: true + loop: "{{ karo_compose_stack_groups }}" + loop_control: + loop_var: stack_group + label: "karo_compose_stack_groups -> {{ stack_group }}" + vars: + stack_group_lookup: "{{ lookup('vars', stack_group, default=false) }}" + +- name: Assert imported stack groups + ansible.builtin.assert: + that: stack_group_dir.path in karo_compose_stack_groups + fail_msg: "Stack group '{{ stack_group_dir.path }}' missing from 'karo_compose_stack_groups'." + quiet: true + loop: "{{ query('filetree', '../templates', exclude='^\\.gitkeep$') }}" loop_control: - label: "{{ item.0.key }}/{{ item.1 }}" + loop_var: stack_group_dir + label: "templates/{{ stack_group_dir.path }}/" + +- name: Set expanded stack list + ansible.builtin.set_fact: + karo_compose_expanded_stacks: | + {% set expanded_stacks = [] %} + {% for group in karo_compose_stack_groups %} + {% for stack in lookup('vars', group) %} + {% set _ = expanded_stacks.append({ + 'enabled': lookup('vars', group ~ '_' ~ stack ~ '_enabled'), + 'group': group, + 'name': stack, + 'namespace': group ~ '_' ~ stack, + 'path': group ~ '/' ~ stack, + }) %} + {% endfor %} + {% endfor %} + {{ expanded_stacks }} -- name: Assert stack name +- name: Assert provided stack name ansible.builtin.assert: that: karo_compose_justfile_stack in ( - karo_compose_stacks_expanded | map(attribute='name') | list + karo_compose_expanded_stacks | map(attribute='name') | list + ['all'] ) fail_msg: "Invalid stack name '{{ karo_compose_justfile_stack }}'." quiet: true +# Actions + - name: Down stacks become: true become_user: dockeruser @@ -46,15 +78,13 @@ block: - name: Down docker compose stacks ansible.builtin.include_tasks: down.yml - loop: "{{ karo_compose_stacks_expanded | reverse }}" + loop: "{{ karo_compose_expanded_stacks | reverse }}" loop_control: - label: "{{ item.name }}" - vars: - stack: "{{ item.name }}" - group: "{{ item.group }}" + loop_var: stack + label: "{{ stack.name }}" when: - - lookup('vars', 'karo_compose_' ~ stack ~ '_enabled') | default(false) - - karo_compose_justfile_stack in [stack, 'all'] + - stack.enabled + - karo_compose_justfile_stack in [stack.name, 'all'] - name: Deploy stacks become: true @@ -63,15 +93,16 @@ block: - name: Deploy docker compose stacks ansible.builtin.include_tasks: deploy.yml - loop: "{{ karo_compose_stacks_expanded }}" + loop: "{{ karo_compose_expanded_stacks }}" loop_control: - label: "{{ item.name }}" + loop_var: stack + label: "{{ stack.name }}" vars: - stack: "{{ item.name }}" - group: "{{ item.group }}" + stack_vars: "{{ lookup('vars', stack.namespace ~ '_stack') }}" + stack_defaults: "{{ lookup('vars', stack.namespace ~ '_stack_defaults') }}" when: - - lookup('vars', 'karo_compose_' ~ stack ~ '_enabled') | default(false) - - karo_compose_justfile_stack in [stack, 'all'] + - stack.enabled + - karo_compose_justfile_stack in [stack.name, 'all'] - name: Create stacks become: true @@ -80,16 +111,15 @@ block: - name: Up docker compose stacks ansible.builtin.include_tasks: up.yml - loop: "{{ karo_compose_stacks_expanded }}" + loop: "{{ karo_compose_expanded_stacks }}" loop_control: - label: "{{ item.name }}" + loop_var: stack + label: "{{ stack.name }}" vars: - stack: "{{ item.name }}" - group: "{{ item.group }}" - secrets: "{{ lookup('vars', 'karo_compose_' ~ stack ~ '_secrets', default={}) | dict2items }}" + stack_secrets: "{{ lookup('vars', stack.namespace ~ '_secrets', default={}) | dict2items }}" when: - - lookup('vars', 'karo_compose_' ~ stack ~ '_enabled') | default(false) - - karo_compose_justfile_stack in [stack, 'all'] + - stack.enabled + - karo_compose_justfile_stack in [stack.name, 'all'] always: - name: Discard tmpfs secrets directory diff --git a/roles/karo-compose/tasks/up.yml b/roles/karo-compose/tasks/up.yml index 7912695..1fc2f57 100644 --- a/roles/karo-compose/tasks/up.yml +++ b/roles/karo-compose/tasks/up.yml @@ -6,7 +6,7 @@ - name: Prepare secrets when: - - secrets | length > 0 + - stack_secrets | length > 0 - secrets_dir is undefined block: - name: Create tmpfs secrets directory @@ -20,19 +20,19 @@ ansible.builtin.set_fact: secrets_dir: true -- name: Create secrets for {{ stack }} +- name: Create secrets for {{ stack.name }} ansible.builtin.copy: dest: "/run/user/1001/karo/compose/{{ secret.key }}" content: "{{ secret.value }}" mode: "0644" - loop: "{{ secrets }}" + loop: "{{ stack_secrets }}" loop_control: loop_var: secret label: "{{ secret.key }}" changed_when: false -- name: Up compose stack {{ stack }} +- name: Up compose stack {{ stack.name }} community.docker.docker_compose_v2: docker_host: unix:///run/user/1001/docker.sock - project_src: "/srv/docker/{{ group }}/{{ stack }}" + project_src: "/srv/docker/{{ stack.path }}" state: present # docker compose up diff --git a/roles/karo-compose/templates/.gitkeep b/roles/karo-compose/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/karo-compose/templates/core/pocketid/compose.yml.j2 b/roles/karo-compose/templates/core/pocketid/compose.yml.j2 deleted file mode 100644 index 8f498dd..0000000 --- a/roles/karo-compose/templates/core/pocketid/compose.yml.j2 +++ /dev/null @@ -1,63 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/core/pocket-id - ---- -name: pocketid -services: - - pocketid: - image: {{ karo_compose_pocketid_image }}:{{ karo_compose_pocketid_version }} - container_name: pocketid - restart: {{ karo_compose_restart_policy }} - read_only: false - networks: - - egress_pocketid - - frontend - volumes: - - type: volume - source: pocketid_data - target: /app/data - labels: - - traefik.enable=true - - traefik.http.routers.pocketid.rule=Host(`{{ karo_compose_oidc_domain }}`) - - traefik.http.services.pocketid.loadbalancer.server.port=1411 - environment: - - LOG_LEVEL={{ karo_compose_pocketid_log_level }} - - APP_URL={{ karo_compose_oidc_url }} - - ENCRYPTION_KEY_FILE=/run/secrets/pocketid_encryption_key - - MAXMIND_LICENSE_KEY_FILE=/run/secrets/pocketid_maxmind_license_key - - ANALYTICS_DISABLED=true - - VERSION_CHECK_DISABLED=true - - TRUST_PROXY=true - - PUID=1000 - - PGID=1000 - secrets: - - pocketid_encryption_key - - pocketid_maxmind_license_key - healthcheck: - test: [ "CMD", "/app/pocket-id", "healthcheck" ] - interval: 1m30s - timeout: 5s - retries: 2 - start_period: 10s - -networks: - egress_pocketid: - name: egress_pocketid - driver: bridge - internal: false - frontend: - external: true - -volumes: - pocketid_data: - name: pocketid_data - -secrets: - pocketid_encryption_key: - file: /run/user/1001/karo/compose/pocketid_encryption_key - pocketid_maxmind_license_key: - file: /run/user/1001/karo/compose/pocketid_maxmind_license_key diff --git a/roles/karo-compose/templates/core/traefik/compose.yml.j2 b/roles/karo-compose/templates/core/traefik/compose.yml.j2 deleted file mode 100644 index f6e4492..0000000 --- a/roles/karo-compose/templates/core/traefik/compose.yml.j2 +++ /dev/null @@ -1,157 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/core/traefik - -{% set traefik_acme_volume = "traefik_acme_staging" if karo_compose_traefik_acme_staging_enabled else "traefik_acme" %} ---- -name: traefik -services: - - traefik: - image: {{ karo_compose_traefik_image }}:{{ karo_compose_traefik_version }} - container_name: traefik - restart: {{ karo_compose_restart_policy }} - security_opt: - - no-new-privileges:true - ports: - - "0.0.0.0:80:80/tcp" - - "0.0.0.0:443:443/tcp" - - "0.0.0.0:443:443/udp" - networks: - socketproxy_traefik: - egress_traefik: - frontend: - ipv4_address: 172.18.0.254 - volumes: - - type: bind - source: /srv/docker/core/traefik/traefik.yml - target: /etc/traefik/traefik.yml - read_only: true - - type: volume - source: {{ traefik_acme_volume }} - target: /etc/traefik/acme - labels: - - traefik.enable=true - - traefik.http.routers.dashboard.rule=Host(`{{ karo_compose_traefik_dashboard_domain }}`) - - traefik.http.routers.dashboard.service=api@internal - - traefik.http.services.dashboard.loadbalancer.server.port=8080 -{% if karo_compose_traefik_dashboard_forward_auth_enabled %} - # forward auth - - traefik.http.routers.dashboard.middlewares=tinyauth - - tinyauth.apps.dashboard.config.domain={{ karo_compose_traefik_dashboard_domain }} - - tinyauth.apps.dashboard.oauth.groups={{ karo_compose_oidc_admin_group }} -{% endif %} - environment: - - CF_ZONE_API_TOKEN_FILE=/run/secrets/traefik_acme_zone_api_token - - CF_DNS_API_TOKEN_FILE=/run/secrets/traefik_acme_dns_api_token - - CLOUDFLARE_POLLING_INTERVAL=10 - - TZ={{ karo_compose_timezone }} - secrets: - - traefik_acme_zone_api_token - - traefik_acme_dns_api_token - depends_on: - tinyauth: - condition: service_healthy - - tinyauth: - image: {{ karo_compose_traefik_tinyauth_image }}:{{ karo_compose_traefik_tinyauth_version }} - container_name: tinyauth - restart: {{ karo_compose_restart_policy }} - # user: 1000:1000 # unsupported - # read_only: true # write secrets - security_opt: - - no-new-privileges:true - networks: - socketproxy_traefik: - frontend: - ipv4_address: 172.18.0.253 - extra_hosts: - - "{{ karo_compose_oidc_domain }}:172.18.0.254" - labels: - - traefik.enable=true - - traefik.http.routers.tinyauth.rule=Host(`{{ karo_compose_traefik_tinyauth_domain }}`) - - traefik.http.services.tinyauth.loadbalancer.server.port=3000 - - traefik.http.middlewares.tinyauth.forwardauth.address=http://tinyauth:3000/api/auth/traefik - - traefik.http.middlewares.tinyauth.forwardauth.maxResponseBodySize=8192 - - traefik.http.middlewares.tinyauth.forwardauth.trustForwardHeader=true - environment: - - TINYAUTH_LOG_LEVEL={{ karo_compose_traefik_tinyauth_log_level }} - - TINYAUTH_ANALYTICS_ENABLED=false - - TINYAUTH_RESOURCES_ENABLED=false - - TINYAUTH_UI_BACKGROUNDIMAGE="" - - TINYAUTH_APPURL=https://{{ karo_compose_traefik_tinyauth_domain }} - - TINYAUTH_OAUTH_AUTOREDIRECT=pocketid - - TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTID={{ karo_compose_traefik_tinyauth_oidc_client_id }} - - TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTSECRETFILE=/run/secrets/tinyauth_oidc_client_secret - - TINYAUTH_OAUTH_PROVIDERS_POCKETID_AUTHURL={{ karo_compose_oidc_authorization_url }} - - TINYAUTH_OAUTH_PROVIDERS_POCKETID_TOKENURL={{ karo_compose_oidc_token_url }} - - TINYAUTH_OAUTH_PROVIDERS_POCKETID_USERINFOURL={{ karo_compose_oidc_userinfo_url }} - - TINYAUTH_OAUTH_PROVIDERS_POCKETID_REDIRECTURL=https://{{ karo_compose_traefik_tinyauth_domain }}/api/oauth/callback/pocketid - - TINYAUTH_OAUTH_PROVIDERS_POCKETID_SCOPES=openid email profile groups - - TINYAUTH_OAUTH_PROVIDERS_POCKETID_NAME={{ karo_compose_oidc_provider_name }} - - DOCKER_HOST=tcp://cetusguard:2375 - secrets: - - tinyauth_oidc_client_secret - depends_on: - - cetusguard - - cetusguard: - image: {{ karo_compose_traefik_cetusguard_image }}:{{ karo_compose_traefik_cetusguard_version }} - container_name: cetusguard_traefik - restart: {{ karo_compose_restart_policy }} - read_only: true - networks: - - socketproxy_traefik - volumes: - - type: bind - source: /run/user/1001/docker.sock - target: /var/run/docker.sock - read_only: true - environment: - CETUSGUARD_BACKEND_ADDR: "unix:///var/run/docker.sock" - CETUSGUARD_FRONTEND_ADDR: "tcp://:2375" - CETUSGUARD_RULES: | - ! monitor events - GET %API_PREFIX_EVENTS% - ! list containers - GET %API_PREFIX_CONTAINERS%/json - ! inspect a container - GET %API_PREFIX_CONTAINERS%/%CONTAINER_ID_OR_NAME%/json - CETUSGUARD_LOG_LEVEL: "{{ karo_compose_traefik_cetusguard_log_level }}" - -networks: - socketproxy_traefik: - name: socketproxy_traefik - driver: bridge - internal: true - egress_traefik: - name: egress_traefik - driver: bridge - internal: false - ipam: - config: - - subnet: 172.19.0.0/24 - gateway: 172.19.0.1 - ip_range: 172.19.0.253/32 - frontend: - name: frontend - driver: bridge - internal: true - ipam: - config: - - subnet: 172.18.0.0/24 - gateway: 172.18.0.1 - -volumes: - {{ traefik_acme_volume }}: - name: {{ traefik_acme_volume }} - -secrets: - traefik_acme_zone_api_token: - file: /run/user/1001/karo/compose/traefik_acme_zone_api_token - traefik_acme_dns_api_token: - file: /run/user/1001/karo/compose/traefik_acme_dns_api_token - tinyauth_oidc_client_secret: - file: /run/user/1001/karo/compose/tinyauth_oidc_client_secret diff --git a/roles/karo-compose/templates/core/traefik/traefik.yml.j2 b/roles/karo-compose/templates/core/traefik/traefik.yml.j2 deleted file mode 100644 index 69ad62d..0000000 --- a/roles/karo-compose/templates/core/traefik/traefik.yml.j2 +++ /dev/null @@ -1,79 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - ---- - -global: - checkNewVersion: false - sendAnonymousUsage: false - -# https://doc.traefik.io/traefik/reference/install-configuration/observability/logs-and-accesslogs/ -log: - level: {{ karo_compose_traefik_log_level | upper }} - -# https://doc.traefik.io/traefik/reference/install-configuration/api-dashboard/ -api: - dashboard: {{ karo_compose_traefik_dashboard_enabled | lower }} - disabledashboardad: true - -# https://doc.traefik.io/traefik/reference/install-configuration/entrypoints/ -entryPoints: - web: - address: :80 - http: - redirections: - entryPoint: - to: websecure - scheme: https - permanent: true - websecure: - address: :443 - http3: true - asDefault: true - forwardedHeaders: - trustedIPs: - - "172.18.0.253" -{% if karo_compose_proxy_client_enabled %} - proxyProtocol: - trustedIPs: - - "{{ karo_compose_proxy_server_wireguard_ipv4 }}" -{% endif %} - http: - tls: - certResolver: cloudflare - domains: - - main: "*.{{ karo_compose_root_domain }}" - {{ karo_compose_traefik_custom_domains_raw }} - -# https://doc.traefik.io/traefik/reference/install-configuration/tls/certificate-resolvers/acme/ -certificatesResolvers: - cloudflare: - acme: - storage: /etc/traefik/acme/acme.json - email: {{ karo_compose_traefik_acme_email }} - keyType: EC384 -{% if karo_compose_traefik_acme_staging_enabled %} - caServer: {{ karo_compose_traefik_acme_staging_ca_server_url }} -{% else %} - caServer: {{ karo_compose_traefik_acme_ca_server_url }} -{% endif %} - dnsChallenge: - provider: cloudflare - resolvers: - - "1.1.1.1:53" - - "1.0.0.1:53" - -# https://doc.traefik.io/traefik/reference/routing-configuration/http/tls/tls-options/ -tls: - options: - default: - minVersion: VersionTLS13 - sniStrict: true - -# https://doc.traefik.io/traefik/reference/install-configuration/providers/docker/ -providers: - docker: - endpoint: "tcp://cetusguard:2375" - exposedByDefault: false - network: frontend # container connections diff --git a/roles/karo-compose/templates/extra/glance/compose.yml.j2 b/roles/karo-compose/templates/extra/glance/compose.yml.j2 deleted file mode 100644 index 9c830f6..0000000 --- a/roles/karo-compose/templates/extra/glance/compose.yml.j2 +++ /dev/null @@ -1,54 +0,0 @@ -# SPDX-FileCopyrightText: 2026 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/glance - ---- -name: glance -services: - - glance: - image: {{ karo_compose_glance_image }}:{{ karo_compose_glance_version }} - container_name: glance - restart: {{ karo_compose_restart_policy }} - user: 1000:1000 - tty: false - stdin_open: false - read_only: true - security_opt: - - no-new-privileges:true - tmpfs: - - /tmp:rw,noexec,nosuid,nodev - cap_drop: - - ALL - networks: - - egress_glance - - frontend - volumes: - - type: bind - source: /etc/localtime - target: /etc/localtime - read_only: true - - type: bind - source: /srv/docker/extra/glance/glance.yml - target: /app/config/glance.yml - read_only: true - labels: - - traefik.enable=true - - traefik.http.routers.glance.rule=Host(`{{ karo_compose_glance_domain }}`) - - traefik.http.services.glance.loadbalancer.server.port=8080 -{% if karo_compose_glance_forward_auth_enabled %} - # forward auth - - traefik.http.routers.glance.middlewares=tinyauth - - tinyauth.apps.glance.config.domain={{ karo_compose_glance_domain }} - - tinyauth.apps.glance.oauth.groups={{ karo_compose_oidc_admin_group }} -{% endif %} - -networks: - egress_glance: - name: egress_glance - driver: bridge - internal: false - frontend: - external: true diff --git a/roles/karo-compose/templates/extra/glance/glance.yml.j2 b/roles/karo-compose/templates/extra/glance/glance.yml.j2 deleted file mode 100644 index a1fbd39..0000000 --- a/roles/karo-compose/templates/extra/glance/glance.yml.j2 +++ /dev/null @@ -1 +0,0 @@ -{{ karo_compose_glance_config_raw }} diff --git a/roles/karo-compose/templates/extra/gluetun/compose.yml.j2 b/roles/karo-compose/templates/extra/gluetun/compose.yml.j2 deleted file mode 100644 index 7efee8e..0000000 --- a/roles/karo-compose/templates/extra/gluetun/compose.yml.j2 +++ /dev/null @@ -1,44 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/gluetun - ---- -name: gluetun -services: - - gluetun: - image: {{ karo_compose_gluetun_image }}:{{ karo_compose_gluetun_version }} - container_name: gluetun - restart: {{ karo_compose_restart_policy }} - cap_add: - - NET_ADMIN - devices: - - /dev/net/tun:/dev/net/tun - networks: - - egress_gluetun - - frontend - extra_hosts: - - "{{ karo_compose_oidc_domain }}:172.18.0.254" - environment: - - TZ={{ karo_compose_timezone }} - - LOG_LEVEL={{ karo_compose_gluetun_log_level }} - - VPN_SERVICE_PROVIDER=custom - - VPN_TYPE=wireguard - - FIREWALL_VPN_INPUT_PORTS={{ karo_compose_gluetun_firewall_vpn_input_ports }} - - WIREGUARD_CONF_SECRETFILE=/run/secrets/gluetun_wireguard_config_raw - secrets: - - gluetun_wireguard_config_raw - -networks: - egress_gluetun: - name: egress_gluetun - driver: bridge - internal: false - frontend: - external: true - -secrets: - gluetun_wireguard_config_raw: - file: /run/user/1001/karo/compose/gluetun_wireguard_config_raw diff --git a/roles/karo-compose/templates/extra/godns/compose.yml.j2 b/roles/karo-compose/templates/extra/godns/compose.yml.j2 deleted file mode 100644 index 2a3d4c9..0000000 --- a/roles/karo-compose/templates/extra/godns/compose.yml.j2 +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/godns - ---- -name: godns -services: - - godns: - image: {{ karo_compose_godns_image }}:{{ karo_compose_godns_version }} - container_name: godns - restart: {{ karo_compose_restart_policy }} - networks: - - egress_godns - volumes: - - type: bind - source: /srv/docker/extra/godns/config.json - target: /config.json - read_only: true - secrets: - - godns_dns_api_token - -networks: - egress_godns: - name: egress_godns - driver: bridge - internal: false - -secrets: - godns_dns_api_token: - file: /run/user/1001/karo/compose/godns_dns_api_token diff --git a/roles/karo-compose/templates/extra/godns/config.json.j2 b/roles/karo-compose/templates/extra/godns/config.json.j2 deleted file mode 100644 index 9127761..0000000 --- a/roles/karo-compose/templates/extra/godns/config.json.j2 +++ /dev/null @@ -1,21 +0,0 @@ -{ - "debug_info": {{ karo_compose_godns_debug_info_enabled | lower }}, - "provider": "Cloudflare", - "login_token_file": "/run/secrets/godns_dns_api_token", - "domains": [ - { - "domain_name": "{{ karo_compose_root_domain }}", - "sub_domains": ["{{ karo_compose_godns_subdomain }}"] - } - ], - "resolver": "1.1.1.1", - "ip_urls": [ - "https://api.ipify.org", - "https://myip.biturl.top", - "https://api-ipv4.ip.sb/ip" - ], - "ip_type": "IPv4", - "interval": 300, - "socks5_proxy": "", - "proxied": false -} diff --git a/roles/karo-compose/templates/extra/jellyfin/compose.yml.j2 b/roles/karo-compose/templates/extra/jellyfin/compose.yml.j2 deleted file mode 100644 index 357a0b5..0000000 --- a/roles/karo-compose/templates/extra/jellyfin/compose.yml.j2 +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/jellyfin - ---- -name: jellyfin -services: - - jellyfin: - image: {{ karo_compose_jellyfin_image }}:{{ karo_compose_jellyfin_version }} - container_name: jellyfin - restart: {{ karo_compose_restart_policy }} - networks: - - egress_jellyfin - - frontend - volumes: - - type: bind - source: {{ karo_compose_jellyfin_movies_media_path }} - target: /data/media/movies - read_only: true - - type: bind - source: {{ karo_compose_jellyfin_series_media_path }} - target: /data/media/series - read_only: true - - type: volume - source: jellyfin_data - target: /config - - type: volume - source: jellyfin_cache - target: /cache - labels: - - traefik.enable=true - - traefik.http.routers.jellyfin.rule=Host(`{{ karo_compose_jellyfin_domain }}`) - - traefik.http.services.jellyfin.loadbalancer.server.port=8096 - environment: - - TZ={{ karo_compose_timezone }} - -networks: - egress_jellyfin: - name: egress_jellyfin - driver: bridge - internal: false - frontend: - external: true - -volumes: - jellyfin_data: - name: jellyfin_data - jellyfin_cache: - name: jellyfin_cache diff --git a/roles/karo-compose/templates/extra/ntfy/compose.yml.j2 b/roles/karo-compose/templates/extra/ntfy/compose.yml.j2 deleted file mode 100644 index a1c3269..0000000 --- a/roles/karo-compose/templates/extra/ntfy/compose.yml.j2 +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-FileCopyrightText: 2026 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/ntfy - ---- -name: ntfy -services: - - ntfy: - image: {{ karo_compose_ntfy_image }}:{{ karo_compose_ntfy_version }} - container_name: ntfy - restart: {{ karo_compose_restart_policy }} - # user: 1000:1000 # issue 1276 - read_only: true - security_opt: - - no-new-privileges:true - init: true # for healthcheck - command: - - serve - networks: - - egress_ntfy - - frontend - volumes: - - type: volume - source: ntfy_data - target: /var/lib/ntfy - labels: - - traefik.enable=true - - traefik.http.routers.ntfy.rule=Host(`{{ karo_compose_ntfy_domain }}`) - - traefik.http.services.ntfy.loadbalancer.server.port=80 - environment: - - TZ={{ karo_compose_timezone }} - - NTFY_BASE_URL=https://{{ karo_compose_ntfy_domain }} - - NTFY_CACHE_FILE=/var/lib/ntfy/cache.db - - NTFY_AUTH_FILE=/var/lib/ntfy/auth.db - - NTFY_AUTH_DEFAULT_ACCESS=deny-all - - NTFY_BEHIND_PROXY=true - - NTFY_ATTACHMENT_CACHE_DIR=/var/lib/ntfy/attachments - - NTFY_UPSTREAM_BASE_URL=https://ntfy.sh - - NTFY_WEB_ROOT=disable - - NTFY_LOG_LEVEL={{ karo_compose_ntfy_log_level }} - - NTFY_AUTH_USERS={{ karo_compose_ntfy_auth_users }} - - NTFY_AUTH_ACCESS={{ karo_compose_ntfy_auth_access }} - - NTFY_AUTH_TOKENS={{ karo_compose_ntfy_auth_tokens }} - healthcheck: - test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"] - interval: 60s - timeout: 10s - retries: 3 - start_period: 40s - -networks: - egress_ntfy: - name: egress_ntfy - driver: bridge - internal: false - frontend: - external: true - -volumes: - ntfy_data: - name: ntfy_data diff --git a/roles/karo-compose/templates/extra/prowlarr/compose.yml.j2 b/roles/karo-compose/templates/extra/prowlarr/compose.yml.j2 deleted file mode 100644 index 5d6bfb9..0000000 --- a/roles/karo-compose/templates/extra/prowlarr/compose.yml.j2 +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/prowlarr - ---- -name: prowlarr -services: - - prowlarr: - image: {{ karo_compose_prowlarr_image }}:{{ karo_compose_prowlarr_version }} - container_name: prowlarr - restart: {{ karo_compose_restart_policy }} - network_mode: "container:gluetun" - volumes: - - type: volume - source: prowlarr_data - target: /config - labels: - - traefik.enable=true - - traefik.http.routers.prowlarr.rule=Host(`{{ karo_compose_prowlarr_domain }}`) - - traefik.http.services.prowlarr.loadbalancer.server.port=9696 -{% if karo_compose_prowlarr_forward_auth_enabled %} - # forward auth - - traefik.http.routers.prowlarr.middlewares=tinyauth - - tinyauth.apps.prowlarr.config.domain={{ karo_compose_prowlarr_domain }} - - tinyauth.apps.prowlarr.oauth.groups={{ karo_compose_oidc_admin_group }} -{% endif %} - environment: - - TZ={{ karo_compose_timezone }} - - PUID=1000 - - PGID=1000 - -volumes: - prowlarr_data: - name: prowlarr_data diff --git a/roles/karo-compose/templates/extra/proxy/compose.yml.j2 b/roles/karo-compose/templates/extra/proxy/compose.yml.j2 deleted file mode 100644 index 0f0ba7a..0000000 --- a/roles/karo-compose/templates/extra/proxy/compose.yml.j2 +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-FileCopyrightText: 2026 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/proxy - ---- -name: proxy -services: - - haproxy: - image: {{ karo_compose_proxy_haproxy_image }}:{{ karo_compose_proxy_haproxy_version }} - container_name: haproxy - restart: {{ karo_compose_restart_policy }} - ports: - - "0.0.0.0:80:80/tcp" - - "0.0.0.0:443:443/tcp" - networks: - - egress_haproxy - volumes: - - type: bind - source: /srv/docker/extra/proxy/haproxy.cfg - target: /usr/local/etc/haproxy/haproxy.cfg - read_only: true - -networks: - egress_haproxy: - name: egress_haproxy - driver: bridge - internal: false diff --git a/roles/karo-compose/templates/extra/proxy/haproxy.cfg.j2 b/roles/karo-compose/templates/extra/proxy/haproxy.cfg.j2 deleted file mode 100644 index aaae45a..0000000 --- a/roles/karo-compose/templates/extra/proxy/haproxy.cfg.j2 +++ /dev/null @@ -1,39 +0,0 @@ -# SPDX-FileCopyrightText: 2026 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - - -global - log stdout format raw local0 - stats timeout 30s - -defaults - # logging - log global - option dontlognull - option tcplog - - # connections - option tcpka - option tcp-smart-accept - option tcp-smart-connect - - # timeouts - timeout connect 10s - timeout check 10s - timeout client 30s - timeout server 30s - timeout tunnel 1h - -frontend http - bind *:80 - mode http - - # https redirect - http-request redirect scheme https code 301 if !{ ssl_fc } - -frontend https - bind *:443 - mode tcp - - {{ karo_compose_proxy_haproxy_config_raw }} diff --git a/roles/karo-compose/templates/extra/qbittorrent/compose.yml.j2 b/roles/karo-compose/templates/extra/qbittorrent/compose.yml.j2 deleted file mode 100644 index 73080b6..0000000 --- a/roles/karo-compose/templates/extra/qbittorrent/compose.yml.j2 +++ /dev/null @@ -1,80 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/qbittorrent - ---- -name: qbittorrent -services: - - qbittorrent: - image: {{ karo_compose_qbittorrent_image }}:{{ karo_compose_qbittorrent_version }} - container_name: qbittorrent - restart: {{ karo_compose_restart_policy }} - stop_grace_period: 30m - tty: false - stdin_open: false - read_only: false - security_opt: - - no-new-privileges:true - tmpfs: - - /tmp:rw,noexec,nosuid,nodev - network_mode: "container:gluetun" - volumes: - - type: bind - source: {{ karo_compose_qbittorrent_downloads_path }} - target: /data/torrents - - type: volume - source: qbittorrent_data - target: /config - labels: - - traefik.enable={{ karo_compose_qbittorrent_webui_enabled | lower }} - - traefik.http.routers.qbittorrent.rule=Host(`{{ karo_compose_qbittorrent_domain }}`) - - traefik.http.services.qbittorrent.loadbalancer.server.port=9814 - environment: - - TZ={{ karo_compose_timezone }} - - PUID=0 - - PGID=0 - - QBT_LEGAL_NOTICE=confirm - - QBT_WEBUI_PORT=9814 - - qui: - image: {{ karo_compose_qbittorrent_qui_image }}:{{ karo_compose_qbittorrent_qui_version }} - container_name: qui - restart: {{ karo_compose_restart_policy }} - network_mode: "container:gluetun" - volumes: - - type: volume - source: qui_data - target: /config - labels: - - traefik.enable=true - - traefik.http.routers.qui.rule=Host(`{{ karo_compose_qbittorrent_qui_domain }}`) - - traefik.http.services.qui.loadbalancer.server.port=7476 - environment: - - QUI__LOG_LEVEL={{ karo_compose_qbittorrent_qui_log_level | upper }} - - QUI__SESSION_SECRET_FILE=/run/secrets/qui_session_secret - - QUI__OIDC_ENABLED=true - - QUI__OIDC_ISSUER={{ karo_compose_oidc_url }} - - QUI__OIDC_CLIENT_ID={{ karo_compose_qbittorrent_qui_oidc_client_id }} - - QUI__OIDC_CLIENT_SECRET_FILE=/run/secrets/qui_oidc_client_secret - - QUI__OIDC_REDIRECT_URL=https://{{ karo_compose_qbittorrent_qui_domain }}/api/auth/oidc/callback - - QUI__OIDC_DISABLE_BUILT_IN_LOGIN=true - secrets: - - qui_session_secret - - qui_oidc_client_secret - depends_on: - - qbittorrent - -volumes: - qbittorrent_data: - name: qbittorrent_data - qui_data: - name: qui_data - -secrets: - qui_session_secret: - file: /run/user/1001/karo/compose/qui_session_secret - qui_oidc_client_secret: - file: /run/user/1001/karo/compose/qui_oidc_client_secret diff --git a/roles/karo-compose/templates/extra/radarr/compose.yml.j2 b/roles/karo-compose/templates/extra/radarr/compose.yml.j2 deleted file mode 100644 index fed38bb..0000000 --- a/roles/karo-compose/templates/extra/radarr/compose.yml.j2 +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-FileCopyrightText: 2026 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/radarr - ---- -name: radarr -services: - - radarr: - image: {{ karo_compose_radarr_image }}:{{ karo_compose_radarr_version }} - container_name: radarr - restart: {{ karo_compose_restart_policy }} - network_mode: "container:gluetun" - volumes: - - type: bind - source: {{ karo_compose_radarr_data_path }} - target: /data - - type: volume - source: radarr_data - target: /config - labels: - - traefik.enable=true - - traefik.http.routers.radarr.rule=Host(`{{ karo_compose_radarr_domain }}`) - - traefik.http.services.radarr.loadbalancer.server.port=7878 -{% if karo_compose_radarr_forward_auth_enabled %} - # forward auth - - traefik.http.routers.radarr.middlewares=tinyauth - - tinyauth.apps.radarr.config.domain={{ karo_compose_radarr_domain }} - - tinyauth.apps.radarr.oauth.groups={{ karo_compose_oidc_admin_group }} -{% endif %} - environment: - - TZ={{ karo_compose_timezone }} - - PUID=0 - - PGID=0 - -volumes: - radarr_data: - name: radarr_data diff --git a/roles/karo-compose/templates/extra/seerr/compose.yml.j2 b/roles/karo-compose/templates/extra/seerr/compose.yml.j2 deleted file mode 100644 index 5544f1c..0000000 --- a/roles/karo-compose/templates/extra/seerr/compose.yml.j2 +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/seerr - ---- -name: seerr -services: - - seerr: - image: {{ karo_compose_seerr_image }}:{{ karo_compose_seerr_version }} - container_name: seerr - restart: {{ karo_compose_restart_policy }} - init: true - networks: - - egress_seerr - - frontend - volumes: - - type: volume - source: seerr_data - target: /app/config - labels: - - traefik.enable=true - - traefik.http.routers.seerr.rule=Host(`{{ karo_compose_seerr_domain }}`) - - traefik.http.services.seerr.loadbalancer.server.port=5055 - environment: - - LOG_LEVEL={{ karo_compose_seerr_log_level }} - - TZ={{ karo_compose_timezone }} - healthcheck: - test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:5055/api/v1/settings/public || exit 1"] - interval: 15s - timeout: 3s - retries: 3 - start_period: 20s - -networks: - egress_seerr: - name: egress_seerr - driver: bridge - internal: false - frontend: - external: true - -volumes: - seerr_data: - name: seerr_data diff --git a/roles/karo-compose/templates/extra/sonarr/compose.yml.j2 b/roles/karo-compose/templates/extra/sonarr/compose.yml.j2 deleted file mode 100644 index 1e4e72b..0000000 --- a/roles/karo-compose/templates/extra/sonarr/compose.yml.j2 +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-FileCopyrightText: 2026 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/sonarr - ---- -name: sonarr -services: - - sonarr: - image: {{ karo_compose_sonarr_image }}:{{ karo_compose_sonarr_version }} - container_name: sonarr - restart: {{ karo_compose_restart_policy }} - network_mode: "container:gluetun" - volumes: - - type: bind - source: {{ karo_compose_sonarr_data_path }} - target: /data - - type: volume - source: sonarr_data - target: /config - labels: - - traefik.enable=true - - traefik.http.routers.sonarr.rule=Host(`{{ karo_compose_sonarr_domain }}`) - - traefik.http.services.sonarr.loadbalancer.server.port=8989 -{% if karo_compose_sonarr_forward_auth_enabled %} - # forward auth - - traefik.http.routers.sonarr.middlewares=tinyauth - - tinyauth.apps.sonarr.config.domain={{ karo_compose_sonarr_domain }} - - tinyauth.apps.sonarr.oauth.groups={{ karo_compose_oidc_admin_group }} -{% endif %} - environment: - - TZ={{ karo_compose_timezone }} - - PUID=0 - - PGID=0 - -volumes: - sonarr_data: - name: sonarr_data diff --git a/roles/karo-compose/templates/extra/whoami/compose.yml.j2 b/roles/karo-compose/templates/extra/whoami/compose.yml.j2 deleted file mode 100644 index 7cecc4a..0000000 --- a/roles/karo-compose/templates/extra/whoami/compose.yml.j2 +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-FileCopyrightText: 2025 hazzuk -# -# SPDX-License-Identifier: AGPL-3.0-only - -# https://docs.karolabs.dev/stacks/extra/whoami - ---- -name: whoami -services: - - whoami: - image: {{ karo_compose_whoami_image }}:{{ karo_compose_whoami_version }} - container_name: whoami - restart: {{ karo_compose_restart_policy }} - networks: - - frontend - labels: - - traefik.enable=true - - traefik.http.routers.whoami.rule=Host(`{{ karo_compose_whoami_domain }}`) - # - traefik.http.routers.whoami.tls=true - # - traefik.http.routers.whoami.tls.certresolver=cloudflare - # - traefik.http.routers.dashboard.entrypoints=websecure - - traefik.http.services.whoami.loadbalancer.server.port=80 -{% if karo_compose_whoami_forward_auth_enabled %} - # forward auth - - traefik.http.routers.whoami.middlewares=tinyauth - - tinyauth.apps.whoami.config.domain={{ karo_compose_whoami_domain }} - - tinyauth.apps.whoami.oauth.groups={{ karo_compose_oidc_admin_group }} - - tinyauth.apps.whoami.path.allow=^\/api -{% endif %} - -networks: - frontend: - external: true