From 29aeb51cf0383426cb8a0063811492680951e51f Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 10 Mar 2026 18:18:04 -0300 Subject: [PATCH] Caddy first implementation --- containers/caddy/Dockerfile-caddy | 8 ++ containers/caddy/entrypoint.sh | 224 ++++++++++++++++++++++++++++++ docker-compose-dev.yml | 24 +++- 3 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 containers/caddy/Dockerfile-caddy create mode 100644 containers/caddy/entrypoint.sh diff --git a/containers/caddy/Dockerfile-caddy b/containers/caddy/Dockerfile-caddy new file mode 100644 index 0000000..9bd6275 --- /dev/null +++ b/containers/caddy/Dockerfile-caddy @@ -0,0 +1,8 @@ +FROM caddy:2-alpine + +COPY entrypoint.sh /usr/local/bin/caddy-entrypoint.sh + +RUN chmod +x /usr/local/bin/caddy-entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/caddy-entrypoint.sh"] +CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/containers/caddy/entrypoint.sh b/containers/caddy/entrypoint.sh new file mode 100644 index 0000000..42c1f68 --- /dev/null +++ b/containers/caddy/entrypoint.sh @@ -0,0 +1,224 @@ +#!/bin/sh + +set -eu + +MANUAL_CERT_DIR="${MANUAL_CERT_DIR:-/certificates}" +CADDYFILE_PATH="${CADDYFILE_PATH:-/etc/caddy/Caddyfile}" + +trim_value() { + printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + +is_valid_hostname() { + host_value="$1" + + if [ -z "$host_value" ]; then + return 1 + fi + + if is_ip_host "$host_value"; then + return 1 + fi + + if printf '%s' "$host_value" | grep -Eq '^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$'; then + return 0 + fi + + return 1 +} + +is_ip_host() { + host_value="$1" + + case "$host_value" in + \[*\]|*:*:*) + return 0 + ;; + *) + ;; + esac + + if printf '%s' "$host_value" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then + return 0 + fi + + return 1 +} + +is_internal_tls_host() { + host_value="$1" + + case "$host_value" in + localhost|*.localhost) + return 0 + ;; + esac + + if is_ip_host "$host_value"; then + return 0 + fi + + return 1 +} + +normalize_host() { + raw_value="$(trim_value "$1")" + raw_value="${raw_value#http://}" + raw_value="${raw_value#https://}" + raw_value="${raw_value%%/*}" + + case "$raw_value" in + \[*\]:*) + printf '%s' "${raw_value%:*}" + ;; + *:*) + case "$raw_value" in + *:*:*) + printf '%s' "$raw_value" + ;; + *) + printf '%s' "${raw_value%%:*}" + ;; + esac + ;; + *) + printf '%s' "$raw_value" + ;; + esac +} + +append_host() { + candidate_host="$(normalize_host "$1")" + + if [ -z "$candidate_host" ]; then + return + fi + + case " +$HOSTS +" in + *" +$candidate_host +"*) + return + ;; + esac + + if [ -z "$HOSTS" ]; then + HOSTS="$candidate_host" + return + fi + + HOSTS="$HOSTS +$candidate_host" +} + +append_dns_host() { + candidate_host="$(normalize_host "$1")" + + if [ -z "$candidate_host" ]; then + return + fi + + if is_internal_tls_host "$candidate_host"; then + return + fi + + append_host "$candidate_host" +} + +build_common_block() { + cat <<'EOF' + import wireguard_common +EOF +} + +build_tls_block() { + domain_name="$1" + cert_file="${MANUAL_CERT_DIR}/${domain_name}/fullchain.pem" + key_file="${MANUAL_CERT_DIR}/${domain_name}/key.pem" + + if [ -f "$cert_file" ] && [ -f "$key_file" ]; then + cat < "$CADDYFILE_PATH" <<'EOF' +(wireguard_common) { + encode gzip + + @static path /static/* + handle @static { + root * /static + uri strip_prefix /static + file_server + header Cache-Control "public, max-age=3600" + } + + handle { + reverse_proxy wireguard-webadmin:8000 { + header_up Host {host} + } + } +} +EOF + +printf '%s\n' "$HOSTS" | while IFS= read -r current_host; do + if [ -z "$current_host" ]; then + continue + fi + + { + printf '\n%s {\n' "$current_host" + build_common_block + build_tls_block "$current_host" + printf '}\n' + } >> "$CADDYFILE_PATH" +done + +if command -v caddy >/dev/null 2>&1; then + caddy fmt --overwrite "$CADDYFILE_PATH" >/dev/null 2>&1 +fi + +exec "$@" diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index c51a2c1..b60f853 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -25,7 +25,7 @@ services: - rrd_data:/rrd_data/ ports: # Do not directly expose the Django port to the internet, use some kind of reverse proxy with SSL. - - "8000:8000" + # - "8000:8000" # Warning: Docker will have a hard time handling large amount of ports. Expose only the ports that you need. # Ports for multiple WireGuard instances. (Probably, you just need one) - "51820-51839:51820-51839/udp" @@ -79,9 +79,31 @@ services: volumes: - dnsmasq_conf:/etc/dnsmasq/ + wireguard-webadmin-caddy: + container_name: wireguard-webadmin-caddy + restart: unless-stopped + build: + context: ./containers/caddy + dockerfile: Dockerfile-caddy + environment: + - SERVER_ADDRESS=${SERVER_ADDRESS} + - EXTRA_ALLOWED_HOSTS=${EXTRA_ALLOWED_HOSTS} + - TZ=${TIMEZONE} + volumes: + - static_volume:/static + - https_cert:/certificates + - letsencrypt_data:/data + ports: + - "80:80" + - "443:443" + depends_on: + - wireguard-webadmin + volumes: static_volume: + https_cert: wireguard: dnsmasq_conf: app_secrets: rrd_data: + letsencrypt_data: