Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions cja/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
41 changes: 36 additions & 5 deletions cja/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading