Skip to content
edgesync Artifact [31b45343a9]
Login

Artifact [31b45343a9]

Artifact 31b45343a90a376064e6dec0f56e7fdb545d0051bc864283e417864e029d060f:


mod acceptor;
mod endpoint;
mod server;
mod tunnel;

use clap::Parser;
use std::path::PathBuf;
use tokio::sync::oneshot;

pub const XFER_ALPN: &[u8] = b"/edgesync/xfer/1";
pub const NATS_ALPN: &[u8] = b"/edgesync/nats-leaf/1";

#[derive(Parser)]
#[command(name = "iroh-sidecar", about = "Iroh P2P proxy for EdgeSync")]
struct Args {
    /// Unix socket path for the HTTP API
    #[arg(long)]
    socket: PathBuf,

    /// Path to persist the Ed25519 keypair
    #[arg(long)]
    key_path: PathBuf,

    /// URL to forward incoming peer requests to (Go agent's HTTP handler)
    #[arg(long)]
    callback: String,

    /// ALPN protocol identifier
    #[arg(long, default_value = "/edgesync/xfer/1")]
    alpn: String,

    /// Local NATS server address for tunnel connections (e.g. 127.0.0.1:4222)
    #[arg(long)]
    nats_addr: Option<String>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt::init();
    let args = Args::parse();
    tracing::info!(socket = %args.socket.display(), "iroh-sidecar starting");

    let alpns: Vec<Vec<u8>> = if args.nats_addr.is_some() {
        vec![args.alpn.as_bytes().to_vec(), NATS_ALPN.to_vec()]
    } else {
        vec![args.alpn.as_bytes().to_vec()]
    };

    let (ep, endpoint_id) = endpoint::create_endpoint(
        &args.key_path,
        &alpns,
    ).await?;

    tracing::info!(%endpoint_id, "iroh endpoint ready");

    // Channel used by POST /shutdown to stop the HTTP server.
    let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();

    let state = server::AppState::new(
        ep.clone(),
        endpoint_id,
        args.alpn.as_bytes().to_vec(),
        shutdown_tx,
    );

    // Accept loop for incoming P2P connections.
    let accept_ep = ep.clone();
    let callback_url = args.callback.clone();
    let accept_nats_addr = args.nats_addr.clone();
    let accept_handle = tokio::spawn(async move {
        acceptor::run_accept_loop(accept_ep, callback_url, accept_nats_addr).await;
    });

    // Task 3: HTTP server on the Unix socket.
    let socket_path = args.socket.clone();
    let http_handle = tokio::spawn(async move {
        if let Err(e) = server::run_server(&socket_path, state, shutdown_rx).await {
            tracing::error!("HTTP server error: {e:#}");
        }
    });

    // Wait for either task to finish.
    // The HTTP server exits when it receives /shutdown; the accept loop exits
    // when the endpoint is closed.
    tokio::select! {
        _ = http_handle => {
            tracing::info!("HTTP server exited");
        }
        _ = accept_handle => {
            tracing::info!("accept loop exited unexpectedly");
        }
        _ = tokio::signal::ctrl_c() => {
            tracing::info!("received ctrl-c");
        }
    }

    ep.close().await;
    tracing::info!("iroh-sidecar stopped");
    Ok(())
}