diff --git a/crates/notstrap/src/lib.rs b/crates/notstrap/src/lib.rs index 0af973a..e293df3 100644 --- a/crates/notstrap/src/lib.rs +++ b/crates/notstrap/src/lib.rs @@ -2,19 +2,18 @@ use anyhow::{Context, Result}; use notcore::{HookPhase, Report, StepStatus}; use notfiles::{LinkOptions, link}; use nothooks::{HookRunner, run_phase}; -use notsecrets::identities::Identity; use notsecrets::sources::IdentitySource; -use notsecrets::{BitwardenSource, FileSource, PromptSource, YubikeySource, resolve_identities}; +use notsecrets::{ + BitwardenSource, FileSource, PromptSource, SecretResolver, YubikeySource, resolve_identities, +}; use serde::Deserialize; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; pub mod prereqs; pub mod repo; pub use notnet::TailscaleOptions; -type EnvInjector = Box>) -> Result>; - #[derive(Deserialize)] pub struct NotstrapConfig { pub bootstrap: BootstrapSection, @@ -56,8 +55,8 @@ pub struct BootstrapOptions { pub tailscale: Option>, /// None = skip prereq check (tests). Some(f) = run f(). pub check_prereqs: Option Result<()>>>, - /// None = skip env injection (tests). Some(f) = decrypt sops at path and inject. - pub env_injector: Option, + /// Path to notsecrets.toml. None = skip secret resolution. + pub secrets_config: Option, } pub fn run(opts: BootstrapOptions) -> Result { @@ -142,7 +141,7 @@ pub fn run(opts: BootstrapOptions) -> Result { ] }; - let identities = match resolve_identities(sources) { + let _identities = match resolve_identities(sources) { Ok(ids) => { report.add("age key", StepStatus::Ok); ids @@ -153,33 +152,44 @@ pub fn run(opts: BootstrapOptions) -> Result { } }; - // 5. Decrypt sops secrets (optional) - if let Some(injector) = opts.env_injector { - let sops_path = dotfiles_dir.join(&cfg.bootstrap.sops_file); - match injector(&sops_path, identities) { - Ok(env_content) => { - for line in env_content.lines() { - match parse_env_line(line) { - Ok(Some((k, v))) => { - // SAFETY: `notstrap` is a single-threaded bootstrap binary; no other - // threads exist. Subsequent Command::spawn calls (git, hooks) will - // inherit these vars — keys are validated by parse_env_line before - // reaching this point (null bytes and dangerous keys are rejected). - unsafe { - std::env::set_var(&k, &v); + // 5b. Resolve secrets via notsecrets.toml (optional) + if let Some(secrets_path) = opts.secrets_config { + match notsecrets::load_config(&secrets_path) { + Ok(secrets_cfg) => match SecretResolver::from_config(secrets_cfg) { + Ok(resolver) => match resolver.resolve_all() { + Ok(env_map) => { + for (k, v) in &env_map { + match parse_env_line(&format!("{k}={v}")) { + Ok(Some((k, v))) => { + // SAFETY: `notstrap` is a single-threaded bootstrap + // binary; no other threads exist. Keys are validated + // by parse_env_line (null bytes and dangerous keys + // are rejected). + unsafe { + std::env::set_var(&k, &v); + } + } + Ok(None) => {} + Err(e) => { + report.add("secrets", StepStatus::Failed(e.to_string())); + return Ok(report); + } } } - Ok(None) => {} - Err(e) => { - report.add("decrypt secrets", StepStatus::Failed(e.to_string())); - return Ok(report); - } + report.add("secrets", StepStatus::Ok); } + Err(e) => { + report.add("secrets", StepStatus::Failed(e.to_string())); + return Ok(report); + } + }, + Err(e) => { + report.add("secrets", StepStatus::Failed(e.to_string())); + return Ok(report); } - report.add("decrypt secrets", StepStatus::Ok); - } + }, Err(e) => { - report.add("decrypt secrets", StepStatus::Failed(e.to_string())); + report.add("secrets", StepStatus::Failed(e.to_string())); return Ok(report); } } diff --git a/crates/notstrap/src/main.rs b/crates/notstrap/src/main.rs index 179a636..6f6d193 100644 --- a/crates/notstrap/src/main.rs +++ b/crates/notstrap/src/main.rs @@ -46,7 +46,7 @@ fn main() -> Result<()> { dotfiles, tailscale: None, // defer to [tailscale] section in config (if present) check_prereqs: Some(Box::new(prereqs::check_prerequisites)), - env_injector: None, + secrets_config: None, }; let report = run(opts)?; report.print(); diff --git a/tests/integration/tests/bootstrap.rs b/tests/integration/tests/bootstrap.rs index b856a9d..7541d81 100644 --- a/tests/integration/tests/bootstrap.rs +++ b/tests/integration/tests/bootstrap.rs @@ -71,7 +71,7 @@ fn make_opts(env: &TestEnv, force: bool) -> BootstrapOptions { dotfiles: Some(env.dotfiles.path().to_path_buf()), tailscale: Some(None), // skip Tailscale in tests check_prereqs: None, - env_injector: None, + secrets_config: None, } } @@ -219,7 +219,7 @@ fn test_bootstrap_fails_fast_on_bad_key() { dotfiles: Some(d.to_path_buf()), tailscale: Some(None), // skip Tailscale in tests check_prereqs: None, - env_injector: None, + secrets_config: None, }; let report = run(opts).expect("run() should return Ok even when a step fails");