Atomic Fedora with Niri

· 6min

Atomic linux distributions promise a lot for the future of the operating system. Security, stability, ease up updates, testability, reproducibility, etc. These projects achieve these benefits by reducing the churn on the base system and nudging the user towards more sanboxed solutions for their apps. An oft perceived downside of this architecture is limited customization options.

I wrote a post about my journey with Nix and thought it would be a fun adventure to explore some other non-traditional Linux environments. This also coincides with my adoption of an awesome new window manager project, Niri!

Update 2024-10-17

While this was a fun adventure, it turns out there is a Niri COPR now!

wget https://copr.fedorainfracloud.org/coprs/yalter/niri/repo/fedora-$(rpm -E %fedora)/yalter-niri-fedora-$(rpm -E %fedora).repo \
    -O /etc/yum.repos.d/_copr_yalter-niri.repo
rpm-ostree install niri

Quick Foundational Aside

Before getting too far, I want to touch on what exactly an atomic system is. In the most simple of terms, we can think of an atomic OS as one in which the entire system is maintained as one "image", or "generation", or "layer"... Whatever mental model works for you. When an update happens, the entire image is laid down to replace the last one. Generally, this happens in an A-B model where the new base is laid down next to the old, and you switch into it after a reboot. This allows everything to be replaced at once, eliminating any conflicts (looking at you, python), while also facilitating rollbacks.

Now, I don't mean to confuse anyone with these terms as they actually have meaning depending on what distribution you are using, I'm not using any specific system's terminology here, just a purely vendor-agnostic description.

So how does this play out in real life? Well (generally) you have a base layer that is placed on the system. This is all the packages and files needed to make the system start up and give you the desired experience. In the case of Fedora Silverblue or OpenSUSE AEON this essentially boils down to a base Gnome experience with packages and configurations to deliver a nice and usable system for the end user.

Disclaimer: Fedora-isms incoming

"What about MyFavoriteApp???" you ask? Well that's where solutions like Flatpak come into play. This allows a platform independent mechanism for installing, updating, and launching apps. Containerized apps don't rely on system dependencies, thus they can be used freely with a very minimal set of actual packages installed.

"But I need my developer tools!!!" you say? Toolbox has your back. Just toolbox enter and boom, you have a mutable containerized shell where you can install anything you want and it is well integrated with your home directory and desktop environment.

Customization

Of course not everything you need to customize your experience can be handled through flatpak and toolbox (or distrobox, if you're using something other than fedora).

Enter the layers!

Atomic systems provide the ability to extend the base image by adding layers. What this means is that when you perform an update, the base image is rolled out and whatever additional layers you have chosen are then applied on top. This helps maintain the integrity of the system and gives you an "as fresh as possible" experience on each generation of your system.

Niri Setup

Now that we have the basics in place, it's time to make this thing our own.

Niri is a scrolling, tiling, very awesome wayland compositor, so to start this adventure I chose to roll out the Fedora Sericea or "Sway Atomic" image. This has a lot of the minimal configs and packages you would expect from a window manager. The install essentially proceeds as any other Fedora install, and in the end we end up in a pretty basic sway environment.

Next I decided to layer in some system level tools that I need:

sudo rpm-ostree install -y alacritty breeze-cursor-theme \
    breeze-icon-theme fuzzel libadwaita \
    mako mate-polkit neovim papirus-icon-theme
    power-profiles-daemon qemu source-foundry-hack-fonts \
    tmux virt-manager zsh

I keep my dotfiles in github and have a script that links them all to the appropriate XDG folders, and that process doesn't change at all.

So now I need to actually compile Niri, and we don't want to install all that tooling on the base system (the more layers the more pollution, and the longer updates will take).

toolbox enter

Once inside toolbox, we can get down to business:

Install all the prerequisites:

sudo dnf install -y dnf5

sudo dnf5 install -y gcc libudev-devel libgbm-devel \
libxkbcommon-devel wayland-devel libinput-devel \
dbus-devel systemd-devel libseat-devel pipewire-devel \
pango-devel cairo-gobject-devel clang git

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Build Niri:

git clone https://github.com/yalter/niri
cd niri
cargo build --release

Once built, we need to drop out of toolbox to continue installation. Toolbox is sandboxed away from your main running system so it only has access to the home directory. Here we have to make some minor changes to the install instructions, since the base OS is technically "immutable", we have to use the alternate directories which are then merged during boot:

sudo mkdir -p /usr/local/share/wayland-sessions
sudo mkdir -p /usr/local/share/xdg-desktop-portal
sudo mkdir -p /usr/local/lib/systemd/user

sudo cp target/release/niri /usr/local/bin
sudo cp resources/niri-session /usr/local/bin
sudo cp resources/niri.desktop /usr/local/share/wayland-sessions
sudo cp resources/niri-portals.conf /usr/local/share/xdg-desktop-portal
sudo cp resources/niri.service /usr/local/lib/systemd/user
sudo cp resources/niri-shutdown.target /usr/local/lib/systemd/user

Edit 2024-07-23: Fixed the files/folder names above. Additionally, the paths in some of these files will need to be updated to reflect the atomic directory structure. For example, /usr/bin/niri-session becomes /usr/local/bin/niri-session.

By default, Sericea/Sway Atomic uses SDDM, when you restart the system you will now be able to choose "Niri" as your session and voila, magic.

Pod-ception

One last bit of developer setup I needed to work around is my use of cross-rs for cross compilation. This requires access to podman (or docker) so it can orchestrate building and running container workloads. It also requires rust tooling to be available which, if you remember from earlier, we installed via rustup so the binaries are in the home directory, totally accessible to both the toolbox and system environments. My solution here was to enable the podman socket activation in the OS:

systemctl --user enable --now podman.socket

Then install podman-remote inside toolbox:

sudo dnf5 install -y podman-remote
ln -s `which podman-remote` ~/.local/bin/podman

Now this allows me to build my cross containers inside toolbox with no issues.

There you have it

That wasn't so hard, right? Now you have a super simple and stable base OS complete with atomic updating and rollbacks. The only minor diversion here is that we wanted a hot-off-the-presses compositor/window manager without using a supported layer.

I think this goes to show that customization and atomic being mutually exclusive is almost entirely a myth. The facts are that it is different than a traditional environment, but most definitely not incompatible.