diff --git a/.forgejo/workflows/mirror.yml b/.forgejo/workflows/mirror.yml new file mode 100644 index 0000000..6a2f9ab --- /dev/null +++ b/.forgejo/workflows/mirror.yml @@ -0,0 +1,21 @@ +name: Mirror to GitHub + +on: + push: + branches: ['**'] + tags: ['**'] + +jobs: + mirror: + runs-on: [self-hosted, hestia] + steps: + - name: Mirror to GitHub + run: | + set -euo pipefail + git clone --mirror "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" repo.git + cd repo.git + # Mirror only branches and tags (not refs/pull/*, which GitHub rejects); + # --prune deletes GitHub refs that no longer exist on Forgejo. + git push --prune \ + "https://x-access-token:${{ secrets.MIRROR_TOKEN }}@github.com/Breadway/bos.git" \ + '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' diff --git a/.forgejo/workflows/package.yml b/.forgejo/workflows/package.yml new file mode 100644 index 0000000..aaf7eb7 --- /dev/null +++ b/.forgejo/workflows/package.yml @@ -0,0 +1,40 @@ +name: Build and publish package + +on: + push: + tags: ['v*'] + +jobs: + package: + runs-on: [self-hosted, hestia] + container: + image: archlinux:latest + steps: + # Note: no actions/checkout — the archlinux image has no Node, which JS + # actions require. Everything runs as shell steps and clones manually. + - name: Build and publish + env: + PUBLISH_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -euo pipefail + VERSION="${GITHUB_REF_NAME#v}" + pacman -Syu --noconfirm base-devel git rust cargo gtk4 glib2 + useradd -m builder + git config --global --add safe.directory '*' + git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /home/builder/src + cd /home/builder/src + git archive --format=tar.gz --prefix="bos-settings-${VERSION}/" HEAD \ + > packaging/arch/bos-settings-${VERSION}.tar.gz + SHA=$(sha256sum packaging/arch/bos-settings-${VERSION}.tar.gz | awk '{print $1}') + sed -i "s/^pkgver=.*/pkgver=${VERSION}/" packaging/arch/PKGBUILD + sed -i "s/^sha256sums=.*/sha256sums=('${SHA}')/" packaging/arch/PKGBUILD + chown -R builder:builder /home/builder/src + # --nocheck: packaging builds the artifact; tests belong in a CI job. + su builder -c "cd /home/builder/src/packaging/arch && makepkg -f --noconfirm --nocheck" + PKG=$(find /home/builder/src/packaging/arch -name '*.pkg.tar.zst' | head -1) + curl -fsS -X PUT \ + -H "Authorization: token ${PUBLISH_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@${PKG}" \ + "https://git.breadway.dev/api/packages/Breadway/arch/os" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c7aaf --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Rust build artifacts +/target/ +**/*.pdb + +# Editor / IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.direnv/ + +# OS artifacts +.DS_Store +Thumbs.db +desktop.ini + +# Environment / secrets +.env +.env.local +*.env.* +secrets/ +*.pem +*.key +*.p12 + +# archiso build artifacts (these are large and reproducible) +/iso-build/ +/iso-out/ +*.iso +*.img + +# Runtime / logs +*.log +logs/ +*.pid +*.sock + +# Claude Code local agent state +.claude/ diff --git a/Cargo.lock b/Cargo.lock index a8c150b..a70ddc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -18,6 +30,7 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" name = "bos-settings" version = "0.1.0" dependencies = [ + "async-channel", "glib", "gtk4", "serde", @@ -58,12 +71,48 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "field-offset" version = "0.3.6" @@ -457,6 +506,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "pin-project-lite" version = "0.2.17" diff --git a/can-you-begin-a-composed-beacon.md b/DESIGN.md similarity index 100% rename from can-you-begin-a-composed-beacon.md rename to DESIGN.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e837178 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Breadway + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bakery.toml b/bakery.toml new file mode 100644 index 0000000..632de76 --- /dev/null +++ b/bakery.toml @@ -0,0 +1,12 @@ +name = "bos-settings" +description = "System settings app for Bread OS" +binaries = ["bos-settings"] +system_deps = ["gtk4", "glib2"] +optional_system_deps = ["snapper"] +bread_deps = [] + +[config] +dir = "~/.config" + +[install] +post_install = [] diff --git a/bos-settings/src/config/mod.rs b/bos-settings/src/config/mod.rs index da3b8eb..bd0c4af 100644 --- a/bos-settings/src/config/mod.rs +++ b/bos-settings/src/config/mod.rs @@ -15,10 +15,13 @@ pub fn save(path: &Path, val: &T) -> Result<(), Box PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| { - std::env::var("XDG_CONFIG_HOME") - .map(|p| PathBuf::from(p).parent().unwrap_or(Path::new("/")).to_string_lossy().to_string()) - .unwrap_or_else(|_| "/home/user".to_string()) - }); + // Honour XDG_CONFIG_HOME if set; otherwise fall back to $HOME/.config. + if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { + let p = PathBuf::from(xdg); + if p.is_absolute() { + return p; + } + } + let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); PathBuf::from(home).join(".config") } diff --git a/bos-settings/src/main.rs b/bos-settings/src/main.rs index fc13dc2..a5e73fd 100644 --- a/bos-settings/src/main.rs +++ b/bos-settings/src/main.rs @@ -2,6 +2,8 @@ mod config; mod theme; mod ui; +use gtk4::prelude::*; + fn main() { let app = gtk4::Application::builder() .application_id("com.breadway.bos-settings") diff --git a/bos-settings/src/state.rs b/bos-settings/src/state.rs deleted file mode 100644 index e0e760e..0000000 --- a/bos-settings/src/state.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub struct AppState { - pub current_view: String, -} - -impl AppState { - pub fn new() -> Self { - Self { - current_view: "snapshots".to_string(), - } - } -} diff --git a/bos-settings/src/theme.rs b/bos-settings/src/theme.rs index ae850d0..6be4f51 100644 --- a/bos-settings/src/theme.rs +++ b/bos-settings/src/theme.rs @@ -1,4 +1,3 @@ -use gtk4::prelude::*; use gtk4::CssProvider; const CSS: &str = r#" diff --git a/bos-settings/src/ui/views/breadbar.rs b/bos-settings/src/ui/views/breadbar.rs index 9f1ceb7..dd49a52 100644 --- a/bos-settings/src/ui/views/breadbar.rs +++ b/bos-settings/src/ui/views/breadbar.rs @@ -3,10 +3,10 @@ use gtk4::{Box as GBox, Button, Label, Orientation, ScrolledWindow, TextView}; use std::path::PathBuf; fn css_path() -> PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string()); - PathBuf::from(home).join(".config/breadbar/style.css") + crate::config::config_dir().join("breadbar/style.css") } + pub fn build() -> GBox { let path = css_path(); let existing_css = std::fs::read_to_string(&path).unwrap_or_default(); diff --git a/bos-settings/src/ui/views/hyprland.rs b/bos-settings/src/ui/views/hyprland.rs index 0ed704d..fba4537 100644 --- a/bos-settings/src/ui/views/hyprland.rs +++ b/bos-settings/src/ui/views/hyprland.rs @@ -23,8 +23,7 @@ fn get_monitors() -> Vec { } fn hypr_path(name: &str) -> std::path::PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string()); - std::path::PathBuf::from(home).join(".config/hypr").join(name) + crate::config::config_dir().join("hypr").join(name) } pub fn build() -> GBox { @@ -51,7 +50,7 @@ pub fn build() -> GBox { for mon in &monitors { let lbl = Label::new(Some(mon)); lbl.set_xalign(0.0); - lbl.set_monospace(true); + lbl.add_css_class("monospace"); vbox.append(&lbl); } } diff --git a/bos-settings/src/ui/views/packages.rs b/bos-settings/src/ui/views/packages.rs index 6b3aedc..1281c44 100644 --- a/bos-settings/src/ui/views/packages.rs +++ b/bos-settings/src/ui/views/packages.rs @@ -8,7 +8,7 @@ use std::io::{BufRead, BufReader}; use std::process::{Command, Stdio}; fn read_installed() -> HashMap { - let home = std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string()); + let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); let path = std::path::Path::new(&home) .join(".local/state/bakery/installed.json"); @@ -132,7 +132,7 @@ pub fn build() -> GBox { Ok(mut child) => { std::thread::spawn(move || { let _ = child.wait(); }); } - Err(e) => eprintln!("bakery update failed: {e}"), + Err(_) => {} // bakery not found; button is a no-op } }); diff --git a/bos-settings/src/ui/window.rs b/bos-settings/src/ui/window.rs index 0088675..c07a231 100644 --- a/bos-settings/src/ui/window.rs +++ b/bos-settings/src/ui/window.rs @@ -1,5 +1,5 @@ use gtk4::prelude::*; -use gtk4::{Application, ApplicationWindow, Box as GBox, Orientation, Paned, Stack}; +use gtk4::{Application, ApplicationWindow, Orientation, Paned, Stack}; use super::sidebar; use super::views; @@ -12,7 +12,7 @@ pub fn build_ui(app: &Application) { .default_height(640) .build(); - crate::theme::load(&window.display()); + crate::theme::load(&WidgetExt::display(&window)); let hpaned = Paned::new(Orientation::Horizontal); hpaned.set_position(190); diff --git a/bread_white.svg b/bread_white.svg new file mode 100644 index 0000000..b705951 --- /dev/null +++ b/bread_white.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/iso/airootfs/etc/calamares/branding/bos/branding.desc b/iso/airootfs/etc/calamares/branding/bos/branding.desc index d3034ab..ff72dd7 100644 --- a/iso/airootfs/etc/calamares/branding/bos/branding.desc +++ b/iso/airootfs/etc/calamares/branding/bos/branding.desc @@ -26,4 +26,4 @@ style: sidebarBackground: "#3b4252" sidebarText: "#eceff4" sidebarTextSelect: "#5e81ac" - sidebarTextHighlight:"#eceff4" + sidebarTextHighlight: "#eceff4" diff --git a/iso/airootfs/etc/calamares/branding/bos/languages.png b/iso/airootfs/etc/calamares/branding/bos/languages.png new file mode 100644 index 0000000..ee04ad0 Binary files /dev/null and b/iso/airootfs/etc/calamares/branding/bos/languages.png differ diff --git a/iso/airootfs/etc/calamares/branding/bos/logo.png b/iso/airootfs/etc/calamares/branding/bos/logo.png new file mode 100644 index 0000000..30bee39 Binary files /dev/null and b/iso/airootfs/etc/calamares/branding/bos/logo.png differ diff --git a/iso/airootfs/etc/calamares/branding/bos/logo.svg b/iso/airootfs/etc/calamares/branding/bos/logo.svg new file mode 100644 index 0000000..b705951 --- /dev/null +++ b/iso/airootfs/etc/calamares/branding/bos/logo.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/iso/packages.x86_64 b/iso/packages.x86_64 index 32c97d4..b69e43c 100644 --- a/iso/packages.x86_64 +++ b/iso/packages.x86_64 @@ -62,13 +62,18 @@ foot # File manager nautilus -# Installer — sourced from [breadway] repo (see pacman.conf) +# Installer — from the official extra repo calamares calamares-qt6 # Bread ecosystem — sourced from [breadway] repo bakery +# Input / screen utilities +brightnessctl +grim +slurp + # Utilities sudo git diff --git a/iso/pacman.conf b/iso/pacman.conf index c071a87..04dbb8a 100644 --- a/iso/pacman.conf +++ b/iso/pacman.conf @@ -26,13 +26,18 @@ Include = /etc/pacman.d/mirrorlist Include = /etc/pacman.d/mirrorlist # ----------------------------------------------------------------------- -# Breadway custom repo — provides: bakery, calamares (pre-built), and the -# bread ecosystem packages (bread, breadbar, breadbox, breadcrumbs, breadpad, -# bos-settings). +# Breadway custom repo — provides: bakery and the bread ecosystem packages +# (bread, breadbar, breadbox, breadcrumbs, breadpad, bos-settings). +# (calamares comes from the official extra repo, not here.) # -# TODO: Replace this URL with the actual hosted repo before building. -# See: https://github.com/Breadway/repo for setup instructions. +# Packages are published to the Forgejo Arch registry (group "os") by the +# .forgejo/workflows/package.yml workflow in each repo, on tag push. +# +# TODO: packages are currently unsigned (TrustAll). For production, sign +# them in CI with a GPG key and switch to SigLevel = Required. # ----------------------------------------------------------------------- -[breadway] +# The section name must match Forgejo's served db filename +# ({owner}.{group}.{domain}.db) — pacman fetches "
.db" from Server. +[Breadway.os.git.breadway.dev] SigLevel = Optional TrustAll -Server = https://repo.breadway.dev/$arch +Server = https://git.breadway.dev/api/packages/Breadway/arch/os/$arch diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD new file mode 100644 index 0000000..6747f88 --- /dev/null +++ b/packaging/arch/PKGBUILD @@ -0,0 +1,38 @@ +# Maintainer: Breadway + +pkgname=bos-settings +pkgver=0.1.0 +pkgrel=1 +pkgdesc="System settings app for Bread OS" +arch=('x86_64') +url="https://github.com/Breadway/bos" +license=('MIT') +# Some Rust deps (ring/mlua) build vendored C/asm into static archives; makepkg's +# default -flto=auto emits GCC LTO bitcode the Rust (lld) link cannot read, +# causing undefined-symbol errors. Disable LTO. +options=(!lto !debug) +depends=('gtk4' 'glib2' 'hicolor-icon-theme') +optdepends=( + 'snapper: snapshot management view' +) +makedepends=('rust' 'cargo') +source=("${pkgname}-${pkgver}.tar.gz") +sha256sums=('SKIP') + +build() { + cd "${srcdir}/${pkgname}-${pkgver}" + cargo build --release --locked -p bos-settings +} + +check() { + cd "${srcdir}/${pkgname}-${pkgver}" + cargo test --release --locked -p bos-settings +} + +package() { + cd "${srcdir}/${pkgname}-${pkgver}" + install -Dm755 target/release/bos-settings "${pkgdir}/usr/bin/bos-settings" + install -Dm644 packaging/arch/bos-settings.desktop \ + "${pkgdir}/usr/share/applications/bos-settings.desktop" + install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" +} diff --git a/packaging/arch/README.md b/packaging/arch/README.md new file mode 100644 index 0000000..af5acc0 --- /dev/null +++ b/packaging/arch/README.md @@ -0,0 +1,25 @@ +Arch packaging +============== + +`PKGBUILD` builds and installs `bos-settings` from source. + +## Local build + +```bash +makepkg -si +``` + +## Before publishing to [breadway] repo + +1. Tag a release on GitHub. +2. Update `pkgver` to match the tag. +3. Update `source` to the release tarball URL. +4. Run `updpkgsums` (or manually set `sha256sums`). + +## Runtime dependencies + +| Package | Required | Notes | +|---------|----------|-------| +| `gtk4` | yes | UI toolkit | +| `glib2` | yes | always | +| `snapper` | optional | snapshot management view | diff --git a/packaging/arch/bos-settings.desktop b/packaging/arch/bos-settings.desktop new file mode 100644 index 0000000..5385adb --- /dev/null +++ b/packaging/arch/bos-settings.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=BOS Settings +Comment=System settings for Bread OS +Exec=bos-settings +Icon=preferences-system +Terminal=false +Type=Application +Categories=Settings;System; +StartupWMClass=bos-settings