Compare commits
50 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bf071b406 | ||
|
|
d7a8f408b5 | ||
|
|
0aeb2c4b6b | ||
|
|
29a0070748 | ||
|
|
e471bfe83e | ||
|
|
7e97b5c04e | ||
|
|
20af38f826 | ||
|
|
b85e0e32fb | ||
|
|
7652d92b81 | ||
|
|
1f53377914 | ||
|
|
c46e348d6a | ||
|
|
31d0875791 | ||
|
|
3dd53f3fe6 | ||
|
|
4a16e18cf3 | ||
|
|
d484c8e933 | ||
|
|
a4af3aa938 | ||
|
|
9ea57d87c0 | ||
|
|
2a866144f4 | ||
|
|
122cd39cb1 | ||
|
|
356cc08dfe | ||
|
|
3a30cd004f | ||
|
|
23e60dffe0 | ||
|
|
b9544d517b | ||
|
|
12dbec5f32 | ||
|
|
de4e3b09ba | ||
|
|
2cbf46a836 | ||
|
|
6c3c33e4ae | ||
|
|
a7b3f70930 | ||
|
|
cbdeccd03e | ||
|
|
337d280f2b | ||
|
|
6135b2215d | ||
|
|
5a14288025 | ||
|
|
82c63bc4c4 | ||
|
|
0d550a1bda | ||
|
|
ed0eea3cb1 | ||
|
|
8e41d9fc2b | ||
|
|
30d94aa286 | ||
|
|
1bcd9588de | ||
|
|
12a8fa00bb | ||
|
|
0ee174e16e | ||
|
|
eda2c44c48 | ||
|
|
f98f21bbdd | ||
|
|
7ef51e8722 | ||
|
|
c259aa9e93 | ||
|
|
a2973b91eb | ||
|
|
769b6283e0 | ||
|
|
2c6feb4ea0 | ||
|
|
6f148e9a06 | ||
|
|
8682698402 | ||
|
|
c744e45c90 |
37
.forgejo/workflows/bibata.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
name: Build and publish bibata-cursor-theme
|
||||
|
||||
# Bibata is AUR-only (not in Arch's official repos), so BOS maintains an
|
||||
# in-house PKGBUILD and publishes the built package to the [breadway] repo.
|
||||
# It's the prebuilt -bin variant, so no build deps beyond base-devel.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'packaging/bibata/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
bibata:
|
||||
runs-on: [self-hosted, hestia]
|
||||
container:
|
||||
image: archlinux:latest
|
||||
steps:
|
||||
- name: Build and publish
|
||||
env:
|
||||
PUBLISH_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pacman -Syu --noconfirm base-devel git
|
||||
useradd -m builder
|
||||
git config --global --add safe.directory '*'
|
||||
# Clone the branch that triggered this run (not the default branch),
|
||||
# so the package can be built/published from a feature branch.
|
||||
git clone --depth 1 --branch "${GITHUB_REF_NAME}" \
|
||||
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /home/builder/src
|
||||
chown -R builder:builder /home/builder/src
|
||||
su builder -c "cd /home/builder/src/packaging/bibata && makepkg -f --noconfirm --nocheck"
|
||||
PKG=$(find /home/builder/src/packaging/bibata -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"
|
||||
35
.forgejo/workflows/calamares.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
name: Build and publish calamares
|
||||
|
||||
# Calamares is AUR-only (not in Arch's official repos), so BOS maintains an
|
||||
# in-house PKGBUILD and publishes the built package to the [breadway] repo.
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'packaging/calamares/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
calamares:
|
||||
runs-on: [self-hosted, hestia]
|
||||
container:
|
||||
image: archlinux:latest
|
||||
steps:
|
||||
- name: Build and publish
|
||||
env:
|
||||
PUBLISH_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pacman -Syu --noconfirm base-devel git cmake ninja \
|
||||
extra-cmake-modules qt6-tools qt6-translations libglvnd \
|
||||
kcoreaddons kpmcore libpwquality qt6-declarative qt6-svg yaml-cpp
|
||||
useradd -m builder
|
||||
git config --global --add safe.directory '*'
|
||||
git clone --depth 1 "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /home/builder/src
|
||||
chown -R builder:builder /home/builder/src
|
||||
su builder -c "cd /home/builder/src/packaging/calamares && makepkg -f --noconfirm --nocheck"
|
||||
PKG=$(find /home/builder/src/packaging/calamares -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"
|
||||
21
.forgejo/workflows/mirror.yml
Normal file
|
|
@ -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/*'
|
||||
40
.forgejo/workflows/package.yml
Normal file
|
|
@ -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"
|
||||
43
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# 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/
|
||||
|
||||
# Wallpaper source drop (baked copy lives in airootfs/usr/share/backgrounds)
|
||||
/Bread Background.png
|
||||
333
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
@ -16,20 +28,34 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
|||
|
||||
[[package]]
|
||||
name = "bos-settings"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"bread-theme",
|
||||
"glib",
|
||||
"gtk4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml 0.8.23",
|
||||
"toml_edit 0.22.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bread-theme"
|
||||
version = "0.2.3"
|
||||
source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.6#0c8c5c00e435fedff4f81e36d603424c153519a9"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"gtk4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.20.12"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e3bd0f4e25afa9cabc157908d14eeef9067d6448c49414d17b3fb55f0eadd0"
|
||||
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-sys-rs",
|
||||
|
|
@ -39,9 +65,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cairo-sys-rs"
|
||||
version = "0.20.10"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b"
|
||||
checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
|
@ -58,12 +84,75 @@ dependencies = [
|
|||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[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 = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
|
@ -138,9 +227,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf"
|
||||
version = "0.20.10"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c"
|
||||
checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646"
|
||||
dependencies = [
|
||||
"gdk-pixbuf-sys",
|
||||
"gio",
|
||||
|
|
@ -150,9 +239,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf-sys"
|
||||
version = "0.20.10"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96"
|
||||
checksum = "48f31b37b1fc4b48b54f6b91b7ef04c18e00b4585d98359dd7b998774bbd91fb"
|
||||
dependencies = [
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
|
|
@ -163,9 +252,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk4"
|
||||
version = "0.9.6"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60"
|
||||
checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"gdk-pixbuf",
|
||||
|
|
@ -178,9 +267,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk4-sys"
|
||||
version = "0.9.6"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a"
|
||||
checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
|
|
@ -194,10 +283,21 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.20.12"
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e27e276e7b6b8d50f6376ee7769a71133e80d093bdc363bd0af71664228b831"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
|
@ -212,22 +312,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.20.10"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83"
|
||||
checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.20.12"
|
||||
version = "0.22.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffc4b6e352d4716d84d7dde562dd9aee2a7d48beb872dd9ece7f2d1515b2d683"
|
||||
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
|
|
@ -246,12 +346,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.20.12"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8084af62f09475a3f529b1629c10c429d7600ee1398ae12dd3bf175d74e7145"
|
||||
checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
|
@ -259,9 +358,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.20.10"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215"
|
||||
checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
|
|
@ -269,9 +368,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.20.10"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda"
|
||||
checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
|
@ -280,9 +379,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "graphene-rs"
|
||||
version = "0.20.10"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b86dfad7d14251c9acaf1de63bc8754b7e3b4e5b16777b6f5a748208fe9519b"
|
||||
checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"graphene-sys",
|
||||
|
|
@ -291,9 +390,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "graphene-sys"
|
||||
version = "0.20.10"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df583a85ba2d5e15e1797e40d666057b28bc2f60a67c9c24145e6db2cc3861ea"
|
||||
checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
|
@ -303,9 +402,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gsk4"
|
||||
version = "0.9.6"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855"
|
||||
checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"gdk4",
|
||||
|
|
@ -318,9 +417,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gsk4-sys"
|
||||
version = "0.9.6"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc"
|
||||
checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk4-sys",
|
||||
|
|
@ -334,9 +433,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4"
|
||||
version = "0.9.7"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f274dd0102c21c47bbfa8ebcb92d0464fab794a22fad6c3f3d5f165139a326d6"
|
||||
checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"field-offset",
|
||||
|
|
@ -355,9 +454,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4-macros"
|
||||
version = "0.9.5"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999"
|
||||
checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
|
|
@ -367,9 +466,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4-sys"
|
||||
version = "0.9.6"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6"
|
||||
checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
|
|
@ -418,6 +517,15 @@ version = "0.2.186"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.2"
|
||||
|
|
@ -434,10 +542,16 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.20.12"
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6576b311f6df659397043a5fa8a021da8f72e34af180b44f7d57348de691ab5c"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438"
|
||||
dependencies = [
|
||||
"gio",
|
||||
"glib",
|
||||
|
|
@ -447,9 +561,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pango-sys"
|
||||
version = "0.20.10"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186909673fc09be354555c302c0b3dcf753cd9fa08dcb8077fa663c80fb243fa"
|
||||
checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
|
@ -457,6 +571,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"
|
||||
|
|
@ -496,6 +616,17 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
|
|
@ -614,6 +745,26 @@ version = "0.13.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
|
|
@ -718,13 +869,43 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -733,28 +914,46 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
|
|
@ -767,24 +966,48 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
|
|||
21
LICENSE
Normal file
|
|
@ -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.
|
||||
209
README.md
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
# BOS — Bread Operating System
|
||||
|
||||
An Arch-based, Hyprland desktop distribution that ships the [bread
|
||||
ecosystem](https://github.com/Breadway) preconfigured. One Calamares install
|
||||
produces a themed, bootable Wayland desktop — no manual Arch bootstrap, no
|
||||
wiring up dotfiles, no per-tool bakery installs.
|
||||
|
||||
> Design rationale and the btrfs/A-B roadmap live in [DESIGN.md](DESIGN.md).
|
||||
> This file is the practical overview: what's in the image, how to build it,
|
||||
> and how to test it.
|
||||
|
||||
## What you get
|
||||
|
||||
- **Compositor**: Hyprland with a native-Lua config (`hyprland.lua`), curated
|
||||
keybinds, snappy animations, blur, and pywal-driven colours on a black base.
|
||||
- **bread ecosystem**, baked into `/etc/skel` from bakery-managed binaries
|
||||
(no network needed at install time): `bread`/`breadd`, `breadbar` (status bar
|
||||
+ notification daemon), `breadbox` (launcher), `breadcrumbs` (Wi-Fi profiles),
|
||||
`breadpad` (notes/reminders), `breadman`, and the `bakery` package manager.
|
||||
- **bos-settings**: a GTK4 control panel that configures every bread\* app's
|
||||
config from a GUI (non-destructively), plus snapshot rollback and bakery
|
||||
updates. See below.
|
||||
- **Login**: greetd + tuigreet → Hyprland session.
|
||||
- **Boot splash**: Plymouth `bos` theme (logo + spinner, black background).
|
||||
- **Theming**: global dark across GTK3 (Adwaita-dark), GTK4/libadwaita
|
||||
(`color-scheme: prefer-dark`), and Qt (qt5ct/qt6ct Fusion dark); Papirus-Dark
|
||||
icons; Bibata cursor.
|
||||
- **Apps**: kitty, nautilus (+ gvfs), Zen browser, VLC, loupe, gnome-text-editor,
|
||||
gnome-calculator, file-roller, with file associations wired in `mimeapps.list`.
|
||||
- **Hardware**: pipewire audio, NetworkManager, BlueZ + blueman, CUPS printing
|
||||
with avahi mDNS discovery, TLP power management, fwupd firmware updates.
|
||||
- **Resilience**: btrfs + snapper + snap-pac + grub-btrfs snapshots on every
|
||||
pacman transaction; zram swap; ufw firewall (deny-incoming, mDNS allowed).
|
||||
|
||||
## Repo layout
|
||||
|
||||
```
|
||||
bos/
|
||||
├── Cargo.toml # workspace (members: bos-settings)
|
||||
├── bos-settings/ # GTK4 unified settings app (Rust)
|
||||
│ └── src/
|
||||
│ ├── config/mod.rs # non-destructive toml_edit config layer
|
||||
│ └── ui/{widgets,window,sidebar}.rs, ui/views/*.rs
|
||||
├── iso/ # archiso profile
|
||||
│ ├── profiledef.sh
|
||||
│ ├── packages.x86_64 # live + installed package set
|
||||
│ └── airootfs/ # files overlaid onto the image
|
||||
│ └── etc/
|
||||
│ ├── skel/ # default user dotfiles (hypr, kitty, gtk, …)
|
||||
│ └── calamares/ # installer config + post-install.sh
|
||||
├── packaging/ # in-house PKGBUILDs for AUR-only deps
|
||||
│ ├── arch/ # bos-settings
|
||||
│ ├── calamares/
|
||||
│ └── bibata/
|
||||
├── .forgejo/workflows/ # CI: build + publish packages to [breadway]
|
||||
├── build-local.sh # native ISO build for this machine
|
||||
└── DESIGN.md
|
||||
```
|
||||
|
||||
## Building the ISO
|
||||
|
||||
`build-local.sh` builds the image natively (no container) and bakes this
|
||||
machine's bakery-installed bread binaries into `/etc/skel`:
|
||||
|
||||
```sh
|
||||
sudo ./build-local.sh # release-quality (xz squashfs)
|
||||
sudo FAST_BUILD=1 ./build-local.sh # fast dev iteration (zstd squashfs)
|
||||
```
|
||||
|
||||
The ISO lands in `out/bos-<date>-x86_64.iso`. The script pins
|
||||
`SOURCE_DATE_EPOCH` (reproducible UUIDs) and rewrites the `[breadway]` repo URL
|
||||
to the Tailscale-reachable Forgejo registry for the build.
|
||||
|
||||
### Why some packages are in-house
|
||||
|
||||
`calamares`, `zen-browser-bin`, and `bibata-cursor-theme` are AUR-only. BOS
|
||||
keeps a PKGBUILD for each under `packaging/` and republishes the built package
|
||||
to the `[breadway]` repo via a Forgejo Actions workflow (built on the hestia
|
||||
self-hosted runner, published with a scoped registry token). `bos-settings`
|
||||
itself publishes the same way on a `v*` tag.
|
||||
|
||||
## Testing in a VM
|
||||
|
||||
A reusable, GPU-accelerated launcher lives at `~/bos-vm/run.sh`:
|
||||
|
||||
```sh
|
||||
~/bos-vm/run.sh install # boot the ISO installer (target disk attached)
|
||||
~/bos-vm/run.sh # boot the installed system from the disk
|
||||
```
|
||||
|
||||
It uses KVM + `-cpu host`, 8 GiB / 8 vCPU, and `virtio-vga-gl` with
|
||||
`-display gtk,gl=on` (virgl) — 3D acceleration is essential for a smooth
|
||||
Hyprland session in QEMU. The disk lives on NVMe (not the tmpfs `/tmp`) to
|
||||
avoid memory pressure.
|
||||
|
||||
## bos-settings
|
||||
|
||||
`bos-settings` edits each bread\* app's TOML **non-destructively**: it parses
|
||||
the file with `toml_edit`, changes only the keys a view exposes, and writes it
|
||||
back — preserving comments and any keys the UI doesn't model (calendar
|
||||
passwords, saved-network passwords, model paths). Views:
|
||||
|
||||
| View | Config |
|
||||
|------|--------|
|
||||
| bread | `bread/breadd.toml` — daemon, lua, modules, all adapters, events, notifications |
|
||||
| breadbar | `breadbar/style.css` override |
|
||||
| breadbox | `breadbox/config.toml` — launcher contexts |
|
||||
| breadcrumbs | `breadcrumbs/breadcrumbs.toml` — settings, saved networks, profiles |
|
||||
| breadpad | `breadpad/breadpad.toml` — settings, model + ollama, reminders, calendar |
|
||||
| Snapshots | `snapper` list / rollback / delete |
|
||||
| Packages | `bakery` installed list + updates |
|
||||
| Hyprland | open config in editor + monitor list |
|
||||
|
||||
Build standalone:
|
||||
|
||||
```sh
|
||||
cargo build --release -p bos-settings
|
||||
cargo test -p bos-settings # includes config round-trip tests
|
||||
```
|
||||
|
||||
## The bread ecosystem at a glance
|
||||
|
||||
| Tool | Role | Launch |
|
||||
|------|------|--------|
|
||||
| `bread` / `breadd` | Reactive automation daemon — normalises hardware/compositor signals into events dispatched to Lua modules | runs at login |
|
||||
| `breadbar` | Top status bar (workspaces, clock, stats, tray) **and** the notification daemon | runs at login |
|
||||
| `breadbox` | Application launcher | `SUPER+Space` |
|
||||
| `breadpad` | Notes & reminders (AI-classified, optional CalDAV sync) | `SUPER+U` |
|
||||
| `breadman` | Package-manager UI | `SUPER+M` |
|
||||
| `breadcrumbs` | Wi-Fi profile state machine (location-aware) | CLI / BOS Settings |
|
||||
| `bakery` | CLI package manager for the ecosystem | `bakery` |
|
||||
| `bos-settings` | Unified GTK4 control panel for all of the above + snapshots + updates | `SUPER+,` |
|
||||
|
||||
## Keyboard shortcuts
|
||||
|
||||
`SUPER` is the Windows/Cmd key. Press **`SUPER+/`** at any time for this
|
||||
cheatsheet in-session; first boot shows a short welcome (once).
|
||||
|
||||
| Keys | Action |
|
||||
|------|--------|
|
||||
| `SUPER+Return` | Terminal (kitty) |
|
||||
| `SUPER+Space` | App launcher (breadbox) |
|
||||
| `SUPER+E` / `SUPER+B` | Files (nautilus) / Browser (zen) |
|
||||
| `SUPER+U` / `SUPER+M` | breadpad / breadman |
|
||||
| `SUPER+,` / `SUPER+/` | BOS Settings / keybind cheatsheet |
|
||||
| `SUPER+L` / `SUPER+N` | Lock / log out |
|
||||
| `SUPER+Backspace` | Close window |
|
||||
| `SUPER+F` / `SUPER+V` / `SUPER+T` | Fullscreen / float / toggle split |
|
||||
| `SUPER+Shift+V` | Clipboard history |
|
||||
| `SUPER+Tab` | Last window |
|
||||
| `SUPER+Shift+S/C/P` | Screenshot region→file / region→clipboard / screen→file |
|
||||
| `SUPER+arrows` | Move focus |
|
||||
| `SUPER+Shift+h/j/k/l` | Move window |
|
||||
| `SUPER+Shift+arrows` | Resize window |
|
||||
| `SUPER+1..0` | Switch to workspace 1–10 |
|
||||
| `SUPER+Shift+1..0` | Move window to workspace |
|
||||
| `SUPER+[ / ]` | Previous / next workspace |
|
||||
| `SUPER+left/right-drag` | Move / resize window with the mouse |
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **GPUs**: ships the generic Mesa stack — AMD and Intel work out of the box.
|
||||
The **NVIDIA proprietary driver is not included**; NVIDIA users must install
|
||||
`nvidia`/`nvidia-utils` and set the usual Hyprland env vars after install.
|
||||
- **Virtual machines**: Hyprland needs GPU acceleration to be smooth. Use
|
||||
`virtio-vga-gl` + `-display gtk,gl=on` (virgl); plain software rendering is
|
||||
noticeably laggy.
|
||||
- **Wayland-first**: X11-only apps run through XWayland; a few may misbehave.
|
||||
- **Secure Boot**: not configured. Boot with Secure Boot disabled, or enroll
|
||||
your own keys. The installer writes both an NVRAM entry and the removable
|
||||
`EFI/BOOT/BOOTX64.EFI` fallback.
|
||||
- **Snapshots assume btrfs**: the snapper/grub-btrfs tooling expects the default
|
||||
btrfs subvolume layout the installer creates.
|
||||
|
||||
## Recovery
|
||||
|
||||
**An update broke something (system still boots):** open BOS Settings →
|
||||
Snapshots and roll back, or pick a pre-update snapshot from the **GRUB
|
||||
“snapshots” submenu** at boot, then run `snapper rollback` from the booted
|
||||
snapshot.
|
||||
|
||||
**The system won't boot (broken GRUB / lost EFI entry):**
|
||||
|
||||
1. Boot the BOS ISO and open a terminal (`SUPER+Return`).
|
||||
2. Mount the installed root and EFI, then chroot:
|
||||
```sh
|
||||
mount -o subvol=@ /dev/sdXN /mnt
|
||||
mount /dev/sdXP /mnt/boot/efi # the EFI partition
|
||||
arch-chroot /mnt
|
||||
```
|
||||
3. Reinstall the bootloader (the same sequence the installer uses):
|
||||
```sh
|
||||
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=BOS --recheck
|
||||
grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable --recheck
|
||||
grub-mkconfig -o /boot/grub/grub.cfg
|
||||
```
|
||||
|
||||
**Firmware shows “no boot device”:** select `EFI/BOOT/BOOTX64.EFI` from the
|
||||
firmware boot menu — the installer always writes that removable fallback.
|
||||
|
||||
## Boot architecture notes
|
||||
|
||||
archiso keeps the kernel and initramfs outside the squashfs, so the installer
|
||||
stages them explicitly: a `shellprocess@kernel` step copies the kernel + ucode
|
||||
into the target `/boot` and writes a stock mkinitcpio preset before the native
|
||||
`initcpio` module builds the initramfs. GRUB is **not** installed by Calamares'
|
||||
`bootloader`/`grubcfg` modules (they leave the ESP empty in this layout) —
|
||||
`post-install.sh` runs `grub-install` (NVRAM **and** `--removable`) +
|
||||
`grub-mkconfig` instead, which is the sequence verified to boot.
|
||||
12
bakery.toml
Normal file
|
|
@ -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 = []
|
||||
|
|
@ -1,12 +1,19 @@
|
|||
[package]
|
||||
name = "bos-settings"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
gtk4 = { version = "0.9", features = ["v4_12"] }
|
||||
glib = "0.20"
|
||||
gtk4 = { version = "0.11", features = ["v4_12"] }
|
||||
glib = "0.22"
|
||||
# Shared ecosystem theming — bos-settings loads the same generated stylesheet as
|
||||
# breadbar/breadbox/breadpad so the whole desktop looks consistent.
|
||||
bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.6", features = ["gtk"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
toml = "0.8"
|
||||
# toml_edit drives non-destructive config editing: it preserves comments and
|
||||
# any keys the UI doesn't model, so saving a single field never rewrites or
|
||||
# drops the rest of the user's config file.
|
||||
toml_edit = "0.22"
|
||||
async-channel = "2"
|
||||
|
|
|
|||
|
|
@ -1,24 +1,195 @@
|
|||
//! Non-destructive config editing.
|
||||
//!
|
||||
//! Every bread* app owns a TOML config that may contain keys, sections, and
|
||||
//! comments this settings app does not model (e.g. breadpad's calendar
|
||||
//! credentials, breadcrumbs' saved-network passwords). To edit safely we parse
|
||||
//! the file into a `toml_edit::DocumentMut`, mutate only the specific keys the
|
||||
//! UI exposes, and write the document back — preserving everything else,
|
||||
//! formatting and comments included.
|
||||
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn load<T: for<'de> serde::Deserialize<'de>>(path: &Path) -> Result<T, Box<dyn Error>> {
|
||||
let text = std::fs::read_to_string(path)?;
|
||||
Ok(toml::from_str(&text)?)
|
||||
use toml_edit::{value, Array, DocumentMut, Item, Table, Value};
|
||||
|
||||
/// Load a TOML file into an editable document. A missing or unparseable file
|
||||
/// yields an empty document so the UI still renders (with defaults).
|
||||
pub fn load_doc(path: &Path) -> DocumentMut {
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<DocumentMut>().ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn save<T: serde::Serialize>(path: &Path, val: &T) -> Result<(), Box<dyn Error>> {
|
||||
/// Write the document back to disk, creating parent dirs as needed.
|
||||
pub fn save_doc(path: &Path, doc: &DocumentMut) -> Result<(), Box<dyn Error>> {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
std::fs::write(path, toml::to_string_pretty(val)?)?;
|
||||
std::fs::write(path, doc.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn config_dir() -> 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")
|
||||
}
|
||||
|
||||
// --- typed readers (walk a dotted path, return None if absent/wrong type) ---
|
||||
|
||||
fn get<'a>(doc: &'a DocumentMut, path: &[&str]) -> Option<&'a Item> {
|
||||
let mut tbl = doc.as_table();
|
||||
let (last, parents) = path.split_last()?;
|
||||
for key in parents {
|
||||
tbl = tbl.get(key)?.as_table()?;
|
||||
}
|
||||
tbl.get(last)
|
||||
}
|
||||
|
||||
pub fn get_bool(doc: &DocumentMut, path: &[&str]) -> Option<bool> {
|
||||
get(doc, path)?.as_bool()
|
||||
}
|
||||
pub fn get_str(doc: &DocumentMut, path: &[&str]) -> Option<String> {
|
||||
get(doc, path)?.as_str().map(str::to_string)
|
||||
}
|
||||
pub fn get_i64(doc: &DocumentMut, path: &[&str]) -> Option<i64> {
|
||||
get(doc, path)?.as_integer()
|
||||
}
|
||||
pub fn get_f64(doc: &DocumentMut, path: &[&str]) -> Option<f64> {
|
||||
let item = get(doc, path)?;
|
||||
item.as_float().or_else(|| item.as_integer().map(|i| i as f64))
|
||||
}
|
||||
/// Read an array of strings (e.g. modules.disable, contexts[].priority).
|
||||
pub fn get_str_list(doc: &DocumentMut, path: &[&str]) -> Vec<String> {
|
||||
match get(doc, path).and_then(Item::as_array) {
|
||||
Some(arr) => arr
|
||||
.iter()
|
||||
.filter_map(|v| v.as_str().map(str::to_string))
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// --- setters (auto-create intermediate tables, replace only the leaf) ---
|
||||
|
||||
fn table_at_mut<'a>(doc: &'a mut DocumentMut, parents: &[&str]) -> &'a mut Table {
|
||||
let mut tbl = doc.as_table_mut();
|
||||
for key in parents {
|
||||
let entry = tbl.entry(key).or_insert_with(|| Item::Table(Table::new()));
|
||||
if !entry.is_table() {
|
||||
*entry = Item::Table(Table::new());
|
||||
}
|
||||
tbl = entry.as_table_mut().expect("just ensured table");
|
||||
}
|
||||
tbl
|
||||
}
|
||||
|
||||
fn set_item(doc: &mut DocumentMut, path: &[&str], item: Item) {
|
||||
let Some((last, parents)) = path.split_last() else {
|
||||
return;
|
||||
};
|
||||
table_at_mut(doc, parents).insert(last, item);
|
||||
}
|
||||
|
||||
pub fn set_bool(doc: &mut DocumentMut, path: &[&str], v: bool) {
|
||||
set_item(doc, path, value(v));
|
||||
}
|
||||
pub fn set_str(doc: &mut DocumentMut, path: &[&str], v: &str) {
|
||||
set_item(doc, path, value(v));
|
||||
}
|
||||
pub fn set_i64(doc: &mut DocumentMut, path: &[&str], v: i64) {
|
||||
set_item(doc, path, value(v));
|
||||
}
|
||||
pub fn set_f64(doc: &mut DocumentMut, path: &[&str], v: f64) {
|
||||
set_item(doc, path, value(v));
|
||||
}
|
||||
pub fn set_str_list(doc: &mut DocumentMut, path: &[&str], items: &[String]) {
|
||||
let mut arr = Array::new();
|
||||
for s in items {
|
||||
arr.push(s.as_str());
|
||||
}
|
||||
set_item(doc, path, Item::Value(Value::Array(arr)));
|
||||
}
|
||||
|
||||
/// Set a string key, or remove it entirely when the value is empty — keeps
|
||||
/// optional fields out of the file rather than persisting `key = ""`.
|
||||
pub fn set_str_or_remove(doc: &mut DocumentMut, path: &[&str], v: &str) {
|
||||
if v.is_empty() {
|
||||
remove(doc, path);
|
||||
} else {
|
||||
set_str(doc, path, v);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(doc: &mut DocumentMut, path: &[&str]) {
|
||||
if let Some((last, parents)) = path.split_last() {
|
||||
let mut tbl = doc.as_table_mut();
|
||||
for key in parents {
|
||||
match tbl.get_mut(key).and_then(Item::as_table_mut) {
|
||||
Some(t) => tbl = t,
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
tbl.remove(last);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn edits_preserve_unmodelled_keys_and_comments() {
|
||||
let src = "\
|
||||
# a leading comment
|
||||
[daemon]
|
||||
log_level = \"info\"
|
||||
|
||||
[calendar]
|
||||
password = \"secret\" # keep me
|
||||
";
|
||||
let mut doc: DocumentMut = src.parse().unwrap();
|
||||
// Modify a single modelled key.
|
||||
set_str(&mut doc, &["daemon", "log_level"], "debug");
|
||||
// A key/section the UI never touches must survive untouched.
|
||||
let out = doc.to_string();
|
||||
assert!(out.contains("log_level = \"debug\""));
|
||||
assert!(out.contains("password = \"secret\""));
|
||||
assert!(out.contains("# keep me"));
|
||||
assert!(out.contains("# a leading comment"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setters_create_missing_tables() {
|
||||
let mut doc = DocumentMut::new();
|
||||
set_bool(&mut doc, &["adapters", "power", "enabled"], false);
|
||||
set_i64(&mut doc, &["adapters", "power", "poll_interval_secs"], 45);
|
||||
assert_eq!(get_bool(&doc, &["adapters", "power", "enabled"]), Some(false));
|
||||
assert_eq!(
|
||||
get_i64(&doc, &["adapters", "power", "poll_interval_secs"]),
|
||||
Some(45)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_string_removes_key() {
|
||||
let mut doc: DocumentMut = "[calendar]\nurl = \"x\"\n".parse().unwrap();
|
||||
set_str_or_remove(&mut doc, &["calendar", "url"], "");
|
||||
assert_eq!(get_str(&doc, &["calendar", "url"]), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_list_roundtrips() {
|
||||
let mut doc = DocumentMut::new();
|
||||
let items = vec!["a".to_string(), "b".to_string()];
|
||||
set_str_list(&mut doc, &["modules", "disable"], &items);
|
||||
assert_eq!(get_str_list(&doc, &["modules", "disable"]), items);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
pub struct AppState {
|
||||
pub current_view: String,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current_view: "snapshots".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +1,30 @@
|
|||
use gtk4::prelude::*;
|
||||
//! Theming for bos-settings.
|
||||
//!
|
||||
//! bos-settings deliberately owns almost no styling: it loads the ecosystem's
|
||||
//! shared stylesheet (the same one breadbar/breadbox/breadpad use, generated by
|
||||
//! `bread-theme` from the pywal palette) and adds only the few layout rules
|
||||
//! specific to this app's sidebar + content shell. This keeps it visually
|
||||
//! identical to the rest of the bread desktop and live-recolouring for free.
|
||||
|
||||
use gtk4::CssProvider;
|
||||
use std::cell::RefCell;
|
||||
|
||||
const CSS: &str = r#"
|
||||
window {
|
||||
background-color: #2e3440;
|
||||
color: #eceff4;
|
||||
// App-specific layout only — everything visual (colours, buttons, entries,
|
||||
// switches, sidebar/row styling, cards, scrollbars) comes from the shared sheet.
|
||||
const APP_CSS: &str = "\
|
||||
.view-content { padding: 24px; }\n\
|
||||
.view-content > label.title { margin-bottom: 16px; }\n\
|
||||
";
|
||||
|
||||
thread_local! {
|
||||
static APP_PROVIDER: RefCell<Option<CssProvider>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: #3b4252;
|
||||
border-right: 1px solid #434c5e;
|
||||
}
|
||||
pub fn load(_display: >k4::gdk::Display) {
|
||||
// Shared ecosystem stylesheet (loads the generated file or a rendered
|
||||
// fallback, and live-reloads when the palette changes).
|
||||
bread_theme::gtk::apply_shared();
|
||||
|
||||
.sidebar row {
|
||||
padding: 8px 12px;
|
||||
color: #d8dee9;
|
||||
}
|
||||
|
||||
.sidebar row:selected {
|
||||
background-color: #5e81ac;
|
||||
color: #eceff4;
|
||||
}
|
||||
|
||||
.sidebar .section-header {
|
||||
padding: 12px 12px 4px 12px;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: #616e88;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.view-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.view-content label.title {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
color: #eceff4;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #5e81ac;
|
||||
color: #eceff4;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 6px 16px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #81a1c1;
|
||||
}
|
||||
|
||||
button.destructive-action {
|
||||
background-color: #bf616a;
|
||||
}
|
||||
|
||||
button.destructive-action:hover {
|
||||
background-color: #d08770;
|
||||
}
|
||||
|
||||
entry {
|
||||
background-color: #434c5e;
|
||||
color: #eceff4;
|
||||
border: 1px solid #4c566a;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
textview {
|
||||
background-color: #272c36;
|
||||
color: #a3be8c;
|
||||
font-family: monospace;
|
||||
padding: 8px;
|
||||
}
|
||||
"#;
|
||||
|
||||
pub fn load(display: >k4::gdk::Display) {
|
||||
let provider = CssProvider::new();
|
||||
provider.load_from_string(CSS);
|
||||
gtk4::style_context_add_provider_for_display(
|
||||
display,
|
||||
&provider,
|
||||
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
// bos-settings layout, layered on top at APPLICATION priority.
|
||||
APP_PROVIDER.with(|cell| bread_theme::gtk::apply_css(APP_CSS, cell));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod sidebar;
|
||||
pub mod views;
|
||||
pub mod widgets;
|
||||
pub mod window;
|
||||
|
|
|
|||
|
|
@ -1,155 +1,158 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, DropDown, Label, Orientation, StringList, Switch};
|
||||
use serde::{Deserialize, Serialize};
|
||||
//! breadd.toml — the bread daemon config.
|
||||
//! Schema mirrors breadd/src/core/config.rs (daemon, lua, modules, adapters,
|
||||
//! events, notifications). Edited non-destructively via the shared document.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::Box as GBox;
|
||||
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct BreadConfig {
|
||||
#[serde(default = "default_log_level")]
|
||||
pub log_level: String,
|
||||
#[serde(default)]
|
||||
pub adapters: AdaptersConfig,
|
||||
}
|
||||
|
||||
fn default_log_level() -> String { "info".to_string() }
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct AdaptersConfig {
|
||||
#[serde(default = "default_true")] pub keyboard: bool,
|
||||
#[serde(default = "default_true")] pub mouse: bool,
|
||||
#[serde(default = "default_true")] pub touchpad: bool,
|
||||
#[serde(default = "default_true")] pub bluetooth: bool,
|
||||
#[serde(default = "default_true")] pub gamepad: bool,
|
||||
}
|
||||
|
||||
fn default_true() -> bool { true }
|
||||
|
||||
impl Default for BreadConfig {
|
||||
fn default() -> Self {
|
||||
Self { log_level: default_log_level(), adapters: AdaptersConfig::default() }
|
||||
}
|
||||
}
|
||||
use crate::ui::widgets as w;
|
||||
|
||||
fn config_path() -> std::path::PathBuf {
|
||||
config::config_dir().join("bread/breadd.toml")
|
||||
}
|
||||
|
||||
fn adapter_row(
|
||||
label: &str,
|
||||
active: bool,
|
||||
cfg: Rc<RefCell<BreadConfig>>,
|
||||
field: &'static str,
|
||||
) -> GBox {
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
let lbl = Label::new(Some(label));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let sw = Switch::new();
|
||||
sw.set_active(active);
|
||||
sw.connect_active_notify(move |s| {
|
||||
let val = s.is_active();
|
||||
let mut c = cfg.borrow_mut();
|
||||
match field {
|
||||
"keyboard" => c.adapters.keyboard = val,
|
||||
"mouse" => c.adapters.mouse = val,
|
||||
"touchpad" => c.adapters.touchpad = val,
|
||||
"bluetooth" => c.adapters.bluetooth = val,
|
||||
"gamepad" => c.adapters.gamepad = val,
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
row.append(&lbl);
|
||||
row.append(&sw);
|
||||
row
|
||||
}
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let path = config_path();
|
||||
let cfg: BreadConfig = config::load(&path).unwrap_or_default();
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
let doc = Rc::new(RefCell::new(config::load_doc(&path)));
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
let (outer, c) = w::view_scaffold("bread");
|
||||
|
||||
let title = Label::new(Some("bread"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
c.append(&w::section("Daemon"));
|
||||
c.append(&w::dropdown_row(
|
||||
"Log level",
|
||||
&doc,
|
||||
&["daemon", "log_level"],
|
||||
&["error", "warn", "info", "debug", "trace"],
|
||||
"info",
|
||||
));
|
||||
c.append(&w::entry_row(
|
||||
"Socket path",
|
||||
&doc,
|
||||
&["daemon", "socket_path"],
|
||||
"default (XDG runtime dir)",
|
||||
"",
|
||||
));
|
||||
|
||||
// Log level
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
row.set_margin_bottom(8);
|
||||
let lbl = Label::new(Some("Log level"));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let levels = StringList::new(&["error", "warn", "info", "debug", "trace"]);
|
||||
let dropdown = DropDown::new(Some(levels), gtk4::Expression::NONE);
|
||||
let pos = match cfg.borrow().log_level.as_str() {
|
||||
"error" => 0u32, "warn" => 1, "info" => 2, "debug" => 3, "trace" => 4, _ => 2,
|
||||
};
|
||||
dropdown.set_selected(pos);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
dropdown.connect_selected_notify(move |dd| {
|
||||
let levels = ["error", "warn", "info", "debug", "trace"];
|
||||
if let Some(&level) = levels.get(dd.selected() as usize) {
|
||||
cfg.borrow_mut().log_level = level.to_string();
|
||||
}
|
||||
});
|
||||
}
|
||||
row.append(&lbl);
|
||||
row.append(&dropdown);
|
||||
vbox.append(&row);
|
||||
|
||||
let adapter_label = Label::new(Some("Adapters"));
|
||||
adapter_label.set_xalign(0.0);
|
||||
adapter_label.set_margin_top(8);
|
||||
adapter_label.set_margin_bottom(4);
|
||||
vbox.append(&adapter_label);
|
||||
|
||||
let (kbd, mouse, touchpad, bluetooth, gamepad) = {
|
||||
let c = cfg.borrow();
|
||||
(c.adapters.keyboard, c.adapters.mouse, c.adapters.touchpad,
|
||||
c.adapters.bluetooth, c.adapters.gamepad)
|
||||
};
|
||||
vbox.append(&adapter_row("Keyboard", kbd, cfg.clone(), "keyboard"));
|
||||
vbox.append(&adapter_row("Mouse", mouse, cfg.clone(), "mouse"));
|
||||
vbox.append(&adapter_row("Touchpad", touchpad, cfg.clone(), "touchpad"));
|
||||
vbox.append(&adapter_row("Bluetooth", bluetooth, cfg.clone(), "bluetooth"));
|
||||
vbox.append(&adapter_row("Gamepad", gamepad, cfg.clone(), "gamepad"));
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 12);
|
||||
btn_row.set_margin_top(16);
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let path = path.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
match config::save(&path, &*cfg.borrow()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status_lbl);
|
||||
vbox.append(&btn_row);
|
||||
|
||||
vbox
|
||||
c.append(&w::section("Lua"));
|
||||
c.append(&w::entry_row(
|
||||
"Entry point",
|
||||
&doc,
|
||||
&["lua", "entry_point"],
|
||||
"~/.config/bread/init.lua",
|
||||
"",
|
||||
));
|
||||
c.append(&w::entry_row(
|
||||
"Module path",
|
||||
&doc,
|
||||
&["lua", "module_path"],
|
||||
"~/.config/bread/modules",
|
||||
"",
|
||||
));
|
||||
|
||||
c.append(&w::section("Modules"));
|
||||
c.append(&w::switch_row(
|
||||
"Load built-in modules",
|
||||
&doc,
|
||||
&["modules", "builtin"],
|
||||
true,
|
||||
));
|
||||
c.append(&w::csv_row(
|
||||
"Disabled modules",
|
||||
&doc,
|
||||
&["modules", "disable"],
|
||||
"module-a, module-b",
|
||||
));
|
||||
|
||||
c.append(&w::section("Adapters"));
|
||||
c.append(&w::hint(
|
||||
"Sources breadd normalises into events. Disable any you don't use.",
|
||||
));
|
||||
c.append(&w::switch_row(
|
||||
"Hyprland",
|
||||
&doc,
|
||||
&["adapters", "hyprland", "enabled"],
|
||||
true,
|
||||
));
|
||||
c.append(&w::switch_row(
|
||||
"udev (devices)",
|
||||
&doc,
|
||||
&["adapters", "udev", "enabled"],
|
||||
true,
|
||||
));
|
||||
c.append(&w::csv_row(
|
||||
"udev subsystems",
|
||||
&doc,
|
||||
&["adapters", "udev", "subsystems"],
|
||||
"usb, input, power_supply",
|
||||
));
|
||||
c.append(&w::switch_row(
|
||||
"Power",
|
||||
&doc,
|
||||
&["adapters", "power", "enabled"],
|
||||
true,
|
||||
));
|
||||
c.append(&w::spin_row(
|
||||
"Power poll interval (s)",
|
||||
&doc,
|
||||
&["adapters", "power", "poll_interval_secs"],
|
||||
1.0,
|
||||
3600.0,
|
||||
1.0,
|
||||
30,
|
||||
));
|
||||
c.append(&w::switch_row(
|
||||
"Network",
|
||||
&doc,
|
||||
&["adapters", "network", "enabled"],
|
||||
true,
|
||||
));
|
||||
c.append(&w::switch_row(
|
||||
"Bluetooth",
|
||||
&doc,
|
||||
&["adapters", "bluetooth", "enabled"],
|
||||
true,
|
||||
));
|
||||
|
||||
c.append(&w::section("Events"));
|
||||
c.append(&w::spin_row(
|
||||
"Dedup window (ms)",
|
||||
&doc,
|
||||
&["events", "dedup_window_ms"],
|
||||
0.0,
|
||||
10000.0,
|
||||
50.0,
|
||||
250,
|
||||
));
|
||||
|
||||
c.append(&w::section("Notifications"));
|
||||
c.append(&w::spin_row(
|
||||
"Default timeout (ms)",
|
||||
&doc,
|
||||
&["notifications", "default_timeout_ms"],
|
||||
0.0,
|
||||
60000.0,
|
||||
500.0,
|
||||
5000,
|
||||
));
|
||||
c.append(&w::dropdown_row(
|
||||
"Default urgency",
|
||||
&doc,
|
||||
&["notifications", "default_urgency"],
|
||||
&["low", "normal", "critical"],
|
||||
"normal",
|
||||
));
|
||||
c.append(&w::entry_row(
|
||||
"notify-send path",
|
||||
&doc,
|
||||
&["notifications", "notify_send_path"],
|
||||
"auto-detected",
|
||||
"",
|
||||
));
|
||||
|
||||
outer.append(&w::save_button(&doc, path));
|
||||
outer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,33 +1,66 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, Entry, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
//! breadbox config.toml — launcher contexts.
|
||||
//! Schema mirrors breadbox-shared (`[[contexts]]` with `name` + `priority`, an
|
||||
//! ordered list of app/category hints). The contexts array is rewritten on
|
||||
//! save; any other top-level keys/comments in the file are preserved.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::{
|
||||
Box as GBox, Button, Entry, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow,
|
||||
};
|
||||
use toml_edit::{value, Array, ArrayOfTables, DocumentMut, Item, Table};
|
||||
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct BreadboxConfig {
|
||||
#[serde(default)]
|
||||
pub context: Vec<Context>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Context {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub apps: Vec<String>,
|
||||
#[derive(Clone, Default)]
|
||||
struct Context {
|
||||
name: String,
|
||||
priority: Vec<String>,
|
||||
}
|
||||
|
||||
fn config_path() -> std::path::PathBuf {
|
||||
config::config_dir().join("breadbox/config.toml")
|
||||
}
|
||||
|
||||
fn rebuild_list(list: &ListBox, cfg: &Rc<RefCell<BreadboxConfig>>) {
|
||||
fn read_contexts(doc: &DocumentMut) -> Vec<Context> {
|
||||
let Some(aot) = doc.get("contexts").and_then(Item::as_array_of_tables) else {
|
||||
return Vec::new();
|
||||
};
|
||||
aot.iter()
|
||||
.map(|t| Context {
|
||||
name: t.get("name").and_then(Item::as_str).unwrap_or("").to_string(),
|
||||
priority: t
|
||||
.get("priority")
|
||||
.and_then(Item::as_array)
|
||||
.map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Rewrite only the `contexts` array-of-tables, leaving the rest of the doc.
|
||||
fn write_contexts(doc: &mut DocumentMut, ctxs: &[Context]) {
|
||||
let mut aot = ArrayOfTables::new();
|
||||
for ctx in ctxs {
|
||||
let mut t = Table::new();
|
||||
t.insert("name", value(&ctx.name));
|
||||
let mut arr = Array::new();
|
||||
for p in &ctx.priority {
|
||||
arr.push(p.as_str());
|
||||
}
|
||||
t.insert("priority", value(arr));
|
||||
aot.push(t);
|
||||
}
|
||||
doc.as_table_mut().insert("contexts", Item::ArrayOfTables(aot));
|
||||
}
|
||||
|
||||
fn rebuild_list(list: &ListBox, model: &Rc<RefCell<Vec<Context>>>) {
|
||||
while let Some(child) = list.first_child() {
|
||||
list.remove(&child);
|
||||
}
|
||||
for (i, ctx) in cfg.borrow().context.iter().enumerate() {
|
||||
for (i, ctx) in model.borrow().iter().enumerate() {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_selectable(false);
|
||||
|
||||
|
|
@ -42,27 +75,28 @@ fn rebuild_list(list: &ListBox, cfg: &Rc<RefCell<BreadboxConfig>>) {
|
|||
name_entry.set_width_chars(14);
|
||||
name_entry.set_placeholder_text(Some("name"));
|
||||
|
||||
let apps_entry = Entry::new();
|
||||
apps_entry.set_text(&ctx.apps.join(", "));
|
||||
apps_entry.set_hexpand(true);
|
||||
apps_entry.set_placeholder_text(Some("app1, app2, ..."));
|
||||
let prio_entry = Entry::new();
|
||||
prio_entry.set_text(&ctx.priority.join(", "));
|
||||
prio_entry.set_hexpand(true);
|
||||
prio_entry.set_placeholder_text(Some("firefox, code, Development, ..."));
|
||||
|
||||
let remove_btn = Button::with_label("Remove");
|
||||
remove_btn.add_css_class("destructive-action");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let model = model.clone();
|
||||
name_entry.connect_changed(move |e| {
|
||||
if let Some(c) = cfg.borrow_mut().context.get_mut(i) {
|
||||
if let Some(c) = model.borrow_mut().get_mut(i) {
|
||||
c.name = e.text().to_string();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
apps_entry.connect_changed(move |e| {
|
||||
if let Some(c) = cfg.borrow_mut().context.get_mut(i) {
|
||||
c.apps = e.text()
|
||||
let model = model.clone();
|
||||
prio_entry.connect_changed(move |e| {
|
||||
if let Some(c) = model.borrow_mut().get_mut(i) {
|
||||
c.priority = e
|
||||
.text()
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
|
|
@ -71,16 +105,16 @@ fn rebuild_list(list: &ListBox, cfg: &Rc<RefCell<BreadboxConfig>>) {
|
|||
});
|
||||
}
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let model = model.clone();
|
||||
let list = list.clone();
|
||||
remove_btn.connect_clicked(move |_| {
|
||||
cfg.borrow_mut().context.remove(i);
|
||||
rebuild_list(&list, &cfg);
|
||||
model.borrow_mut().remove(i);
|
||||
rebuild_list(&list, &model);
|
||||
});
|
||||
}
|
||||
|
||||
hbox.append(&name_entry);
|
||||
hbox.append(&apps_entry);
|
||||
hbox.append(&prio_entry);
|
||||
hbox.append(&remove_btn);
|
||||
row.set_child(Some(&hbox));
|
||||
list.append(&row);
|
||||
|
|
@ -89,8 +123,8 @@ fn rebuild_list(list: &ListBox, cfg: &Rc<RefCell<BreadboxConfig>>) {
|
|||
|
||||
pub fn build() -> GBox {
|
||||
let path = config_path();
|
||||
let cfg: BreadboxConfig = config::load(&path).unwrap_or_default();
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
let doc = Rc::new(RefCell::new(config::load_doc(&path)));
|
||||
let model = Rc::new(RefCell::new(read_contexts(&doc.borrow())));
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
|
|
@ -100,14 +134,17 @@ pub fn build() -> GBox {
|
|||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
let subtitle = Label::new(Some("Context priority lists — apps shown in each context."));
|
||||
let subtitle = Label::new(Some(
|
||||
"Launcher contexts — each lists, in priority order, the apps/categories surfaced first.",
|
||||
));
|
||||
subtitle.set_xalign(0.0);
|
||||
subtitle.set_wrap(true);
|
||||
subtitle.set_margin_bottom(8);
|
||||
vbox.append(&subtitle);
|
||||
|
||||
let list = ListBox::new();
|
||||
list.set_selection_mode(gtk4::SelectionMode::None);
|
||||
rebuild_list(&list, &cfg);
|
||||
rebuild_list(&list, &model);
|
||||
|
||||
let scroll = ScrolledWindow::new();
|
||||
scroll.set_vexpand(true);
|
||||
|
|
@ -119,27 +156,30 @@ pub fn build() -> GBox {
|
|||
|
||||
let add_btn = Button::with_label("Add context");
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let model = model.clone();
|
||||
let list = list.clone();
|
||||
add_btn.connect_clicked(move |_| {
|
||||
cfg.borrow_mut().context.push(Context {
|
||||
model.borrow_mut().push(Context {
|
||||
name: "new".to_string(),
|
||||
apps: Vec::new(),
|
||||
priority: Vec::new(),
|
||||
});
|
||||
rebuild_list(&list, &cfg);
|
||||
rebuild_list(&list, &model);
|
||||
});
|
||||
}
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
save_btn.add_css_class("suggested-action");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let doc = doc.clone();
|
||||
let model = model.clone();
|
||||
let path = path.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
match config::save(&path, &*cfg.borrow()) {
|
||||
write_contexts(&mut doc.borrow_mut(), &model.borrow());
|
||||
match config::save_doc(&path, &doc.borrow()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
|
|
|
|||
|
|
@ -1,162 +1,477 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, Entry, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
//! breadcrumbs.toml — Wi-Fi profile state machine.
|
||||
//! Schema mirrors breadcrumbs/src/config.rs:
|
||||
//! [settings] scalar tunables
|
||||
//! [[networks]] saved networks (ssid / password / hidden)
|
||||
//! [profiles.<name>] per-location profile (networks, tailscale, …)
|
||||
//! `[settings]` is edited in place; the `networks` array and `profiles` table
|
||||
//! are rewritten from their editors on save. Other keys/comments are preserved.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::{
|
||||
Box as GBox, Button, Entry, Label, ListBox, ListBoxRow, Orientation, ScrolledWindow, Switch,
|
||||
};
|
||||
use toml_edit::{value, Array, ArrayOfTables, DocumentMut, Item, Table};
|
||||
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
pub struct BreadcrumbsConfig {
|
||||
#[serde(default)]
|
||||
pub profile: Vec<Profile>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Profile {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub ssids: Vec<String>,
|
||||
}
|
||||
use crate::ui::widgets as w;
|
||||
|
||||
fn config_path() -> std::path::PathBuf {
|
||||
config::config_dir().join("breadcrumbs/breadcrumbs.toml")
|
||||
}
|
||||
|
||||
fn rebuild_list(list: &ListBox, cfg: &Rc<RefCell<BreadcrumbsConfig>>) {
|
||||
// --- networks ---------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct Network {
|
||||
ssid: String,
|
||||
password: String,
|
||||
hidden: bool,
|
||||
}
|
||||
|
||||
fn read_networks(doc: &DocumentMut) -> Vec<Network> {
|
||||
let Some(aot) = doc.get("networks").and_then(Item::as_array_of_tables) else {
|
||||
return Vec::new();
|
||||
};
|
||||
aot.iter()
|
||||
.map(|t| Network {
|
||||
ssid: t.get("ssid").and_then(Item::as_str).unwrap_or("").to_string(),
|
||||
password: t
|
||||
.get("password")
|
||||
.and_then(Item::as_str)
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
hidden: t.get("hidden").and_then(Item::as_bool).unwrap_or(false),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn write_networks(doc: &mut DocumentMut, nets: &[Network]) {
|
||||
let mut aot = ArrayOfTables::new();
|
||||
for n in nets {
|
||||
let mut t = Table::new();
|
||||
t.insert("ssid", value(&n.ssid));
|
||||
t.insert("password", value(&n.password));
|
||||
t.insert("hidden", value(n.hidden));
|
||||
aot.push(t);
|
||||
}
|
||||
doc.as_table_mut().insert("networks", Item::ArrayOfTables(aot));
|
||||
}
|
||||
|
||||
fn rebuild_networks(list: &ListBox, model: &Rc<RefCell<Vec<Network>>>) {
|
||||
while let Some(child) = list.first_child() {
|
||||
list.remove(&child);
|
||||
}
|
||||
for (i, profile) in cfg.borrow().profile.iter().enumerate() {
|
||||
for (i, n) in model.borrow().iter().enumerate() {
|
||||
let row = ListBoxRow::new();
|
||||
row.set_selectable(false);
|
||||
|
||||
let hbox = GBox::new(Orientation::Horizontal, 8);
|
||||
hbox.set_margin_top(6);
|
||||
hbox.set_margin_bottom(6);
|
||||
hbox.set_margin_start(8);
|
||||
hbox.set_margin_end(8);
|
||||
|
||||
let name_entry = Entry::new();
|
||||
name_entry.set_text(&profile.name);
|
||||
name_entry.set_width_chars(14);
|
||||
name_entry.set_placeholder_text(Some("name"));
|
||||
let ssid = Entry::new();
|
||||
ssid.set_text(&n.ssid);
|
||||
ssid.set_width_chars(16);
|
||||
ssid.set_placeholder_text(Some("SSID"));
|
||||
|
||||
let ssids_entry = Entry::new();
|
||||
ssids_entry.set_text(&profile.ssids.join(", "));
|
||||
ssids_entry.set_hexpand(true);
|
||||
ssids_entry.set_placeholder_text(Some("SSID1, SSID2, ..."));
|
||||
let pass = Entry::new();
|
||||
pass.set_text(&n.password);
|
||||
pass.set_hexpand(true);
|
||||
pass.set_visibility(false);
|
||||
pass.set_input_purpose(gtk4::InputPurpose::Password);
|
||||
pass.set_placeholder_text(Some("password"));
|
||||
|
||||
let remove_btn = Button::with_label("Remove");
|
||||
remove_btn.add_css_class("destructive-action");
|
||||
let hidden = Switch::new();
|
||||
hidden.set_active(n.hidden);
|
||||
hidden.set_valign(gtk4::Align::Center);
|
||||
hidden.set_tooltip_text(Some("Hidden network"));
|
||||
|
||||
let remove = Button::with_label("Remove");
|
||||
remove.add_css_class("destructive-action");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
name_entry.connect_changed(move |e| {
|
||||
if let Some(p) = cfg.borrow_mut().profile.get_mut(i) {
|
||||
p.name = e.text().to_string();
|
||||
let model = model.clone();
|
||||
ssid.connect_changed(move |e| {
|
||||
if let Some(n) = model.borrow_mut().get_mut(i) {
|
||||
n.ssid = e.text().to_string();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
ssids_entry.connect_changed(move |e| {
|
||||
if let Some(p) = cfg.borrow_mut().profile.get_mut(i) {
|
||||
p.ssids = e.text()
|
||||
let model = model.clone();
|
||||
pass.connect_changed(move |e| {
|
||||
if let Some(n) = model.borrow_mut().get_mut(i) {
|
||||
n.password = e.text().to_string();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let model = model.clone();
|
||||
hidden.connect_active_notify(move |s| {
|
||||
if let Some(n) = model.borrow_mut().get_mut(i) {
|
||||
n.hidden = s.is_active();
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let model = model.clone();
|
||||
let list = list.clone();
|
||||
remove.connect_clicked(move |_| {
|
||||
model.borrow_mut().remove(i);
|
||||
rebuild_networks(&list, &model);
|
||||
});
|
||||
}
|
||||
|
||||
hbox.append(&ssid);
|
||||
hbox.append(&pass);
|
||||
hbox.append(&Label::new(Some("hidden")));
|
||||
hbox.append(&hidden);
|
||||
hbox.append(&remove);
|
||||
row.set_child(Some(&hbox));
|
||||
list.append(&row);
|
||||
}
|
||||
}
|
||||
|
||||
// --- profiles ---------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct Profile {
|
||||
name: String,
|
||||
networks: Vec<String>,
|
||||
detect_ssids: Vec<String>,
|
||||
bootstrap: String,
|
||||
exit_node: String,
|
||||
tailscale: bool,
|
||||
include_all_known: bool,
|
||||
}
|
||||
|
||||
fn read_profiles(doc: &DocumentMut) -> Vec<Profile> {
|
||||
let Some(tbl) = doc.get("profiles").and_then(Item::as_table) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let str_list = |item: Option<&Item>| -> Vec<String> {
|
||||
item.and_then(Item::as_array)
|
||||
.map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
tbl.iter()
|
||||
.filter_map(|(name, item)| {
|
||||
let p = item.as_table()?;
|
||||
Some(Profile {
|
||||
name: name.to_string(),
|
||||
networks: str_list(p.get("networks")),
|
||||
detect_ssids: str_list(p.get("detect_ssids")),
|
||||
bootstrap: p.get("bootstrap").and_then(Item::as_str).unwrap_or("").to_string(),
|
||||
exit_node: p.get("exit_node").and_then(Item::as_str).unwrap_or("").to_string(),
|
||||
tailscale: p.get("tailscale").and_then(Item::as_bool).unwrap_or(false),
|
||||
include_all_known: p
|
||||
.get("include_all_known")
|
||||
.and_then(Item::as_bool)
|
||||
.unwrap_or(false),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn write_profiles(doc: &mut DocumentMut, profiles: &[Profile]) {
|
||||
let mut tbl = Table::new();
|
||||
let to_arr = |items: &[String]| {
|
||||
let mut a = Array::new();
|
||||
for s in items {
|
||||
a.push(s.as_str());
|
||||
}
|
||||
a
|
||||
};
|
||||
for p in profiles {
|
||||
if p.name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut t = Table::new();
|
||||
t.insert("networks", value(to_arr(&p.networks)));
|
||||
t.insert("tailscale", value(p.tailscale));
|
||||
t.insert("include_all_known", value(p.include_all_known));
|
||||
if !p.detect_ssids.is_empty() {
|
||||
t.insert("detect_ssids", value(to_arr(&p.detect_ssids)));
|
||||
}
|
||||
if !p.bootstrap.is_empty() {
|
||||
t.insert("bootstrap", value(&p.bootstrap));
|
||||
}
|
||||
if !p.exit_node.is_empty() {
|
||||
t.insert("exit_node", value(&p.exit_node));
|
||||
}
|
||||
tbl.insert(&p.name, Item::Table(t));
|
||||
}
|
||||
doc.as_table_mut().insert("profiles", Item::Table(tbl));
|
||||
}
|
||||
|
||||
fn field(label: &str, control: &impl IsA<gtk4::Widget>) -> GBox {
|
||||
let row = GBox::new(Orientation::Horizontal, 12);
|
||||
let lbl = Label::new(Some(label));
|
||||
lbl.set_xalign(0.0);
|
||||
lbl.set_width_chars(16);
|
||||
row.append(&lbl);
|
||||
control.set_hexpand(true);
|
||||
row.append(control);
|
||||
row
|
||||
}
|
||||
|
||||
fn rebuild_profiles(container: &GBox, model: &Rc<RefCell<Vec<Profile>>>) {
|
||||
while let Some(child) = container.first_child() {
|
||||
container.remove(&child);
|
||||
}
|
||||
for (i, p) in model.borrow().iter().enumerate() {
|
||||
let card = GBox::new(Orientation::Vertical, 6);
|
||||
card.add_css_class("card");
|
||||
card.set_margin_top(6);
|
||||
card.set_margin_bottom(6);
|
||||
|
||||
let header = GBox::new(Orientation::Horizontal, 8);
|
||||
let name = Entry::new();
|
||||
name.set_text(&p.name);
|
||||
name.set_hexpand(true);
|
||||
name.set_placeholder_text(Some("profile name (e.g. home)"));
|
||||
let remove = Button::with_label("Remove");
|
||||
remove.add_css_class("destructive-action");
|
||||
header.append(&name);
|
||||
header.append(&remove);
|
||||
card.append(&header);
|
||||
|
||||
let networks = Entry::new();
|
||||
networks.set_text(&p.networks.join(", "));
|
||||
networks.set_placeholder_text(Some("SSID1, SSID2"));
|
||||
card.append(&field("Networks", &networks));
|
||||
|
||||
let detect = Entry::new();
|
||||
detect.set_text(&p.detect_ssids.join(", "));
|
||||
detect.set_placeholder_text(Some("SSIDs that auto-select this profile"));
|
||||
card.append(&field("Detect SSIDs", &detect));
|
||||
|
||||
let exit_node = Entry::new();
|
||||
exit_node.set_text(&p.exit_node);
|
||||
exit_node.set_placeholder_text(Some("tailscale exit node (optional)"));
|
||||
card.append(&field("Exit node", &exit_node));
|
||||
|
||||
let bootstrap = Entry::new();
|
||||
bootstrap.set_text(&p.bootstrap);
|
||||
bootstrap.set_placeholder_text(Some("bootstrap command (optional)"));
|
||||
card.append(&field("Bootstrap", &bootstrap));
|
||||
|
||||
let tailscale = Switch::new();
|
||||
tailscale.set_active(p.tailscale);
|
||||
tailscale.set_halign(gtk4::Align::Start);
|
||||
card.append(&field("Tailscale", &tailscale));
|
||||
|
||||
let include_all = Switch::new();
|
||||
include_all.set_active(p.include_all_known);
|
||||
include_all.set_halign(gtk4::Align::Start);
|
||||
card.append(&field("Include all known", &include_all));
|
||||
|
||||
// bind each control to the in-memory model entry
|
||||
macro_rules! bind_csv {
|
||||
($entry:ident, $f:ident) => {{
|
||||
let model = model.clone();
|
||||
$entry.connect_changed(move |e| {
|
||||
if let Some(p) = model.borrow_mut().get_mut(i) {
|
||||
p.$f = e
|
||||
.text()
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
}
|
||||
});
|
||||
}};
|
||||
}
|
||||
macro_rules! bind_str {
|
||||
($entry:ident, $f:ident) => {{
|
||||
let model = model.clone();
|
||||
$entry.connect_changed(move |e| {
|
||||
if let Some(p) = model.borrow_mut().get_mut(i) {
|
||||
p.$f = e.text().to_string();
|
||||
}
|
||||
});
|
||||
}};
|
||||
}
|
||||
macro_rules! bind_bool {
|
||||
($sw:ident, $f:ident) => {{
|
||||
let model = model.clone();
|
||||
$sw.connect_active_notify(move |s| {
|
||||
if let Some(p) = model.borrow_mut().get_mut(i) {
|
||||
p.$f = s.is_active();
|
||||
}
|
||||
});
|
||||
}};
|
||||
}
|
||||
bind_str!(name, name);
|
||||
bind_csv!(networks, networks);
|
||||
bind_csv!(detect, detect_ssids);
|
||||
bind_str!(exit_node, exit_node);
|
||||
bind_str!(bootstrap, bootstrap);
|
||||
bind_bool!(tailscale, tailscale);
|
||||
bind_bool!(include_all, include_all_known);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let list = list.clone();
|
||||
remove_btn.connect_clicked(move |_| {
|
||||
cfg.borrow_mut().profile.remove(i);
|
||||
rebuild_list(&list, &cfg);
|
||||
let model = model.clone();
|
||||
let container = container.clone();
|
||||
remove.connect_clicked(move |_| {
|
||||
model.borrow_mut().remove(i);
|
||||
rebuild_profiles(&container, &model);
|
||||
});
|
||||
}
|
||||
|
||||
hbox.append(&name_entry);
|
||||
hbox.append(&ssids_entry);
|
||||
hbox.append(&remove_btn);
|
||||
row.set_child(Some(&hbox));
|
||||
list.append(&row);
|
||||
container.append(&card);
|
||||
}
|
||||
}
|
||||
|
||||
// --- view -------------------------------------------------------------------
|
||||
|
||||
pub fn build() -> GBox {
|
||||
let path = config_path();
|
||||
let cfg: BreadcrumbsConfig = config::load(&path).unwrap_or_default();
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
let doc = Rc::new(RefCell::new(config::load_doc(&path)));
|
||||
let nets = Rc::new(RefCell::new(read_networks(&doc.borrow())));
|
||||
let profiles = Rc::new(RefCell::new(read_profiles(&doc.borrow())));
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
let outer = GBox::new(Orientation::Vertical, 8);
|
||||
outer.add_css_class("view-content");
|
||||
|
||||
let title = Label::new(Some("breadcrumbs"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
|
||||
let subtitle = Label::new(Some("Network profiles — SSIDs associated with each location."));
|
||||
subtitle.set_xalign(0.0);
|
||||
subtitle.set_margin_bottom(8);
|
||||
vbox.append(&subtitle);
|
||||
|
||||
let list = ListBox::new();
|
||||
list.set_selection_mode(gtk4::SelectionMode::None);
|
||||
rebuild_list(&list, &cfg);
|
||||
outer.append(&title);
|
||||
|
||||
let content = GBox::new(Orientation::Vertical, 8);
|
||||
let scroll = ScrolledWindow::new();
|
||||
scroll.set_vexpand(true);
|
||||
scroll.set_child(Some(&list));
|
||||
vbox.append(&scroll);
|
||||
scroll.set_hscrollbar_policy(gtk4::PolicyType::Never);
|
||||
scroll.set_child(Some(&content));
|
||||
outer.append(&scroll);
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 8);
|
||||
btn_row.set_margin_top(8);
|
||||
// [settings] — edited in place on the shared doc
|
||||
content.append(&w::section("Settings"));
|
||||
content.append(&w::dropdown_row(
|
||||
"Default profile",
|
||||
&doc,
|
||||
&["settings", "default_profile"],
|
||||
&["home", "away"],
|
||||
"home",
|
||||
));
|
||||
content.append(&w::entry_row("DNS", &doc, &["settings", "dns"], "1.1.1.1", ""));
|
||||
content.append(&w::entry_row(
|
||||
"Exit node",
|
||||
&doc,
|
||||
&["settings", "exit_node"],
|
||||
"tailscale exit node",
|
||||
"",
|
||||
));
|
||||
content.append(&w::entry_row(
|
||||
"Ping host",
|
||||
&doc,
|
||||
&["settings", "ping_host"],
|
||||
"1.1.1.1",
|
||||
"",
|
||||
));
|
||||
content.append(&w::entry_row(
|
||||
"Connectivity URL",
|
||||
&doc,
|
||||
&["settings", "connectivity_url"],
|
||||
"http://connectivitycheck.gstatic.com/generate_204",
|
||||
"",
|
||||
));
|
||||
content.append(&w::spin_row(
|
||||
"nmcli wait (s)",
|
||||
&doc,
|
||||
&["settings", "nmcli_wait"],
|
||||
1.0,
|
||||
120.0,
|
||||
1.0,
|
||||
8,
|
||||
));
|
||||
content.append(&w::spin_row(
|
||||
"Watch interval (s)",
|
||||
&doc,
|
||||
&["settings", "watch_interval"],
|
||||
1.0,
|
||||
600.0,
|
||||
1.0,
|
||||
12,
|
||||
));
|
||||
|
||||
let add_btn = Button::with_label("Add profile");
|
||||
// [[networks]]
|
||||
content.append(&w::section("Saved networks"));
|
||||
let net_list = ListBox::new();
|
||||
net_list.set_selection_mode(gtk4::SelectionMode::None);
|
||||
rebuild_networks(&net_list, &nets);
|
||||
content.append(&net_list);
|
||||
let add_net = Button::with_label("Add network");
|
||||
add_net.set_halign(gtk4::Align::Start);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let list = list.clone();
|
||||
add_btn.connect_clicked(move |_| {
|
||||
cfg.borrow_mut().profile.push(Profile {
|
||||
name: "new".to_string(),
|
||||
ssids: Vec::new(),
|
||||
});
|
||||
rebuild_list(&list, &cfg);
|
||||
let nets = nets.clone();
|
||||
let net_list = net_list.clone();
|
||||
add_net.connect_clicked(move |_| {
|
||||
nets.borrow_mut().push(Network::default());
|
||||
rebuild_networks(&net_list, &nets);
|
||||
});
|
||||
}
|
||||
content.append(&add_net);
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
// [profiles.*]
|
||||
content.append(&w::section("Profiles"));
|
||||
let prof_box = GBox::new(Orientation::Vertical, 4);
|
||||
rebuild_profiles(&prof_box, &profiles);
|
||||
content.append(&prof_box);
|
||||
let add_prof = Button::with_label("Add profile");
|
||||
add_prof.set_halign(gtk4::Align::Start);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let profiles = profiles.clone();
|
||||
let prof_box = prof_box.clone();
|
||||
add_prof.connect_clicked(move |_| {
|
||||
profiles.borrow_mut().push(Profile {
|
||||
name: "new".to_string(),
|
||||
..Default::default()
|
||||
});
|
||||
rebuild_profiles(&prof_box, &profiles);
|
||||
});
|
||||
}
|
||||
content.append(&add_prof);
|
||||
|
||||
// Save — fold the network + profile editors back into the doc, then write.
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 12);
|
||||
btn_row.set_margin_top(16);
|
||||
let save_btn = Button::with_label("Save");
|
||||
save_btn.add_css_class("suggested-action");
|
||||
let status = Label::new(None);
|
||||
status.add_css_class("dim-label");
|
||||
{
|
||||
let doc = doc.clone();
|
||||
let nets = nets.clone();
|
||||
let profiles = profiles.clone();
|
||||
let path = path.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
let status = status.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
match config::save(&path, &*cfg.borrow()) {
|
||||
{
|
||||
let mut d = doc.borrow_mut();
|
||||
write_networks(&mut d, &nets.borrow());
|
||||
write_profiles(&mut d, &profiles.borrow());
|
||||
}
|
||||
match config::save_doc(&path, &doc.borrow()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
status.set_text("Saved");
|
||||
let lbl = status.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
|
||||
Err(e) => status.set_text(&format!("Error: {e}")),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&add_btn);
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status_lbl);
|
||||
vbox.append(&btn_row);
|
||||
btn_row.append(&status);
|
||||
outer.append(&btn_row);
|
||||
|
||||
vbox
|
||||
outer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,16 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{Box as GBox, Button, Entry, Label, Orientation, Switch};
|
||||
use serde::{Deserialize, Serialize};
|
||||
//! breadpad.toml — the breadpad notes/reminders config.
|
||||
//! Schema mirrors breadpad-shared/src/config.rs (settings, model + model.ollama,
|
||||
//! reminders, calendar). Edited non-destructively (the calendar password and
|
||||
//! model paths are preserved across saves).
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::Box as GBox;
|
||||
|
||||
use crate::config;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct BreadpadConfig {
|
||||
#[serde(default)]
|
||||
pub model: String,
|
||||
#[serde(default = "default_true")]
|
||||
pub reminders: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub calendar: bool,
|
||||
}
|
||||
|
||||
fn default_true() -> bool { true }
|
||||
|
||||
impl Default for BreadpadConfig {
|
||||
fn default() -> Self {
|
||||
Self { model: String::new(), reminders: true, calendar: true }
|
||||
}
|
||||
}
|
||||
use crate::ui::widgets as w;
|
||||
|
||||
fn config_path() -> std::path::PathBuf {
|
||||
config::config_dir().join("breadpad/breadpad.toml")
|
||||
|
|
@ -30,93 +18,129 @@ fn config_path() -> std::path::PathBuf {
|
|||
|
||||
pub fn build() -> GBox {
|
||||
let path = config_path();
|
||||
let cfg: BreadpadConfig = config::load(&path).unwrap_or_default();
|
||||
let cfg = Rc::new(RefCell::new(cfg));
|
||||
let doc = Rc::new(RefCell::new(config::load_doc(&path)));
|
||||
|
||||
let vbox = GBox::new(Orientation::Vertical, 12);
|
||||
vbox.add_css_class("view-content");
|
||||
let (outer, c) = w::view_scaffold("breadpad");
|
||||
|
||||
let title = Label::new(Some("breadpad"));
|
||||
title.add_css_class("title");
|
||||
title.set_xalign(0.0);
|
||||
vbox.append(&title);
|
||||
c.append(&w::section("Capture"));
|
||||
c.append(&w::dropdown_row(
|
||||
"Default note type",
|
||||
&doc,
|
||||
&["settings", "default_type"],
|
||||
&["note", "reminder", "task"],
|
||||
"note",
|
||||
));
|
||||
c.append(&w::switch_row(
|
||||
"Tag with active workspace",
|
||||
&doc,
|
||||
&["settings", "workspace_tag"],
|
||||
true,
|
||||
));
|
||||
c.append(&w::csv_row(
|
||||
"Snooze options",
|
||||
&doc,
|
||||
&["settings", "snooze_options"],
|
||||
"15m, 1h, tomorrow_morning",
|
||||
));
|
||||
c.append(&w::spin_row(
|
||||
"Archive after (days)",
|
||||
&doc,
|
||||
&["settings", "archive_after_days"],
|
||||
0.0,
|
||||
3650.0,
|
||||
1.0,
|
||||
30,
|
||||
));
|
||||
|
||||
// Model entry
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
let lbl = Label::new(Some("Model"));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let model_entry = Entry::new();
|
||||
model_entry.set_text(&cfg.borrow().model);
|
||||
model_entry.set_placeholder_text(Some("e.g. claude-sonnet-4-6"));
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
model_entry.connect_changed(move |e| {
|
||||
cfg.borrow_mut().model = e.text().to_string();
|
||||
});
|
||||
}
|
||||
row.append(&lbl);
|
||||
row.append(&model_entry);
|
||||
vbox.append(&row);
|
||||
|
||||
// Reminders
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
let lbl = Label::new(Some("Reminders"));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let sw = Switch::new();
|
||||
sw.set_active(cfg.borrow().reminders);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
sw.connect_active_notify(move |s| { cfg.borrow_mut().reminders = s.is_active(); });
|
||||
}
|
||||
row.append(&lbl);
|
||||
row.append(&sw);
|
||||
vbox.append(&row);
|
||||
|
||||
// Calendar
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
let lbl = Label::new(Some("Calendar integration"));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
let sw = Switch::new();
|
||||
sw.set_active(cfg.borrow().calendar);
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
sw.connect_active_notify(move |s| { cfg.borrow_mut().calendar = s.is_active(); });
|
||||
}
|
||||
row.append(&lbl);
|
||||
row.append(&sw);
|
||||
vbox.append(&row);
|
||||
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 12);
|
||||
btn_row.set_margin_top(16);
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
let status_lbl = Label::new(None);
|
||||
status_lbl.add_css_class("dim-label");
|
||||
|
||||
{
|
||||
let cfg = cfg.clone();
|
||||
let status_lbl = status_lbl.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
match config::save(&path, &*cfg.borrow()) {
|
||||
Ok(()) => {
|
||||
status_lbl.set_text("Saved");
|
||||
let lbl = status_lbl.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status_lbl);
|
||||
vbox.append(&btn_row);
|
||||
|
||||
vbox
|
||||
c.append(&w::section("Classifier model"));
|
||||
c.append(&w::entry_row(
|
||||
"ONNX model path",
|
||||
&doc,
|
||||
&["model", "path"],
|
||||
"~/.local/share/breadpad/model/classifier.onnx",
|
||||
"",
|
||||
));
|
||||
c.append(&w::entry_row(
|
||||
"Tokenizer path",
|
||||
&doc,
|
||||
&["model", "tokenizer"],
|
||||
"~/.local/share/breadpad/model/tokenizer.json",
|
||||
"",
|
||||
));
|
||||
|
||||
c.append(&w::section("Ollama (LLM classifier)"));
|
||||
c.append(&w::switch_row(
|
||||
"Use Ollama",
|
||||
&doc,
|
||||
&["model", "ollama", "enabled"],
|
||||
true,
|
||||
));
|
||||
c.append(&w::entry_row(
|
||||
"Endpoint",
|
||||
&doc,
|
||||
&["model", "ollama", "endpoint"],
|
||||
"http://localhost:11434",
|
||||
"",
|
||||
));
|
||||
c.append(&w::entry_row(
|
||||
"Model",
|
||||
&doc,
|
||||
&["model", "ollama", "model"],
|
||||
"e.g. fastflowlm",
|
||||
"",
|
||||
));
|
||||
c.append(&w::spin_f64_row(
|
||||
"Confidence threshold",
|
||||
&doc,
|
||||
&["model", "ollama", "confidence_threshold"],
|
||||
0.0,
|
||||
1.0,
|
||||
0.05,
|
||||
2,
|
||||
0.6,
|
||||
));
|
||||
|
||||
c.append(&w::section("Reminders"));
|
||||
c.append(&w::entry_row(
|
||||
"Default morning time",
|
||||
&doc,
|
||||
&["reminders", "default_morning"],
|
||||
"7:00",
|
||||
"",
|
||||
));
|
||||
c.append(&w::spin_row(
|
||||
"Missed grace (minutes)",
|
||||
&doc,
|
||||
&["reminders", "missed_grace_minutes"],
|
||||
0.0,
|
||||
1440.0,
|
||||
5.0,
|
||||
60,
|
||||
));
|
||||
|
||||
c.append(&w::section("Calendar (CalDAV)"));
|
||||
c.append(&w::switch_row(
|
||||
"Sync to calendar",
|
||||
&doc,
|
||||
&["calendar", "enabled"],
|
||||
false,
|
||||
));
|
||||
c.append(&w::entry_row(
|
||||
"CalDAV URL",
|
||||
&doc,
|
||||
&["calendar", "url"],
|
||||
"https://host/remote.php/dav/calendars/...",
|
||||
"",
|
||||
));
|
||||
c.append(&w::entry_row(
|
||||
"Username",
|
||||
&doc,
|
||||
&["calendar", "username"],
|
||||
"",
|
||||
"",
|
||||
));
|
||||
c.append(&w::password_row("Password", &doc, &["calendar", "password"]));
|
||||
|
||||
outer.append(&w::save_button(&doc, path));
|
||||
outer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ fn get_monitors() -> Vec<String> {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::io::{BufRead, BufReader};
|
|||
use std::process::{Command, Stdio};
|
||||
|
||||
fn read_installed() -> HashMap<String, String> {
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
235
bos-settings/src/ui/widgets.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
//! Reusable settings rows bound to a shared `toml_edit` document.
|
||||
//!
|
||||
//! Every row reads its current value from the document on build and writes the
|
||||
//! single key it owns back into the document on change. A view collects rows,
|
||||
//! then a [`save_button`] persists the whole document to disk in one shot — so
|
||||
//! unmodelled keys and comments are always preserved (see `crate::config`).
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::{
|
||||
Adjustment, Box as GBox, Button, DropDown, Entry, Expression, Label, Orientation,
|
||||
SpinButton, StringList, Switch,
|
||||
};
|
||||
use toml_edit::DocumentMut;
|
||||
|
||||
use crate::config;
|
||||
|
||||
/// Shared, mutable config document handed to every row in a view.
|
||||
pub type Doc = Rc<RefCell<DocumentMut>>;
|
||||
|
||||
/// A fixed key path into the document, e.g. `&["adapters", "power", "enabled"]`.
|
||||
type Path = &'static [&'static str];
|
||||
|
||||
fn field_label(text: &str) -> Label {
|
||||
let lbl = Label::new(Some(text));
|
||||
lbl.set_hexpand(true);
|
||||
lbl.set_xalign(0.0);
|
||||
lbl
|
||||
}
|
||||
|
||||
fn row(label: &str, control: &impl IsA<gtk4::Widget>) -> GBox {
|
||||
let row = GBox::new(Orientation::Horizontal, 16);
|
||||
row.append(&field_label(label));
|
||||
control.set_halign(gtk4::Align::End);
|
||||
control.set_valign(gtk4::Align::Center);
|
||||
row.append(control);
|
||||
row
|
||||
}
|
||||
|
||||
/// A bold section heading with spacing above it.
|
||||
pub fn section(text: &str) -> Label {
|
||||
let lbl = Label::new(Some(text));
|
||||
lbl.add_css_class("heading");
|
||||
lbl.set_xalign(0.0);
|
||||
lbl.set_margin_top(12);
|
||||
lbl.set_margin_bottom(2);
|
||||
lbl
|
||||
}
|
||||
|
||||
/// Small dimmed helper text under a section or row.
|
||||
pub fn hint(text: &str) -> Label {
|
||||
let lbl = Label::new(Some(text));
|
||||
lbl.add_css_class("dim-label");
|
||||
lbl.set_xalign(0.0);
|
||||
lbl.set_wrap(true);
|
||||
lbl.set_margin_bottom(4);
|
||||
lbl
|
||||
}
|
||||
|
||||
/// Standard view scaffold: an outer vertical box with a title and a scrollable
|
||||
/// content area. Append setting rows to the returned `content`, then append a
|
||||
/// [`save_button`] to `outer`. Returns `(outer, content)`.
|
||||
pub fn view_scaffold(title: &str) -> (GBox, GBox) {
|
||||
let outer = GBox::new(Orientation::Vertical, 8);
|
||||
outer.add_css_class("view-content");
|
||||
|
||||
let title_lbl = Label::new(Some(title));
|
||||
title_lbl.add_css_class("title");
|
||||
title_lbl.set_xalign(0.0);
|
||||
outer.append(&title_lbl);
|
||||
|
||||
let content = GBox::new(Orientation::Vertical, 8);
|
||||
let scroll = gtk4::ScrolledWindow::new();
|
||||
scroll.set_vexpand(true);
|
||||
scroll.set_hscrollbar_policy(gtk4::PolicyType::Never);
|
||||
scroll.set_child(Some(&content));
|
||||
outer.append(&scroll);
|
||||
|
||||
(outer, content)
|
||||
}
|
||||
|
||||
pub fn switch_row(label: &str, doc: &Doc, path: Path, default: bool) -> GBox {
|
||||
let cur = config::get_bool(&doc.borrow(), path).unwrap_or(default);
|
||||
let sw = Switch::new();
|
||||
sw.set_active(cur);
|
||||
let doc = doc.clone();
|
||||
sw.connect_active_notify(move |s| {
|
||||
config::set_bool(&mut doc.borrow_mut(), path, s.is_active());
|
||||
});
|
||||
row(label, &sw)
|
||||
}
|
||||
|
||||
pub fn entry_row(label: &str, doc: &Doc, path: Path, placeholder: &str, default: &str) -> GBox {
|
||||
let cur = config::get_str(&doc.borrow(), path).unwrap_or_else(|| default.to_string());
|
||||
let entry = Entry::new();
|
||||
entry.set_text(&cur);
|
||||
entry.set_hexpand(true);
|
||||
entry.set_width_chars(28);
|
||||
if !placeholder.is_empty() {
|
||||
entry.set_placeholder_text(Some(placeholder));
|
||||
}
|
||||
let doc = doc.clone();
|
||||
entry.connect_changed(move |e| {
|
||||
config::set_str_or_remove(&mut doc.borrow_mut(), path, e.text().as_str());
|
||||
});
|
||||
row(label, &entry)
|
||||
}
|
||||
|
||||
pub fn password_row(label: &str, doc: &Doc, path: Path) -> GBox {
|
||||
let cur = config::get_str(&doc.borrow(), path).unwrap_or_default();
|
||||
let entry = Entry::new();
|
||||
entry.set_text(&cur);
|
||||
entry.set_visibility(false);
|
||||
entry.set_hexpand(true);
|
||||
entry.set_width_chars(28);
|
||||
entry.set_input_purpose(gtk4::InputPurpose::Password);
|
||||
let doc = doc.clone();
|
||||
entry.connect_changed(move |e| {
|
||||
config::set_str_or_remove(&mut doc.borrow_mut(), path, e.text().as_str());
|
||||
});
|
||||
row(label, &entry)
|
||||
}
|
||||
|
||||
/// A dropdown that stores the selected option string at `path`.
|
||||
pub fn dropdown_row(label: &str, doc: &Doc, path: Path, options: &[&str], default: &str) -> GBox {
|
||||
let cur = config::get_str(&doc.borrow(), path).unwrap_or_else(|| default.to_string());
|
||||
let model = StringList::new(options);
|
||||
let dd = DropDown::new(Some(model), Expression::NONE);
|
||||
let sel = options.iter().position(|o| *o == cur).unwrap_or(0) as u32;
|
||||
dd.set_selected(sel);
|
||||
let owned: Vec<String> = options.iter().map(|s| s.to_string()).collect();
|
||||
let doc = doc.clone();
|
||||
dd.connect_selected_notify(move |dd| {
|
||||
if let Some(opt) = owned.get(dd.selected() as usize) {
|
||||
config::set_str(&mut doc.borrow_mut(), path, opt);
|
||||
}
|
||||
});
|
||||
row(label, &dd)
|
||||
}
|
||||
|
||||
/// An integer spin button storing its value at `path`.
|
||||
pub fn spin_row(
|
||||
label: &str,
|
||||
doc: &Doc,
|
||||
path: Path,
|
||||
min: f64,
|
||||
max: f64,
|
||||
step: f64,
|
||||
default: i64,
|
||||
) -> GBox {
|
||||
let cur = config::get_i64(&doc.borrow(), path).unwrap_or(default);
|
||||
let adj = Adjustment::new(cur as f64, min, max, step, step, 0.0);
|
||||
let spin = SpinButton::new(Some(&adj), step, 0);
|
||||
let doc = doc.clone();
|
||||
spin.connect_value_changed(move |s| {
|
||||
config::set_i64(&mut doc.borrow_mut(), path, s.value() as i64);
|
||||
});
|
||||
row(label, &spin)
|
||||
}
|
||||
|
||||
/// A fractional spin button (e.g. 0.0–1.0 confidence) storing a float.
|
||||
pub fn spin_f64_row(
|
||||
label: &str,
|
||||
doc: &Doc,
|
||||
path: Path,
|
||||
min: f64,
|
||||
max: f64,
|
||||
step: f64,
|
||||
digits: u32,
|
||||
default: f64,
|
||||
) -> GBox {
|
||||
let cur = config::get_f64(&doc.borrow(), path).unwrap_or(default);
|
||||
let adj = Adjustment::new(cur, min, max, step, step, 0.0);
|
||||
let spin = SpinButton::new(Some(&adj), step, digits);
|
||||
let doc = doc.clone();
|
||||
spin.connect_value_changed(move |s| {
|
||||
config::set_f64(&mut doc.borrow_mut(), path, s.value());
|
||||
});
|
||||
row(label, &spin)
|
||||
}
|
||||
|
||||
/// A comma-separated list editor storing an array of strings at `path`.
|
||||
pub fn csv_row(label: &str, doc: &Doc, path: Path, placeholder: &str) -> GBox {
|
||||
let cur = config::get_str_list(&doc.borrow(), path).join(", ");
|
||||
let entry = Entry::new();
|
||||
entry.set_text(&cur);
|
||||
entry.set_hexpand(true);
|
||||
entry.set_width_chars(28);
|
||||
if !placeholder.is_empty() {
|
||||
entry.set_placeholder_text(Some(placeholder));
|
||||
}
|
||||
let doc = doc.clone();
|
||||
entry.connect_changed(move |e| {
|
||||
let items: Vec<String> = e
|
||||
.text()
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
config::set_str_list(&mut doc.borrow_mut(), path, &items);
|
||||
});
|
||||
row(label, &entry)
|
||||
}
|
||||
|
||||
/// A Save button + transient status label that persists the document to `path`.
|
||||
pub fn save_button(doc: &Doc, path: PathBuf) -> GBox {
|
||||
let btn_row = GBox::new(Orientation::Horizontal, 12);
|
||||
btn_row.set_margin_top(16);
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
save_btn.add_css_class("suggested-action");
|
||||
let status = Label::new(None);
|
||||
status.add_css_class("dim-label");
|
||||
|
||||
let doc = doc.clone();
|
||||
let status_c = status.clone();
|
||||
save_btn.connect_clicked(move |_| match config::save_doc(&path, &doc.borrow()) {
|
||||
Ok(()) => {
|
||||
status_c.set_text("Saved");
|
||||
let lbl = status_c.clone();
|
||||
glib::timeout_add_seconds_local(3, move || {
|
||||
lbl.set_text("");
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
}
|
||||
Err(e) => status_c.set_text(&format!("Error: {e}")),
|
||||
});
|
||||
|
||||
btn_row.append(&save_btn);
|
||||
btn_row.append(&status);
|
||||
btn_row
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
4
bread_white.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 677 369" width="677" height="369" role="img" aria-label="Bread terminal logo">
|
||||
<path fill="#FFFFFF" d="M 233.00 96.00 C 228.18 100.52 224.36 105.12 222.00 111.00 C 219.37 117.57 218.50 127.32 219.00 134.00 C 219.39 139.29 220.67 143.48 223.00 148.00 C 225.68 153.19 232.13 154.83 235.00 163.00 C 242.25 183.68 230.25 267.37 235.00 286.00 C 236.52 291.95 238.13 294.33 241.00 297.00 C 243.82 299.62 245.92 300.68 252.00 302.00 C 274.95 306.97 399.57 308.16 424.00 302.00 C 431.16 300.20 434.14 297.95 437.00 295.00 C 439.23 292.71 439.95 291.45 441.00 287.00 C 444.91 270.42 433.78 182.71 441.00 163.00 C 443.68 155.70 449.32 154.77 452.00 150.00 C 454.58 145.40 456.36 140.55 457.00 135.00 C 457.76 128.44 457.34 119.48 455.00 113.00 C 452.81 106.92 448.70 101.69 444.00 97.00 C 438.89 91.90 431.52 87.55 425.00 84.00 C 418.83 80.64 414.26 78.40 406.00 76.00 C 391.93 71.90 363.58 67.13 347.00 66.00 C 335.07 65.19 326.39 66.02 316.00 67.00 C 305.39 68.00 294.29 69.44 284.00 72.00 C 273.98 74.49 263.78 77.77 255.00 82.00 C 246.91 85.90 238.71 90.65 233.00 96.00 Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#22272B" d="M 233.00 96.00 C 228.18 100.52 224.36 105.12 222.00 111.00 C 219.37 117.57 218.50 127.32 219.00 134.00 C 219.39 139.29 220.67 143.48 223.00 148.00 C 225.68 153.19 232.13 154.83 235.00 163.00 C 242.25 183.68 230.25 267.37 235.00 286.00 C 236.52 291.95 238.13 294.33 241.00 297.00 C 243.82 299.62 245.92 300.68 252.00 302.00 C 274.95 306.97 399.57 308.16 424.00 302.00 C 431.16 300.20 434.14 297.95 437.00 295.00 C 439.23 292.71 439.95 291.45 441.00 287.00 C 444.91 270.42 433.78 182.71 441.00 163.00 C 443.68 155.70 449.32 154.77 452.00 150.00 C 454.58 145.40 456.36 140.55 457.00 135.00 C 457.76 128.44 457.34 119.48 455.00 113.00 C 452.81 106.92 448.70 101.69 444.00 97.00 C 438.89 91.90 431.52 87.55 425.00 84.00 C 418.83 80.64 414.26 78.40 406.00 76.00 C 391.93 71.90 363.58 67.13 347.00 66.00 C 335.07 65.19 326.39 66.02 316.00 67.00 C 305.39 68.00 294.29 69.44 284.00 72.00 C 273.98 74.49 263.78 77.77 255.00 82.00 C 246.91 85.90 238.71 90.65 233.00 96.00 Z M 236.00 111.00 C 240.70 104.95 252.16 97.27 260.00 93.00 C 266.44 89.50 271.52 88.07 279.00 86.00 C 289.20 83.18 303.07 80.21 316.00 79.00 C 329.97 77.69 345.66 77.61 360.00 79.00 C 373.97 80.35 388.66 82.80 401.00 87.00 C 412.06 90.77 423.64 96.26 431.00 102.00 C 436.45 106.26 440.67 110.64 443.00 116.00 C 445.31 121.32 445.92 128.39 445.00 134.00 C 444.12 139.36 440.97 145.01 438.00 149.00 C 435.46 152.40 431.30 151.88 429.00 157.00 C 421.48 173.75 434.77 272.07 429.00 286.00 C 427.65 289.26 427.35 289.61 424.00 291.00 C 407.50 297.83 268.52 298.74 252.00 291.00 C 248.48 289.35 248.34 288.69 247.00 285.00 C 241.59 270.06 254.49 173.67 247.00 157.00 C 244.70 151.89 240.54 152.40 238.00 149.00 C 235.03 145.01 232.05 138.72 231.00 134.00 C 230.15 130.15 230.27 126.67 231.00 123.00 C 231.80 119.01 232.90 114.98 236.00 111.00 Z M 250.00 114.00 C 247.10 116.53 245.19 118.87 244.00 122.00 C 242.69 125.44 242.28 130.28 243.00 134.00 C 243.69 137.59 245.68 140.95 248.00 144.00 C 250.55 147.34 255.50 147.38 258.00 153.00 C 265.57 170.01 255.06 263.56 258.00 276.00 C 258.56 278.37 258.16 278.85 260.00 280.00 C 270.82 286.73 401.52 284.73 414.00 281.00 C 415.91 280.43 416.14 280.60 417.00 279.00 C 422.10 269.55 409.97 170.96 418.00 153.00 C 420.76 146.83 426.43 146.39 429.00 143.00 C 430.99 140.37 432.40 138.08 433.00 135.00 C 433.73 131.28 433.45 125.75 432.00 122.00 C 430.64 118.49 428.73 115.87 425.00 113.00 C 418.39 107.92 402.93 102.18 392.00 99.00 C 381.94 96.07 371.95 95.00 362.00 94.00 C 352.28 93.03 343.91 92.52 333.00 93.00 C 318.78 93.62 295.80 96.46 284.00 99.00 C 277.13 100.48 273.37 101.70 268.00 104.00 C 262.05 106.55 254.16 110.38 250.00 114.00 Z M 292.00 152.00 L 294.00 152.00 L 334.00 192.00 L 334.00 194.00 L 294.00 234.00 L 291.00 234.00 L 283.00 223.00 L 312.00 194.00 L 312.00 192.00 L 283.00 163.00 Z M 337.00 222.00 L 339.00 220.00 L 393.00 220.00 L 394.00 234.00 L 339.00 235.00 L 337.00 233.00 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4 KiB |
90
build-local.sh
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env bash
|
||||
# Local BOS ISO build for hermes (native Arch — no container needed).
|
||||
#
|
||||
# Builds straight from the working tree in ./iso. Two speedups vs the hestia
|
||||
# container build:
|
||||
# * runs natively (hermes is Arch; hestia needed a dockerised Arch)
|
||||
# * no 2 GB scp afterwards — the ISO lands here, where we test it
|
||||
#
|
||||
# FAST_BUILD=1 zstd squashfs instead of xz -9e: compresses many times
|
||||
# faster at the cost of a slightly larger image. Dev only.
|
||||
#
|
||||
# Usage: sudo ./build-local.sh # release-quality xz
|
||||
# sudo FAST_BUILD=1 ./build-local.sh # fast dev iteration
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "$0")" && pwd)"
|
||||
WORK=/tmp/bos-work
|
||||
OUT="${OUT:-$REPO/out}"
|
||||
|
||||
# Build against a throwaway copy of the profile so the working tree stays clean
|
||||
# when FAST_BUILD / the registry rewrite mutate profile files.
|
||||
STAGE=/tmp/bos-iso-stage
|
||||
rm -rf "$STAGE" && cp -a "$REPO/iso" "$STAGE"
|
||||
|
||||
# The public git.breadway.dev URL is flaky/unreachable from hermes; Forgejo is
|
||||
# directly reachable over Tailscale (hestia 100.66.238.26:3002). Only rewrites
|
||||
# the staged copy, never the committed pacman.conf.
|
||||
sed -i 's#https://git.breadway.dev/api/packages/Breadway/arch/os#http://100.66.238.26:3002/api/packages/Breadway/arch/os#' "$STAGE/pacman.conf"
|
||||
|
||||
if [ "${FAST_BUILD:-0}" = "1" ]; then
|
||||
echo "=== FAST_BUILD: squashfs -> zstd level 6 ==="
|
||||
sed -i "s#^airootfs_image_tool_options=.*#airootfs_image_tool_options=('-comp' 'zstd' '-Xcompression-level' '6' '-b' '1M')#" "$STAGE/profiledef.sh"
|
||||
fi
|
||||
grep airootfs_image_tool_options "$STAGE/profiledef.sh"
|
||||
|
||||
# --- Bake this laptop's bakery-installed bread ecosystem into /etc/skel -------
|
||||
# The bread apps are managed by bakery (which fetches release binaries from
|
||||
# GitHub), not pacman. bakery needs DNS at install time, which the live/installed
|
||||
# image doesn't have — so instead of running bakery on the target, we copy the
|
||||
# exact binaries + bakery manifest this laptop already has into skel. Every user
|
||||
# created from skel (the live user and the installed user) then gets the same
|
||||
# versions `bakery list` reports here, fully offline. Copied at build time so the
|
||||
# binaries never bloat the git repo and always track the current bakery state.
|
||||
BREAD_BINS=(bakery bread breadd breadman breadbar breadbox breadbox-sync breadcrumbs breadpad bread-theme)
|
||||
LAPTOP_HOME="${LAPTOP_HOME:-$(getent passwd "${SUDO_USER:-$USER}" | cut -d: -f6)}"
|
||||
BAKERY_BIN="$LAPTOP_HOME/.local/bin"
|
||||
BAKERY_STATE="$LAPTOP_HOME/.local/state/bakery"
|
||||
BAKERY_CACHE="$LAPTOP_HOME/.cache/bakery"
|
||||
SKEL="$STAGE/airootfs/etc/skel"
|
||||
echo "=== baking bakery bread ecosystem from $LAPTOP_HOME ==="
|
||||
install -d -m 0755 "$SKEL/.local/bin" "$SKEL/.local/state/bakery" "$SKEL/.cache/bakery"
|
||||
for b in "${BREAD_BINS[@]}"; do
|
||||
install -m 0755 "$BAKERY_BIN/$b" "$SKEL/.local/bin/$b"
|
||||
done
|
||||
install -m 0644 "$BAKERY_STATE/installed.json" "$SKEL/.local/state/bakery/installed.json"
|
||||
# bakery fetches its package index from dl.breadway.dev (then a GitHub fallback),
|
||||
# but falls back to a cached index when both are unreachable. With no network/DNS
|
||||
# in the live/installed image, even `bakery list` errors unless that cache exists,
|
||||
# so bake it in too — then bakery works fully offline (list/info from cache;
|
||||
# install/update still need network, as expected).
|
||||
install -m 0644 "$BAKERY_CACHE/index.json" "$SKEL/.cache/bakery/index.json"
|
||||
echo "baked: $(ls "$SKEL/.local/bin")"
|
||||
|
||||
# mkarchiso resets every airootfs file to 0644, so executables must be declared
|
||||
# in profiledef.sh's file_permissions array or they ship non-executable and the
|
||||
# exec-once launches fail with "permission denied". Inject a 0755 entry for each
|
||||
# baked binary right after the array opener (keeps the binary list in one place).
|
||||
perm_file="$(mktemp)"
|
||||
for b in "${BREAD_BINS[@]}"; do
|
||||
printf ' ["/etc/skel/.local/bin/%s"]="0:0:755"\n' "$b" >>"$perm_file"
|
||||
done
|
||||
sed -i "/^file_permissions=(/r $perm_file" "$STAGE/profiledef.sh"
|
||||
rm -f "$perm_file"
|
||||
echo "=== file_permissions after injection ==="; grep -A14 '^file_permissions=(' "$STAGE/profiledef.sh"
|
||||
|
||||
# Pin one timestamp for the whole build. Without this, mkarchiso derives the
|
||||
# boot-config UUID (%ARCHISO_UUID%) when it starts and the iso9660 volume UUID
|
||||
# when xorriso writes the image at the end — on a slow build these diverge by
|
||||
# the build duration, so the initramfs searches /dev/disk/by-uuid/<wrong-uuid>,
|
||||
# never finds the medium, and drops to a recovery shell. Fixing the epoch makes
|
||||
# both derive from the same instant (and makes builds reproducible).
|
||||
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(date +%s)}"
|
||||
echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH ($(date -u -d "@$SOURCE_DATE_EPOCH" +%Y-%m-%d-%H-%M-%S-00))"
|
||||
|
||||
echo "=== running mkarchiso ==="
|
||||
rm -rf "$WORK" && mkdir -p "$OUT"
|
||||
mkarchiso -v -w "$WORK" -o "$OUT" "$STAGE"
|
||||
|
||||
echo "=== RESULT ==="
|
||||
if ls -lh "$OUT"/*.iso 2>/dev/null; then echo "ISO BUILT OK -> $OUT"; else echo "ISO BUILD FAILED"; exit 1; fi
|
||||
|
|
@ -46,7 +46,6 @@ input {
|
|||
}
|
||||
|
||||
dwindle {
|
||||
pseudotile = true
|
||||
preserve_split = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ slideshow: "show.qml"
|
|||
slideshowAPI: 2
|
||||
|
||||
style:
|
||||
sidebarBackground: "#3b4252"
|
||||
sidebarText: "#eceff4"
|
||||
sidebarTextSelect: "#5e81ac"
|
||||
sidebarTextHighlight:"#eceff4"
|
||||
sidebarBackground: "#230b00"
|
||||
sidebarText: "#f1dcbd"
|
||||
sidebarTextSelect: "#EAB672"
|
||||
sidebarTextHighlight: "#ffffff"
|
||||
|
|
|
|||
BIN
iso/airootfs/etc/calamares/branding/bos/languages.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
iso/airootfs/etc/calamares/branding/bos/logo.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
4
iso/airootfs/etc/calamares/branding/bos/logo.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 677 369" width="677" height="369" role="img" aria-label="Bread terminal logo">
|
||||
<path fill="#FFFFFF" d="M 233.00 96.00 C 228.18 100.52 224.36 105.12 222.00 111.00 C 219.37 117.57 218.50 127.32 219.00 134.00 C 219.39 139.29 220.67 143.48 223.00 148.00 C 225.68 153.19 232.13 154.83 235.00 163.00 C 242.25 183.68 230.25 267.37 235.00 286.00 C 236.52 291.95 238.13 294.33 241.00 297.00 C 243.82 299.62 245.92 300.68 252.00 302.00 C 274.95 306.97 399.57 308.16 424.00 302.00 C 431.16 300.20 434.14 297.95 437.00 295.00 C 439.23 292.71 439.95 291.45 441.00 287.00 C 444.91 270.42 433.78 182.71 441.00 163.00 C 443.68 155.70 449.32 154.77 452.00 150.00 C 454.58 145.40 456.36 140.55 457.00 135.00 C 457.76 128.44 457.34 119.48 455.00 113.00 C 452.81 106.92 448.70 101.69 444.00 97.00 C 438.89 91.90 431.52 87.55 425.00 84.00 C 418.83 80.64 414.26 78.40 406.00 76.00 C 391.93 71.90 363.58 67.13 347.00 66.00 C 335.07 65.19 326.39 66.02 316.00 67.00 C 305.39 68.00 294.29 69.44 284.00 72.00 C 273.98 74.49 263.78 77.77 255.00 82.00 C 246.91 85.90 238.71 90.65 233.00 96.00 Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#22272B" d="M 233.00 96.00 C 228.18 100.52 224.36 105.12 222.00 111.00 C 219.37 117.57 218.50 127.32 219.00 134.00 C 219.39 139.29 220.67 143.48 223.00 148.00 C 225.68 153.19 232.13 154.83 235.00 163.00 C 242.25 183.68 230.25 267.37 235.00 286.00 C 236.52 291.95 238.13 294.33 241.00 297.00 C 243.82 299.62 245.92 300.68 252.00 302.00 C 274.95 306.97 399.57 308.16 424.00 302.00 C 431.16 300.20 434.14 297.95 437.00 295.00 C 439.23 292.71 439.95 291.45 441.00 287.00 C 444.91 270.42 433.78 182.71 441.00 163.00 C 443.68 155.70 449.32 154.77 452.00 150.00 C 454.58 145.40 456.36 140.55 457.00 135.00 C 457.76 128.44 457.34 119.48 455.00 113.00 C 452.81 106.92 448.70 101.69 444.00 97.00 C 438.89 91.90 431.52 87.55 425.00 84.00 C 418.83 80.64 414.26 78.40 406.00 76.00 C 391.93 71.90 363.58 67.13 347.00 66.00 C 335.07 65.19 326.39 66.02 316.00 67.00 C 305.39 68.00 294.29 69.44 284.00 72.00 C 273.98 74.49 263.78 77.77 255.00 82.00 C 246.91 85.90 238.71 90.65 233.00 96.00 Z M 236.00 111.00 C 240.70 104.95 252.16 97.27 260.00 93.00 C 266.44 89.50 271.52 88.07 279.00 86.00 C 289.20 83.18 303.07 80.21 316.00 79.00 C 329.97 77.69 345.66 77.61 360.00 79.00 C 373.97 80.35 388.66 82.80 401.00 87.00 C 412.06 90.77 423.64 96.26 431.00 102.00 C 436.45 106.26 440.67 110.64 443.00 116.00 C 445.31 121.32 445.92 128.39 445.00 134.00 C 444.12 139.36 440.97 145.01 438.00 149.00 C 435.46 152.40 431.30 151.88 429.00 157.00 C 421.48 173.75 434.77 272.07 429.00 286.00 C 427.65 289.26 427.35 289.61 424.00 291.00 C 407.50 297.83 268.52 298.74 252.00 291.00 C 248.48 289.35 248.34 288.69 247.00 285.00 C 241.59 270.06 254.49 173.67 247.00 157.00 C 244.70 151.89 240.54 152.40 238.00 149.00 C 235.03 145.01 232.05 138.72 231.00 134.00 C 230.15 130.15 230.27 126.67 231.00 123.00 C 231.80 119.01 232.90 114.98 236.00 111.00 Z M 250.00 114.00 C 247.10 116.53 245.19 118.87 244.00 122.00 C 242.69 125.44 242.28 130.28 243.00 134.00 C 243.69 137.59 245.68 140.95 248.00 144.00 C 250.55 147.34 255.50 147.38 258.00 153.00 C 265.57 170.01 255.06 263.56 258.00 276.00 C 258.56 278.37 258.16 278.85 260.00 280.00 C 270.82 286.73 401.52 284.73 414.00 281.00 C 415.91 280.43 416.14 280.60 417.00 279.00 C 422.10 269.55 409.97 170.96 418.00 153.00 C 420.76 146.83 426.43 146.39 429.00 143.00 C 430.99 140.37 432.40 138.08 433.00 135.00 C 433.73 131.28 433.45 125.75 432.00 122.00 C 430.64 118.49 428.73 115.87 425.00 113.00 C 418.39 107.92 402.93 102.18 392.00 99.00 C 381.94 96.07 371.95 95.00 362.00 94.00 C 352.28 93.03 343.91 92.52 333.00 93.00 C 318.78 93.62 295.80 96.46 284.00 99.00 C 277.13 100.48 273.37 101.70 268.00 104.00 C 262.05 106.55 254.16 110.38 250.00 114.00 Z M 292.00 152.00 L 294.00 152.00 L 334.00 192.00 L 334.00 194.00 L 294.00 234.00 L 291.00 234.00 L 283.00 223.00 L 312.00 194.00 L 312.00 192.00 L 283.00 163.00 Z M 337.00 222.00 L 339.00 220.00 L 393.00 220.00 L 394.00 234.00 L 339.00 235.00 L 337.00 233.00 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4 KiB |
23
iso/airootfs/etc/calamares/branding/bos/stylesheet.qss
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* BOS Calamares styling.
|
||||
*
|
||||
* The branding `style:` sidebar keys were being overridden, leaving the
|
||||
* step tabs invisible. This forces them: bread-palette sidebar with clearly
|
||||
* legible step labels. (A full installer retheme is tracked separately.)
|
||||
*/
|
||||
|
||||
/* Left sidebar / progress steps */
|
||||
#sidebarApp {
|
||||
background-color: #230b00;
|
||||
}
|
||||
|
||||
#sidebarApp QLabel {
|
||||
color: #f1dcbd;
|
||||
font-size: 11pt;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
/* Logo at the top of the sidebar — keep aspect ratio, don't stretch */
|
||||
#logoApp {
|
||||
qproperty-alignment: AlignCenter;
|
||||
margin: 14px 8px;
|
||||
}
|
||||
|
|
@ -8,3 +8,32 @@ mountOptions:
|
|||
options: [noatime, "compress=zstd", "space_cache=v2"]
|
||||
- filesystem: vfat
|
||||
options: [umask=0077]
|
||||
|
||||
# API filesystems mounted into the target so chroot steps work. Without these
|
||||
# the chroot has no /proc or /dev and `mkinitcpio` aborts ("/proc must be
|
||||
# mounted!" / "/dev must be mounted!") and grub-install can't probe properly.
|
||||
# All are real-fs mounts (not bind) — Calamares 3.4.2 here applies fs-type mounts
|
||||
# reliably but not bind-type ones, so /dev uses a fresh devtmpfs (which still
|
||||
# exposes all device nodes). extraMountsEfi adds efivars on UEFI so grub-install
|
||||
# can write an NVRAM boot entry.
|
||||
extraMounts:
|
||||
- device: proc
|
||||
fs: proc
|
||||
mountPoint: /proc
|
||||
- device: sys
|
||||
fs: sysfs
|
||||
mountPoint: /sys
|
||||
- device: udev
|
||||
fs: devtmpfs
|
||||
mountPoint: /dev
|
||||
- device: devpts
|
||||
fs: devpts
|
||||
mountPoint: /dev/pts
|
||||
- device: tmpfs
|
||||
fs: tmpfs
|
||||
mountPoint: /run
|
||||
|
||||
extraMountsEfi:
|
||||
- device: efivarfs
|
||||
fs: efivarfs
|
||||
mountPoint: /sys/firmware/efi/efivars
|
||||
|
|
|
|||
6
iso/airootfs/etc/calamares/modules/plymouthcfg.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
# Boot-splash theme for the installed system. Calamares sets this as the default
|
||||
# plymouth theme and signals initcpiocfg to add the plymouth hook to the
|
||||
# initramfs. The post-install script also enforces the hook + a quiet/splash
|
||||
# cmdline as a belt-and-suspenders.
|
||||
plymouth_theme: bos
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Lay the kernel into the target /boot before the bootloader/initramfs steps.
|
||||
# Runs in the live environment (not the chroot) so it can read the ISO boot dir.
|
||||
dontChroot: true
|
||||
timeout: 60
|
||||
|
||||
script:
|
||||
- "/usr/bin/bash /usr/local/bin/bos-copy-kernel ${ROOT}"
|
||||
|
|
@ -1,3 +1,10 @@
|
|||
---
|
||||
# BOS finalization, run after the native initcpio + bootloader modules. It does
|
||||
# only fast, non-boot-critical work (live-medium cleanup, snapper, services,
|
||||
# dotfiles), so the shellprocess timeout can no longer leave the system
|
||||
# unbootable — the boot-critical steps are owned by dedicated Calamares modules.
|
||||
# A generous timeout is kept as a safety margin, and the leading "-" keeps a
|
||||
# non-zero exit non-fatal to the install.
|
||||
timeout: 600
|
||||
script:
|
||||
- "-/usr/bin/bash /etc/calamares/post-install.sh"
|
||||
|
|
|
|||
|
|
@ -38,3 +38,4 @@ passwordRequirements:
|
|||
- minlen=6
|
||||
|
||||
allowWeakPasswords: false
|
||||
userShell: /bin/zsh
|
||||
|
|
|
|||
|
|
@ -1,41 +1,151 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
# BOS-specific finalization, run inside the installed-system chroot (Calamares
|
||||
# shellprocess), AFTER the native initcpio module has built the initramfs. The
|
||||
# kernel (shellprocess@kernel) and initramfs (initcpio) are in place by now, so
|
||||
# this script installs GRUB and does the rest of setup. Calamares' own
|
||||
# `bootloader`/`grubcfg` modules are NOT used — in this archiso layout they leave
|
||||
# the ESP empty and abort; the explicit grub-install below is verified to boot.
|
||||
# Best-effort: do NOT use `set -e`; a single failure here must not abort the rest.
|
||||
set -uo pipefail
|
||||
|
||||
# --- Snapper root config ---
|
||||
snapper -c root create-config /
|
||||
MAIN_USER="$(getent passwd 1000 | cut -d: -f1 || true)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Strip live-only bits that unpackfs copied verbatim from the live medium.
|
||||
# ---------------------------------------------------------------------------
|
||||
rm -f /etc/systemd/system/getty@tty1.service.d/autologin.conf
|
||||
rm -f /etc/systemd/system/bos-live-setup.service \
|
||||
/etc/systemd/system/multi-user.target.wants/bos-live-setup.service
|
||||
rm -f /usr/local/bin/bos-live-setup /usr/local/bin/bos-launch-calamares
|
||||
rm -f /etc/sudoers.d/99-bos-live
|
||||
userdel -r liveuser 2>/dev/null || true
|
||||
|
||||
# Root used a passwordless entry on the live medium; lock it (sudo model).
|
||||
passwd -l root || true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Boot splash (Plymouth) — BOS logo + spinner instead of kernel text. Done
|
||||
# BEFORE grub so grub.cfg picks up the new cmdline and the rebuilt initramfs.
|
||||
# All best-effort: if anything here fails the system still boots (just without
|
||||
# the splash) — the initramfs the initcpio module already built stays valid.
|
||||
# ---------------------------------------------------------------------------
|
||||
if command -v plymouth-set-default-theme &>/dev/null; then
|
||||
# Ensure the plymouth hook is in HOOKS (plymouthcfg/initcpiocfg usually add it;
|
||||
# this is the belt). Handle both the udev and systemd initramfs styles.
|
||||
if ! grep -q 'plymouth' /etc/mkinitcpio.conf 2>/dev/null; then
|
||||
if grep -qE '^HOOKS=.*\bsystemd\b' /etc/mkinitcpio.conf; then
|
||||
sed -i 's/^\(HOOKS=.*\bsystemd\b\)/\1 sd-plymouth/' /etc/mkinitcpio.conf \
|
||||
|| echo "WARN: adding sd-plymouth hook failed"
|
||||
else
|
||||
sed -i 's/^\(HOOKS=.*\budev\b\)/\1 plymouth/' /etc/mkinitcpio.conf \
|
||||
|| echo "WARN: adding plymouth hook failed"
|
||||
fi
|
||||
fi
|
||||
# Clean boot: splash activates plymouth; hiding systemd status removes the
|
||||
# "[ OK ] Started ..." text (what looked like kernel output) even if the
|
||||
# splash itself doesn't grab the display (e.g. in some VMs).
|
||||
if ! grep -q 'splash' /etc/default/grub 2>/dev/null; then
|
||||
sed -i 's/^\(GRUB_CMDLINE_LINUX_DEFAULT="\)/\1splash quiet vt.global_cursor_default=0 systemd.show_status=false rd.systemd.show_status=false rd.udev.log_level=3 /' \
|
||||
/etc/default/grub || echo "WARN: adding splash cmdline failed"
|
||||
fi
|
||||
# Set the BOS theme and rebuild the initramfs (-R) with the plymouth hook.
|
||||
plymouth-set-default-theme -R bos || echo "WARN: plymouth-set-default-theme failed"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Install GRUB (UEFI). /boot now has the kernel + initramfs, and the mount
|
||||
# module has bind-mounted /proc /sys /dev /run + efivars into this chroot, so
|
||||
# both grub-install passes and grub-mkconfig succeed.
|
||||
# 1. NVRAM entry (EFI/BOS/grubx64.efi + a firmware boot entry)
|
||||
# 2. --removable copy to EFI/BOOT/BOOTX64.EFI, so firmware that ignores/loses
|
||||
# the NVRAM entry (the "no boot device / PXE fallback" failure) still finds
|
||||
# a bootloader.
|
||||
# ---------------------------------------------------------------------------
|
||||
if command -v grub-install &>/dev/null; then
|
||||
grub-install --target=x86_64-efi --efi-directory=/boot/efi \
|
||||
--bootloader-id=BOS --recheck \
|
||||
|| echo "WARN: grub-install (nvram) failed"
|
||||
grub-install --target=x86_64-efi --efi-directory=/boot/efi \
|
||||
--removable --recheck \
|
||||
|| echo "WARN: grub-install (removable) failed"
|
||||
fi
|
||||
if command -v grub-mkconfig &>/dev/null; then
|
||||
grub-mkconfig -o /boot/grub/grub.cfg || echo "WARN: grub-mkconfig failed"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Snapper root config (root is btrfs).
|
||||
# ---------------------------------------------------------------------------
|
||||
if command -v snapper &>/dev/null; then
|
||||
snapper -c root create-config / || echo "WARN: snapper create-config failed"
|
||||
if [[ -f /etc/snapper/configs/root ]]; then
|
||||
sed -i 's/TIMELINE_CREATE="yes"/TIMELINE_CREATE="no"/' /etc/snapper/configs/root
|
||||
sed -i 's/NUMBER_CLEANUP="no"/NUMBER_CLEANUP="yes"/' /etc/snapper/configs/root
|
||||
sed -i 's/NUMBER_MIN_AGE="[^"]*"/NUMBER_MIN_AGE="1800"/' /etc/snapper/configs/root
|
||||
sed -i 's/NUMBER_LIMIT="[^"]*"/NUMBER_LIMIT="10"/' /etc/snapper/configs/root
|
||||
sed -i 's/NUMBER_LIMIT_IMPORTANT="[^"]*"/NUMBER_LIMIT_IMPORTANT="5"/' /etc/snapper/configs/root
|
||||
|
||||
# Allow main user to list/create/delete snapshots without sudo
|
||||
MAIN_USER=$(getent passwd 1000 | cut -d: -f1)
|
||||
[[ -n "$MAIN_USER" ]] && \
|
||||
sed -i "s/ALLOW_USERS=\"\"/ALLOW_USERS=\"$MAIN_USER\"/" /etc/snapper/configs/root
|
||||
|
||||
# --- System services ---
|
||||
systemctl enable NetworkManager
|
||||
systemctl enable bluetooth
|
||||
systemctl enable snapper-cleanup.timer
|
||||
systemctl enable grub-btrfs.path
|
||||
|
||||
# --- Bakery: install bread ecosystem ---
|
||||
# Requires [breadway] repo in /etc/pacman.conf — see iso/pacman.conf
|
||||
if command -v bakery &>/dev/null; then
|
||||
sudo -u "$MAIN_USER" bakery install bread breadbar breadbox breadcrumbs breadpad bos-settings
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Deploy dotfiles into user home (skip any file that already exists) ---
|
||||
SKEL_SRC="/etc/skel/.config"
|
||||
DOTFILES_DEST="/home/$MAIN_USER/.config"
|
||||
# ---------------------------------------------------------------------------
|
||||
# System services. Enable each one INDEPENDENTLY: `systemctl enable a b c`
|
||||
# resolves every unit first and enables NONE if any one can't be loaded, so a
|
||||
# single wrong/absent unit name would silently leave NetworkManager (etc.)
|
||||
# disabled. The loop isolates failures to the offending unit.
|
||||
# greetd — graphical login (shipped disabled; live uses tty autologin)
|
||||
# grub-btrfsd — regenerates GRUB snapshot entries (the unit is grub-btrfsd.service,
|
||||
# NOT grub-btrfs.path, which no longer exists)
|
||||
# ---------------------------------------------------------------------------
|
||||
for unit in NetworkManager.service bluetooth.service systemd-timesyncd.service \
|
||||
tlp.service greetd.service snapper-cleanup.timer grub-btrfsd.service \
|
||||
fstrim.timer cups.socket avahi-daemon.service ufw.service \
|
||||
fwupd-refresh.timer reflector.timer; do
|
||||
systemctl enable "$unit" || echo "WARN: failed to enable $unit"
|
||||
done
|
||||
systemctl set-default graphical.target || echo "WARN: set-default graphical failed"
|
||||
|
||||
if [[ -d "$SKEL_SRC" ]]; then
|
||||
mkdir -p "$DOTFILES_DEST"
|
||||
cp -rn "$SKEL_SRC/." "$DOTFILES_DEST/"
|
||||
chown -R "$MAIN_USER:$MAIN_USER" "$DOTFILES_DEST"
|
||||
# ---------------------------------------------------------------------------
|
||||
# mDNS resolution (nss-mdns): insert mdns_minimal into the hosts: line so the
|
||||
# resolver answers *.local (network printers, other hosts) via avahi. Idempotent.
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ -f /etc/nsswitch.conf ]] && ! grep -q 'mdns_minimal' /etc/nsswitch.conf; then
|
||||
sed -i 's/^\(hosts:[[:space:]]*\)/\1mdns_minimal [NOTFOUND=return] /' \
|
||||
/etc/nsswitch.conf || echo "WARN: wiring nss-mdns failed"
|
||||
fi
|
||||
|
||||
# --- XDG user dirs ---
|
||||
sudo -u "$MAIN_USER" xdg-user-dirs-update
|
||||
# ---------------------------------------------------------------------------
|
||||
# Firewall: deny inbound by default, allow outbound, and permit inbound mDNS so
|
||||
# avahi printer/service discovery keeps working. Best-effort — rule application
|
||||
# happens at boot; here we only persist the policy + enable the unit.
|
||||
# ---------------------------------------------------------------------------
|
||||
if command -v ufw &>/dev/null; then
|
||||
ufw default deny incoming || echo "WARN: ufw default deny incoming failed"
|
||||
ufw default allow outgoing || echo "WARN: ufw default allow outgoing failed"
|
||||
ufw allow 5353/udp || echo "WARN: ufw allow mDNS failed"
|
||||
ufw --force enable || echo "WARN: ufw enable failed"
|
||||
fi
|
||||
|
||||
echo "BOS post-install complete. Reboot to start your system."
|
||||
# The bread ecosystem (bakery + bread, breadbar, breadbox, breadcrumbs, breadpad)
|
||||
# is bakery-managed, not pacman: the binaries and bakery manifest live in
|
||||
# /etc/skel/.local (baked in at ISO build time) and are copied into the user's
|
||||
# home below, so the install works fully offline with no DNS for bakery/GitHub.
|
||||
# bos-settings is the only pacman bread package and was installed by unpackfs.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Deploy dotfiles + the bakery bread ecosystem into the user's home (Calamares
|
||||
# already seeds from /etc/skel, but copy explicitly too so a fresh install is
|
||||
# self-contained even if the users module skips skel). Don't clobber existing.
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ -n "$MAIN_USER" && -d /etc/skel ]]; then
|
||||
for d in .config .local .cache; do
|
||||
[[ -d "/etc/skel/$d" ]] || continue
|
||||
mkdir -p "/home/$MAIN_USER/$d"
|
||||
cp -rn "/etc/skel/$d/." "/home/$MAIN_USER/$d/" || true
|
||||
chown -R "$MAIN_USER:$MAIN_USER" "/home/$MAIN_USER/$d" || true
|
||||
done
|
||||
sudo -u "$MAIN_USER" xdg-user-dirs-update || true
|
||||
fi
|
||||
|
||||
echo "BOS post-install complete."
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
---
|
||||
modules-search: [/etc/calamares/modules, /usr/lib/calamares/modules]
|
||||
|
||||
# Second shellprocess instance: copies the live kernel into the target /boot
|
||||
# (archiso keeps it out of the squashfs) before the bootloader step runs.
|
||||
instances:
|
||||
- id: kernel
|
||||
module: shellprocess
|
||||
config: shellprocess-kernel.conf
|
||||
|
||||
sequence:
|
||||
- show:
|
||||
- welcome
|
||||
|
|
@ -22,7 +29,21 @@ sequence:
|
|||
- networkcfg
|
||||
- hwclock
|
||||
- packages
|
||||
- bootloader
|
||||
# archiso strips the kernel from the squashfs; stage it, drop the archiso
|
||||
# initramfs config, and write a stock mkinitcpio preset before initcpio runs.
|
||||
- shellprocess@kernel
|
||||
# plymouthcfg sets the boot-splash theme and flags plymouth in use, so
|
||||
# initcpiocfg adds the plymouth hook to the initramfs that initcpio builds.
|
||||
- plymouthcfg
|
||||
# Native initramfs generation (works reliably here). The native `bootloader`
|
||||
# and `grubcfg` modules do NOT — in this archiso layout they leave the ESP
|
||||
# empty and abort the install, so GRUB is installed explicitly in
|
||||
# post-install.sh instead (grub-install --removable + NVRAM + grub-mkconfig,
|
||||
# the sequence verified to produce a bootable system).
|
||||
- initcpiocfg
|
||||
- initcpio
|
||||
# BOS finalization: GRUB install + cleanup + snapper + services + dotfiles.
|
||||
# All fast, and runs after initcpio so /boot has the kernel + initramfs.
|
||||
- shellprocess
|
||||
- umount
|
||||
- show:
|
||||
|
|
|
|||
12
iso/airootfs/etc/greetd/config.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# greetd drives login on the INSTALLED system. It is shipped DISABLED in the
|
||||
# squashfs and enabled by post-install.sh; the live ISO instead autologins
|
||||
# liveuser on tty1 (see getty@tty1 drop-in) so the installer comes straight up.
|
||||
#
|
||||
# tuigreet shows a minimal greeter, then launches the BOS Hyprland session via
|
||||
# bos-session (which fixes up PATH for the bakery bread apps).
|
||||
[terminal]
|
||||
vt = 1
|
||||
|
||||
[default_session]
|
||||
command = "tuigreet --remember --time --cmd /usr/local/bin/bos-session"
|
||||
user = "greeter"
|
||||
1
iso/airootfs/etc/hostname
Normal file
|
|
@ -0,0 +1 @@
|
|||
bos
|
||||
1
iso/airootfs/etc/locale.conf
Normal file
|
|
@ -0,0 +1 @@
|
|||
LANG=C.UTF-8
|
||||
1
iso/airootfs/etc/localtime
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/usr/share/zoneinfo/UTC
|
||||
3
iso/airootfs/etc/mkinitcpio.conf.d/archiso.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
HOOKS=(base udev microcode modconf kms memdisk archiso archiso_loop_mnt archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs block filesystems keyboard)
|
||||
COMPRESSION="xz"
|
||||
COMPRESSION_OPTIONS=(-9e)
|
||||
8
iso/airootfs/etc/mkinitcpio.d/linux.preset
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# mkinitcpio preset file for the 'linux' package on archiso
|
||||
|
||||
PRESETS=('archiso')
|
||||
|
||||
ALL_kver='/boot/vmlinuz-linux'
|
||||
archiso_config='/etc/mkinitcpio.conf.d/archiso.conf'
|
||||
|
||||
archiso_image="/boot/initramfs-linux.img"
|
||||
44
iso/airootfs/etc/pacman.conf
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#
|
||||
# BOS pacman.conf — used during ISO build and installed to the target system.
|
||||
# Based on the standard Arch Linux pacman.conf.
|
||||
#
|
||||
|
||||
[options]
|
||||
HoldPkg = pacman glibc
|
||||
Architecture = auto
|
||||
CheckSpace
|
||||
ParallelDownloads = 5
|
||||
|
||||
Color
|
||||
VerbosePkgLists
|
||||
ILoveCandy
|
||||
|
||||
SigLevel = Required DatabaseOptional
|
||||
LocalFileSigLevel = Optional
|
||||
|
||||
[core]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[extra]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[multilib]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 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.)
|
||||
#
|
||||
# Packages are published to the Forgejo Arch registry (group "os") by the
|
||||
# .forgejo/workflows/package.yml workflow in each repo, on tag push.
|
||||
#
|
||||
# Forgejo signs the repo db with a key pacman can't look up, so TrustAll
|
||||
# fails. SigLevel = Never skips verification (acceptable for this private
|
||||
# repo over TLS). TODO: import Forgejo's signing key + SigLevel = Required.
|
||||
# -----------------------------------------------------------------------
|
||||
# The section name must match Forgejo's served db filename
|
||||
# ({owner}.{group}.{domain}.db) — pacman fetches "<section>.db" from Server.
|
||||
[Breadway.os.git.breadway.dev]
|
||||
SigLevel = Never
|
||||
Server = https://git.breadway.dev/api/packages/Breadway/arch/os/$arch
|
||||
11
iso/airootfs/etc/pacman.d/mirrorlist
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# BOS default mirrorlist.
|
||||
#
|
||||
# geo.mirror.pkgbuild.com is the official Arch geo-IP redirect — it routes to a
|
||||
# nearby mirror anywhere in the world, so pacman works out of the box without
|
||||
# region-specific configuration. The others are reliable global fallbacks.
|
||||
#
|
||||
# To optimise for your location/speed later: sudo reflector --save \
|
||||
# /etc/pacman.d/mirrorlist --protocol https --latest 20 --sort rate
|
||||
Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch
|
||||
Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch
|
||||
Server = https://mirror.leaseweb.net/archlinux/$repo/os/$arch
|
||||
1
iso/airootfs/etc/passwd
Normal file
|
|
@ -0,0 +1 @@
|
|||
root:x:0:0:root:/root:/usr/bin/bash
|
||||
9
iso/airootfs/etc/profile.d/bos-local-bin.sh
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Put the per-user bakery bin dir on PATH. The bread ecosystem (breadd, breadbar,
|
||||
# breadbox, …) is installed there by bakery, and the Hyprland session launches
|
||||
# them via `exec-once`, which resolves against the PATH it inherits from the
|
||||
# login shell. Arch's stock /etc/profile does not add ~/.local/bin, so do it here
|
||||
# for every login shell (live user and installed user alike).
|
||||
case ":$PATH:" in
|
||||
*":$HOME/.local/bin:"*) ;;
|
||||
*) export PATH="$HOME/.local/bin:$PATH" ;;
|
||||
esac
|
||||
1
iso/airootfs/etc/shadow
Normal file
|
|
@ -0,0 +1 @@
|
|||
root::14871::::::
|
||||
28
iso/airootfs/etc/skel/.cache/wal/colors.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"wallpaper": "/usr/share/backgrounds/bos/bread-background.png",
|
||||
"alpha": "100",
|
||||
|
||||
"special": {
|
||||
"background": "#0c0c0c",
|
||||
"foreground": "#e8e8e8",
|
||||
"cursor": "#eab672"
|
||||
},
|
||||
"colors": {
|
||||
"color0": "#1a1a1a",
|
||||
"color1": "#b98749",
|
||||
"color2": "#cd9450",
|
||||
"color3": "#e3a85c",
|
||||
"color4": "#eab672",
|
||||
"color5": "#f6c477",
|
||||
"color6": "#eabe82",
|
||||
"color7": "#d8d8d8",
|
||||
"color8": "#3a3a3a",
|
||||
"color9": "#b98749",
|
||||
"color10": "#cd9450",
|
||||
"color11": "#e3a85c",
|
||||
"color12": "#eab672",
|
||||
"color13": "#f6c477",
|
||||
"color14": "#eabe82",
|
||||
"color15": "#f5f5f5"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,21 @@
|
|||
# breadd daemon configuration.
|
||||
# Every section is optional and every adapter defaults to enabled; this file
|
||||
# just makes the defaults explicit. See the bread docs for the full schema.
|
||||
|
||||
[daemon]
|
||||
log_level = "info"
|
||||
|
||||
[adapters]
|
||||
keyboard = true
|
||||
mouse = true
|
||||
touchpad = true
|
||||
bluetooth = true
|
||||
gamepad = true
|
||||
[adapters.hyprland]
|
||||
enabled = true
|
||||
|
||||
[adapters.udev]
|
||||
enabled = true
|
||||
|
||||
[adapters.power]
|
||||
enabled = true
|
||||
|
||||
[adapters.network]
|
||||
enabled = true
|
||||
|
||||
[adapters.bluetooth]
|
||||
enabled = true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
-- low-battery-warning — notify once when the battery runs low (zero-config).
|
||||
-- Shipped active in BOS; auto-discovered by breadd. Safe on desktops too
|
||||
-- (simply never fires without a battery).
|
||||
|
||||
local M = bread.module({ name = "low-battery-warning", version = "1.0.0" })
|
||||
|
||||
local warned = false
|
||||
|
||||
function M.on_load()
|
||||
bread.on("bread.power.battery.low", function(event)
|
||||
if warned then return end
|
||||
warned = true
|
||||
local pct = event.data.battery_percent or "?"
|
||||
bread.notify("Battery low (" .. pct .. "%). Plug in soon.", {
|
||||
urgency = "critical",
|
||||
title = "Battery",
|
||||
timeout = 10000,
|
||||
})
|
||||
end)
|
||||
|
||||
bread.on("bread.power.ac.connected", function()
|
||||
warned = false
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
7
iso/airootfs/etc/skel/.config/gtk-3.0/settings.ini
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[Settings]
|
||||
gtk-application-prefer-dark-theme=1
|
||||
gtk-theme-name=Adwaita-dark
|
||||
gtk-icon-theme-name=Papirus-Dark
|
||||
gtk-cursor-theme-name=Bibata-Modern-Ice
|
||||
gtk-cursor-theme-size=24
|
||||
gtk-font-name=Noto Sans 11
|
||||
7
iso/airootfs/etc/skel/.config/gtk-4.0/settings.ini
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[Settings]
|
||||
gtk-application-prefer-dark-theme=1
|
||||
gtk-theme-name=Adwaita-dark
|
||||
gtk-icon-theme-name=Papirus-Dark
|
||||
gtk-cursor-theme-name=Bibata-Modern-Ice
|
||||
gtk-cursor-theme-size=24
|
||||
gtk-font-name=Noto Sans 11
|
||||
37
iso/airootfs/etc/skel/.config/hypr/hypridle.conf
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Idle management (hypridle). Conservative, mainstream-OS-like defaults:
|
||||
# dim -> screen off -> lock -> suspend, all respecting idle inhibitors (so media
|
||||
# playback, etc. won't dim or suspend the machine). Vendor-neutral: nothing here
|
||||
# is Intel/AMD specific.
|
||||
general {
|
||||
lock_cmd = pidof hyprlock || hyprlock
|
||||
before_sleep_cmd = loginctl lock-session
|
||||
after_sleep_cmd = hyprctl dispatch dpms on
|
||||
ignore_dbus_inhibit = false
|
||||
}
|
||||
|
||||
# Dim the backlight after 5 minutes (restored on activity). No-op on hardware
|
||||
# without a backlight (desktops / VMs).
|
||||
listener {
|
||||
timeout = 300
|
||||
on-timeout = brightnessctl -s set 10%
|
||||
on-resume = brightnessctl -r
|
||||
}
|
||||
|
||||
# Turn the display off after 8 minutes.
|
||||
listener {
|
||||
timeout = 480
|
||||
on-timeout = hyprctl dispatch dpms off
|
||||
on-resume = hyprctl dispatch dpms on
|
||||
}
|
||||
|
||||
# Lock shortly after the screen turns off.
|
||||
listener {
|
||||
timeout = 510
|
||||
on-timeout = loginctl lock-session
|
||||
}
|
||||
|
||||
# Suspend after 20 minutes of inactivity (skipped while something inhibits idle).
|
||||
listener {
|
||||
timeout = 1200
|
||||
on-timeout = systemctl suspend
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
monitor=,preferred,auto,1
|
||||
|
||||
exec-once = breadd
|
||||
exec-once = breadbar
|
||||
exec-once = breadbox-sync
|
||||
|
||||
source = ~/.config/hypr/keybinds.conf
|
||||
|
||||
general {
|
||||
gaps_in = 5
|
||||
gaps_out = 10
|
||||
border_size = 2
|
||||
col.active_border = rgba(88c0d0ff)
|
||||
col.inactive_border = rgba(4c566aff)
|
||||
layout = dwindle
|
||||
}
|
||||
|
||||
decoration {
|
||||
rounding = 8
|
||||
blur {
|
||||
enabled = true
|
||||
size = 6
|
||||
passes = 2
|
||||
}
|
||||
shadow {
|
||||
enabled = true
|
||||
range = 12
|
||||
render_power = 3
|
||||
}
|
||||
}
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = ease, 0.25, 0.1, 0.25, 1.0
|
||||
animation = windows, 1, 4, ease
|
||||
animation = fade, 1, 4, ease
|
||||
animation = workspaces, 1, 5, ease
|
||||
}
|
||||
|
||||
input {
|
||||
kb_layout = us
|
||||
follow_mouse = 1
|
||||
touchpad {
|
||||
natural_scroll = true
|
||||
}
|
||||
}
|
||||
|
||||
dwindle {
|
||||
pseudotile = true
|
||||
preserve_split = true
|
||||
}
|
||||
|
||||
misc {
|
||||
disable_hyprland_logo = true
|
||||
disable_splash_rendering = true
|
||||
}
|
||||
216
iso/airootfs/etc/skel/.config/hypr/hyprland.lua
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
-- BOS Hyprland configuration — native Lua config (Hyprland 0.55+).
|
||||
-- hyprlang (.conf) is deprecated; this uses the built-in `hl` API.
|
||||
-- Single-file and non-modular by design. Reference: https://wiki.hypr.land/
|
||||
|
||||
local mod = "SUPER"
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Monitors — generic default that works on any hardware.
|
||||
-- ---------------------------------------------------------------------------
|
||||
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "auto" })
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Core settings.
|
||||
-- ---------------------------------------------------------------------------
|
||||
hl.config({
|
||||
general = {
|
||||
gaps_in = 5,
|
||||
gaps_out = 10,
|
||||
border_size = 2,
|
||||
col = {
|
||||
active_border = "rgba(88c0d0ff)",
|
||||
inactive_border = "rgba(4c566aff)",
|
||||
},
|
||||
layout = "dwindle",
|
||||
resize_on_border = true,
|
||||
},
|
||||
decoration = {
|
||||
rounding = 8,
|
||||
active_opacity = 1.0,
|
||||
inactive_opacity = 1.0,
|
||||
blur = {
|
||||
enabled = true,
|
||||
size = 6,
|
||||
passes = 2,
|
||||
new_optimizations = true,
|
||||
},
|
||||
shadow = {
|
||||
enabled = true,
|
||||
range = 12,
|
||||
render_power = 3,
|
||||
},
|
||||
},
|
||||
input = {
|
||||
kb_layout = "us",
|
||||
follow_mouse = 1,
|
||||
touchpad = { natural_scroll = true },
|
||||
},
|
||||
dwindle = {
|
||||
preserve_split = true,
|
||||
},
|
||||
animations = {
|
||||
enabled = true,
|
||||
},
|
||||
misc = {
|
||||
disable_hyprland_logo = true,
|
||||
disable_splash_rendering = true,
|
||||
},
|
||||
})
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Animations — snappy curves + per-leaf speeds (matches the reference laptop;
|
||||
-- the hl.config default above is much slower).
|
||||
-- ---------------------------------------------------------------------------
|
||||
local curves = {
|
||||
easeOutQuint = { type = "bezier", points = { { 0.23, 1 }, { 0.32, 1 } } },
|
||||
easeInOutCubic = { type = "bezier", points = { { 0.65, 0.05 }, { 0.36, 1 } } },
|
||||
almostLinear = { type = "bezier", points = { { 0.5, 0.5 }, { 0.75, 1 } } },
|
||||
quick = { type = "bezier", points = { { 0.15, 0 }, { 0.1, 1 } } },
|
||||
}
|
||||
for name, curve in pairs(curves) do
|
||||
hl.curve(name, curve)
|
||||
end
|
||||
|
||||
local animations = {
|
||||
{ leaf = "global", enabled = true, speed = 10, bezier = "default" },
|
||||
{ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" },
|
||||
{ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" },
|
||||
{ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" },
|
||||
{ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" },
|
||||
{ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" },
|
||||
{ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" },
|
||||
{ leaf = "workspaces", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" },
|
||||
}
|
||||
for _, animation in ipairs(animations) do
|
||||
hl.animation(animation)
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Window rules — float + centre the onboarding popups (kitty --class …).
|
||||
-- ---------------------------------------------------------------------------
|
||||
hl.window_rule({ name = "bos-keybinds", match = { class = "^(bos-keybinds)$" }, float = true, size = { 760, 720 } })
|
||||
hl.window_rule({ name = "bos-welcome", match = { class = "^(bos-welcome)$" }, float = true, size = { 700, 560 } })
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Environment (vendor-neutral; no GPU-specific vars so it works on Intel/AMD).
|
||||
-- ---------------------------------------------------------------------------
|
||||
hl.env("XCURSOR_SIZE", "24")
|
||||
hl.env("HYPRCURSOR_SIZE", "24")
|
||||
hl.env("XCURSOR_THEME", "Bibata-Modern-Ice")
|
||||
hl.env("MOZ_ENABLE_WAYLAND", "1")
|
||||
hl.env("QT_QPA_PLATFORM", "wayland;xcb")
|
||||
hl.env("QT_QPA_PLATFORMTHEME", "qt5ct")
|
||||
hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
|
||||
hl.env("SDL_VIDEODRIVER", "wayland")
|
||||
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto")
|
||||
hl.env("_JAVA_AWT_WM_NONREPARENTING", "1")
|
||||
|
||||
-- kitty sets its own background_opacity (see kitty.conf), so the global blur
|
||||
-- above blurs behind the terminal while keeping text fully opaque.
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Standard BOS keybinds (SUPER = mod).
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Apps / window management
|
||||
hl.bind(mod .. " + RETURN", hl.dsp.exec_cmd("kitty"))
|
||||
hl.bind(mod .. " + BACKSPACE", hl.dsp.window.close())
|
||||
hl.bind(mod .. " + SPACE", hl.dsp.exec_cmd("breadbox"))
|
||||
hl.bind(mod .. " + E", hl.dsp.exec_cmd("nautilus"))
|
||||
hl.bind(mod .. " + B", hl.dsp.exec_cmd("zen-browser"))
|
||||
hl.bind(mod .. " + U", hl.dsp.exec_cmd("breadpad"))
|
||||
hl.bind(mod .. " + M", hl.dsp.exec_cmd("breadman"))
|
||||
hl.bind(mod .. " + comma", hl.dsp.exec_cmd("bos-settings"))
|
||||
hl.bind(mod .. " + slash", hl.dsp.exec_cmd("bos-keybinds"))
|
||||
hl.bind(mod .. " + L", hl.dsp.exec_cmd("loginctl lock-session"))
|
||||
hl.bind(mod .. " + F", hl.dsp.window.fullscreen({ action = "toggle" }))
|
||||
hl.bind(mod .. " + V", hl.dsp.window.float({ action = "toggle" }))
|
||||
hl.bind(mod .. " + SHIFT + V", hl.dsp.exec_cmd([[bash -c 'cliphist list | fzf --reverse --prompt="Clipboard > " | cliphist decode | wl-copy']]))
|
||||
hl.bind(mod .. " + T", hl.dsp.layout("togglesplit"))
|
||||
hl.bind(mod .. " + Tab", hl.dsp.focus({ urgent_or_last = true }))
|
||||
hl.bind(mod .. " + N", hl.dsp.exit())
|
||||
|
||||
-- Screenshots (grim + slurp + wl-clipboard)
|
||||
hl.bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd([[bash -c 'mkdir -p ~/Pictures/Screenshots && grim -g "$(slurp)" ~/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png']]))
|
||||
hl.bind(mod .. " + SHIFT + C", hl.dsp.exec_cmd([[bash -c 'grim -g "$(slurp)" - | wl-copy']]))
|
||||
hl.bind(mod .. " + SHIFT + P", hl.dsp.exec_cmd([[bash -c 'mkdir -p ~/Pictures/Screenshots && grim ~/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png']]))
|
||||
|
||||
-- Focus (directional)
|
||||
hl.bind(mod .. " + left", hl.dsp.focus({ direction = "left" }))
|
||||
hl.bind(mod .. " + right", hl.dsp.focus({ direction = "right" }))
|
||||
hl.bind(mod .. " + up", hl.dsp.focus({ direction = "up" }))
|
||||
hl.bind(mod .. " + down", hl.dsp.focus({ direction = "down" }))
|
||||
|
||||
-- Move window (directional, vim keys)
|
||||
hl.bind(mod .. " + SHIFT + h", hl.dsp.window.move({ direction = "left" }))
|
||||
hl.bind(mod .. " + SHIFT + j", hl.dsp.window.move({ direction = "down" }))
|
||||
hl.bind(mod .. " + SHIFT + k", hl.dsp.window.move({ direction = "up" }))
|
||||
hl.bind(mod .. " + SHIFT + l", hl.dsp.window.move({ direction = "right" }))
|
||||
|
||||
-- Resize active window (arrows)
|
||||
hl.bind(mod .. " + SHIFT + right", hl.dsp.window.resize({ x = 30, y = 0, relative = true }), { repeating = true })
|
||||
hl.bind(mod .. " + SHIFT + left", hl.dsp.window.resize({ x = -30, y = 0, relative = true }), { repeating = true })
|
||||
hl.bind(mod .. " + SHIFT + up", hl.dsp.window.resize({ x = 0, y = -30, relative = true }), { repeating = true })
|
||||
hl.bind(mod .. " + SHIFT + down", hl.dsp.window.resize({ x = 0, y = 30, relative = true }), { repeating = true })
|
||||
|
||||
-- Workspaces 1–10 (0 = workspace 10)
|
||||
for i = 1, 10 do
|
||||
local key = tostring(i % 10)
|
||||
hl.bind(mod .. " + " .. key, hl.dsp.focus({ workspace = i }))
|
||||
hl.bind(mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i }))
|
||||
end
|
||||
|
||||
-- Workspace cycling
|
||||
hl.bind(mod .. " + bracketright", hl.dsp.focus({ workspace = "e+1" }))
|
||||
hl.bind(mod .. " + bracketleft", hl.dsp.focus({ workspace = "e-1" }))
|
||||
hl.bind(mod .. " + SHIFT + bracketright", hl.dsp.window.move({ workspace = "e+1" }))
|
||||
hl.bind(mod .. " + SHIFT + bracketleft", hl.dsp.window.move({ workspace = "e-1" }))
|
||||
|
||||
-- Mouse
|
||||
hl.bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
|
||||
hl.bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
|
||||
hl.bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
|
||||
hl.bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
|
||||
|
||||
-- Media / hardware keys (work locked, i.e. on the lock screen too)
|
||||
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true })
|
||||
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true })
|
||||
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true })
|
||||
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true })
|
||||
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%+"), { locked = true, repeating = true })
|
||||
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%-"), { locked = true, repeating = true })
|
||||
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
|
||||
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
|
||||
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Autostart. polkit agent + the bread ecosystem + idle daemon + wallpaper.
|
||||
-- (bos-live-setup appends the live-installer launch below this on the ISO.)
|
||||
-- ---------------------------------------------------------------------------
|
||||
hl.on("hyprland.start", function()
|
||||
local startup = {
|
||||
-- Generate the shared bread GUI stylesheet first, so breadbar/breadbox/
|
||||
-- bos-settings load it on start (they also live-reload if it changes).
|
||||
"bread-theme generate",
|
||||
-- Global dark theme: GTK4/libadwaita + GTK3 theme + icon + cursor.
|
||||
"gsettings set org.gnome.desktop.interface color-scheme prefer-dark",
|
||||
"gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark",
|
||||
"gsettings set org.gnome.desktop.interface icon-theme Papirus-Dark",
|
||||
"gsettings set org.gnome.desktop.interface cursor-theme Bibata-Modern-Ice",
|
||||
"gsettings set org.gnome.desktop.interface cursor-size 24",
|
||||
-- Clipboard history daemon (feeds SUPER+V history picker via wl-paste).
|
||||
"wl-paste --type text --watch cliphist store",
|
||||
"/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1",
|
||||
"awww-daemon",
|
||||
-- set the default wallpaper once the daemon is up (retry until ready)
|
||||
[[bash -c 'until awww img /usr/share/backgrounds/bos/bread-background.png 2>/dev/null; do sleep 0.3; done']],
|
||||
"breadd",
|
||||
"breadbar",
|
||||
"breadbox-sync",
|
||||
"hypridle",
|
||||
-- first-boot onboarding (self-gates after the first run)
|
||||
"bos-welcome",
|
||||
}
|
||||
for _, cmd in ipairs(startup) do
|
||||
hl.dispatch(hl.dsp.exec_cmd(cmd))
|
||||
end
|
||||
end)
|
||||
28
iso/airootfs/etc/skel/.config/hypr/hyprlock.conf
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Lock screen (hyprlock). Solid dark background (no runtime-generated wallpaper
|
||||
# dependency), accent matched to the Hyprland border colour.
|
||||
general {
|
||||
hide_cursor = true
|
||||
}
|
||||
|
||||
background {
|
||||
monitor =
|
||||
color = rgba(46, 52, 64, 1.0)
|
||||
blur_passes = 0
|
||||
}
|
||||
|
||||
input-field {
|
||||
monitor =
|
||||
size = 20%, 5%
|
||||
outline_thickness = 3
|
||||
inner_color = rgba(0, 0, 0, 0.2)
|
||||
outer_color = rgba(136, 192, 208, 0.8)
|
||||
check_color = rgba(120, 220, 140, 0.95)
|
||||
fail_color = rgba(255, 90, 90, 0.95)
|
||||
font_color = rgba(255, 255, 255, 0.95)
|
||||
fade_on_empty = false
|
||||
rounding = 12
|
||||
placeholder_text = <i>Password…</i>
|
||||
position = 0, -20
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
$mod = SUPER
|
||||
|
||||
# App launchers
|
||||
bind = $mod, Space, exec, breadbox
|
||||
bind = $mod, N, exec, breadpad
|
||||
bind = $mod, M, exec, breadman
|
||||
bind = $mod, S, exec, bos-settings
|
||||
|
||||
# Core
|
||||
bind = $mod, Return, exec, foot
|
||||
bind = $mod, Q, killactive
|
||||
bind = $mod SHIFT, E, exit
|
||||
bind = $mod, F, fullscreen
|
||||
|
||||
# Focus
|
||||
bind = $mod, H, movefocus, l
|
||||
bind = $mod, L, movefocus, r
|
||||
bind = $mod, K, movefocus, u
|
||||
bind = $mod, J, movefocus, d
|
||||
|
||||
# Move windows
|
||||
bind = $mod SHIFT, H, movewindow, l
|
||||
bind = $mod SHIFT, L, movewindow, r
|
||||
bind = $mod SHIFT, K, movewindow, u
|
||||
bind = $mod SHIFT, J, movewindow, d
|
||||
|
||||
# Workspaces
|
||||
bind = $mod, 1, workspace, 1
|
||||
bind = $mod, 2, workspace, 2
|
||||
bind = $mod, 3, workspace, 3
|
||||
bind = $mod, 4, workspace, 4
|
||||
bind = $mod, 5, workspace, 5
|
||||
|
||||
bind = $mod SHIFT, 1, movetoworkspace, 1
|
||||
bind = $mod SHIFT, 2, movetoworkspace, 2
|
||||
bind = $mod SHIFT, 3, movetoworkspace, 3
|
||||
bind = $mod SHIFT, 4, movetoworkspace, 4
|
||||
bind = $mod SHIFT, 5, movetoworkspace, 5
|
||||
|
||||
# Scroll through workspaces
|
||||
bind = $mod, mouse_down, workspace, e+1
|
||||
bind = $mod, mouse_up, workspace, e-1
|
||||
|
||||
# Mouse binds
|
||||
bindm = $mod, mouse:272, movewindow
|
||||
bindm = $mod, mouse:273, resizewindow
|
||||
|
||||
# Volume
|
||||
bind = , XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
|
||||
bind = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||
bind = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||
|
||||
# Brightness
|
||||
bind = , XF86MonBrightnessUp, exec, brightnessctl set 5%+
|
||||
bind = , XF86MonBrightnessDown, exec, brightnessctl set 5%-
|
||||
|
||||
# Screenshot
|
||||
bind = , Print, exec, grimblast copy area
|
||||
14
iso/airootfs/etc/skel/.config/kitty/kitty.conf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# BOS kitty config.
|
||||
# Translucent background so Hyprland's blur shows through behind the terminal,
|
||||
# while text stays fully opaque. Colours are left to kitty's default / pywal.
|
||||
# 0.6 matches the reference laptop; the actual blur is supplied by Hyprland's
|
||||
# decoration:blur (kitty's own background_blur is macOS-only).
|
||||
background_opacity 0.6
|
||||
|
||||
font_family JetBrains Mono
|
||||
font_size 11.0
|
||||
cursor_shape beam
|
||||
scrollback_lines 10000
|
||||
enable_audio_bell no
|
||||
window_padding_width 8
|
||||
confirm_os_window_close 0
|
||||
51
iso/airootfs/etc/skel/.config/mimeapps.list
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Default applications for common file types. Without this, freshly installed
|
||||
# BOS has no handler registered for images/video/text/etc., so opening a file
|
||||
# from nautilus does nothing. Maps to the apps shipped in packages.x86_64.
|
||||
[Default Applications]
|
||||
# Images -> Loupe
|
||||
image/png=org.gnome.Loupe.desktop
|
||||
image/jpeg=org.gnome.Loupe.desktop
|
||||
image/gif=org.gnome.Loupe.desktop
|
||||
image/webp=org.gnome.Loupe.desktop
|
||||
image/bmp=org.gnome.Loupe.desktop
|
||||
image/tiff=org.gnome.Loupe.desktop
|
||||
image/svg+xml=org.gnome.Loupe.desktop
|
||||
|
||||
# Audio/Video -> VLC
|
||||
audio/mpeg=vlc.desktop
|
||||
audio/flac=vlc.desktop
|
||||
audio/ogg=vlc.desktop
|
||||
audio/x-wav=vlc.desktop
|
||||
audio/aac=vlc.desktop
|
||||
video/mp4=vlc.desktop
|
||||
video/x-matroska=vlc.desktop
|
||||
video/webm=vlc.desktop
|
||||
video/quicktime=vlc.desktop
|
||||
video/x-msvideo=vlc.desktop
|
||||
|
||||
# Plain text / source -> GNOME Text Editor
|
||||
text/plain=org.gnome.TextEditor.desktop
|
||||
text/markdown=org.gnome.TextEditor.desktop
|
||||
application/x-shellscript=org.gnome.TextEditor.desktop
|
||||
application/json=org.gnome.TextEditor.desktop
|
||||
application/toml=org.gnome.TextEditor.desktop
|
||||
text/x-readme=org.gnome.TextEditor.desktop
|
||||
|
||||
# Documents / web -> Zen (PDF + HTML)
|
||||
application/pdf=zen.desktop
|
||||
text/html=zen.desktop
|
||||
x-scheme-handler/http=zen.desktop
|
||||
x-scheme-handler/https=zen.desktop
|
||||
|
||||
# Archives -> File Roller
|
||||
application/zip=org.gnome.FileRoller.desktop
|
||||
application/x-tar=org.gnome.FileRoller.desktop
|
||||
application/gzip=org.gnome.FileRoller.desktop
|
||||
application/x-7z-compressed=org.gnome.FileRoller.desktop
|
||||
application/x-rar=org.gnome.FileRoller.desktop
|
||||
application/vnd.rar=org.gnome.FileRoller.desktop
|
||||
application/x-xz=org.gnome.FileRoller.desktop
|
||||
application/x-bzip2=org.gnome.FileRoller.desktop
|
||||
|
||||
# Directories -> Nautilus
|
||||
inode/directory=org.gnome.Nautilus.desktop
|
||||
25
iso/airootfs/etc/skel/.config/qt5ct/qt5ct.conf
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[Appearance]
|
||||
style=Fusion
|
||||
color_scheme_path=/usr/share/qt5ct/colors/darker.conf
|
||||
custom_palette=true
|
||||
standard_dialogs=default
|
||||
icon_theme=Papirus-Dark
|
||||
|
||||
[Fonts]
|
||||
fixed=@Variant(\0\0\0@\0\0\0\x12JetBrains Mono\0\0\0\0\0\0\0\0\0\0\0\0\0\xa0\0\x64\xff\xff\xff\xff)
|
||||
general=@Variant(\0\0\0@\0\0\0\x16Noto Sans\0\0\0\0\0\0\0\0\0\0\0\0\x11\0\x64\xff\xff\xff\xff)
|
||||
|
||||
[Interface]
|
||||
activate_item_on_single_click=1
|
||||
buttonbox_layout=0
|
||||
cursor_flash_time=1000
|
||||
dialog_buttons_have_icons=1
|
||||
double_click_interval=400
|
||||
gui_effects=@Invalid()
|
||||
keyboard_scheme=2
|
||||
menus_have_icons=true
|
||||
show_shortcuts_in_context_menus=true
|
||||
stylesheets=@Invalid()
|
||||
toolbutton_style=4
|
||||
underline_shortcut=1
|
||||
wheel_scroll_lines=3
|
||||
25
iso/airootfs/etc/skel/.config/qt6ct/qt6ct.conf
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[Appearance]
|
||||
style=Fusion
|
||||
color_scheme_path=/usr/share/qt6ct/colors/darker.conf
|
||||
custom_palette=true
|
||||
standard_dialogs=default
|
||||
icon_theme=Papirus-Dark
|
||||
|
||||
[Fonts]
|
||||
fixed=@Variant(\0\0\0@\0\0\0\x12JetBrains Mono\0\0\0\0\0\0\0\0\0\0\0\0\0\xa0\0\x64\xff\xff\xff\xff)
|
||||
general=@Variant(\0\0\0@\0\0\0\x16Noto Sans\0\0\0\0\0\0\0\0\0\0\0\0\x11\0\x64\xff\xff\xff\xff)
|
||||
|
||||
[Interface]
|
||||
activate_item_on_single_click=1
|
||||
buttonbox_layout=0
|
||||
cursor_flash_time=1000
|
||||
dialog_buttons_have_icons=1
|
||||
double_click_interval=400
|
||||
gui_effects=@Invalid()
|
||||
keyboard_scheme=2
|
||||
menus_have_icons=true
|
||||
show_shortcuts_in_context_menus=true
|
||||
stylesheets=@Invalid()
|
||||
toolbutton_style=4
|
||||
underline_shortcut=1
|
||||
wheel_scroll_lines=3
|
||||
65
iso/airootfs/etc/skel/.zshrc
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# BOS default zsh config — quality-of-life defaults, easy to extend.
|
||||
|
||||
# History
|
||||
HISTFILE=~/.zsh_history
|
||||
HISTSIZE=10000
|
||||
SAVEHIST=10000
|
||||
setopt HIST_IGNORE_DUPS HIST_IGNORE_SPACE SHARE_HISTORY
|
||||
|
||||
# Completion
|
||||
autoload -Uz compinit && compinit
|
||||
zstyle ':completion:*' menu select
|
||||
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
|
||||
|
||||
# Key bindings (emacs style + common extras)
|
||||
bindkey -e
|
||||
bindkey '^[[A' history-search-backward
|
||||
bindkey '^[[B' history-search-forward
|
||||
|
||||
# fzf — fuzzy history search on Ctrl+R, fuzzy file find on Ctrl+T
|
||||
if command -v fzf &>/dev/null; then
|
||||
source /usr/share/fzf/key-bindings.zsh 2>/dev/null || true
|
||||
source /usr/share/fzf/completion.zsh 2>/dev/null || true
|
||||
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
||||
fi
|
||||
|
||||
# zoxide — smarter cd (type z instead of cd)
|
||||
if command -v zoxide &>/dev/null; then
|
||||
eval "$(zoxide init zsh)"
|
||||
fi
|
||||
|
||||
# Modern replacements with fallbacks
|
||||
if command -v eza &>/dev/null; then
|
||||
alias ls='eza --icons --group-directories-first'
|
||||
alias ll='eza -la --icons --group-directories-first --git'
|
||||
alias lt='eza --tree --icons --level=2'
|
||||
else
|
||||
alias ls='ls --color=auto'
|
||||
alias ll='ls -la'
|
||||
fi
|
||||
|
||||
if command -v bat &>/dev/null; then
|
||||
alias cat='bat --style=plain --paging=never'
|
||||
fi
|
||||
|
||||
# General aliases
|
||||
alias clr='clear'
|
||||
alias ..='cd ..'
|
||||
alias ...='cd ../..'
|
||||
alias mkdir='mkdir -p'
|
||||
alias cp='cp -i'
|
||||
alias mv='mv -i'
|
||||
alias df='df -h'
|
||||
alias free='free -h'
|
||||
alias grep='grep --color=auto'
|
||||
alias ip='ip --color=auto'
|
||||
|
||||
# bakery / bread
|
||||
alias update='bakery update'
|
||||
|
||||
# Prompt — simple and fast (no starship dep)
|
||||
autoload -Uz vcs_info
|
||||
precmd() { vcs_info }
|
||||
zstyle ':vcs_info:git:*' formats ' (%b)'
|
||||
setopt PROMPT_SUBST
|
||||
PROMPT='%F{cyan}%~%f%F{yellow}${vcs_info_msg_0_}%f %(?.%F{green}.%F{red})❯%f '
|
||||
3
iso/airootfs/etc/sudoers.d/99-bos-live
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Live medium only: the unprivileged live user may escalate without a password
|
||||
# so the installer (Calamares) can run as root from the Wayland session.
|
||||
liveuser ALL=(ALL) NOPASSWD: ALL
|
||||
7
iso/airootfs/etc/systemd/logind.conf.d/90-bos-power.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Lid behaviour: suspend on close (on battery or AC), but ignore the lid when
|
||||
# docked / an external display is connected so closing the laptop with a monitor
|
||||
# attached keeps the session running. Mainstream-desktop default.
|
||||
[Login]
|
||||
HandleLidSwitch=suspend
|
||||
HandleLidSwitchExternalPower=suspend
|
||||
HandleLidSwitchDocked=ignore
|
||||
14
iso/airootfs/etc/systemd/system/bos-live-setup.service
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Set up the BOS live user and session
|
||||
# Only on the live medium — the installed system has no archisobasedir cmdline.
|
||||
ConditionKernelCommandLine=archisobasedir
|
||||
Before=getty@tty1.service
|
||||
After=systemd-tmpfiles-setup.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/local/bin/bos-live-setup
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin root - $TERM
|
||||
ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin liveuser - $TERM
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
/usr/lib/systemd/system/NetworkManager.service
|
||||
|
|
@ -0,0 +1 @@
|
|||
../bos-live-setup.service
|
||||
6
iso/airootfs/etc/systemd/zram-generator.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Compressed RAM swap. systemd-zram-generator reads this and creates a zram
|
||||
# device + swap at boot — no on-disk swap partition needed. Sized at half RAM
|
||||
# capped to 4 GiB, zstd-compressed (typically ~3:1, so cheap headroom).
|
||||
[zram0]
|
||||
zram-size = min(ram / 2, 4096)
|
||||
compression-algorithm = zstd
|
||||
1
iso/airootfs/etc/vconsole.conf
Normal file
|
|
@ -0,0 +1 @@
|
|||
KEYMAP=us
|
||||
|
|
@ -1,4 +1,19 @@
|
|||
# Auto-start Hyprland on tty1 in the live session
|
||||
if [[ "$(tty)" == "/dev/tty1" ]] && [[ -z "$WAYLAND_DISPLAY" ]]; then
|
||||
exec Hyprland
|
||||
# Allow a software-rendering fallback so the live session comes up even
|
||||
# without a GPU (VMs, headless, exotic hardware). On real hardware wlroots
|
||||
# still selects the hardware renderer; this only permits llvmpipe when no
|
||||
# GPU renderer is available. Must be exported before Hyprland starts —
|
||||
# wlroots reads it at renderer init, earlier than any Hyprland `env=` line.
|
||||
export WLR_RENDERER_ALLOW_SOFTWARE=1
|
||||
# Software cursors: hardware-cursor planes are often unusable in VMs and
|
||||
# show as invisible/garbled; this is the reliable choice for a live medium.
|
||||
export WLR_NO_HARDWARE_CURSORS=1
|
||||
# Run the compositor, capturing its output so a failed live boot is
|
||||
# diagnosable (Hyprland also keeps its own log under $XDG_RUNTIME_DIR/hypr/).
|
||||
# On exit, drop to an interactive shell with the error in view instead of
|
||||
# letting the getty autologin respawn-loop hide it behind a blank cursor.
|
||||
Hyprland &>/var/log/hyprland-live.log
|
||||
echo "Hyprland exited (rc=$?). Log: /var/log/hyprland-live.log"
|
||||
exec bash -i
|
||||
fi
|
||||
|
|
|
|||
46
iso/airootfs/usr/local/bin/bos-copy-kernel
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
# Copy the live kernel into the freshly-unpacked target /boot.
|
||||
#
|
||||
# archiso keeps vmlinuz/initramfs in the ISO boot dir (arch/boot/x86_64/), NOT
|
||||
# in the squashfs, so the rootfs that unpackfs lays down has an empty /boot.
|
||||
# The kernel must be present before Calamares' `initcpio` module runs mkinitcpio
|
||||
# (the stock linux.preset points ALL_kver at /boot/vmlinuz-linux) and before the
|
||||
# `bootloader` module runs grub — otherwise the installed system is unbootable.
|
||||
#
|
||||
# Runs in the LIVE environment (Calamares shellprocess, dontChroot) so it can
|
||||
# read /run/archiso/bootmnt; the target root mount point is passed as $1.
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="${1:?target root required}"
|
||||
SRC="/run/archiso/bootmnt/arch/boot/x86_64"
|
||||
|
||||
install -d -m 0755 "$ROOT/boot"
|
||||
cp -f "$SRC/vmlinuz-linux" "$ROOT/boot/vmlinuz-linux"
|
||||
|
||||
# Microcode, if the live medium carries it (grub-mkconfig picks it up).
|
||||
for u in amd-ucode.img intel-ucode.img; do
|
||||
[ -f "$SRC/$u" ] && cp -f "$SRC/$u" "$ROOT/boot/$u"
|
||||
done
|
||||
|
||||
# Replace the archiso initramfs setup that unpackfs copied from the live medium.
|
||||
# On archiso the linux preset is PRESETS=('archiso') using archiso.conf (the live
|
||||
# HOOKS). Calamares' `initcpio` runs `mkinitcpio -P`, which would build that
|
||||
# archiso preset and either bake the live-boot hooks into the install or fail
|
||||
# once archiso.conf is gone. Drop the drop-in and write a stock default+fallback
|
||||
# preset so `initcpio` produces a normal, bootable initramfs from the config that
|
||||
# the `initcpiocfg` module generates at /etc/mkinitcpio.conf.
|
||||
rm -f "$ROOT/etc/mkinitcpio.conf.d/archiso.conf"
|
||||
install -d -m 0755 "$ROOT/etc/mkinitcpio.d"
|
||||
cat >"$ROOT/etc/mkinitcpio.d/linux.preset" <<'PRESET'
|
||||
# mkinitcpio preset file for the 'linux' package
|
||||
ALL_config="/etc/mkinitcpio.conf"
|
||||
ALL_kver="/boot/vmlinuz-linux"
|
||||
|
||||
PRESETS=('default' 'fallback')
|
||||
|
||||
default_image="/boot/initramfs-linux.img"
|
||||
fallback_image="/boot/initramfs-linux-fallback.img"
|
||||
fallback_options="-S autodetect"
|
||||
PRESET
|
||||
|
||||
echo "Copied live kernel into $ROOT/boot; reset mkinitcpio to a stock preset"
|
||||
4
iso/airootfs/usr/local/bin/bos-keybinds
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
# Show the BOS keybind cheatsheet in a floating terminal (bound to SUPER+/).
|
||||
# The bos-keybinds window class is floated/centred by a Hyprland window rule.
|
||||
exec kitty --class bos-keybinds --title "BOS Keybinds" -- less -R /usr/share/bos/keybinds.txt
|
||||
7
iso/airootfs/usr/local/bin/bos-launch-calamares
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
# Launch Calamares as root on the live user's Wayland session.
|
||||
# Calamares performs partitioning/bootloader work and needs root; the live user
|
||||
# has passwordless sudo (see /etc/sudoers.d/99-bos-live). We preserve the Wayland
|
||||
# environment so the root process renders on the user's compositor.
|
||||
export QT_QPA_PLATFORM=wayland
|
||||
exec sudo --preserve-env=WAYLAND_DISPLAY,XDG_RUNTIME_DIR,QT_QPA_PLATFORM calamares
|
||||
49
iso/airootfs/usr/local/bin/bos-live-setup
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/bash
|
||||
# Create the unprivileged BOS live user and its Hyprland session.
|
||||
#
|
||||
# Hyprland refuses to run as root (superuser-privileges check), so the live
|
||||
# session must run as a normal user. Calamares — which does need root — is
|
||||
# launched onto the user's Wayland socket via passwordless sudo (see
|
||||
# bos-launch-calamares). Runs once at boot, before the tty1 autologin getty.
|
||||
set -e
|
||||
|
||||
# useradd -m copies /etc/skel, so the live user gets the real BOS desktop
|
||||
# (breadd + breadbar + breadbox + keybinds) — proper live-media functionality,
|
||||
# not an installer kiosk.
|
||||
if ! id liveuser &>/dev/null; then
|
||||
useradd -m -s /bin/bash liveuser
|
||||
for g in wheel video input audio storage power; do
|
||||
getent group "$g" >/dev/null 2>&1 && gpasswd -a liveuser "$g" >/dev/null || true
|
||||
done
|
||||
passwd -d liveuser >/dev/null
|
||||
fi
|
||||
|
||||
# Layer the installer onto the live desktop: auto-launch it, and bind Super+I to
|
||||
# relaunch it after it's been closed. Appended (in Lua) to the skel hyprland.lua
|
||||
# native config so the full desktop stays intact.
|
||||
HYPR=/home/liveuser/.config/hypr/hyprland.lua
|
||||
install -d -m 0755 -o liveuser -g liveuser /home/liveuser/.config/hypr
|
||||
if ! grep -q bos-launch-calamares "$HYPR" 2>/dev/null; then
|
||||
cat >>"$HYPR" <<'EOF'
|
||||
|
||||
-- --- live-media installer (added by bos-live-setup; absent on installed system) ---
|
||||
hl.bind("SUPER + I", hl.dsp.exec_cmd("bos-launch-calamares"))
|
||||
hl.on("hyprland.start", function() hl.dispatch(hl.dsp.exec_cmd("bos-launch-calamares")) end)
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Start Hyprland on tty1 login; capture output and fall back to a shell so a
|
||||
# failed compositor start is visible rather than a blank looping cursor.
|
||||
cat >/home/liveuser/.bash_profile <<'EOF'
|
||||
if [[ "$(tty)" == /dev/tty1 ]] && [[ -z "$WAYLAND_DISPLAY" ]]; then
|
||||
export WLR_RENDERER_ALLOW_SOFTWARE=1
|
||||
export WLR_NO_HARDWARE_CURSORS=1
|
||||
# Log to a user-writable path (/var/log is root-only; redirecting there
|
||||
# would fail and silently keep the compositor from ever launching).
|
||||
Hyprland &>/tmp/hyprland-live.log
|
||||
echo "Hyprland exited (rc=$?). Log: /tmp/hyprland-live.log"
|
||||
exec bash -i
|
||||
fi
|
||||
EOF
|
||||
|
||||
chown -R liveuser:liveuser /home/liveuser
|
||||
15
iso/airootfs/usr/local/bin/bos-session
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
# BOS graphical session launcher, run by greetd on the INSTALLED system after
|
||||
# the user authenticates (see /etc/greetd/config.toml).
|
||||
#
|
||||
# greetd does not start a login shell, so /etc/profile.d is never sourced — which
|
||||
# means ~/.local/bin (where bakery installs the bread ecosystem: breadd, breadbar,
|
||||
# breadbox-sync, …) would be missing from PATH and the Hyprland `exec-once`
|
||||
# launches would fail. Source the login profile here so PATH is correct, set the
|
||||
# Wayland session hints, then hand off to Hyprland.
|
||||
source /etc/profile 2>/dev/null
|
||||
|
||||
export XDG_SESSION_TYPE=wayland
|
||||
export XDG_CURRENT_DESKTOP=Hyprland
|
||||
|
||||
exec Hyprland
|
||||
15
iso/airootfs/usr/local/bin/bos-welcome
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
# First-run welcome. Shows a short getting-started message once, then drops a
|
||||
# marker so it never shows again. Launched from the Hyprland autostart; the
|
||||
# bos-welcome window class is floated/centred by a Hyprland window rule.
|
||||
set -u
|
||||
|
||||
# Never run in the live/installer session — only on an installed system.
|
||||
[[ "$(id -un)" == "liveuser" ]] && exit 0
|
||||
|
||||
marker="${XDG_CONFIG_HOME:-$HOME/.config}/bos/.welcomed"
|
||||
[[ -f "$marker" ]] && exit 0
|
||||
mkdir -p "$(dirname "$marker")"
|
||||
touch "$marker"
|
||||
|
||||
exec kitty --class bos-welcome --title "Welcome to BOS" -- less -R /usr/share/bos/welcome.txt
|
||||
BIN
iso/airootfs/usr/share/backgrounds/bos/bread-background.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
50
iso/airootfs/usr/share/bos/keybinds.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
██████ ██████ ███████ keyboard shortcuts
|
||||
██ ██ ██ ██ ██ SUPER is the Windows/Cmd key
|
||||
██████ ██ ██ ███████
|
||||
══════════════════════════════════════════════════════════
|
||||
|
||||
APPS & WINDOWS
|
||||
SUPER + Return terminal (kitty)
|
||||
SUPER + Space app launcher (breadbox)
|
||||
SUPER + E files (nautilus)
|
||||
SUPER + B browser (zen)
|
||||
SUPER + U notes / reminders (breadpad)
|
||||
SUPER + M package manager (breadman)
|
||||
SUPER + , BOS Settings
|
||||
SUPER + / this keybind cheatsheet
|
||||
SUPER + L lock screen
|
||||
SUPER + Backspace close window
|
||||
SUPER + F fullscreen
|
||||
SUPER + V toggle floating
|
||||
SUPER + Shift + V clipboard history
|
||||
SUPER + T toggle split direction
|
||||
SUPER + Tab last window
|
||||
SUPER + N exit Hyprland (log out)
|
||||
|
||||
SCREENSHOTS
|
||||
SUPER + Shift + S select region -> file
|
||||
SUPER + Shift + C select region -> clipboard
|
||||
SUPER + Shift + P whole screen -> file
|
||||
|
||||
FOCUS & MOVE
|
||||
SUPER + arrows move focus
|
||||
SUPER + Shift + h/j/k/l move window
|
||||
SUPER + Shift + arrows resize window
|
||||
|
||||
WORKSPACES
|
||||
SUPER + 1..0 switch to workspace 1..10
|
||||
SUPER + Shift + 1..0 move window to workspace
|
||||
SUPER + [ / ] previous / next workspace
|
||||
SUPER + Shift + [ / ] move window prev / next workspace
|
||||
SUPER + scroll cycle workspaces
|
||||
|
||||
MOUSE
|
||||
SUPER + left-drag move window
|
||||
SUPER + right-drag resize window
|
||||
|
||||
MEDIA & HARDWARE KEYS
|
||||
volume / brightness / play-pause / next / prev (work on lock screen)
|
||||
|
||||
──────────────────────────────────────────────────────────
|
||||
Press q to close. Configure everything in BOS Settings (SUPER + ,).
|
||||
24
iso/airootfs/usr/share/bos/welcome.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
Welcome to BOS — the Bread Operating System
|
||||
══════════════════════════════════════════════════════════
|
||||
|
||||
You're running a complete Hyprland desktop with the bread
|
||||
ecosystem preinstalled. A few things to get you started:
|
||||
|
||||
• SUPER + / show the keybind cheatsheet (any time)
|
||||
• SUPER + , open BOS Settings — configure bread, the
|
||||
bar, launcher, Wi-Fi profiles, notes,
|
||||
snapshots and package updates, all in one
|
||||
place (no config files needed)
|
||||
• SUPER + Space the app launcher (breadbox)
|
||||
• SUPER + Return a terminal
|
||||
|
||||
The bar at the top (breadbar) shows workspaces, the clock,
|
||||
system stats, and your tray. Notifications appear top-right.
|
||||
|
||||
Your system is snapshotted on every package change — if an
|
||||
update breaks something, roll back from BOS Settings or pick
|
||||
a snapshot from the GRUB menu at boot.
|
||||
|
||||
──────────────────────────────────────────────────────────
|
||||
Press q to close. This message won't show again.
|
||||
BIN
iso/airootfs/usr/share/fonts/TTF/VarelaRound-Regular.ttf
Normal file
8
iso/airootfs/usr/share/plymouth/themes/bos/bos.plymouth
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[Plymouth Theme]
|
||||
Name=BOS
|
||||
Description=Bread Operating System boot splash — logo, spinner, status
|
||||
ModuleName=script
|
||||
|
||||
[script]
|
||||
ImageDir=/usr/share/plymouth/themes/bos
|
||||
ScriptFile=/usr/share/plymouth/themes/bos/bos.script
|
||||
49
iso/airootfs/usr/share/plymouth/themes/bos/bos.script
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# BOS Plymouth boot splash (script module).
|
||||
# Black background, centred white BOS logo, a spinning accent ring, and a
|
||||
# status line at the bottom. Colours match the bread palette (black base + warm accent).
|
||||
|
||||
# --- background (#0c0c0c) ---
|
||||
Window.SetBackgroundTopColor(0.047, 0.047, 0.047);
|
||||
Window.SetBackgroundBottomColor(0.047, 0.047, 0.047);
|
||||
|
||||
screen_w = Window.GetWidth();
|
||||
screen_h = Window.GetHeight();
|
||||
|
||||
# --- logo (centred, slightly above the middle) ---
|
||||
logo.image = Image("logo.png");
|
||||
logo.sprite = Sprite(logo.image);
|
||||
logo.x = screen_w / 2 - logo.image.GetWidth() / 2;
|
||||
logo.y = screen_h / 2 - logo.image.GetHeight() / 2 - 40;
|
||||
logo.sprite.SetX(logo.x);
|
||||
logo.sprite.SetY(logo.y);
|
||||
logo.sprite.SetZ(1);
|
||||
|
||||
# --- spinner (rotating accent ring, below the logo) ---
|
||||
spinner.image = Image("spinner.png");
|
||||
spinner.sprite = Sprite();
|
||||
spinner.cx = screen_w / 2;
|
||||
spinner.cy = logo.y + logo.image.GetHeight() + 60;
|
||||
spinner.angle = 0;
|
||||
|
||||
fun refresh_callback() {
|
||||
spinner.angle += 0.10;
|
||||
if (spinner.angle > 6.28318) spinner.angle -= 6.28318;
|
||||
rotated = spinner.image.Rotate(spinner.angle);
|
||||
spinner.sprite.SetImage(rotated);
|
||||
spinner.sprite.SetX(spinner.cx - rotated.GetWidth() / 2);
|
||||
spinner.sprite.SetY(spinner.cy - rotated.GetHeight() / 2);
|
||||
spinner.sprite.SetZ(2);
|
||||
}
|
||||
Plymouth.SetRefreshFunction(refresh_callback);
|
||||
|
||||
# --- status line (cream text, near the bottom) ---
|
||||
status.sprite = Sprite();
|
||||
fun show_status(text) {
|
||||
status.image = Image.Text(text, 0.945, 0.863, 0.741);
|
||||
status.sprite.SetImage(status.image);
|
||||
status.sprite.SetX(screen_w / 2 - status.image.GetWidth() / 2);
|
||||
status.sprite.SetY(screen_h * 0.84);
|
||||
status.sprite.SetZ(2);
|
||||
}
|
||||
Plymouth.SetMessageFunction(show_status);
|
||||
Plymouth.SetUpdateStatusFunction(show_status);
|
||||
BIN
iso/airootfs/usr/share/plymouth/themes/bos/logo.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
iso/airootfs/usr/share/plymouth/themes/bos/spinner.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
5
iso/efiboot/loader/entries/01-archiso-linux.conf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
title Bread OS install medium (%ARCH%, UEFI)
|
||||
sort-key 01
|
||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID%
|
||||
5
iso/efiboot/loader/entries/02-archiso-speech-linux.conf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
title Bread OS install medium (%ARCH%, UEFI) with speech
|
||||
sort-key 02
|
||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on
|
||||
4
iso/efiboot/loader/entries/03-archiso-memtest86+x64.conf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
title Memtest86+
|
||||
sort-key 03
|
||||
efi /boot/memtest86+/memtest.efi
|
||||
architecture x64
|
||||
3
iso/efiboot/loader/loader.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
timeout 15
|
||||
default 01-archiso-linux.conf
|
||||
beep on
|
||||
112
iso/grub/grub.cfg
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Load partition table and file system modules
|
||||
insmod part_gpt
|
||||
insmod part_msdos
|
||||
insmod fat
|
||||
insmod iso9660
|
||||
insmod ntfs
|
||||
insmod ntfscomp
|
||||
insmod exfat
|
||||
insmod udf
|
||||
|
||||
# Use graphics-mode output
|
||||
if loadfont "${prefix}/fonts/unicode.pf2" ; then
|
||||
insmod all_video
|
||||
set gfxmode="auto"
|
||||
terminal_input console
|
||||
terminal_output console
|
||||
fi
|
||||
|
||||
# Enable serial console
|
||||
insmod serial
|
||||
insmod usbserial_common
|
||||
insmod usbserial_ftdi
|
||||
insmod usbserial_pl2303
|
||||
insmod usbserial_usbdebug
|
||||
if serial --unit=0 --speed=115200; then
|
||||
terminal_input --append serial
|
||||
terminal_output --append serial
|
||||
fi
|
||||
|
||||
# Get a human readable platform identifier
|
||||
if [ "${grub_platform}" == 'efi' ]; then
|
||||
archiso_platform='UEFI'
|
||||
elif [ "${grub_platform}" == 'pc' ]; then
|
||||
archiso_platform='BIOS'
|
||||
else
|
||||
archiso_platform="${grub_cpu}-${grub_platform}"
|
||||
fi
|
||||
|
||||
# Set default menu entry
|
||||
default=archlinux
|
||||
timeout=15
|
||||
timeout_style=menu
|
||||
|
||||
|
||||
# Menu entries
|
||||
|
||||
menuentry "Bread OS install medium (%ARCH%, ${archiso_platform})" --class arch --class gnu-linux --class gnu --class os --id 'archlinux' {
|
||||
set gfxpayload=keep
|
||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID%
|
||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
}
|
||||
|
||||
menuentry "Bread OS install medium with speakup screen reader (%ARCH%, ${archiso_platform})" --hotkey s --class arch --class gnu-linux --class gnu --class os --id 'archlinux-accessibility' {
|
||||
set gfxpayload=keep
|
||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on
|
||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
}
|
||||
|
||||
|
||||
if [ "${grub_platform}" == 'efi' -a "${grub_cpu}" == 'x86_64' -a -f '/boot/memtest86+/memtest.efi' ]; then
|
||||
menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool {
|
||||
set gfxpayload=800x600,1024x768
|
||||
linux /boot/memtest86+/memtest.efi
|
||||
}
|
||||
fi
|
||||
if [ "${grub_platform}" == 'pc' -a -f '/boot/memtest86+/memtest' ]; then
|
||||
menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool {
|
||||
set gfxpayload=800x600,1024x768
|
||||
linux /boot/memtest86+/memtest
|
||||
}
|
||||
fi
|
||||
if [ "${grub_platform}" == 'efi' ]; then
|
||||
if [ "${grub_cpu}" == 'x86_64' -a -f '/shellx64.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellx64.efi
|
||||
}
|
||||
elif [ "${grub_cpu}" == 'i386' -a -f '/shellia32.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellia32.efi
|
||||
}
|
||||
elif [ "${grub_cpu}" == 'arm64' -a -f '/shellaa64.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellaa64.efi
|
||||
}
|
||||
elif [ "${grub_cpu}" == 'riscv64' -a -f '/shellriscv64.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellriscv64.efi
|
||||
}
|
||||
elif [ "${grub_cpu}" == 'loongarch64' -a -f '/shellloongarch64.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellloongarch64.efi
|
||||
}
|
||||
fi
|
||||
|
||||
menuentry 'UEFI Firmware Settings' --id 'uefi-firmware' {
|
||||
fwsetup
|
||||
}
|
||||
fi
|
||||
|
||||
menuentry 'System shutdown' --class shutdown --class poweroff {
|
||||
echo 'System shutting down...'
|
||||
halt
|
||||
}
|
||||
|
||||
menuentry 'System restart' --class reboot --class restart {
|
||||
echo 'System rebooting...'
|
||||
reboot
|
||||
}
|
||||
|
||||
|
||||
# GRUB init tune for accessibility
|
||||
play 600 988 1 1319 4
|
||||
85
iso/grub/loopback.cfg
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# https://www.supergrubdisk.org/wiki/Loopback.cfg
|
||||
|
||||
# Search for the ISO volume
|
||||
search --no-floppy --set=archiso_img_dev --file "${iso_path}"
|
||||
probe --set archiso_img_dev_uuid --fs-uuid "${archiso_img_dev}"
|
||||
|
||||
# Get a human readable platform identifier
|
||||
if [ "${grub_platform}" == 'efi' ]; then
|
||||
archiso_platform='UEFI'
|
||||
elif [ "${grub_platform}" == 'pc' ]; then
|
||||
archiso_platform='BIOS'
|
||||
else
|
||||
archiso_platform="${grub_cpu}-${grub_platform}"
|
||||
fi
|
||||
|
||||
# Set default menu entry
|
||||
default=archlinux
|
||||
timeout=15
|
||||
timeout_style=menu
|
||||
|
||||
|
||||
# Menu entries
|
||||
|
||||
menuentry "Bread OS install medium (%ARCH%, ${archiso_platform})" --class arch --class gnu-linux --class gnu --class os --id 'archlinux' {
|
||||
set gfxpayload=keep
|
||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}"
|
||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
}
|
||||
|
||||
menuentry "Bread OS install medium with speakup screen reader (%ARCH%, ${archiso_platform})" --hotkey s --class arch --class gnu-linux --class gnu --class os --id 'archlinux-accessibility' {
|
||||
set gfxpayload=keep
|
||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" accessibility=on
|
||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
}
|
||||
|
||||
|
||||
if [ "${grub_platform}" == 'efi' -a "${grub_cpu}" == 'x86_64' -a -f '/boot/memtest86+/memtest.efi' ]; then
|
||||
menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool {
|
||||
set gfxpayload=800x600,1024x768
|
||||
linux /boot/memtest86+/memtest.efi
|
||||
}
|
||||
fi
|
||||
if [ "${grub_platform}" == 'pc' -a -f '/boot/memtest86+/memtest' ]; then
|
||||
menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool {
|
||||
set gfxpayload=800x600,1024x768
|
||||
linux /boot/memtest86+/memtest
|
||||
}
|
||||
fi
|
||||
if [ "${grub_platform}" == 'efi' ]; then
|
||||
if [ "${grub_cpu}" == 'x86_64' -a -f '/shellx64.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellx64.efi
|
||||
}
|
||||
elif [ "${grub_cpu}" == 'i386' -a -f '/shellia32.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellia32.efi
|
||||
}
|
||||
elif [ "${grub_cpu}" == 'arm64' -a -f '/shellaa64.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellaa64.efi
|
||||
}
|
||||
elif [ "${grub_cpu}" == 'riscv64' -a -f '/shellriscv64.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellriscv64.efi
|
||||
}
|
||||
elif [ "${grub_cpu}" == 'loongarch64' -a -f '/shellloongarch64.efi' ]; then
|
||||
menuentry 'UEFI Shell' --class efi {
|
||||
chainloader /shellloongarch64.efi
|
||||
}
|
||||
fi
|
||||
|
||||
menuentry 'UEFI Firmware Settings' --id 'uefi-firmware' {
|
||||
fwsetup
|
||||
}
|
||||
fi
|
||||
|
||||
menuentry 'System shutdown' --class shutdown --class poweroff {
|
||||
echo 'System shutting down...'
|
||||
halt
|
||||
}
|
||||
|
||||
menuentry 'System restart' --class reboot --class restart {
|
||||
echo 'System rebooting...'
|
||||
reboot
|
||||
}
|
||||
|
|
@ -4,6 +4,21 @@ base-devel
|
|||
linux
|
||||
linux-firmware
|
||||
linux-headers
|
||||
# CPU microcode — applied early by GRUB on the installed system (picked up by
|
||||
# the bootloader module). amd-ucode for the dev laptop's Ryzen; intel-ucode for
|
||||
# Intel targets. bos-copy-kernel also stages these into the live target /boot.
|
||||
amd-ucode
|
||||
intel-ucode
|
||||
|
||||
# Power management (vendor-neutral; works on Intel and AMD). tlp auto-tunes power
|
||||
# by AC/battery and CPU driver; hypridle/hyprlock handle idle dim/off/lock/suspend
|
||||
# and the lock screen; upower exposes battery state. NOT power-profiles-daemon —
|
||||
# it conflicts with tlp.
|
||||
tlp
|
||||
tlp-rdw
|
||||
upower
|
||||
hypridle
|
||||
hyprlock
|
||||
|
||||
# Bootloader + filesystem
|
||||
grub
|
||||
|
|
@ -11,6 +26,22 @@ efibootmgr
|
|||
btrfs-progs
|
||||
dosfstools
|
||||
mtools
|
||||
# squashfs-tools: provides unsquashfs, which Calamares' unpackfs module uses
|
||||
# to extract airootfs.sfs onto the target during install.
|
||||
squashfs-tools
|
||||
# rsync: unpackfs copies the unpacked rootfs onto the target with rsync.
|
||||
rsync
|
||||
# Live-ISO boot (archiso bootmodes: bios.syslinux + uefi.systemd-boot)
|
||||
# mkinitcpio-archiso provides the initramfs hooks that find and mount
|
||||
# airootfs.sfs and switch root into it — without it the live ISO drops
|
||||
# to emergency mode on boot.
|
||||
mkinitcpio
|
||||
mkinitcpio-archiso
|
||||
mkinitcpio-nfs-utils
|
||||
syslinux
|
||||
memtest86+
|
||||
memtest86+-efi
|
||||
edk2-shell
|
||||
|
||||
# Snapshot infrastructure
|
||||
snapper
|
||||
|
|
@ -21,6 +52,13 @@ inotify-tools
|
|||
# Wayland / Hyprland
|
||||
hyprland
|
||||
xdg-desktop-portal-hyprland
|
||||
# GTK portal backend — file-chooser/screenshot portals for Flatpak, Electron,
|
||||
# and Firefox-based apps (Zen). Without it those apps get no file dialog.
|
||||
xdg-desktop-portal-gtk
|
||||
# Login manager for the installed system (Wayland-native; enabled by
|
||||
# post-install.sh, launches the Hyprland session via tuigreet → bos-session).
|
||||
greetd
|
||||
greetd-tuigreet
|
||||
xdg-utils
|
||||
xdg-user-dirs
|
||||
polkit
|
||||
|
|
@ -37,15 +75,37 @@ pipewire-jack
|
|||
networkmanager
|
||||
network-manager-applet
|
||||
iw
|
||||
iwd
|
||||
# mDNS service/name resolution — lets CUPS auto-discover network printers and
|
||||
# resolves .local hostnames (avahi-daemon enabled + nss-mdns wired in
|
||||
# post-install.sh).
|
||||
avahi
|
||||
nss-mdns
|
||||
# Wi-Fi backend for NetworkManager (its default; no extra config needed).
|
||||
wpa_supplicant
|
||||
bluez
|
||||
bluez-utils
|
||||
# blueman: GUI Bluetooth manager (pair/connect devices; breadbar shows status only).
|
||||
blueman
|
||||
|
||||
# GTK4 runtime
|
||||
gtk4
|
||||
gtk4-layer-shell
|
||||
librsvg
|
||||
libpulse
|
||||
# GTK3 dark theme (Adwaita-dark); without this package the gtk-theme-name in
|
||||
# skel settings.ini silently falls back to the light theme for GTK3 apps.
|
||||
gnome-themes-extra
|
||||
# Schema + backend behind `gsettings set org.gnome.desktop.interface
|
||||
# color-scheme prefer-dark` (set in hyprland.lua autostart). Without these the
|
||||
# gsettings call fails silently and libadwaita apps (nautilus, gnome-text-editor)
|
||||
# render in LIGHT mode regardless of the GTK theme.
|
||||
gsettings-desktop-schemas
|
||||
dconf
|
||||
# Credential/keyring storage — browsers, SSH agents, and most apps persist
|
||||
# passwords here; without it every session loses saved logins. seahorse is the
|
||||
# GUI to view/manage the stored secrets and keys.
|
||||
gnome-keyring
|
||||
seahorse
|
||||
|
||||
# Display (wlroots is bundled with Hyprland; don't list separately)
|
||||
wayland
|
||||
|
|
@ -53,21 +113,85 @@ wayland-protocols
|
|||
|
||||
# Fonts
|
||||
noto-fonts
|
||||
noto-fonts-cjk
|
||||
noto-fonts-emoji
|
||||
ttf-jetbrains-mono
|
||||
# Nerd font variant — icons in terminal tools (eza --icons, fastfetch, yazi)
|
||||
ttf-jetbrains-mono-nerd
|
||||
# Metric-compatible (Arial/Times/Courier) so Office/web docs lay out correctly,
|
||||
# broad Unicode fallback, and the Font Awesome icon glyph set (otf-, the desktop
|
||||
# variant — ttf-font-awesome resolves to the web-only woff2 build).
|
||||
ttf-liberation
|
||||
ttf-dejavu
|
||||
otf-font-awesome
|
||||
|
||||
# Terminal
|
||||
foot
|
||||
kitty
|
||||
|
||||
# File manager
|
||||
nautilus
|
||||
# gvfs: virtual filesystem layer for nautilus (trash, network places, removable
|
||||
# media). gvfs-mtp adds Android/MTP device support (phones, tablets via USB).
|
||||
gvfs
|
||||
gvfs-mtp
|
||||
# file-roller: archive manager — gives nautilus right-click Extract/Compress.
|
||||
file-roller
|
||||
|
||||
# Installer — sourced from [breadway] repo (see pacman.conf)
|
||||
# GUI applications a general desktop is expected to have out of the box.
|
||||
# gnome-text-editor: graphical editor (terminal editors aside); gnome-calculator:
|
||||
# calculator; loupe: Wayland-native image viewer (default for image files).
|
||||
gnome-text-editor
|
||||
gnome-calculator
|
||||
loupe
|
||||
# Media player — BOS ships gstreamer codecs but otherwise has no player app.
|
||||
vlc
|
||||
# Web browser (served from the [Breadway] repo; AUR zen-browser-bin republished
|
||||
# there so the ISO build can pull it via pacman). mailcap satisfies zen's
|
||||
# mime-types dependency explicitly.
|
||||
zen-browser-bin
|
||||
mailcap
|
||||
|
||||
# Installer — Calamares is AUR-only; built in-house and served from [breadway]
|
||||
# (calamares 3.4.x is already Qt6; there is no separate calamares-qt6 package)
|
||||
calamares
|
||||
calamares-qt6
|
||||
|
||||
# Bread ecosystem — sourced from [breadway] repo
|
||||
bakery
|
||||
# Bread ecosystem.
|
||||
#
|
||||
# The bread apps themselves (bakery, bread, breadbar, breadbox, breadcrumbs,
|
||||
# breadpad) are NOT pacman packages here — they are bakery-managed binaries
|
||||
# baked into /etc/skel/.local/bin at build time (see build-local.sh), so every
|
||||
# user gets the exact versions from this laptop's bakery install with no
|
||||
# network/DNS needed at install or runtime. Their runtime system deps are pulled
|
||||
# in elsewhere in this list (gtk4, gtk4-layer-shell, iw, libpulse, librsvg,
|
||||
# networkmanager, openssl, zlib, systemd-libs) — keep those even though no bread
|
||||
# package depends on them.
|
||||
#
|
||||
# bos-settings is a BOS-specific pacman package (not part of the bakery index),
|
||||
# so it stays here, served from the [breadway] repo.
|
||||
bos-settings
|
||||
|
||||
# Input / screen utilities
|
||||
brightnessctl
|
||||
grim
|
||||
slurp
|
||||
# Clipboard (Wayland copy/paste; also clipboard screenshots) and media keys.
|
||||
wl-clipboard
|
||||
playerctl
|
||||
# Clipboard history daemon (stores wl-clipboard events; breadbox bind replays them).
|
||||
cliphist
|
||||
# Wallpaper daemon + pywal (drives the bread* colour palette from the wallpaper).
|
||||
awww
|
||||
python-pywal
|
||||
# Boot splash (BOS logo + spinner instead of kernel text).
|
||||
plymouth
|
||||
|
||||
# Media codecs — GStreamer plugins for video thumbnails in nautilus, browser
|
||||
# media, and general playback. bad/ugly add patent-encumbered formats (H.264 etc).
|
||||
gst-plugins-good
|
||||
gst-plugins-bad
|
||||
gst-plugins-ugly
|
||||
# GUI audio mixer — useful when output device needs manual switching.
|
||||
pavucontrol
|
||||
|
||||
# Utilities
|
||||
sudo
|
||||
|
|
@ -82,5 +206,79 @@ man-db
|
|||
man-pages
|
||||
less
|
||||
|
||||
# Base CLI tools every install should have.
|
||||
# Shell
|
||||
zsh
|
||||
# Editors
|
||||
nano
|
||||
micro
|
||||
vim
|
||||
neovim
|
||||
# Shell QoL — modern replacements shipped with skel aliases set up
|
||||
eza
|
||||
bat
|
||||
fzf
|
||||
zoxide
|
||||
# Fast search — pairs with fzf/zsh and underpins a good neovim experience
|
||||
ripgrep
|
||||
fd
|
||||
# System / hardware inspection
|
||||
htop
|
||||
usbutils
|
||||
pciutils
|
||||
dmidecode
|
||||
lsof
|
||||
tree
|
||||
fastfetch
|
||||
# Removable-media filesystems (USB sticks, external drives)
|
||||
ntfs-3g
|
||||
exfatprogs
|
||||
# Archives
|
||||
7zip
|
||||
zip
|
||||
unrar
|
||||
# Remote access (ssh client; sshd ships disabled)
|
||||
openssh
|
||||
# Mirror management (refresh /etc/pacman.d/mirrorlist for the user's location)
|
||||
reflector
|
||||
|
||||
# Printing — CUPS daemon + GUI printer setup. cups-pk-helper lets the GUI add
|
||||
# printers via polkit without a root shell. cups.socket is enabled in
|
||||
# post-install.sh so printing works on the installed system.
|
||||
cups
|
||||
cups-pk-helper
|
||||
system-config-printer
|
||||
|
||||
# Flatpak — sandboxed third-party app distribution (Flathub). The user adds a
|
||||
# remote post-install (needs network); the runtime is shipped ready.
|
||||
flatpak
|
||||
|
||||
# Firewall — ufw, enabled deny-incoming in post-install.sh (mDNS allowed so
|
||||
# printer discovery still works).
|
||||
ufw
|
||||
# Firmware updates via LVFS (works with gnome-software / fwupdmgr).
|
||||
fwupd
|
||||
# Compressed RAM swap — see /etc/systemd/zram-generator.conf.
|
||||
zram-generator
|
||||
|
||||
# Icon and cursor themes
|
||||
# Papirus-Dark: cohesive icon set used as the BOS default (set via gsettings in
|
||||
# hyprland.lua autostart and in skel gtk-3.0/settings.ini).
|
||||
papirus-icon-theme
|
||||
# Bibata-Modern-Ice: BOS default cursor. AUR-only upstream, republished to the
|
||||
# [breadway] repo (see packaging/bibata + .forgejo/workflows/bibata.yml). Set via
|
||||
# XCURSOR_THEME env in hyprland.lua and gtk settings.ini / gsettings.
|
||||
bibata-cursor-theme-bin
|
||||
|
||||
# Qt dark theme — makes Qt5/Qt6 apps (VLC, pavucontrol, etc.) respect the dark
|
||||
# palette. qt5ct/qt6ct are configured via skel to use Fusion style in dark mode;
|
||||
# QT_QPA_PLATFORMTHEME=qt5ct is set in hyprland.lua env.
|
||||
qt5ct
|
||||
qt6ct
|
||||
# Native Wayland platform plugins for Qt — QT_QPA_PLATFORM=wayland (set in
|
||||
# hyprland.lua) needs these or Qt apps fall back to (blurry) XWayland.
|
||||
qt5-wayland
|
||||
qt6-wayland
|
||||
|
||||
# Dev tools (for bos-settings standalone install)
|
||||
rustup
|
||||
|
|
|
|||
|
|
@ -26,13 +26,19 @@ 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.
|
||||
#
|
||||
# Forgejo signs the repo db with a key pacman can't look up, so TrustAll
|
||||
# fails. SigLevel = Never skips verification (acceptable for this private
|
||||
# repo over TLS). TODO: import Forgejo's signing key + SigLevel = Required.
|
||||
# -----------------------------------------------------------------------
|
||||
[breadway]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://repo.breadway.dev/$arch
|
||||
# The section name must match Forgejo's served db filename
|
||||
# ({owner}.{group}.{domain}.db) — pacman fetches "<section>.db" from Server.
|
||||
[Breadway.os.git.breadway.dev]
|
||||
SigLevel = Never
|
||||
Server = https://git.breadway.dev/api/packages/Breadway/arch/os/$arch
|
||||
|
|
|
|||
|
|
@ -8,17 +8,19 @@ iso_application="Bread Operating System"
|
|||
iso_version="$(date +%Y.%m.%d)"
|
||||
install_dir="arch"
|
||||
buildmodes=('iso')
|
||||
bootmodes=(
|
||||
'bios.syslinux.mbr'
|
||||
'bios.syslinux.eltorito'
|
||||
'uefi-x64.systemd-boot.esp'
|
||||
'uefi-x64.systemd-boot.eltorito'
|
||||
)
|
||||
bootmodes=('bios.syslinux' 'uefi.systemd-boot')
|
||||
arch="x86_64"
|
||||
pacman_conf="pacman.conf"
|
||||
airootfs_image_type="squashfs"
|
||||
airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '1M')
|
||||
file_permissions=(
|
||||
["/etc/shadow"]="0:0:400"
|
||||
["/etc/sudoers.d/99-bos-live"]="0:0:440"
|
||||
["/etc/calamares/post-install.sh"]="0:0:755"
|
||||
["/usr/local/bin/bos-live-setup"]="0:0:755"
|
||||
["/usr/local/bin/bos-launch-calamares"]="0:0:755"
|
||||
["/usr/local/bin/bos-copy-kernel"]="0:0:755"
|
||||
["/usr/local/bin/bos-session"]="0:0:755"
|
||||
["/usr/local/bin/bos-keybinds"]="0:0:755"
|
||||
["/usr/local/bin/bos-welcome"]="0:0:755"
|
||||
)
|
||||
|
|
|
|||
28
iso/syslinux/archiso_head.cfg
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
SERIAL 0 115200
|
||||
UI vesamenu.c32
|
||||
MENU TITLE Bread OS
|
||||
MENU BACKGROUND splash.png
|
||||
|
||||
MENU WIDTH 78
|
||||
MENU MARGIN 4
|
||||
MENU ROWS 7
|
||||
MENU VSHIFT 10
|
||||
MENU TABMSGROW 14
|
||||
MENU CMDLINEROW 14
|
||||
MENU HELPMSGROW 16
|
||||
MENU HELPMSGENDROW 29
|
||||
|
||||
# Refer to https://wiki.syslinux.org/wiki/index.php/Comboot/menu.c32
|
||||
|
||||
MENU COLOR border 30;44 #40ffffff #a0000000 std
|
||||
MENU COLOR title 1;36;44 #9033ccff #a0000000 std
|
||||
MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all
|
||||
MENU COLOR unsel 37;44 #50ffffff #a0000000 std
|
||||
MENU COLOR help 37;40 #c0ffffff #a0000000 std
|
||||
MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std
|
||||
MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std
|
||||
MENU COLOR msg07 37;40 #90ffffff #a0000000 std
|
||||
MENU COLOR tabmsg 31;40 #30ffffff #00000000 std
|
||||
|
||||
MENU CLEAR
|
||||
MENU IMMEDIATE
|
||||
32
iso/syslinux/archiso_pxe-linux.cfg
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
LABEL arch_nbd
|
||||
TEXT HELP
|
||||
Boot the Bread OS install medium using NBD.
|
||||
It allows you to install Bread OS or perform system maintenance.
|
||||
ENDTEXT
|
||||
MENU LABEL Bread OS install medium (%ARCH%, NBD)
|
||||
LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||
INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archiso_nbd_srv=${pxeserver} cms_verify=y
|
||||
SYSAPPEND 3
|
||||
|
||||
LABEL arch_nfs
|
||||
TEXT HELP
|
||||
Boot the Bread OS live medium using NFS.
|
||||
It allows you to install Bread OS or perform system maintenance.
|
||||
ENDTEXT
|
||||
MENU LABEL Bread OS install medium (%ARCH%, NFS)
|
||||
LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||
INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
APPEND archisobasedir=%INSTALL_DIR% archiso_nfs_srv=${pxeserver}:/run/archiso/bootmnt cms_verify=y
|
||||
SYSAPPEND 3
|
||||
|
||||
LABEL arch_http
|
||||
TEXT HELP
|
||||
Boot the Bread OS live medium using HTTP.
|
||||
It allows you to install Bread OS or perform system maintenance.
|
||||
ENDTEXT
|
||||
MENU LABEL Bread OS install medium (%ARCH%, HTTP)
|
||||
LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||
INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||
APPEND archisobasedir=%INSTALL_DIR% archiso_http_srv=http://${pxeserver}/ cms_verify=y
|
||||
SYSAPPEND 3
|
||||