7 files changed
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -24,8 +24,7 @@
#[cfg(feature = "indicatif")]
use indicatif::{ProgressState, ProgressStyle};
use nix_eval::{
- add_file_to_store, gc_register_my_thread, gc_unregister_my_thread, init_libraries,
- init_tokio_for_nix,
+ eval_store, gc_register_my_thread, gc_unregister_my_thread, init_libraries, init_tokio_for_nix,
};
use opentelemetry::trace::TracerProvider;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
@@ -33,6 +32,7 @@
OtlpBaseSettings, OtlpLogsSettings, OtlpTracesSettings, ResolvedOtlpSettings,
};
use opentelemetry_sdk::{logs::SdkLoggerProvider, trace::SdkTracerProvider};
+use tokio::task::spawn_blocking;
use tracing::{Instrument, error, info, info_span};
#[cfg(feature = "indicatif")]
use tracing_indicatif::IndicatifLayer;
@@ -59,7 +59,8 @@
Utf8PathBuf::try_from(entry.path()).context("prefetch path should be utf8")?;
let span = info_span!("prefetching", name = %name);
tasks.push(async move {
- let added = tokio::task::spawn_blocking(move || add_file_to_store(&name, &path))
+ let store = eval_store();
+ let added = spawn_blocking(move || store.add_file(&name, &path))
.instrument(span.clone())
.await??;
let _g = span.enter();
@@ -121,9 +122,7 @@
Opts::Prefetch(p) => p.run(config).await?,
Opts::Tf(t) => t.run(config).await?,
// TODO: actually parse commands before starting the async runtime
- Opts::Complete(c) => {
- tokio::task::spawn_blocking(move || c.run(RootOpts::command())).await?
- }
+ Opts::Complete(c) => spawn_blocking(move || c.run(RootOpts::command())).await?,
};
Ok(())
}
1use std::{2 collections::{BTreeMap, BTreeSet, HashSet},3 future::Future,4 io::Write,5 ops::Deref,6 path::PathBuf,7 pin::Pin,8 str::FromStr,9 sync::{Arc, OnceLock},10};1112use anyhow::{Context, Result, anyhow, bail, ensure};13use camino::{Utf8Path, Utf8PathBuf};14use chrono::{DateTime, Utc};15use fleet_shared::SecretData;16use nix_eval::{Store, Value, nix_go, nix_go_json, util::assert_warn};17use remowt_client::{AgentBundle, Remowt};18use remowt_endpoints::fs::FsClient;19use remowt_link_shared::Address;20use remowt_ui_prompt::auto::AutoPrompter;21use remowt_ui_prompt::bifrost::PromptEndpoints;22use remowt_ui_prompt::{PrependSourcePrompter, Source};23use tabled::Tabled;24use tempfile::NamedTempFile;25use time::UtcDateTime;26use tokio::task::spawn_blocking;27use tracing::{info, warn};2829use crate::fleetdata::{30 FleetData, FleetSecretData, FleetSecretDistribution, FleetSecretPart, SecretOwner,31};3233pub struct FleetConfigInternals {34 pub prefer_identities: BTreeSet<SecretOwner>,35 pub now: DateTime<Utc>,3637 38 pub directory: PathBuf,39 40 pub local_system: String,41 pub data: Arc<FleetData>,42 43 pub config_field: Value,44 45 pub flake_outputs: Value,46 47 pub localhost: String,4849 50 pub default_pkgs: Value,51 52 pub nixpkgs: Value,5354 pub local_host: OnceLock<Arc<ConfigHost>>,55}565758#[derive(Clone)]59pub struct Config(pub Arc<FleetConfigInternals>);6061impl Deref for Config {62 type Target = FleetConfigInternals;6364 fn deref(&self) -> &Self::Target {65 &self.066 }67}6869#[derive(Clone, PartialEq, Copy, Debug)]70pub enum DeployKind {71 72 UpgradeToFleet,73 74 Fleet,75 76 77 NixosInstall,78 79 80 81 NixosLustrate,82}8384impl FromStr for DeployKind {85 type Err = anyhow::Error;86 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {87 match s {88 "upgrade-to-fleet" => Ok(Self::UpgradeToFleet),89 "fleet" => Ok(Self::Fleet),90 "nixos-install" => Ok(Self::NixosInstall),91 "nixos-lustrate" => Ok(Self::NixosLustrate),92 v => bail!(93 "unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\", \"nixos-install\", \"nixos-lustrate\""94 ),95 }96 }97}98pub struct ConfigHost {99 config: Config,100 pub name: String,101 groups: OnceLock<Vec<String>>,102103 104 deploy_kind: OnceLock<DeployKind>,105 session_destination: OnceLock<String>,106 legacy_ssh_store: OnceLock<bool>,107108 pub host_config: Option<Value>,109 pub nixos_config: OnceLock<Value>,110 pub nixos_unchecked_config: OnceLock<Value>,111 pub pkgs_override: Option<Value>,112113 114 pub local: bool,115 pub remowt: OnceLock<Remowt>,116 nix_store: OnceLock<Arc<Store>>,117 nix_plugin: tokio::sync::OnceCell<()>,118}119120const NIX_PLUGIN_ID: u16 = 2;121122fn agents_dir() -> Result<PathBuf> {123 std::env::var_os("REMOWT_AGENTS_DIR")124 .map(PathBuf::from)125 .or_else(|| option_env!("REMOWT_AGENTS_DIR").map(PathBuf::from))126 .ok_or_else(|| {127 anyhow!("no remowt-agents bundle; set REMOWT_AGENTS_DIR to a remowt-agents output")128 })129}130131fn agent_bundle() -> Result<AgentBundle> {132 AgentBundle::from_dir(agents_dir()?)133}134135#[derive(Debug, Clone, Copy)]136pub enum GenerationStorage {137 Deployer,138 Machine,139 Pusher,140}141impl GenerationStorage {142 fn prefix(&self) -> &'static str {143 match self {144 GenerationStorage::Deployer => "deployer.",145 GenerationStorage::Machine => "",146 GenerationStorage::Pusher => "pusher.",147 }148 }149}150151#[derive(Tabled, Debug)]152pub struct Generation {153 #[tabled(rename = "ID", format("{}", self.rollback_id()))]154 pub id: u32,155 #[tabled(rename = "Current")]156 pub current: bool,157 #[tabled(rename = "Created at")]158 pub datetime: UtcDateTime,159 #[tabled(format = "{:?}")]160 pub store_path: Utf8PathBuf,161 #[tabled(skip)]162 pub location: GenerationStorage,163}164impl Generation {165 pub fn rollback_id(&self) -> String {166 format!("{}{}", self.location.prefix(), self.id)167 }168}169170impl ConfigHost {171 pub async fn list_generations(&self, profile: &str) -> Result<Vec<Generation>> {172 let plugin_id = self.ensure_nix_plugin().await?;173 let nix = self174 .remowt()175 .await?176 .plugin_endpoints::<remowt_fleet::NixClient<_>>(plugin_id);177 let raw = nix178 .list_generations(profile.to_owned())179 .await180 .map_err(|e| anyhow!("{e:?}"))?181 .map_err(|e| anyhow!("{e}"))?;182 raw.into_iter()183 .map(|g| {184 let id: u32 =185 g.id.try_into()186 .with_context(|| format!("generation id {} doesn't fit in u32", g.id))?;187 let datetime = UtcDateTime::from_unix_timestamp(g.creation_time_unix)188 .with_context(|| {189 format!("invalid generation timestamp {}", g.creation_time_unix)190 })?;191 Ok(Generation {192 id,193 current: g.current,194 datetime,195 store_path: g.store_path,196 location: GenerationStorage::Machine,197 })198 })199 .collect()200 }201202 pub fn set_session_destination(&self, dest: String) {203 self.session_destination204 .set(dest)205 .expect("session destination is already set")206 }207 pub fn set_deploy_kind(&self, kind: DeployKind) {208 self.deploy_kind209 .set(kind)210 .expect("deploy kind is already set");211 }212 pub fn set_legacy_ssh_store(&self, legacy: bool) {213 self.legacy_ssh_store214 .set(legacy)215 .expect("legacy ssh store is already set")216 }217 pub async fn deploy_kind(&self) -> Result<DeployKind> {218 if let Some(kind) = self.deploy_kind.get() {219 return Ok(*kind);220 }221 let remowt = self.remowt().await?;222 let fs = remowt.endpoints::<FsClient<_>>();223 let is_fleet_managed = match fs.file_exists(Utf8PathBuf::from("/etc/FLEET_HOST")).await {224 Ok(v) => v,225 Err(e) => {226 bail!("failed to query remote system kind: {e}");227 }228 };229 if !is_fleet_managed {230 bail!(231 "{}",232 indoc::indoc! {"233 host is not marked as managed by fleet234 if you're not trying to lustrate/install system from scratch,235 you should either236 1. manually create /etc/FLEET_HOST file on the target host,237 2. use ?deploy_kind=fleet host argument if you're upgrading from older version of fleet238 3. use ?deploy_kind=upgrade_to_fleet if you're upgrading from plain nixos to fleet-managed nixos239 for installation use ?deploy_kind=nixos_install / ?deploy_kind=nixos_lustrate 240 "}241 );242 }243 244 let _ = self.deploy_kind.set(DeployKind::Fleet);245 Ok(*self.deploy_kind.get().expect("deploy kind is just set"))246 }247 async fn connection(&self) -> Result<Remowt> {248 if let Some(conn) = self.remowt.get() {249 return Ok(conn.clone());250 }251 let bundle = agent_bundle()?;252 let conn = if self.local {253 Remowt::connect_local(&bundle)254 .await255 .context("starting local remowt agent")?256 } else {257 let dest = self258 .session_destination259 .get()260 .cloned()261 .unwrap_or_else(|| self.name.clone());262 Remowt::connect(&dest, &bundle)263 .await264 .map_err(|e| anyhow!("remowt error while connecting to {}: {e:#?}", self.name))?265 };266 PromptEndpoints(PrependSourcePrompter {267 prompter: AutoPrompter::new().await,268 source: if self.local {269 vec![]270 } else {271 vec![Source(std::borrow::Cow::Owned(format!(272 "ssh host: {}",273 self.name274 )))]275 },276 description: "".to_owned(),277 })278 .register_endpoints(&mut conn.rpc());279 let _ = self.remowt.set(conn);280 Ok(self.remowt.get().expect("just set").clone())281 }282283 284 pub async fn remowt(&self) -> Result<Remowt> {285 Ok(self.connection().await?)286 }287288 pub fn ensure_nix_plugin(&self) -> Pin<Box<dyn Future<Output = Result<u16>> + Send + '_>> {289 Box::pin(async {290 self.nix_plugin291 .get_or_try_init(|| async {292 let pkgs = self.pkgs()?;293 let name = "remowt-plugin-fleet";294 let plugin = nix_go!(pkgs[{ name }]);295 let built = plugin296 .build("out")297 .context("failed to build the fleet nix plugin")?;298 let copied = self299 .remote_derivation(&built)300 .await301 .context("failed to copy the fleet nix plugin to the host store")?;302 let bin = copied.join("bin/remowt-plugin-fleet");303 self.remowt()304 .await?305 .run0_load_plugin_path(NIX_PLUGIN_ID, bin.as_str())306 .await307 .context("failed to load the fleet nix plugin")?;308 self.remowt()309 .await?310 .rpc()311 .wait_for_connection_to(Address::Plugin(NIX_PLUGIN_ID))312 .await313 .map_err(|e| anyhow!("failed to wait for plugin"))?;314 anyhow::Ok(())315 })316 .await?;317 Ok(NIX_PLUGIN_ID)318 })319 }320321 async fn nix_store(&self) -> Result<Arc<Store>> {322 if let Some(store) = self.nix_store.get() {323 return Ok(store.clone());324 }325 let conn = self.connection().await?;326 let socket = match self.deploy_kind().await? {327 DeployKind::NixosInstall => {328 remowt_fleet::nix_store_socket(conn, "/mnt?require-sigs=false").await?329 }330 _ => remowt_fleet::nix_store_socket(conn, "auto").await?,331 };332 let uri = format!("unix://{}", socket.display());333 let store = Arc::new(Store::open(&uri)?);334 let _ = self.nix_store.set(store);335 Ok(self.nix_store.get().expect("just set").clone())336 }337338 pub async fn decrypt(&self, data: SecretData) -> Result<Vec<u8>> {339 ensure!(data.encrypted, "secret is not encrypted");340 let remowt = self.remowt().await?;341 let mut cmd = remowt.cmd("fleet-install-secrets");342 cmd.arg("decrypt").eqarg("--secret", data.to_string());343 let encoded = cmd344 .sudo()345 .run_string()346 .await347 .context("failed to call remote host for decrypt")?;348 let data: SecretData = encoded.parse().map_err(|e| anyhow!("{e}"))?;349 ensure!(!data.encrypted, "secret came out encrypted");350 Ok(data.data)351 }352 pub async fn reencrypt_distribution(353 &self,354 data: &FleetSecretDistribution,355 targets: BTreeSet<SecretOwner>,356 now: DateTime<Utc>,357 ) -> Result<FleetSecretDistribution> {358 let mut parts = BTreeMap::new();359 for (part_name, part) in &data.secret.parts {360 parts.insert(361 part_name.clone(),362 if part.raw.encrypted {363 FleetSecretPart {364 raw: self.reencrypt(part.raw.clone(), targets.clone()).await?,365 }366 } else {367 part.clone()368 },369 );370 }371 let secret = FleetSecretData {372 created_at: data.secret.created_at,373 expires_at: data.secret.expires_at,374 generation_data: data.secret.generation_data.clone(),375 parts,376 };377 Ok(FleetSecretDistribution::new(targets, secret, now))378 }379 pub async fn reencrypt(380 &self,381 data: SecretData,382 targets: BTreeSet<SecretOwner>,383 ) -> Result<SecretData> {384 let remowt = self.remowt().await?;385 ensure!(data.encrypted, "secret is not encrypted");386 let mut cmd = remowt.cmd("fleet-install-secrets");387 cmd.arg("reencrypt").eqarg("--secret", data.to_string());388 for target in targets {389 let key = self.config.key(&target).await?;390 cmd.eqarg("--targets", key);391 }392 let encoded = cmd393 .sudo()394 .run_string()395 .await396 .context("failed to call remote host for decrypt")?;397 let data: SecretData = encoded.parse().map_err(|e| anyhow!("{e}"))?;398 ensure!(data.encrypted, "secret came out not encrypted");399 Ok(data)400 }401 402 pub async fn remote_derivation(&self, path: impl AsRef<Utf8Path>) -> Result<Utf8PathBuf> {403 let path = path.as_ref().to_owned();404 if self.local {405 406 return Ok(path);407 }408 let sign: Pin<Box<dyn Future<Output = Result<()>> + Send>> = {409 let path = path.clone();410 Box::pin(async move {411 let local = self.config.local_host();412 let plugin_id = local.ensure_nix_plugin().await?;413 let nix = local414 .remowt()415 .await?416 .plugin_endpoints::<remowt_fleet::NixClient<_>>(plugin_id);417 nix.sign_closure(path, Utf8PathBuf::from("/etc/nix/private-key"))418 .await419 .map_err(|e| anyhow!("{e:?}"))?420 .map_err(|e| anyhow!("{e}"))?;421 Ok(())422 })423 };424 if let Err(e) = sign.await {425 warn!("failed to sign store paths: {e}");426 }427 let store = self.nix_store().await?;428 {429 let path = path.clone();430 spawn_blocking(move || nix_eval::copy_closure_to(&store, path.as_ref()))431 .await?432 .context("copying closure to remote store")?;433 }434 Ok(path)435 }436}437438struct HostSecretDefinition(Value);439440impl ConfigHost {441 442 443 pub fn tags(&self) -> Result<Vec<String>> {444 if let Some(v) = self.groups.get() {445 return Ok(v.clone());446 }447 let Some(host_config) = &self.host_config else {448 return Ok(vec![]);449 };450 let tags: Vec<String> = nix_go_json!(host_config.tags);451452 let _ = self.groups.set(tags.clone());453454 Ok(tags)455 }456 pub fn nixos_config(&self) -> Result<Value> {457 if let Some(v) = self.nixos_config.get() {458 return Ok(v.clone());459 }460 let Some(host_config) = &self.host_config else {461 bail!("local host has no nixos_config");462 };463 let nixos_config = nix_go!(host_config.nixos.config);464 assert_warn("nixos config evaluation", &nixos_config)?;465466 let _ = self.nixos_config.set(nixos_config.clone());467468 Ok(nixos_config)469 }470 pub fn nixos_unchecked_config(&self) -> Result<Value> {471 if let Some(v) = self.nixos_unchecked_config.get() {472 return Ok(v.clone());473 }474 let Some(host_config) = &self.host_config else {475 bail!("local host has no nixos_config");476 };477 let nixos_config = nix_go!(host_config.nixos_unchecked.config);478479 let _ = self.nixos_unchecked_config.set(nixos_config.clone());480481 Ok(nixos_config)482 }483484 pub fn list_defined_secrets(&self) -> Result<Vec<String>> {485 let nixos = self.nixos_unchecked_config()?;486 let secrets = nix_go!(nixos.secrets);487 secrets.list_fields()488 }489490 491 pub fn pkgs(&self) -> Result<Value> {492 if let Some(value) = &self.pkgs_override {493 return Ok(value.clone());494 }495 let Some(host_config) = &self.host_config else {496 bail!("local host has no host_config");497 };498 499 Ok(nix_go!(host_config.nixos.options._module.args.value.pkgs))500 }501}502503#[derive(Clone)]504pub struct SharedSecretDefinition(Value);505impl SharedSecretDefinition {506 pub fn expected_owners(&self) -> Result<BTreeSet<SecretOwner>> {507 let secret = &self.0;508 Ok(nix_go_json!(secret.expectedOwners))509 }510 pub fn allow_different(&self) -> Result<bool> {511 let secret = &self.0;512 Ok(nix_go_json!(secret.allowDifferent))513 }514 pub fn regenerate_on_owner_added(&self) -> Result<bool> {515 let secret = &self.0;516 Ok(nix_go_json!(secret.regenerateOnOwnerAdded))517 }518 pub fn regenerate_on_owner_removed(&self) -> Result<bool> {519 let secret = &self.0;520 Ok(nix_go_json!(secret.regenerateOnOwnerRemoved))521 }522 pub fn generator(&self) -> Result<Value> {523 let secret = &self.0;524 Ok(nix_go!(secret.generator))525 }526}527528impl Config {529 pub fn tagged_hostnames(&self, tag: &str) -> Result<Vec<String>> {530 let config = &self.config_field;531 let tagged: Vec<String> = nix_go_json!(config.taggedWith[{ tag }]);532 Ok(tagged)533 }534 pub fn expand_owner_set(&self, owners: Vec<String>) -> Result<BTreeSet<String>> {535 let mut out = BTreeSet::new();536 for owner in owners {537 if let Some(tag) = owner.strip_prefix('@') {538 let hosts = self.tagged_hostnames(tag)?;539 out.extend(hosts);540 } else {541 out.insert(owner);542 }543 }544 Ok(out)545 }546 pub fn local_host(&self) -> Arc<ConfigHost> {547 self.local_host548 .get_or_init(|| {549 Arc::new(ConfigHost {550 config: self.clone(),551 name: "<virtual localhost>".to_owned(),552 host_config: None,553 nixos_config: OnceLock::new(),554 nixos_unchecked_config: OnceLock::new(),555 groups: {556 let cell = OnceLock::new();557 let _ = cell.set(vec![]);558 cell559 },560 pkgs_override: Some(self.default_pkgs.clone()),561562 local: true,563 remowt: OnceLock::new(),564 nix_store: OnceLock::new(),565 nix_plugin: tokio::sync::OnceCell::new(),566 deploy_kind: OnceLock::new(),567 session_destination: OnceLock::new(),568 legacy_ssh_store: OnceLock::new(),569 })570 })571 .clone()572 }573574 pub fn preferred_hosts(575 &self,576 filter: impl Fn(&str) -> bool,577 ) -> Result<impl Iterator<Item = Result<ConfigHost>>> {578 let prefer = self579 .prefer_identities580 .iter()581 .filter_map(|v| v.as_host())582 .collect::<HashSet<_>>();583 let config = &self.config_field;584 let mut names = nix_go!(config.hosts).list_fields()?;585 names.retain(|s| filter(s));586 names.sort_by_key(|h| prefer.contains(h.as_str()));587588 Ok(names.into_iter().map(|h| self.host(&h)))589 }590591 pub fn host(&self, name: &str) -> Result<ConfigHost> {592 let config = &self.config_field;593 let host_config = nix_go!(config.hosts[{ name }]);594595 Ok(ConfigHost {596 config: self.clone(),597 name: name.to_owned(),598 host_config: Some(host_config),599 nixos_config: OnceLock::new(),600 nixos_unchecked_config: OnceLock::new(),601 groups: OnceLock::new(),602 pkgs_override: None,603604 605 local: self.localhost == name,606 remowt: OnceLock::new(),607 nix_store: OnceLock::new(),608 nix_plugin: tokio::sync::OnceCell::new(),609 deploy_kind: OnceLock::new(),610 session_destination: OnceLock::new(),611 legacy_ssh_store: OnceLock::new(),612 })613 }614 pub fn list_hosts(&self) -> Result<Vec<ConfigHost>> {615 let config = &self.config_field;616 let names = nix_go!(config.hosts).list_fields()?;617 let mut out = vec![];618 for name in names {619 out.push(self.host(&name)?);620 }621 Ok(out)622 }623 624 pub fn system_config(&self, host: &str) -> Result<Value> {625 let fleet_field = &self.config_field;626 Ok(nix_go!(fleet_field.hosts[{ host }].nixos.config))627 }628629 pub fn secret_definition(&self, secret: &str) -> Result<Option<SharedSecretDefinition>> {630 let config = &self.config_field;631 let shared_secrets = nix_go!(config.secrets);632 if !shared_secrets.has_field(secret)? {633 return Ok(None);634 }635 Ok(Some(SharedSecretDefinition(nix_go!(636 shared_secrets[secret]637 ))))638 }639640 pub fn save(&self) -> Result<()> {641 let mut tempfile = NamedTempFile::new_in(self.directory.clone()).context("failed to create updated version of fleet.nix in the same directory as original.\nDo you have write access to it? Access only to the fleet.nix won't be enough, the directory is used for atomic overwrite operation.\nIt is not recommended to use fleet by root anyway, move fleet project to your home directory.")?;642 let data = nixlike::serialize(&*self.data)?;643 tempfile.write_all(644 format!(645 "# This file contains fleet state and shouldn't be edited by hand\n\n{data}\n\n# vim: ts=2 et nowrap\n"646 )647 .as_bytes(),648 )?;649 let mut fleet_data_path = self.directory.clone();650 fleet_data_path.push("fleet.nix");651 tempfile.persist(fleet_data_path)?;652 Ok(())653 }654}
1use std::{2 collections::{BTreeMap, BTreeSet, HashSet},3 future::Future,4 io::Write,5 ops::Deref,6 path::PathBuf,7 pin::Pin,8 str::FromStr,9 sync::{Arc, OnceLock},10};1112use anyhow::{Context, Result, anyhow, bail, ensure};13use camino::{Utf8Path, Utf8PathBuf};14use chrono::{DateTime, Utc};15use fleet_shared::SecretData;16use nix_eval::{Store, Value, eval_store, nix_go, nix_go_json, util::assert_warn};17use remowt_client::{AgentBundle, Remowt};18use remowt_endpoints::fs::FsClient;19use remowt_link_shared::Address;20use remowt_ui_prompt::auto::AutoPrompter;21use remowt_ui_prompt::bifrost::PromptEndpoints;22use remowt_ui_prompt::{PrependSourcePrompter, Source};23use tabled::Tabled;24use tempfile::NamedTempFile;25use time::UtcDateTime;26use tokio::task::spawn_blocking;27use tracing::{info, warn};2829use crate::fleetdata::{30 FleetData, FleetSecretData, FleetSecretDistribution, FleetSecretPart, SecretOwner,31};3233pub struct FleetConfigInternals {34 pub prefer_identities: BTreeSet<SecretOwner>,35 pub now: DateTime<Utc>,3637 38 pub directory: PathBuf,39 40 pub local_system: String,41 pub data: Arc<FleetData>,42 43 pub config_field: Value,44 45 pub flake_outputs: Value,46 47 pub localhost: String,4849 50 pub default_pkgs: Value,51 52 pub nixpkgs: Value,5354 pub local_host: OnceLock<Arc<ConfigHost>>,55}565758#[derive(Clone)]59pub struct Config(pub Arc<FleetConfigInternals>);6061impl Deref for Config {62 type Target = FleetConfigInternals;6364 fn deref(&self) -> &Self::Target {65 &self.066 }67}6869#[derive(Clone, PartialEq, Copy, Debug)]70pub enum DeployKind {71 72 UpgradeToFleet,73 74 Fleet,75 76 77 NixosInstall,78 79 80 81 NixosLustrate,82}8384impl FromStr for DeployKind {85 type Err = anyhow::Error;86 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {87 match s {88 "upgrade-to-fleet" => Ok(Self::UpgradeToFleet),89 "fleet" => Ok(Self::Fleet),90 "nixos-install" => Ok(Self::NixosInstall),91 "nixos-lustrate" => Ok(Self::NixosLustrate),92 v => bail!(93 "unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\", \"nixos-install\", \"nixos-lustrate\""94 ),95 }96 }97}98pub struct ConfigHost {99 config: Config,100 pub name: String,101 groups: OnceLock<Vec<String>>,102103 104 deploy_kind: OnceLock<DeployKind>,105 session_destination: OnceLock<String>,106 legacy_ssh_store: OnceLock<bool>,107108 pub host_config: Option<Value>,109 pub nixos_config: OnceLock<Value>,110 pub nixos_unchecked_config: OnceLock<Value>,111 pub pkgs_override: Option<Value>,112113 114 pub local: bool,115 pub remowt: OnceLock<Remowt>,116 nix_store: OnceLock<Arc<Store>>,117 nix_plugin: tokio::sync::OnceCell<()>,118}119120const NIX_PLUGIN_ID: u16 = 2;121122fn agents_dir() -> Result<PathBuf> {123 std::env::var_os("REMOWT_AGENTS_DIR")124 .map(PathBuf::from)125 .or_else(|| option_env!("REMOWT_AGENTS_DIR").map(PathBuf::from))126 .ok_or_else(|| {127 anyhow!("no remowt-agents bundle; set REMOWT_AGENTS_DIR to a remowt-agents output")128 })129}130131fn agent_bundle() -> Result<AgentBundle> {132 AgentBundle::from_dir(agents_dir()?)133}134135#[derive(Debug, Clone, Copy)]136pub enum GenerationStorage {137 Deployer,138 Machine,139 Pusher,140}141impl GenerationStorage {142 fn prefix(&self) -> &'static str {143 match self {144 GenerationStorage::Deployer => "deployer.",145 GenerationStorage::Machine => "",146 GenerationStorage::Pusher => "pusher.",147 }148 }149}150151#[derive(Tabled, Debug)]152pub struct Generation {153 #[tabled(rename = "ID", format("{}", self.rollback_id()))]154 pub id: u32,155 #[tabled(rename = "Current")]156 pub current: bool,157 #[tabled(rename = "Created at")]158 pub datetime: UtcDateTime,159 #[tabled(format = "{:?}")]160 pub store_path: Utf8PathBuf,161 #[tabled(skip)]162 pub location: GenerationStorage,163}164impl Generation {165 pub fn rollback_id(&self) -> String {166 format!("{}{}", self.location.prefix(), self.id)167 }168}169170impl ConfigHost {171 pub async fn list_generations(&self, profile: &str) -> Result<Vec<Generation>> {172 let plugin_id = self.ensure_nix_plugin().await?;173 let nix = self174 .remowt()175 .await?176 .plugin_endpoints::<remowt_fleet::NixClient<_>>(plugin_id);177 let raw = nix178 .list_generations(profile.to_owned())179 .await180 .map_err(|e| anyhow!("{e:?}"))?181 .map_err(|e| anyhow!("{e}"))?;182 raw.into_iter()183 .map(|g| {184 let id: u32 =185 g.id.try_into()186 .with_context(|| format!("generation id {} doesn't fit in u32", g.id))?;187 let datetime = UtcDateTime::from_unix_timestamp(g.creation_time_unix)188 .with_context(|| {189 format!("invalid generation timestamp {}", g.creation_time_unix)190 })?;191 Ok(Generation {192 id,193 current: g.current,194 datetime,195 store_path: g.store_path,196 location: GenerationStorage::Machine,197 })198 })199 .collect()200 }201202 pub fn set_session_destination(&self, dest: String) {203 self.session_destination204 .set(dest)205 .expect("session destination is already set")206 }207 pub fn set_deploy_kind(&self, kind: DeployKind) {208 self.deploy_kind209 .set(kind)210 .expect("deploy kind is already set");211 }212 pub fn set_legacy_ssh_store(&self, legacy: bool) {213 self.legacy_ssh_store214 .set(legacy)215 .expect("legacy ssh store is already set")216 }217 pub async fn deploy_kind(&self) -> Result<DeployKind> {218 if let Some(kind) = self.deploy_kind.get() {219 return Ok(*kind);220 }221 let remowt = self.remowt().await?;222 let fs = remowt.endpoints::<FsClient<_>>();223 let is_fleet_managed = match fs.file_exists(Utf8PathBuf::from("/etc/FLEET_HOST")).await {224 Ok(v) => v,225 Err(e) => {226 bail!("failed to query remote system kind: {e}");227 }228 };229 if !is_fleet_managed {230 bail!(231 "{}",232 indoc::indoc! {"233 host is not marked as managed by fleet234 if you're not trying to lustrate/install system from scratch,235 you should either236 1. manually create /etc/FLEET_HOST file on the target host,237 2. use ?deploy_kind=fleet host argument if you're upgrading from older version of fleet238 3. use ?deploy_kind=upgrade_to_fleet if you're upgrading from plain nixos to fleet-managed nixos239 for installation use ?deploy_kind=nixos_install / ?deploy_kind=nixos_lustrate 240 "}241 );242 }243 244 let _ = self.deploy_kind.set(DeployKind::Fleet);245 Ok(*self.deploy_kind.get().expect("deploy kind is just set"))246 }247 async fn connection(&self) -> Result<Remowt> {248 if let Some(conn) = self.remowt.get() {249 return Ok(conn.clone());250 }251 let bundle = agent_bundle()?;252 let conn = if self.local {253 Remowt::connect_local(&bundle)254 .await255 .context("starting local remowt agent")?256 } else {257 let dest = self258 .session_destination259 .get()260 .cloned()261 .unwrap_or_else(|| self.name.clone());262 Remowt::connect(&dest, &bundle)263 .await264 .map_err(|e| anyhow!("remowt error while connecting to {}: {e:#?}", self.name))?265 };266 PromptEndpoints(PrependSourcePrompter {267 prompter: AutoPrompter::new().await,268 source: if self.local {269 vec![]270 } else {271 vec![Source(std::borrow::Cow::Owned(format!(272 "ssh host: {}",273 self.name274 )))]275 },276 description: "".to_owned(),277 })278 .register_endpoints(&mut conn.rpc());279 let _ = self.remowt.set(conn);280 Ok(self.remowt.get().expect("just set").clone())281 }282283 284 pub async fn remowt(&self) -> Result<Remowt> {285 Ok(self.connection().await?)286 }287288 pub fn ensure_nix_plugin(&self) -> Pin<Box<dyn Future<Output = Result<u16>> + Send + '_>> {289 Box::pin(async {290 self.nix_plugin291 .get_or_try_init(|| async {292 let pkgs = self.pkgs()?;293 let name = "remowt-plugin-fleet";294 let plugin = nix_go!(pkgs[{ name }]);295 let built = plugin296 .build("out")297 .context("failed to build the fleet nix plugin")?;298 let copied = self299 .remote_derivation(&built)300 .await301 .context("failed to copy the fleet nix plugin to the host store")?;302 let bin = copied.join("bin/remowt-plugin-fleet");303 self.remowt()304 .await?305 .run0_load_plugin_path(NIX_PLUGIN_ID, bin.as_str())306 .await307 .context("failed to load the fleet nix plugin")?;308 self.remowt()309 .await?310 .rpc()311 .wait_for_connection_to(Address::Plugin(NIX_PLUGIN_ID))312 .await313 .map_err(|e| anyhow!("failed to wait for plugin"))?;314 anyhow::Ok(())315 })316 .await?;317 Ok(NIX_PLUGIN_ID)318 })319 }320321 async fn nix_store(&self) -> Result<Arc<Store>> {322 if let Some(store) = self.nix_store.get() {323 return Ok(store.clone());324 }325 let conn = self.connection().await?;326 let socket = match self.deploy_kind().await? {327 DeployKind::NixosInstall => {328 remowt_fleet::nix_store_socket(conn, "/mnt?require-sigs=false").await?329 }330 _ => remowt_fleet::nix_store_socket(conn, "auto").await?,331 };332 let uri = format!("unix://{}", socket.display());333 let store = Arc::new(Store::open(&uri)?);334 let _ = self.nix_store.set(store);335 Ok(self.nix_store.get().expect("just set").clone())336 }337338 pub async fn decrypt(&self, data: SecretData) -> Result<Vec<u8>> {339 ensure!(data.encrypted, "secret is not encrypted");340 let remowt = self.remowt().await?;341 let mut cmd = remowt.cmd("fleet-install-secrets");342 cmd.arg("decrypt").eqarg("--secret", data.to_string());343 let encoded = cmd344 .sudo()345 .run_string()346 .await347 .context("failed to call remote host for decrypt")?;348 let data: SecretData = encoded.parse().map_err(|e| anyhow!("{e}"))?;349 ensure!(!data.encrypted, "secret came out encrypted");350 Ok(data.data)351 }352 pub async fn reencrypt_distribution(353 &self,354 data: &FleetSecretDistribution,355 targets: BTreeSet<SecretOwner>,356 now: DateTime<Utc>,357 ) -> Result<FleetSecretDistribution> {358 let mut parts = BTreeMap::new();359 for (part_name, part) in &data.secret.parts {360 parts.insert(361 part_name.clone(),362 if part.raw.encrypted {363 FleetSecretPart {364 raw: self.reencrypt(part.raw.clone(), targets.clone()).await?,365 }366 } else {367 part.clone()368 },369 );370 }371 let secret = FleetSecretData {372 created_at: data.secret.created_at,373 expires_at: data.secret.expires_at,374 generation_data: data.secret.generation_data.clone(),375 parts,376 };377 Ok(FleetSecretDistribution::new(targets, secret, now))378 }379 pub async fn reencrypt(380 &self,381 data: SecretData,382 targets: BTreeSet<SecretOwner>,383 ) -> Result<SecretData> {384 let remowt = self.remowt().await?;385 ensure!(data.encrypted, "secret is not encrypted");386 let mut cmd = remowt.cmd("fleet-install-secrets");387 cmd.arg("reencrypt").eqarg("--secret", data.to_string());388 for target in targets {389 let key = self.config.key(&target).await?;390 cmd.eqarg("--targets", key);391 }392 let encoded = cmd393 .sudo()394 .run_string()395 .await396 .context("failed to call remote host for decrypt")?;397 let data: SecretData = encoded.parse().map_err(|e| anyhow!("{e}"))?;398 ensure!(data.encrypted, "secret came out not encrypted");399 Ok(data)400 }401 402 pub async fn remote_derivation(&self, path: impl AsRef<Utf8Path>) -> Result<Utf8PathBuf> {403 let path = path.as_ref().to_owned();404 if self.local {405 406 return Ok(path);407 }408 let sign: Pin<Box<dyn Future<Output = Result<()>> + Send>> = {409 let path = path.clone();410 Box::pin(async move {411 let local = self.config.local_host();412 let plugin_id = local.ensure_nix_plugin().await?;413 let nix = local414 .remowt()415 .await?416 .plugin_endpoints::<remowt_fleet::NixClient<_>>(plugin_id);417 nix.sign_closure(path, Utf8PathBuf::from("/etc/nix/private-key"))418 .await419 .map_err(|e| anyhow!("{e:?}"))?420 .map_err(|e| anyhow!("{e}"))?;421 Ok(())422 })423 };424 if let Err(e) = sign.await {425 warn!("failed to sign store paths: {e}");426 }427 let store = self.nix_store().await?;428 {429 let path = path.clone();430 let store = eval_store();431 spawn_blocking(move || store.copy_to(&store, path.as_ref()))432 .await433 .expect("copy_to panicked")434 .context("copying closure to remote store")?;435 }436 Ok(path)437 }438}439440struct HostSecretDefinition(Value);441442impl ConfigHost {443 444 445 pub fn tags(&self) -> Result<Vec<String>> {446 if let Some(v) = self.groups.get() {447 return Ok(v.clone());448 }449 let Some(host_config) = &self.host_config else {450 return Ok(vec![]);451 };452 let tags: Vec<String> = nix_go_json!(host_config.tags);453454 let _ = self.groups.set(tags.clone());455456 Ok(tags)457 }458 pub fn nixos_config(&self) -> Result<Value> {459 if let Some(v) = self.nixos_config.get() {460 return Ok(v.clone());461 }462 let Some(host_config) = &self.host_config else {463 bail!("local host has no nixos_config");464 };465 let nixos_config = nix_go!(host_config.nixos.config);466 assert_warn("nixos config evaluation", &nixos_config)?;467468 let _ = self.nixos_config.set(nixos_config.clone());469470 Ok(nixos_config)471 }472 pub fn nixos_unchecked_config(&self) -> Result<Value> {473 if let Some(v) = self.nixos_unchecked_config.get() {474 return Ok(v.clone());475 }476 let Some(host_config) = &self.host_config else {477 bail!("local host has no nixos_config");478 };479 let nixos_config = nix_go!(host_config.nixos_unchecked.config);480481 let _ = self.nixos_unchecked_config.set(nixos_config.clone());482483 Ok(nixos_config)484 }485486 pub fn list_defined_secrets(&self) -> Result<Vec<String>> {487 let nixos = self.nixos_unchecked_config()?;488 let secrets = nix_go!(nixos.secrets);489 secrets.list_fields()490 }491492 493 pub fn pkgs(&self) -> Result<Value> {494 if let Some(value) = &self.pkgs_override {495 return Ok(value.clone());496 }497 let Some(host_config) = &self.host_config else {498 bail!("local host has no host_config");499 };500 501 Ok(nix_go!(host_config.nixos.options._module.args.value.pkgs))502 }503}504505#[derive(Clone)]506pub struct SharedSecretDefinition(Value);507impl SharedSecretDefinition {508 pub fn expected_owners(&self) -> Result<BTreeSet<SecretOwner>> {509 let secret = &self.0;510 Ok(nix_go_json!(secret.expectedOwners))511 }512 pub fn allow_different(&self) -> Result<bool> {513 let secret = &self.0;514 Ok(nix_go_json!(secret.allowDifferent))515 }516 pub fn regenerate_on_owner_added(&self) -> Result<bool> {517 let secret = &self.0;518 Ok(nix_go_json!(secret.regenerateOnOwnerAdded))519 }520 pub fn regenerate_on_owner_removed(&self) -> Result<bool> {521 let secret = &self.0;522 Ok(nix_go_json!(secret.regenerateOnOwnerRemoved))523 }524 pub fn generator(&self) -> Result<Value> {525 let secret = &self.0;526 Ok(nix_go!(secret.generator))527 }528}529530impl Config {531 pub fn tagged_hostnames(&self, tag: &str) -> Result<Vec<String>> {532 let config = &self.config_field;533 let tagged: Vec<String> = nix_go_json!(config.taggedWith[{ tag }]);534 Ok(tagged)535 }536 pub fn expand_owner_set(&self, owners: Vec<String>) -> Result<BTreeSet<String>> {537 let mut out = BTreeSet::new();538 for owner in owners {539 if let Some(tag) = owner.strip_prefix('@') {540 let hosts = self.tagged_hostnames(tag)?;541 out.extend(hosts);542 } else {543 out.insert(owner);544 }545 }546 Ok(out)547 }548 pub fn local_host(&self) -> Arc<ConfigHost> {549 self.local_host550 .get_or_init(|| {551 Arc::new(ConfigHost {552 config: self.clone(),553 name: "<virtual localhost>".to_owned(),554 host_config: None,555 nixos_config: OnceLock::new(),556 nixos_unchecked_config: OnceLock::new(),557 groups: {558 let cell = OnceLock::new();559 let _ = cell.set(vec![]);560 cell561 },562 pkgs_override: Some(self.default_pkgs.clone()),563564 local: true,565 remowt: OnceLock::new(),566 nix_store: OnceLock::new(),567 nix_plugin: tokio::sync::OnceCell::new(),568 deploy_kind: OnceLock::new(),569 session_destination: OnceLock::new(),570 legacy_ssh_store: OnceLock::new(),571 })572 })573 .clone()574 }575576 pub fn preferred_hosts(577 &self,578 filter: impl Fn(&str) -> bool,579 ) -> Result<impl Iterator<Item = Result<ConfigHost>>> {580 let prefer = self581 .prefer_identities582 .iter()583 .filter_map(|v| v.as_host())584 .collect::<HashSet<_>>();585 let config = &self.config_field;586 let mut names = nix_go!(config.hosts).list_fields()?;587 names.retain(|s| filter(s));588 names.sort_by_key(|h| prefer.contains(h.as_str()));589590 Ok(names.into_iter().map(|h| self.host(&h)))591 }592593 pub fn host(&self, name: &str) -> Result<ConfigHost> {594 let config = &self.config_field;595 let host_config = nix_go!(config.hosts[{ name }]);596597 Ok(ConfigHost {598 config: self.clone(),599 name: name.to_owned(),600 host_config: Some(host_config),601 nixos_config: OnceLock::new(),602 nixos_unchecked_config: OnceLock::new(),603 groups: OnceLock::new(),604 pkgs_override: None,605606 607 local: self.localhost == name,608 remowt: OnceLock::new(),609 nix_store: OnceLock::new(),610 nix_plugin: tokio::sync::OnceCell::new(),611 deploy_kind: OnceLock::new(),612 session_destination: OnceLock::new(),613 legacy_ssh_store: OnceLock::new(),614 })615 }616 pub fn list_hosts(&self) -> Result<Vec<ConfigHost>> {617 let config = &self.config_field;618 let names = nix_go!(config.hosts).list_fields()?;619 let mut out = vec![];620 for name in names {621 out.push(self.host(&name)?);622 }623 Ok(out)624 }625 626 pub fn system_config(&self, host: &str) -> Result<Value> {627 let fleet_field = &self.config_field;628 Ok(nix_go!(fleet_field.hosts[{ host }].nixos.config))629 }630631 pub fn secret_definition(&self, secret: &str) -> Result<Option<SharedSecretDefinition>> {632 let config = &self.config_field;633 let shared_secrets = nix_go!(config.secrets);634 if !shared_secrets.has_field(secret)? {635 return Ok(None);636 }637 Ok(Some(SharedSecretDefinition(nix_go!(638 shared_secrets[secret]639 ))))640 }641642 pub fn save(&self) -> Result<()> {643 let mut tempfile = NamedTempFile::new_in(self.directory.clone()).context("failed to create updated version of fleet.nix in the same directory as original.\nDo you have write access to it? Access only to the fleet.nix won't be enough, the directory is used for atomic overwrite operation.\nIt is not recommended to use fleet by root anyway, move fleet project to your home directory.")?;644 let data = nixlike::serialize(&*self.data)?;645 tempfile.write_all(646 format!(647 "# This file contains fleet state and shouldn't be edited by hand\n\n{data}\n\n# vim: ts=2 et nowrap\n"648 )649 .as_bytes(),650 )?;651 let mut fleet_data_path = self.directory.clone();652 fleet_data_path.push("fleet.nix");653 tempfile.persist(fleet_data_path)?;654 Ok(())655 }656}
--- a/crates/nix-eval/src/drv.rs
+++ b/crates/nix-eval/src/drv.rs
@@ -1,41 +1,21 @@
use std::collections::{HashMap, HashSet, VecDeque};
-use std::ffi::CString;
use anyhow::{Result, bail};
+use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
use serde::Deserialize;
use crate::nix_raw::{derivation_free, derivation_to_json, store_drv_from_store_path};
-use crate::{copy_nix_str, with_store_context};
-
-fn store_dir() -> Result<String> {
- let mut out = String::new();
- with_store_context(|c, store, _| unsafe {
- crate::nix_raw::store_get_storedir(c, store, Some(copy_nix_str), (&raw mut out).cast())
- })?;
- Ok(out)
-}
-
-fn to_absolute_store_path(store_dir: &str, path: &str) -> String {
- if path.starts_with('/') {
- path.to_owned()
- } else {
- format!("{store_dir}/{path}")
- }
-}
+use crate::{Store, copy_nix_str, with_default_context};
pub struct Derivation(*mut crate::nix_raw::derivation);
unsafe impl Send for Derivation {}
impl Derivation {
- pub fn from_path(drv_path: &str) -> Result<Self> {
- let path_c = CString::new(drv_path)?;
- let store_path = with_store_context(|c, store, _| unsafe {
- crate::nix_raw::store_parse_path(c, store, path_c.as_ptr())
- })?;
- let drv = with_store_context(|c, store, _| unsafe {
- store_drv_from_store_path(c, store, store_path)
+ pub fn from_path(store: &Store, drv_path: &Utf8Path) -> Result<Self> {
+ let store_path = store.parse_path(drv_path)?;
+ let drv = with_default_context(|c, _| unsafe {
+ store_drv_from_store_path(c, store.as_ptr(), store_path.as_ptr())
});
- unsafe { crate::nix_raw::store_path_free(store_path) };
let drv = drv?;
if drv.is_null() {
bail!("failed to read derivation from {drv_path}");
@@ -45,7 +25,7 @@
pub fn to_json_string(&self) -> Result<String> {
let mut out = String::new();
- with_store_context(|c, _, _| unsafe {
+ with_default_context(|c, _| unsafe {
derivation_to_json(c, self.0, Some(copy_nix_str), (&raw mut out).cast())
})?;
Ok(out)
@@ -78,9 +58,9 @@
#[derive(Debug, Deserialize)]
pub struct DrvInputs {
#[serde(default)]
- pub srcs: Vec<String>,
+ pub srcs: Vec<Utf8PathBuf>,
#[serde(default)]
- pub drvs: HashMap<String, DrvInputEntry>,
+ pub drvs: HashMap<Utf8PathBuf, DrvInputEntry>,
}
#[derive(Debug, Deserialize)]
@@ -90,23 +70,23 @@
#[derive(Debug, Clone)]
pub struct DrvGraph {
- pub root: String,
- pub nodes: HashMap<String, DrvNode>,
+ pub root: Utf8PathBuf,
+ pub nodes: HashMap<Utf8PathBuf, DrvNode>,
}
#[derive(Debug, Clone)]
pub struct DrvNode {
pub name: String,
- pub input_drvs: HashMap<String, Vec<String>>,
- pub input_srcs: Vec<String>,
+ pub input_drvs: HashMap<Utf8PathBuf, Vec<String>>,
+ pub input_srcs: Vec<Utf8PathBuf>,
// TODO: CA outputs without a known paths are skipped
- pub outputs: HashMap<String, String>,
+ pub outputs: HashMap<String, Utf8PathBuf>,
}
impl DrvGraph {
- pub fn resolve(drv_path: &str) -> Result<Self> {
- let sd = store_dir()?;
- let root = to_absolute_store_path(&sd, drv_path);
+ pub fn resolve(store: &Store, drv_path: &Utf8Path) -> Result<Self> {
+ let sd = store.store_dir()?;
+ let root = sd.join(drv_path);
let mut nodes = HashMap::new();
let mut queue = VecDeque::new();
@@ -115,14 +95,14 @@
visited.insert(root.clone());
while let Some(path) = queue.pop_front() {
- let drv = Derivation::from_path(&path)?;
+ let drv = Derivation::from_path(store, &path)?;
let parsed = drv.parsed()?;
- let input_drvs: HashMap<String, Vec<String>> = parsed
+ let input_drvs: HashMap<Utf8PathBuf, Vec<String>> = parsed
.inputs
.drvs
.into_iter()
- .map(|(k, v)| (to_absolute_store_path(&sd, &k), v.outputs))
+ .map(|(k, v)| (sd.join(&k), v.outputs))
.collect();
for dep_path in input_drvs.keys() {
@@ -131,10 +111,10 @@
}
}
- let outputs: HashMap<String, String> = parsed
+ let outputs: HashMap<String, Utf8PathBuf> = parsed
.outputs
.into_iter()
- .filter_map(|(name, out)| out.path.map(|p| (name, to_absolute_store_path(&sd, &p))))
+ .filter_map(|(name, out)| out.path.map(|p| (name, sd.join(&p))))
.collect();
nodes.insert(
@@ -151,11 +131,11 @@
Ok(Self { root, nodes })
}
- pub fn wanted_outputs(&self, root_outputs: &[String]) -> HashMap<String, Vec<String>> {
- let mut wanted: HashMap<String, HashSet<String>> = HashMap::new();
+ pub fn wanted_outputs(&self, root_outputs: &[String]) -> HashMap<Utf8PathBuf, Vec<String>> {
+ let mut wanted: HashMap<Utf8PathBuf, HashSet<String>> = HashMap::new();
wanted.insert(self.root.clone(), root_outputs.iter().cloned().collect());
- let mut queue: VecDeque<String> = VecDeque::new();
+ let mut queue: VecDeque<Utf8PathBuf> = VecDeque::new();
queue.push_back(self.root.clone());
while let Some(path) = queue.pop_front() {
let Some(node) = self.nodes.get(&path) else {
@@ -186,12 +166,19 @@
}
}
-fn extract_drv_name(drv_path: &str) -> String {
- drv_path
- .rsplit('/')
+pub fn extract_drv_name(drv_path: &Utf8Path) -> String {
+ let comp = drv_path
+ .components()
+ .rev()
.next()
- .and_then(|f| f.strip_suffix(".drv"))
- .and_then(|f| f.split_once('-').map(|(_, name)| name))
- .unwrap_or(drv_path)
- .to_owned()
+ .expect("drv path is at least one component");
+ let Utf8Component::Normal(n) = comp else {
+ panic!("drv path is normal");
+ };
+
+ let n = n.strip_suffix(".drv").unwrap_or(n);
+
+ let n = n.split_once(' ').map(|(_, n)| n).unwrap_or(n);
+
+ n.to_owned()
}
--- a/crates/nix-eval/src/lib.rs
+++ b/crates/nix-eval/src/lib.rs
@@ -25,7 +25,7 @@
PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,
bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,
clear_err, copy_value, err_NIX_ERR_KEY, err_NIX_ERR_NIX_ERROR, err_NIX_ERR_OVERFLOW,
- err_NIX_ERR_UNKNOWN, err_code, err_info_msg, err_msg, eval_state_build,
+ err_NIX_ERR_UNKNOWN, err_NIX_OK, err_code, err_info_msg, err_msg, eval_state_build,
eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,
expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,
flake_lock, flake_lock_flags, flake_lock_flags_free, flake_lock_flags_new, flake_reference,
@@ -320,15 +320,16 @@
struct GlobalState {
// Store should be valid as long as EvalState is valid
#[allow(dead_code)]
- store: Store,
+ store: Arc<Store>,
state: EvalState,
}
impl GlobalState {
fn new() -> Result<Self> {
let mut ctx = NixContext::new();
- let store = ctx
- .run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })
- .map(Store)?;
+ let store = Arc::new(
+ ctx.run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })
+ .map(Store)?,
+ );
let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;
ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;
@@ -385,66 +386,8 @@
v
}
-/// Same as with_default_context, but also passes store...
-/// Yep, this code is garbage and needs to be refactored.
-pub(crate) fn with_store_context<T>(
- f: impl FnOnce(*mut c_context, *mut c_store, *mut c_eval_state) -> T,
-) -> Result<T> {
- let global = &GLOBAL_STATE;
- let (ctx, store, state) =
- THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.store.0, global.state.0));
- let mut ctx = NixContext(ctx);
- let v = ctx.run_in_context(|c| f(c, store, state));
- std::mem::forget(ctx);
- v
-}
-
pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {
with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())
-}
-
-#[instrument(skip(dst))]
-pub fn copy_closure_to(dst: &Store, path: &Utf8Path) -> Result<()> {
- let path_c = CString::new(path.as_str())?;
- with_store_context(|c, src_store, _state| -> Result<()> {
- let sp = unsafe { store_parse_path(c, src_store, path_c.as_ptr()) };
- if sp.is_null() {
- bail!("failed to parse store path {path}");
- }
- let rc = unsafe { store_copy_closure(c, src_store, dst.0, sp) };
- unsafe { store_path_free(sp) };
- if rc != nix_raw::err_NIX_OK {
- bail!("store_copy_closure failed (code {rc})");
- }
- Ok(())
- })?
-}
-
-#[instrument]
-pub fn switch_profile(profile: &str, store_path: &Utf8Path) -> Result<()> {
- let msg = with_store_context(|_c, store, _state| unsafe {
- nix_cxx::switch_profile(store.cast(), profile, store_path.as_str())
- })?
- .to_string();
- if msg.is_empty() {
- Ok(())
- } else {
- bail!("failed to switch profile {profile}: {msg}");
- }
-}
-
-// TODO: fleet operator-managed key file
-#[instrument]
-pub fn sign_closure(store_path: &str, key_file: &str) -> Result<()> {
- let msg = with_store_context(|_c, store, _state| unsafe {
- nix_cxx::sign_closure(store.cast(), store_path, key_file)
- })?
- .to_string();
- if msg.is_empty() {
- Ok(())
- } else {
- bail!("failed to sign {store_path}: {msg}");
- }
}
#[derive(Debug)]
@@ -480,48 +423,8 @@
current: g.current,
})
.collect())
-}
-
-#[instrument]
-pub fn add_file_to_store(name: &str, path: &Utf8Path) -> Result<AddedFile> {
- let res = with_store_context(|_c, store, _state| unsafe {
- nix_cxx::add_file_to_store(store.cast(), name, path.as_str())
- })?;
- if !res.error.is_empty() {
- bail!("failed to add {path} to store: {}", res.error);
- }
- Ok(AddedFile {
- store_path: Utf8PathBuf::from(res.store_path),
- hash: res.hash,
- })
-}
-
-pub fn build_drv_outputs(drv_path: &str, output_names: &[String]) -> Result<Vec<String>> {
- let joined = output_names.join("\n");
- let res = with_store_context(|_c, store, _state| unsafe {
- nix_cxx::build_drv_outputs(store.cast(), drv_path, &joined)
- })?;
- if !res.error.is_empty() {
- bail!("build of {drv_path} failed: {}", res.error);
- }
- Ok(res.outputs)
}
-pub fn substitute_paths(paths: &[String]) -> Result<Vec<String>> {
- let joined = paths.join("\n");
- let res = with_store_context(|_c, store, _state| unsafe {
- nix_cxx::substitute_paths(store.cast(), &joined)
- })?;
- if !res.error.is_empty() {
- warn!("substitute_paths reported: {}", res.error);
- }
- Ok(res.outputs)
-}
-
-pub fn is_valid_path(path: &str) -> Result<bool> {
- with_store_context(|_c, store, _state| unsafe { nix_cxx::is_valid_path(store.cast(), path) })
-}
-
pub struct FetchSettings(*mut fetchers_settings);
impl FetchSettings {
pub fn new() -> Self {
@@ -624,6 +527,10 @@
unsafe impl Send for Store {}
unsafe impl Sync for Store {}
+pub fn eval_store() -> Arc<Store> {
+ GLOBAL_STATE.store.clone()
+}
+
impl Store {
pub fn open(uri: &str) -> Result<Self> {
let uri = CString::new(uri)?;
@@ -634,11 +541,108 @@
Ok(Store(ptr))
}
- fn parse_path(&self, path: &CStr) -> Result<StorePath> {
+ pub fn parse_path(&self, path: &Utf8Path) -> Result<StorePath> {
+ let path = CString::new(path.as_str()).expect("valid cstr");
with_default_context(|c, _| {
StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })
})
}
+
+ #[instrument(skip(self))]
+ pub fn sign_closure(&self, path: &Utf8Path, key_file: &Utf8Path) -> Result<()> {
+ let err = with_default_context(|_, _| unsafe {
+ nix_cxx::sign_closure(self.as_ptr().cast(), path.as_str(), key_file.as_str())
+ })?
+ .to_string();
+
+ if err.is_empty() {
+ Ok(())
+ } else {
+ bail!("failed to sign {path}: {err}");
+ }
+ }
+
+ #[instrument(skip(self, dst))]
+ pub fn copy_to(&self, dst: &Store, path: &Utf8Path) -> Result<()> {
+ let sp = self
+ .parse_path(&path)
+ .context("failed to parse store path")?;
+ let rc = with_default_context(|c, _| unsafe {
+ store_copy_closure(c, self.as_ptr(), dst.0, sp.as_ptr())
+ })?;
+ if rc != err_NIX_OK {
+ bail!("store_copy_closure failed (code {rc})");
+ }
+ Ok(())
+ }
+
+ /// Would only work with local store.
+ #[instrument(skip(self))]
+ pub fn switch_profile(&self, profile: &str, path: &Utf8Path) -> Result<()> {
+ let msg = unsafe { nix_cxx::switch_profile(self.as_ptr().cast(), profile, path.as_str()) };
+ if msg.is_empty() {
+ Ok(())
+ } else {
+ bail!("failed to switch profile {profile}: {msg}");
+ }
+ }
+
+ #[instrument(skip(self))]
+ pub fn add_file(&self, name: &str, path: &Utf8Path) -> Result<AddedFile> {
+ let msg = unsafe { nix_cxx::add_file_to_store(self.as_ptr().cast(), name, path.as_str()) };
+ if !msg.error.is_empty() {
+ bail!("failed to add {path} to store: {}", msg.error)
+ }
+ Ok(AddedFile {
+ store_path: Utf8PathBuf::from(msg.store_path),
+ hash: msg.hash,
+ })
+ }
+
+ #[instrument(skip(self))]
+ pub fn substitute_paths(&self, paths: &[Utf8PathBuf]) -> Result<Vec<Utf8PathBuf>> {
+ let joined = paths.into_iter().join("\n");
+ let res = unsafe { nix_cxx::substitute_paths(self.as_ptr().cast(), &joined) };
+ if !res.error.is_empty() {
+ warn!("substitute_paths reported: {}", res.error);
+ }
+ Ok(res.outputs.into_iter().map(Utf8PathBuf::from).collect())
+ }
+
+ #[instrument(skip(self))]
+ pub fn is_valid_path(&self, path: &Utf8Path) -> bool {
+ unsafe { nix_cxx::is_valid_path(self.as_ptr().cast(), path.as_str()) }
+ }
+
+ #[instrument(skip(self))]
+ pub fn build_drv_outputs(
+ &self,
+ drv_path: &Utf8Path,
+ output_names: &[String],
+ ) -> Result<Vec<String>> {
+ let joined = output_names.join("\n");
+ let res =
+ unsafe { nix_cxx::build_drv_outputs(self.as_ptr().cast(), drv_path.as_str(), &joined) };
+ if !res.error.is_empty() {
+ bail!("build of {drv_path} failed: {}", res.error);
+ }
+ Ok(res.outputs)
+ }
+
+ #[instrument(skip(self))]
+ pub fn store_dir(&self) -> Result<Utf8PathBuf> {
+ let mut out = String::new();
+ with_default_context(|c, es| unsafe {
+ nix_raw::store_get_storedir(c, self.as_ptr(), Some(copy_nix_str), (&raw mut out).cast())
+ })?;
+ let p = Utf8PathBuf::from(out);
+ assert!(p.is_absolute());
+ Ok(p)
+ }
+
+ fn as_ptr(&self) -> *mut c_store {
+ self.0
+ }
}
impl Drop for Store {
fn drop(&mut self) {
@@ -1060,11 +1064,12 @@
self.clone()
};
- let drv_path = v
- .get_field("drvPath")
- .context("getting drvPath")?
- .to_string()?;
- let graph = Arc::new(drv::DrvGraph::resolve(&drv_path)?);
+ let drv_path = Utf8PathBuf::from(
+ v.get_field("drvPath")
+ .context("getting drvPath")?
+ .to_string()?,
+ );
+ let graph = Arc::new(drv::DrvGraph::resolve(&eval_store(), &drv_path)?);
let _guard = logging::register_build_graph(&Span::current(), &graph);
scheduler::build_graph_sync(graph.clone(), vec![output.to_owned()])?;
@@ -1255,8 +1260,12 @@
}
}
-struct StorePath(*mut c_store_path);
-impl StorePath {}
+pub struct StorePath(*mut c_store_path);
+impl StorePath {
+ fn as_ptr(&self) -> *mut c_store_path {
+ self.0
+ }
+}
impl Drop for StorePath {
fn drop(&mut self) {
--- a/crates/nix-eval/src/logging.rs
+++ b/crates/nix-eval/src/logging.rs
@@ -2,6 +2,7 @@
use std::fmt::Arguments;
use std::sync::{LazyLock, Mutex};
+use camino::{Utf8Path, Utf8PathBuf};
use cxx::ExternType;
use tracing::{
Level, Span, debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn,
@@ -11,6 +12,8 @@
use tracing_indicatif::span_ext::IndicatifSpanExt as _;
use vte::Parser;
+use crate::drv::extract_drv_name;
+
#[derive(Debug)]
enum ActivityType {
Unknown = 0,
@@ -33,20 +36,13 @@
a.strip_prefix(pref)?.strip_suffix(suff)
}
-fn parse_path(path: &str) -> &str {
- strip_prefix_suffix(path, "\x1b[35;1m", "\x1b[0m").unwrap_or(path)
+fn parse_path(path: &str) -> Utf8PathBuf {
+ Utf8PathBuf::from(strip_prefix_suffix(path, "\x1b[35;1m", "\x1b[0m").unwrap_or(path))
}
-fn parse_drv(drv: &str) -> &str {
+fn parse_drv(drv: &str) -> String {
let drv = parse_path(drv);
- if let Some(pkg) = drv.strip_prefix("/nix/store/") {
- let mut it = pkg.splitn(2, '-');
- it.next();
- if let Some(pkg) = it.next() {
- return pkg;
- }
- }
- drv
+ extract_drv_name(&drv)
}
fn parse_host(host: &str) -> &str {
if host.is_empty() || host == "local" {
@@ -287,19 +283,19 @@
struct DrvGraphEntry {
name: String,
- parent: Option<String>,
+ parent: Option<Utf8PathBuf>,
span: Option<Span>,
refcount: usize,
}
-static DRV_GRAPH: LazyLock<Mutex<HashMap<String, DrvGraphEntry>>> =
+static DRV_GRAPH: LazyLock<Mutex<HashMap<Utf8PathBuf, DrvGraphEntry>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
-static ACTIVITY_TO_DRV: LazyLock<Mutex<HashMap<u64, String>>> =
+static ACTIVITY_TO_DRV: LazyLock<Mutex<HashMap<u64, Utf8PathBuf>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub struct BuildGraphGuard {
- paths: Vec<String>,
+ paths: Vec<Utf8PathBuf>,
}
impl Drop for BuildGraphGuard {
@@ -369,7 +365,7 @@
BuildGraphGuard { paths }
}
-fn ensure_drv_span(drv_path: &str) -> Option<Span> {
+fn ensure_drv_span(drv_path: &Utf8Path) -> Option<Span> {
let mut drv_graph = DRV_GRAPH.lock().expect("not poisoned");
if let Some(span) = drv_graph.get(drv_path).and_then(|e| e.span.clone()) {
@@ -442,7 +438,7 @@
self.fields.first().and_then(|f| match f {
FieldValue::Str(drv_path) => {
let clean = parse_path(drv_path);
- let span = ensure_drv_span(clean);
+ let span = ensure_drv_span(&clean);
if span.is_some() {
ACTIVITY_TO_DRV
.lock()
--- a/crates/nix-eval/src/scheduler.rs
+++ b/crates/nix-eval/src/scheduler.rs
@@ -1,12 +1,16 @@
use std::collections::{HashMap, HashSet};
+use std::mem;
use std::sync::Arc;
use anyhow::{Context, Result, bail};
+use camino::{Utf8Path, Utf8PathBuf};
use futures::stream::{FuturesUnordered, StreamExt};
use tokio::sync::{Semaphore, broadcast};
+use tokio::task::spawn_blocking;
use tracing::{debug, info, instrument, warn};
use crate::drv::DrvGraph;
+use crate::{Store, eval_store};
#[derive(Clone, Debug)]
pub enum BuildEvent {
@@ -17,31 +21,32 @@
satisfied: usize,
},
DrvStarted {
- drv_path: String,
+ drv_path: Utf8PathBuf,
name: String,
wanted: Vec<String>,
},
DrvSkipped {
- drv_path: String,
+ drv_path: Utf8PathBuf,
name: String,
},
DrvFinished {
- drv_path: String,
+ drv_path: Utf8PathBuf,
name: String,
},
DrvFailed {
- drv_path: String,
+ drv_path: Utf8PathBuf,
name: String,
error: String,
},
DrvCancelled {
- drv_path: String,
+ drv_path: Utf8PathBuf,
name: String,
- failed_dep: String,
+ failed_dep: Utf8PathBuf,
},
}
pub struct Scheduler {
+ store: Arc<Store>,
parallelism: usize,
events: broadcast::Sender<BuildEvent>,
}
@@ -51,6 +56,7 @@
let parallelism = parallelism.max(1);
let (events, _) = broadcast::channel(1024);
Self {
+ store: eval_store(),
parallelism,
events,
}
@@ -71,7 +77,7 @@
async fn substitute_prepass(
&self,
graph: &DrvGraph,
- wanted: &HashMap<String, Vec<String>>,
+ wanted: &HashMap<Utf8PathBuf, Vec<String>>,
) -> Result<()> {
let paths = collect_substitute_paths(graph, wanted);
if paths.is_empty() {
@@ -82,7 +88,8 @@
.send(BuildEvent::SubstitutePrepassStarted { paths: paths.len() });
debug!("substitute pre-pass: {} paths", paths.len());
- let satisfied = tokio::task::spawn_blocking(move || crate::substitute_paths(&paths))
+ let store = self.store.clone();
+ let satisfied = spawn_blocking(move || store.substitute_paths(&paths))
.await
.expect("substitute pre-pass task should not panic")?;
@@ -95,14 +102,14 @@
async fn build_topo(
&self,
graph: &Arc<DrvGraph>,
- wanted: HashMap<String, Vec<String>>,
+ wanted: HashMap<Utf8PathBuf, Vec<String>>,
) -> Result<()> {
- let mut indeg: HashMap<String, usize> = graph
+ let mut indeg: HashMap<Utf8PathBuf, usize> = graph
.nodes
.iter()
.map(|(k, n)| (k.clone(), n.input_drvs.len()))
.collect();
- let mut dependents: HashMap<String, Vec<String>> = HashMap::new();
+ let mut dependents: HashMap<Utf8PathBuf, Vec<Utf8PathBuf>> = HashMap::new();
for (path, node) in &graph.nodes {
for dep in node.input_drvs.keys() {
dependents
@@ -113,18 +120,18 @@
}
let sem = Arc::new(Semaphore::new(self.parallelism));
- let mut ready: Vec<String> = indeg
+ let mut ready: Vec<Utf8PathBuf> = indeg
.iter()
.filter(|(_, d)| **d == 0)
.map(|(k, _)| k.clone())
.collect();
let mut in_flight = FuturesUnordered::new();
- let mut failed: HashMap<String, String> = HashMap::new();
+ let mut failed: HashMap<Utf8PathBuf, String> = HashMap::new();
// Tainted = transitively depends on a failed drv
- let mut tainted: HashMap<String, String> = HashMap::new();
+ let mut tainted: HashMap<Utf8PathBuf, Utf8PathBuf> = HashMap::new();
loop {
- let batch: Vec<String> = std::mem::take(&mut ready);
+ let batch: Vec<Utf8PathBuf> = mem::take(&mut ready);
for path in batch {
if let Some(failed_dep) = tainted.get(&path) {
let name = graph
@@ -145,6 +152,7 @@
let events = self.events.clone();
let graph = graph.clone();
let wanted_here = wanted.get(&path).cloned().unwrap_or_default();
+ let store = self.store.clone();
in_flight.push(tokio::spawn(async move {
let _permit = sem.acquire_owned().await.expect("semaphore not closed");
let node = graph
@@ -158,7 +166,7 @@
&& wanted_here.iter().all(|o| {
node.outputs
.get(o)
- .map(|p| crate::is_valid_path(p).unwrap_or(false))
+ .map(|p| store.is_valid_path(p))
.unwrap_or(false)
});
if all_valid {
@@ -176,8 +184,9 @@
});
let path_for_build = path.clone();
- let res = tokio::task::spawn_blocking(move || {
- crate::build_drv_outputs(&path_for_build, &wanted_here)
+ let store = store.clone();
+ let res = spawn_blocking(move || {
+ store.build_drv_outputs(&path_for_build, &wanted_here)
})
.await
.expect("build task should not panic");
@@ -259,10 +268,10 @@
}
fn propagate_done(
- dependents: &HashMap<String, Vec<String>>,
- indeg: &mut HashMap<String, usize>,
- ready: &mut Vec<String>,
- finished: &str,
+ dependents: &HashMap<Utf8PathBuf, Vec<Utf8PathBuf>>,
+ indeg: &mut HashMap<Utf8PathBuf, usize>,
+ ready: &mut Vec<Utf8PathBuf>,
+ finished: &Utf8Path,
) {
if let Some(deps) = dependents.get(finished) {
for d in deps {
@@ -276,11 +285,11 @@
}
fn mark_tainted(
- dependents: &HashMap<String, Vec<String>>,
- failed: &str,
- tainted: &mut HashMap<String, String>,
+ dependents: &HashMap<Utf8PathBuf, Vec<Utf8PathBuf>>,
+ failed: &Utf8Path,
+ tainted: &mut HashMap<Utf8PathBuf, Utf8PathBuf>,
) {
- let mut queue: Vec<String> = dependents.get(failed).cloned().unwrap_or_default();
+ let mut queue: Vec<Utf8PathBuf> = dependents.get(failed).cloned().unwrap_or_default();
while let Some(node) = queue.pop() {
if tainted
.entry(node.clone())
@@ -298,20 +307,17 @@
}
}
-fn path_to_root(graph: &DrvGraph, from: &str) -> Vec<String> {
- let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
+fn path_to_root(graph: &DrvGraph, from: &Utf8Path) -> Vec<String> {
+ let mut dependents: HashMap<&Utf8Path, Vec<&Utf8Path>> = HashMap::new();
for (path, node) in &graph.nodes {
for dep in node.input_drvs.keys() {
- dependents
- .entry(dep.as_str())
- .or_default()
- .push(path.as_str());
+ dependents.entry(dep).or_default().push(path);
}
}
let mut chain: Vec<String> = vec![node_name(graph, from)];
let mut cur = from;
- let mut seen: HashSet<&str> = HashSet::new();
+ let mut seen: HashSet<&Utf8Path> = HashSet::new();
seen.insert(cur);
while cur != graph.root.as_str() {
let Some(next) = dependents.get(cur).and_then(|v| v.first().copied()) else {
@@ -326,19 +332,19 @@
chain
}
-fn node_name(graph: &DrvGraph, path: &str) -> String {
+fn node_name(graph: &DrvGraph, path: &Utf8Path) -> String {
graph
.nodes
.get(path)
.map(|n| n.name.clone())
- .unwrap_or_else(|| path.to_owned())
+ .unwrap_or_else(|| path.to_string())
}
fn collect_substitute_paths(
graph: &DrvGraph,
- wanted: &HashMap<String, Vec<String>>,
-) -> Vec<String> {
- let mut paths: HashSet<String> = HashSet::new();
+ wanted: &HashMap<Utf8PathBuf, Vec<String>>,
+) -> Vec<Utf8PathBuf> {
+ let mut paths: HashSet<Utf8PathBuf> = HashSet::new();
for node in graph.nodes.values() {
for src in &node.input_srcs {
paths.insert(src.clone());
--- a/crates/remowt-fleet/src/lib.rs
+++ b/crates/remowt-fleet/src/lib.rs
@@ -1,13 +1,15 @@
use std::path::PathBuf;
use anyhow::{Context as _, Result};
+use bifrostlink::declarative::endpoints;
use bifrostlink::Config;
-use bifrostlink::declarative::endpoints;
use camino::Utf8PathBuf;
+use nix_eval::eval_store;
use remowt_client::Remowt;
use remowt_endpoints::nix_daemon::NixDaemonClient;
use serde::{Deserialize, Serialize};
use tokio::net::UnixListener;
+use tokio::task::spawn_blocking;
use tracing::error;
pub struct Nix;
@@ -35,9 +37,10 @@
profile: String,
store_path: Utf8PathBuf,
) -> Result<(), NixError> {
- tokio::task::spawn_blocking(move || nix_eval::switch_profile(&profile, &store_path))
+ let store = eval_store();
+ spawn_blocking(move || store.switch_profile(&profile, &store_path))
.await
- .map_err(|e| NixError::Profile(e.to_string()))?
+ .expect("switch_profile panicked")
.map_err(|e| NixError::Profile(e.to_string()))
}
@@ -47,11 +50,12 @@
store_path: Utf8PathBuf,
key_file: Utf8PathBuf,
) -> Result<(), NixError> {
- tokio::task::spawn_blocking(move || {
- nix_eval::sign_closure(store_path.as_str(), key_file.as_str())
+ spawn_blocking(move || {
+ let store = eval_store();
+ store.sign_closure(&store_path, &key_file)
})
.await
- .map_err(|e| NixError::Sign(e.to_string()))?
+ .expect("store signing panicked")
.map_err(|e| NixError::Sign(e.to_string()))
}
@@ -60,11 +64,11 @@
&self,
profile: String,
) -> Result<Vec<nix_eval::ProfileGeneration>, NixError> {
- tokio::task::spawn_blocking(move || {
+ spawn_blocking(move || {
nix_eval::list_generations(&format!("/nix/var/nix/profiles/{profile}"))
})
.await
- .map_err(|e| NixError::ListGenerations(e.to_string()))?
+ .expect("generation listing panicked")
.map_err(|e| NixError::ListGenerations(e.to_string()))
}
}