listenfd support

This commit is contained in:
Robin Appelman 2025-06-24 22:36:04 +02:00
commit 0f3ac7ebd1
6 changed files with 130 additions and 10 deletions

96
Cargo.lock generated
View file

@ -148,6 +148,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytes"
version = "1.10.1"
@ -381,6 +387,16 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -393,6 +409,17 @@ version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "listenfd"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b87bc54a4629b4294d0b3ef041b64c40c611097a677d9dc07b2c67739fe39dba"
dependencies = [
"libc",
"uuid",
"winapi",
]
[[package]]
name = "log"
version = "0.4.27"
@ -621,6 +648,7 @@ version = "0.1.0"
dependencies = [
"axum",
"clap",
"listenfd",
"main_error",
"serde",
"thiserror",
@ -864,6 +892,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "valuable"
version = "0.1.1"
@ -876,6 +914,64 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -13,3 +13,4 @@ serde = { version = "1.0.219", features = ["derive"] }
toml = "0.8.23"
thiserror = "2.0.12"
clap = { version = "4.5.40", features = ["derive"] }
listenfd = "1.0.2"

View file

@ -11,7 +11,7 @@ A basic web server for
# address = "0.0.0.0" # defaults to "127.0.0.1"
# port = 1234 # defaults to 7366
# you can set it to listen over a unix socket instead.
socket = "/var/run/single-file.sock"
socket = "/run/single-file.sock"
[single-file]
# path to single-file-cli
@ -22,6 +22,10 @@ executable = "/path/to/single-file" # defaults to "single-file"
executable = "/path/to/chome" # defaults to "chromium"
```
In additional to the `listen` configuration, the server will automatically
detect if it get's activated trough systemd socket activation and takes the
provided socket.
## Usage
```bash

View file

@ -9,7 +9,7 @@ use toml::from_str;
pub struct Config {
#[serde(default)]
pub listen: ListenConfig,
#[serde(default)]
#[serde(default, rename = "single-file")]
pub single_file: SingleFileConfig,
#[serde(default)]
pub browser: BrowserConfig,

View file

@ -4,7 +4,7 @@ use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use thiserror::Error;
use tokio::task::{JoinError, spawn_blocking};
use tracing::info;
use tracing::{debug, info, warn};
pub async fn fetch_single_file(config: &Config, url: &str) -> Result<String, FetchError> {
info!(url, "fetching url");
@ -20,9 +20,16 @@ pub async fn fetch_single_file(config: &Config, url: &str) -> Result<String, Fet
command.arg(url).arg("--dump-content");
debug!(?command, "running single-file command");
let output = spawn_blocking(move || command.output().map_err(FetchError::Command)).await??;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
if !output.stderr.is_empty() {
warn!(output = %String::from_utf8_lossy(&output.stderr), "command returned stderr");
}
let result = String::from_utf8_lossy(&output.stdout).to_string();
debug!(length = result.len(), status = ?output.status.code().unwrap_or_default(), "single-file done");
Ok(result)
}
#[derive(Debug, Error)]

View file

@ -9,10 +9,12 @@ use axum::{debug_handler, Router};
use axum::extract::{Path, State};
use axum::routing::get;
use clap::Parser;
use listenfd::ListenFd;
use main_error::MainResult;
use thiserror::Error;
use tokio::net::UnixListener;
use tokio::signal::ctrl_c;
use tracing::error;
use crate::config::{Config, ListenConfig};
use crate::fetch::{fetch_single_file, FetchError};
@ -46,8 +48,18 @@ async fn main() -> MainResult {
ctrl_c().await.ok();
};
match &config.listen {
ListenConfig::Unix { socket: path } => {
let mut systemd_listener = ListenFd::from_env();
match (systemd_listener.take_unix_listener(0), &config.listen) {
(Ok(Some(listener)), _) => {
println!("listening on fd 0");
let listener = UnixListener::from_std(listener).unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(cancel)
.await
.unwrap();
}
(_, ListenConfig::Unix { socket: path }) => {
if let Some(parent) = path.parent() {
if !parent.exists() {
create_dir_all(parent)
@ -60,7 +72,7 @@ async fn main() -> MainResult {
let uds = UnixListener::bind(path)
.map_err(|error| SetupError::BindUnix { error, path: path.clone() })?;
let _ = set_permissions(path, PermissionsExt::from_mode(0o660));
let _ = set_permissions(path, PermissionsExt::from_mode(0o666));
println!("listening on {}", path.display());
@ -70,7 +82,7 @@ async fn main() -> MainResult {
.await
.unwrap();
}
ListenConfig::Tcp {address, port} => {
(_, ListenConfig::Tcp {address, port}) => {
let address = SocketAddr::new(*address, *port);
let listener = tokio::net::TcpListener::bind(address).await
.map_err(|error| SetupError::BindTcp { error, address })?;
@ -112,6 +124,6 @@ enum SetupError {
#[debug_handler]
async fn fetch(State(state): State<App>, Path(url): Path<String>) -> Result<String, FetchError> {
fetch_single_file(state.config, &url).await
fetch_single_file(state.config, &url).await.inspect_err(|error| error!(%error, url, "Error while fetching url"))
}