Add Caddy reverse proxy for blumeops services (#55)
## Summary - Add Caddy ansible role following zot pattern (manual build, ansible deploy) - Caddy built with Gandi DNS plugin for ACME DNS-01 challenges - Gandi PAT fetched from 1Password and written to secured file on indri - Configure wildcard TLS for `*.ops.eblu.me` - Initial services: forge, registry (indri-local) - Uses port 8443 during testing to avoid Tailscale serve conflicts ## Build Instructions (already done) On indri: ```bash cd ~/code/3rd/caddy && mise run build ``` ## Deployment and Testing - [ ] Review Caddyfile configuration - [ ] Run `mise run provision-indri -- --tags caddy` to deploy - [ ] Test: `curl -v https://forge.ops.eblu.me:8443` (should get TLS cert) - [ ] Test: `curl -v https://registry.ops.eblu.me:8443/v2/` (should return `{}`) - [ ] Once verified, switch to port 443 and migrate services from Tailscale serve ## Files Changed - `ansible/playbooks/indri.yml` - Add pre_task for Gandi PAT, add caddy role - `ansible/roles/caddy/` - New role with Caddyfile and LaunchAgent templates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.tail8d86e.ts.net/eblume/blumeops/pulls/55
This commit is contained in:
parent
b08faa50cc
commit
682a68dc9c
7 changed files with 222 additions and 0 deletions
37
ansible/roles/caddy/defaults/main.yml
Normal file
37
ansible/roles/caddy/defaults/main.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
# Caddy reverse proxy configuration
|
||||
# Caddy is built manually from ~/code/3rd/caddy with the Gandi DNS plugin
|
||||
|
||||
caddy_repo_dir: /Users/erichblume/code/3rd/caddy
|
||||
caddy_binary: "{{ caddy_repo_dir }}/bin/caddy"
|
||||
caddy_config_dir: /Users/erichblume/.config/caddy
|
||||
caddy_data_dir: /Users/erichblume/.local/share/caddy
|
||||
caddy_log_dir: /Users/erichblume/Library/Logs
|
||||
|
||||
# Gandi API token file (written by ansible, chmod 0600)
|
||||
# Caddy reads this file for ACME DNS-01 challenges
|
||||
caddy_gandi_token_file: /Users/erichblume/.config/caddy/gandi-token
|
||||
|
||||
# Domain configuration
|
||||
caddy_domain: ops.eblu.me
|
||||
|
||||
# Listen on Tailscale interface only (port 443)
|
||||
# Use 8443 during testing to avoid conflicts with Tailscale serve
|
||||
caddy_https_port: 8443
|
||||
|
||||
# Services to proxy
|
||||
# Format: { name: "service", host: "hostname", backend: "url" }
|
||||
caddy_services:
|
||||
# Indri-local services
|
||||
- name: forge
|
||||
host: "forge.{{ caddy_domain }}"
|
||||
backend: "http://localhost:3001"
|
||||
- name: registry
|
||||
host: "registry.{{ caddy_domain }}"
|
||||
backend: "http://localhost:5050"
|
||||
|
||||
# K8s services (via minikube NodePort or ClusterIP)
|
||||
# These will be configured once we determine the correct backend URLs
|
||||
# - name: grafana
|
||||
# host: "grafana.{{ caddy_domain }}"
|
||||
# backend: "http://minikube-ip:nodeport"
|
||||
6
ansible/roles/caddy/handlers/main.yml
Normal file
6
ansible/roles/caddy/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- name: Restart caddy
|
||||
ansible.builtin.shell: |
|
||||
launchctl unload ~/Library/LaunchAgents/mcquack.eblume.caddy.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/mcquack.eblume.caddy.plist
|
||||
changed_when: true
|
||||
80
ansible/roles/caddy/tasks/main.yml
Normal file
80
ansible/roles/caddy/tasks/main.yml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
---
|
||||
# Caddy reverse proxy deployment
|
||||
# Binary is built manually - see ~/code/3rd/caddy/mise.toml
|
||||
|
||||
- name: Verify caddy binary exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ caddy_binary }}"
|
||||
register: caddy_bin
|
||||
failed_when: not caddy_bin.stat.exists
|
||||
changed_when: false
|
||||
|
||||
- name: Create caddy config directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ caddy_config_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Create caddy data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ caddy_data_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Fetch Gandi PAT (when running with --tags caddy)
|
||||
ansible.builtin.command:
|
||||
cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get mco6ka3dc3rmw7zkg2dhia5d2m --fields pat --reveal
|
||||
delegate_to: localhost
|
||||
register: _caddy_gandi_token_fallback
|
||||
changed_when: false
|
||||
no_log: true
|
||||
check_mode: false
|
||||
when: caddy_gandi_token is not defined
|
||||
|
||||
- name: Set Gandi token fact (fallback)
|
||||
ansible.builtin.set_fact:
|
||||
caddy_gandi_token: "{{ _caddy_gandi_token_fallback.stdout }}"
|
||||
no_log: true
|
||||
when: caddy_gandi_token is not defined
|
||||
|
||||
- name: Write Gandi token file
|
||||
ansible.builtin.copy:
|
||||
content: "{{ caddy_gandi_token }}"
|
||||
dest: "{{ caddy_gandi_token_file }}"
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
notify: Restart caddy
|
||||
|
||||
- name: Deploy Caddyfile
|
||||
ansible.builtin.template:
|
||||
src: Caddyfile.j2
|
||||
dest: "{{ caddy_config_dir }}/Caddyfile"
|
||||
mode: "0644"
|
||||
notify: Restart caddy
|
||||
|
||||
- name: Deploy caddy wrapper script
|
||||
ansible.builtin.template:
|
||||
src: caddy-wrapper.sh.j2
|
||||
dest: "{{ caddy_config_dir }}/caddy-wrapper.sh"
|
||||
mode: "0755"
|
||||
notify: Restart caddy
|
||||
|
||||
- name: Deploy caddy LaunchAgent plist
|
||||
ansible.builtin.template:
|
||||
src: caddy.plist.j2
|
||||
dest: ~/Library/LaunchAgents/mcquack.eblume.caddy.plist
|
||||
mode: "0644"
|
||||
notify: Restart caddy
|
||||
|
||||
- name: Check if caddy LaunchAgent is loaded
|
||||
ansible.builtin.command:
|
||||
cmd: launchctl list mcquack.eblume.caddy
|
||||
register: caddy_launchctl
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Load caddy LaunchAgent
|
||||
ansible.builtin.command:
|
||||
cmd: launchctl load ~/Library/LaunchAgents/mcquack.eblume.caddy.plist
|
||||
when: caddy_launchctl.rc != 0
|
||||
changed_when: true
|
||||
38
ansible/roles/caddy/templates/Caddyfile.j2
Normal file
38
ansible/roles/caddy/templates/Caddyfile.j2
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Caddy reverse proxy for blumeops services
|
||||
# Managed by ansible - do not edit manually
|
||||
#
|
||||
# All *.{{ caddy_domain }} requests are proxied to backend services.
|
||||
# TLS certificates are obtained via ACME DNS-01 challenge using Gandi.
|
||||
|
||||
{
|
||||
# Global options
|
||||
admin off
|
||||
}
|
||||
|
||||
# Wildcard certificate for all services
|
||||
*.{{ caddy_domain }}:{{ caddy_https_port }} {
|
||||
tls {
|
||||
dns gandi {env.GANDI_BEARER_TOKEN}
|
||||
}
|
||||
|
||||
{% for service in caddy_services %}
|
||||
@{{ service.name }} host {{ service.host }}
|
||||
handle @{{ service.name }} {
|
||||
reverse_proxy {{ service.backend }}
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
# Fallback for unknown hosts
|
||||
handle {
|
||||
respond "Unknown service" 404
|
||||
}
|
||||
}
|
||||
|
||||
# Base domain (ops.eblu.me)
|
||||
{{ caddy_domain }}:{{ caddy_https_port }} {
|
||||
tls {
|
||||
dns gandi {env.GANDI_BEARER_TOKEN}
|
||||
}
|
||||
|
||||
respond "blumeops services - use a subdomain (e.g., forge.{{ caddy_domain }})"
|
||||
}
|
||||
6
ansible/roles/caddy/templates/caddy-wrapper.sh.j2
Normal file
6
ansible/roles/caddy/templates/caddy-wrapper.sh.j2
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
# Wrapper script for Caddy that loads the Gandi token from file
|
||||
# Managed by ansible - do not edit manually
|
||||
|
||||
export GANDI_BEARER_TOKEN=$(cat {{ caddy_gandi_token_file }})
|
||||
exec {{ caddy_binary }} run --config {{ caddy_config_dir }}/Caddyfile
|
||||
36
ansible/roles/caddy/templates/caddy.plist.j2
Normal file
36
ansible/roles/caddy/templates/caddy.plist.j2
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>mcquack.eblume.caddy</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{{ caddy_config_dir }}/caddy-wrapper.sh</string>
|
||||
</array>
|
||||
|
||||
<key>WorkingDirectory</key>
|
||||
<string>{{ caddy_data_dir }}</string>
|
||||
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>XDG_DATA_HOME</key>
|
||||
<string>/Users/erichblume/.local/share</string>
|
||||
<key>XDG_CONFIG_HOME</key>
|
||||
<string>/Users/erichblume/.config</string>
|
||||
</dict>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>{{ caddy_log_dir }}/mcquack.caddy.out.log</string>
|
||||
|
||||
<key>StandardErrorPath</key>
|
||||
<string>{{ caddy_log_dir }}/mcquack.caddy.err.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Loading…
Add table
Add a link
Reference in a new issue