From d55ef1c2f00820d1f77cce455e53f86f080a8569 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Wed, 7 Jan 2026 13:25:36 +0000 Subject: [PATCH 1/2] fix: wait for Caddy to be ready before restoring routes on startup The worker was attempting to restore Caddy routes immediately on startup, before Caddy's admin API was available. This caused all route restoration to fail when both services started simultaneously. Added wait_for_caddy_ready() that polls the Caddy admin API every 500ms with a 60s timeout before attempting route restoration. --- src/worker/deploy/caddy.rs | 52 ++++++++++++++++++++++++++++++++++++++ src/worker/deploy/mod.rs | 2 +- src/worker/server.rs | 23 ++++++++++------- 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/worker/deploy/caddy.rs b/src/worker/deploy/caddy.rs index 9f9c098..6bfd404 100644 --- a/src/worker/deploy/caddy.rs +++ b/src/worker/deploy/caddy.rs @@ -1,6 +1,58 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::path::Path; +use std::time::Duration; + +const CADDY_READY_TIMEOUT: Duration = Duration::from_secs(60); +const CADDY_READY_INTERVAL: Duration = Duration::from_millis(500); + +/// Wait for Caddy admin API to be ready +/// +/// Polls the Caddy admin API until it responds or timeout is reached. +/// This should be called before attempting to restore routes on startup. +pub async fn wait_for_caddy_ready( + http_client: &reqwest::Client, + caddy_admin_api: &str, +) -> Result<()> { + let start = std::time::Instant::now(); + let url = format!("{}/config/", caddy_admin_api); + + tracing::info!( + caddy_admin_api = caddy_admin_api, + timeout_secs = CADDY_READY_TIMEOUT.as_secs(), + "Waiting for Caddy to be ready" + ); + + loop { + match http_client.get(&url).send().await { + Ok(response) if response.status().is_success() => { + tracing::info!( + elapsed_ms = start.elapsed().as_millis() as u64, + "Caddy admin API is ready" + ); + return Ok(()); + } + Ok(response) => { + tracing::debug!( + status = %response.status(), + "Caddy not ready yet (unexpected status)" + ); + } + Err(e) => { + tracing::debug!(error = %e, "Caddy not ready yet"); + } + } + + if start.elapsed() >= CADDY_READY_TIMEOUT { + anyhow::bail!( + "Caddy admin API not ready after {:?}", + CADDY_READY_TIMEOUT + ); + } + + tokio::time::sleep(CADDY_READY_INTERVAL).await; + } +} /// Configure a Caddy route for a deployment via the admin API /// diff --git a/src/worker/deploy/mod.rs b/src/worker/deploy/mod.rs index 7ec6796..e5e491c 100644 --- a/src/worker/deploy/mod.rs +++ b/src/worker/deploy/mod.rs @@ -2,6 +2,6 @@ pub mod caddy; pub mod cloudflare; pub mod sites; -pub use caddy::{configure_caddy_route, remove_caddy_route}; +pub use caddy::{configure_caddy_route, remove_caddy_route, wait_for_caddy_ready}; pub use cloudflare::{CloudflareClient, CloudflareConfig}; pub use sites::{SiteMetadata, restore_all_routes, write_site_metadata}; diff --git a/src/worker/server.rs b/src/worker/server.rs index 07ad29c..ac08cec 100644 --- a/src/worker/server.rs +++ b/src/worker/server.rs @@ -8,7 +8,7 @@ use axum::{ use tower_http::trace::TraceLayer; use crate::config::WorkerConfig; -use crate::worker::deploy::{CloudflareClient, CloudflareConfig, restore_all_routes}; +use crate::worker::deploy::{CloudflareClient, CloudflareConfig, restore_all_routes, wait_for_caddy_ready}; use crate::worker::handlers::{handle_build, handle_cleanup}; /// Shared application state @@ -45,15 +45,20 @@ pub async fn run(config: WorkerConfig) -> Result<()> { cloudflare, }; - // Restore Caddy routes for existing site deployments - match restore_all_routes(&http_client, &config.caddy_admin_api, &config.sites_dir).await { - Ok(count) => { - if count > 0 { - tracing::info!(count, "Restored Caddy routes for existing sites"); + // Wait for Caddy admin API to be ready before restoring routes + if let Err(e) = wait_for_caddy_ready(&http_client, &config.caddy_admin_api).await { + tracing::error!(error = %e, "Caddy admin API not available, skipping route restoration"); + } else { + // Restore Caddy routes for existing site deployments + match restore_all_routes(&http_client, &config.caddy_admin_api, &config.sites_dir).await { + Ok(count) => { + if count > 0 { + tracing::info!(count, "Restored Caddy routes for existing sites"); + } + } + Err(e) => { + tracing::error!(error = %e, "Failed to restore Caddy routes"); } - } - Err(e) => { - tracing::error!(error = %e, "Failed to restore Caddy routes"); } } From 94c25557acb984f84faf44b926003471e6d0f3ff Mon Sep 17 00:00:00 2001 From: mfw78 Date: Wed, 7 Jan 2026 13:36:00 +0000 Subject: [PATCH 2/2] style: fix formatting --- src/worker/deploy/caddy.rs | 5 +---- src/worker/server.rs | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/worker/deploy/caddy.rs b/src/worker/deploy/caddy.rs index 6bfd404..b69f1b6 100644 --- a/src/worker/deploy/caddy.rs +++ b/src/worker/deploy/caddy.rs @@ -44,10 +44,7 @@ pub async fn wait_for_caddy_ready( } if start.elapsed() >= CADDY_READY_TIMEOUT { - anyhow::bail!( - "Caddy admin API not ready after {:?}", - CADDY_READY_TIMEOUT - ); + anyhow::bail!("Caddy admin API not ready after {:?}", CADDY_READY_TIMEOUT); } tokio::time::sleep(CADDY_READY_INTERVAL).await; diff --git a/src/worker/server.rs b/src/worker/server.rs index ac08cec..637e5ab 100644 --- a/src/worker/server.rs +++ b/src/worker/server.rs @@ -8,7 +8,9 @@ use axum::{ use tower_http::trace::TraceLayer; use crate::config::WorkerConfig; -use crate::worker::deploy::{CloudflareClient, CloudflareConfig, restore_all_routes, wait_for_caddy_ready}; +use crate::worker::deploy::{ + CloudflareClient, CloudflareConfig, restore_all_routes, wait_for_caddy_ready, +}; use crate::worker::handlers::{handle_build, handle_cleanup}; /// Shared application state