Fix audit findings: bakery index signing, artifact checksums, stale theme docs
- Add minisign-based signing/verification for the bakery index: scripts/gen-index.sh signs index.json (MINISIGN_SEC_KEY env var, dormant no-op with a loud warning until a key is provisioned); bakery/src/manifest.rs fetches index.json.minisig and verifies it with minisign-verify against a hardcoded PUBKEY before parsing/caching, and re-verifies the cached copy on every load (falls back to one re-fetch if the cache predates signing or fails verification; a fresh fetch that fails verification is a hard error). - Close the previously-unchecksummed config-example and systemd-unit downloads in bakery/src/install.rs (scaffold_config, install_service): index.json now carries `sha256`/`example_sha256` for these artifacts (computed in gen-index.sh), verified via the same download::verify_sha256 used for binaries. Downloads without a matching sha256 in the index are refused rather than installed unverified. - scripts/get.sh now verifies the bakery release binary itself against a pinned minisign public key before installing it (falls back to the existing sha256-only check with a loud warning if no .minisig is published yet or minisign isn't installed; a present-but-invalid signature is a hard failure). - Add dormant "sign release binary" steps to the bakery and bread-theme release workflows (.github/workflows/release.yml, .forgejo/workflows/release-bread-theme.yml), gated on secrets that are not yet configured — binaries ship unsigned exactly as before until the owner wires up the secret. - .gitignore: add *.minisign-sec / minisign.key so the signing key can never be committed by accident. - bread-theme: fix stale docs describing a "Catppuccin Mocha fallback" (BREAD_DESIGN_SYSTEM.md, README.md, Cargo.toml/bakery.toml/registry descriptions) — the actual implementation (palette.rs) uses a fixed BOS dark base with only accent colors from pywal. - bread-theme: fix the legacy css_vars() path, which had its own hand-written @define-color block that predated the `accent` and computed `on-*` ink colors used by the rest of the stylesheet — any caller whose CSS referenced those names against css_vars()'s output would hit undefined colors (the illegible-text bug). css_vars() now delegates to the same define_colors() the full stylesheet uses, so the two can't drift apart again.
This commit is contained in:
parent
fa0597f482
commit
394a252f9e
18 changed files with 472 additions and 84 deletions
|
|
@ -31,7 +31,31 @@ jobs:
|
|||
cp src/bread-theme/bakery.toml "${PKG_DIR}/bakery.toml"
|
||||
ln -sfn "${VERSION}" "/srv/breadway-dl/bread-theme/latest"
|
||||
|
||||
# Signs the bread-theme binary with the shared bakery ecosystem signing
|
||||
# key (same key that signs index.json — get.sh / manifest.rs pin the
|
||||
# matching public key). BAKERY_MINISIGN_SEC_KEY_PATH is a *path on this
|
||||
# runner's disk* (hestia has persistent storage, unlike a fresh
|
||||
# GitHub-hosted runner), not the key contents — see the handoff note
|
||||
# in scripts/gen-index.sh. Dormant (binary ships unsigned, as today)
|
||||
# until that secret is provisioned.
|
||||
- name: sign release binary
|
||||
env:
|
||||
MINISIGN_SEC_KEY: ${{ secrets.BAKERY_MINISIGN_SEC_KEY_PATH }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
PKG_DIR="/srv/breadway-dl/bread-theme/${VERSION}"
|
||||
if [ -n "${MINISIGN_SEC_KEY:-}" ]; then
|
||||
minisign -W -S -s "${MINISIGN_SEC_KEY}" -m "${PKG_DIR}/bread-theme-x86_64" \
|
||||
-x "${PKG_DIR}/bread-theme-x86_64.minisig" </dev/null
|
||||
echo "signed bread-theme-x86_64"
|
||||
else
|
||||
echo "::warning::BAKERY_MINISIGN_SEC_KEY_PATH not set — shipping bread-theme-x86_64 UNSIGNED"
|
||||
fi
|
||||
|
||||
- name: regenerate index.json
|
||||
env:
|
||||
MINISIGN_SEC_KEY: ${{ secrets.BAKERY_MINISIGN_SEC_KEY_PATH }}
|
||||
run: cd src && bash scripts/gen-index.sh
|
||||
|
||||
- name: upload to GitHub Release
|
||||
|
|
@ -43,7 +67,6 @@ jobs:
|
|||
PKG_DIR="/srv/breadway-dl/bread-theme/${VERSION}"
|
||||
gh release create "${GITHUB_REF_NAME}" --repo Breadway/bread-ecosystem \
|
||||
--title "bread-ecosystem ${GITHUB_REF_NAME}" --generate-notes 2>/dev/null || true
|
||||
gh release upload "${GITHUB_REF_NAME}" --repo Breadway/bread-ecosystem \
|
||||
"${PKG_DIR}/bread-theme-x86_64" \
|
||||
"${PKG_DIR}/bread-theme-x86_64.sha256" \
|
||||
--clobber
|
||||
ASSETS="${PKG_DIR}/bread-theme-x86_64 ${PKG_DIR}/bread-theme-x86_64.sha256"
|
||||
[ -f "${PKG_DIR}/bread-theme-x86_64.minisig" ] && ASSETS="${ASSETS} ${PKG_DIR}/bread-theme-x86_64.minisig"
|
||||
gh release upload "${GITHUB_REF_NAME}" --repo Breadway/bread-ecosystem ${ASSETS} --clobber
|
||||
|
|
|
|||
42
.github/workflows/release.yml
vendored
42
.github/workflows/release.yml
vendored
|
|
@ -36,8 +36,41 @@ jobs:
|
|||
cp bakery.toml "${PKG_DIR}/bakery.toml"
|
||||
ln -sfn "${VERSION}" "${DL_DIR}/bakery/latest"
|
||||
|
||||
# Signs the bakery binary itself with the same minisign key that signs
|
||||
# index.json (get.sh pins the matching public key). Dormant until the
|
||||
# BAKERY_MINISIGN_SEC_KEY secret is actually provisioned in this repo's
|
||||
# Actions settings — until then this step logs a warning and the
|
||||
# binary ships unsigned, exactly as it does today.
|
||||
- name: sign release binary
|
||||
env:
|
||||
MINISIGN_SEC_KEY_CONTENTS: ${{ secrets.BAKERY_MINISIGN_SEC_KEY }}
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
PKG_DIR="${DL_DIR}/bakery/${VERSION}"
|
||||
if [ -n "${MINISIGN_SEC_KEY_CONTENTS}" ]; then
|
||||
command -v minisign >/dev/null 2>&1 || { echo "::error::minisign not installed on runner"; exit 1; }
|
||||
KEY_FILE="$(mktemp)"
|
||||
trap 'shred -u "${KEY_FILE}" 2>/dev/null || rm -f "${KEY_FILE}"' EXIT
|
||||
printf '%s' "${MINISIGN_SEC_KEY_CONTENTS}" > "${KEY_FILE}"
|
||||
minisign -W -S -s "${KEY_FILE}" -m "${PKG_DIR}/bakery-x86_64" \
|
||||
-x "${PKG_DIR}/bakery-x86_64.minisig" </dev/null
|
||||
echo "signed bakery-x86_64"
|
||||
else
|
||||
echo "::warning::BAKERY_MINISIGN_SEC_KEY secret not set — shipping bakery-x86_64 UNSIGNED"
|
||||
fi
|
||||
|
||||
- name: regenerate index.json
|
||||
run: bash "${GITHUB_WORKSPACE}/scripts/gen-index.sh"
|
||||
env:
|
||||
MINISIGN_SEC_KEY_CONTENTS: ${{ secrets.BAKERY_MINISIGN_SEC_KEY }}
|
||||
run: |
|
||||
if [ -n "${MINISIGN_SEC_KEY_CONTENTS}" ]; then
|
||||
KEY_FILE="$(mktemp)"
|
||||
trap 'shred -u "${KEY_FILE}" 2>/dev/null || rm -f "${KEY_FILE}"' EXIT
|
||||
printf '%s' "${MINISIGN_SEC_KEY_CONTENTS}" > "${KEY_FILE}"
|
||||
MINISIGN_SEC_KEY="${KEY_FILE}" bash "${GITHUB_WORKSPACE}/scripts/gen-index.sh"
|
||||
else
|
||||
bash "${GITHUB_WORKSPACE}/scripts/gen-index.sh"
|
||||
fi
|
||||
|
||||
- name: upload to GitHub Release
|
||||
env:
|
||||
|
|
@ -47,7 +80,6 @@ jobs:
|
|||
PKG_DIR="${DL_DIR}/bakery/${VERSION}"
|
||||
gh release create "${GITHUB_REF_NAME}" \
|
||||
--title "bakery v${VERSION}" --generate-notes 2>/dev/null || true
|
||||
gh release upload "${GITHUB_REF_NAME}" \
|
||||
"${PKG_DIR}/bakery-x86_64" \
|
||||
"${PKG_DIR}/bakery-x86_64.sha256" \
|
||||
--clobber
|
||||
ASSETS="${PKG_DIR}/bakery-x86_64 ${PKG_DIR}/bakery-x86_64.sha256"
|
||||
[ -f "${PKG_DIR}/bakery-x86_64.minisig" ] && ASSETS="${ASSETS} ${PKG_DIR}/bakery-x86_64.minisig"
|
||||
gh release upload "${GITHUB_REF_NAME}" ${ASSETS} --clobber
|
||||
|
|
|
|||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -1 +1,7 @@
|
|||
/target/
|
||||
|
||||
# minisign secret keys must never be committed — the bakery/index signing
|
||||
# key lives outside this repo entirely (see scripts/gen-index.sh /
|
||||
# scripts/get.sh for how it's consumed via MINISIGN_SEC_KEY).
|
||||
*.minisign-sec
|
||||
minisign.key
|
||||
|
|
|
|||
|
|
@ -48,16 +48,27 @@ Establish a visual hierarchy with consistent rounding:
|
|||
|
||||
## Color System
|
||||
|
||||
All projects use **pywal dynamic theming** with **Catppuccin Mocha** as the fallback palette:
|
||||
All projects use **pywal dynamic theming** for accents, layered on a **fixed BOS
|
||||
dark base** — background, surface, overlay, and foreground never come from
|
||||
pywal, only the accent slots (color1–6) track the current wallpaper:
|
||||
|
||||
- **Background**: `#1e1e2e` (Catppuccin)
|
||||
- **Foreground**: `#cdd6f4` (Catppuccin)
|
||||
- **Surface**: `#181825` (Catppuccin)
|
||||
- **Accent**: Dynamic (from pywal)
|
||||
- **Background**: `#0c0c0c` (fixed)
|
||||
- **Foreground**: `#e8e8e8` (fixed)
|
||||
- **Surface**: `#1a1a1a` (fixed, `color0`)
|
||||
- **Overlay**: `#d8d8d8` (fixed, `color7`)
|
||||
- **Accent**: Dynamic (from pywal `color4`), with curated bread-toned defaults
|
||||
before any wallpaper has been set
|
||||
|
||||
Without pinning bg/surface/overlay, a light or muddy-toned wallpaper makes
|
||||
pywal hand back a light or off-hue background, and every bread GUI's panels
|
||||
inherit it — see `bread-theme/src/palette.rs` for the implementation.
|
||||
|
||||
Color palette slots (via wal):
|
||||
- color0–color7: ANSI colors
|
||||
- color0–color7: ANSI colors (0 and 7 fixed, 1–6 pywal-derived)
|
||||
- Semantic: red, green, yellow, blue, pink, teal
|
||||
- Computed ink: `on-bg`, `on-surface`, `on-accent`, `on-red`, `on-overlay` —
|
||||
black or white text, whichever is legible against that background (see
|
||||
`bread_theme::ink_on`)
|
||||
|
||||
## Component Standards
|
||||
|
||||
|
|
@ -104,8 +115,9 @@ add only app-specific rules:
|
|||
hardcoded Nord palette; migrated to the shared stylesheet).
|
||||
- **breadcrumbs** — CLI tool; ANSI colours only, no GUI styling.
|
||||
|
||||
> Palette note: the fallback is Catppuccin Mocha, but installs (e.g. BOS) drive
|
||||
> the real palette from pywal — BOS ships a black-base palette.
|
||||
> Palette note: background/surface/overlay/foreground are a fixed BOS dark
|
||||
> base, never pywal-derived; only the accent slots (color1–6) track the
|
||||
> current wallpaper via pywal.
|
||||
|
||||
## Future Consistency Checks
|
||||
|
||||
|
|
|
|||
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -88,6 +88,7 @@ dependencies = [
|
|||
"clap",
|
||||
"dirs",
|
||||
"hex",
|
||||
"minisign-verify",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
|
@ -973,6 +974,12 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minisign-verify"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ sha2 = "0.10"
|
|||
hex = "0.4"
|
||||
clap = { version = "4", features = ["derive", "env"] }
|
||||
chrono = "0.4"
|
||||
minisign-verify = "0.2"
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -36,7 +36,9 @@ to keys.
|
|||
## Theming
|
||||
|
||||
All GUIs share one look via `bread-theme`. The `bread-theme` CLI renders the
|
||||
component stylesheet from your pywal palette (Catppuccin Mocha fallback) to
|
||||
component stylesheet from your pywal palette, layered on a fixed BOS dark
|
||||
base (background/surface/overlay never come from pywal — only the accent
|
||||
colors do, so a light wallpaper can't wash out the UI), to
|
||||
`$XDG_RUNTIME_DIR/bread/theme.css`; every app loads that file and **live-reloads**
|
||||
it, so changing your wallpaper recolours the whole ecosystem with no rebuilds:
|
||||
|
||||
|
|
@ -95,8 +97,12 @@ Install all required deps with `sudo pacman -S <packages>`. Use `pacman -Q <pkg>
|
|||
## Theming
|
||||
|
||||
All GUI products (breadbar, breadbox, breadpad) read pywal colors from
|
||||
`~/.cache/wal/colors.json` and fall back to Catppuccin Mocha when that file
|
||||
is absent. Per-app CSS overrides live at `~/.config/<app>/style.css`.
|
||||
`~/.cache/wal/colors.json` for accents only; background, surface, overlay,
|
||||
and foreground are always BOS's fixed dark values (see
|
||||
[`BREAD_DESIGN_SYSTEM.md`](BREAD_DESIGN_SYSTEM.md#color-system)) regardless
|
||||
of what pywal extracted from the wallpaper. When `colors.json` is absent,
|
||||
accents fall back to BOS's curated bread-toned defaults. Per-app CSS
|
||||
overrides live at `~/.config/<app>/style.css`.
|
||||
|
||||
The shared theming logic lives in the `bread-theme` crate in this repo.
|
||||
|
||||
|
|
@ -107,7 +113,7 @@ This repo is a Cargo workspace:
|
|||
```
|
||||
bread-ecosystem/
|
||||
├── bakery/ # package manager binary
|
||||
├── bread-theme/ # shared pywal + Catppuccin theming crate
|
||||
├── bread-theme/ # shared pywal + fixed-dark-base theming crate
|
||||
├── registry/ # bread-ecosystem.toml — product registry
|
||||
└── scripts/
|
||||
├── get.sh # curl | sh bootstrap
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ sha2 = { workspace = true }
|
|||
hex = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
minisign-verify = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,12 @@ pub fn fetch_and_place(binary: &Binary, dest: &Path) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_sha256(bytes: &[u8], expected_hex: &str) -> Result<()> {
|
||||
/// Verify that `bytes` hashes to `expected_hex` under SHA-256.
|
||||
///
|
||||
/// Shared by every artifact download path — binaries (via
|
||||
/// [`fetch_and_place`]), and config-example / systemd-unit downloads in
|
||||
/// `install.rs` — so all downloaded artifacts get the same integrity check.
|
||||
pub fn verify_sha256(bytes: &[u8], expected_hex: &str) -> Result<()> {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(bytes);
|
||||
let actual = hex::encode(hasher.finalize());
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::{Context, Result};
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::download::fetch_and_place;
|
||||
use crate::download::{fetch_and_place, verify_sha256};
|
||||
use crate::manifest::{fetch_binary, Package, Service};
|
||||
use crate::state::{InstalledPackage, State};
|
||||
|
||||
|
|
@ -119,11 +119,28 @@ fn scaffold_config(cfg: &crate::manifest::ConfigScaffold, pkg: &Package) -> Resu
|
|||
if !dest.exists() {
|
||||
if let Some((primary, fallback)) = pkg.artifact_urls(example) {
|
||||
match fetch_binary(&primary, &fallback) {
|
||||
Ok(bytes) => {
|
||||
Ok(bytes) => match &cfg.example_sha256 {
|
||||
Some(expected) => match verify_sha256(&bytes, expected) {
|
||||
Ok(()) => {
|
||||
std::fs::write(&dest, &bytes)
|
||||
.with_context(|| format!("writing {}", dest.display()))?;
|
||||
println!(" installed example config at {}", dest.display());
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
" warning: checksum mismatch for example config {example}: {e} — not installed"
|
||||
);
|
||||
println!(" config dir created at {}", dir.display());
|
||||
}
|
||||
},
|
||||
None => {
|
||||
eprintln!(
|
||||
" warning: index.json has no sha256 for example config \
|
||||
{example} — refusing to install an unverified download"
|
||||
);
|
||||
println!(" config dir created at {}", dir.display());
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!(" warning: could not download example config {example}: {e}");
|
||||
println!(" config dir created at {}", dir.display());
|
||||
|
|
@ -151,11 +168,19 @@ fn install_service(svc: &Service, bin_dir: &Path, pkg: &Package) -> Result<()> {
|
|||
if !unit_path.exists() {
|
||||
if let Some((primary, fallback)) = pkg.artifact_urls(&svc.unit) {
|
||||
match fetch_binary(&primary, &fallback) {
|
||||
Ok(bytes) => {
|
||||
Ok(bytes) => match verify_sha256(&bytes, &svc.sha256) {
|
||||
Ok(()) => {
|
||||
std::fs::write(&unit_path, &bytes)
|
||||
.with_context(|| format!("writing {}", unit_path.display()))?;
|
||||
println!(" downloaded unit {}", unit_path.display());
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
" warning: checksum mismatch for unit {}: {e} — not installed",
|
||||
svc.unit
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!(" warning: could not download {}: {e}", svc.unit);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,45 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use minisign_verify::{PublicKey, Signature};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const PRIMARY_URL: &str = "https://dl.breadway.dev/index.json";
|
||||
const SIG_URL: &str = "https://dl.breadway.dev/index.json.minisig";
|
||||
const CACHE_MAX_AGE: Duration = Duration::from_secs(24 * 3600);
|
||||
|
||||
/// The bakery index-signing public key.
|
||||
///
|
||||
/// The matching secret key is used offline (never on this machine, never in
|
||||
/// this repo) to sign `index.json` with `minisign` as part of publishing a
|
||||
/// new index — see `scripts/gen-index.sh`. Every fetch of `index.json`, and
|
||||
/// every load of the on-disk cache, must verify against this key before the
|
||||
/// bytes are trusted or parsed. This is the single control point: the
|
||||
/// per-artifact `sha256` fields and `post_install` hook strings all live
|
||||
/// inside `index.json` itself, so a valid signature transitively covers them.
|
||||
const PUBKEY: &str = "RWRh2Zr5SUinvVFCtD7S7HwGjfrye6j31Xq2mYXRdkGFDWe3yHF7W11K";
|
||||
|
||||
/// Verify `bytes` against `sig_text` (the contents of an `index.json.minisig`
|
||||
/// file) using the pinned [`PUBKEY`]. Returns an error on any failure —
|
||||
/// missing/malformed signature, wrong key, or a hash mismatch.
|
||||
fn verify_index_signature(bytes: &[u8], sig_text: &str) -> Result<()> {
|
||||
verify_against_key(bytes, sig_text, PUBKEY)
|
||||
}
|
||||
|
||||
/// Verify `bytes` against a minisign `sig_text` using an arbitrary base64
|
||||
/// public key. Split out from [`verify_index_signature`] purely so tests can
|
||||
/// exercise the verification logic with a throwaway keypair instead of the
|
||||
/// real production key.
|
||||
fn verify_against_key(bytes: &[u8], sig_text: &str, pubkey_b64: &str) -> Result<()> {
|
||||
let public_key =
|
||||
PublicKey::from_base64(pubkey_b64).context("public key is malformed")?;
|
||||
let signature =
|
||||
Signature::decode(sig_text).context("index.json.minisig is malformed or unreadable")?;
|
||||
public_key
|
||||
.verify(bytes, &signature, false)
|
||||
.context("index.json failed signature verification against the pinned bakery key")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Binary {
|
||||
pub name: String,
|
||||
|
|
@ -18,6 +52,10 @@ pub struct Binary {
|
|||
pub struct Service {
|
||||
pub unit: String,
|
||||
pub enable: bool,
|
||||
/// SHA-256 of the unit file artifact. Required to verify the download in
|
||||
/// `install::install_service`, same as binaries; `index.json` carries it
|
||||
/// (and is itself minisign-signed, which is what makes it trustworthy).
|
||||
pub sha256: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
|
|
@ -25,6 +63,10 @@ pub struct ConfigScaffold {
|
|||
pub dir: String,
|
||||
/// Example config filename, relative to the release artifact directory.
|
||||
pub example: Option<String>,
|
||||
/// SHA-256 of the example config artifact, when `example` is set.
|
||||
/// Verified in `install::scaffold_config` the same way binaries are.
|
||||
#[serde(default)]
|
||||
pub example_sha256: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
|
|
@ -80,15 +122,38 @@ impl Index {
|
|||
|
||||
/// Load the manifest, using the on-disk cache when it is fresh enough.
|
||||
/// Always fetches if `force_refresh` is true.
|
||||
///
|
||||
/// Every path — fresh fetch or cached read — verifies the minisign
|
||||
/// signature over the raw `index.json` bytes before the JSON is parsed or
|
||||
/// trusted. A signature failure on a freshly fetched index is always a hard
|
||||
/// error. A signature failure on the *cached* copy is treated as a
|
||||
/// (possibly tampered, possibly just stale-format) cache and triggers one
|
||||
/// re-fetch from the network rather than bricking the CLI outright; if the
|
||||
/// freshly fetched copy also fails to verify, that's a hard error.
|
||||
pub fn load(force_refresh: bool) -> Result<Index> {
|
||||
let cache_path = cache_path();
|
||||
let sig_cache_path = sig_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")?;
|
||||
return serde_json::from_str(&text).context("parsing cached index");
|
||||
match read_and_verify_cache(&cache_path, &sig_cache_path) {
|
||||
Ok(index) => return Ok(index),
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
" warning: cached index.json failed verification ({err}), re-fetching…"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetch_and_cache(&cache_path)
|
||||
fetch_and_cache(&cache_path, &sig_cache_path)
|
||||
}
|
||||
|
||||
fn read_and_verify_cache(cache_path: &PathBuf, sig_cache_path: &PathBuf) -> Result<Index> {
|
||||
let bytes = std::fs::read(cache_path).context("reading cached index")?;
|
||||
let sig_text = std::fs::read_to_string(sig_cache_path)
|
||||
.context("reading cached index.json.minisig (cache predates signing support)")?;
|
||||
verify_index_signature(&bytes, &sig_text)?;
|
||||
serde_json::from_slice(&bytes).context("parsing cached index")
|
||||
}
|
||||
|
||||
fn cache_is_fresh(path: &PathBuf) -> bool {
|
||||
|
|
@ -98,13 +163,26 @@ fn cache_is_fresh(path: &PathBuf) -> bool {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn fetch_and_cache(cache_path: &PathBuf) -> Result<Index> {
|
||||
let text = fetch_text(PRIMARY_URL)?;
|
||||
fn fetch_and_cache(cache_path: &PathBuf, sig_cache_path: &PathBuf) -> Result<Index> {
|
||||
let bytes = fetch_bytes(PRIMARY_URL)?;
|
||||
let sig_text = fetch_text(SIG_URL).context(
|
||||
"fetching index.json.minisig — the index must be signed before it can be trusted",
|
||||
)?;
|
||||
verify_index_signature(&bytes, &sig_text)
|
||||
.context("freshly fetched index.json failed signature verification")?;
|
||||
|
||||
if let Some(dir) = cache_path.parent() {
|
||||
std::fs::create_dir_all(dir)?;
|
||||
}
|
||||
std::fs::write(cache_path, &text)?;
|
||||
serde_json::from_str(&text).context("parsing index.json")
|
||||
std::fs::write(cache_path, &bytes)?;
|
||||
std::fs::write(sig_cache_path, &sig_text)?;
|
||||
serde_json::from_slice(&bytes).context("parsing index.json")
|
||||
}
|
||||
|
||||
fn sig_cache_path(cache_path: &Path) -> PathBuf {
|
||||
let mut name = cache_path.file_name().unwrap_or_default().to_os_string();
|
||||
name.push(".minisig");
|
||||
cache_path.with_file_name(name)
|
||||
}
|
||||
|
||||
fn fetch_text(url: &str) -> Result<String> {
|
||||
|
|
@ -151,3 +229,51 @@ fn fetch_bytes(url: &str) -> Result<Vec<u8>> {
|
|||
.context("reading response")?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// A throwaway test-only minisign keypair, generated solely to produce
|
||||
// these fixtures (`minisign -G` then `minisign -S`). It has no
|
||||
// relationship to the real bakery signing key (PUBKEY above) and the
|
||||
// matching secret key was discarded — these are just fixed vectors to
|
||||
// exercise the verification code path deterministically.
|
||||
const TEST_PUBKEY: &str = "RWQTYQi9Fe4trQDQmbb9txWDxzUIPYs57J//A5wG9BHcZXgC8YP0Cf59";
|
||||
const TEST_DATA: &[u8] = b"{\"hello\":\"world\"}\n";
|
||||
const TEST_SIG: &str = "untrusted comment: signature from minisign secret key\n\
|
||||
RUQTYQi9Fe4trXY/WBxk++476WhTqtVd3hlNWQj5h5DF8keP8sEJn22LDG2hloNgJesXt6HsTQs9uktayRVp/HB4XfC6e+rhYAs=\n\
|
||||
trusted comment: timestamp:1784230084\tfile:test-data.json\thashed\n\
|
||||
znmVfINB4jFDR2a4wuY8rOKlUBeSDOFjMkHYDXV3vxvAjK+r4V12ae9ZRQkfVtQ1YIEmFXbnJfbxywg+NR/1AA==\n";
|
||||
|
||||
#[test]
|
||||
fn valid_signature_verifies() {
|
||||
verify_against_key(TEST_DATA, TEST_SIG, TEST_PUBKEY)
|
||||
.expect("known-good signature must verify");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tampered_bytes_fail_verification() {
|
||||
let tampered = b"{\"hello\":\"world!\"}\n".to_vec();
|
||||
assert!(verify_against_key(&tampered, TEST_SIG, TEST_PUBKEY).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_key_fails_verification() {
|
||||
// PUBKEY is the real production key — unrelated to the throwaway
|
||||
// TEST_PUBKEY the fixture was signed with, so it must not verify.
|
||||
assert!(verify_against_key(TEST_DATA, TEST_SIG, PUBKEY).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_signature_text_errors_cleanly() {
|
||||
assert!(verify_against_key(TEST_DATA, "not a real signature", TEST_PUBKEY).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_pubkey_constant_is_well_formed() {
|
||||
// Guards against a future typo/truncation in the hardcoded PUBKEY —
|
||||
// it must at least parse as a valid minisign public key.
|
||||
PublicKey::from_base64(PUBKEY).expect("PUBKEY must be a valid minisign public key");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ version.workspace = true
|
|||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
description = "Shared pywal + Catppuccin theming crate for the bread ecosystem"
|
||||
description = "Shared pywal-accented, fixed-dark-base theming crate for the bread ecosystem"
|
||||
repository = "https://github.com/Breadway/bread-ecosystem"
|
||||
keywords = ["theming", "pywal", "gtk4", "wayland"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name = "bread-theme"
|
||||
description = "Shared pywal + Catppuccin theming CLI for the bread ecosystem — generates the shared GTK4 stylesheet every bread app loads"
|
||||
description = "Shared pywal-accented, fixed-dark-base theming CLI for the bread ecosystem — generates the shared GTK4 stylesheet every bread app loads"
|
||||
binaries = ["bread-theme"]
|
||||
system_deps = []
|
||||
optional_system_deps = ["python-pywal"]
|
||||
|
|
|
|||
|
|
@ -24,31 +24,23 @@ pub mod tokens {
|
|||
pub const RADIUS_PILL: u16 = 999;
|
||||
}
|
||||
|
||||
/// Emit the `@define-color` block that all bread apps use.
|
||||
/// Apps append their own rules below this; user CSS goes on top.
|
||||
/// Emit the `@define-color` block that all bread apps use, plus the shared
|
||||
/// font rule.
|
||||
///
|
||||
/// Kept for API compatibility with older callers that only want the color
|
||||
/// variables (not the full [`stylesheet`] component rules). It used to carry
|
||||
/// its own hand-written `@define-color` block that predated the `accent` and
|
||||
/// computed-ink (`on-*`) colors — that duplication is exactly what let it
|
||||
/// drift out of sync and reintroduce the illegible-text bug (light pywal
|
||||
/// colors + no computed ink meant white-on-white / black-on-black text
|
||||
/// wherever a caller's own CSS referenced `@on-surface`, `@on-accent`, etc.,
|
||||
/// since those names simply didn't exist in this block). It now delegates
|
||||
/// to the same [`define_colors`] the full stylesheet uses, so there is only
|
||||
/// one color-block implementation and it cannot drift again.
|
||||
pub fn css_vars(p: &Palette) -> String {
|
||||
format!(
|
||||
"@define-color bg {bg};\n\
|
||||
@define-color fg {fg};\n\
|
||||
@define-color surface {c0};\n\
|
||||
@define-color red {c1};\n\
|
||||
@define-color green {c2};\n\
|
||||
@define-color yellow {c3};\n\
|
||||
@define-color blue {c4};\n\
|
||||
@define-color pink {c5};\n\
|
||||
@define-color teal {c6};\n\
|
||||
@define-color overlay {c7};\n\
|
||||
* {{ font-family: '{font}'; font-size: {size}px; }}\n",
|
||||
bg = p.background,
|
||||
fg = p.foreground,
|
||||
c0 = p.color0,
|
||||
c1 = p.color1,
|
||||
c2 = p.color2,
|
||||
c3 = p.color3,
|
||||
c4 = p.color4,
|
||||
c5 = p.color5,
|
||||
c6 = p.color6,
|
||||
c7 = p.color7,
|
||||
"{vars}* {{ font-family: '{font}'; font-size: {size}px; }}\n",
|
||||
vars = define_colors(p),
|
||||
font = tokens::FONT_FAMILY,
|
||||
size = tokens::FONT_SIZE_BASE,
|
||||
)
|
||||
|
|
@ -241,6 +233,33 @@ mod tests {
|
|||
assert!(css.contains("14px"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn css_vars_includes_accent_and_computed_ink_colors() {
|
||||
// Regression test: css_vars() used to be a second, hand-written
|
||||
// @define-color block that predated `accent` and the computed `on-*`
|
||||
// ink colors. Any caller whose own CSS referenced `@on-surface` /
|
||||
// `@on-accent` etc. against that older block would hit an undefined
|
||||
// color name — the illegible-text bug. css_vars() must now emit
|
||||
// exactly the same color set as the full stylesheet.
|
||||
let css = css_vars(&Palette::default());
|
||||
for name in &["accent", "on-bg", "on-surface", "on-accent", "on-red", "on-overlay"] {
|
||||
assert!(css.contains(&format!("@define-color {name} ")), "missing @define-color {name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn css_vars_and_stylesheet_agree_on_color_block() {
|
||||
// Both must derive their color variables from the same
|
||||
// `define_colors` implementation, so they can't drift apart again.
|
||||
let p = Palette::default();
|
||||
let vars = css_vars(&p);
|
||||
let sheet = stylesheet(&p);
|
||||
for name in &["bg", "fg", "surface", "overlay", "accent", "on-bg", "on-surface", "on-accent"] {
|
||||
let needle = format!("@define-color {name} ");
|
||||
assert!(vars.contains(&needle) && sheet.contains(&needle));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stylesheet_defines_canonical_colors_and_components() {
|
||||
let css = stylesheet(&Palette::default());
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ description = "Bread ecosystem package manager"
|
|||
[[products]]
|
||||
name = "bread-theme"
|
||||
repo = "Breadway/bread-ecosystem"
|
||||
description = "Shared pywal + Catppuccin theming CLI for the bread ecosystem"
|
||||
description = "Shared pywal-accented, fixed-dark-base theming CLI for the bread ecosystem"
|
||||
|
||||
[[products]]
|
||||
name = "bread"
|
||||
|
|
|
|||
|
|
@ -119,8 +119,12 @@ with open('${bakery_toml}', 'rb') as f:
|
|||
print(json.dumps(d.get('bread_deps', [])))
|
||||
" 2>/dev/null || echo "[]")"
|
||||
|
||||
# [[service]] entries → [{unit, enable}]
|
||||
services="$(python3 -c "
|
||||
# [[service]] entries → [{unit, enable, sha256}]. sha256 comes from the
|
||||
# actual unit file shipped in this version dir — the same
|
||||
# artifact-integrity guarantee binaries already get. A missing unit file
|
||||
# gets an empty sha256; install.rs refuses to install an unverified
|
||||
# download rather than silently skipping the check.
|
||||
service_units="$(python3 -c "
|
||||
import tomllib, json
|
||||
with open('${bakery_toml}', 'rb') as f:
|
||||
d = tomllib.load(f)
|
||||
|
|
@ -128,7 +132,24 @@ svcs = d.get('service', [])
|
|||
print(json.dumps([{'unit': s['unit'], 'enable': s.get('enable', False)} for s in svcs]))
|
||||
" 2>/dev/null || echo "[]")"
|
||||
|
||||
# [config] → {dir, example?} or null
|
||||
services="[]"
|
||||
while IFS= read -r svc_entry; do
|
||||
[[ -z "${svc_entry}" ]] && continue
|
||||
unit_name="$(echo "${svc_entry}" | jq -r '.unit')"
|
||||
enable="$(echo "${svc_entry}" | jq -r '.enable')"
|
||||
unit_path="${version_dir}/${unit_name}"
|
||||
unit_sha256=""
|
||||
if [[ -f "${unit_path}" ]]; then
|
||||
unit_sha256="$(sha256sum "${unit_path}" | awk '{print $1}')"
|
||||
else
|
||||
echo " warning: service unit '${unit_name}' not found at ${unit_path}" >&2
|
||||
fi
|
||||
svc_json="$(jq -n --arg unit "${unit_name}" --argjson enable "${enable}" --arg sha256 "${unit_sha256}" \
|
||||
'{unit: $unit, enable: $enable, sha256: $sha256}')"
|
||||
services="$(jq -n --argjson arr "${services}" --argjson e "${svc_json}" '$arr + [$e]')"
|
||||
done < <(echo "${service_units}" | jq -c '.[]')
|
||||
|
||||
# [config] → {dir, example?, example_sha256?} or null
|
||||
config="$(python3 -c "
|
||||
import tomllib, json
|
||||
with open('${bakery_toml}', 'rb') as f:
|
||||
|
|
@ -142,6 +163,19 @@ if cfg:
|
|||
else:
|
||||
print('null')
|
||||
" 2>/dev/null || echo "null")"
|
||||
if [[ "${config}" != "null" ]]; then
|
||||
example_name="$(echo "${config}" | jq -r '.example // empty')"
|
||||
if [[ -n "${example_name}" ]]; then
|
||||
example_path="${version_dir}/${example_name}"
|
||||
example_sha256=""
|
||||
if [[ -f "${example_path}" ]]; then
|
||||
example_sha256="$(sha256sum "${example_path}" | awk '{print $1}')"
|
||||
else
|
||||
echo " warning: config.example '${example_name}' not found at ${example_path}" >&2
|
||||
fi
|
||||
config="$(echo "${config}" | jq -c --arg sha "${example_sha256}" '. + {example_sha256: $sha}')"
|
||||
fi
|
||||
fi
|
||||
|
||||
post_install="$(python3 -c "
|
||||
import tomllib, json
|
||||
|
|
@ -194,3 +228,42 @@ jq -n \
|
|||
> "${OUT}"
|
||||
|
||||
echo "wrote ${OUT}"
|
||||
|
||||
# Sign the index so `bakery` can verify it before trusting a single byte.
|
||||
# Every artifact sha256 and post_install hook string lives inside index.json,
|
||||
# so a valid signature over these raw bytes transitively covers all of it —
|
||||
# no separate per-artifact signing is needed.
|
||||
#
|
||||
# MINISIGN_SEC_KEY must point at the *secret* key file generated with
|
||||
# `minisign -G`. It is intentionally never read from inside either git repo;
|
||||
# point it at wherever the key actually lives on the machine that runs this
|
||||
# script (e.g. a root-only path on hestia), and set MINISIGN_SEC_KEY_PASSWORD
|
||||
# too if the key was generated with a password.
|
||||
#
|
||||
# This step is a no-op (with a loud warning) if the key isn't configured, so
|
||||
# existing unsigned publishing flows don't break until the key is actually
|
||||
# wired up — see the handoff note in the fix commit for this repo.
|
||||
if [[ -n "${MINISIGN_SEC_KEY:-}" ]]; then
|
||||
if [[ ! -f "${MINISIGN_SEC_KEY}" ]]; then
|
||||
echo "ERROR: MINISIGN_SEC_KEY=${MINISIGN_SEC_KEY} does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v minisign >/dev/null 2>&1; then
|
||||
echo "ERROR: MINISIGN_SEC_KEY is set but the 'minisign' binary is not installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
sign_args=(-S -s "${MINISIGN_SEC_KEY}" -m "${OUT}" -x "${OUT}.minisig")
|
||||
if [[ -n "${MINISIGN_SEC_KEY_PASSWORD:-}" ]]; then
|
||||
MINISIGN_PASSWORD="${MINISIGN_SEC_KEY_PASSWORD}" minisign "${sign_args[@]}" </dev/null
|
||||
else
|
||||
# -W: the key has no password (matches how CI-facing signing keys are
|
||||
# normally generated, since there's no human to type a passphrase).
|
||||
minisign -W "${sign_args[@]}" </dev/null
|
||||
fi
|
||||
echo "signed ${OUT} -> ${OUT}.minisig"
|
||||
else
|
||||
echo "WARNING: MINISIGN_SEC_KEY not set — index.json was NOT signed." >&2
|
||||
echo " bakery clients built with signature verification will reject" >&2
|
||||
echo " this index. Set MINISIGN_SEC_KEY before running this in" >&2
|
||||
echo " production once the signing key has been provisioned." >&2
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@
|
|||
# Or: curl -sSfL https://breadway.dev/get | sh
|
||||
set -eu
|
||||
|
||||
# Pinned minisign public key for the bakery release binary. Matches the
|
||||
# PUBKEY constant in bakery/src/manifest.rs (same keypair signs both
|
||||
# index.json and the bakery binary itself). Do not source this from the
|
||||
# network — it must be baked into this script so a compromised dl server
|
||||
# can't swap it out along with a malicious binary.
|
||||
BAKERY_MINISIGN_PUBKEY="RWRh2Zr5SUinvVFCtD7S7HwGjfrye6j31Xq2mYXRdkGFDWe3yHF7W11K"
|
||||
|
||||
BAKERY_VERSION="${BAKERY_VERSION:-latest}"
|
||||
BIN_DIR="${BAKERY_BIN_DIR:-$HOME/.local/bin}"
|
||||
|
||||
|
|
@ -19,12 +26,16 @@ if [ "${BAKERY_VERSION}" = "latest" ]; then
|
|||
DL_PRIMARY="https://dl.breadway.dev/bakery/latest/bakery-x86_64"
|
||||
DL_FALLBACK="https://github.com/Breadway/bread-ecosystem/releases/latest/download/bakery-x86_64"
|
||||
SHA256_URL="https://dl.breadway.dev/bakery/latest/bakery-x86_64.sha256"
|
||||
SIG_URL="https://dl.breadway.dev/bakery/latest/bakery-x86_64.minisig"
|
||||
SIG_FALLBACK="https://github.com/Breadway/bread-ecosystem/releases/latest/download/bakery-x86_64.minisig"
|
||||
else
|
||||
# Strip a leading 'v' if the caller included it, then add it back consistently.
|
||||
ver="${BAKERY_VERSION#v}"
|
||||
DL_PRIMARY="https://dl.breadway.dev/bakery/${ver}/bakery-x86_64"
|
||||
DL_FALLBACK="https://github.com/Breadway/bread-ecosystem/releases/download/v${ver}/bakery-x86_64"
|
||||
SHA256_URL="https://dl.breadway.dev/bakery/${ver}/bakery-x86_64.sha256"
|
||||
SIG_URL="https://dl.breadway.dev/bakery/${ver}/bakery-x86_64.minisig"
|
||||
SIG_FALLBACK="https://github.com/Breadway/bread-ecosystem/releases/download/v${ver}/bakery-x86_64.minisig"
|
||||
fi
|
||||
|
||||
# Pick a download tool.
|
||||
|
|
@ -38,12 +49,48 @@ fi
|
|||
|
||||
mkdir -p "${BIN_DIR}"
|
||||
TMP="$(mktemp)"
|
||||
trap 'rm -f "${TMP}" "${TMP}.sha256"' EXIT
|
||||
trap 'rm -f "${TMP}" "${TMP}.sha256" "${TMP}.minisig"' EXIT
|
||||
|
||||
echo "downloading bakery…"
|
||||
if fetch "${DL_PRIMARY}" "${TMP}" 2>/dev/null; then
|
||||
echo " from dl.breadway.dev"
|
||||
# Verify checksum when available from primary.
|
||||
sig_url="${SIG_URL}"
|
||||
checksum_only_fallback_note=" warning: could not fetch checksum — skipping verification"
|
||||
elif fetch "${DL_FALLBACK}" "${TMP}" 2>/dev/null; then
|
||||
echo " from GitHub (fallback)"
|
||||
sig_url="${SIG_FALLBACK}"
|
||||
checksum_only_fallback_note=" warning: no checksum available for GitHub fallback download"
|
||||
else
|
||||
die "failed to download bakery from both primary and fallback URLs"
|
||||
fi
|
||||
|
||||
# Signature verification is the authoritative check: it proves the binary
|
||||
# was produced by whoever holds the bakery signing key, not just that bytes
|
||||
# match whatever the same (possibly compromised) server also reports as the
|
||||
# checksum. Prefer it whenever both a .minisig is published and a minisign
|
||||
# verifier is available on this machine.
|
||||
sig_verified=0
|
||||
if fetch "${sig_url}" "${TMP}.minisig" 2>/dev/null; then
|
||||
if command -v minisign >/dev/null 2>&1; then
|
||||
if minisign -V -q -m "${TMP}" -x "${TMP}.minisig" -P "${BAKERY_MINISIGN_PUBKEY}"; then
|
||||
echo " signature verified (minisign)"
|
||||
sig_verified=1
|
||||
else
|
||||
die "minisign signature verification FAILED — refusing to install a binary that doesn't match the pinned bakery key"
|
||||
fi
|
||||
else
|
||||
echo " warning: 'minisign' is not installed — cannot verify the binary's" >&2
|
||||
echo " warning: signature, only its checksum. Install minisign for the" >&2
|
||||
echo " warning: strongest guarantee: pacman -S minisign / apt install minisign" >&2
|
||||
fi
|
||||
else
|
||||
echo " warning: no .minisig published for this release yet — signature not verified" >&2
|
||||
fi
|
||||
|
||||
# Checksum is a secondary, best-effort check (kept for defense in depth and
|
||||
# for the case where minisign isn't installed). It is not a substitute for
|
||||
# signature verification: both the binary and its checksum typically come
|
||||
# from the same server, so a compromised server can serve a matching pair.
|
||||
if fetch "${SHA256_URL}" "${TMP}.sha256" 2>/dev/null; then
|
||||
expected="$(awk '{print $1}' "${TMP}.sha256")"
|
||||
actual="$(sha256sum "${TMP}" | awk '{print $1}')"
|
||||
|
|
@ -52,14 +99,11 @@ if fetch "${DL_PRIMARY}" "${TMP}" 2>/dev/null; then
|
|||
fi
|
||||
echo " checksum verified"
|
||||
else
|
||||
echo " warning: could not fetch checksum — skipping verification"
|
||||
echo "${checksum_only_fallback_note}"
|
||||
fi
|
||||
elif fetch "${DL_FALLBACK}" "${TMP}" 2>/dev/null; then
|
||||
echo " from GitHub (fallback)"
|
||||
# No .sha256 on the GitHub fallback path; proceed without verification.
|
||||
echo " warning: checksum not verified for GitHub fallback download"
|
||||
else
|
||||
die "failed to download bakery from both primary and fallback URLs"
|
||||
|
||||
if [ "${sig_verified}" -ne 1 ]; then
|
||||
echo " warning: proceeding WITHOUT a verified signature on the bakery binary" >&2
|
||||
fi
|
||||
|
||||
chmod +x "${TMP}"
|
||||
|
|
|
|||
|
|
@ -110,4 +110,12 @@ check "post_install[0]" \
|
|||
"echo installed" \
|
||||
"$(jq -r '.packages.fakepkg.post_install[0]' "${OUT}")"
|
||||
|
||||
check "services[0].sha256" \
|
||||
"$(sha256sum "${PKG_VER_DIR}/fakepkg.service" | awk '{print $1}')" \
|
||||
"$(jq -r '.packages.fakepkg.services[0].sha256' "${OUT}")"
|
||||
|
||||
check "config.example_sha256" \
|
||||
"$(sha256sum "${PKG_VER_DIR}/fakepkg.example.toml" | awk '{print $1}')" \
|
||||
"$(jq -r '.packages.fakepkg.config.example_sha256' "${OUT}")"
|
||||
|
||||
echo "OK: all gen-index assertions passed"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue