diff --git a/src/worker/deploy/caddy.rs b/src/worker/deploy/caddy.rs index 9f9c098..b69f1b6 100644 --- a/src/worker/deploy/caddy.rs +++ b/src/worker/deploy/caddy.rs @@ -1,6 +1,55 @@ 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..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}; +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 +47,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"); } }