1use std::path::PathBuf;23use anyhow::{Context as _, Result};4use bifrostlink::declarative::endpoints;5use bifrostlink::Config;6use camino::Utf8PathBuf;7use nix_eval::eval_store;8use remowt_client::Remowt;9use remowt_endpoints::nix_daemon::NixDaemonClient;10use remowt_link_shared::iroh_tunnel::TunnelAddr;11use serde::{Deserialize, Serialize};12use tokio::net::UnixListener;13use tokio::task::spawn_blocking;14use tracing::error;1516pub struct Nix;17pub use nix_eval::{init_libraries, init_tokio_for_nix};1819#[derive(Serialize, Deserialize, Debug, thiserror::Error)]20pub enum NixError {21 #[error("nix daemon unavailable: {0}")]22 DaemonUnavailable(String),23 #[error("tunnel socket unavailable: {0}")]24 Tunnel(String),25 #[error("profile switch failed: {0}")]26 Profile(String),27 #[error("signing failed: {0}")]28 Sign(String),29 #[error("listing generations failed: {0}")]30 ListGenerations(String),31}3233#[endpoints(ns = 91)]34impl Nix {35 #[endpoints(id = 3)]36 async fn switch_profile(37 &self,38 profile: String,39 store_path: Utf8PathBuf,40 ) -> Result<(), NixError> {41 let store = eval_store();42 spawn_blocking(move || store.switch_profile(&profile, &store_path))43 .await44 .expect("switch_profile panicked")45 .map_err(|e| NixError::Profile(e.to_string()))46 }4748 #[endpoints(id = 4)]49 async fn sign_closure(50 &self,51 store_path: Utf8PathBuf,52 key_file: Utf8PathBuf,53 ) -> Result<(), NixError> {54 spawn_blocking(move || {55 let store = eval_store();56 store.sign_closure(&store_path, &key_file)57 })58 .await59 .expect("store signing panicked")60 .map_err(|e| NixError::Sign(e.to_string()))61 }6263 #[endpoints(id = 5)]64 async fn list_generations(65 &self,66 profile: String,67 ) -> Result<Vec<nix_eval::ProfileGeneration>, NixError> {68 spawn_blocking(move || {69 nix_eval::list_generations(&format!("/nix/var/nix/profiles/{profile}"))70 })71 .await72 .expect("generation listing panicked")73 .map_err(|e| NixError::ListGenerations(e.to_string()))74 }75}7677pub async fn nix_store_socket(conn: Remowt, store: &str) -> Result<PathBuf> {78 let store = store.to_owned();79 let path = std::env::temp_dir().join(format!("fleet-nix-{}.sock", uuid::Uuid::new_v4()));80 let _ = std::fs::remove_file(&path);81 let listener = UnixListener::bind(&path)?;82 tokio::spawn(async move {83 if let Err(e) = serve(conn, listener, store).await {84 error!("nix daemon proxy failed: {e}");85 }86 });87 Ok(path)88}8990async fn serve(conn: Remowt, listener: UnixListener, store: String) -> Result<()> {91 let nix = conn.endpoints::<NixDaemonClient<_>>();92 loop {93 let (mut local, _) = listener.accept().await?;9495 let (rx, remote_sock) = match conn.bind_runtime_unix("nix-daemon").await {96 Ok(rx) => rx,97 Err(e) => {98 error!("streamlocal_forward failed: {e}");99 continue;100 }101 };102 match nix103 .serve_store(store.clone(), TunnelAddr::Unix(remote_sock))104 .await105 {106 Ok(Ok(())) => {}107 Ok(Err(e)) => {108 error!("nix bridge: {e}");109 continue;110 }111 Err(e) => {112 error!("nix bridge rpc failed: {e}");113 continue;114 }115 }116117 let mut channel = rx118 .accept()119 .await120 .context("failed to accept remote nix connection")?;121 tokio::spawn(async move {122 if let Err(e) = tokio::io::copy_bidirectional(&mut local, &mut channel).await {123 tracing::debug!("nix tunnel ended: {e}");124 }125 });126 }127}