refactor(notstrap): replace EnvInjector with SecretResolver
Some checks failed
Public Repo Readiness / Check Public Repo Readiness (push) Has been cancelled
Some checks failed
Public Repo Readiness / Check Public Repo Readiness (push) Has been cancelled
Remove the EnvInjector closure and replace with a secrets_config path that loads notsecrets.toml and uses SecretResolver for env injection. Existing parse_env_line validation (blocked keys, null bytes) is preserved in the injection loop.
This commit is contained in:
@@ -2,19 +2,18 @@ use anyhow::{Context, Result};
|
|||||||
use notcore::{HookPhase, Report, StepStatus};
|
use notcore::{HookPhase, Report, StepStatus};
|
||||||
use notfiles::{LinkOptions, link};
|
use notfiles::{LinkOptions, link};
|
||||||
use nothooks::{HookRunner, run_phase};
|
use nothooks::{HookRunner, run_phase};
|
||||||
use notsecrets::identities::Identity;
|
|
||||||
use notsecrets::sources::IdentitySource;
|
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 serde::Deserialize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub mod prereqs;
|
pub mod prereqs;
|
||||||
pub mod repo;
|
pub mod repo;
|
||||||
|
|
||||||
pub use notnet::TailscaleOptions;
|
pub use notnet::TailscaleOptions;
|
||||||
|
|
||||||
type EnvInjector = Box<dyn Fn(&Path, Vec<Box<dyn Identity>>) -> Result<String>>;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct NotstrapConfig {
|
pub struct NotstrapConfig {
|
||||||
pub bootstrap: BootstrapSection,
|
pub bootstrap: BootstrapSection,
|
||||||
@@ -56,8 +55,8 @@ pub struct BootstrapOptions {
|
|||||||
pub tailscale: Option<Option<TailscaleOptions>>,
|
pub tailscale: Option<Option<TailscaleOptions>>,
|
||||||
/// None = skip prereq check (tests). Some(f) = run f().
|
/// None = skip prereq check (tests). Some(f) = run f().
|
||||||
pub check_prereqs: Option<Box<dyn Fn() -> Result<()>>>,
|
pub check_prereqs: Option<Box<dyn Fn() -> Result<()>>>,
|
||||||
/// None = skip env injection (tests). Some(f) = decrypt sops at path and inject.
|
/// Path to notsecrets.toml. None = skip secret resolution.
|
||||||
pub env_injector: Option<EnvInjector>,
|
pub secrets_config: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
||||||
@@ -142,7 +141,7 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
let identities = match resolve_identities(sources) {
|
let _identities = match resolve_identities(sources) {
|
||||||
Ok(ids) => {
|
Ok(ids) => {
|
||||||
report.add("age key", StepStatus::Ok);
|
report.add("age key", StepStatus::Ok);
|
||||||
ids
|
ids
|
||||||
@@ -153,33 +152,44 @@ pub fn run(opts: BootstrapOptions) -> Result<Report> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 5. Decrypt sops secrets (optional)
|
// 5b. Resolve secrets via notsecrets.toml (optional)
|
||||||
if let Some(injector) = opts.env_injector {
|
if let Some(secrets_path) = opts.secrets_config {
|
||||||
let sops_path = dotfiles_dir.join(&cfg.bootstrap.sops_file);
|
match notsecrets::load_config(&secrets_path) {
|
||||||
match injector(&sops_path, identities) {
|
Ok(secrets_cfg) => match SecretResolver::from_config(secrets_cfg) {
|
||||||
Ok(env_content) => {
|
Ok(resolver) => match resolver.resolve_all() {
|
||||||
for line in env_content.lines() {
|
Ok(env_map) => {
|
||||||
match parse_env_line(line) {
|
for (k, v) in &env_map {
|
||||||
|
match parse_env_line(&format!("{k}={v}")) {
|
||||||
Ok(Some((k, v))) => {
|
Ok(Some((k, v))) => {
|
||||||
// SAFETY: `notstrap` is a single-threaded bootstrap binary; no other
|
// SAFETY: `notstrap` is a single-threaded bootstrap
|
||||||
// threads exist. Subsequent Command::spawn calls (git, hooks) will
|
// binary; no other threads exist. Keys are validated
|
||||||
// inherit these vars — keys are validated by parse_env_line before
|
// by parse_env_line (null bytes and dangerous keys
|
||||||
// reaching this point (null bytes and dangerous keys are rejected).
|
// are rejected).
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var(&k, &v);
|
std::env::set_var(&k, &v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
report.add("decrypt secrets", StepStatus::Failed(e.to_string()));
|
report.add("secrets", StepStatus::Failed(e.to_string()));
|
||||||
return Ok(report);
|
return Ok(report);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
report.add("decrypt secrets", StepStatus::Ok);
|
report.add("secrets", StepStatus::Ok);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
report.add("decrypt secrets", StepStatus::Failed(e.to_string()));
|
report.add("secrets", StepStatus::Failed(e.to_string()));
|
||||||
|
return Ok(report);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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);
|
return Ok(report);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ fn main() -> Result<()> {
|
|||||||
dotfiles,
|
dotfiles,
|
||||||
tailscale: None, // defer to [tailscale] section in config (if present)
|
tailscale: None, // defer to [tailscale] section in config (if present)
|
||||||
check_prereqs: Some(Box::new(prereqs::check_prerequisites)),
|
check_prereqs: Some(Box::new(prereqs::check_prerequisites)),
|
||||||
env_injector: None,
|
secrets_config: None,
|
||||||
};
|
};
|
||||||
let report = run(opts)?;
|
let report = run(opts)?;
|
||||||
report.print();
|
report.print();
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ fn make_opts(env: &TestEnv, force: bool) -> BootstrapOptions {
|
|||||||
dotfiles: Some(env.dotfiles.path().to_path_buf()),
|
dotfiles: Some(env.dotfiles.path().to_path_buf()),
|
||||||
tailscale: Some(None), // skip Tailscale in tests
|
tailscale: Some(None), // skip Tailscale in tests
|
||||||
check_prereqs: None,
|
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()),
|
dotfiles: Some(d.to_path_buf()),
|
||||||
tailscale: Some(None), // skip Tailscale in tests
|
tailscale: Some(None), // skip Tailscale in tests
|
||||||
check_prereqs: None,
|
check_prereqs: None,
|
||||||
env_injector: None,
|
secrets_config: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let report = run(opts).expect("run() should return Ok even when a step fails");
|
let report = run(opts).expect("run() should return Ok even when a step fails");
|
||||||
|
|||||||
Reference in New Issue
Block a user