Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion crates/rds-agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,29 @@ async fn main() -> anyhow::Result<()> {
if agent.policy.allow.is_empty() {
eprintln!("warning: empty --allow list; every peer will be rejected");
}
agent.run().await
tokio::select! {
res = agent.run() => res?,
_ = shutdown_signal() => {}
}
// Close the endpoint so peers get CONNECTION_CLOSE instead of an
// abrupt socket death (and iroh does not log an ungraceful drop).
agent.endpoint.close().await;
Ok(())
}

/// SIGINT on every platform, SIGTERM on unix (systemd stop).
async fn shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
let mut term = signal(SignalKind::terminate()).expect("SIGTERM handler");
tokio::select! {
_ = tokio::signal::ctrl_c() => {}
_ = term.recv() => {}
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
}
3 changes: 3 additions & 0 deletions crates/rds-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ async fn main() -> anyhow::Result<()> {
);
}
}
// Dropping the endpoint without close() makes iroh log an
// "ungraceful abort" error on every command exit.
endpoint.close().await;
Ok(())
}

Expand Down
22 changes: 21 additions & 1 deletion crates/rds-relay/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@ async fn main() -> anyhow::Result<()> {
"relay listening on http://{}",
server.http_addr().expect("relay config enabled")
);
tokio::signal::ctrl_c().await?;
shutdown_signal().await;
// Graceful stop: close listener + client websockets instead of
// letting attached endpoints hit a silent RST.
let _ = server.shutdown().await;
Ok(())
}

/// SIGINT on every platform, SIGTERM on unix (systemd stop).
async fn shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
let mut term = signal(SignalKind::terminate()).expect("SIGTERM handler");
tokio::select! {
_ = tokio::signal::ctrl_c() => {}
_ = term.recv() => {}
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
}
22 changes: 21 additions & 1 deletion crates/rds-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ async fn main() -> anyhow::Result<()> {
let relay = rds_relay::serve(cli.relay_addr, allow).await?;
info!(addr = %relay.http_addr().expect("relay config enabled"), "relay listening");

tokio::signal::ctrl_c().await?;
shutdown_signal().await;
// Graceful stop: close listener + client websockets instead of
// letting attached endpoints hit a silent RST.
let _ = relay.shutdown().await;
Ok(())
}

/// SIGINT on every platform, SIGTERM on unix (systemd stop).
async fn shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
let mut term = signal(SignalKind::terminate()).expect("SIGTERM handler");
tokio::select! {
_ = tokio::signal::ctrl_c() => {}
_ = term.recv() => {}
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
}
2 changes: 1 addition & 1 deletion deploy/systemd/rds-agent.service
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[Unit]
Description=rds-agent: remote-device-sync service host (ssh/tcp forward, desktop, sync)
Documentation=https://github.com/rldyourmnd/remote-device-sync
Documentation=https://github.com/NDDev-OpenNetwork/remote-device-sync
After=network-online.target
Wants=network-online.target

Expand Down
2 changes: 1 addition & 1 deletion deploy/systemd/rds-server.service
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[Unit]
Description=rds-server: relay + discovery directory for remote-device-sync
Documentation=https://github.com/rldyourmnd/remote-device-sync
Documentation=https://github.com/NDDev-OpenNetwork/remote-device-sync
After=network-online.target
Wants=network-online.target

Expand Down
23 changes: 23 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,29 @@ table inet rds {
accepts direct paths opportunistically (hole-punched or via the
endpoint's discovered addresses).

**TLS on the relay.** `rds-server` serves the iroh relay protocol
(WebSocket over HTTP) on 3340 in plaintext: relayed payloads are
end-to-end-encrypted QUIC the relay cannot read, and relay admission is
keyed by `EndpointId` signature challenge, so a network MITM can only
disrupt, not decrypt. For defence-in-depth on a public IP, terminate TLS
in front of 3340 with any TCP-level TLS terminator (nginx `stream`,
haproxy) and give endpoints `--relay https://<host>:<tls-port>`; the
relay protocol rides WebSocket inside TLS unchanged. The embedded
iroh-relay also supports ACME natively — wiring `TlsConfig` through
`rds_relay::serve` is future work, not required for launch.

**No tunnel/VPN dependency.** The design assumes only *outbound*
connectivity from endpoints: tcp/3340 (relay) + tcp/3341 (directory) +
udp for direct paths. A Cloudflare Tunnel could front the *directory*
(plain HTTP — works) and probably the relay (WebSocket — unverified, and
Cloudflare terminates long-lived proxied connections at the edge, so
attached endpoints would drop whenever `cloudflared` reconnects), but it
cannot carry the endpoints' QUIC/UDP data path at all — public hostnames
do not proxy UDP, and private-network UDP requires every device enrolled
in WARP/Zero Trust. WARP itself is a client VPN solving a problem the
relay already solves without a per-device client dependency. If the
services host has no public IP, prefer any small VPS over a tunnel.

## Sandboxing

Both units set `NoNewPrivileges`, `ProtectSystem=strict`,
Expand Down
Loading