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 #[error("substitution failed: {0}")]32 Substitute(String),33}3435#[endpoints(ns = 91)]36impl Nix {37 #[endpoints(id = 3)]38 async fn switch_profile(39 &self,40 profile: String,41 store_path: Utf8PathBuf,42 ) -> Result<(), NixError> {43 let store = eval_store();44 spawn_blocking(move || store.switch_profile(&profile, &store_path))45 .await46 .expect("switch_profile panicked")47 .map_err(|e| NixError::Profile(e.to_string()))48 }4950 #[endpoints(id = 4)]51 async fn sign_closure(52 &self,53 store_path: Utf8PathBuf,54 key_file: Utf8PathBuf,55 ) -> Result<(), NixError> {56 spawn_blocking(move || {57 let store = eval_store();58 store.sign_closure(&store_path, &key_file)59 })60 .await61 .expect("store signing panicked")62 .map_err(|e| NixError::Sign(e.to_string()))63 }6465 66 67 68 #[endpoints(id = 6)]69 async fn substitute(&self, paths: Vec<Utf8PathBuf>) -> Result<Vec<Utf8PathBuf>, NixError> {70 spawn_blocking(move || {71 let store = eval_store();72 store.substitute_paths(&paths)73 })74 .await75 .expect("substitution panicked")76 .map_err(|e| NixError::Substitute(e.to_string()))77 }7879 #[endpoints(id = 5)]80 async fn list_generations(81 &self,82 profile: String,83 ) -> Result<Vec<nix_eval::ProfileGeneration>, NixError> {84 spawn_blocking(move || {85 nix_eval::list_generations(&format!("/nix/var/nix/profiles/{profile}"))86 })87 .await88 .expect("generation listing panicked")89 .map_err(|e| NixError::ListGenerations(e.to_string()))90 }91}9293pub async fn nix_store_socket(conn: Remowt, store: &str) -> Result<PathBuf> {94 let store = store.to_owned();95 let path = std::env::temp_dir().join(format!("fleet-nix-{}.sock", uuid::Uuid::new_v4()));96 let _ = std::fs::remove_file(&path);97 let listener = UnixListener::bind(&path)?;98 tokio::spawn(async move {99 if let Err(e) = serve(conn, listener, store).await {100 error!("nix daemon proxy failed: {e}");101 }102 });103 Ok(path)104}105106async fn serve(conn: Remowt, listener: UnixListener, store: String) -> Result<()> {107 let nix = conn.endpoints::<NixDaemonClient<_>>();108 loop {109 let (mut local, _) = listener.accept().await?;110111 let (rx, remote_sock) = match conn.bind_runtime_unix("nix-daemon").await {112 Ok(rx) => rx,113 Err(e) => {114 error!("streamlocal_forward failed: {e}");115 continue;116 }117 };118 match nix119 .serve_store(store.clone(), TunnelAddr::Unix(remote_sock))120 .await121 {122 Ok(Ok(())) => {}123 Ok(Err(e)) => {124 error!("nix bridge: {e}");125 continue;126 }127 Err(e) => {128 error!("nix bridge rpc failed: {e}");129 continue;130 }131 }132133 let mut channel = rx134 .accept()135 .await136 .context("failed to accept remote nix connection")?;137 tokio::spawn(async move {138 if let Err(e) = tokio::io::copy_bidirectional(&mut local, &mut channel).await {139 tracing::debug!("nix tunnel ended: {e}");140 }141 });142 }143}