
Figure 1: Screenshot of my LFS VM's GNOME session as of 28 July 2026.
I first installed LFS 12.4 systemd edition to a virtual machine on 9 February 2026. Since then, I have upgraded the system to the development systemd branch, and kept the system up to date. It has been a challenging, yet informative journey.
My motivations for setting us this VM include:
Curiosity, as I have dozens of free operating system (OS) VMs that I maintain just as a curiosity, so setting up a LFS VM keeps within this.
A desire to prove to myself that I can actually run and maintain LFS long term and get it to the point of being a viable daily driver.
From my NixOS host machine, I have written — with the help of artificial intelligence (AI) — several shell functions that are imported into my LFS VM and provide basic package management functionality. These functions are part of both my host's and VM's shell profile. These functions can be found in my NixOS configuration user shell profile.
Table 1: Shell functions used for package management within the LFS VM. | Function name | Defined in | Syntax | Description |
| Guest | Host |
autobuild | lfs_autobuild | lfs-autobuild.sh | autobuild PACKAGE(S) [OPTION(S)] | Default: build and install the specified package(s), if and only if the latest version of the package is not already installed. LFS/Beyond LFS (BLFS) instructions are used to build most packages. Although, some packages are built using custom build scripts defined in ~/lfs_packaging. Options: --dry-run: show what actions would be executed to build and install the package. --strip: run stripping commands after build. --no-upstream: disable upstream version searching. --include-config: include configuration commands in the LFS/BLFS book entry. --rm-libs: remove old library versions after build (disabled by default). --lfs: search only in the LFS book. --blfs: search only in the BLFS book. --lfs-book : specify LFS book (e.g., development, systemd, stable, or full URL). --blfs-book : specify BLFS book (e.g., systemd, development, stable, or full URL). --skip-tests: skip test commands (make check/test, etc.). --ignore-test-failures: ignore test failures by appending '|| true' to test commands. -f/--force: force rebuild and installation even when latest version is already installed. -h/--help: show help message. |
autoremove | lfs_autoremove | 21-lfs.sh | autoremove PACKAGE(S) [OPTION(S)] | Default: remove the specified package(s), if and only if no other packages have libraries that depend on the package(s). Options: --dry-run: show what actions would be executed to remove the package. -f/--force: force removal, without regard for library dependencies. |
cleanup_old_libraries | cleanup_old_libraries | 21-lfs.sh | cleanup_old_libraries | Remove old unused libraries. As for used old used libraries, rebuild packages that depend on the library and then remove it. |
cleanup_old_doc_dirs | cleanup_old_doc_dirs | 21-lfs.sh | cleanup_old_doc_dirs | Remove old unused documentation directories. |
cleanup_share_dirs | N/A | lfs-vm-bootstrap.sh | cleanup_share_dirs [OPTION(S)] | Removes old unused /usr/share subdirectories. Options:
--dry-run shows what would be done without actually executing those actions. |
ls_old_libs | N/A | 21-lfs.sh | ls_old_libs [OPTION(S)] | List old installed versions of libraries. Options:
-d option it list files that depend on listed files. |
update | lfs_update | 21-lfs.sh | update [OPTION(s)] | Update packages. Options: --dry-run: Show what would be updated without downloading/building. --no-upstream: Check only LFS/BLFS book versions (disable upstream tracking). -h/--help: Show help message. |
updates | lfs_updates | lfs-updates.sh | updates | Print table that list available updates (marked with [UPDATE]), as well as packages with missing inventories (marked with [FILES MISSING]) and packages with versioning failures (marked with [FAILED]). |
The desktop configuration files in ~/lfs_apps generate plots of boot times and cycle through wallpapers.
Plotting files:
plotbts.sh and plotbts.desktop — boot time histogram with linear scaling on both axes; outliers excluded; not including more recent boots.
plotbtsa.sh and plotbtsa.desktop — boot time histogram with logarithmic scaling on both axes; outliers included; including more recent boots.
plotbtso.sh and plotbtso.desktop — boot time histogram with linear scaling on both axes; outliers included; not including more recent boots.
These rely on ~/lfs_gnuplot Gnuplot code.
Wallpaper cycling files:
cycle-wallpaper.sh and cycle-wallpaper.desktop — moves us forward through the wallpapers in ~/wallpapers. Keyboard shortcut: Win+W.
cycle-wallpaper-previous.sh and cycle-wallpaper-previous.desktop — moves us backward through the wallpapers in ~/wallpapers. Keyboard shortcut: Win+Z.
cycle-wallpaper-shuffle.sh and cycle-wallpaper-shuffle.desktop — moves us randomly through the wallpapers in ~/wallpapers. Keyboard shortcut: Win+S.
specify-wallpaper.sh and specify-wallpaper.desktop — specify the wallpaper (by number) that you want to be set as you desktop background. Keyboard shortcut: Win+N.
Some of the packages in ~/lfs_packaging also have build instructions in LFS and BLFS books — such as Linux PAM, Vim, rustc and packages within the xorg-apps and xorg-libs metapackages. I provide these custom packages sometimes to overcome build errors that the book-extraction function cause and other times to more robustly ensure I have the latest version of these packages at all times. Other packages are provided in this repository because LFS and BLFS do not provide them; other books in the LFS such as SLFS do provide some of these packages, but some are unique to this repository (e.g. GNU Octave and R are).
I have also customized Fastfetch/HyFetch output so that it accurately prints the number of packages I have installed. The Fastfetch configuration file used is located in ~/lfs_dotfiles/config.jsonc. The HyFetch configuration files are also in ~/lfs_dotfiles/hyfetch.json.
In the screenshot above, 813 [ 552, 157, 1, 74, 29] means that 813 packages are installed in total. Of them 552 are LFS or BLFS book packages installed via autobuild and its extracting build commands and source URLs from the books' webpages. A further 157 were installed via custom build scripts in ~/lfs_packaging. 1 Julia package was installed; this package is Julia itself which was installed via juliaup (the compilation process of Julia is incredibly complex and even requires its own custom build of LLVM). 74 Python packages were installed via pip. 29 R packages were installed.
383, 212 refers to number of package inventory git repository commits I have published. 383 refers to /var/lib/book-packages and 212 refers to /var/lib/custom-packages. I include it in Fastfetch output as a way of tracking the versions of custom packages.
My shell profile is defined in ~/lfs-scripts. Some scripts called for by GNOME and KDE Plasma Executor/Command Output commands are in this repository, too.
GNOME was the first desktop I installed. Its packages are kept at the latest upstream version, not merely the latest version in the BLFS book. Dash to Dock is enabled and installed, as is WeatherPanel, Extension List, Kiwimenu, Show Desktop Button and Super Into Apps. Executor is another extension I use; I've actually created my own fork with more features.
Table 2: GNOME themes. | Cursor | Legacy applications | Icons | Shell |
| WhiteSur-cursors | WhiteSur-Dark | WhiteSur-dark | TST - Semi Transparent |
Has ~/lfs_packaging package called executor. It can be installed via more standard ways, too.
The base Executor extension provides up to three widgets in the GNOME panel on the left, centre and right of the panel. In these widgets is displayed the output of specified commands. The interval at which the command is re-run can also be specified. The Executor fork I maintain provides the following additional features:
Tooltips — which can have two components. They are, in order: (1) static text and (2) command output.
Command execution when the panel widget is clicked, with separate commands for left-, middle-, and right-click actions.
Table 3: my Executor extension settings. | Field | Left widget | Centre widget | Right widget |
| Index in widget | 3 | 2 | 2 |
| Output command | ~/lfs-scripts/left_widget_command.sh — displays the boot time of the system. In my set up, it is used to generate output for the left widget. Runs every 60s. | ~/lfs-scripts/count-wallpapers.sh — displays the number of the currently shown wallpaper / the total number of wallpapers in ~/wallpapers. Runs every second. | ~/lfs-scripts/updates_no.sh — checks for updates using the updates command in the shell profile. It displays $in_progress $mod_time $no_updates $no_missing_total $no_failed where $in_progress is replaced with nothing if the updates command is not running, and ${percent}% otherwise, where $percent is an approximation of how far through the running of updates we are. $mod_time is replaced with the time the updates command last stopped running. $no_updates is replaced with the number of available package updates. $no_missing_total is replaced with the number of packages with missing inventories. $no_failed is replaced with the number of package versioning failures. updates runs every 5 minutes - the average duration of updates runs. updates_no.sh is run every second. |
| Left-click command | ~/lfs_apps/cycle-wallpaper-previous.sh — show previous wallpaper. | gnome-terminal -- zsh -ic ~/lfs-scripts/list-wallpapers.sh — displays the list of wallpapers in `~/wallpapers` with the currently shown wallpaper highlighted and centred. | gnome-terminal -- zsh -ic "update; exec zsh" — updates the system's packages, including those installed via book instructions, custom packages and pip-managed packages. |
| Middle-click command | ~/lfs_apps/cycle-wallpaper-shuffle.sh — show a randomly-selected wallpaper. | gnome-extensions prefs executor@raujonas.github.io — opens the settings dialog for Executor. | gnome-terminal -- zsh -ic "source ~/lfs-scripts/updates_no_func.sh; silent_updates" — runs updates to update the output shown in the widget. |
| Right-click command | ~/lfs_apps/cycle-wallpaper.sh — show next wallpaper. | ~/lfs-scripts/open-wallpaper.sh — opens the displayed wallpaper in Eye of GNOME. | gnome-terminal -- zsh -ic "tail -f ~/updates.log" — opens a terminal and follows the output of the updates command being used to generate the widget content. |
| Tooltip text | Left click: previous wallpaper (Win+Z). Middle click: shuffle wallpaper (Win+S). Right click: next wallpaper (Win+W). Win+N: show wallpaper whose number you will be asked to specify. | Left click: list wallpapers with displayed wallpaper centred and highlighted. Middle click: open Executor settings (Win+E). Right click: open wallpaper in EOG. | Left click: run `update`. Middle click: update notifications. Right click: show log of last update check. |
| Tooltip command | ~/lfs-scripts/left_widget_tooltip_command.sh — generates a line describing the version of LFS/BLFS installed, along with the number of packages installed via different means, and package inventory commit numbers in a similar format as in the Fastfetch output. | ~/lfs-scripts/centre_widget_tooltip_command_wrap.sh — lists selected wallpaper (indicated with `>`) and the 25 wallpapers before and after this one. If there are not 25 wallpapers before the current one, it will show some of the last wallpapers in the collection before the wallpaper numbered 1 to ensure that 51 wallpapers are listed (including the one set as the desktop background). If there are not 25 wallpapers after the current one, it will show some of the first wallpapers in the collection after the final one in the list to ensure that 51 wallpapers are listed in total. | ~/lfs-scripts/update-table.sh — generates a more compact table of packages with updates, missing inventories and versioning failures. |
Table 4: Example tooltip contents. | Left | Centre | Right |
| | |
BLFS did not provide a gnome-browser-connector package, which is required for installing GNOME extensions within one's browser. Manually compiling and installing it was fairly easy, however. That being said, whenever I tried to install an extension using it, I noticed that the extension was not successfully installed despite there being a folder in ~/.local/share/gnome-shell/extensions for it. As this folder would be completely empty. Why? Well, running strace on GNOME shell revealed the problem was actually that the gnome-browser-connector was running unzip commands that assumed that Info-ZIP's unzip command was installed, not the bsdunzip variety provided by libarchive (which is the only one provided by BLFS or LFS).
I tried compiling Info-ZIP's unzip, such as by following some old BLFS instructions but this failed as Info-ZIP's unzip has not been updated since ~2009 and requires multiple intricate patches to get it to compile. The consolidated patch provided by BLFS was not even sufficient, even after I located the patch (the link provided in the book entry shared is actually dead, so I had to find a link to the patch elsewhere by Googling).
Luckily, ChatGPT provided a script version of unzip that would run bsdtar in the background and could take all the arguments that gnome-browser-connector provided it. I have since included this script in my custom package for libarchive.
KDE Plasma was the second desktop I installed. Panel Spacer Extended extension is installed, as is the Command Output Plasma widget.