Reading view

There are new articles available, click to refresh the page.

Building your own Atomic (bootc) Desktop

Bootc and associated tools provide the basis for building a personalised desktop. This article will describe the process to build your own custom installation.

Disclaimer

Building and using a custom installation is “at your own risk”. Your installation may be harder to find support for when compared with a mainstream solution.

Motivation

There has been an increasing interest in atomic distros, which offer significant benefits in terms of stability and security.

These distros apply updates as a single transaction, known as atomic upgrades, which means if an update doesn’t work as expected, the system can instantly roll back to its last stable state, saving users from potential issues. The immutable nature of the filesystem components reduces the risk of system corruption and unauthorised modifications as the core system files are read-only, making them impossible to modify.

If you are planning to spin off various instances from the same image (e.g. setting up computers for members of your family or work), atomic distros provide a reliable desktop experience where every instance of the desktop is consistent with each other, reducing discrepancies in software versions and behaviour.

Mainstream sources like Fedora and Universal Blue offer various atomic desktops with curated configurations and package selections for the average user. But what if you’re ready to take control of your desktop and customise it entirely, from packages and configurations to firewall, DNS, and update schedules?

Thanks to bootc and the associated tools, building a personalised desktop experience is no longer difficult.

What is bootc?

Using existing container building techniques, bootc allows you to build your own OS. Container images adhere to the OCI specification and utilise container tools for building and transporting your containers. Once installed on a node, the container functions as a regular OS.

The filesystem structure follows ostree specifications:

  • The /usr directory is read-only, with all changes managed by the container image.
  • The /etc directory is editable, but any changes applied in the container image will be transferred to the node unless the file was modified locally.
  • Changes to /var (including /var/home) are made during the first boot. Afterwards, /var remains untouched.

You can find the full documentation for bootc here: https://bootc-dev.github.io/bootc/

Creating your own bootc desktop

The approach described in this article uses quay.io/fedora/fedora-bootc as a base image to create a customizable container for building your personalised Fedora KDE atomic desktop.

Although tailored to KDE Plasma, most of the concepts and methodologies described here also apply to other desktop environments.

The kde-bootc repository

I published kde-bootc as a repository available in GitHub, and I will use it as a reference. It will help this explanation providing additional details, and a source to clone and experiment. You may wish to clone kde-bootc to following along.

Folder structure:

  • scripts/
  • system/
  • systemd/
  • Containerfile

scripts: Scripts to be ran from the Containerfile during building
system: Files to be copied to /usr and /etc
systemd: Systemd unit files to be copied to /usr/lib/systemd

Each file follows a specific naming convention. For instance a file /usr/lib/credstore/home.create.admin is named as usr__lib__credstore__home.create.admin

Explaining the Containerfile

The following will describe and show, step by step, the contents of the example Containerfile created.

Image base

The fedora-bootc project is part of the Cloud Native Computing Foundation (CNCF) Sandbox projects and  generates reference “base images” of bootable containers designed for use with the bootc project.

In this example, I’m using quay.io/fedora/fedora-bootc as the base image. The containerfile starts off with:

FROM quay.io/fedora/fedora-bootc

Setup filesystem

If you plan to install software on day 2, i.e. after the kde-bootc installation is complete, you may need to link /opt to /var/opt. Otherwise, /opt will remain an immutable directory that you can only populate from the container build.

RUN rmdir /opt
RUN ln -s -T /var/opt /opt

In some cases, for successful package installation, the /var/roothome directory must exist. If this folder is missing, the container build may fail. It is advisable to create this directory before installing the packages.

RUN mkdir /var/roothome

Prepare packages

To simplify the installation, and to have a record of installed and removed packages for future reference, I found it useful to keep them as a resource under /usr/share.

  • All additional packages to be installed on top of fedora-bootc and the KDE environment are documented in packages-added.

COPY --chmod=0644 ./system/usr__local__share__kde-bootc__packages-added /usr/local/share/kde-bootc/packages-added

  • Packages to be removed from fedora-bootc and the KDE environment are documented in packages-removed.

COPY --chmod=0644 ./system/usr__local__share__kde-bootc__packages-removed /usr/local/share/kde-bootc/packages-removed

  • For convenience, the packages included in the base fedora-bootc are documented in packages-fedora-bootc.

RUN jq -r .packages[] /usr/share/rpm-ostree/treefile.json > /usr/local/share/kde-bootc/packages-fedora-bootc

Install repositories

This section handles adding extra repositories needed before installing packages.

In this example, I’m adding Tailscale, but the same principle applies to any other source you may add to your repositories.

Adding repositories uses the config-manager verb, available as a DNF5 plugin. This plugin is not pre-installed by default in fedora-bootc, so it will need to be installed beforehand.

RUN dnf -y install dnf5-plugins
RUN dnf config-manager addrepo --from-repofile=https://pkgs.tailscale.com/stable/fedora/tailscale.repo

Install packages

For clarity and task separation, I divided the installation into two steps:

Installation of environment and groups.

RUN dnf -y install @kde-desktop-environment

And the installation of all other individual packages. The script will select all lines not starting with # passing them as arguments to dnf -y install. The --allowerasing option is necessary for cases like installing vim-default-editor, which would conflict with nano-default-editor, removing the latter first.

RUN grep -vE '^#' /usr/local/share/kde-bootc/packages-added | xargs dnf -y install –-allowerasing

PACKAGES-ADDED
# LibreOffice
libreoffice
libreoffice-help-en
# Utilities
vim-default-editor
git
....

Remove packages

Some of the standard packages included in @kde-desktop-environment don’t behave well and sometimes conflict with an immutable desktop, so we will remove them.

This is also an opportunity to remove software you may never use, saving resources and storage.

RUN grep -vE '^#' /usr/local/share/kde-bootc/packages-removed | xargs dnf -y remove
RUN dnf -y autoremove
RUN dnf clean all

The criteria used to remove some packages is listed below:

Conflict with bootc and its immutable nature.
plasma-discover-offline-updates
plasma-discover-packagekit
PackageKit-command-not-found

Bring unwanted dependencies.
tracker
tracker-miners
mariadb-server-utils
abrt
at
dnf-data

Deprecated services.
iptables-services
iptables-utils

Packages that are resource-heavy, or bring unnecessary services.
rsyslog
dracut-config-rescue

Configuration

This section will copy all necessary configuration files to /usr and /etc. As recommended by the bootc project, prioritise using /usr and use /etc as a fallback if needed.

Bash scripts that will be used by systemd services are stored in /usr/local/bin:

COPY --chmod=0755 ./system/usr__local__bin/* /usr/local/bin/

Custom configuration for new users’ home directories will be added to /etc/skel/. As an example you can customise bash.

COPY --chmod=0644 ./system/etc__skel__kde-bootc /etc/skel/.bashrc.d/kde-bootc

If you’re building your container image on GitHub and keeping it private, you’ll need to create a GITHUB_TOKEN to download the image. Further information is available at GitHub container registry.

COPY --chmod=0600 ./system/usr__lib__ostree__auth.json /usr/lib/ostree/auth.json

Users

I opted for systemd-homed users because they are better suited than regular users for immutable desktops, preventing potential drift in case of local modifications in /etc/passwd. Additionally, each user home benefits from LUKS encrypted volume.

The process begins when firstboot-setup runs, triggered by firstboot-setup.service during boot. It executes homectl firstboot, which checks if any regular home areas exist. If none are found, it searches for service credentials starting with home.create. to create users at boot.

The parameter below imports service credentials into the systemd service:

FIRSTBOOT-SETUP.SERVICE
...
ImportCredential=home.create.*

For more details, refer to the homectl and systemd.exec manual pages.

The homed identity file (usr__lib__credstore__home.create.admin) sets the user’s parameters, including username, real name, storage type, etc.

Common systemd-homed parameters:

  • userName: A single word for your username and home directory. In this example, it is admin.
  • realName: Full name for the user
  • diskSize: The size of the LUKS storage volume, calculated in bytes. For instance, 1 GB equals 1024x1024x1024 bytes, which is 1073741824 bytes.
  • rebalanceWeight: Relevant only when multiple user accounts share the available storage. If diskSize is defined, this parameter can be set to false.
  • uid/gid: User and Group ID. The default range for regular users is 1000-6000, and for systemd-homed users, it is 60001-60513. However, you can assign uid/gid for systemd-homed users from both ranges.
  • memberOf: The groups the user belongs to. As a power user, it should be part of the wheel group.
  • hashedPassword: This is the hashed version of the password stored under secret. Setting up an initial password allows homectl firstboot to create the user without prompting. This password should be changed afterwards (homectl passwd admin). The hash password can be created using the mkpasswd utility.

We are storing the identity file in one of the directories where systemd-homed expects to find credentials.

COPY --chmod=0644 ./system/usr__lib__credstore__home.create.admin /usr/lib/credstore/home.create.admin

For more information on user records, visit: https://systemd.io/USER_RECORD/

This section also creates a temporary password for the root user. As I will explain later, having a root user as an alternative login is important.

echo "Temp#SsaP" | passwd root -s

Subuid and Subgid:

Another key parameter to set up is the range for /etc/subuid and /etc/subgid for the admin user. This range is necessary for running rootless containers since each uid inside the container will be mapped to a uid outside the container within this range. Systemd-homed predefines ranges for uid/gid.

The available range is 524288…1879048191. Choosing 1000001 makes it easy to identify the service running in the container. For instance, if the container is running Apache with uid=48, the volume or folder bound to it will have uid=1000048.

echo "admin:1000001:65536">/etc/subuid
echo "admin:1000001:65536">/etc/subgid

For more information on available ranges, visit: https://systemd.io/UIDS-GIDS/

The next step will set up authselect to enable authenticating the admin user on the login page. To achieve this, we need to enable the features with-systemd-homed and with-fingerprint (if your computer has a fingerprint reader) for the local profile.

authselect enable-feature with-systemd-homed
authselect enable-feature with-fingerprint

Systemd services

I decided to install at least two services; One to complete the configuration during machine boot, to run commands that require systemd (firstboot-setup.service), and the other one to automate updates (bootc-fetch.service).

We are enabling, by default, the first systemd service firstboot-setup:

COPY --chmod=0644 ./systemd/usr__lib__systemd__system__firstboot-setup.service /usr/lib/systemd/system/firstboot-setup.service
RUN systemctl enable firstboot-setup.service

USR__LIB__SYSTEMD__SYSTEM__FIRTBOOT-SETUP.SERVICE
[Unit]
Description=Setup USERS and /VAR at boot
After=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/firstboot-setup
ImportCredential=home.create.*
[Install]
WantedBy=multi-user.target

And it runs the script below:

FIRSTBOOT-SETUP
# Setup hostname
HOST_NAME=kde-bootc
hostnamectl hostname $HOST_NAME
# Create user(s)
homectl firstboot
# Setup firewall to allow kdeconnect to functions
firewall-cmd --set-default-zone=public
firewall-cmd --add-service=kdeconnect --permanent

We are triggering bootc-fetch daily by a timer as a second systemd service:

COPY --chmod=0644 ./systemd/usr__lib__systemd__system__ bootc-fetch.service /usr/lib/systemd/system/bootc-fetch.service
COPY --chmod=0644 ./systemd/usr__lib__systemd__system__bootc-fetch.timer /usr/lib/systemd/system/bootc-fetch.timer


USR__LIB__SYSTEMD__SYSTEM__BOOTC-FETCH.TIMER
[Unit]
Description=Fetch bootc image daily
[Timer]
OnCalendar=*-*-* 12:30:00
Persistent=true
[Install]
WantedBy=timers.target

USR__LIB__SYSTEMD__SYSTEM__BOOTC-FETCH.SERVICE
[Unit]
Description=Fetch bootc image
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/bootc update --quiet

This service replaces bootc-fetch-apply-updates, which would download and apply updates as soon as they are available. This approach is problematic because it causes your computer to shut down without warning, so it is better to disable by masking the timer:

RUN systemctl mask bootc-fetch-apply-updates.timer

How to create an ISO?

The instructions that follow will build the container locally. You need to do it as root so bootc-image-builder can use the image to make the ISO.

cd /path-to-your-repo
sudo podman build -t kde-bootc .

Then, outside the repository on a different directory, create a folder named output for the ISO image. And also you need to create the configuration file config.toml to feed the installer.

CONFIG.TOML
[customizations.installer.kickstart]
contents = "graphical"

[customizations.installer.modules]
disable = [
"org.fedoraproject.Anaconda.Modules.Users"
]

It instructs the installer to use the graphical interface and disable the module for user creation. We do not need to set up a user during installation, as this is already being taken care of.

Within the directory where ./output/ and ./config.toml exists, run bootc-image-builder utility which is available as a container. It must be run as root.

sudo podman run --rm -it --privileged --pull=newer \
--security-opt label=type:unconfined_t \
-v ./output:/output \
-v /var/lib/containers/storage:/var/lib/containers/storage \
-v ./config.toml:/config.toml:ro \
quay.io/centos-bootc/bootc-image-builder:latest \
--type iso \
--chown 1000:1000 \
localhost/kde-bootc

If everything goes well, the ISO image will be available in the ./output directory. You can use Fedora Media Writer to create a USB and put your images on a portable drive such as flash disk.

At the time of writing, the installer uses Anaconda and functions like any other Fedora flavor installation.

For more information on bootc-image-builder, visit: https://github.com/osbuild/bootc-image-builder

Post installation

The first step is to restore the SELinux context for the systemd-homed home directory. Without this, you may not be able to log in as admin. To complete this task, log in as root, activate admin home area, and then run restorecon to restore the SELinux context.

homectl activate admin
<< enter password for admin
restorecon -R /home/admin
homectl deactivate admin

At this point, you can change the passwords for root and admin:

passwd root
homectl passwd admin

After completing these steps, you can log out from root and log in to admin.

If your computer has a fingerprint reader, setting it up is not possible from Plasma’s user settings, as systemd-homed is not yet recognised by the desktop. However, you can manually enroll your fingerprint by running fprintd-enroll and placing your finger on the reader as you normally would.

sudo fprintd-enroll admin

Same as above, you cannot set up the avatar from Plasma’s user settings, but you can copy an available avatar (PNG file) from Plasma’s avatar directory to the account service’s directory. The file name needs to be the same as the username:

/usr/share/plasma/avatars/<avatar.png> -> /var/lib/AccountsService/icons/admin

Finally, enable the service to keep your system updated and any other desired services:

systemctl enable --now bootc-fetch.timer
systemctl enable --now tailscaled

Troubleshooting

Drifts on /etc

Please note that a configuration file in /etc drifts when it is modified locally. Consequently, bootc will no longer manage this file, and new releases won’t be transferred to your installation. While this might be desired in some cases, it can also lead to issues.

For instance, if /etc/passwd is locally modified, uid or gid allocations for services may not get updated, resulting in service failures.

Use ostree admin config-diff to list the files in your local /etc that are no longer managed by bootc, because they are modified or added.

If a particular configuration file needs to be managed by bootc, you can revert it by copying the version created by the container build from /usr/etc to /etc.

Adding packages after first installation

The /var directory is populated in the container image and transferred to your OS during initial installation. Subsequent updates to the container image will not affect /var. This is the expected behavior of bootc and generally works fine. However, some RPM packages execute scriptlets after installation, resulting in changes to /var that will not be transferred to your OS.

Instead of trying to identify and update the missing bits in /var, I found it easier to overlay /usr (bootc usr-overlay) and reinstall the packages (dnf reinstall ..) after updating and rebooting bootc.

References

GitHub – kde-bootc: https://github.com/sigulete/kde-bootc
GitHub – bootc: https://github.com/bootc-dev/bootc
GitLab – fedora-bootc: https://gitlab.com/fedora/bootc

❌