From d52bcc15f8e5e430b03a81e39c9fee4c2b12dcaa Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 25 Aug 2026 21:56:04 -0700 Subject: [PATCH] Migrate Gitea proxy to canonical Caddy --- dankclaw/gitea/quadlet/gitea-caddy.container | 31 ------------- dankclaw/gitea/quadlet/gitea.network | 4 +- scripts/deploy-plan.py | 9 ++-- scripts/deploy-remote.sh | 49 +++++++++++++------- scripts/deploy.sh | 2 +- tests/caddyfile.bats | 9 ++++ tests/deployment_flow.bats | 7 +++ tests/deployment_scoping.bats | 32 +++++++++++++ 8 files changed, 87 insertions(+), 56 deletions(-) delete mode 100644 dankclaw/gitea/quadlet/gitea-caddy.container diff --git a/dankclaw/gitea/quadlet/gitea-caddy.container b/dankclaw/gitea/quadlet/gitea-caddy.container deleted file mode 100644 index a39a7ca..0000000 --- a/dankclaw/gitea/quadlet/gitea-caddy.container +++ /dev/null @@ -1,31 +0,0 @@ -# Quadlet unit: Gitea Caddy reverse proxy (rootless, boot-persistent). -# -# Install path on dankclaw: ~/.config/containers/systemd/gitea-caddy.container -# Unit name: gitea-caddy.service (podman 5.x drops the container- prefix) -# -# Serves gitea.franksdank.cloud (public domain, DNS pending). The LAN -# reaches Gitea directly on port 3000. This file holds no secrets. -# See docs/Dankclaw/Gitea.md. - -[Unit] -Description=Gitea Caddy reverse proxy container -Wants=gitea-server.service -After=gitea-server.service network.target - -[Container] -ContainerName=gitea-caddy -Network=gitea.network -Image=docker.io/library/caddy:latest -PublishPort=80:80 -PublishPort=443:443 -Volume=/opt/gitea/data/caddy/Caddyfile:/etc/caddy/Caddyfile -Volume=/opt/gitea/data/caddy/site:/srv -Volume=/opt/gitea/data/caddy/config:/config -Volume=/opt/gitea/data/caddy/data:/root/.local/share/caddy - -[Service] -TimeoutStartSec=900 -Restart=always - -[Install] -WantedBy=default.target diff --git a/dankclaw/gitea/quadlet/gitea.network b/dankclaw/gitea/quadlet/gitea.network index eb2383a..69cda7e 100644 --- a/dankclaw/gitea/quadlet/gitea.network +++ b/dankclaw/gitea/quadlet/gitea.network @@ -4,7 +4,7 @@ # Unit name: gitea-network.service (podman network name: systemd-gitea) # # DNS is enabled by default for quadlet networks. This matters: the -# automatic "podman" default network has dns_enabled=false, so container -# names (gitea-postgres, gitea-server, gitea-caddy) would not resolve. +# automatic "podman" default network has dns_enabled=false, so the +# gitea-postgres and gitea-server container names would not resolve. [Network] diff --git a/scripts/deploy-plan.py b/scripts/deploy-plan.py index 51fa8af..2658f50 100755 --- a/scripts/deploy-plan.py +++ b/scripts/deploy-plan.py @@ -27,7 +27,6 @@ SERVICE_ORDER = ( "open-webui", "gitea-postgres", "gitea-server", - "gitea-caddy", "comfyui", "hermes", "hermes-dashboard", @@ -66,7 +65,6 @@ DANKCLAW_SERVICES = { "open-webui", "gitea-postgres", "gitea-server", - "gitea-caddy", "comfyui", "hermes", "hermes-dashboard", @@ -276,9 +274,12 @@ def services_for_path(path: str, target: str) -> set[str]: elif name == "gitea-server.container": _add(services, "gitea-server") elif name == "gitea-caddy.container": - _add(services, "gitea-caddy") + # Removing the old split proxy requires the canonical + # Caddy handler to retire its installed unit/container + # before it claims ports 80/443. + _add(services, "caddy") elif name == "gitea.network": - _add(services, "gitea-postgres", "gitea-server", "gitea-caddy") + _add(services, "gitea-postgres", "gitea-server") return services return services diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh index 446d4ba..26f22c2 100755 --- a/scripts/deploy-remote.sh +++ b/scripts/deploy-remote.sh @@ -84,7 +84,7 @@ service_is_allowed() { case "$TARGET:$service" in dankclaw:gluetun|dankclaw:searxng-valkey|dankclaw:searxng|dankclaw:pihole|\ dankclaw:open-webui|dankclaw:gitea-postgres|dankclaw:gitea-server|\ - dankclaw:gitea-caddy|dankclaw:comfyui|dankclaw:hermes|\ + dankclaw:comfyui|dankclaw:hermes|\ dankclaw:hermes-dashboard|dankclaw:prometheus|dankclaw:grafana|\ dankclaw:loki|dankclaw:alloy|dankclaw:node-exporter|\ dankclaw:authentik-postgres|dankclaw:authentik-server|\ @@ -248,6 +248,33 @@ deploy_homepage() { run_privileged python3 /usr/local/bin/homepage-status.py /srv/homepage/services.json /srv/homepage/status.json } +retire_legacy_gitea_caddy() { + # The old split proxy owns ports 80/443 on hosts upgraded in place. Stop + # and disable its generated unit before the canonical Caddy is started. + if systemctl --user is-active --quiet gitea-caddy.service 2>/dev/null; then + systemctl --user stop gitea-caddy.service + fi + if systemctl --user is-enabled --quiet gitea-caddy.service 2>/dev/null; then + systemctl --user disable gitea-caddy.service + fi + local unit_dir="$HOME/.config/containers/systemd" + local legacy_trash="$HOME/.AGENTTRASH/$(date +%Y%m%d)-gitea-caddy" + # Podman generators scan the unit directory recursively on this host, so + # leaving the retired file in systemd/.AGENTTRASH would regenerate it. + # Stage it outside the unit tree in the dated home trash instead. + for unit_file in "$unit_dir/gitea-caddy.container" \ + "$unit_dir/.AGENTTRASH/gitea-caddy.container"; do + if [[ -e "$unit_file" ]]; then + mkdir -p "$legacy_trash" + mv -- "$unit_file" "$legacy_trash/" + fi + done + systemctl --user daemon-reload + if podman container exists gitea-caddy; then + podman rm --force gitea-caddy + fi +} + deploy_caddy() { if [[ "$TARGET" != dankclaw ]]; then echo "caddy is owned by dankclaw; refusing to deploy it on $TARGET" >&2 @@ -263,22 +290,9 @@ deploy_caddy() { podman build --no-cache --label "cfhash=$cfhash" -t "$image:latest" \ -f dankclaw/caddy/Containerfile . fi - # The canonical reverse proxy may be gitea-caddy on a host with the old - # split deployment. Never stop or replace it implicitly. Refuse the Caddy - # deployment until the operator has explicitly migrated port ownership. - if podman container exists gitea-caddy && ! podman container exists caddy; then - echo "FAIL: gitea-caddy owns the reverse-proxy deployment; refusing to replace it with caddy." >&2 - return 1 - fi - if podman container exists gitea-caddy && podman container exists caddy; then - local gitea_state caddy_state - gitea_state="$(podman inspect gitea-caddy --format '{{.State.Status}}' 2>/dev/null || true)" - caddy_state="$(podman inspect caddy --format '{{.State.Status}}' 2>/dev/null || true)" - if [[ "$gitea_state" == running && "$caddy_state" != running ]]; then - echo "FAIL: gitea-caddy currently owns ports 80/443; refusing to start caddy." >&2 - return 1 - fi - fi + # Retire the old split proxy before claiming ports 80/443. This is + # idempotent and leaves the old unit recoverable in the target trash. + retire_legacy_gitea_caddy if podman container exists caddy; then podman-compose -f dankclaw/compose.yaml up -d --no-deps caddy local reloaded=0 @@ -482,7 +496,6 @@ deploy_service() { pihole) deploy_pihole ;; gitea-postgres) deploy_gitea_unit gitea-postgres gitea-postgres ;; gitea-server) deploy_gitea_unit gitea-server gitea-server ;; - gitea-caddy) deploy_gitea_unit gitea-caddy gitea-caddy ;; frank-default-large) deploy_llm_large ;; frank-default-small) deploy_llm_small ;; frank-default-npu) deploy_llm_npu ;; diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 82a2a0f..69aabd8 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -115,7 +115,7 @@ valid_service() { case "$TARGET:$service" in dankclaw:gluetun|dankclaw:searxng-network|dankclaw:searxng-valkey|dankclaw:searxng|\ dankclaw:pihole|dankclaw:open-webui|dankclaw:gitea-postgres|dankclaw:gitea-server|\ - dankclaw:gitea-caddy|dankclaw:comfyui|dankclaw:hermes|dankclaw:hermes-dashboard|\ + dankclaw:comfyui|dankclaw:hermes|dankclaw:hermes-dashboard|\ dankclaw:prometheus|dankclaw:grafana|dankclaw:loki|dankclaw:alloy|\ dankclaw:node-exporter|dankclaw:authentik-postgres|dankclaw:authentik-server|\ dankclaw:authentik-worker|dankclaw:caddy|dankclaw:homepage|\ diff --git a/tests/caddyfile.bats b/tests/caddyfile.bats index 8690529..fbb38a7 100644 --- a/tests/caddyfile.bats +++ b/tests/caddyfile.bats @@ -85,6 +85,15 @@ CADDYFILE="$REPO_ROOT/dankclaw/Caddyfile" grep -q 'curl.*-sf.*http://localhost:80/' "$REPO_ROOT/dankclaw/compose.yaml" } +@test "Gitea has no standalone reverse-proxy Quadlet" { + [ ! -e "$REPO_ROOT/dankclaw/gitea/quadlet/gitea-caddy.container" ] +} + +@test "the canonical Caddy owns the Gitea route" { + grep -qF 'gitea.franksdank.cloud {' "$CADDYFILE" + grep -qF 'reverse_proxy 192.168.0.143:3000' "$CADDYFILE" +} + # --- Phase 2: real TLS via DNS-01 (Porkbun wildcard cert) --- @test "all vhosts use TLS except the legacy .casa name" { diff --git a/tests/deployment_flow.bats b/tests/deployment_flow.bats index 6520908..85d1fcd 100644 --- a/tests/deployment_flow.bats +++ b/tests/deployment_flow.bats @@ -47,6 +47,13 @@ load helpers ! grep -qE 'podman-compose.*(up|restart).*\$\(' "$REPO_ROOT/scripts/deploy-remote.sh" } +@test "Caddy migration retires the standalone Gitea proxy" { + grep -q 'retire_legacy_gitea_caddy' "$REPO_ROOT/scripts/deploy-remote.sh" + grep -q 'systemctl --user stop gitea-caddy.service' "$REPO_ROOT/scripts/deploy-remote.sh" + grep -q 'systemctl --user disable gitea-caddy.service' "$REPO_ROOT/scripts/deploy-remote.sh" + ! grep -q 'deploy_gitea_unit gitea-caddy' "$REPO_ROOT/scripts/deploy-remote.sh" +} + @test "remote runner has separate handlers for both inference services" { grep -q 'deploy_llm_large' "$REPO_ROOT/scripts/deploy-remote.sh" grep -q 'deploy_llm_small' "$REPO_ROOT/scripts/deploy-remote.sh" diff --git a/tests/deployment_scoping.bats b/tests/deployment_scoping.bats index a8db6dc..6c2f05a 100644 --- a/tests/deployment_scoping.bats +++ b/tests/deployment_scoping.bats @@ -97,6 +97,38 @@ REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh" [ "$output" = $'gluetun\nsearxng' ] } +@test "retiring the standalone Gitea proxy deploys the canonical Caddy" { + run python3 "$PLANNER" --target dankclaw --paths \ + dankclaw/gitea/quadlet/gitea-caddy.container + [ "$status" -eq 0 ] + [ "$output" = "caddy" ] +} + +@test "Gitea network changes exclude the retired standalone proxy" { + run python3 "$PLANNER" --target dankclaw --paths \ + dankclaw/gitea/quadlet/gitea.network + [ "$status" -eq 0 ] + [ "$output" = $'gitea-postgres\ngitea-server' ] +} + +@test "remote Caddy deployment no longer blocks on the retired Gitea proxy" { + ! grep -q 'refusing to replace it with caddy' "$REMOTE_DEPLOY" + ! grep -q 'podman container exists gitea-caddy &&' "$REMOTE_DEPLOY" +} + +@test "remote Caddy deployment retires the installed Gitea proxy" { + grep -q 'retire_legacy_gitea_caddy' "$REMOTE_DEPLOY" + grep -q 'systemctl --user stop gitea-caddy.service' "$REMOTE_DEPLOY" + grep -q 'systemctl --user disable gitea-caddy.service' "$REMOTE_DEPLOY" + grep -q 'gitea-caddy.container' "$REMOTE_DEPLOY" + grep -q 'HOME/.AGENTTRASH' "$REMOTE_DEPLOY" +} + +@test "remote deployment inventory no longer accepts gitea-caddy as a service" { + ! grep -qE 'dankclaw:gitea-caddy|gitea-caddy\) deploy' "$REPO_ROOT/scripts/deploy.sh" "$REPO_ROOT/scripts/deploy-remote.sh" + ! grep -q '"gitea-caddy"' "$REPO_ROOT/scripts/deploy-plan.py" +} + @test "remote deployer carries per-service deployment state" { [ -x "$REMOTE_DEPLOY" ] grep -q 'deploy-state' "$REMOTE_DEPLOY"