fix: comprehensive bakery package manager audit and repair

Critical fixes:
- gen-index.sh: emit services, config, optional_system_deps from bakery.toml;
  parse product list from registry TOML instead of hardcoded array; fail loudly
  when bakery.toml is missing (was silently producing empty metadata in prod)
- install.rs: download service units and example configs from dl server at
  install time (were never fetched); check systemctl exit codes (were swallowed);
  save state before file cleanup in remove_package (was inconsistent on error)
- doctor.rs: rewrite dep detection to use `pacman -Q` as primary (no more
  dependency on `which` or pkg-config name mismatches); add optional_system_deps
  support returning (missing, warnings) — warnings print but never block install
- get.sh: fix GitHub fallback URL (was 404 for both latest and versioned
  releases); add SHA-256 checksum verification using published .sha256 file

High priority fixes:
- bakery doctor <unknown-pkg>: exit non-zero (was silently passing)
- bakery update: add --all flag (documented in README but missing from CLI);
  add doctor gate before update (was bypassing dep check)
- bread_deps: now resolved recursively with cycle detection (was ignored)
- manifest.rs: add artifact_urls() helper and optional_system_deps field
- state.rs: atomic save via tmp+rename; cmd_info shows optional_system_deps

Tests: 17 new unit tests across doctor, download, install, state modules;
scripts/test-gen-index.sh fixture test for full pipeline
This commit is contained in:
Breadway 2026-06-11 13:37:09 +08:00
parent 0b38e8cce3
commit 694829c50f
13 changed files with 971 additions and 148 deletions

View file

@ -23,7 +23,7 @@ pub struct Service {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ConfigScaffold {
pub dir: String,
/// relative to the product repo root; copied as-is if absent at install time
/// Example config filename, relative to the release artifact directory.
pub example: Option<String>,
}
@ -36,6 +36,8 @@ pub struct Package {
#[serde(default)]
pub system_deps: Vec<String>,
#[serde(default)]
pub optional_system_deps: Vec<String>,
#[serde(default)]
pub bread_deps: Vec<String>,
#[serde(default)]
pub services: Vec<Service>,
@ -44,6 +46,21 @@ pub struct Package {
pub post_install: Vec<String>,
}
impl Package {
/// Returns `(primary_url, github_url)` for any artifact filename in this
/// package's release directory. Derived by stripping the filename from the
/// first binary's URLs.
pub fn artifact_urls(&self, filename: &str) -> Option<(String, String)> {
let first = self.binaries.first()?;
let dl_base = first.dl_url.rsplit_once('/')?.0;
let gh_base = first.github_url.rsplit_once('/')?.0;
Some((
format!("{dl_base}/{filename}"),
format!("{gh_base}/{filename}"),
))
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Index {
pub version: String,
@ -67,8 +84,7 @@ pub fn load(force_refresh: bool) -> Result<Index> {
let cache_path = cache_path();
if !force_refresh && cache_is_fresh(&cache_path) {
let text = std::fs::read_to_string(&cache_path)
.context("reading cached index")?;
let text = std::fs::read_to_string(&cache_path).context("reading cached index")?;
return serde_json::from_str(&text).context("parsing cached index");
}
@ -132,6 +148,6 @@ fn fetch_bytes(url: &str) -> Result<Vec<u8>> {
let mut buf = Vec::new();
resp.into_reader()
.read_to_end(&mut buf)
.context("reading binary")?;
.context("reading response")?;
Ok(buf)
}