feat(reverse_proxy): add Netcup DNS ACME challenge support and refactor Caddy setup

Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
This commit is contained in:
Tuan-Dat Tran
2025-04-28 23:24:29 +02:00
parent 8f2998abc0
commit e3c67a32e9
23 changed files with 223 additions and 109 deletions

View File

@@ -11,7 +11,6 @@
url: https://raw.githubusercontent.com/eza-community/eza/main/deb.asc
dest: /etc/apt/keyrings/gierens.asc
mode: "0644"
register: gpg_key_result
become: true
- name: Add Gierens repository to apt sources

View File

@@ -3,7 +3,7 @@
ansible.builtin.set_fact:
is_keycloak_host: "{{ inventory_hostname in (services | selectattr('name', 'equalto', 'keycloak') | map(attribute='vm') | first) }}"
- name: Run Keycloak tasks
- name: Create Keycloak directories
ansible.builtin.file:
path: "{{ docker.directories.local }}/keycloak/"
owner: "{{ ansible_user_id }}"
@@ -13,7 +13,7 @@
when: is_keycloak_host | bool
become: true
- name: Run Keycloak tasks
- name: Setup Keycloak realms
ansible.builtin.template:
src: "templates/keycloak/realm.json.j2"
dest: "{{ docker.directories.local }}/keycloak/{{ keycloak.realm }}-realm.json"

View File

@@ -34,7 +34,15 @@ services:
{% endif %}
{% if chosen_http_port_value is defined %}
healthcheck:
{% set healthcheck = 'curl' %}
{% if service.healthcheck is defined %}
{% set healthcheck = service.healthcheck %}
{% endif %}
{% if healthcheck == 'curl' %}
test: ["CMD", "curl", "-f", "--silent", "--show-error", "--connect-timeout", "5", "http://localhost:{{ chosen_http_port_value }}/"]
{% elif healthcheck == 'wget' %}
test: ["CMD-SHELL", "wget --quiet --spider --timeout=5 http://localhost:{{ chosen_http_port_value }}/ || exit 1"]
{% endif %}
interval: 30s
timeout: 10s
retries: 5

View File

@@ -1,4 +1,11 @@
---
- name: Restart Caddy
ansible.builtin.command: "{{ caddy_binary }} reload --config {{ caddy_config_path }}"
- name: Restart caddy service
ansible.builtin.systemd:
name: caddy
state: restarted
become: true
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
become: true

View File

@@ -0,0 +1,23 @@
---
- name: Download Caddy GPG Key
ansible.builtin.get_url:
url: https://dl.cloudsmith.io/public/caddy/stable/gpg.key
dest: /usr/share/keyrings/caddy-stable-archive-keyring.asc
mode: "0644"
become: true
- name: Add Caddy repository source list
ansible.builtin.apt_repository:
repo: "{{ item }}"
state: present
become: true
notify: Update apt cache
loop:
- "deb [signed-by=/usr/share/keyrings/caddy-stable-archive-keyring.asc] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main"
- "deb-src [signed-by=/usr/share/keyrings/caddy-stable-archive-keyring.asc] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main"
- name: Install Caddy
ansible.builtin.apt:
name: caddy
state: present
become: true

View File

@@ -24,8 +24,8 @@
state: present
become: true
- name: Install Caddy
ansible.builtin.command: xcaddy build --with github.com/caddy-dns/netcup
- name: Build Custom Caddy with netcup
ansible.builtin.command: xcaddy build --with github.com/caddy-dns/netcup {{ reverse_proxy_caddy_version}}
environment:
PATH: "{{ ansible_env.PATH }}:/usr/local/go/bin"
register: xcaddy_build

View File

@@ -0,0 +1,41 @@
---
- name: Check current diversion status for {{ reverse_proxy_default_caddy_path }}
ansible.builtin.command:
cmd: dpkg-divert --list {{ reverse_proxy_default_caddy_path }}
register: divert_check_result
changed_when: false # This task only checks state
failed_when: false # Don't fail if diversion doesn't exist (rc=1)
become: true
- name: Divert package manager's caddy binary path
ansible.builtin.command:
cmd: dpkg-divert --divert {{ reverse_proxy_diverted_caddy_path }} --rename {{ reverse_proxy_default_caddy_path }}
# Only run if the diversion isn't already set correctly
when: "reverse_proxy_diverted_caddy_path not in divert_check_result.stdout"
notify: Restart caddy service # Notify restart if diversion happens
become: true
- name: Copy custom Caddy binary to destination path
ansible.builtin.copy:
src: "{{ reverse_proxy_custom_caddy_source_path }}"
dest: "{{ reverse_proxy_custom_caddy_dest_path }}"
owner: root
group: root
mode: "0755"
remote_src: true
notify: Restart caddy service # Notify restart if binary changes
become: true
- name: Install original (diverted) caddy binary alternative
ansible.builtin.command:
# Use --force if the link /usr/bin/caddy might exist but not be managed by alternatives yet
cmd: update-alternatives --install {{ reverse_proxy_alternatives_link }} {{ reverse_proxy_alternatives_name }} {{ reverse_proxy_diverted_caddy_path }} 10
changed_when: false # update-alternatives is idempotent but often reports no change via rc
become: true
- name: Install custom caddy binary alternative with higher priority
ansible.builtin.command:
cmd: update-alternatives --install {{ reverse_proxy_alternatives_link }} {{ reverse_proxy_alternatives_name }} {{ reverse_proxy_custom_caddy_dest_path }} 50
changed_when: false # update-alternatives is idempotent but often reports no change via rc
notify: Restart caddy service
become: true

View File

@@ -0,0 +1,14 @@
---
- name: Setup DNS on Netcup
community.general.netcup_dns:
api_key: "{{ reverse_proxy_netcup_api_key }}"
api_password: "{{ reverse_proxy_netcup_api_password }}"
customer_id: "{{ reverse_proxy_netcup_customer_id }}"
domain: "{{ domain }}"
name: "{{ service.name }}"
type: "A"
value: "{{ hostvars['docker-lb'].ansible_default_ipv4.address }}"
loop: "{{ services }}"
loop_control:
loop_var: service
delegate_to: localhost

View File

@@ -13,4 +13,10 @@
mode: "0644"
backup: true
become: true
notify: Restart Caddy
notify: Restart caddy service
- name: Format Caddy configuration file
ansible.builtin.command:
cmd: "caddy fmt --overwrite {{ caddy_config_path }}"
become: true
notify: Restart caddy service

View File

@@ -1,9 +1,18 @@
---
- name: Install Prerequisites
ansible.builtin.include_tasks: prereq.yml
- name: Install Go for Caddy
ansible.builtin.include_tasks: 00_go_install.yml
- name: Install Caddy
ansible.builtin.include_tasks: install.yml
ansible.builtin.include_tasks: 10_caddy_install.yml
- name: Install xCaddy
ansible.builtin.include_tasks: 20_xcaddy_install.yml
- name: Setup Custom Caddy
ansible.builtin.include_tasks: 30_custom_caddy.yml
- name: Setup Netcup DNS
ansible.builtin.include_tasks: 50_netcup_dns.yml
- name: Configure Caddy
ansible.builtin.include_tasks: configure.yml
- name: Start Caddy
ansible.builtin.include_tasks: start.yml
ansible.builtin.include_tasks: 80_configure.yml

View File

@@ -1,4 +0,0 @@
---
- name: Ensure Caddy service is running
ansible.builtin.command: "{{ caddy_binary }} start --config {{ caddy_config_path }}"
become: true

View File

@@ -9,13 +9,14 @@
{% set http_port = service.ports | selectattr('name', 'equalto', 'http') | map(attribute='external') | list %}
{% if http_port %}
{{ service.name }}.{{ domain }} {
{% for vm in service.vm %}
reverse_proxy {{ hostvars[vm].ansible_host }}:{{ http_port[0] }}
{% endfor %}
log {
output file /var/log/caddy/{{ service.name }}.log
format json
}
{% for vm in service.vm -%}
reverse_proxy {{ hostvars[vm].ansible_default_ipv4.address }}:{{ http_port[0] }}
{% endfor %}{{''}}
log {
output file /var/log/caddy/{{ service.name }}.log
format json
}
tls {
dns netcup {
customer_number {{ vault_netcup.customer_number }}

View File

@@ -0,0 +1,12 @@
reverse_proxy_caddy_version: v2.9.1
reverse_proxy_custom_caddy_source_path: "{{ ansible_env.HOME }}/caddy"
reverse_proxy_default_caddy_path: "/usr/bin/caddy"
reverse_proxy_custom_caddy_dest_path: "/usr/bin/caddy.custom"
reverse_proxy_diverted_caddy_path: "/usr/bin/caddy.default"
reverse_proxy_alternatives_link: "/usr/bin/caddy"
reverse_proxy_alternatives_name: "caddy"
reverse_proxy_netcup_api_key: "{{ netcup_api_key }}"
reverse_proxy_netcup_api_password: "{{ netcup_api_password }}"
reverse_proxy_netcup_customer_id: "{{ netcup_customer_id }}"