Implement SIGHUP certificate reloading for Let's Encrypt
- Add tokio signal handling for SIGHUP - Implement thread-safe TLS acceptor reloading with Mutex - Modify main loop to handle signals alongside connections - Update systemd service (already had ExecReload) - Add certbot hook script documentation to INSTALL.md - Enable zero-downtime certificate renewal support
This commit is contained in:
parent
ea8083fe1f
commit
caf9d0984f
3 changed files with 102 additions and 14 deletions
35
dist/INSTALL.md
vendored
35
dist/INSTALL.md
vendored
|
|
@ -200,6 +200,41 @@ See `config.toml` for all available options. Key settings:
|
||||||
- `max_concurrent_requests`: Connection limit
|
- `max_concurrent_requests`: Connection limit
|
||||||
- `log_level`: Logging verbosity
|
- `log_level`: Logging verbosity
|
||||||
|
|
||||||
|
## Certificate Management
|
||||||
|
|
||||||
|
The server supports automatic certificate reloading via SIGHUP signals.
|
||||||
|
|
||||||
|
### Let's Encrypt Integration
|
||||||
|
|
||||||
|
For automatic certificate renewal with certbot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create post-renewal hook
|
||||||
|
sudo tee /etc/letsencrypt/renewal-hooks/post/reload-pollux.sh > /dev/null << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
# Reload Pollux after Let's Encrypt certificate renewal
|
||||||
|
|
||||||
|
systemctl reload pollux
|
||||||
|
logger -t certbot-pollux-reload "Reloaded pollux after certificate renewal"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Make it executable
|
||||||
|
sudo chmod +x /etc/letsencrypt/renewal-hooks/post/reload-pollux.sh
|
||||||
|
|
||||||
|
# Test the hook
|
||||||
|
sudo /etc/letsencrypt/renewal-hooks/post/reload-pollux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Certificate Reload
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reload certificates without restarting
|
||||||
|
sudo systemctl reload pollux
|
||||||
|
|
||||||
|
# Check reload in logs
|
||||||
|
sudo journalctl -u pollux -f
|
||||||
|
```
|
||||||
|
|
||||||
## Upgrading
|
## Upgrading
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
2
dist/pollux.service
vendored
2
dist/pollux.service
vendored
|
|
@ -15,6 +15,8 @@ NoNewPrivileges=yes
|
||||||
ProtectHome=yes
|
ProtectHome=yes
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
ReadOnlyPaths=/etc/pollux /etc/letsencrypt/live/example.com /var/www/example.com
|
ReadOnlyPaths=/etc/pollux /etc/letsencrypt/live/example.com /var/www/example.com
|
||||||
|
# NOTE: Adjust /etc/letsencrypt/live/example.com and /var/www/example.com to match your config
|
||||||
|
# The server needs read access to config, certificates, and content files
|
||||||
# NOTE: Adjust paths to match your config:
|
# NOTE: Adjust paths to match your config:
|
||||||
# - /etc/letsencrypt/live/example.com for Let's Encrypt certs
|
# - /etc/letsencrypt/live/example.com for Let's Encrypt certs
|
||||||
# - /var/www/example.com for your content root
|
# - /var/www/example.com for your content root
|
||||||
|
|
|
||||||
77
src/main.rs
77
src/main.rs
|
|
@ -8,10 +8,32 @@ use clap::Parser;
|
||||||
use rustls::ServerConfig;
|
use rustls::ServerConfig;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
use tokio_rustls::TlsAcceptor;
|
use tokio_rustls::TlsAcceptor;
|
||||||
use logging::init_logging;
|
use logging::init_logging;
|
||||||
|
|
||||||
|
async fn reload_tls_acceptor(
|
||||||
|
cert_path: &str,
|
||||||
|
key_path: &str,
|
||||||
|
) -> Result<TlsAcceptor, Box<dyn std::error::Error>> {
|
||||||
|
tracing::info!("Reloading TLS certificates");
|
||||||
|
|
||||||
|
let certs = tls::load_certs(cert_path)?;
|
||||||
|
let key = tls::load_private_key(key_path)?;
|
||||||
|
|
||||||
|
let config = ServerConfig::builder()
|
||||||
|
.with_safe_defaults()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_single_cert(certs, key)?;
|
||||||
|
|
||||||
|
let acceptor = TlsAcceptor::from(Arc::new(config));
|
||||||
|
|
||||||
|
tracing::info!("TLS certificates reloaded successfully");
|
||||||
|
Ok(acceptor)
|
||||||
|
}
|
||||||
|
|
||||||
fn print_startup_info(host: &str, port: u16, root: &str, cert: &str, key: &str, log_level: Option<&str>, max_concurrent: usize) {
|
fn print_startup_info(host: &str, port: u16, root: &str, cert: &str, key: &str, log_level: Option<&str>, max_concurrent: usize) {
|
||||||
println!("Pollux Gemini Server");
|
println!("Pollux Gemini Server");
|
||||||
println!("Listening on: {}:{}", host, port);
|
println!("Listening on: {}:{}", host, port);
|
||||||
|
|
@ -104,27 +126,56 @@ async fn main() {
|
||||||
.with_no_client_auth()
|
.with_no_client_auth()
|
||||||
.with_single_cert(certs, key).unwrap();
|
.with_single_cert(certs, key).unwrap();
|
||||||
|
|
||||||
let acceptor = TlsAcceptor::from(Arc::new(config));
|
let initial_acceptor = TlsAcceptor::from(Arc::new(config));
|
||||||
|
let acceptor = Arc::new(Mutex::new(initial_acceptor));
|
||||||
|
|
||||||
let listener = TcpListener::bind(format!("{}:{}", bind_host, port)).await.unwrap();
|
let listener = TcpListener::bind(format!("{}:{}", bind_host, port)).await.unwrap();
|
||||||
|
|
||||||
|
// Create SIGHUP signal handler for certificate reload
|
||||||
|
let mut sighup = signal(SignalKind::hangup())
|
||||||
|
.map_err(|e| format!("Failed to create SIGHUP handler: {}", e))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Print startup information
|
// Print startup information
|
||||||
print_startup_info(&bind_host, port, &root, &cert_path, &key_path, Some(log_level), max_concurrent_requests);
|
print_startup_info(&bind_host, port, &root, &cert_path, &key_path, Some(log_level), max_concurrent_requests);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (stream, _) = listener.accept().await.unwrap();
|
tokio::select! {
|
||||||
tracing::debug!("Accepted connection from {}", stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()));
|
// Handle new connections
|
||||||
let acceptor = acceptor.clone();
|
result = listener.accept() => {
|
||||||
let dir = root.clone();
|
let (stream, _) = result.unwrap();
|
||||||
let expected_hostname = hostname.clone(); // Use configured hostname
|
tracing::debug!("Accepted connection from {}", stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()));
|
||||||
let max_concurrent = max_concurrent_requests;
|
let acceptor = Arc::clone(&acceptor);
|
||||||
let test_delay = test_processing_delay;
|
let dir = root.clone();
|
||||||
tokio::spawn(async move {
|
let expected_hostname = hostname.clone();
|
||||||
if let Ok(stream) = acceptor.accept(stream).await {
|
let max_concurrent = max_concurrent_requests;
|
||||||
if let Err(e) = server::handle_connection(stream, &dir, &expected_hostname, port, max_concurrent, test_delay).await {
|
let test_delay = test_processing_delay;
|
||||||
tracing::error!("Error handling connection: {}", e);
|
tokio::spawn(async move {
|
||||||
|
let acceptor_guard = acceptor.lock().await;
|
||||||
|
if let Ok(stream) = acceptor_guard.accept(stream).await {
|
||||||
|
drop(acceptor_guard); // Release lock before long-running handler
|
||||||
|
if let Err(e) = server::handle_connection(stream, &dir, &expected_hostname, port, max_concurrent, test_delay).await {
|
||||||
|
tracing::error!("Error handling connection: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle SIGHUP for certificate reload
|
||||||
|
_ = sighup.recv() => {
|
||||||
|
tracing::info!("Received SIGHUP, reloading certificates");
|
||||||
|
match reload_tls_acceptor(&cert_path, &key_path).await {
|
||||||
|
Ok(new_acceptor) => {
|
||||||
|
let mut acceptor_guard = acceptor.lock().await;
|
||||||
|
*acceptor_guard = new_acceptor;
|
||||||
|
tracing::info!("TLS certificates reloaded successfully");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to reload TLS certificates: {}", e);
|
||||||
|
// Continue with old certificates
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue