1use std::path::PathBuf;23use anyhow::{Context as _, Result};4use bifrostlink::Config;5use bifrostlink::declarative::endpoints;6use camino::Utf8PathBuf;7use remowt_client::Remowt;8use remowt_endpoints::nix_daemon::NixDaemonClient;9use serde::{Deserialize, Serialize};10use tokio::net::UnixListener;11use tracing::error;1213pub struct Nix;14pub use nix_eval::{init_libraries, init_tokio_for_nix};1516#[derive(Serialize, Deserialize, Debug, thiserror::Error)]17pub enum NixError {18 #[error("nix daemon unavailable: {0}")]19 DaemonUnavailable(String),20 #[error("tunnel socket unavailable: {0}")]21 Tunnel(String),22 #[error("profile switch failed: {0}")]23 Profile(String),24 #[error("signing failed: {0}")]25 Sign(String),26 #[error("listing generations failed: {0}")]27 ListGenerations(String),28}2930#[endpoints(ns = 91)]31impl Nix {32 #[endpoints(id = 3)]33 async fn switch_profile(34 &self,35 profile: String,36 store_path: Utf8PathBuf,37 ) -> Result<(), NixError> {38 tokio::task::spawn_blocking(move || nix_eval::switch_profile(&profile, &store_path))39 .await40 .map_err(|e| NixError::Profile(e.to_string()))?41 .map_err(|e| NixError::Profile(e.to_string()))42 }4344 #[endpoints(id = 4)]45 async fn sign_closure(46 &self,47 store_path: Utf8PathBuf,48 key_file: Utf8PathBuf,49 ) -> Result<(), NixError> {50 tokio::task::spawn_blocking(move || {51 nix_eval::sign_closure(store_path.as_str(), key_file.as_str())52 })53 .await54 .map_err(|e| NixError::Sign(e.to_string()))?55 .map_err(|e| NixError::Sign(e.to_string()))56 }5758 #[endpoints(id = 5)]59 async fn list_generations(60 &self,61 profile: String,62 ) -> Result<Vec<nix_eval::ProfileGeneration>, NixError> {63 tokio::task::spawn_blocking(move || {64 nix_eval::list_generations(&format!("/nix/var/nix/profiles/{profile}"))65 })66 .await67 .map_err(|e| NixError::ListGenerations(e.to_string()))?68 .map_err(|e| NixError::ListGenerations(e.to_string()))69 }70}7172pub async fn nix_store_socket(conn: Remowt, store: &str) -> Result<PathBuf> {73 let store = store.to_owned();74 let path = std::env::temp_dir().join(format!("fleet-nix-{}.sock", uuid::Uuid::new_v4()));75 let _ = std::fs::remove_file(&path);76 let listener = UnixListener::bind(&path)?;77 tokio::spawn(async move {78 if let Err(e) = serve(conn, listener, store).await {79 error!("nix daemon proxy failed: {e}");80 }81 });82 Ok(path)83}8485async fn serve(conn: Remowt, listener: UnixListener, store: String) -> Result<()> {86 let nix = conn.endpoints::<NixDaemonClient<_>>();87 loop {88 let (mut local, _) = listener.accept().await?;8990 let (rx, remote_sock) = match conn.bind_runtime_unix("nix-daemon").await {91 Ok(rx) => rx,92 Err(e) => {93 error!("streamlocal_forward failed: {e}");94 continue;95 }96 };97 let sock_str = remote_sock.as_str().to_owned();98 match nix.serve_store(store.clone(), sock_str).await {99 Ok(Ok(())) => {}100 Ok(Err(e)) => {101 error!("nix bridge: {e}");102 continue;103 }104 Err(e) => {105 error!("nix bridge rpc failed: {e}");106 continue;107 }108 }109110 let mut channel = rx111 .accept()112 .await113 .context("failed to accept remote nix connection")?;114 tokio::spawn(async move {115 if let Err(e) = tokio::io::copy_bidirectional(&mut local, &mut channel).await {116 tracing::debug!("nix tunnel ended: {e}");117 }118 });119 }120}