Compare commits

...

2 commits

Author SHA1 Message Date
74cf34edff
fix(clippy): split code a bit
All checks were successful
/ build (push) Successful in 1s
/ check-integration-tests (push) Successful in 10s
/ report-size (push) Successful in 2s
Main was too big for clippy
2025-06-07 00:18:30 +02:00
ae0dbf3bd9
chore(deps): lock file maintenance
Some checks failed
/ build (push) Successful in 1m15s
/ check-integration-tests (push) Failing after 2m23s
/ report-size (push) Successful in 10s
2025-06-06 22:00:43 +02:00
3 changed files with 90 additions and 81 deletions

16
Cargo.lock generated
View file

@ -185,9 +185,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.25" version = "1.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@ -834,9 +834,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.0" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "socket2" name = "socket2"
@ -1027,9 +1027,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-attributes" name = "tracing-attributes"
version = "0.1.28" version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1038,9 +1038,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.33" version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable", "valuable",

20
flake.lock generated
View file

@ -37,11 +37,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1749008870, "lastModified": 1749238035,
"narHash": "sha256-5QEAcgawS2tOJrLr+U5DtzlShSCEpeg7PZDC7txvLQs=", "narHash": "sha256-+w+VydE8NSQXu8RSNLhn0fVMaEoFPgUbNYa8m1rewuM=",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", "rev": "d3d2d80a2191a73d1e86456a751b83aa13085d7d",
"type": "tarball", "type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-25.11pre810143.c2a03962b8e2/nixexprs.tar.xz?rev=c2a03962b8e24e669fb37b7df10e7c79531ff1a4" "url": "https://releases.nixos.org/nixos/unstable/nixos-25.11pre811203.d3d2d80a2191/nixexprs.tar.xz?rev=d3d2d80a2191a73d1e86456a751b83aa13085d7d"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",
@ -65,11 +65,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1749091064, "lastModified": 1749177458,
"narHash": "sha256-TGtYjzRX0sueFhwYsnNNFF5TTKnpnloznpIghLzxeXo=", "narHash": "sha256-9HNq3EHZIvvxXQyEn0sYOywcESF1Xqw2Q8J1ZewcXuk=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "12419593ce78f2e8e1e89a373c6515885e218acb", "rev": "d58933b88cef7a05e9677e94352fd6fedba402cd",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -100,11 +100,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1748243702, "lastModified": 1749194973,
"narHash": "sha256-9YzfeN8CB6SzNPyPm2XjRRqSixDopTapaRsnTpXUEY8=", "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "1f3f7b784643d488ba4bf315638b2b0a4c5fb007", "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -128,7 +128,7 @@ impl SavedIPs {
.chain(self.ipv6.map(IpAddr::V6)) .chain(self.ipv6.map(IpAddr::V6))
} }
fn from_str(data: &str) -> miette::Result<Self> { fn from_str(data: &str) -> Result<Self> {
match data.parse::<IpAddr>() { match data.parse::<IpAddr>() {
// Old format // Old format
Ok(IpAddr::V4(ipv4)) => Ok(Self { Ok(IpAddr::V4(ipv4)) => Ok(Self {
@ -145,7 +145,7 @@ impl SavedIPs {
} }
impl AppState<'static> { impl AppState<'static> {
fn from_args(args: &Opts, config: &config::Config) -> miette::Result<Self> { fn from_args(args: &Opts, config: &config::Config) -> Result<Self> {
let Opts { let Opts {
verbosity: _, verbosity: _,
data_dir, data_dir,
@ -180,7 +180,7 @@ impl AppState<'static> {
// Load keyfile // Load keyfile
key_file: key_file key_file: key_file
.as_deref() .as_deref()
.map(|path| -> miette::Result<_> { .map(|path| -> Result<_> {
std::fs::File::open(path) std::fs::File::open(path)
.into_diagnostic() .into_diagnostic()
.wrap_err_with(|| { .wrap_err_with(|| {
@ -255,6 +255,18 @@ impl std::str::FromStr for Ipv6Prefix {
} }
} }
fn load_password(path: &Path) -> Result<Box<[u8]>> {
let pass = std::fs::read_to_string(path).into_diagnostic()?;
let pass: Box<[u8]> = URL_SAFE_NO_PAD
.decode(pass.trim().as_bytes())
.into_diagnostic()
.wrap_err_with(|| format!("failed to decode password from {}", path.display()))?
.into();
Ok(pass)
}
#[tracing::instrument(err)] #[tracing::instrument(err)]
fn main() -> Result<()> { fn main() -> Result<()> {
// set panic hook to pretty print with miette's formatter // set panic hook to pretty print with miette's formatter
@ -310,18 +322,8 @@ fn main() -> Result<()> {
let password_hash = config let password_hash = config
.password .password
.password_file .password_file
.map(|path| -> miette::Result<_> { .as_deref()
let path = path.as_path(); .map(load_password)
let pass = std::fs::read_to_string(path).into_diagnostic()?;
let pass: Box<[u8]> = URL_SAFE_NO_PAD
.decode(pass.trim().as_bytes())
.into_diagnostic()
.wrap_err_with(|| format!("failed to decode password from {}", path.display()))?
.into();
Ok(pass)
})
.transpose() .transpose()
.wrap_err("failed to load password hash")?; .wrap_err("failed to load password hash")?;
@ -336,63 +338,70 @@ fn main() -> Result<()> {
.into_diagnostic() .into_diagnostic()
.wrap_err("failed to start the tokio runtime")?; .wrap_err("failed to start the tokio runtime")?;
rt.block_on(async { rt.block_on(async_main(state, config, password_hash))
// Update DNS record with previous IPs (if available) .wrap_err("failed to run main loop")
let ips = state.last_ips.lock().await.clone(); }
let mut actions = ips #[tracing::instrument(err, skip(state, pass))]
.ips() async fn async_main(
.filter(|ip| config.records.ip_type.valid_for_type(*ip)) state: AppState<'static>,
.flat_map(|ip| nsupdate::Action::from_records(ip, state.ttl, state.records)) config: Config,
.peekable(); pass: Option<Box<[u8]>>,
) -> Result<()> {
// Update DNS record with previous IPs (if available)
let ips = state.last_ips.lock().await.clone();
if actions.peek().is_some() { let mut actions = ips
match nsupdate::nsupdate(state.key_file, actions).await { .ips()
Ok(status) => { .filter(|ip| config.records.ip_type.valid_for_type(*ip))
if !status.success() { .flat_map(|ip| nsupdate::Action::from_records(ip, state.ttl, state.records))
error!("nsupdate failed: code {status}"); .peekable();
bail!("nsupdate returned with code {status}");
} if actions.peek().is_some() {
} match nsupdate::nsupdate(state.key_file, actions).await {
Err(err) => { Ok(status) => {
error!("Failed to update records with previous IP: {err}"); if !status.success() {
return Err(err) error!("nsupdate failed: code {status}");
.into_diagnostic() bail!("nsupdate returned with code {status}");
.wrap_err("failed to update records with previous IP");
} }
} }
Err(err) => {
error!("Failed to update records with previous IP: {err}");
return Err(err)
.into_diagnostic()
.wrap_err("failed to update records with previous IP");
}
} }
}
// Create services // Create services
let app = Router::new().route("/update", get(update_records)); let app = Router::new().route("/update", get(update_records));
// if a password is provided, validate it // if a password is provided, validate it
let app = if let Some(pass) = password_hash { let app = if let Some(pass) = pass {
app.layer(auth::layer( app.layer(auth::layer(
Box::leak(pass), Box::leak(pass),
Box::leak(config.password.salt), Box::leak(config.password.salt),
)) ))
} else { } else {
app app
} }
.layer(config.records.ip_source.into_extension()) .layer(config.records.ip_source.into_extension())
.with_state(state); .with_state(state);
let config::Server { address } = config.server; let config::Server { address } = config.server;
// Start services // Start services
info!("starting listener on {address}"); info!("starting listener on {address}");
let listener = tokio::net::TcpListener::bind(address) let listener = tokio::net::TcpListener::bind(address)
.await
.into_diagnostic()?;
info!("listening on {address}");
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.await .await
.into_diagnostic() .into_diagnostic()?;
}) info!("listening on {address}");
.wrap_err("failed to run main loop") axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.await
.into_diagnostic()
} }
/// Serde deserialization decorator to map empty Strings to None, /// Serde deserialization decorator to map empty Strings to None,