#![forbid(unsafe_code)]
use std::path::PathBuf;
use std::process::ExitCode;
use anyhow::{Context, bail};
use clap::{Parser, Subcommand};
use varve_core::{Pin, Store, discover, resolve};
mod docs;
#[derive(Parser)]
#[command(
name = "varve",
about = "Pinned, signed, dated toolchain bundles — one layer per release",
version
)]
struct Cli {
#[command(subcommand)]
command: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
Which {
tool: String,
},
List,
Install {
#[arg(long, value_name = "SOURCE")]
from: Option<String>,
#[arg(long, value_name = "TRIPLE")]
platform: Option<String>,
},
Verify {
#[arg(long)]
all: bool,
#[arg(long = "lockfile", value_name = "FILE")]
lockfile: Option<PathBuf>,
#[arg(long = "export", value_name = "DIR")]
export: Vec<PathBuf>,
},
Archive {
layer: String,
dest: PathBuf,
#[arg(long, value_name = "TRIPLE")]
platform: Option<String>,
},
Run {
#[arg(long, value_name = "LAYER")]
varve: Option<String>,
#[arg(trailing_var_arg = true, required = true)]
tool_and_args: Vec<std::ffi::OsString>,
},
Keygen {
#[arg(long, value_name = "FILE")]
out: PathBuf,
#[arg(long = "pub", value_name = "FILE")]
public: Option<PathBuf>,
},
Pubkey {
key: PathBuf,
},
Deposit {
#[arg(long, value_name = "FILE", conflicts_with_all = ["layer", "channel", "counter", "tools"])]
spec: Option<PathBuf>,
#[arg(long, required_unless_present = "spec")]
layer: Option<String>,
#[arg(long, required_unless_present = "spec")]
channel: Option<String>,
#[arg(long, required_unless_present = "spec")]
counter: Option<u64>,
#[arg(long, value_name = "RFC3339")]
issued_at: String,
#[arg(long, value_name = "FILE")]
key: PathBuf,
#[arg(long, default_value = "varve-root-1")]
key_id: String,
#[arg(long, value_name = "DIR")]
out: PathBuf,
#[arg(long = "tool", value_name = "NAME@VERSION=PATH")]
tools: Vec<String>,
},
ExportBazel {
#[arg(long)]
layer: Option<String>,
#[arg(long, value_name = "DIR")]
out: PathBuf,
},
ExportCargo {
#[arg(long)]
layer: Option<String>,
#[arg(long, value_name = "DIR")]
out: PathBuf,
},
ExportCratesVendor {
#[arg(long)]
layer: Option<String>,
#[arg(long, value_name = "DIR")]
out: PathBuf,
},
ExportBazelDistdir {
#[arg(long)]
layer: Option<String>,
#[arg(long, value_name = "DIR")]
out: PathBuf,
},
ExportVsix {
#[arg(long)]
layer: Option<String>,
#[arg(long, value_name = "DIR")]
out: PathBuf,
},
Sbom {
#[arg(long)]
layer: Option<String>,
#[arg(long, default_value = "cyclonedx")]
format: String,
#[arg(long, value_name = "FILE")]
out: Option<PathBuf>,
},
Status {
#[arg(long = "from-file", value_name = "ENVELOPE")]
from_file: Option<PathBuf>,
},
SignAttestation {
#[arg(long)]
layer: Option<String>,
#[arg(long)]
kind: String,
#[arg(long, value_name = "FILE")]
file: PathBuf,
#[arg(long, default_value = "varve")]
producer: String,
#[arg(long, value_name = "FILE")]
key: PathBuf,
#[arg(long, default_value = "varve-root-1")]
key_id: String,
#[arg(long, value_name = "FILE")]
out: PathBuf,
#[arg(long = "attach-to", value_name = "DIR")]
attach_to: Option<PathBuf>,
},
CheckAttestation {
#[arg(long, value_name = "FILE")]
statement: PathBuf,
#[arg(long, value_name = "FILE")]
file: PathBuf,
},
SignStatus {
#[arg(long, value_name = "FILE")]
file: PathBuf,
#[arg(long, value_name = "FILE")]
key: PathBuf,
#[arg(long, default_value = "varve-root-1")]
key_id: String,
#[arg(long, value_name = "FILE")]
out: PathBuf,
},
SignIndex {
#[arg(long, value_name = "FILE")]
file: PathBuf,
#[arg(long, value_name = "FILE")]
key: PathBuf,
#[arg(long, default_value = "varve-root-1")]
key_id: String,
#[arg(long, value_name = "FILE")]
out: PathBuf,
},
AttachIndex {
#[arg(long, value_name = "DIR")]
layout: PathBuf,
#[arg(long, value_name = "ENVELOPE")]
index: PathBuf,
},
AttachStatus {
#[arg(long, value_name = "DIR")]
layout: PathBuf,
#[arg(long, value_name = "ENVELOPE")]
status: PathBuf,
},
#[command(subcommand)]
Shim(ShimCmd),
Env {
#[arg(long, default_value = "sh")]
shell: String,
},
Completions { shell: clap_complete::Shell },
SignSums {
#[arg(long, value_name = "FILE")]
sums: PathBuf,
#[arg(long, value_name = "FILE")]
key: PathBuf,
#[arg(long, default_value = "varve-root-1")]
key_id: String,
#[arg(long, value_name = "FILE")]
out: PathBuf,
},
SelfUpdate {
#[arg(long)]
check: bool,
#[arg(long, value_name = "PATH")]
to: Option<PathBuf>,
},
SelfVerify {
#[arg(long, value_name = "FILE")]
archive: PathBuf,
#[arg(long, value_name = "FILE")]
envelope: PathBuf,
},
Docs {
topic: Option<String>,
#[arg(long)]
list: bool,
#[arg(long, value_name = "QUERY")]
grep: Option<String>,
#[arg(long)]
coverage: bool,
#[arg(long)]
strict: bool,
#[arg(long, value_name = "FMT", default_value = "text")]
format: String,
},
}
#[derive(Subcommand)]
enum ShimCmd {
Install {
#[arg(long = "tool", value_name = "NAME")]
extra_tools: Vec<String>,
},
}
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("error: {err:#}");
ExitCode::FAILURE
}
}
}
fn dispatch_tool_name(argv0: Option<&std::ffi::OsStr>) -> Option<String> {
let raw = argv0?;
let name = std::path::Path::new(raw).file_name()?.to_str()?;
let name = name.strip_suffix(".exe").unwrap_or(name);
if name.is_empty() || name == "varve" || name == ".." || name == "." {
return None;
}
if name.contains('/') || name.contains('\\') {
return None;
}
Some(name.to_string())
}
fn run() -> anyhow::Result<()> {
let mut args = std::env::args_os();
let argv0 = args.next();
if let Some(tool) = dispatch_tool_name(argv0.as_deref()) {
let store = Store::at(store_root()?);
let rest: Vec<std::ffi::OsString> = args.collect();
return run_tool(&store, None, &tool, &rest);
}
let cli = Cli::parse();
let store = Store::at(store_root()?);
match cli.command {
Cmd::Which { tool } => which(&store, &tool),
Cmd::List => list(&store),
Cmd::Install { from, platform } => install(&store, from.as_deref(), platform),
Cmd::Verify {
all,
export,
lockfile,
} => verify(&store, all, &export, lockfile.as_deref()),
Cmd::Archive {
layer,
dest,
platform,
} => archive(&store, &layer, &dest, platform),
Cmd::Run {
varve,
tool_and_args,
} => {
let (tool, args) = tool_and_args
.split_first()
.context("no tool named — usage: varve run [--varve LAYER] -- <tool> [args…]")?;
let tool = tool
.to_str()
.context("tool name must be valid UTF-8 — it names an entry in the layer")?;
run_tool(&store, varve.as_deref(), tool, args)
}
Cmd::Deposit {
spec,
layer,
channel,
counter,
issued_at,
key,
key_id,
out,
tools,
} => deposit_cmd(
spec.as_deref(),
layer.as_deref(),
channel.as_deref(),
counter,
&issued_at,
&key,
&key_id,
&out,
&tools,
),
Cmd::ExportBazel { layer, out } => export_bazel(&store, layer.as_deref(), &out),
Cmd::ExportCargo { layer, out } => export_cargo(&store, layer.as_deref(), &out),
Cmd::ExportCratesVendor { layer, out } => {
export_crates_vendor(&store, layer.as_deref(), &out)
}
Cmd::ExportBazelDistdir { layer, out } => {
export_bazel_distdir(&store, layer.as_deref(), &out)
}
Cmd::ExportVsix { layer, out } => export_vsix(&store, layer.as_deref(), &out),
Cmd::Sbom { layer, format, out } => {
sbom_cmd(&store, layer.as_deref(), &format, out.as_deref())
}
Cmd::Status { from_file } => status(&store, from_file.as_deref()),
Cmd::SignStatus {
file,
key,
key_id,
out,
} => sign_status(&file, &key, &key_id, &out),
Cmd::Keygen { out, public } => keygen(&out, public.as_deref()),
Cmd::Pubkey { key } => pubkey(&key),
Cmd::SignAttestation {
layer,
kind,
file,
producer,
key,
key_id,
out,
attach_to,
} => sign_attestation(
&store,
layer.as_deref(),
&kind,
&file,
&producer,
&key,
&key_id,
&out,
attach_to.as_deref(),
),
Cmd::CheckAttestation { statement, file } => check_attestation(&store, &statement, &file),
Cmd::AttachStatus { layout, status } => attach_status(&layout, &status),
Cmd::SignIndex {
file,
key,
key_id,
out,
} => sign_index(&file, &key, &key_id, &out),
Cmd::AttachIndex { layout, index } => attach_index(&layout, &index),
Cmd::Shim(ShimCmd::Install { extra_tools }) => shim_install(&store, &extra_tools),
Cmd::Env { shell } => print_env(&store, &shell),
Cmd::Completions { shell } => {
use clap::CommandFactory;
clap_complete::generate(shell, &mut Cli::command(), "varve", &mut std::io::stdout());
Ok(())
}
Cmd::SignSums {
sums,
key,
key_id,
out,
} => sign_sums(&sums, &key, &key_id, &out),
Cmd::SelfUpdate { check, to } => self_update(check, to.as_deref()),
Cmd::SelfVerify { archive, envelope } => self_verify(&archive, &envelope),
Cmd::Docs {
topic,
list,
grep,
coverage,
strict,
format,
} => docs_cmd(
topic.as_deref(),
list,
grep.as_deref(),
coverage,
strict,
&format,
),
}
}
fn env_script_sh(shim_dir: &std::path::Path) -> String {
format!(
"case \":$PATH:\" in\n *:\"{dir}\":*) ;;\n *) export PATH=\"{dir}:$PATH\" ;;\nesac\n",
dir = shim_dir.display()
)
}
fn print_env(store: &Store, shell: &str) -> anyhow::Result<()> {
let shim_dir = store.root().join("shims");
match shell {
"sh" | "bash" | "zsh" => print!("{}", env_script_sh(&shim_dir)),
"fish" => println!(
"if not contains \"{dir}\" $PATH\n set -gx PATH \"{dir}\" $PATH\nend",
dir = shim_dir.display()
),
other => bail!("unknown shell '{other}' — supported: sh, bash, zsh, fish"),
}
Ok(())
}
fn shim_install(store: &Store, extra_tools: &[String]) -> anyhow::Result<()> {
let ctx = project_ctx(store)?;
let resolved = resolve(&ctx.pin, &ctx.store)?;
let mut names: Vec<String> = resolved.tools.iter().map(|(n, _)| n.clone()).collect();
names.extend(extra_tools.iter().cloned());
names.sort();
names.dedup();
if names.contains(&"varve".to_string()) {
bail!("refusing to shim 'varve' itself — a shim that resolves through itself recurses");
}
let varve_exe = std::env::current_exe().context("cannot locate the varve binary")?;
let shim_dir = store.root().join("shims");
std::fs::create_dir_all(&shim_dir)
.with_context(|| format!("cannot create {}", shim_dir.display()))?;
for name in &names {
let path = shim_dir.join(name);
match std::fs::remove_file(&path) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => {
return Err(e).with_context(|| format!("cannot replace {}", path.display()));
}
}
#[cfg(unix)]
std::os::unix::fs::symlink(&varve_exe, &path).with_context(|| {
format!("cannot link {} -> {}", path.display(), varve_exe.display())
})?;
#[cfg(not(unix))]
std::fs::copy(&varve_exe, &path)
.with_context(|| format!("cannot copy varve to {}", path.display()))?;
}
let env_file = store.root().join("env");
std::fs::write(&env_file, env_script_sh(&shim_dir))
.with_context(|| format!("cannot write {}", env_file.display()))?;
println!(
"installed {} shim(s) in {} — switching projects is cd\n\nadd ONE line to your shell config:\n . \"{}\" # or: eval \"$(varve env)\"",
names.len(),
shim_dir.display(),
env_file.display()
);
Ok(())
}
fn sign_sums(
sums: &std::path::Path,
key: &std::path::Path,
key_id: &str,
out: &std::path::Path,
) -> anyhow::Result<()> {
let bytes = std::fs::read(sums).with_context(|| format!("cannot read {}", sums.display()))?;
let hex_key = std::fs::read_to_string(key)
.with_context(|| format!("cannot read signing key {}", key.display()))?;
let sk = varve_core::keys::check_keypair(&hex_key, &key.display().to_string())?;
let envelope = varve_core::sign_release_sums(&bytes, &sk, key_id)?;
std::fs::write(out, envelope).with_context(|| format!("cannot write {}", out.display()))?;
println!("signed release sums -> {}", out.display());
Ok(())
}
fn status(store: &Store, from_file: Option<&std::path::Path>) -> anyhow::Result<()> {
let ctx = project_ctx(store)?;
let pin = &ctx.pin;
let root_pk = ctx_root_bytes(&ctx)?;
let line = pin.layer.line().clone();
let cache = varve_core::StatusCache::at_root(ctx.store.root());
if let Some(path) = from_file {
let envelope = std::fs::read(path)
.with_context(|| format!("cannot read status envelope {}", path.display()))?;
let doc = varve_core::LineStatus::verify_and_parse(&envelope, &root_pk)?;
if doc.line != line.to_string() {
bail!(
"status document covers line {} but this project pins line {line}",
doc.line
);
}
cache.update(&line, &envelope, &doc)?;
}
let Some(doc) = cache.load(&line, &root_pk)? else {
bail!(
"no line-status document cached for line {line}.\n\
\n\
Line-status carries the support window, known problems, and yank state \
for the line. `varve install` caches it automatically when the installed \
layer's oci-layout carries one; otherwise ingest a signed envelope \
explicitly:\n\
\n varve status --from-file <line-status.dsse.json>\n\
\n\
Distribution of line-status via the registry is tracked in \
REQ-STATUS-DIST-001 (pulseengine/varve#34)."
);
};
if doc.line != line.to_string() {
bail!(
"cached status document covers line {} but this project pins line {line}",
doc.line
);
}
let report = doc.report_for(&pin.layer);
println!(
"layer {} (line {line}, status document #{})",
pin.layer, doc.counter
);
match &report.yanked_reason {
Some(reason) => println!(" YANKED: {reason}"),
None => println!(" not yanked"),
}
match &report.support_until {
Some(until) => println!(" supported until {until}"),
None => println!(" no stated support window"),
}
println!(
" {} known problem(s) affect this layer, {} with workarounds",
report.problems_total, report.problems_with_workaround
);
Ok(())
}
fn attach_status(layout: &std::path::Path, status: &std::path::Path) -> anyhow::Result<()> {
let envelope = std::fs::read(status)
.with_context(|| format!("cannot read status envelope {}", status.display()))?;
let (line, counter) = varve_core::attach_status_envelope_to_layout(layout, &envelope)?;
println!(
"attached baseline line-status #{counter} for line {line} to {}",
layout.display()
);
Ok(())
}
fn attach_index(layout: &std::path::Path, index: &std::path::Path) -> anyhow::Result<()> {
let envelope = std::fs::read(index)
.with_context(|| format!("cannot read index envelope {}", index.display()))?;
let (line, counter) = varve_core::attach_index_envelope_to_layout(layout, &envelope)?;
println!(
"attached signed line-index #{counter} for line {line} to {}",
layout.display()
);
Ok(())
}
fn sign_index(
file: &std::path::Path,
key: &std::path::Path,
key_id: &str,
out: &std::path::Path,
) -> anyhow::Result<()> {
let bytes = std::fs::read(file)
.with_context(|| format!("cannot read index document {}", file.display()))?;
let doc: varve_core::LineIndex =
serde_json::from_slice(&bytes).context("index document does not match the schema")?;
doc.line()?;
let hex_key = std::fs::read_to_string(key)
.with_context(|| format!("cannot read signing key {}", key.display()))?;
let sk = varve_core::keys::check_keypair(&hex_key, &key.display().to_string())?;
let envelope = doc.sign(&sk, key_id)?;
std::fs::write(out, envelope)
.with_context(|| format!("cannot write envelope {}", out.display()))?;
println!(
"signed line-index #{} for line {} ({} layer(s)) -> {}",
doc.counter,
doc.line,
doc.layers.len(),
out.display()
);
Ok(())
}
fn sign_status(
file: &std::path::Path,
key: &std::path::Path,
key_id: &str,
out: &std::path::Path,
) -> anyhow::Result<()> {
let bytes = std::fs::read(file)
.with_context(|| format!("cannot read status document {}", file.display()))?;
let doc: varve_core::LineStatus =
serde_json::from_slice(&bytes).context("status document does not match the schema")?;
let hex_key = std::fs::read_to_string(key)
.with_context(|| format!("cannot read signing key {}", key.display()))?;
let sk = varve_core::keys::check_keypair(&hex_key, &key.display().to_string())?;
let envelope = doc.sign(&sk, key_id)?;
std::fs::write(out, envelope)
.with_context(|| format!("cannot write envelope {}", out.display()))?;
println!(
"signed line-status #{} for line {} -> {}",
doc.counter,
doc.line,
out.display()
);
Ok(())
}
fn self_update(check: bool, to: Option<&std::path::Path>) -> anyhow::Result<()> {
let current = env!("CARGO_PKG_VERSION");
let api = std::env::var("VARVE_UPDATE_API").unwrap_or_else(|_| {
"https://api.github.com/repos/pulseengine/varve/releases/latest".to_string()
});
let platform = varve_core::host_platform();
let dest = match to {
Some(path) => path.to_path_buf(),
None => std::env::current_exe().context("cannot locate the running varve binary")?,
};
let on_disk = std::fs::read(&dest).ok();
if varve_core::update::check_latest(&api, current, &platform)?.is_none() {
println!("varve {current} is current");
return Ok(());
}
let root_pk = trust_root_bytes().context(
"self-update needs the trust root to confirm a verified update — set VARVE_TRUST_ROOT \
or pin a realm",
)?;
match varve_core::update::resolve_update(
&api,
current,
&platform,
on_disk.as_deref(),
&root_pk,
)? {
varve_core::update::UpdateDecision::UpToDate => {
println!("varve {current} is current");
}
varve_core::update::UpdateDecision::AlreadyCurrent { latest } => {
println!(
"varve is already the latest release bytes ({latest}); the reported version \
{current} is stale but the binary is current — nothing to do"
);
}
varve_core::update::UpdateDecision::Available {
plan,
binary,
digest,
} => {
if check {
println!(
"varve {current} installed; {} available — run `varve self-update` to install \
(verified)",
plan.latest
);
} else {
varve_core::update::install_binary(&binary, &dest)?;
println!(
"varve {current} -> {} installed at {} ({digest})",
plan.latest,
dest.display()
);
}
}
}
Ok(())
}
fn docs_cmd(
topic: Option<&str>,
list: bool,
grep: Option<&str>,
coverage: bool,
strict: bool,
format: &str,
) -> anyhow::Result<()> {
use clap::CommandFactory;
let json = match format {
"text" => false,
"json" => true,
other => bail!("unknown --format '{other}' (expected `text` or `json`)"),
};
if coverage || topic == Some("check") {
let gaps = docs::coverage_gaps(&Cli::command());
let missing_topics = docs::missing_required_topics();
let bare_topics = docs::topics_without_examples();
if gaps.is_empty() && missing_topics.is_empty() && bare_topics.is_empty() {
println!(
"docs coverage: OK — {} subcommands documented, {} workflow topic(s) present, \
{} topic(s) carry a worked example",
Cli::command().get_subcommands().count(),
docs::REQUIRED_TOPICS.len(),
docs::TOPICS_NEEDING_EXAMPLES.len()
);
return Ok(());
}
if !missing_topics.is_empty() {
eprintln!(
"docs coverage: {} workflow topic(s) missing — a user cannot do the job \
without them:",
missing_topics.len()
);
for m in &missing_topics {
eprintln!(" {m}");
}
}
if !bare_topics.is_empty() {
eprintln!(
"docs coverage: {} topic(s) must SHOW a literal example, not describe one:",
bare_topics.len()
);
for b in &bare_topics {
eprintln!(" {b}");
}
}
if gaps.is_empty() {
if strict {
bail!(
"documentation gaps (REQ-DOCS-003): {} missing topic(s), {} without an \
example",
missing_topics.len(),
bare_topics.len()
);
}
return Ok(());
}
eprintln!("docs coverage: {} subcommand(s) undocumented:", gaps.len());
for g in &gaps {
eprintln!(" {g}");
}
if strict {
bail!(
"undocumented subcommands (REQ-DOCS-001): {}{}",
gaps.join(", "),
if missing_topics.is_empty() && bare_topics.is_empty() {
String::new()
} else {
format!(
" — plus {} missing workflow topic(s) and {} without an example \
(REQ-DOCS-003)",
missing_topics.len(),
bare_topics.len()
)
}
);
}
return Ok(());
}
if let Some(q) = grep {
let hits = docs::grep(q);
if hits.is_empty() {
println!("no topic matches '{q}'");
}
for (slug, line) in hits {
println!("{slug}: {line}");
}
return Ok(());
}
if list || topic.is_none() {
if json {
println!("{}", docs::render_json(None));
} else {
print!("{}", docs::render_list());
}
return Ok(());
}
let slug = topic.unwrap();
match docs::find(slug) {
Some(_) if json => println!("{}", docs::render_json(Some(slug))),
Some(t) => println!("{}", t.body.trim_end()),
None => {
eprintln!("no topic '{slug}'.\n");
print!("{}", docs::render_list());
bail!("unknown topic: {slug}");
}
}
Ok(())
}
fn self_verify(archive: &std::path::Path, envelope: &std::path::Path) -> anyhow::Result<()> {
let root_pk = trust_root_bytes()?;
let name = archive
.file_name()
.context("archive path has no file name")?
.to_string_lossy();
let bytes =
std::fs::read(archive).with_context(|| format!("cannot read {}", archive.display()))?;
let env_bytes =
std::fs::read(envelope).with_context(|| format!("cannot read {}", envelope.display()))?;
let digest = varve_core::verify_release_file(&name, &bytes, &env_bytes, &root_pk)?;
println!("{name} verified against the signed release sums ({digest})");
Ok(())
}
fn run_tool(
store: &Store,
override_layer: Option<&str>,
tool: &str,
args: &[std::ffi::OsString],
) -> anyhow::Result<()> {
let ctx = project_ctx(store)?;
let mut pin = ctx.pin;
if let Some(layer) = override_layer {
pin.layer = layer.parse()?;
pin.digest = None;
}
let resolved = resolve(&pin, &ctx.store)?;
let Some((_, path)) = resolved.tools.iter().find(|(name, _)| name == tool) else {
bail!(
"tool '{tool}' is not part of layer {} — it exposes: {}",
resolved.layer.layer,
resolved
.tools
.iter()
.map(|(n, _)| n.as_str())
.collect::<Vec<_>>()
.join(", ")
);
};
let mut cmd = if let Some(contract) = resolved.runners.get(tool) {
let Some((_, runner_path)) = resolved.tools.iter().find(|(n, _)| n == &contract.tool)
else {
bail!(
"tool '{tool}' runs via '{runner}' but the layer does not expose it — refusing",
runner = contract.tool
);
};
let mut cmd = std::process::Command::new(runner_path);
cmd.args(&contract.args).arg(path);
for arg in args {
if let Some(prefix) = &contract.arg_prefix {
cmd.arg(prefix);
}
cmd.arg(arg);
}
cmd
} else {
let mut cmd = std::process::Command::new(path);
cmd.args(args);
cmd
};
cmd.env("VARVE_LAYER", resolved.layer.layer.to_string())
.env("VARVE_LAYER_MANIFEST_DIGEST", &resolved.layer.digest);
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
Err(anyhow::Error::from(cmd.exec()).context(format!("failed to exec {tool}")))
}
#[cfg(not(unix))]
{
let status = cmd
.status()
.with_context(|| format!("failed to run {tool}"))?;
std::process::exit(status.code().unwrap_or(1));
}
}
fn export_target(
base: &Store,
layer: Option<&str>,
) -> anyhow::Result<(Store, varve_core::store::InstalledLayer)> {
match layer {
Some(l) => {
let (store, verifier) = match project_ctx(base) {
Ok(ctx) => {
let v = ctx_verifier(&ctx)?;
(ctx.store, v)
}
Err(_) => (base.clone(), trust_root()?),
};
let wanted: varve_core::LayerId = l.parse()?;
let entry = store
.list()?
.into_iter()
.find(|e| e.layer == wanted)
.with_context(|| format!("layer {l} is not installed — varve install it first"))?;
varve_core::verify_installed(&store, &entry, &verifier, &varve_core::host_platform())?;
Ok((store, entry))
}
None => {
let ctx = project_ctx(base)?;
let verifier = ctx_verifier(&ctx)?;
let resolved = varve_core::resolve(&ctx.pin, &ctx.store)?;
varve_core::verify_installed(
&ctx.store,
&resolved.layer,
&verifier,
&varve_core::host_platform(),
)?;
Ok((ctx.store, resolved.layer))
}
}
}
fn write_export_stamp(
out: &std::path::Path,
entry: &varve_core::store::InstalledLayer,
kind: &str,
) -> anyhow::Result<()> {
let stamp = varve_core::exportstamp::ExportStamp {
layer: entry.layer.to_string(),
manifest_digest: entry.digest.clone(),
kind: kind.to_string(),
};
varve_core::exportstamp::write_stamp(out, &stamp)?;
println!(
"stamped {} — export bound to layer {} ({}); `varve verify --export {}` checks it",
out.join(varve_core::exportstamp::STAMP_FILE).display(),
entry.layer,
entry.digest,
out.display(),
);
Ok(())
}
fn export_bazel(store: &Store, layer: Option<&str>, out: &std::path::Path) -> anyhow::Result<()> {
let (_store, entry) = export_target(store, layer)?;
let payload = std::fs::read(entry.root.join("layer.json"))?;
let manifest = varve_core::LayerManifest::parse(&payload)?;
let export = varve_core::bazel::export(&manifest);
std::fs::create_dir_all(out)?;
for (tool, json) in &export.registries {
let path = out.join(format!("{tool}.json"));
std::fs::write(&path, serde_json::to_vec_pretty(json)?)?;
println!("wrote {}", path.display());
}
for (tool, platform, reason) in &export.skipped {
eprintln!("skipped {tool} ({platform}): {reason}");
}
if export.registries.is_empty() {
bail!(
"nothing exported — no entry in layer {} carries source provenance",
entry.layer
);
}
write_export_stamp(out, &entry, "bazel-registry")?;
Ok(())
}
struct VerifiedPayload {
name: String,
version: String,
digest: String,
bytes: Vec<u8>,
}
fn collect_verified_payloads(
store: &Store,
entry: &varve_core::store::InstalledLayer,
want: varve_core::PayloadKind,
) -> anyhow::Result<Vec<VerifiedPayload>> {
let payload = std::fs::read(entry.root.join("layer.json"))?;
let manifest = varve_core::LayerManifest::parse(&payload)?;
let kind = want.as_str();
let mut found = Vec::new();
for e in &manifest.entries {
if e.kind().map_err(|err| anyhow::anyhow!(err.to_string()))? != want {
continue;
}
let name = e
.annotations
.get("eu.pulseengine.tool")
.with_context(|| format!("{kind} entry missing its name annotation"))?;
let version = e
.annotations
.get("eu.pulseengine.tool.version")
.with_context(|| format!("{kind} entry missing its version annotation"))?;
let bytes_path = store.entry_path(entry, e).with_context(|| {
format!("{kind} '{name}' version {version} is not present in the store")
})?;
let bytes = std::fs::read(&bytes_path)?;
if varve_core::manifest_digest(&bytes) != e.digest {
bail!(
"{kind} '{name}' version {version} on-disk bytes do not match the signed \
digest {}",
e.digest
);
}
found.push(VerifiedPayload {
name: name.clone(),
version: version.clone(),
digest: e.digest.clone(),
bytes,
});
}
if found.is_empty() {
bail!(
"nothing exported — layer {} carries no `{kind}` entries",
entry.layer
);
}
Ok(found)
}
fn collect_verified_crates(
store: &Store,
entry: &varve_core::store::InstalledLayer,
) -> anyhow::Result<Vec<varve_core::crateexport::CrateEntry>> {
let mut crates = Vec::new();
for p in collect_verified_payloads(store, entry, varve_core::PayloadKind::Crate)? {
let cksum = p
.digest
.strip_prefix("sha256:")
.with_context(|| format!("crate '{}' digest is not sha256:<hex>", p.name))?
.to_string();
crates.push(varve_core::crateexport::CrateEntry {
name: p.name,
version: p.version,
cksum,
bytes: p.bytes,
});
}
Ok(crates)
}
#[allow(clippy::too_many_arguments)]
fn keygen(out: &std::path::Path, public: Option<&std::path::Path>) -> anyhow::Result<()> {
if out.exists() {
bail!(
"{} already exists — refusing to overwrite a signing key. Move it aside first.",
out.display()
);
}
if let Some(p) = public
&& p.exists()
{
bail!(
"{} already exists — refusing to overwrite a published trust root. \
Re-print it instead with `varve pubkey <key>`, or choose another path.",
p.display()
);
}
let (secret, pub_hex) = varve_core::keys::generate();
std::fs::write(out, format!("{secret}\n"))
.with_context(|| format!("cannot write {}", out.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(out, std::fs::Permissions::from_mode(0o600))?;
}
match public {
Some(p) => {
std::fs::write(p, format!("{pub_hex}\n"))
.with_context(|| format!("cannot write {}", p.display()))?;
println!(
"signing key -> {} (KEEP SECRET, mode 0600)\npublic half -> {}\n\n\
Consumers pin the public half as a realm's trust-root:\n\n \
[realm.<your-realm>]\n registry = \"oci://<your registry>\"\n \
trust-root = \"{pub_hex}\"\n\n\
Sign layers with: varve deposit --key {} …",
out.display(),
p.display(),
out.display()
);
}
None => {
println!(
"signing key -> {} (KEEP SECRET, mode 0600)\n\n\
Its public half — what consumers pin as a realm's trust-root:\n\n {pub_hex}\n\n\
Re-print it any time with: varve pubkey {}",
out.display(),
out.display()
);
}
}
Ok(())
}
fn pubkey(key: &std::path::Path) -> anyhow::Result<()> {
let text = std::fs::read_to_string(key)
.with_context(|| format!("cannot read signing key {}", key.display()))?;
let public = varve_core::keys::public_from_secret(&text, &key.display().to_string())?;
println!("{public}");
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn sign_attestation(
store: &Store,
layer: Option<&str>,
kind: &str,
file: &std::path::Path,
producer: &str,
key: &std::path::Path,
key_id: &str,
out: &std::path::Path,
attach_to: Option<&std::path::Path>,
) -> anyhow::Result<()> {
let kind: varve_core::attest::AttestationKind = kind.parse()?;
let (_store, entry) = export_target(store, layer)?;
let bytes = std::fs::read(file)
.with_context(|| format!("cannot read attestation {}", file.display()))?;
let st = varve_core::attest::statement(
&entry.layer.to_string(),
&entry.digest,
kind,
&bytes,
producer,
);
let hex_key = std::fs::read_to_string(key)
.with_context(|| format!("cannot read signing key {}", key.display()))?;
let sk = varve_core::keys::check_keypair(&hex_key, &key.display().to_string())?;
let envelope = varve_core::attest::sign(&st, &sk, key_id)?;
std::fs::write(out, &envelope).with_context(|| format!("cannot write {}", out.display()))?;
if let Some(layout) = attach_to {
varve_core::attestcarry::attach(layout, envelope.as_bytes(), &bytes).with_context(
|| {
format!(
"cannot attach the attestation to layout {}",
layout.display()
)
},
)?;
}
println!(
"signed a {kind} attestation statement by {producer} -> {out}\n\
\u{20}\u{20}attestation digest : {adigest}\n\
\u{20}\u{20}bound to layer : {layer} ({ldigest})\n\
varve vouches that these bytes accompany this layer; what {producer} claims is \
{producer}'s to prove.",
out = out.display(),
adigest = st.digest,
layer = entry.layer,
ldigest = st.layer_manifest_digest,
);
if let Some(layout) = attach_to {
println!(
"attached to layout {} as referrer artifacts — the statement AND the bytes, so it \
travels with the layer through a registry push, `varve archive`, and an offline \
install",
layout.display()
);
}
Ok(())
}
fn check_attestation(
store: &Store,
statement: &std::path::Path,
file: &std::path::Path,
) -> anyhow::Result<()> {
let ctx = project_ctx(store)?;
let root = ctx_root_bytes(&ctx)?;
let (_store, entry) = export_target(store, None)?;
let envelope = std::fs::read(statement)
.with_context(|| format!("cannot read statement {}", statement.display()))?;
let bytes = std::fs::read(file)
.with_context(|| format!("cannot read attestation {}", file.display()))?;
let st = varve_core::attest::verify_statement(&envelope, &root)?;
varve_core::attest::check(&st, &bytes, &entry.digest, &entry.layer.to_string())?;
println!(
"attestation OK: a {kind} document produced by {producer}, {n} bytes\n\
\u{20}\u{20}attestation digest : {adigest}\n\
\u{20}\u{20}bound to layer : {layer} ({ldigest})\n\
note: varve verified the ASSOCIATION and the bytes' integrity, and re-verified the \
layer itself. Any claim {producer} makes INSIDE the document is verified with \
{producer}'s own key, not this one.",
kind = st.kind,
producer = st.producer,
n = bytes.len(),
adigest = st.digest,
layer = entry.layer,
ldigest = st.layer_manifest_digest,
);
Ok(())
}
fn sbom_cmd(
store: &Store,
layer: Option<&str>,
format: &str,
out: Option<&std::path::Path>,
) -> anyhow::Result<()> {
let format: varve_core::sbom::SbomFormat =
format.parse().map_err(|e: String| anyhow::anyhow!(e))?;
let (_store, entry) = export_target(store, layer)?;
let payload = std::fs::read(entry.root.join("layer.json"))?;
let manifest = varve_core::LayerManifest::parse(&payload)?;
let doc = varve_core::sbom::emit(&manifest, &entry.digest, format);
match out {
Some(path) => {
std::fs::write(path, &doc)
.with_context(|| format!("cannot write {}", path.display()))?;
let written = serde_json::from_str::<serde_json::Value>(&doc)
.ok()
.and_then(|v| v["components"].as_array().map(|a| a.len()))
.unwrap_or_default();
println!(
"wrote an SBOM for layer {} ({written} component(s), from {} signed entries) to \
{} — transcribed from the signed manifest, not scanned",
entry.layer,
manifest.entries.len(),
path.display()
);
}
None => println!("{doc}"),
}
Ok(())
}
fn absolute_export_dir(out: &std::path::Path) -> anyhow::Result<std::path::PathBuf> {
std::fs::create_dir_all(out).with_context(|| format!("cannot create {}", out.display()))?;
out.canonicalize()
.with_context(|| format!("cannot resolve {} to an absolute path", out.display()))
}
fn export_cargo(store: &Store, layer: Option<&str>, out: &std::path::Path) -> anyhow::Result<()> {
let (store, entry) = export_target(store, layer)?;
let out = &absolute_export_dir(out)?;
let crates = collect_verified_crates(&store, &entry)?;
let registry_dir = out.join("registry");
let n = varve_core::crateexport::export_local_registry(&crates, ®istry_dir)?;
let cargo_dir = out.join(".cargo");
std::fs::create_dir_all(&cargo_dir)?;
let config = cargo_dir.join("config.toml");
std::fs::write(
&config,
varve_core::crateexport::cargo_config_toml(®istry_dir),
)?;
println!(
"exported {n} verified crate(s) to a local registry at {} — copy {} into \
.cargo/config.toml and `cargo build --offline`",
registry_dir.display(),
config.display()
);
write_export_stamp(out, &entry, "cargo")?;
Ok(())
}
fn export_bazel_distdir(
store: &Store,
layer: Option<&str>,
out: &std::path::Path,
) -> anyhow::Result<()> {
let (store, entry) = export_target(store, layer)?;
let crates = collect_verified_crates(&store, &entry)?;
let n = varve_core::crateexport::export_distdir(&crates, out)?;
println!(
"wrote {n} verified .crate tarball(s) to the Bazel distdir {} — with a pre-generated \
crate_universe output, `bazel build --distdir={}` resolves them offline by sha256",
out.display(),
out.display()
);
write_export_stamp(out, &entry, "bazel-distdir")?;
Ok(())
}
fn export_vsix(store: &Store, layer: Option<&str>, out: &std::path::Path) -> anyhow::Result<()> {
let (store, entry) = export_target(store, layer)?;
let out = &absolute_export_dir(out)?;
let extensions: Vec<varve_core::VsixEntry> =
collect_verified_payloads(&store, &entry, varve_core::PayloadKind::Vsix)?
.into_iter()
.map(|p| varve_core::VsixEntry {
name: p.name,
version: p.version,
bytes: p.bytes,
})
.collect();
let n = varve_core::export_vsix(&extensions, out)?;
println!(
"exported {n} verified VS Code extension(s) to {} — install them with:",
out.display()
);
for e in &extensions {
println!(
" code --install-extension {}",
out.join(varve_core::vsixexport::vsix_file_name(&e.name, &e.version))
.display()
);
}
write_export_stamp(out, &entry, "vsix")?;
Ok(())
}
fn export_crates_vendor(
store: &Store,
layer: Option<&str>,
out: &std::path::Path,
) -> anyhow::Result<()> {
let (store, entry) = export_target(store, layer)?;
let out = &absolute_export_dir(out)?;
let crates = collect_verified_crates(&store, &entry)?;
let vendor_dir = out.join("vendor");
let n = varve_core::crateexport::export_vendor_dir(&crates, &vendor_dir)?;
let cargo_dir = out.join(".cargo");
std::fs::create_dir_all(&cargo_dir)?;
let config = cargo_dir.join("config.toml");
std::fs::write(
&config,
varve_core::crateexport::vendored_config_toml(&vendor_dir),
)?;
println!(
"vendored {n} verified crate(s) to {} — a cargo-vendor tree bare Cargo and Corrosion \
build against offline; copy {} into .cargo/config.toml (rules_rust needs BUILD files \
on top — not yet emitted, REQ-VENDOR-002)",
vendor_dir.display(),
config.display()
);
write_export_stamp(out, &entry, "crates-vendor")?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn deposit_cmd(
spec: Option<&std::path::Path>,
layer: Option<&str>,
channel: Option<&str>,
counter: Option<u64>,
issued_at: &str,
key: &std::path::Path,
key_id: &str,
out: &std::path::Path,
tools: &[String],
) -> anyhow::Result<()> {
if let Some(spec_path) = spec {
let text = std::fs::read_to_string(spec_path)
.with_context(|| format!("cannot read spec {}", spec_path.display()))?;
let file_spec = varve_core::parse_deposit_spec(&text)?;
let base = spec_path.parent().unwrap_or(std::path::Path::new("."));
let mut deposit_tools = Vec::new();
for tool in file_spec.tools {
let path = base.join(&tool.path);
let bytes = std::fs::read(&path)
.with_context(|| format!("cannot read tool binary {}", path.display()))?;
let kind = tool
.kind
.as_deref()
.map(str::parse)
.transpose()
.map_err(|e: varve_core::UnknownKind| anyhow::anyhow!(e.to_string()))?;
deposit_tools.push(varve_core::DepositTool {
name: tool.name,
version: tool.version,
platform: tool.platform,
bytes,
source: tool.source,
runner: tool.runner,
kind,
});
}
let includes = file_spec
.includes
.into_iter()
.map(|i| varve_core::deposit::DepositInclude {
digest: i.digest,
realm: i.realm,
layer: i.layer,
})
.collect();
return run_deposit(
&file_spec.layer,
&file_spec.channel,
file_spec.counter,
issued_at,
key,
key_id,
out,
deposit_tools,
includes,
);
}
let (layer, channel, counter) = (
layer.context("--layer required without --spec")?,
channel.context("--channel required without --spec")?,
counter.context("--counter required without --spec")?,
);
let mut deposit_tools = Vec::new();
for spec in tools {
let (name_version, path) = spec
.split_once('=')
.with_context(|| format!("--tool '{spec}' is not NAME@VERSION=PATH"))?;
let mut parts = name_version.split('@');
let name = parts.next().unwrap_or_default();
let version = parts
.next()
.with_context(|| format!("--tool '{spec}' is not NAME@VERSION[@PLATFORM]=PATH"))?;
let platform = parts.next();
let bytes =
std::fs::read(path).with_context(|| format!("cannot read tool binary {path}"))?;
deposit_tools.push(varve_core::DepositTool {
name: name.to_string(),
version: version.to_string(),
platform: platform.map(str::to_string),
bytes,
source: None,
runner: None,
kind: None,
});
}
run_deposit(
layer,
channel,
counter,
issued_at,
key,
key_id,
out,
deposit_tools,
Vec::new(),
)
}
#[allow(clippy::too_many_arguments)]
fn run_deposit(
layer: &str,
channel: &str,
counter: u64,
issued_at: &str,
key: &std::path::Path,
key_id: &str,
out: &std::path::Path,
deposit_tools: Vec<varve_core::DepositTool>,
includes: Vec<varve_core::deposit::DepositInclude>,
) -> anyhow::Result<()> {
let out_str = out.to_string_lossy();
if let Some(scheme) = out_str.split_once("://").map(|(s, _)| s.to_string()) {
bail!(
"--out {out_str} looks like a {scheme} registry reference, but deposit writes a \
LOCAL oci-layout directory. Deposit to a directory, then push that directory to \
the registry with your OCI client (e.g. `oras cp --from-oci-layout <dir>:<tag> \
{out_str}`)."
);
}
let hex_key = std::fs::read_to_string(key)
.with_context(|| format!("cannot read signing key {}", key.display()))?;
let sk = varve_core::keys::check_keypair(&hex_key, &key.display().to_string())?;
let spec = varve_core::DepositSpec {
includes,
layer: layer.parse()?,
channel: channel.to_string(),
counter,
issued_at: issued_at.to_string(),
tools: deposit_tools,
};
let outcome = varve_core::deposit(&spec, &sk, key_id, out)?;
println!(
"deposited layer {} (counter {}) {} at {}",
outcome.layer,
outcome.counter,
outcome.digest,
out.display()
);
Ok(())
}
fn archive(
store: &Store,
layer: &str,
dest: &std::path::Path,
platform: Option<String>,
) -> anyhow::Result<()> {
let platform = platform.unwrap_or_else(varve_core::host_platform);
let store = match project_ctx(store) {
Ok(ctx) => ctx.store,
Err(_) => store.clone(),
};
let wanted: varve_core::LayerId = layer.parse()?;
let matching: Vec<_> = store
.list()?
.into_iter()
.filter(|entry| entry.layer == wanted)
.collect();
let entry = match matching.len() {
0 => bail!(
"layer {layer} is not installed — run `varve install` in a project pinning it, then archive"
),
1 => matching.into_iter().next().expect("len checked"),
n => bail!(
"layer {layer} is installed {n} times under different digests — archive by digest is \
not supported yet; clean up the core first"
),
};
let summary = varve_core::export_archive(&store, &entry, dest, &platform)?;
println!(
"archived layer {} {} as oci-layout at {}",
entry.layer,
entry.digest,
dest.display()
);
println!(
" {} payload{} for {}",
summary.archived,
if summary.archived == 1 { "" } else { "s" },
summary.platform
);
if !summary.omitted.is_empty() {
let total: usize = summary.omitted.values().sum();
let detail = summary
.omitted
.iter()
.map(|(p, n)| format!("{p} ({n})"))
.collect::<Vec<_>>()
.join(", ");
println!(
" {total} entr{} omitted — this core holds no payload for them: {detail}\n \
this archive installs on {} only; for another platform, install and archive the \
layer there.",
if total == 1 { "y" } else { "ies" },
summary.platform
);
}
Ok(())
}
struct ProjectCtx {
pin: Pin,
store: Store,
realm: Option<varve_core::Realm>,
}
fn project_ctx(base: &Store) -> anyhow::Result<ProjectCtx> {
let pin = load_pin()?;
match &pin.realm {
Some(name) => {
let cwd = std::env::current_dir().context("cannot determine working directory")?;
let realm = varve_core::resolve_realm(&cwd, name)?;
let store = Store::at(realm.effective_root(base.root()));
Ok(ProjectCtx {
pin,
store,
realm: Some(realm),
})
}
None => Ok(ProjectCtx {
pin,
store: base.clone(),
realm: None,
}),
}
}
fn ctx_root_bytes(ctx: &ProjectCtx) -> anyhow::Result<Vec<u8>> {
match &ctx.realm {
Some(realm) => Ok(realm.trust_root.clone()),
None => trust_root_bytes(),
}
}
fn ctx_verifier(ctx: &ProjectCtx) -> anyhow::Result<varve_core::PinnedKeyVerifier> {
varve_core::PinnedKeyVerifier::from_public_key_bytes(&ctx_root_bytes(ctx)?)
.map_err(|e| anyhow::anyhow!("{e}"))
}
fn trust_root() -> anyhow::Result<varve_core::PinnedKeyVerifier> {
varve_core::PinnedKeyVerifier::from_public_key_bytes(&trust_root_bytes()?)
.map_err(|e| anyhow::anyhow!("{e}"))
}
fn trust_root_bytes() -> anyhow::Result<Vec<u8>> {
let Some(path) = std::env::var_os("VARVE_TRUST_ROOT") else {
bail!(
"no trust root configured.\n\
\n\
The zero-config path is a realm: add `realm = \"pulseengine\"` to your \
varve.toml and commit a varve-realms.toml naming the registry and trust \
root — then no environment variable is needed and the realm's root is \
authoritative. The canonical file ships as `varve-realms.toml` with each \
release (and at pulseengine/varve/trust-roots/).\n\
\n\
Or, without a realm, point VARVE_TRUST_ROOT at the published root key \
(`rolling.pub`, a release asset). See the Getting started section of the \
README."
);
};
let path = PathBuf::from(path);
let hex_key = std::fs::read_to_string(&path)
.with_context(|| format!("cannot read trust root {}", path.display()))?;
hex_decode(hex_key.trim())
.with_context(|| format!("trust root {} is not valid hex", path.display()))
}
fn hex_decode(s: &str) -> anyhow::Result<Vec<u8>> {
if !s.len().is_multiple_of(2) || !s.chars().all(|c| c.is_ascii_hexdigit()) {
bail!("odd length or non-hex characters");
}
Ok((0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("checked hex"))
.collect())
}
fn today_rfc3339() -> String {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system clock before 1970")
.as_secs() as i64;
let mut days = secs / 86_400;
days += 719_468;
let era = if days >= 0 { days } else { days - 146_096 } / 146_097;
let doe = days - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
format!("{y:04}-{m:02}-{d:02}T00:00:00Z")
}
fn install(store: &Store, from: Option<&str>, platform: Option<String>) -> anyhow::Result<()> {
let platform = platform.unwrap_or_else(varve_core::host_platform);
let ctx = project_ctx(store)?;
let from = match (from, &ctx.realm) {
(Some(explicit), _) => explicit.to_string(),
(None, Some(realm)) => realm.registry.clone(),
(None, None) => {
bail!("no source: pass --from, or name a realm in the pin so its registry applies")
}
};
let from = from.as_str();
let pin = &ctx.pin;
let verifier = ctx_verifier(&ctx)?;
let store = &ctx.store;
let source: Box<dyn varve_core::LayerSource> =
if from.starts_with("oci://") || from.starts_with("oci+http://") {
Box::new(varve_core::RegistrySource::parse(from)?)
} else {
let path = std::path::Path::new(from);
if path.join("oci-layout").is_file() {
Box::new(varve_core::OciLayoutSource::at(path).for_platform(&platform))
} else {
Box::new(varve_core::DirSource::at(path))
}
};
let source = &*source;
let mut marks = varve_core::HighWaterMarks::load(store.root())?;
let now = today_rfc3339();
let index = ctx.realm.as_ref().map(|realm| varve_core::IndexPolicy {
realm: &realm.name,
root_public_key: &realm.trust_root,
required: realm.signed_index,
});
let policy = varve_core::InstallPolicy {
index,
now: &now,
staleness_threshold_days: 90,
platform: &platform,
};
let outcome = varve_core::install(pin, source, &verifier, store, &mut marks, &policy)?;
println!(
"installed layer {} (counter {}) {}",
outcome.layer, outcome.counter, outcome.digest
);
if let Some(high_water) = outcome.index_high_water {
let realm = ctx.realm.as_ref().map(|r| r.name.as_str()).unwrap_or("-");
println!(
" realm '{realm}' signed index for line {}: greatest counter {high_water}; this \
install accepted counter {} (reported, not enforced — your pin stays installable)",
pin.layer.line(),
outcome.counter
);
}
if let Some(age) = outcome.staleness_days {
eprintln!(
"warning: layer {} was issued {age} days ago — check whether a newer deposit of \
its line exists",
outcome.layer
);
}
if outcome.attestations_carried > 0 {
println!(
" carried {} attestation(s) with the layer — `varve verify` reports what each \
claims and whether it still binds",
outcome.attestations_carried
);
}
if let Some(note) = &outcome.attestation_note {
eprintln!(
"warning: layer {} carried attestations that did not survive the trip: {note}",
outcome.layer
);
}
if let Some(entry) = ctx.store.get(&outcome.digest)?
&& let Ok(bytes) = std::fs::read(entry.root.join("layer.json"))
&& let Ok(view) = varve_core::compose::view(&bytes)
&& !view.includes.is_empty()
{
let missing: Vec<String> = view
.includes
.iter()
.filter(|inc| {
ctx.store
.find_anywhere(&inc.digest)
.ok()
.flatten()
.is_none()
})
.map(|inc| {
let name = inc.layer.clone().unwrap_or_else(|| inc.digest.clone());
match &inc.realm {
Some(r) => format!("{name} (realm '{r}')"),
None => name,
}
})
.collect();
if !missing.is_empty() {
bail!(
"layer {} composes {} layer(s) that are not installed: {}.\n\
Install each first — a composed layer names what it needs by digest, \
but does not fetch it. `install` resolves THIS project's pin, so \
pointing --from at the other source is not enough: give the included \
layer its own pin (a directory whose varve.toml names that realm, \
channel and layer), run `varve install` there, then re-run this one. \
Both land in the same store. See `varve docs composition`.",
outcome.layer,
missing.len(),
missing.join(", ")
);
}
println!(
" composes {} installed layer(s) — `varve verify` checks each against its own \
realm's trust root",
view.includes.len()
);
}
let line = pin.layer.line().clone();
let layer_ref = match &pin.digest {
Some(digest) => varve_core::LayerRef::Digest(digest.clone()),
None => varve_core::LayerRef::Name(pin.layer.clone()),
};
let root_pk = ctx_root_bytes(&ctx)?;
match varve_core::cache_baseline_line_status(source, &layer_ref, &line, &root_pk, store.root())
{
Ok(Some(counter)) => {
println!("cached baseline line-status #{counter} for line {line}");
}
Ok(None) => {}
Err(e) => eprintln!("note: layer carried a line-status but it was not cached: {e}"),
}
Ok(())
}
fn verify(
store: &Store,
all: bool,
exports: &[PathBuf],
lockfile: Option<&std::path::Path>,
) -> anyhow::Result<()> {
let ctx = project_ctx(store)?;
let verifier = ctx_verifier(&ctx)?;
let store = &ctx.store;
let layers = if all {
store.list()?
} else {
vec![varve_core::resolve(&ctx.pin, store)?.layer]
};
if layers.is_empty() {
bail!("nothing to verify — no layers installed");
}
for layer in layers {
let checked =
varve_core::verify_installed(store, &layer, &verifier, &varve_core::host_platform())?;
println!(
"layer {} {} verified: signature OK, {checked} tool(s) match their signed digests",
layer.layer, layer.digest
);
report_attestations(&ctx, &layer)?;
verify_composition(&ctx, store, &layer)?;
}
if !exports.is_empty() {
let current = varve_core::resolve(&ctx.pin, store)?.layer.digest;
verify_exports(¤t, exports)?;
}
if let Some(path) = lockfile {
verify_lockfile(&ctx, path)?;
}
verify_no_shadowing(&ctx, store)?;
verify_no_rollback(&ctx, store)?;
Ok(())
}
fn verify_no_rollback(ctx: &ProjectCtx, store: &Store) -> anyhow::Result<()> {
let resolved = varve_core::resolve(&ctx.pin, store)?;
let payload = std::fs::read(resolved.layer.root.join("layer.json"))?;
let manifest = varve_core::LayerManifest::parse(&payload)?;
let marks = varve_core::HighWaterMarks::load(store.root())?;
if let varve_core::RollbackVerdict::Rollback {
line,
presented,
high_water,
} = marks.check(&manifest)
{
bail!(
"the layer verifies, but the pin resolves to a STALE layer: {} presents counter \
{presented} while the {line} line's high-water mark is {high_water}. A \
validly-signed older layer is exactly what anti-rollback exists to refuse — \
`install` would have refused this. Move the pin forward, or if you are \
deliberately going back, see `varve docs recovery`.",
manifest.layer
);
}
Ok(())
}
fn report_attestations(ctx: &ProjectCtx, layer: &varve_core::InstalledLayer) -> anyhow::Result<()> {
let root = ctx_root_bytes(ctx)?;
let reports = match varve_core::attestcarry::report_installed(
&layer.root,
&layer.layer.to_string(),
&layer.digest,
&root,
) {
Ok(reports) => reports,
Err(e) => {
eprintln!("note: attestations carried by layer {} : {e}", layer.layer);
return Ok(());
}
};
if reports.is_empty() {
return Ok(());
}
println!(" carries {} attestation(s):", reports.len());
for r in &reports {
if r.binds {
println!(
" {kind} by {producer}: binds to this layer (varve vouches for the \
association and the bytes' integrity — what {producer} CLAIMS is verified \
with {producer}'s own key)",
kind = r.kind,
producer = r.producer,
);
} else {
println!(
" {kind} by {producer}: DOES NOT BIND — {reason}",
kind = r.kind,
producer = r.producer,
reason = r.reason.as_deref().unwrap_or("unknown"),
);
}
}
Ok(())
}
fn verify_no_shadowing(ctx: &ProjectCtx, store: &Store) -> anyhow::Result<()> {
let resolved = varve_core::resolve(&ctx.pin, store)?;
let path_var = std::env::var_os("PATH");
let me = std::env::current_exe().ok();
let mut shadowed = Vec::new();
for (name, path) in &resolved.tools {
if let varve_core::shadow::Shadowing::Shadowed { found } =
varve_core::shadow::check(path_var.as_deref(), name, path, me.as_deref())
{
shadowed.push(varve_core::shadow::describe(name, path, &found));
}
}
if shadowed.is_empty() {
return Ok(());
}
bail!(
"the layer verifies, but {} of its tool(s) are not what your PATH runs \
(REQ-SHADOW-001):\n\n{}",
shadowed.len(),
shadowed.join("\n\n")
);
}
fn verify_lockfile(ctx: &ProjectCtx, path: &std::path::Path) -> anyhow::Result<()> {
let resolved = varve_core::resolve(&ctx.pin, &ctx.store)?;
let text = std::fs::read_to_string(path)
.with_context(|| format!("cannot read lockfile {}", path.display()))?;
let locked = varve_core::lockpin::parse_lockfile(&text, &path.display().to_string())?;
let crates = match collect_verified_crates(&ctx.store, &resolved.layer) {
Ok(c) => c,
Err(e) if e.to_string().contains("carries no `crate` entries") => {
println!(
"lockfile {}: parsed {} package(s); layer {} pins no crates, so this check \
asserted nothing",
path.display(),
locked.len(),
resolved.layer.layer
);
return Ok(());
}
Err(e) => {
return Err(e).context(
"cannot read the pinned layer's crates, so the lockfile CANNOT be checked \
— refusing to report agreement",
);
}
};
let found = varve_core::lockpin::disagreements(&crates, &locked);
if found.is_empty() {
println!(
"lockfile {} agrees with layer {} ({} pinned crate(s) checked against {} package(s))",
path.display(),
resolved.layer.layer,
crates.len(),
locked.len()
);
return Ok(());
}
for d in &found {
eprintln!(" {d}");
}
bail!(
"{} package(s) in {} disagree with layer {} (REQ-LOCKPIN-001) — re-resolve against the \
pinned layer, or move the pin",
found.len(),
path.display(),
resolved.layer.layer
);
}
fn verify_composition(
ctx: &ProjectCtx,
store: &Store,
layer: &varve_core::store::InstalledLayer,
) -> anyhow::Result<()> {
let mut path = std::collections::BTreeSet::new();
verify_composition_inner(ctx, store, layer, &mut path)
}
fn verify_composition_inner(
ctx: &ProjectCtx,
store: &Store,
layer: &varve_core::store::InstalledLayer,
path: &mut std::collections::BTreeSet<String>,
) -> anyhow::Result<()> {
if !path.insert(layer.digest.clone()) {
bail!(
"composition cycle while verifying: layer {} ({}) reappears on its own path — refusing to follow it",
layer.layer,
layer.digest
);
}
if path.len() > varve_core::compose::MAX_DEPTH {
bail!(
"composition is more than {} layers deep while verifying — refusing to walk further",
varve_core::compose::MAX_DEPTH
);
}
let Ok(bytes) = std::fs::read(layer.root.join("layer.json")) else {
return Ok(());
};
let view = varve_core::compose::view(&bytes)?;
if view.includes.is_empty() {
return Ok(());
}
let cwd = std::env::current_dir().context("cannot determine working directory")?;
for inc in &view.includes {
let Some((owner, entry)) = store.find_anywhere(&inc.digest)? else {
bail!(
"layer {} composes {}, which is not installed — `varve install` it, then re-verify",
layer.layer,
inc.layer.clone().unwrap_or_else(|| inc.digest.clone())
);
};
let verifier = match &inc.realm {
Some(name) => {
let realm = varve_core::resolve_realm(&cwd, name).with_context(|| {
format!(
"layer {} composes a layer from realm '{name}', but that realm is not defined here — add it to varve-realms.toml so its trust root can verify what it vouches for",
layer.layer
)
})?;
varve_core::PinnedKeyVerifier::from_public_key_bytes(&realm.trust_root)
.map_err(|e| anyhow::anyhow!("{e}"))?
}
None => ctx_verifier(ctx)?,
};
let vouched_by = match &inc.realm {
Some(name) => format!("realm '{name}'"),
None => "this project's own trust root (the include names no realm)".to_string(),
};
let checked =
varve_core::verify_installed(store, &entry, &verifier, &varve_core::host_platform())
.with_context(|| {
let mut msg = format!(
"composed layer {} failed verification against {vouched_by}",
entry.layer
);
if inc.realm.is_none() {
msg.push_str(
". If the included layer comes from a DIFFERENT realm, this is the \
expected result of an `[[include]]` with no `realm =` — its own \
realm's root is the one that vouches for it. Add `realm` to the \
include and re-deposit; the annotation is inside the signed payload, \
so it cannot be added afterwards",
);
}
msg
})?;
println!(
" composes {} {} — verified against {vouched_by}: {checked} tool(s) match",
entry.layer, entry.digest,
);
verify_composition_inner(ctx, &owner, &entry, path)?;
}
Ok(())
}
fn verify_exports(current_digest: &str, exports: &[PathBuf]) -> anyhow::Result<()> {
use varve_core::exportstamp::{ExportStatus, read_stamp, status};
let mut stale = Vec::new();
for dir in exports {
let stamp = read_stamp(dir)?;
match status(&stamp, current_digest) {
ExportStatus::Current => println!(
"export {} — fresh: bound to layer {} ({})",
dir.display(),
stamp.layer,
stamp.kind
),
ExportStatus::Stale { stamped, current } => {
eprintln!(
"export {} — STALE: stamped from layer {} ({stamped}); the pin now \
resolves {current}. Re-run the export against the current pin.",
dir.display(),
stamp.layer
);
stale.push(dir.display().to_string());
}
}
}
if !stale.is_empty() {
bail!(
"{} stale export(s) diverged from the pin (REQ-EXPORT-SYNC-001): {}",
stale.len(),
stale.join(", ")
);
}
Ok(())
}
fn store_root() -> anyhow::Result<PathBuf> {
if let Some(root) = std::env::var_os("VARVE_ROOT") {
return Ok(PathBuf::from(root));
}
let home = std::env::var_os("HOME").context("HOME is not set and VARVE_ROOT is not set")?;
Ok(PathBuf::from(home).join(".varve"))
}
fn load_pin() -> anyhow::Result<Pin> {
let cwd = std::env::current_dir().context("cannot determine working directory")?;
let Some(path) = discover::find_pin(&cwd) else {
bail!(
"no varve.toml found walking up from {} — this project has no pinned layer. \
Create varve.toml next to your rust-toolchain.toml to pin one.",
cwd.display()
);
};
Ok(Pin::load(&path)?)
}
fn which(store: &Store, tool: &str) -> anyhow::Result<()> {
let ctx = project_ctx(store)?;
let resolved = resolve(&ctx.pin, &ctx.store)?;
let Some((_, path)) = resolved.tools.iter().find(|(name, _)| name == tool) else {
bail!(
"tool '{tool}' is not part of layer {} as pinned here — the pin exposes: {}",
resolved.layer.layer,
resolved
.tools
.iter()
.map(|(n, _)| n.as_str())
.collect::<Vec<_>>()
.join(", ")
);
};
println!("{}", path.display());
println!(
"layer {} ({}) {}",
resolved.layer.layer, resolved.layer.channel, resolved.layer.digest
);
let me = std::env::current_exe().ok();
if let varve_core::shadow::Shadowing::Shadowed { found } = varve_core::shadow::check(
std::env::var_os("PATH").as_deref(),
tool,
path,
me.as_deref(),
) {
eprintln!(
"warning: {}",
varve_core::shadow::describe(tool, path, &found)
);
}
Ok(())
}
fn list(store: &Store) -> anyhow::Result<()> {
let mut rows: Vec<(String, varve_core::store::InstalledLayer)> = store
.list()?
.into_iter()
.map(|e| (String::new(), e))
.collect();
let realms_dir = store.root().join("realms");
if let Ok(entries) = std::fs::read_dir(&realms_dir) {
let mut partitions: Vec<std::path::PathBuf> =
entries.filter_map(|e| e.ok()).map(|e| e.path()).collect();
partitions.sort();
for part in partitions {
let Some(fingerprint) = part.file_name().map(|f| f.to_string_lossy().into_owned())
else {
continue;
};
let label = realm_name_for(&fingerprint).unwrap_or(fingerprint);
for entry in Store::at(&part).list().unwrap_or_default() {
rows.push((label.clone(), entry));
}
}
}
if rows.is_empty() {
println!("no layers installed in {}", store.root().display());
return Ok(());
}
for (realm, entry) in rows {
if realm.is_empty() {
println!("{} {} {}", entry.layer, entry.channel, entry.digest);
} else {
println!(
"{} {} {} realm={realm}",
entry.layer, entry.channel, entry.digest
);
}
}
Ok(())
}
fn realm_name_for(fingerprint: &str) -> Option<String> {
let cwd = std::env::current_dir().ok()?;
let names = varve_core::realm::realm_names(&cwd).ok()?;
for name in names {
if let Ok(realm) = varve_core::resolve_realm(&cwd, &name)
&& realm.fingerprint() == fingerprint
{
return Some(name);
}
}
None
}
#[cfg(test)]
mod dispatch_tests {
use super::dispatch_tool_name;
use std::ffi::OsStr;
#[test]
fn varve_invoked_as_itself_is_not_a_dispatch() {
for own in ["varve", "/usr/local/bin/varve", "varve.exe"] {
assert_eq!(
dispatch_tool_name(Some(OsStr::new(own))),
None,
"{own} must run the CLI, not dispatch"
);
}
assert_eq!(dispatch_tool_name(None), None);
}
#[test]
fn a_shim_name_is_the_file_name_only() {
assert_eq!(
dispatch_tool_name(Some(OsStr::new("/home/u/.varve/shims/synth"))),
Some("synth".into())
);
assert_eq!(
dispatch_tool_name(Some(OsStr::new("rivet"))),
Some("rivet".into())
);
assert_eq!(
dispatch_tool_name(Some(OsStr::new("synth.exe"))),
Some("synth".into())
);
}
#[test]
fn a_hostile_argv0_cannot_smuggle_a_path() {
for hostile in ["..", ".", ""] {
assert_eq!(
dispatch_tool_name(Some(OsStr::new(hostile))),
None,
"{hostile:?} must not name a tool"
);
}
let got = dispatch_tool_name(Some(OsStr::new("../../etc/passwd")));
assert_eq!(got, Some("passwd".into()));
assert!(!got.unwrap().contains('/'));
}
}