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 serde::{Deserialize, Serialize};11use tokio::net::UnixListener;12use tokio::task::spawn_blocking;13use tracing::error;1415pub struct Nix;16pub use nix_eval::{init_libraries, init_tokio_for_nix};1718#[derive(Serialize, Deserialize, Debug, thiserror::Error)]19pub enum NixError {20 #[error("nix daemon unavailable: {0}")]21 DaemonUnavailable(String),22 #[error("tunnel socket unavailable: {0}")]23 Tunnel(String),24 #[error("profile switch failed: {0}")]25 Profile(String),26 #[error("signing failed: {0}")]27 Sign(String),28 #[error("listing generations failed: {0}")]29 ListGenerations(String),30}3132#[endpoints(ns = 91)]33impl Nix {34 #[endpoints(id = 3)]35 async fn switch_profile(36 &self,37 profile: String,38 store_path: Utf8PathBuf,39 ) -> Result<(), NixError> {40 let store = eval_store();41 spawn_blocking(move || store.switch_profile(&profile, &store_path))42 .await43 .expect("switch_profile panicked")44 .map_err(|e| NixError::Profile(e.to_string()))45 }4647 #[endpoints(id = 4)]48 async fn sign_closure(49 &self,50 store_path: Utf8PathBuf,51 key_file: Utf8PathBuf,52 ) -> Result<(), NixError> {53 spawn_blocking(move || {54 let store = eval_store();55 store.sign_closure(&store_path, &key_file)56 })57 .await58 .expect("store signing panicked")59 .map_err(|e| NixError::Sign(e.to_string()))60 }6162 #[endpoints(id = 5)]63 async fn list_generations(64 &self,65 profile: String,66 ) -> Result<Vec<nix_eval::ProfileGeneration>, NixError> {67 spawn_blocking(move || {68 nix_eval::list_generations(&format!("/nix/var/nix/profiles/{profile}"))69 })70 .await71 .expect("generation listing panicked")72 .map_err(|e| NixError::ListGenerations(e.to_string()))73 }74}7576pub async fn nix_store_socket(conn: Remowt, store: &str) -> Result<PathBuf> {77 let store = store.to_owned();78 let path = std::env::temp_dir().join(format!("fleet-nix-{}.sock", uuid::Uuid::new_v4()));79 let _ = std::fs::remove_file(&path);80 let listener = UnixListener::bind(&path)?;81 tokio::spawn(async move {82 if let Err(e) = serve(conn, listener, store).await {83 error!("nix daemon proxy failed: {e}");84 }85 });86 Ok(path)87}8889async fn serve(conn: Remowt, listener: UnixListener, store: String) -> Result<()> {90 let nix = conn.endpoints::<NixDaemonClient<_>>();91 loop {92 let (mut local, _) = listener.accept().await?;9394 let (rx, remote_sock) = match conn.bind_runtime_unix("nix-daemon").await {95 Ok(rx) => rx,96 Err(e) => {97 error!("streamlocal_forward failed: {e}");98 continue;99 }100 };101 let sock_str = remote_sock.as_str().to_owned();102 match nix.serve_store(store.clone(), sock_str).await {103 Ok(Ok(())) => {}104 Ok(Err(e)) => {105 error!("nix bridge: {e}");106 continue;107 }108 Err(e) => {109 error!("nix bridge rpc failed: {e}");110 continue;111 }112 }113114 let mut channel = rx115 .accept()116 .await117 .context("failed to accept remote nix connection")?;118 tokio::spawn(async move {119 if let Err(e) = tokio::io::copy_bidirectional(&mut local, &mut channel).await {120 tracing::debug!("nix tunnel ended: {e}");121 }122 });123 }124}