initial commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
75891c3271
129 changed files with 8046 additions and 0 deletions
14
roles/caddy/defaults/main.yml
Normal file
14
roles/caddy/defaults/main.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
# Caddy metrics port (scraped by Prometheus)
|
||||
caddy_metrics_port: 9000
|
||||
|
||||
# Static sites served by Caddy — override in config.yml
|
||||
# Each entry gets /srv/caddy/sites/<domain>/ and www → apex redirect
|
||||
caddy_sites:
|
||||
- "{{ domain }}"
|
||||
|
||||
# Service domain defaults — override individually in config.yml or overrides.yml
|
||||
webmail_domain: "webmail.{{ domain }}"
|
||||
rspamd_domain: "rspamd.{{ domain }}"
|
||||
goaccess_domain: "stats.{{ domain }}"
|
||||
grafana_domain: "watch.{{ domain }}"
|
||||
8
roles/caddy/handlers/main.yml
Normal file
8
roles/caddy/handlers/main.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
- name: Restart Caddy
|
||||
# full restart (not caddy reload) avoids stale bind-mount inodes when Caddyfile changes on host
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /srv/caddy
|
||||
state: present
|
||||
build: never
|
||||
|
||||
104
roles/caddy/tasks/main.yml
Normal file
104
roles/caddy/tasks/main.yml
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
- name: Allow HTTP traffic
|
||||
ufw:
|
||||
rule: allow
|
||||
port: 80
|
||||
proto: tcp
|
||||
|
||||
- name: Allow HTTPS traffic
|
||||
ufw:
|
||||
rule: allow
|
||||
port: 443
|
||||
proto: tcp
|
||||
|
||||
- name: Allow HTTPS/QUIC (HTTP/3) traffic
|
||||
ufw:
|
||||
rule: allow
|
||||
port: 443
|
||||
proto: udp
|
||||
|
||||
- name: Create Caddy directories
|
||||
file:
|
||||
path: "/srv/caddy/{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: docker
|
||||
mode: "0755"
|
||||
loop:
|
||||
- ""
|
||||
- data
|
||||
- config
|
||||
- sites
|
||||
|
||||
- name: Create site roots
|
||||
file:
|
||||
path: "/srv/caddy/sites/{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: docker
|
||||
mode: "0775" # also allow members of the docker group to write
|
||||
loop: "{{ caddy_sites }}"
|
||||
|
||||
- name: Install Caddyfile
|
||||
template:
|
||||
src: Caddyfile.j2
|
||||
dest: /srv/caddy/Caddyfile
|
||||
owner: root
|
||||
group: docker
|
||||
mode: "0644"
|
||||
notify: Restart Caddy
|
||||
tags: config
|
||||
|
||||
- name: Check for cached goaccess hash
|
||||
ansible.builtin.stat:
|
||||
path: /srv/caddy/.goaccess_hash
|
||||
register: _goaccess_hash_stat
|
||||
when: enable_goaccess | default(true)
|
||||
|
||||
- name: Read goaccess hash from cache
|
||||
ansible.builtin.slurp:
|
||||
src: /srv/caddy/.goaccess_hash
|
||||
register: _goaccess_hash_file
|
||||
when: enable_goaccess | default(true) and _goaccess_hash_stat.stat.exists
|
||||
|
||||
- name: Set goaccess hash fact from cache
|
||||
ansible.builtin.set_fact:
|
||||
caddy_goaccess_hash_stdout: "{{ _goaccess_hash_file.content | b64decode | trim }}"
|
||||
when: enable_goaccess | default(true) and _goaccess_hash_stat.stat.exists
|
||||
|
||||
- name: Generate goaccess password hash
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- run
|
||||
- --rm
|
||||
- "caddy:{{ caddy_version }}"
|
||||
- caddy
|
||||
- hash-password
|
||||
- --plaintext
|
||||
- "{{ goaccess_password }}"
|
||||
register: _goaccess_hash_result
|
||||
changed_when: false
|
||||
no_log: true
|
||||
when: enable_goaccess | default(true) and not _goaccess_hash_stat.stat.exists
|
||||
|
||||
- name: Cache goaccess hash
|
||||
ansible.builtin.copy:
|
||||
content: "{{ _goaccess_hash_result.stdout }}"
|
||||
dest: /srv/caddy/.goaccess_hash
|
||||
mode: "0600"
|
||||
when: enable_goaccess | default(true) and not _goaccess_hash_stat.stat.exists
|
||||
|
||||
- name: Set goaccess hash fact from generation
|
||||
ansible.builtin.set_fact:
|
||||
caddy_goaccess_hash_stdout: "{{ _goaccess_hash_result.stdout }}"
|
||||
when: enable_goaccess | default(true) and not _goaccess_hash_stat.stat.exists
|
||||
|
||||
- name: Deploy Caddy compose.yml
|
||||
template:
|
||||
src: compose.yml.j2
|
||||
dest: /srv/caddy/compose.yml
|
||||
owner: root
|
||||
group: docker
|
||||
mode: "0644"
|
||||
notify: Restart Caddy
|
||||
tags: config
|
||||
124
roles/caddy/templates/Caddyfile.j2
Normal file
124
roles/caddy/templates/Caddyfile.j2
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
email {{ admin_user }}@{{ domain }}
|
||||
log {
|
||||
output stdout
|
||||
}
|
||||
metrics {
|
||||
per_host
|
||||
}
|
||||
}
|
||||
|
||||
(access_log) {
|
||||
log
|
||||
}
|
||||
|
||||
:{{ caddy_metrics_port }} {
|
||||
metrics
|
||||
}
|
||||
|
||||
{% for site in caddy_sites %}
|
||||
# Redirect www → apex
|
||||
www.{{ site }} {
|
||||
import access_log
|
||||
redir https://{{ site }}{uri} permanent
|
||||
}
|
||||
|
||||
{{ site }} {
|
||||
import access_log
|
||||
root * /srv/sites/{{ site }}
|
||||
encode zstd gzip
|
||||
file_server
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "DENY"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
}
|
||||
{% if site == domain and enable_tuwunel | default(false) %}
|
||||
|
||||
handle /.well-known/matrix/server {
|
||||
header Content-Type application/json
|
||||
respond `{"m.server": "{{ tuwunel_domain }}:443"}`
|
||||
}
|
||||
|
||||
handle /.well-known/matrix/client {
|
||||
header Content-Type application/json
|
||||
header Access-Control-Allow-Origin *
|
||||
respond `{"m.homeserver": {"base_url": "https://{{ tuwunel_domain }}"}}`
|
||||
}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
{% if enable_mail | default(false) %}
|
||||
{{ webmail_domain }} {
|
||||
import access_log
|
||||
reverse_proxy rainloop:{{ rainloop_port }}
|
||||
}
|
||||
|
||||
{{ rspamd_domain }} {
|
||||
import access_log
|
||||
reverse_proxy mailserver:{{ rspamd_port }}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if enable_forgejo | default(false) %}
|
||||
{{ forgejo_domain }} {
|
||||
import access_log
|
||||
reverse_proxy forgejo:{{ forgejo_port }}
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if enable_monitoring | default(false) %}
|
||||
{{ grafana_domain }} {
|
||||
import access_log
|
||||
reverse_proxy grafana:{{ grafana_port }} {
|
||||
header_up Host {host}
|
||||
header_up X-Real-IP {remote_host}
|
||||
}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if enable_tuwunel | default(false) %}
|
||||
{{ tuwunel_domain }} {
|
||||
import access_log
|
||||
reverse_proxy tuwunel:{{ tuwunel_port }}
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "DENY"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if enable_radicale | default(false) %}
|
||||
{{ radicale_domain }} {
|
||||
import access_log
|
||||
|
||||
redir /.well-known/caldav / permanent
|
||||
redir /.well-known/carddav / permanent
|
||||
|
||||
reverse_proxy radicale:{{ radicale_port }}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% if enable_goaccess | default(false) %}
|
||||
{{ goaccess_domain }} {
|
||||
import access_log
|
||||
root * /srv/goaccess/reports
|
||||
file_server browse
|
||||
basic_auth {
|
||||
{$GOACCESS_USER} {$GOACCESS_HASH}
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
67
roles/caddy/templates/compose.yml.j2
Normal file
67
roles/caddy/templates/compose.yml.j2
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
services:
|
||||
caddy:
|
||||
image: caddy:{{ caddy_version }}
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost:{{ caddy_metrics_port }}/metrics || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
volumes:
|
||||
- /srv/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- /srv/caddy/data:/data
|
||||
- /srv/caddy/config:/config
|
||||
- /srv/caddy/sites:/srv/sites:ro
|
||||
- /srv/goaccess/reports:/srv/goaccess/reports:ro
|
||||
environment:
|
||||
{% if enable_goaccess | default(true) %}
|
||||
GOACCESS_USER: "{{ goaccess_user }}"
|
||||
GOACCESS_HASH: "{{ caddy_goaccess_hash_stdout | replace('$', '$$') }}"
|
||||
{% endif %}
|
||||
networks:
|
||||
- caddy
|
||||
{% if enable_mail | default(true) %}
|
||||
- webmail
|
||||
{% endif %}
|
||||
{% if enable_forgejo | default(true) %}
|
||||
- git
|
||||
{% endif %}
|
||||
{% if enable_monitoring | default(true) %}
|
||||
- monitoring
|
||||
{% endif %}
|
||||
{% if enable_tuwunel | default(true) %}
|
||||
- tuwunel
|
||||
{% endif %}
|
||||
{% if enable_radicale | default(false) %}
|
||||
- radicale
|
||||
{% endif %}
|
||||
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
||||
{% if enable_mail | default(true) %}
|
||||
webmail:
|
||||
external: true
|
||||
{% endif %}
|
||||
{% if enable_forgejo | default(true) %}
|
||||
git:
|
||||
external: true
|
||||
{% endif %}
|
||||
{% if enable_monitoring | default(true) %}
|
||||
monitoring:
|
||||
external: true
|
||||
{% endif %}
|
||||
{% if enable_tuwunel | default(true) %}
|
||||
tuwunel:
|
||||
external: true
|
||||
{% endif %}
|
||||
{% if enable_radicale | default(false) %}
|
||||
radicale:
|
||||
external: true
|
||||
{% endif %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue