Make joining a network idempotent and shut down cleanly on every path
Running `tsunagi up` twice with the same arguments failed with "network ... is already active", and then dropped the iroh endpoint without closing it. A configured network is activated automatically at startup, so the second run found it already up. `join_network` is declarative — "be a member of this network" — so joining one that is already active now succeeds and changes nothing. `activate_network` stays strict for callers that specifically want to know whether an inactive network was started. The CLI now closes the agent on the error path too, and handles SIGTERM as well as Ctrl-C, so a service manager stopping the agent gets the same clean shutdown an interactive user does. Also documents the two lookups people conflate: resolving one endpoint's address is iroh's public pkarr/DNS service and works today, which is why `--peer <endpoint-id>` needs no address; finding who is in a network is this project's `NetworkDiscovery` and is still static bootstrap only. Notes in the README and the threat model that `n0` and `direct` publish this endpoint's addresses to a public third-party service. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+45
-8
@@ -71,13 +71,17 @@ impl PathArgs {
|
||||
}
|
||||
|
||||
/// How much external connectivity machinery the endpoint may use.
|
||||
///
|
||||
/// `direct` and `n0` publish this endpoint's addresses, keyed by its endpoint
|
||||
/// id, to Number 0's public lookup service, and resolve peers through it.
|
||||
/// That is what makes `--peer <endpoint-id>` work without an address.
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
enum Transport {
|
||||
/// Loopback and the local network only. No relays, no address lookup.
|
||||
/// Loopback and the local network only. Publishes nothing.
|
||||
Local,
|
||||
/// Public address lookup, but no relays.
|
||||
Direct,
|
||||
/// iroh's defaults: address lookup plus the public n0 relays.
|
||||
/// iroh's defaults: public address lookup plus the public n0 relays.
|
||||
N0,
|
||||
}
|
||||
|
||||
@@ -378,8 +382,16 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
|
||||
let agent = Agent::spawn(config).await?;
|
||||
// From here on every exit goes through `agent.shutdown()`, so the endpoint
|
||||
// is never dropped without being closed.
|
||||
let mut events = agent.subscribe();
|
||||
let network = agent.join_network(&name, &secret).await?;
|
||||
let network = match agent.join_network(&name, &secret).await {
|
||||
Ok(network) => network,
|
||||
Err(err) => {
|
||||
agent.shutdown().await;
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
println!("tsunagi is up");
|
||||
println!(" endpoint id {}", agent.endpoint_id());
|
||||
@@ -401,11 +413,8 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
signal = tokio::signal::ctrl_c() => {
|
||||
if let Err(err) = signal {
|
||||
eprintln!("cannot listen for Ctrl-C: {err}");
|
||||
}
|
||||
println!("\nstopping...");
|
||||
reason = stop_signal() => {
|
||||
println!("\nstopping ({reason})...");
|
||||
break;
|
||||
}
|
||||
event = events.recv() => match event {
|
||||
@@ -431,6 +440,34 @@ async fn up(args: UpArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolves when the process is asked to stop.
|
||||
///
|
||||
/// Both Ctrl-C and `SIGTERM` are handled, so a service manager stopping the
|
||||
/// agent gets the same clean shutdown an interactive user does.
|
||||
async fn stop_signal() -> &'static str {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
let mut terminate = match signal(SignalKind::terminate()) {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => {
|
||||
eprintln!("cannot listen for SIGTERM: {err}");
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
return "interrupted";
|
||||
}
|
||||
};
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => "interrupted",
|
||||
_ = terminate.recv() => "terminated",
|
||||
}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
"interrupted"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tun-device")]
|
||||
fn system_tun_factory() -> Result<Arc<dyn TunFactory>, Box<dyn std::error::Error>> {
|
||||
use tsunagi::dataplane::wireguard::SystemTunFactory;
|
||||
|
||||
Reference in New Issue
Block a user