From de382536aa486b4773baddb62db8351189eb9391 Mon Sep 17 00:00:00 2001 From: Breadway Date: Thu, 23 Jul 2026 10:10:30 +0800 Subject: [PATCH] Onboard onto bakery: bakery.toml + dev/beta/release CI, relocate content paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds bakery.toml (binaries, license_file, desktop_file, data_archive for the guide content directory) and the standard three-track CI workflows, mirroring the pattern used across the rest of the bread ecosystem. Also fixes the tour/troubleshooting-symptoms loaders (tour.rs, troubleshoot.rs) to check the bakery-writable user content root (~/.local/share/breadhelp/content) before the system path a pacman package would have used — the main ContentStore already did this via user_content_root(), these two were the last holdouts still hardcoded to /usr/share/breadhelp only. --- .forgejo/workflows/beta-release.yml | 81 +++++++++++++++++++++++++++++ .forgejo/workflows/dev-release.yml | 81 +++++++++++++++++++++++++++++ .forgejo/workflows/release.yml | 67 ++++++++++++++++++++++++ bakery.toml | 12 +++++ src/content/mod.rs | 2 +- src/content/tour.rs | 15 +++++- src/content/troubleshoot.rs | 21 +++++--- 7 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 .forgejo/workflows/beta-release.yml create mode 100644 .forgejo/workflows/dev-release.yml create mode 100644 .forgejo/workflows/release.yml create mode 100644 bakery.toml diff --git a/.forgejo/workflows/beta-release.yml b/.forgejo/workflows/beta-release.yml new file mode 100644 index 0000000..b4b5ec4 --- /dev/null +++ b/.forgejo/workflows/beta-release.yml @@ -0,0 +1,81 @@ +name: beta release + +# Publishes a beta-track build on every push to `beta` — a frozen +# stabilization branch cut from `dev` when ready to stabilize; only +# fix/ branches merged into `beta` should land here afterward. +# See bread-ecosystem's docs/release-channels.md for the three-track policy. +on: + push: + branches: ['beta'] + +jobs: + build: + runs-on: [self-hosted, hestia] + steps: + - name: checkout + run: | + set -euo pipefail + rm -rf src && mkdir src + git clone --branch beta --depth 1 \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src + + - name: build + run: cd src && cargo build --release --locked + + - name: test + run: cd src && cargo test --release --locked + + - name: compute beta version + run: | + set -euo pipefail + cd src + # Base the beta version off the latest published stable tag, + # not Cargo.toml — Cargo.toml can go stale relative to the last + # real release, which would make a beta build sort as OLDER than + # what's already installed and bakery would correctly refuse it. + LATEST_TAG="$(git ls-remote --tags --refs \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" 'v*' \ + | awk -F/ '{print $NF}' | sed 's/^v//' | sort -V | tail -1)" + if [ -n "${LATEST_TAG}" ]; then + CUR="${LATEST_TAG}" + else + CUR="$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')" + fi + IFS='.' read -r MA MI PA <<< "${CUR}" + SHA="$(git rev-parse --short HEAD)" + TS="$(date -u +%Y%m%d%H%M%S)" + echo "VERSION=${MA}.${MI}.$((PA + 1))-beta.${TS}+${SHA}" >> "$GITHUB_ENV" + + - name: prepare artifacts + run: | + set -euo pipefail + PKG_DIR="/srv/breadway-dl/beta/breadhelp/${VERSION}" + mkdir -p "${PKG_DIR}" + cp "src/target/release/breadhelp" "${PKG_DIR}/breadhelp-x86_64" + strip "${PKG_DIR}/breadhelp-x86_64" + sha256sum "${PKG_DIR}/breadhelp-x86_64" | awk '{print $1}' \ + > "${PKG_DIR}/breadhelp-x86_64.sha256" + cp src/packaging/breadhelp.desktop "${PKG_DIR}/" + cp src/LICENSE "${PKG_DIR}/" + tar czf "${PKG_DIR}/content.tar.gz" -C src content + cp src/bakery.toml "${PKG_DIR}/bakery.toml" + ln -sfn "${VERSION}" "/srv/breadway-dl/beta/breadhelp/latest" + + # No GitHub Release upload — beta, like dev, is only distributed via + # dl.breadway.dev/beta/. + - name: regenerate beta index.json + env: + MINISIGN_SEC_KEY: ${{ secrets.BAKERY_MINISIGN_SEC_KEY_PATH }} + run: | + set -euo pipefail + if [ -z "${MINISIGN_SEC_KEY:-}" ]; then + echo "::error::BAKERY_MINISIGN_SEC_KEY_PATH secret not set — refusing to regenerate beta index.json unsigned (would leave a stale signature mismatched against fresh content and break bakery for everyone on the beta track)" + exit 1 + fi + rm -rf /tmp/bread-ecosystem-ci-* 2>/dev/null || true + # mktemp: a fixed clone path races when multiple repos' dev/beta + # workflows run close together on the same self-hosted runner. + ECOSYSTEM_CI_DIR="$(mktemp -d /tmp/bread-ecosystem-ci-XXXXXX)" + git clone https://git.breadway.dev/Breadway/bread-ecosystem.git "${ECOSYSTEM_CI_DIR}" + TRACK=beta bash "${ECOSYSTEM_CI_DIR}/scripts/gen-index.sh" + rm -rf "${ECOSYSTEM_CI_DIR}" diff --git a/.forgejo/workflows/dev-release.yml b/.forgejo/workflows/dev-release.yml new file mode 100644 index 0000000..819e4f8 --- /dev/null +++ b/.forgejo/workflows/dev-release.yml @@ -0,0 +1,81 @@ +name: dev release + +# Publishes a dev-track build on every push to `dev` — +# separate from release.yml's tag-triggered stable releases. See +# bread-ecosystem's docs/release-channels.md for the three-track policy +# this is part of. +on: + push: + branches: ['dev'] + +jobs: + build: + runs-on: [self-hosted, hestia] + steps: + - name: checkout + run: | + set -euo pipefail + rm -rf src && mkdir src + git clone --branch dev --depth 1 \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src + + - name: build + run: cd src && cargo build --release --locked + + - name: test + run: cd src && cargo test --release --locked + + - name: compute dev version + run: | + set -euo pipefail + cd src + # Base the dev version off the latest published stable tag, + # not Cargo.toml — Cargo.toml can go stale relative to the last + # real release, which would make a dev build sort as OLDER than + # what's already installed and bakery would correctly refuse it. + LATEST_TAG="$(git ls-remote --tags --refs \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" 'v*' \ + | awk -F/ '{print $NF}' | sed 's/^v//' | sort -V | tail -1)" + if [ -n "${LATEST_TAG}" ]; then + CUR="${LATEST_TAG}" + else + CUR="$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')" + fi + IFS='.' read -r MA MI PA <<< "${CUR}" + SHA="$(git rev-parse --short HEAD)" + TS="$(date -u +%Y%m%d%H%M%S)" + echo "VERSION=${MA}.${MI}.$((PA + 1))-dev.${TS}+${SHA}" >> "$GITHUB_ENV" + + - name: prepare artifacts + run: | + set -euo pipefail + PKG_DIR="/srv/breadway-dl/dev/breadhelp/${VERSION}" + mkdir -p "${PKG_DIR}" + cp "src/target/release/breadhelp" "${PKG_DIR}/breadhelp-x86_64" + strip "${PKG_DIR}/breadhelp-x86_64" + sha256sum "${PKG_DIR}/breadhelp-x86_64" | awk '{print $1}' \ + > "${PKG_DIR}/breadhelp-x86_64.sha256" + cp src/packaging/breadhelp.desktop "${PKG_DIR}/" + cp src/LICENSE "${PKG_DIR}/" + tar czf "${PKG_DIR}/content.tar.gz" -C src content + cp src/bakery.toml "${PKG_DIR}/bakery.toml" + ln -sfn "${VERSION}" "/srv/breadway-dl/dev/breadhelp/latest" + + # No GitHub Release upload — dev, like the other non-stable track, + # is only distributed via dl.breadway.dev/dev/. + - name: regenerate dev index.json + env: + MINISIGN_SEC_KEY: ${{ secrets.BAKERY_MINISIGN_SEC_KEY_PATH }} + run: | + set -euo pipefail + if [ -z "${MINISIGN_SEC_KEY:-}" ]; then + echo "::error::BAKERY_MINISIGN_SEC_KEY_PATH secret not set — refusing to regenerate dev index.json unsigned (would leave a stale signature mismatched against fresh content and break bakery for everyone on the dev track)" + exit 1 + fi + rm -rf /tmp/bread-ecosystem-ci-* 2>/dev/null || true + # mktemp: a fixed clone path races when multiple repos' dev/beta + # workflows run close together on the same self-hosted runner. + ECOSYSTEM_CI_DIR="$(mktemp -d /tmp/bread-ecosystem-ci-XXXXXX)" + git clone --branch dev https://git.breadway.dev/Breadway/bread-ecosystem.git "${ECOSYSTEM_CI_DIR}" + TRACK=dev bash "${ECOSYSTEM_CI_DIR}/scripts/gen-index.sh" + rm -rf "${ECOSYSTEM_CI_DIR}" diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 0000000..d9d6674 --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,67 @@ +name: release + +on: + push: + tags: ["v*"] + +jobs: + build: + runs-on: [self-hosted, hestia] + steps: + - name: checkout + run: | + set -euo pipefail + rm -rf src && mkdir src + git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src + + - name: build + run: cd src && cargo build --release --locked + + - name: test + run: cd src && cargo test --release --locked + + - name: prepare artifacts + run: | + set -euo pipefail + VERSION="${GITHUB_REF_NAME#v}" + PKG_DIR="/srv/breadway-dl/breadhelp/${VERSION}" + mkdir -p "${PKG_DIR}" + cp "src/target/release/breadhelp" "${PKG_DIR}/breadhelp-x86_64" + strip "${PKG_DIR}/breadhelp-x86_64" + sha256sum "${PKG_DIR}/breadhelp-x86_64" | awk '{print $1}' \ + > "${PKG_DIR}/breadhelp-x86_64.sha256" + cp src/packaging/breadhelp.desktop "${PKG_DIR}/" + cp src/LICENSE "${PKG_DIR}/" + tar czf "${PKG_DIR}/content.tar.gz" -C src content + cp src/bakery.toml "${PKG_DIR}/bakery.toml" + ln -sfn "${VERSION}" "/srv/breadway-dl/breadhelp/latest" + + - name: regenerate index.json + env: + MINISIGN_SEC_KEY: ${{ secrets.BAKERY_MINISIGN_SEC_KEY_PATH }} + run: | + set -euo pipefail + if [ -z "${MINISIGN_SEC_KEY:-}" ]; then + echo "::error::BAKERY_MINISIGN_SEC_KEY_PATH secret not set — refusing to regenerate index.json unsigned (would leave a stale signature mismatched against fresh content and break bakery for everyone)" + exit 1 + fi + rm -rf /tmp/bread-ecosystem-ci-* 2>/dev/null || true + ECOSYSTEM_CI_DIR="$(mktemp -d /tmp/bread-ecosystem-ci-XXXXXX)" + git clone https://git.breadway.dev/Breadway/bread-ecosystem.git "${ECOSYSTEM_CI_DIR}" + bash "${ECOSYSTEM_CI_DIR}/scripts/gen-index.sh" + rm -rf "${ECOSYSTEM_CI_DIR}" + + - name: upload to GitHub Release + env: + GH_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }} + run: | + set -euo pipefail + VERSION="${GITHUB_REF_NAME#v}" + PKG_DIR="/srv/breadway-dl/breadhelp/${VERSION}" + gh release create "${GITHUB_REF_NAME}" --repo Breadway/breadhelp \ + --title "breadhelp v${VERSION}" --generate-notes 2>/dev/null || true + gh release upload "${GITHUB_REF_NAME}" --repo Breadway/breadhelp \ + "${PKG_DIR}/breadhelp-x86_64" \ + "${PKG_DIR}/breadhelp-x86_64.sha256" \ + --clobber diff --git a/bakery.toml b/bakery.toml new file mode 100644 index 0000000..69fd764 --- /dev/null +++ b/bakery.toml @@ -0,0 +1,12 @@ +name = "breadhelp" +description = "Onboarding and help center for Bread OS" +binaries = ["breadhelp"] +system_deps = ["gtk4", "glib2", "gtk4-layer-shell", "hicolor-icon-theme"] +optional_system_deps = ["snapper"] +bread_deps = [] +license_file = "LICENSE" +desktop_file = "breadhelp.desktop" +data_archive = "content.tar.gz" + +[install] +post_install = [] diff --git a/src/content/mod.rs b/src/content/mod.rs index 814c6c9..520b5f0 100644 --- a/src/content/mod.rs +++ b/src/content/mod.rs @@ -41,7 +41,7 @@ pub struct ContentStore { guides: Vec, } -fn user_content_root() -> PathBuf { +pub(crate) fn user_content_root() -> PathBuf { if let Ok(xdg) = std::env::var("XDG_DATA_HOME") { let p = PathBuf::from(xdg); if p.is_absolute() { diff --git a/src/content/tour.rs b/src/content/tour.rs index d49709f..9bda162 100644 --- a/src/content/tour.rs +++ b/src/content/tour.rs @@ -86,15 +86,26 @@ struct TourFile { } const SYSTEM_TOUR_PATH: &str = "/usr/share/breadhelp/content/tours/onboarding.toml"; +const TOUR_SUBPATH: &str = "tours/onboarding.toml"; +/// Checks the user content root first (where bakery installs content — +/// see `content::user_content_root`), falling back to the system path a +/// pacman package would have used. Same "user copy wins" precedent as +/// `ContentStore::load`. pub fn load() -> Vec { - let Ok(text) = std::fs::read_to_string(Path::new(SYSTEM_TOUR_PATH)) else { + let user_path = super::user_content_root().join(TOUR_SUBPATH); + let path = if user_path.exists() { + user_path + } else { + Path::new(SYSTEM_TOUR_PATH).to_path_buf() + }; + let Ok(text) = std::fs::read_to_string(&path) else { return Vec::new(); }; match toml::from_str::(&text) { Ok(f) => f.steps, Err(e) => { - eprintln!("breadhelp: {SYSTEM_TOUR_PATH} failed to parse: {e}"); + eprintln!("breadhelp: {} failed to parse: {e}", path.display()); Vec::new() } } diff --git a/src/content/troubleshoot.rs b/src/content/troubleshoot.rs index 3613f84..7a39da4 100644 --- a/src/content/troubleshoot.rs +++ b/src/content/troubleshoot.rs @@ -36,13 +36,11 @@ struct SymptomFile { } const SYSTEM_SYMPTOMS_DIR: &str = "/usr/share/breadhelp/content/troubleshooting/_symptoms"; +const SYMPTOMS_SUBPATH: &str = "troubleshooting/_symptoms"; -/// Loads every `*.toml` file in the symptoms directory, keyed by file stem so -/// a `SymptomOption::goto` of `"no-sound:check-mute"` can be resolved. -pub fn load_all() -> std::collections::HashMap> { - let mut out = std::collections::HashMap::new(); - let Ok(entries) = std::fs::read_dir(Path::new(SYSTEM_SYMPTOMS_DIR)) else { - return out; +fn scan_symptoms_dir(dir: &Path, out: &mut std::collections::HashMap>) { + let Ok(entries) = std::fs::read_dir(dir) else { + return; }; for entry in entries.flatten() { let path = entry.path(); @@ -62,5 +60,16 @@ pub fn load_all() -> std::collections::HashMap> { Err(e) => eprintln!("breadhelp: {} failed to parse: {e}", path.display()), } } +} + +/// Loads every `*.toml` file in the symptoms directory, keyed by file stem so +/// a `SymptomOption::goto` of `"no-sound:check-mute"` can be resolved. Scans +/// the system path (a pacman package's location) then the user content root +/// (where bakery installs content — see `content::user_content_root`) — +/// same "user copy wins on collision" precedent as `ContentStore::load`. +pub fn load_all() -> std::collections::HashMap> { + let mut out = std::collections::HashMap::new(); + scan_symptoms_dir(Path::new(SYSTEM_SYMPTOMS_DIR), &mut out); + scan_symptoms_dir(&super::user_content_root().join(SYMPTOMS_SUBPATH), &mut out); out }