Skip to content
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
recipe:
# !! Add your recipes here
- recipe-gnome.yml
- recipe-gnome-nvidia.yml
steps:
# the build is fully handled by the reusable github action
- name: Build Custom Image
Expand Down
297 changes: 297 additions & 0 deletions recipes/gnome-base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
# Shared module stack for the Monolith GNOME editions (plain + NVIDIA),
# imported via `from-file:` by recipe-gnome.yml and recipe-gnome-nvidia.yml.
# Per-edition os-release, the NVIDIA stack, initramfs, and signing live in
# those recipes, not here.
modules:
- type: files
files:
- source: system
destination: /

- type: dnf
repos:
copr:
- lilay/topgrade
files:
- https://brave-browser-rpm-nightly.s3.brave.com/brave-browser-nightly.repo
- https://negativo17.org/repos/fedora-steam.repo
keys:
- https://brave-browser-rpm-nightly.s3.brave.com/brave-core-nightly.asc
- https://negativo17.org/repos/RPM-GPG-KEY-slaanesh
install:
packages:
# Basics
- fish
- git
- gh
- curl
- btop
- fastfetch
# Fonts
- google-noto-fonts-all
# Gnome Tools
- gnome-tweaks
# Tailscale
- tailscale
# Updating helper
- topgrade
# Steam/controller support
- steam-devices
# Gaming tools
- gamescope
- gamemode
- mangohud
- vkBasalt
- vulkan-tools
- winetricks
- protontricks
- evtest
# FUSE 2 support for AppImages (provides libfuse.so.2)
- fuse-libs
# Brave Origin
- brave-origin-nightly
# Layered Steam RPM
- steam
remove:
packages:
- firefox
- firefox-langpacks
- gnome-software
# Use distrobox exclusively; toolbox is redundant and its label-based
# detection collides with distrobox-managed boxes (see topgrade toolbx).
- toolbox

- type: script
snippets:
- |
# Swap the stock Fedora kernel for the CachyOS kernel and add the
# optional sched-ext (scx) userspace schedulers.
#
# The CachyOS COPRs are pulled in transiently with --repofrompath, so
# nothing is written to /etc/yum.repos.d. The final image ships with no
# CachyOS repo, so users can neither enable nor change it. The kernel
# packages are versionlocked so no later transaction can replace them.
#
# All of this lives in /usr (binaries) plus a system service; it edits
# no user files. Removing this block and rebuilding reverts users to the
# stock kernel with no scripts or per-machine cleanup, leaving nothing
# behind in /etc.
set -euxo pipefail

kernel_repo="https://download.copr.fedorainfracloud.org/results/bieszczaders/kernel-cachyos/fedora-\$releasever-\$basearch/"
addons_repo="https://download.copr.fedorainfracloud.org/results/bieszczaders/kernel-cachyos-addons/fedora-\$releasever-\$basearch/"

# 1. Kernel swap, following ublue-os/bazzite's install-kernel-akmods.
# A plain install/remove fails on bootc: the kernel %posttrans runs
# the kernel-install hooks (dracut) mid-transaction, before depmod
# has written modules.dep, and a dnf remove of the stock kernel
# cascade-erases NetworkManager and friends. So we shim the two
# kernel-install hooks to no-ops, erase the stock kernel with
# rpm --nodeps, install CachyOS, then restore the hooks. The
# initramfs module in the importing recipe builds the initramfs.
pushd /usr/lib/kernel/install.d
mv 05-rpmostree.install 05-rpmostree.install.bak
mv 50-dracut.install 50-dracut.install.bak
printf '%s\n' '#!/bin/sh' 'exit 0' > 05-rpmostree.install
printf '%s\n' '#!/bin/sh' 'exit 0' > 50-dracut.install
chmod +x 05-rpmostree.install 50-dracut.install
popd

for pkg in kernel kernel-core kernel-modules kernel-modules-core kernel-modules-extra; do
rpm --erase "$pkg" --nodeps || true
done
rm -rf /usr/lib/modules

dnf5 -y install --nogpgcheck \
--repofrompath "cachyos-kernel,$kernel_repo" \
kernel-cachyos kernel-cachyos-core kernel-cachyos-modules kernel-cachyos-devel-matched

pushd /usr/lib/kernel/install.d
mv -f 05-rpmostree.install.bak 05-rpmostree.install
mv -f 50-dracut.install.bak 50-dracut.install
popd

# Make sure modules.dep exists for the new kernel; the initramfs module
# in the importing recipe builds the actual initramfs (it loops over
# /usr/lib/modules and runs dracut --add ostree for each kernel). On the
# NVIDIA edition the nvidia.yml akmod build runs after this file and adds
# its module before that initramfs step, so it is baked in too.
depmod -a "$(ls /usr/lib/modules)"

# 2. scx addons: scx-scheds (the BPF schedulers) and scx-tools
# (scx_loader.service + scxctl). Pure /usr, removable on their own.
dnf5 -y install --nogpgcheck \
--repofrompath "cachyos-addons,$addons_repo" \
scx-scheds scx-tools

# NOTE: CachyOS's docs say to run
# setsebool -P domain_kernel_load_modules on
# but that targets mutable installs where akmods loads modules from a
# confined context at install time. Here the kernel and its modules are
# baked in at build time and loaded at boot in the init context, which
# the boolean does not gate, so it should not be needed. It is omitted
# to keep this change /usr-only with no sticky /etc SELinux state. If a
# confined service fails to autoload a module at runtime, it shows up as
# sudo ausearch -m avc -ts boot | grep -iE 'module_(load|request)'
# in which case re-add the boolean (or, cleaner, a targeted policy
# module for just that domain).

# 3. Pin the CachyOS kernel so nothing swaps it at runtime.
dnf5 versionlock add \
kernel-cachyos \
kernel-cachyos-core \
kernel-cachyos-modules \
kernel-cachyos-devel-matched

- type: systemd
system:
enabled:
- tailscaled.service
- monolith-firstboot-label.service
# scx_loader picks the default sched-ext scheduler at boot.
- scx_loader.service

- type: fonts
fonts:
nerd-fonts:
- JetBrainsMono

- type: gnome-extensions
install:
- AppIndicator and KStatusNotifierItem Support
- Blur my Shell
- Clipboard History
- Tiling Shell
- Caffeine
- User Themes
- Add to Steam
- Restart To
- Dash to Dock
- Dash to Panel
- Gnome 4x, 5x UI Improvements
- Vicinae

- type: gschema-overrides
include:
- zzz-monolith.gschema.override

- type: default-flatpaks
configurations:
- notify: false
scope: system
install:
- org.gnome.Loupe
- org.gnome.Papers
- org.gnome.SimpleScan
- org.gnome.TextEditor
- org.gnome.Weather
- org.gnome.Calendar
- org.gnome.Calculator
- org.gnome.clocks
- org.gnome.font-viewer
- org.gnome.baobab
- org.gnome.Characters
- org.gnome.Showtime
- it.mijorus.gearlever
- io.github.kolunmi.Bazaar
- com.ranfdev.DistroShelf
- com.mattjakeman.ExtensionManager
- com.vysp3r.ProtonPlus
- com.github.zocker_160.SyncThingy
- com.saivert.pwvucontrol
- org.jellyfin.JellyfinDesktop
- io.github.getnf.embellish
- org.cryptomator.Cryptomator
- io.github.wivrn.wivrn
- io.github.Faugus.faugus-launcher
- com.system76.Popsicle
- com.github.tchx84.Flatseal
- org.mozilla.Thunderbird
- com.discordapp.Discord
- com.yubico.yubioath
- scope: user
notify: false

- type: script
snippets:
- |
# Add Terra repo and swap mesa to Terra build.
set -euxo pipefail
dnf5 -y install --nogpgcheck \
--repofrompath terra,https://repos.fyralabs.com/terra\$releasever \
terra-release terra-release-mesa
dnf5 -y config-manager setopt "*terra*".priority=1
dnf5 -y config-manager setopt "terra-mesa".enabled=false
dnf5 -y config-manager setopt "fedora-steam".exclude="mesa-*"
dnf5 -y config-manager setopt "*fedora*".exclude="mesa-*"
dnf5 --enable-repo=terra-mesa -y swap mesa-filesystem mesa-filesystem
dnf5 --enable-repo=terra-mesa -y install mesa-libOpenCL
dnf5 versionlock add \
mesa-dri-drivers \
mesa-filesystem \
mesa-libEGL \
mesa-libGL \
mesa-libgbm \
mesa-vulkan-drivers
dnf5 -y config-manager setopt terra.enabled=false
dnf5 -y config-manager setopt "fedora-steam".enabled=false
- |
# Install the Vicinae launcher from Terra. terra-release (added by the
# mesa swap above) already ships the terra repo on disk but left it
# disabled, so re-enable it just for this transaction with --enable-repo,
# mirroring the mesa swap's own --enable-repo=terra-mesa. Reusing the
# on-disk repo avoids the duplicate-id error a second --repofrompath
# would cause. vicinae is only carried by Terra, so it bakes in and
# updates on rebase rather than from an enabled repo at runtime.
set -euxo pipefail
dnf5 --enable-repo=terra -y install vicinae
# Explicitly leave the terra repo disabled in the final image. The
# --enable-repo above is only transaction-scoped, but make the end state
# unambiguous now that this is the last block to touch terra.
dnf5 -y config-manager setopt terra.enabled=false
# Enable Vicinae's per-user launcher daemon for every user. The systemd
# module runs before this script installs the package, so the unit would
# not yet exist there; --global enable is what that module does for user
# units.
systemctl --global enable vicinae.service
- |
# Install Bazaar Companion GNOME Shell extension system-wide.
set -euxo pipefail
uuid="bazaar-integration@kolunmi.github.io"
tmpdir="$(mktemp -d)"
git clone --depth=1 https://github.com/bazaar-org/bazaar-companion "$tmpdir"
install -d "/usr/share/gnome-shell/extensions/$uuid"
cp -a "$tmpdir/src/." "/usr/share/gnome-shell/extensions/$uuid/"
chmod -R a+rX "/usr/share/gnome-shell/extensions/$uuid"
rm -rf "$tmpdir"
- |
# Bake the pure fish prompt system-wide into the image's vendor dirs
# (no fisher, no per-user install). Fish autoloads these for every
# user, and the files refresh on each image build, so updates ship via
# image rebase rather than a runtime plugin manager.
set -euxo pipefail
tmpdir="$(mktemp -d)"
git clone --depth=1 https://github.com/pure-fish/pure "$tmpdir"
# Pure's _pure_set_default writes its ~73 default options as *universal*
# variables into each user's ~/.config/fish/fish_variables on first
# shell start. That both edits user files and freezes those defaults in
# the user's home, so later image updates to a default never reach them.
# Patch the helper to use global (session) scope instead: defaults are
# re-applied from /usr every shell start, so they track image rebuilds
# and nothing is written to the user's home. A user's own `set -U
# pure_*` override still wins, since the helper only sets when unset.
sed -i 's/set --universal/set --global/' "$tmpdir/functions/_pure_set_default.fish"
grep -q 'set --global' "$tmpdir/functions/_pure_set_default.fish"
! grep -q 'set --universal' "$tmpdir/functions/_pure_set_default.fish"
install -d /usr/share/fish/vendor_functions.d /usr/share/fish/vendor_conf.d
cp -a "$tmpdir/functions/." /usr/share/fish/vendor_functions.d/
cp -a "$tmpdir/conf.d/." /usr/share/fish/vendor_conf.d/
chmod -R a+rX /usr/share/fish/vendor_functions.d /usr/share/fish/vendor_conf.d
rm -rf "$tmpdir"
test -f /usr/share/fish/vendor_functions.d/fish_prompt.fish
test -f /usr/share/fish/vendor_conf.d/pure.fish
- |
# Compile GDM dconf defaults.
dconf update
Loading
Loading