Something went wrong. Try again.
wip/draft: git transport over iroh
Something went wrong. Try again.
4.0 kB · 126 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127//! Iroh server that accepts git push/fetch connections.//!//! Runs as a background process (or integrated into lichen-server).//! When a peer connects, reads the operation header, spawns the//! appropriate git command, and pipes its stdin/stdout over the//! QUIC stream.
use std::path::PathBuf;use std::sync::Arc;
use anyhow::{Context, Result};use iroh::endpoint::Endpoint;use iroh::protocol::{AcceptError, ProtocolHandler, Router};use tokio::io::{AsyncReadExt, AsyncWriteExt};use tokio::process::Command;
use crate::protocol::{GIT_IROH_ALPN, GitOp};
/// Git pack protocol handler for iroh.////// Accepts incoming connections, reads the operation byte, spawns/// `git-receive-pack` or `git-upload-pack`, and pipes the QUIC/// stream to the git process's stdin/stdout.#[derive(Debug, Clone)]pub struct GitIrohHandler { /// the git repo directory to serve repo_dir: PathBuf,}
impl GitIrohHandler { pub fn new(repo_dir: PathBuf) -> Self { Self { repo_dir } }}
impl ProtocolHandler for GitIrohHandler { async fn accept(&self, conn: iroh::endpoint::Connection) -> Result<(), AcceptError> { let remote = conn.remote_id(); tracing::info!("git-iroh: connection from {}", remote);
let (mut send, mut recv) = conn.accept_bi().await?;
// read operation byte let op_byte = recv .read_u8() .await .map_err(|e| std::io::Error::other(format!("read op: {}", e)))?;
let op = GitOp::from_byte(op_byte).ok_or_else(|| { std::io::Error::other(format!("unknown git op: {}", op_byte)) })?;
tracing::info!("git-iroh: {:?} from {}", op, remote);
// spawn the git command let mut child = Command::new(op.server_command()) .arg(&self.repo_dir) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::inherit()) .spawn() .map_err(|e| { std::io::Error::other(format!("spawn {}: {}", op.server_command(), e)) })?;
let mut child_stdin = child.stdin.take().unwrap(); let mut child_stdout = child.stdout.take().unwrap();
// pipe QUIC recv → git stdin, git stdout → QUIC send let (r1, r2) = tokio::join!( tokio::io::copy(&mut recv, &mut child_stdin), tokio::io::copy(&mut child_stdout, &mut send), );
// close stdin so git process can finish drop(child_stdin);
if let Err(e) = r1 { tracing::warn!("git-iroh: recv→stdin error: {}", e); } if let Err(e) = r2 { tracing::warn!("git-iroh: stdout→send error: {}", e); }
let status = child.wait().await.map_err(|e| { std::io::Error::other(format!("wait for git: {}", e)) })?;
if !status.success() { tracing::warn!("git-iroh: {} exited with {}", op.server_command(), status); }
send.finish().map_err(|e| { std::io::Error::other(format!("finish send: {}", e)) })?;
tracing::info!("git-iroh: {:?} complete for {}", op, remote); Ok(()) }}
/// Starts the git-iroh server on an iroh endpoint.////// Returns the Router (keeps the server alive) and the endpoint/// for address info.pub async fn start_server( secret_key: iroh::SecretKey, repo_dir: PathBuf,) -> Result<(Router, Endpoint)> { let ep = Endpoint::builder(iroh::endpoint::presets::N0) .secret_key(secret_key) .alpns(vec![GIT_IROH_ALPN.to_vec()]) .bind() .await .context("failed to bind iroh endpoint")?;
let node_id = ep.id(); tracing::info!("git-iroh server started, node_id={}", node_id);
let handler = GitIrohHandler::new(repo_dir); let router = Router::builder(ep.clone()) .accept(GIT_IROH_ALPN, handler) .spawn();
Ok((router, ep))}