Make BOS a complete, bootable, themed desktop OS

Install/boot reliability:
- Use native Calamares initcpiocfg/initcpio + explicit grub-install (nvram +
  --removable) in post-install; drop the flaky native bootloader/grubcfg modules.
- mount.conf: bind /proc /sys /dev (devtmpfs) /run + efivars into the chroot.
- bos-copy-kernel: stage kernel + write a stock mkinitcpio preset (replace the
  archiso preset). Per-service systemctl enable (fixes NetworkManager et al.
  silently not enabling due to the all-or-nothing grub-btrfs.path name).

System completeness:
- greetd + tuigreet graphical login; installed pacman.conf + working mirrorlist;
  base CLI tools (nano, micro, vim, htop, …); amd/intel-ucode; tlp + hypridle
  power management; systemd-timesyncd, fstrim.timer; wpa_supplicant wifi; Zen
  browser (republished to the [Breadway] repo).

Desktop + theming:
- Native Lua Hyprland config (hyprland.lua) with curated standard binds; kitty
  (blur) replaces foot; awww wallpaper + pywal palette (tamed to a black base
  with warm accents); GTK dark mode.
- Plymouth boot splash (bos theme: logo + spinner + status) via plymouthcfg.
- Varela Round font; Calamares bread-palette sidebar (logo/black-region polish
  still pending).
This commit is contained in:
Breadway 2026-06-16 09:09:34 +08:00
parent 787cc0e4c5
commit f8ae8fe125
35 changed files with 787 additions and 180 deletions

3
.gitignore vendored
View file

@ -38,3 +38,6 @@ logs/
# Claude Code local agent state
.claude/
# Wallpaper source drop (baked copy lives in airootfs/usr/share/backgrounds)
/Bread Background.png

90
build-local.sh Executable file
View 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)
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

View file

@ -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"

View 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;
}

View file

@ -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

View 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

View file

@ -1,9 +1,10 @@
---
# Calamares defaults shellprocess to a 10-second timeout. post-install.sh runs
# mkinitcpio, grub-install, grub-mkconfig, snapper setup and a networked bakery
# install — minutes of work — so without a generous timeout it gets killed
# partway (leaving /boot and the ESP half-populated → unbootable system). The
# leading "-" keeps a non-zero exit non-fatal to the install.
timeout: 1800
# 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"

View file

@ -1,7 +1,11 @@
#!/bin/bash
# Runs inside the installed-system chroot (Calamares shellprocess, after the
# bootloader step). Best-effort: a single failure must not abort the rest, so
# we deliberately do NOT use `set -e`.
# 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
MAIN_USER="$(getent passwd 1000 | cut -d: -f1 || true)"
@ -20,30 +24,42 @@ userdel -r liveuser 2>/dev/null || true
passwd -l root || true
# ---------------------------------------------------------------------------
# Rebuild the initramfs for a real install — the live image ships the archiso
# hooks, which would send the installed system into the live-boot path.
# 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.
# ---------------------------------------------------------------------------
rm -f /etc/mkinitcpio.conf.d/archiso.conf
cat >/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
mkinitcpio -P || echo "WARN: mkinitcpio regeneration failed"
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 ourselves. Calamares' bootloader module runs before the kernel
# and initramfs exist (archiso keeps them out of the squashfs; shellprocess
# @kernel only lays vmlinuz down just beforehand), so its grub-install/config
# leaves the ESP empty. Redo it here, now that /boot is fully populated.
#
# Two passes: the standard NVRAM entry, plus a --removable copy to
# EFI/BOOT/BOOTX64.EFI so firmware that lost/never wrote an NVRAM entry (the
# "no boot device / screen just refreshes" failure) still finds a bootloader.
# 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 \
@ -53,8 +69,6 @@ if command -v grub-install &>/dev/null; then
--removable --recheck \
|| echo "WARN: grub-install (removable) failed"
fi
# Refresh GRUB so it references the kernel + rebuilt initramfs.
if command -v grub-mkconfig &>/dev/null; then
grub-mkconfig -o /boot/grub/grub.cfg || echo "WARN: grub-mkconfig failed"
fi
@ -76,22 +90,39 @@ if command -v snapper &>/dev/null; then
fi
# ---------------------------------------------------------------------------
# System services.
# 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)
# ---------------------------------------------------------------------------
systemctl enable NetworkManager bluetooth snapper-cleanup.timer grub-btrfs.path \
|| echo "WARN: enabling some services failed"
for unit in NetworkManager.service bluetooth.service systemd-timesyncd.service \
tlp.service greetd.service snapper-cleanup.timer grub-btrfsd.service \
fstrim.timer; do
systemctl enable "$unit" || echo "WARN: failed to enable $unit"
done
systemctl set-default graphical.target || echo "WARN: set-default graphical failed"
# The bread ecosystem (bread, breadbar, breadbox, breadcrumbs, breadpad,
# bos-settings) is baked into the squashfs and already copied onto the target by
# unpackfs — no install step needed here, and the install works fully offline.
# 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 into the user's home (don't clobber existing files).
# 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/.config ]]; then
mkdir -p "/home/$MAIN_USER/.config"
cp -rn /etc/skel/.config/. "/home/$MAIN_USER/.config/" || true
chown -R "$MAIN_USER:$MAIN_USER" "/home/$MAIN_USER/.config" || true
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

View file

@ -29,8 +29,21 @@ sequence:
- networkcfg
- hwclock
- packages
# 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
- bootloader
# 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:

View 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"

View 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

View 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

View 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

View 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"
}
}

View file

@ -0,0 +1,3 @@
[Settings]
gtk-application-prefer-dark-theme=1
gtk-theme-name=Adwaita-dark

View file

@ -0,0 +1,3 @@
[Settings]
gtk-application-prefer-dark-theme=1
gtk-theme-name=Adwaita-dark

View 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
}

View file

@ -1,55 +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 {
preserve_split = true
}
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
}

View file

@ -0,0 +1,168 @@
-- 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,
},
})
-- ---------------------------------------------------------------------------
-- 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("MOZ_ENABLE_WAYLAND", "1")
hl.env("QT_QPA_PLATFORM", "wayland;xcb")
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 .. " + 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 .. " + 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 110 (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 = {
-- Prefer dark for GTK4/libadwaita apps (GTK3 uses settings.ini); without
-- this nautilus/breadbox render in light mode.
"gsettings set org.gnome.desktop.interface color-scheme prefer-dark",
"gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark",
"/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",
}
for _, cmd in ipairs(startup) do
hl.dispatch(hl.dsp.exec_cmd(cmd))
end
end)

View 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
}

View file

@ -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

View file

@ -0,0 +1,13 @@
# 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.
background_opacity 0.88
background_blur 1
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

View 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

View file

@ -0,0 +1 @@
/usr/lib/systemd/system/NetworkManager.service

View file

@ -3,8 +3,9 @@
#
# 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.
# Without a kernel, the chroot's `mkinitcpio -P` and `grub-mkconfig` produce
# nothing and the installed system is unbootable.
# 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.
@ -21,4 +22,25 @@ for u in amd-ucode.img intel-ucode.img; do
[ -f "$SRC/$u" ] && cp -f "$SRC/$u" "$ROOT/boot/$u"
done
echo "Copied live kernel into $ROOT/boot"
# 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"

View file

@ -19,16 +19,16 @@ if ! id liveuser &>/dev/null; then
fi
# Layer the installer onto the live desktop: auto-launch it, and bind Super+I to
# relaunch it after it's been closed. Appended to (not replacing) the skel
# Hyprland config so the full desktop stays intact.
HYPR=/home/liveuser/.config/hypr/hyprland.conf
# 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) ---
exec-once = bos-launch-calamares
bind = SUPER, I, exec, bos-launch-calamares
-- --- 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

View 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

View file

@ -0,0 +1,49 @@
# BOS Plymouth boot splash (script module).
# Bread-brown background, centred white BOS logo, a spinning accent ring, and a
# status line at the bottom. Colours match the bread palette.
# --- background (#230b00) ---
Window.SetBackgroundTopColor(0.137, 0.043, 0.0);
Window.SetBackgroundBottomColor(0.137, 0.043, 0.0);
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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -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
@ -37,6 +52,10 @@ inotify-tools
# Wayland / Hyprland
hyprland
xdg-desktop-portal-hyprland
# 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
@ -53,7 +72,8 @@ pipewire-jack
networkmanager
network-manager-applet
iw
iwd
# Wi-Fi backend for NetworkManager (its default; no extra config needed).
wpa_supplicant
bluez
bluez-utils
@ -73,29 +93,47 @@ noto-fonts-emoji
ttf-jetbrains-mono
# Terminal
foot
kitty
# File manager
nautilus
# 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
# Bread ecosystem — sourced from [breadway] repo. Baked into the squashfs so a
# fresh install (and the live session) has the full desktop with no network.
bakery
bread
breadbar
breadbox
breadcrumbs
breadpad
# 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
# Wallpaper daemon + pywal (drives the bread* colour palette from the wallpaper).
awww
python-pywal
# Boot splash (BOS logo + spinner instead of kernel text).
plymouth
# Utilities
sudo
@ -110,5 +148,32 @@ man-db
man-pages
less
# Base CLI tools every install should have.
# Editors
nano
micro
vim
# Shell UX
bash-completion
# 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
# Dev tools (for bos-settings standalone install)
rustup

View file

@ -20,4 +20,5 @@ file_permissions=(
["/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"
)