diff --git a/Cargo.lock b/Cargo.lock index b6a99c12..5fbb7d2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,6 +633,7 @@ dependencies = [ "http 1.0.0", "serde", "serde_json", + "socket2 0.5.8", "sqlx", "thiserror", "tokio", @@ -2399,7 +2400,7 @@ dependencies = [ "http-body 1.0.0", "hyper 1.2.0", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.8", "tokio", ] @@ -4800,12 +4801,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -5283,7 +5284,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.48.0", ] diff --git a/Procfile b/Procfile index 6be57ef7..a52dace4 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -server: cd server && PORT=3002 cargo watch -x run --no-gitignore +server: cd server && systemfd --no-pid -s http::3002 -- cargo watch -x run --no-gitignore tailwind: tailwindcss -i server/src/styles/tailwind.css -o target/tailwind.css --watch diff --git a/README.md b/README.md index 18d79519..a6234d8d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,31 @@ New and Hopefully Improved Personal Site +## Development + +### Zero-Downtime Reloading + +This project uses `systemfd` and `cargo-watch` to enable zero-downtime reloading during development. This allows the server to keep serving requests while recompiling code in the background. + +Install the required tools: + +```bash +cargo install systemfd cargo-watch +``` + +Run the development server with: + +```bash +foreman start +``` + +This setup allows: +1. The socket to remain open during code changes +2. The old version to continue serving requests while the new version compiles +3. A smooth transition to the new version once compiled + +This works automatically in development with `systemfd`, and in production it falls back to normal socket binding with `cargo run`. + ## Screenshots There are generated from `shot-scraper` on the 'live' site diff --git a/cja/Cargo.toml b/cja/Cargo.toml index b0fcbc52..e9221347 100644 --- a/cja/Cargo.toml +++ b/cja/Cargo.toml @@ -26,6 +26,7 @@ tower-http = { version = "0.5.2", features = ["trace"] } axum = "0.7.4" tower-service = "0.3.2" tower = "0.4.13" +socket2 = { version = "0.5.6", features = ["all"] } tracing-common = { path = "../tracing-common" } color-eyre = "0.6.3" diff --git a/cja/src/server/mod.rs b/cja/src/server/mod.rs index 0822cae9..d01b0a3b 100644 --- a/cja/src/server/mod.rs +++ b/cja/src/server/mod.rs @@ -29,12 +29,43 @@ where let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string()); let port: u16 = port.parse()?; - let addr = SocketAddr::from(([0, 0, 0, 0], port)); - let listener = TcpListener::bind(&addr) - .await - .wrap_err("Failed to open port")?; - tracing::debug!("listening on {}", addr); + + // Check if we're being run under systemfd (LISTEN_FD will be set) + let listener = if let Ok(listener_env) = std::env::var("LISTEN_FD") { + // Use the socket2 crate for socket reuse + let builder = socket2::Socket::new( + socket2::Domain::IPV4, + socket2::Type::STREAM, + Some(socket2::Protocol::TCP), + )?; + + // Set reuse options + builder.set_reuse_address(true)?; + #[cfg(unix)] + builder.set_reuse_port(true)?; + + // Bind to the address + let socket_addr = addr.into(); + builder.bind(&socket_addr)?; + builder.listen(1024)?; + + tracing::info!("Zero-downtime reloading enabled (LISTEN_FD={})", listener_env); + tracing::info!("Using reusable socket on port {}", port); + + // Convert to a TcpListener + let std_listener = builder.into(); + TcpListener::from_std(std_listener)? + } else { + // Otherwise, create our own listener + tracing::info!("Starting server on port {}", port); + TcpListener::bind(&addr) + .await + .wrap_err("Failed to open port")? + }; + + let addr = listener.local_addr()?; + tracing::info!("Listening on {}", addr); axum::serve(listener, app) .await