A personal Fedora Atomic desktop image built for one machine: AMD GPU, Fedora 43, single user. It ships Hyprland with basecamp/omarchy baked in. It is not designed to be general-purpose — assumptions about GPU, hardware, and workflow are hard-coded throughout.
Containerfile— two-stage OCI build (scratch context + base-main)build_files/build.sh— layer 2 entry point: packages, desktop setup, Flathub/systemd/cleanupbuild_files/repos.sh— COPR/repo setup +copr_install_isolatedhelperbuild_files/packages.sh— all dnf5 package installsbuild_files/source-builds.sh— layer 1 entry point: repos + full Hyprland ecosystem + non-hyprwm source buildsbuild_files/desktop.sh— SDDM greeter + omarchy into/etc/skelfiles/— static filesystem overlay (systemd units, SDDM configs, ujust recipes)DESIGN.md— architecture and design decisions.github/workflows/build.yml— CI/CD (weekly + push + PR)
just build # podman build the image locally
just lint # shellcheck all .sh files
just format # shfmt all .sh filesThere are no unit tests. The build itself is the test — if build.sh exits non-zero or bootc container lint fails, CI catches it.
The build is split into two layers. source-builds.sh runs first for repos + source-built binaries, then build.sh handles packages, desktop setup, Flathub, systemd units, and cleanup. All pinned refs (source-build tags, the Hyprland COPR name, Omarchy ref) live in pins.sh, which both layers source.
| File | Responsibility |
|---|---|
build.sh |
Layer 2 entry point, desktop setup, Flathub/systemd/cleanup |
repos.sh |
COPR enablement, VS Code repo, Docker CE repo, copr_install_isolated helper |
packages.sh |
dnf5 install, isolated COPR installs, Firefox removal |
source-builds.sh |
Layer 1 entry point, repo setup, non-hyprwm source builds + cargo_install helper |
desktop.sh |
SDDM greeter + theme + omarchy into /etc/skel |
- Bash:
set -euo pipefail. Use|| trueonly for tolerable failures (e.g., Firefox removal). UPPERCASE for constants/pinned tags, lowercase for locals. - Packages: always
--setopt=install_weak_deps=False. Group by purpose with comments. - Repos left enabled (brycensranch/gpu-screen-recorder-git, VS Code, Docker CE) vs isolated COPRs (che/nerd-fonts, ublue-os/packages, erikreider/swayosd). Isolated COPRs must use
copr_install_isolatedso no.reposurvives. - Pinned refs use
*_TAG/*_COMMIT/*_REFvariables inpins.sh(sourced by both layers). The hyprwm ecosystem has no tags to pin; it comes from the COPR named inHYPRLAND_COPR. - Systemd units use
atomic-hyprland-prefix. - Static overlay files go in
files/mirroring the filesystem root. - justfile user-tunable knobs use module-level
key := "0"assignments and are overridden from the CLI asujust key=value recipe(the override must precede the recipe name — that's howjustparses its command line). Do not use recipe parameters for this:justhas no named-parameter CLI syntax, soujust recipe key=valuesilently passes the literal string"key=value"as the positional value instead of setting the parameter.
- Add to
PACKAGES=()inpackages.sh, in the appropriate category block. - If the package is in a new COPR that should stay enabled, add it to the loop in
repos.sh. - If the package is in a COPR that should NOT stay enabled, call
copr_install_isolatedinpackages.sh. - If the package isn't in any repo, add a source-build block in
source-builds.shwith a pinned tag inpins.sh.
Drop a .preinstall file in files/usr/share/flatpak/preinstall.d/. The existing flatpak-preinstall.service helper installs it on first boot.
Arch-specific bits are stripped out at build time rather than sed-patched:
- All
omarchy-install-*,omarchy-pkg-*,omarchy-webapp-*,omarchy-tui-*, andomarchy-windows-*scripts are deleted from skel. The image ships apps via Flatpak/COPR/brew, not pacman/yay. - The "Install" entry is sed-stripped from the top-level
omarchy-menu(both the menu string and the case handler). omarchy-updateis overwritten with a one-line stub that callsujust update(which handles bootc + flatpak + brew).JetBrainsMono Nerd Font(omarchy upstream default) is source-installed from theryanoasis/nerd-fontsrelease insource-builds.sh;files/etc/fonts/conf.d/80-atomic-hyprland-monospace.confpins themonospacefontconfig alias so walker and other generic-alias consumers resolve correctly.
Default Hyprland theme is tokyo-night, bootstrapped at build time as a real directory at /etc/skel/.config/omarchy/current/theme/ (via omarchy-theme-set-templates rendering + atomic mv into place — no symlink).
Do not patch config/ files unless the change cannot be done by the user after first login.
The entire hyprwm ecosystem is installed from the lionheartp/Hyprland COPR, not source-built. The COPR name is pinned as HYPRLAND_COPR in pins.sh and installed via copr_install_isolated in packages.sh. Installing the compositor + satellites pulls the hypr* libraries (hyprutils, hyprlang, hyprcursor, hyprgraphics, aquamarine, hyprwire, hyprland-protocols, glaze) as dependencies in the same transaction.
Installed set: hyprland (compositor), hyprland-uwsm (uwsm session + hyprland-uwsm.desktop), uwsm (session manager), hyprlock, hypridle, hyprpicker, hyprsunset, xdg-desktop-portal-hyprland, hyprtoolkit, hyprland-guiutils, hyprland-qt-support, hyprpolkitagent.
Caveats to know:
- Unlike the
craftidore/wayblueorg-hyprlandCOPR (wayblue's, which ships only a rollinghyprland-gittracking master), lionheartp packages tagged stable Hyprland releases. So the compositor is pinned to a released version (HYPRLAND_VERSION) rather than a git snapshot, and the whole session stack (includinguwsmand thehyprland-uwsm.desktopsession file) comes from one coherent source. Omarchy's hyprland config may still need fixes when upstream renames/deprecates options across releases. hyprland-uwsmRequires: uwsm, satisfied by theuwsmRPM from the same COPR in the same transaction. This is why we no longer source-build uwsm.- The expected binary paths are asserted in
verify.shviaPACKAGED_HYPRWM_EXECUTABLESinmanifest.sh(now including/usr/bin/uwsm). If the COPR changes a binary's install path, that check catches it.
Only the non-hyprwm tools that have no usable Fedora/COPR package are source-built in source-builds.sh. The cargo_install helper reduces repetition; BUILD_DEPS is scoped to exactly these builds.
- walker (Cargo + GTK4) + hyprland-preview-share-picker (Cargo + GTK4/gtk4-layer-shell) screen-share picker
- wiremix (Cargo + PipeWire, bindgen needs
clang-devel) audio mixer TUI - elephant (Go, + provider
.soplugins built with the matching toolchain) walker data provider - xdg-terminal-exec + hyprshot (shell scripts)
- JetBrainsMono Nerd Font (upstream release tarball)
Other tools (satty, cliphist, gum, starship, impala, bluetui) are installed as pinned upstream release binaries/RPMs in packages.sh, not source-built.