Skip to content
Bobby Comet edited this page Jul 10, 2026 · 10 revisions

XKM Multi-Kernel Manager: Wiki

Overview

XKM is a PyQt6 graphical frontend for managing Linux kernels on Ubuntu-based systems. It wraps the system's apt package manager and dpkg tooling into a unified interface, letting users browse, install, remove, hold, and manage kernels across three families: XanMod, Liquorix, and standard mainline/Ubuntu kernels, without writing any terminal commands.

The application is a single Python 3 script. It uses the python-apt bindings to read the local package cache directly (no subprocess for reads), and routes every privileged write operation, installs, removals, holds, repository additions, DKMS builds, and grub updates, through a single dedicated helper binary, /usr/lib/xkm/xkm-helper, invoked via pkexec.


Dependencies

XKM requires a small set of system packages, all available in the standard Ubuntu repositories. On a typical Ubuntu desktop, most of these are already installed, but on minimal/server installs you may need to install some manually.

Required packages

  • python3: the runtime (Python 3.10 or newer)
  • python3-apt: Python bindings for the apt package library (used to read the package cache)
  • python3-pyqt6: PyQt6 bindings, used for the entire user interface
  • policykit-1: provides pkexec for privilege escalation, and the polkit authentication agent that prompts for a password (installed by default on all desktop Ubuntu flavors, but not guaranteed running on minimal window manager setups)

Install all dependencies in one command

sudo apt update && sudo apt install \
    python3 \
    python3-apt \
    python3-pyqt6 \
    policykit-1
Distro / Base Version Status Notes
Ubuntu 22.04 LTS (Jammy) Supported All packages available
Ubuntu 24.04 LTS (Noble) Supported All packages available
Ubuntu 24.10 / 25.04 Supported ,
Linux Mint 21.x Supported Based on Ubuntu 22.04
Linux Mint 22.x Supported Based on Ubuntu 24.04
Pop!_OS 22.04 / 24.04 Supported ,
Kubuntu / Xubuntu / Lubuntu 22.04 or newer Supported ,
elementary OS 7.x Should work Based on Ubuntu 22.04
Ubuntu 20.04 LTS (Focal) Not supported Ships Python 3.8, below the 3.10 minimum; the earlier libadwaita restriction no longer applies since 3.0.0 dropped GTK4/libadwaita entirely

Architecture

XKM is structured around three layers:

Data layer: the apt cache is opened via python-apt bindings inside a background thread. Packages are iterated, classified, and assembled into plain Python dicts. No direct subprocess calls are made for reading.

Model layer: classified packages are converted into plain Python row objects and held in per-family lists, one each for XanMod, Liquorix, and meta-packages, feeding the list views used on those tabs. Mainline versioned packages don't use a flat list; they live in a dict keyed by kernel version string (_mainline_groups), which drives the grouped card UI.

View layer: the UI is built with PyQt6. XanMod and Liquorix use a list view backed by a filterable model. The Mainline tab is built as a manually constructed scrollable container of collapsible cards, rebuilt on demand using a chunked scheduler (QTimer.singleShot(0, ...)) to avoid freezing the interface.

Background threads never touch the UI directly. Results are marshaled back onto the main thread through a small dispatch helper (self._dispatch.call(...)), which is the PyQt6 equivalent of what GLib.idle_add did in earlier releases.


Startup & Initialization

When the main window is created, XKM immediately kicks off a background thread to load the package cache. While that runs, a "Loading package cache…" placeholder is shown in the Mainline tab so the interface is never blank or unresponsive.

A separate background thread also runs a CPU capability check at startup (new in 3.0.0); see "XanMod Flavors" below for how this feeds into flavor suggestions.

A few seconds after the window appears, another background thread checks GitHub for a newer version of XKM. This is fire-and-forget: if it fails (no network, API down), it silently does nothing.

The periodic auto-refresh timer is also set up at this point, configured by the auto_check_hours config value. It calls _reload_kernels_async on a repeating schedule to keep the package list fresh.


The Package Cache

XKM reads the system's apt package database directly using the python-apt library rather than shelling out to apt list or similar. This is significantly faster and avoids parsing human-readable text output.

The cache is opened via a custom SilentCache subclass that suppresses the stderr noise apt normally emits during cache initialization. It does this by temporarily redirecting file descriptor 2 to /dev/null for the duration of the apt.Cache.__init__ call, then restoring the real stderr afterward. This is done at the file descriptor level (not just sys.stderr) because some of apt's output comes from native C code that bypasses Python's I/O layer entirely.

apt_pkg configuration is also set early in the startup sequence to enforce quiet mode and non-interactive behavior globally, preventing any apt subprocess from trying to prompt the user.

When the cache needs to be refreshed after an install or remove operation, cache.open(None) is called on the existing cache object rather than constructing a new one. This re-reads the database from disk and picks up any changes made by the completed operation.

Kernel availability is read live, not maintained locally

XKM does not keep its own list of "known" kernels. Every kernel it shows comes directly from what's currently in the apt cache metadata for the XanMod, Liquorix, and mainline repositories. If a specific build, for example a XanMod 6.12.74-x64v3 package, is phased out or pulled by the upstream maintainers, the next cache refresh simply won't find it anymore, and it disappears from XKM. This is expected behavior, not a bug: XKM can only display what the maintainers are currently publishing, and has no way to reach past that.


Kernel Classification

Every package in the cache is passed through a set of classification functions before any UI is built. These functions are pure string operations and run entirely in the background thread.

is_xanmod_name(name) checks for two forms of XanMod packages. Meta-packages (like linux-xanmod-x64v3 or linux-xanmod-edge) start with known prefixes. Versioned packages (like linux-image-6.18.3-x64v3-xanmod1) start with linux-image- or linux-headers- and contain xanmod anywhere in the name. Both forms must be handled because the XanMod repository ships them differently.

is_liquorix_name(name) checks for the linux-image-liquorix- and linux-headers-liquorix- prefixes, which are consistent across all Liquorix packages.

is_generic_kernel_name(name) matches standard Ubuntu/mainline packages starting with linux-image-, linux-headers-, linux-modules-, and a few variant prefixes like linux-image-unsigned- and linux-image-oem-. Any package that matches XanMod or Liquorix is excluded first, so there's no overlap.

is_mainline_meta(name) identifies metapackages like linux-generic and linux-lowlatency, packages that track a kernel flavour without embedding a version number in the name. These are treated separately from versioned packages because they behave differently: installing one will always pull in the latest kernel, they're never grouped by version number, and, unlike a XanMod kernel's bundled dependencies, XKM does not auto-select them; the user has to check the specific meta-package they want manually.

extract_kernel_version(name) pulls the numeric version string out of a package name using a compiled regex. For example, linux-image-6.14.0-37-generic yields 6.14.0-37. This version string is the grouping key for the Mainline tab's card view.

pkg_category(name) assigns a human-readable category label (Image, Headers, Modules, Modules Extra, etc.) used to sort packages within a version card.


The Three Kernel Families

XanMod kernels are built with aggressive compiler optimizations and patches aimed at desktop responsiveness and throughput. They come in CPU-specific builds (v1 through v4) and specialized variants like edge (latest upstream) and lts (long-term support). XanMod packages come from a third-party apt repository at deb.xanmod.org and require a separate signing key and source file.

Liquorix kernels focus on low-latency performance, particularly for audio work and gaming. They use a tuned scheduler configuration and are distributed via a Launchpad PPA (ppa:damentz/liquorix). Liquorix only ships two packages per release, an image and a headers package, making it simpler than XanMod in terms of package count.

Mainline kernels are the standard Ubuntu-provided kernels. Each kernel version ships multiple packages: the kernel image itself, headers for building external modules, and various linux-modules-* packages that contain drivers. Some versions also ship unsigned variants (linux-image-unsigned-) and GPU-specific module packages for NVIDIA. All of these are grouped together under a single version card in the UI, and a separate variant selector (see "The Mainline Grouped View" below) lets the user narrow that grouping down by build type.


XanMod Flavors

XanMod's CPU-level builds are called flavors. XKM extracts the flavor from a package name by scanning for specific tokens:

  • x64v4, x64v3, x64v2, x64v1: CPU optimization level embedded in the package name
  • -edge, -lts, -rt: variant suffixes
  • -v4, -v3, -v2, -v1: alternative form used in some meta-package names

The tokens are checked in order from most specific to least specific. For example, x64v3 is checked before -v3 so that a package containing x64v3 doesn't accidentally match the shorter pattern and get misclassified.

Packages that contain xanmod but none of the above tokens are labeled generic.

CPU detection (new in 3.0.0)

At startup, XKM reads the CPU's supported instruction set extensions (checking for SSE4, AVX2, and AVX-512 support) and uses that to pre-select the best matching XanMod flavor as the default filter, so most users never have to think about v1 through v4 at all. The dropdown still allows a manual override for anyone who wants a different build regardless of what their CPU supports.

The flavor filter dropdown on the XanMod tab is backed by a custom filter model rather than a plain text filter, because it needs to check two things at once: the text search query and the selected flavor. A single-property text filter can't express both conditions, so XKM's filter evaluates each row against both and only keeps the ones that pass both checks.


GPU Detection & Package Filtering

At startup, XKM runs lspci and scans the output for GPU-related lines (VGA, 3D, Display controllers). It builds a set of detected vendors: nvidia, amd, intel.

This set is then used to filter Mainline packages. Packages matching patterns like linux-modules-nvidia-*, linux-modules-amd-*, and linux-modules-intel-* are marked as GPU-relevant only if the corresponding vendor was detected. If no NVIDIA GPU is found, all linux-modules-nvidia-* packages still appear in the UI but are visually dimmed, struck through, and have their checkboxes disabled so they can't be accidentally selected and installed.

This prevents a common mistake where users install GPU driver modules for hardware they don't have, which can cause module signing issues or bloat.


The Mainline Grouped View

Unlike XanMod and Liquorix, which use a standard list view with a filterable model, the Mainline tab is built manually as a scrollable vertical container of cards. Each card represents one kernel version and contains all of that version's packages grouped by category.

This approach was chosen because a standard list view is designed for flat, homogeneous rows, and the grouped card layout, with collapsible headers, tri-state checkboxes, and per-category sub-labels, doesn't map naturally onto it. The trade-off is that the card view must be rebuilt from scratch whenever the data or search query changes.

To prevent this rebuild from freezing the UI, it uses a chunked scheduling approach. The rebuild function computes a list of "chunks", one callable per card, and then dispatches them one at a time via QTimer.singleShot(0, ...). Each scheduled chunk returns control to the Qt event loop before the next one runs, so the interface stays responsive while potentially dozens of cards are being constructed.

A _rebuild_generation counter is incremented every time a new rebuild is triggered. Each dispatched chunk checks whether its generation still matches the current one before doing any work. If a newer rebuild has been triggered (e.g. the user typed another character in the search box), all in-flight chunks from the previous rebuild abort immediately without touching the UI.

Meta-packages still require manual selection

Because meta/tracking packages (linux-generic, linux-lowlatency, and so on) are grouped onto the same card as their version's other packages, XKM does not auto-select them the way it does the dependency set for a XanMod kernel choice. If you want a specific meta-package installed alongside a kernel version, you need to check it yourself.

Variant select button (new in 3.0.0)

A new selector on the Mainline tab lets you filter cards by build type, generic, low-latency, OEM, and so on, so you can jump straight to the variant you actually run instead of scrolling through every version card looking for it.


Installing Kernels

Installation runs as a background thread that streams the output of pkexec /usr/lib/xkm/xkm-helper install <packages> line by line, appending each line to the log view in real time. This means the user can watch the download and installation progress as it happens.

Package names are validated against a strict regex ([a-z0-9][a-z0-9+.-]*) before being passed to the install command. This is a safety measure: if a malformed or unexpected package name somehow ends up in the selection (due to a bug or a corrupt cache), it's rejected before it can be passed to a privileged process. The same validation pattern is independently re-applied inside xkm-helper itself, so the GUI-side check is a convenience, not the actual security boundary; see "Privilege Escalation" below.

After installation completes, XKM automatically:

  1. Runs update-grub (via the helper's update-grub subcommand) to register the new kernel with the bootloader
  2. Checks for new directories under /usr/lib/modules/ to find the freshly installed kernel version
  3. Hands any newly found kernel versions to the helper's dkms-autoinstall subcommand to rebuild third-party kernel modules
  4. Prompts the user to reboot

If the Auto-remove after install checkbox is ticked, _auto_remove_old_kernels is also called immediately after installation finishes.


Install + Hold

The Install + Hold action is a two-step sequence: pkexec /usr/lib/xkm/xkm-helper install <packages>, followed by pkexec /usr/lib/xkm/xkm-helper hold <packages>. If the install fails, the hold step is skipped entirely. If the hold step fails after a successful install, the user is shown an error with the manual command needed to apply the hold themselves.

Held packages are excluded from apt upgrade and apt dist-upgrade automatically. They won't be upgraded, downgraded, or removed by apt unless the hold is explicitly released first. This makes Install + Hold the recommended approach when pinning a specific XanMod version for long-term stability.


Removing Kernels

Before any removal begins, XKM checks whether any of the selected packages belong to the currently running kernel. If they do, the operation is blocked with an error dialog. Removing the active kernel would make the system unbootable on next restart.

The user is then asked to choose between Remove and Purge. Remove uninstalls the packages but leaves configuration files on disk. Purge removes everything including config files. Both map to the same helper subcommand, pkexec /usr/lib/xkm/xkm-helper remove [--purge] <packages>.

XKM deliberately does not use --auto-remove with either option. While --auto-remove would clean up dependencies automatically, it can cascade to removing shared packages like linux-firmware or linux-base that other installed kernels still depend on, which is difficult to undo. Users who want dependency cleanup can run apt autoremove manually afterward.

After a successful removal, update-grub runs automatically (again via the helper) to remove the deleted kernel from the bootloader menu.


Holding & Unholding Kernels

Held packages are detected at data-load time by running dpkg --get-selections directly (a read-only call, no privilege escalation needed) and collecting all packages with a hold status. This is done once per data load in a single subprocess call rather than querying each package individually, which would be significantly slower for large package lists.

Held packages display an orange [Held] badge in the UI. Their status field reads "Held" rather than "Installed".

The Hold and Unhold buttons call pkexec /usr/lib/xkm/xkm-helper hold <packages> and pkexec /usr/lib/xkm/xkm-helper unhold <packages> respectively, then reload the package list so the badges update to reflect the new state.

Auto-remove explicitly skips held packages. A held kernel will never be touched by the auto-remove logic regardless of its age or version.


Auto-Remove

Auto-remove identifies old installed kernels and offers to remove them. The logic works as follows:

  1. Collect all installed kernel rows across all families and groups
  2. Group them by version string
  3. Sort versions newest-first using apt_pkg.version_compare
  4. Build a "keep" set containing: the currently active kernel version, and the two most recent versions
  5. Everything outside the keep set that isn't held is a candidate for removal

The user is shown a count of candidate packages and asked whether to Remove or Purge, using the same dialog and the same underlying helper subcommand as manual removal.


DKMS Handling

DKMS (Dynamic Kernel Module Support) is a framework that automatically recompiles out-of-tree kernel modules (like NVIDIA proprietary drivers) when a new kernel is installed. XKM triggers DKMS rebuilds automatically after any installation that adds new kernel module directories.

The process:

  1. Before installation, XKM snapshots the contents of /usr/lib/modules/
  2. After installation, it compares the directory listing to find newly added kernel versions
  3. It hands the full list of new kernel versions to a single pkexec /usr/lib/xkm/xkm-helper dkms-autoinstall <version...> call
  4. The autoinstall-then-verify logic now lives entirely inside that helper subcommand: for each kernel version it runs dkms autoinstall -k <version>, then queries dkms status afterward to verify each module built successfully, and reports back a combined summary
  5. Any modules in a state other than installed or built are flagged in a warning summary in that report

Kernel version strings passed to this subcommand are validated against a fixed pattern on both the GUI side and, authoritatively, inside the helper itself.

If DKMS reports failures, the user is shown a warning dialog explaining that the kernel was installed successfully but some driver modules may not work. They can choose to stay and review the log or reboot anyway.

The currently running kernel is excluded from DKMS processing; it's already built and running, so rebuilding it would be wasteful and potentially disruptive.


Bootloader Updates

update-grub is run automatically after both installs and removals, via pkexec /usr/lib/xkm/xkm-helper update-grub (this subcommand takes no arguments). While the kernel package's own post-install/post-remove scripts normally handle this, running it explicitly acts as a safety net for cases where those scripts fail silently or where the user has a non-standard GRUB configuration.

update-grub output is streamed into the current log session so the user can see what GRUB detected and registered. After removal, the grub update runs before the log session is formally closed, so all output lands in the same log file.


Repository Management

On first launch, XKM checks whether the XanMod and Liquorix repositories are present on the system. For XanMod, it looks for the source file at /etc/apt/sources.list.d/xanmod-kernel.list and the keyring at /usr/share/keyrings/xanmod-archive-keyring.gpg, and also scans all source files for the deb.xanmod.org domain as a fallback. For Liquorix, it checks for the known PPA source files and also scans sources for damentz/liquorix and liquorix.net. All of this detection is read-only and needs no privilege escalation.

If either repository is missing, a dialog appears offering to add them. This check only runs once per session; a flag is set after the first check so the dialog doesn't reappear if the user dismisses it and navigates away.

Adding XanMod now runs as a single pkexec /usr/lib/xkm/xkm-helper add-repo-xanmod call. Inside the helper, that subcommand creates the keyrings directory if needed, downloads and dearmors the GPG key, writes the source list entry, and runs an apt cache update, all in one privileged invocation so the user is only prompted for a password once.

Adding Liquorix runs as a single pkexec /usr/lib/xkm/xkm-helper add-repo-liquorix call, which runs add-apt-repository -y ppa:damentz/liquorix followed by an apt cache update. This relies on add-apt-repository being available, which it is on all standard Ubuntu desktop installations.

After either repository is added, the package list is reloaded so the newly available kernels appear immediately.


Privilege Escalation

Every privileged operation, installs, removals, holds, repository additions, DKMS builds, and grub updates, goes through one fixed entry point: pkexec /usr/lib/xkm/xkm-helper <subcommand> [args...]. This is a deliberate change from earlier versions, which called pkexec apt install, pkexec apt-mark hold, and similar commands directly for each operation.

The helper is installed to a fixed system path and registered with a single PolicyKit action, com.xanmod.kernel.manager.helper. It only recognizes a fixed set of subcommands (install, remove, hold, unhold, update-sources, add-repo-xanmod, add-repo-liquorix, update-grub, dkms-autoinstall, reboot) and refuses anything else outright. Every package name and kernel version argument is re-validated inside the helper against strict regexes, on top of the validation already performed client-side, so the helper itself is the actual trust boundary, not the GUI process. This centralizes every privileged code path behind one small, reviewable script instead of scattering raw pkexec apt ... calls throughout the application.

XKM tracks consecutive pkexec failures with a counter. If pkexec fails twice in a row, a yellow warning bar appears at the top of the window explaining that privilege escalation seems to be failing. This helps users in restricted environments (for example, systems without sudo rights, or locked-down enterprise configurations) understand why operations aren't working. The banner dismisses itself automatically if a subsequent pkexec call succeeds.


Search & Filtering

The search bar applies to all three tabs simultaneously. For XanMod, filtering checks both the text query and the selected flavor at once, using a custom filter model, for the reasons described in "XanMod Flavors" above. Liquorix uses a simpler text-only filter against the package's display string (name, version, and status tags).

Both filters are debounced: a 250ms single-shot timer is (re)started on every keystroke, and the actual filtering only runs once the user stops typing for 250ms. This prevents every intermediate keystroke from triggering a potentially expensive rebuild.

For the Mainline tab, filtering is done manually inside the card rebuild routine. Each version group and each meta-package is checked against the query string before a card is constructed for it. Groups with no matching packages are skipped entirely, so a search for 6.14 would only show cards for the 6.14.x version groups and hide everything else.


Logging

Every privileged operation starts a log session. A log file is created in ~/.config/xanmod-kernel-manager/logs/ with a filename combining the operation type and a timestamp (e.g. install_2026-07-09_14-32-01.log).

All subprocess output is written to both the on-screen log view and the file simultaneously. The file handle is kept open for the duration of the operation and closed when the session ends, ensuring output is never lost even if the application crashes mid-operation.

The on-screen log auto-scrolls to the bottom as new lines arrive.

The log view is hidden by default behind a checkable Show Details button (btn_details), which expands a collapsible log panel. XKM automatically checks this button and shows the log panel when starting any operation, so the user doesn't have to manually reveal it to see what's happening.


Configuration

XKM stores its configuration as a JSON file at ~/.config/xanmod-kernel-manager/config.json. The config is loaded and saved using module-level functions (load_config, save_config) rather than instance methods, so any part of the codebase can access it without holding a reference to a specific object.

The config tracks:

  • auto_check_hours: how often to automatically refresh the package list (default: every 6 hours)
  • auto_remove_after_install: whether to automatically run auto-remove after a successful install (default: off)
  • win_size: the last window size, restored on next launch
  • dark_mode: whether the Griffin dark theme is active

The window size is saved when the window closes. The auto-remove checkbox state is also saved at close time. All other settings take effect immediately when changed.

If the config file doesn't exist or can't be parsed, all values fall back to the defaults defined in DEFAULT_CONFIG. Partial configs are supported; any missing keys are filled in from defaults at load time.


Update Checker

A few seconds after the window opens, XKM fetches the latest release information from the GitHub API in a background thread. It compares the remote version tag against the current APP_VERSION string using a tuple comparison on the numeric version components.

If a newer version is available, a persistent notification banner appears with a button to open the release page. The banner has no timeout; it stays visible until dismissed. If no update is found, or if the network request fails for any reason, nothing happens and no error is shown.

Version strings are split on both dots and hyphens, and only numeric segments are compared. This means tags like v2.1.0 and 2.1.0 are handled identically, and pre-release suffixes that aren't purely numeric are ignored rather than causing a crash.


Version Comparison

XKM uses apt_pkg.version_compare for all kernel version sorting. This is the same comparison function apt itself uses, which correctly handles the complex versioning schemes Ubuntu kernels use (e.g. 6.14.0-37 vs 6.14.0-38, or epoch-prefixed versions).

The comparison is wrapped in a small helper that falls back to a basic Python string comparison if apt_pkg.version_compare raises an exception, which can happen with malformed version strings.

Version sorting is used in three places: ordering packages within a family (active first, then installed, then available, then newest-first within each group), ordering version cards in the Mainline tab, and in the auto-remove logic to determine which versions are "old enough" to be candidates for removal.


Security Considerations

Package name and kernel version validation, checked twice: before any package name or kernel version is passed to a privileged operation, it's checked against a regex that only allows characters valid in Debian package names (or, for kernel versions, the version-token pattern used under /usr/lib/modules). This happens once in the GUI process before the pkexec call is built, and again, independently, inside xkm-helper once the call reaches the privileged process. The helper-side check is the one that actually matters for security; the GUI-side check exists mainly to fail fast and give a clear error before ever invoking pkexec.

Single privileged entry point: rather than composing separate pkexec apt ..., pkexec apt-mark ..., and pkexec dkms ... calls throughout the codebase, every privileged action goes through one fixed, minimal helper script with a hardcoded subcommand vocabulary. This keeps the entire privileged surface reviewable in one place instead of scattered across the application.

No shell=True: all subprocess calls use list arguments rather than shell strings. Arguments built by XKM never pass through shell interpolation on their way to xkm-helper.

No direct apt library writes: all write operations (install, remove, hold) use subprocesses through the helper rather than the python-apt library's write interfaces. This is intentional: python-apt's write path is less tested for edge cases and doesn't easily integrate with pkexec-style privilege escalation.

Stderr suppression scope: the stderr redirect used during cache initialization is scoped tightly using try/finally to guarantee the real stderr is always restored, even if an import or initialization raises an exception.


Self-Tests

XKM includes a suite of unit tests that can be run without a display, a running desktop, or any apt packages installed. They cover the classification functions (is_xanmod_name, is_liquorix_name, is_generic_kernel_name), the flavor extractor, the kernel version regex, and the update checker's version comparison logic.

Run them with:

xkm --test

The tests use Python's built-in unittest module and exit with code 0 on success or 1 on failure, making them suitable for use in CI pipelines. They are pure logic tests, no PyQt6, no apt cache, no filesystem access, so they run in any environment that has Python 3 available.