1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//! deCDN node daemon — `decdn-node` binary entry point.
//!
//! Daemon-only: the single subcommand is `decdn-node run [--config <path>]`.
//! User-facing commands (probe, node admin, key-gen, config) live on the
//! `decdn` binary in `crates/cli/`.
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use decdn_common::cli::{self, RunArgs};
use decdn_node::commands;
#[derive(Parser, Debug)]
#[command(
name = "decdn-node",
version,
about = "deCDN cache node daemon",
long_about = "Run a deCDN cache node. The daemon listens for QUIC \
client/peer connections and loopback admin RPC. Use the \
`decdn` CLI for one-shot user/operator commands (probe, \
node admin, key-gen, config)."
)]
struct DaemonCli {
/// Path to TOML config file [default: ~/.decdn/node.toml].
#[arg(long, global = true, value_name = "PATH")]
config: Option<PathBuf>,
#[command(subcommand)]
command: DaemonCommand,
}
#[derive(Subcommand, Debug)]
enum DaemonCommand {
/// Run the deCDN node.
Run(Box<RunArgs>),
}
/// Print a fatal bring-up failure through
/// [`decdn_common::redact::sanitize_err_chain`] rather than letting `anyhow`'s
/// `Termination` impl `Debug`-print the raw chain: an RPC-reachability failure's
/// source error carries the `rpc_url` (API keys live in its path/query). Mid-run
/// chain errors are logged via `tracing` and sanitized at their own call sites.
/// See issue #954.
#[tokio::main]
#[expect(
clippy::print_stderr,
reason = "process exit boundary; tracing may never have started"
)]
async fn main() -> std::process::ExitCode {
// First, so the systemd watchdog also covers bring-up and shutdown drain.
decdn_node::systemd_watchdog::spawn();
// Before any reqwest client: chain RPC, origin pulls, metrics push.
decdn_common::tls::install_crypto_provider();
let parsed = DaemonCli::parse();
let config_path = parsed.config.map(|p| cli::common::expand_tilde(&p));
let result = match parsed.command {
// `Box::pin` the bring-up future: the runtime `run` state machine is
// large (many sequential await points across endpoint/handler/task
// setup) and crosses clippy's `large_futures` threshold. It runs once
// per process, so heap-allocating it has no meaningful cost.
DaemonCommand::Run(run_args) => {
Box::pin(commands::run(config_path.as_deref(), &run_args)).await
}
};
match result {
Ok(()) => std::process::ExitCode::SUCCESS,
Err(e) => {
// `eprintln!` not `tracing::error!`: this is the process exit
// boundary, and the failure may be the one that stopped tracing
// from ever initializing.
eprintln!("Error: {}", decdn_common::redact::sanitize_err_chain(&e));
std::process::ExitCode::FAILURE
}
}
}