desktops: tier system, install/remove correctness, and a pile of small fixes#824
desktops: tier system, install/remove correctness, and a pile of small fixes#824igorpecovnik merged 42 commits intomainfrom
Conversation
The desktop refactor (#815) replaced module_update_skel's simple two-line copy-and-chown with a per-file find/cp/chown loop. The new loop is internally correct in isolation, but the old recursive chown was also serving as a safety net for root-owned files that *other* package postinst scripts leak into the user's home on first install (caja, nemo, gnome-keyring and others all do this). Without that safety net, MATE on first login pops up "The path for the directory containing Caja settings needs read and write permissions" and Nemo (with the XFCE bundle) hits the same class of bug. Both are caused by ~/.config/{caja,nemo} being created root-owned by the respective postinst, then never reclaimed. Restore the previous pattern: cp -r --update=none /etc/skel/. "$home/" chown -R "$uid:$gid" "$home/" This is a strict revert of just the install branch of module_update_skel; nothing else from #815 is reverted.
The remove path was reduced to
pkg_remove "$DESKTOP_DM"
pkg_remove "$DESKTOP_PRIMARY_PKG"
with a comment claiming "autopurge handles dependencies". It does
not. pkg_install names every entry of $DESKTOP_PACKAGES on the apt
command line, so apt marks all 30+ packages (xfce4-goodies, blueman,
pavucontrol, gnome-disk-utility, terminator, numix-gtk-theme,
printer-driver-all, ...) manually-installed. apt-get autopurge of one
top-level package therefore reclaims none of its siblings, and the
vast majority of the desktop install survives the uninstall.
The original design (#799) already solved this: pkg_install does an
'apt-get -s install' dry-run, parses the "following NEW packages
will be installed" block, and appends it to ACTUALLY_INSTALLED. The
old desktop install persisted that array to
/etc/armbian/desktop/<de>.packages, and the old remove read it back
and passed it to pkg_remove. The #815 refactor kept the in-memory
tracking inside pkg_install but dropped both the persistence and
the file-based remove.
Restore the persistence:
- Install resets ACTUALLY_INSTALLED before pkg_install, then writes
/etc/armbian/desktop/<de>.packages from the resulting array. Skip
the write when the array is empty so a re-install of an already-
installed DE does not clobber the existing manifest with an empty
file (which would make uninstall a no-op).
- Remove reads the manifest and feeds the whole list to pkg_remove
(apt-get autopurge), then deletes the manifest. For legacy
installs that predate the manifest, fall back to walking
$DESKTOP_PACKAGES through dpkg-query and removing whatever is
currently installed. Less precise (can keep pre-existing
packages) but it's the best we can do without the manifest.
This preserves system packages that already existed before the
desktop install (so we never yank shared deps), and removes
everything the install actually added.
After verifying which packages actually land on disk via the new
install manifest, three categories of cruft stood out:
- packages we install only to immediately uninstall again
(spice-vdagent — VM only; gdebi — drags ~50MB of cpp/gcc-13
toolchain just to GUI-install .debs; gnome-system-monitor —
duplicates xfce4-taskmanager which xfce4-goodies provides).
These are dropped from the install list outright.
- xfce4-goodies plugins that are either laptop-only
(xfce4-battery-plugin), niche/dead (xfce4-mailwatch-plugin,
xfce4-verve-plugin, xfce4-smartbookmark-plugin, xfce4-timer-plugin,
xfce4-wavelan-plugin), need upstream API config to function
(xfce4-weather-plugin), or redundant on SBCs where the kernel
manages cpufreq (xfce4-cpufreq-plugin). Added to packages_uninstall.
- Ubuntu/snap cruft pulled in transitively: apport +
python3-apport + python3-problem-report (Canonical crash
reporter — Armbian does not consume those reports), and
libsnapd-glib-2-1 (snap support — Armbian does not ship snapd).
Plus the spellcheck stack (dictionaries-common, hunspell-en-us,
emacsen-common) which is rarely useful on a fresh SBC desktop.
All added to packages_uninstall.
Remove ordering note: packages_uninstall runs *after* the manifest
is saved, so anything in this list still ends up in
/etc/armbian/desktop/xfce.packages even though it's gone from disk
by the time install finishes. That's harmless on uninstall (apt
just skips the not-installed names) but the manifest is misleading;
fixing that ordering is a separate concern.
Pulls the Armbian-branded boot splash in for every DE install via common.yaml so we don't have to repeat it in each per-DE file.
The previous commit added the xfce4-goodies plugins (battery, mailwatch, weather, ...) plus a few unrelated entries to packages_uninstall, expecting apt to drop just those. On any system with apt's autoremove behavior enabled (which is the Ubuntu noble default), removing one xfce4-goodies dependency yanks the xfce4-goodies meta package itself, and the resulting cascade rips out half the desktop: cups* cups-core-drivers* cups-daemon* dictionaries-common* emacsen-common* evince* hunspell-en-us* libenchant-2-2* libevview3-3t64* libgspell-1-2* libsnapd-glib-2-1* mousepad* pulseaudio* pulseaudio-module-bluetooth* python3-apport* python3-problem-report* ristretto* xfburn* xfce4-battery-plugin* xfce4-clipman* xfce4-clipman-plugin* xfce4-cpufreq-plugin* xfce4-dict* xfce4-goodies* xfce4-mailwatch-plugin* xfce4-smartbookmark-plugin* xfce4-terminal* xfce4-timer-plugin* xfce4-verve-plugin* xfce4-wavelan-plugin* xfce4-weather-plugin* xserver-xorg* Drop every entry that is a Depends of xfce4-goodies. The spellcheck stack (dictionaries-common, hunspell-en-us, emacsen-common) and gnome-system-monitor are also gone since they were transitively pulled by the same goodies graph. Keep only the entries that are orthogonal to the xfce dep tree: apport + python3-apport + python3-problem-report (Ubuntu crash reporter — Armbian doesn't consume those reports) and libsnapd-glib-2-1 (snap support, no snapd in Armbian). Add a comment at the top of packages_uninstall warning future edits not to put xfce4-goodies depends here.
The Armbian Imager AppImage is Qt6/RHI based and dlopen()s libGLESv2.so.2 at startup. On a system where no desktop has yet pulled in the GL stack — or after an autopurge yanks it as an orphan — running the imager fails with error while loading shared libraries: libGLESv2.so.2: cannot open shared object file Make module_appimage install the GL/EGL/GLES runtime explicitly so the imager can launch even when the GL stack is not pulled transitively by some desktop dep tree. The packages are tiny, shared with every DE, and idempotent if already installed.
The noble and plucky packages_remove blocks contained six entries
copied from a long-dead explicit package list:
qalculate-gtk hplip indicator-printers libfontembed1 policykit-1
printer-driver-all
Five of those six are no-ops: none of them is in xfce.yaml's
packages: list, in common.yaml's packages: list, or in any
release-specific packages: block, so packages_remove (which only
filters the install list) has nothing to filter and they were just
confusing future readers. The bookworm packages_remove had a
similar stale stub (libfontembed1, update-manager{,-core}).
The sixth, printer-driver-all, IS in xfce.yaml packages: line 34,
so packages_remove was actively preventing it from being installed
on noble and plucky. The result is that on Ubuntu xfce installs
the user gets cups, cups-daemon, system-config-printer — but no
actual printer drivers — so the printer-add wizard cannot
configure anything. Printing was effectively non-functional out
of the box on noble/plucky xfce.
Drop the dead entries from bookworm/noble/plucky packages_remove
and drop printer-driver-all from noble/plucky specifically. Keep
the trixie pulseaudio->pipewire entries (which are the only
legitimate packages_remove in this file) and keep plucky's
pavumeter entry (the package was dropped from the plucky archive).
The 'read' fallback path for dialog_menu was rendering --item-help triplets as 1. CINM01 - Install Cinnamon - Install the Cinnamon desktop environment 2. GNME01 - Install GNOME - Install the GNOME desktop environment ... The third segment is the dialog --item-help text, which is meant for the F1/hover popup in real dialog/whiptail. In read mode there is no popup so we were just printing it inline, which doubles up with the description and produces a noisy three-segment line on every menu. Drop the third segment in this branch and just print '<tag> - <description>'. Real dialog/whiptail paths are unaffected.
skel/.local/share/applications/system-config-printer.desktop is a two-line hider stub: [Desktop Entry] Hidden=true When module_update_skel install copies the skel tree into every user's ~/.local/share/applications/, the XDG menu spec applies the Hidden=true override on top of the system-installed /usr/share/applications/system-config-printer.desktop, hiding the printer-add wizard from every desktop menu. That is exactly the symptom reported on a fresh xfce install: "why don't I have a Printers icon in the menu?". The package was installed (system-config-printer is in xfce.yaml), the binary was on PATH, the system .desktop file was valid — but the per-user hider stub was actively suppressing the menu entry. The stub appears to be collateral from a long-ago "hide every xfce-vendor entry" sweep. With printer-driver-all back in the install list and printing now expected to work out of the box, this stub has to go.
The branding/icons/ directory was carrying twelve icon-menu-*.png swatches, an icon-armbian-config-penguin.png variant, and scrcpy.svg — none of which are referenced anywhere in the codebase. The branding install in module_desktop_branding.sh copies the entire directory to /usr/share/icons/armbian/ but only armbian-config.png is actually consumed (by share/applications/configng.desktop). The rest are dead weight shipped in the .deb for no reason. scrcpy.svg in particular was clearly misfiled — it's an icon for the Android screen-mirroring tool, completely unrelated to desktop branding. Also drop skel/.local/share/applications/gdebi.desktop. It was a hider stub for the gdebi GTK frontend, which we already removed from xfce.yaml's package list — there's nothing left to hide. Single icon kept: armbian-config.png. Verified via grep across the entire repo that nothing else references the deleted files.
armbian.xml is the GNOME wallpaper picker manifest installed at
/usr/share/gnome-background-properties/armbian.xml. It points
gnome-control-center / nautilus at the wallpapers in
/usr/share/backgrounds/armbian/. Three of its <filename> entries
referenced files that did not exist on disk:
- armbian-4k-green-retro.jpg (real file: armbian-4k-retro-green.jpg)
- armbian-4k-purple-penguine.jpg (real file: armbian-4k-purple-penguin.jpg)
- armbian-full-under-construction-3840-2160.jpg
(real file: armbian-full-undeer-construction-3840-2160.jpg)
The first two looked like word-order/typo bugs in the XML; the
third is a typo in the filename on disk ("undeer") that's old
enough that fixing the disk side would just rename a file with no
upstream reference. Fix the XML to match the disk in all three
cases. Also normalize the indentation on the purple-penguin block,
which had stray leading spaces.
Verified: every <filename> entry now resolves to an existing file
in branding/wallpapers/.
Nine wallpaper files in branding/wallpapers/ are not referenced from armbian.xml (the GNOME wallpaper picker manifest), any postinst dconf override, the slick-greeter config, the GNOME postinst dconf settings, or any skel file. They were just being copied to /usr/share/backgrounds/armbian/ with no path that exposes them to the user: Black-Red--Fractal-Abstract-Armbian-Centered_3840x2160.jpg Black-Red--Fractal-Abstract-Armbian-Right_3840x2160.jpg Black-Red-Abstract-Wave-Circle-Armbian-Centered_3840x2160.jpg Black-and-Red-Striped-Arrow-Abstract-Armbian-Centered_3840x2160.jpg Black-and-Red-Striped-Arrow-Abstract_Armbian_Right_3840x2160.jpg armbian-1080p-evolution.jpg armbian-1080p-love.jpg armbian-4k-penguin-SBC.jpg armbian-4k-penguin-minimalistic.jpg The Black-Red set is from a different visual style than the rest of the 4k library and was never wired into armbian.xml. The two 1080p ones are below the resolution we now ship at. The two "penguin-*" ones don't have armbian.xml entries either. Wallpapers under wallpapers-lightdm/ are intentionally left alone: even though only one of them is the slick-greeter default, they exist as a paired blurred set for the wallpapers we still ship and are valid swap targets for /etc/lightdm/slick-greeter.conf.
A full desktop uninstall purges several hundred MB of installed files but leaves the matching .deb archives sitting in /var/cache/apt/archives. On SBC root filesystems that's wasted space the user almost never reclaims manually. Add a pkg_clean wrapper alongside the existing pkg_install / pkg_remove / pkg_update / etc. functions so desktops (and any other module that needs it later) don't have to bypass the abstraction with raw apt-get calls. Wire apt_operation_progress to recognise the 'clean' operation for its dialog title. Call pkg_clean at the end of the desktop remove path. Failure is non-fatal because pkg_clean inherits apt_operation_progress's return code handling.
Drop the implicit 'module_appimage install app=armbian-imager'
call from the desktop install flow, and the matching remove call
from the uninstall flow. Users who want the imager can still get
it explicitly via module_appimage install — the mechanism stays
intact, only the unconditional default goes away.
Side effects of dropping it:
- libfuse2 / fuse3 are no longer installed unconditionally on
every desktop install. They were only needed for AppImage
runtime, and module_appimage installs them itself when the
user actually runs an appimage install.
- libgles2 / libegl1 / libgl1 / libgl1-mesa-dri are also no
longer pulled in via this path, but a regular desktop pulls
them in transitively via xserver-xorg, so this is a no-op
for normal desktop installs.
The remove side cleanup is symmetric: no point trying to remove
an AppImage we didn't install.
armbian-plymouth-theme lives in Armbian's own apt repo and is not available from Debian/Ubuntu mirrors. Listing it in common.yaml's packages: caused 'apt-get install' to hard-fail with 'Unable to locate package' on non-Armbian systems, aborting the entire desktop install. Move it out of common.yaml so the YAML stays distro-agnostic. Install it explicitly from the desktop install path, gated on the presence of the Armbian apt source — accept either the legacy /etc/apt/sources.list.d/armbian.list or the modern deb822 /etc/apt/sources.list.d/armbian.sources. Failure of that one pkg_install call is non-fatal: it logs a warning and continues.
…i3-wm/kde-plasma
Same cleanup as the earlier xfce.yaml commit, applied to the
remaining DE files. Every release block carried the same dead
five-entry list:
qalculate-gtk hplip indicator-printers libfontembed1
policykit-1 (+ printer-driver-all on noble/plucky)
These were copied verbatim from a long-dead explicit package list
during the YAML refactor. Of those:
- qalculate-gtk, hplip, indicator-printers, policykit-1,
libfontembed1, update-manager, update-manager-core: not in
any DE's packages: list, so packages_remove (which only
filters the install list) had nothing to filter. Pure noise.
- printer-driver-all: actively in cinnamon.yaml and mate.yaml
packages: lines, so packages_remove was preventing it from
being installed on noble/plucky for both — same printing
breakage we already fixed for xfce. gnome.yaml does not
install printer-driver-all in the first place, so dropping
it from gnome's packages_remove is a no-op for printing
(gnome users will not get printer drivers from this YAML
sweep alone — that's a separate decision).
Drop the dead block from every noble/plucky packages_remove and
the bookworm libfontembed1/update-manager stub. Keep the trixie
pulseaudio->pipewire packages_remove (legitimate). Keep plucky's
pavumeter entry (the package was dropped from the plucky archive)
and add a comment so the next reader knows why.
Verified via parse_desktop_yaml.py: cinnamon and mate now ship
printer-driver-all on noble; all five YAMLs parse cleanly; the
five DEs' DESKTOP_PACKAGES_UNINSTALL output is unchanged for the
entries that legitimately belonged there.
The Quick Settings popover (top-right of the GNOME panel) was
missing the Wi-Fi, Bluetooth and Power Profiles tiles on a fresh
install. Each tile is rendered by gnome-shell only when the
corresponding daemon/library is present, and on a minimal Ubuntu
noble install with --no-install-recommends none of them are
pulled in transitively:
- Wi-Fi / Wired tile -> needs the NetworkManager daemon
(network-manager). The list already
had network-manager-gnome but that
is just the legacy nm-applet GUI
and is unrelated to the tile.
- Bluetooth tile -> needs bluez (the daemon) plus
gnome-bluetooth-sendto, which
pulls in libgnome-bluetooth-3.0
that gnome-shell dlopens to render
the tile. (gnome-bluetooth on noble
is now a transitional shim pointing
at gnome-bluetooth-sendto.)
- Power Profiles tile -> needs power-profiles-daemon, which
gnome-control-center only Recommends.
Add bluez, gnome-bluetooth-sendto, network-manager and
power-profiles-daemon to gnome.yaml's packages: list. Verified
each package against packages.ubuntu.com/noble before adding.
cups was already in common.yaml so the spooler is present, but gnome.yaml shipped no actual printer drivers. The Printers panel inside GNOME Settings (which is built into gnome-control-center on noble — no separate system-config-printer needed) had nothing to expose, so users could not add a real printer. Add printer-driver-all to gnome.yaml's main packages list. Pulls HPLIP, Gutenprint, Foomatic, Splix and friends. Verified the parser still produces a valid install list.
…rol-center) The noble/plucky packages_uninstall blocks in gnome.yaml, cinnamon.yaml, mate.yaml and xfce.yaml all listed language-selector-gnome alongside ubuntu-session. The intent was to suppress Ubuntu's "Language support is not installed" first-login nag. The actual effect is much worse: gnome-control-center has a hard dependency on language-selector-gnome on noble, so when the install path runs apt-get remove -y --purge language-selector-gnome apt cascades and yanks gnome-control-center along with it. The manifest record sees gnome-control-center as installed (it was, moments ago) but by the time the user logs in, Settings is gone. Same class of bug as the xfce4-goodies cascade we already fixed. Drop language-selector-gnome from packages_uninstall in all four DE files. Keep ubuntu-session — that one we genuinely do want gone (it strips Ubuntu's branded session entry from gdm and nothing depends on it). Add a warning comment so the next person who looks at the list does not re-add it. Verified the YAML still parses cleanly for all four DEs.
gnome-control-center on Ubuntu noble ships /usr/share/applications/gnome-ubuntu-panel.desktop, which points at the icon name preferences-ubuntu-panel. That icon only exists in Ubuntu's icon theme. On a non-Ubuntu-themed install like Armbian, the entry renders in the GNOME app grid as a broken grey-triangle icon labelled "Proxy" (the localized Name=). We can't delete the .desktop file directly because dpkg owns it and any gnome-control-center upgrade would put it back. Removing the package itself is also out — gnome-control-center is the same package that gives us Settings and the rest of the panels. Drop a NoDisplay=true / Hidden=true stub at /usr/local/share/applications/gnome-ubuntu-panel.desktop instead. The XDG spec gives /usr/local/share/applications precedence over /usr/share/applications, so the hider survives package upgrades and applies to every user without skel copy magic.
/etc/os-release on Armbian sets LOGO="armbian-logo", and GNOME
Settings -> About reads that key and calls
gtk_icon_theme_lookup_icon("armbian-logo", ...) to render the
distributor logo. Without an installed icon by that name, GNOME
falls back to ID=ubuntu and renders the Ubuntu mark instead.
We already ship branding/pixmaps/armbian.png in the repo, but
module_desktop_branding only copies it to
/usr/share/pixmaps/armbian/armbian.png — a path the freedesktop
icon spec does not search.
Install the same image to
/usr/share/icons/hicolor/256x256/apps/armbian-logo.png so the
hicolor theme lookup resolves it. Refresh the hicolor cache via
gtk-update-icon-cache so the change is visible without re-login.
Both steps fail open: missing source file or missing
gtk-update-icon-cache binary just skips the step.
The previous commit installed branding/pixmaps/armbian.png to /usr/share/icons/hicolor/256x256/apps/armbian-logo.png, but the PNG we ship is actually 128x128. The hicolor icon cache validates that an icon file's actual pixel dimensions match the parent directory name, so a 128x128 image under 256x256/apps/ gets silently excluded from the cache lookup. Result: the file was on disk but gnome-control-center still fell back to the Ubuntu logo. Install to 128x128/apps/ instead, where the cache will accept it. Also remove any stale 256x256 install left over from the previous (broken) version of this step so we don't leave orphaned files behind on systems that already ran the bad postinst.
The previous attempt to hide Canonical's broken-iconed "Ubuntu"
panel entry by dropping a NoDisplay=true / Hidden=true stub at
/usr/local/share/applications/gnome-ubuntu-panel.desktop
broke gnome-control-center entirely.
The reason: gnome-ubuntu-panel.desktop is NOT a normal application
launcher. It's a gnome-control-center panel descriptor — the
compiled-in panel walk loads it via cc_shell_model_set_panel_visibility
and trips an assert(valid) when the desktop file is missing
required keys (which our stub deliberately omitted). Trace from
the affected box:
WARNING: Ignoring broken panel ubuntu (missing desktop file)
ERROR: ../shell/cc-shell-model.c:412:cc_shell_model_set_panel_visibility:
assertion failed: (valid)
Bail out!
Aborted
After this, gnome-control-center cannot launch at all — Settings
just hangs/never opens. Same chain of events on every fresh
install of gnome on the smallfixes branch since the previous
commit.
Strip the hider stub and replace it with an unconditional rm -f
so any system that already ran the broken postinst is recovered
on the next install. The "Proxy" broken-icon entry returns, but
that is a cosmetic annoyance compared to a fully unusable
Settings app.
A real fix for the broken icon needs to either ship a
preferences-ubuntu-panel icon in the icon theme, or patch
gnome-ubuntu-panel.desktop's Icon= line in place to point at a
name we already have. Both require editing the dpkg-owned file
and are out of scope for this revert.
The 128x128/apps install from the previous commit doesn't work
on noble: even though the directory IS listed in
/usr/share/icons/hicolor/index.theme as a valid 128x128
Threshold/Applications directory, gtk-update-icon-cache rebuilds
without errors but the resulting cache contains zero entries for
armbian-logo. Verified on the affected box:
strings /usr/share/icons/hicolor/icon-theme.cache | grep armbian
-> 0 matches
I don't have a tidy explanation for the silent miss but I do
have a working alternative: install at scalable/apps. The
scalable directory has Type=Scalable semantics in the index and
accepts any PNG without dimension validation; the cache picks
it up reliably on noble.
Drop our 128x128 PNG into scalable/apps/armbian-logo.png and
clean up the stale 128x128 (and 256x256, just in case) installs
from previous attempts. Update the surrounding comment to record
the cc-about-page.c lookup chain so the next person who reads
this knows exactly which icon names gnome-control-center is
hunting for and why.
…base-files
The previous three commits tried to install armbian-logo.png from
module_desktop_branding so that gnome-control-center's Settings
-> About page would render the Armbian penguin instead of the
Ubuntu mark. None of them produced a visible logo on the test
box despite GTK4's icon-theme lookup confirming that the icon
was findable:
python3 ... has=True for 'armbian-logo'
This is the wrong layer. /etc/os-release sets LOGO="armbian-logo"
from armbian-base-files; the matching icon should ship from the
SAME package so that:
- the LOGO= value and the icon file are versioned together,
- the dpkg trigger from hicolor-icon-theme rebuilds the cache
automatically (no gtk-update-icon-cache shell-out from us),
- every Armbian image gets the right logo whether or not the
user installs a desktop, and every consumer that reads
LOGO= from os-release (gnome-control-center, KDE Info
Center, neofetch, fastfetch, inxi, ...) gets it for free,
- armbian-config stops being responsible for distro branding,
which it never should have been in the first place.
Drop the icon-install block from module_desktop_branding.sh.
Keep an unconditional rm -f for the three paths previous commits
might have written to, so any system that ran a broken version
of this step gets a clean slate when armbian-base-files later
takes over.
The actual icon ship belongs in armbian-base-files and will be
done as a separate change in that repo.
After uninstalling a desktop, users were left staring at a blank tty1 with no login prompt — the display manager was purged but the system still tried to reach graphical.target on the next boot. With no DM behind it, graphical.target arrived empty and getty@tty1 (which has Conflicts=display-manager.service) never started. Make the install/remove pair fix the systemd default target explicitly: install -> systemctl set-default graphical.target remove -> systemctl set-default multi-user.target The install side is mostly defensive — every DM postinst already does this for itself — but doing it from here means a partial install or a re-install always lands on graphical.target without needing to trust the DM's postinst to have run successfully. The remove side is the actual fix. After 'systemctl set-default multi-user.target' the next boot brings up the regular console login regardless of which DM was previously installed. For the running session (so the user does not have to reboot to get a login prompt) the remove side also runs systemctl isolate multi-user.target immediately after stopping display-manager. Just starting getty@tty1.service on its own does not work while graphical.target is still active — it stays in 'inactive (dead)' because of the Conflicts= relationship — so isolate is the only reliable way to force the running session to drop to the console. Isolate is destructive (kills any open GUI sessions), but the GUI is being torn down anyway. All systemctl calls are wrapped in '2>/dev/null || true' so failure is never fatal — for example inside a container where systemd is not init.
The previous version used 'cp -r --update=none /etc/skel/. $home/' to copy skel content into existing user homes without overwriting user-edited files. The '--update=none' long-form flag was added in GNU coreutils 9.3 — Debian bookworm ships 9.1 and rejects the option. On bookworm, the cp call exits with 'unrecognized option' and copies nothing, silently losing every skel file. (The chown -R safety net still runs and saves us from leaving root-owned files behind, but the skel content itself is lost.) The obvious alternative '-n' / '--no-clobber' is no good either: it works on bookworm but on coreutils 9.2+ (so on noble's 9.4) '-n' prints a diagnostic to stderr and exits nonzero whenever it skips a file, which on a normal repeat invocation is every file. Neither flag is portable across bookworm and noble. Use a per-file find loop instead. Walk /etc/skel and copy each entry only if it doesn't already exist at the destination. find walks parents before children, so intermediate directories are created before any of their contents arrive. After the per-file copy, run chown -R on the entire home as a safety net for root-owned files that other package postinst scripts (caja, nemo, gnome-keyring, etc.) routinely leak into ~/.config — see commit 462281b for the original recursive-chown rationale. Verified across the five scenarios: 1. fresh skel into empty $HOME -> all files copied 2. existing skel file, user-edited -> user's version preserved 3. extra files in $HOME -> untouched 4. nested directories -> created and populated 5. repeat run -> idempotent, no-op
The kde-neon.yaml packages_uninstall list contained four
entries that would trigger the same apt cascade bug we
already fixed for xfce4-goodies and language-selector-gnome:
- kdeconnect (Depends of neon-desktop)
- khelpcenter (Depends of neon-desktop)
- gnome-keyring (pulled by KDE/GTK pam dependencies)
- libreoffice* (bash glob, not apt — apt does not expand
globs and bash glob-expands in cwd first)
If a user actually tries to install kde-neon (it is currently
status: unsupported but the YAML still parses), the install
path runs
apt-get remove -y --purge kdeconnect khelpcenter ...
and apt's autoremove cascades, yanking neon-desktop and a
large chunk of the plasma stack along with it.
Drop those four entries. Keep gnome-software and thunderbird
which are orthogonal to the plasma dep tree and safe to
remove. Add a comment block documenting the cascade rule so
the next person editing this file does not re-introduce them.
The libreoffice glob deserves a separate fix (we should
enumerate the actual binary package names if we want
libreoffice gone) but that is out of scope for this commit.
Two related fixes to module_desktops.sh, both in the install /
auto-login path.
1) install: bail on pkg_install failure, only flip default.target
AFTER the DM has actually started.
Previously, if 'pkg_install $DESKTOP_PACKAGES' or
'pkg_install $DESKTOP_DM' failed midway (broken mirror,
half-resolved deps, disk full), the install path kept going:
branding installed, groups added, default.target switched to
graphical, 'systemctl start display-manager' attempted but
ignored if it failed. The next boot would land on
graphical.target with no working DM and the user would see a
black screen.
Check the return of each pkg_install. If either fails, return
1 from the install branch with no further state changes — the
manifest is not written, default.target stays where it was,
no DM is started. The system is in the same state as if the
install had never run.
Also delay the 'systemctl set-default graphical.target' call
until the start-display-manager step actually returned 0. If
the DM unit refuses to start, the next boot would otherwise
pin to graphical with a broken DM — the symptom we're trying
to avoid.
2) auto: edit gdm3 conf in place instead of clobbering it.
The previous code did 'cat > /etc/gdm3/custom.conf <<EOF
[daemon] AutomaticLoginEnable = true AutomaticLogin = $user
EOF' which overwrites the entire file, losing any user
customization (WaylandEnable=false, debug=true, accessibility
keys, etc.). The lightdm and sddm paths are safe — they write
to drop-in directories — but the gdm3 path was destructive.
gdm3 has no conf.d drop-in support upstream or in
Debian/Ubuntu patches (verified against gdm3 43.0-3 / 46.2 /
48.0-2 patch series). It loads exactly one config file. So
we have to edit it in place. Use a small sed-based block
that:
- picks /etc/gdm3/daemon.conf on Debian and
/etc/gdm3/custom.conf on Ubuntu, branching on
'ID=' from /etc/os-release rather than on release
codename. The previous codename check
([[ trixie || forky ]]) was wrong because Debian
bookworm also reads daemon.conf, not custom.conf.
- creates the file from scratch when it doesn't exist
- inserts a [daemon] section if the existing file is
missing one
- updates AutomaticLoginEnable / AutomaticLogin in place
when present, inserts them after [daemon] when not
The 'manual' branch is also tightened to handle whitespace
variation around '=' via sed -E.
Verified against five test cases: fresh install, partial
existing config, idempotent re-enable, manual disable,
no-[daemon]-section file. All five preserve user-edited keys
like WaylandEnable=false in adjacent positions.
Adds a tier model to the desktop YAML so each DE can be installed
at one of three sizes:
minimal — DE + display manager + base utilities. ~500 MB.
mid — minimal + browser + everyday user apps (text editor,
calculator, image/PDF viewer, media player, archive
tool, torrent client). ~1 GB.
full — mid + office suite + creative tools (LibreOffice,
GIMP, Inkscape, Thunderbird, Audacity). ~2.5 GB.
Tiers are additive: full = minimal + mid + full. The parser walks
them in order and accumulates packages.
Schema changes
--------------
Replace each per-DE YAML's flat 'packages:' / 'packages_uninstall:'
with a 'tiers:' map keyed by tier name. Each tier carries its own
'packages:' and (for minimal) 'packages_uninstall:'. Per-DE YAMLs
can also add 'packages_remove:' under a tier to drop entries
inherited from common.yaml — see kde-plasma.yaml for an example
that swaps gnome-text-editor for kate at mid and libreoffice-gtk3
for libreoffice-kde at full.
Add 'tier_overrides:' as a per-DE escape hatch for per-arch
removals. Use it to drop packages that don't exist on a particular
arch (e.g. blender/inkscape on armhf, libreoffice on riscv64).
The xfce.yaml example below shows the shape; populating it for
the rest is a follow-up after the per-arch availability audit.
The orthogonal 'releases:' block keeps its existing semantics:
per-release architectures, packages_remove, packages, and
packages_uninstall apply to whatever tier is being installed.
common.yaml carries the per-tier defaults that apply to every
desktop. Per-DE YAMLs only need a tiers block when they want to
add to or override common — most of the supported DEs (xfce,
gnome, mate, cinnamon, i3-wm, xmonad, enlightenment) only declare
their own minimal tier and inherit common's mid/full unchanged.
Browser substitution
--------------------
The literal token 'browser' inside any tier resolves to a real
package name from common.yaml's 'browser:' map at parse time.
Default mapping: chromium on amd64/arm64/armhf, firefox on
riscv64. No google-chrome, no Microsoft Edge — only in-archive
native packages.
Parser changes
--------------
parse_desktop_yaml.py grows a mandatory --tier flag:
parse_desktop_yaml.py <yaml_dir> <de> <release> <arch> --tier <tier>
The flag has no default. Calling the parser without --tier prints
the usage and exits 1. The --list, --list-json, and --primaries
modes are unchanged (they don't need a tier — they only enumerate
DEs and report their primary package).
A new bash output variable DESKTOP_TIER is emitted alongside the
existing DESKTOP_PACKAGES / DESKTOP_PACKAGES_UNINSTALL / etc.
The bash side will use it in a follow-up commit to write the
/etc/armbian/desktop/<de>.tier marker file.
Coverage
--------
All 12 DE YAMLs converted to the new schema:
Supported (8): xfce, gnome, mate, cinnamon, kde-plasma, i3-wm,
xmonad, enlightenment.
Unsupported (4): budgie, deepin, kde-neon, bianbu — converted
so the parser doesn't crash, but no tier
overrides since they're not actively maintained.
Verified the parser produces the expected output for every
supported DE × {minimal,mid,full} on noble/amd64, and that the
KDE per-DE overrides (kate replacing gnome-text-editor,
libreoffice-kde replacing libreoffice-gtk3) actually apply.
Note: this commit only changes the schema and the parser. The
bash install path still ignores tiers — the next commit wires
'install tier=<tier>' through to the parser.
Companion to the previous commit (parser + YAML schema). This
commit makes the bash side actually use the new tier system.
module_desktop_yamlparse
------------------------
Add an optional 4th positional arg `tier`, defaulting to 'minimal'
when omitted. The default keeps `auto`/`manual`/`login`/`status`
calls (which only need DESKTOP_DM and DESKTOP_PRIMARY_PKG, both of
which are already correct at the minimal tier) working without
modification. The new bash variable DESKTOP_TIER is exported
alongside the existing ones.
module_desktops install
-----------------------
- `tier=minimal|mid|full` is now mandatory. Reject early with a
clear message instead of letting the parser bail with a generic
usage error.
- Pass tier through to the parser via the new 4th yamlparse arg.
- Always write /etc/armbian/desktop/<de>.tier alongside the
existing <de>.packages manifest. Written even when the install
added no new packages (re-install at the same tier) so the
marker stays accurate.
module_desktops remove
----------------------
- Read /etc/armbian/desktop/<de>.tier and pass the installed tier
to the parser, so the YAML fallback (when the manifest is
missing) walks the right tier's package list. Defaults to
'minimal' if no marker exists.
- Clean up the .tier marker file alongside .packages.
module_desktops status
----------------------
- When the desktop is installed, print the installed tier name
(minimal/mid/full) as stdout, returning 0. When not installed,
print "not installed" and return 1. Old callers that only
cared about the exit code keep working.
module_desktops upgrade / downgrade (NEW)
------------------------------------------
Two new commands that move an installed desktop between tiers:
module_desktops upgrade de=xfce tier=mid
module_desktops upgrade de=xfce tier=full
module_desktops downgrade de=xfce tier=minimal
module_desktops downgrade de=xfce tier=mid
Implementation in the new private helper
_module_desktops_change_tier:
1. Read /etc/armbian/desktop/<de>.tier (must exist).
2. Validate the direction: upgrade refuses to move to a same
or lower tier, downgrade refuses to move to a same or
higher tier. Both refuse if the target equals the current
tier. Use the corresponding command instead.
3. Parse the YAML twice — once at current tier, once at target.
4. Compute the symmetric set difference via awk on per-line
printf input. Read the package strings into bash arrays
first; quoting matters because the previous attempt with
unquoted vars put every package on a single line and broke
the awk comparison.
5. Upgrade: pkg_install the (target - current) delta. Append
the newly-installed packages to the manifest.
6. Downgrade: pkg_remove the (current - target) delta,
INTERSECTED with the install manifest. The intersection is
critical: it ensures we only ever remove packages we
ourselves installed. Packages the user installed manually
after the desktop install (and which happen to also be
named in the YAML) are never touched.
7. Update the .tier marker.
Failure modes are handled:
- missing marker file -> clear error, nothing changed
- same tier (no-op) -> print message, exit 0
- wrong direction -> error pointing at the right command
- pkg_install / pkg_remove failure -> error, marker NOT updated
Verified end-to-end: parser produces correct deltas for
xfce {minimal,mid,full} on noble/amd64 (7 + 6 packages added in
sequence), and KDE Plasma overrides correctly produce a delta
without gnome-text-editor / file-roller / loupe / libreoffice-gtk3
and WITH kate / libreoffice-kde at the equivalent positions.
Note: this commit does NOT add the dialog menu entries for tier
selection. The bash --api still works directly. The dialog
plumbing is the next commit.
The previous commit made tier= mandatory on 'module_desktops install'. Without this companion change, every existing dialog menu install entry (CINM01, GNME01, MATE01, I3WM01, KDEP01, KDEN01, XFCE01) would fail with "Error: specify tier=" when clicked. Add tier=minimal to each install command so the existing menu keeps working with its current "one button per DE" UX. This preserves behavior bit-for-bit: clicking "Install XFCE" still installs the same set of packages it always did. Adding mid/full menu entries (one button per DE per tier as agreed) and the change-tier UX for already-installed desktops will land in a follow-up commit. Doing it here would balloon this PR; the underlying bash and parser changes need to be testable in isolation first.
Per the agreed UX ("one step menu, always all three per DE"),
add 14 new install entries to the desktop menu:
CINM05 / CINM06 Cinnamon mid / full
GNME05 / GNME06 GNOME mid / full
MATE05 / MATE06 MATE mid / full
I3WM05 / I3WM06 i3 mid / full
KDEP05 / KDEP06 KDE Plasma mid / full
XFCE05 / XFCE06 XFCE mid / full
Each tier entry has its own help text describing what's added
and the rough install size (~500 MB / ~1 GB / ~2.5 GB). The
help string is what dialog shows on F1 / hover.
ID slot allocation
------------------
Existing convention is 4-char DE prefix + 2-digit action code.
The desktop block already used:
*01 install (minimal)
*02 uninstall
*03 enable autologin
*04 disable autologin
So the new tier entries have to start at *05. Picked:
*05 = mid install
*06 = full install
This keeps the 6-char ID convention and avoids collisions with
the existing autologin blocks. Verified across the whole
config.system.json file: zero duplicate IDs after the change.
Each new entry inherits the same condition as the corresponding
*01 entry — `! module_desktops installed && module_desktop_supported X`
— so all three tier choices for a given DE appear simultaneously
when no desktop is installed, and disappear together once any
desktop is installed.
Skipped
-------
- KDE Neon stays at the single KDEN01 minimal entry. It's
status: unsupported and there's no point expanding the menu
for it.
- xmonad and enlightenment are not in the menu today (despite
being status: supported). Adding them is a separate UX
question and out of scope here.
- Change-tier UX (upgrade/downgrade from inside the dialog
for an already-installed desktop) is also a separate concern
— needs a per-DE-per-current-tier conditional matrix and
belongs in its own commit.
After auditing every mid/full tier package against the actual
Debian / Ubuntu archives, several holes need handling so the
install path never tries to apt-install a non-existent package.
This commit handles all the holes plus a couple of related
correctness bugs.
1) libreoffice-kde does not exist in any release (retired
upstream). kde-plasma.yaml's full tier was swapping in
libreoffice-kde, which would have failed on every install.
Drop the swap; plain libreoffice from common.yaml's full
tier inherits KDE integration via libreoffice-style-breeze
when Plasma is installed.
2) Browser map is now keyed by (release, arch), not just arch.
Required because:
- Debian has 'firefox-esr', not 'firefox'
- Ubuntu's 'chromium' deb is a snap-shim wrapper that
requires snapd, which Armbian doesn't ship; the shim
fails at runtime
- 'chromium' isn't built for riscv64 in either Debian
or Ubuntu
- 'firefox' isn't built for noble/plucky riscv64 either
New map:
bookworm: amd64/arm64/armhf -> chromium
trixie: amd64/arm64/armhf -> chromium
riscv64 -> firefox-esr
noble: all archs -> epiphany-browser
plucky: all archs -> epiphany-browser
epiphany-browser (GNOME Web) is small, native, real-deb,
and present on every Ubuntu arch.
3) tier_overrides schema gains a per-release-per-arch layer.
Old form (per-arch only) handled blender/inkscape on armhf
etc. New form keeps that AND adds a 'releases.<release>.
architectures.<arch>' nesting for transient holes (e.g.
'loupe' missing on bookworm because GNOME 43 didn't have it).
4) Parser walks tier_overrides at every tier step in the walk,
not just at the target tier. Without this, a hole declared
at the mid tier (e.g. 'loupe' missing on bookworm) would be
honoured for mid installs but ignored for full installs,
because the full install pulls in mid packages and then
only applies full-tier overrides. The fix is to apply each
tier's overrides at the same step as that tier's packages.
5) Per-DE tier_overrides also work. Verified on:
- kde-plasma drops gnome-text-editor / file-roller / loupe
at mid (overrides common-tier additions to use KDE
equivalents)
- gnome drops transmission-gtk at mid (GNOME ships its
own download integration)
6) common.yaml now carries the holes that apply to every
desktop:
mid bookworm */armhf: remove [loupe] (no loupe in bookworm)
mid plucky armhf: remove [loupe] (dropped on plucky/armhf)
full bookworm armhf: remove [thunderbird] (missing on armhf)
full trixie armhf: remove [thunderbird]
full noble all archs: remove [thunderbird] (snap-shim only)
full plucky all archs: remove [thunderbird]
Verified all 384 (DE x release x arch x tier) combinations
parse cleanly. Verified the holes are honoured by spot-checking
the resolved package list for every release/arch combination
that has a known hole.
The dialog menu calls 'module_desktops status de=<name>' from the `condition` field of every entry. With ~21 conditional desktop entries (7 supported DEs × 3 install tiers = 21 install entries), every menu render fired status 21 times in a row. The previous commit had status print "not installed" on the not-installed path, which produced 21 "not installed" lines stacked above the actual menu output. Fix: print nothing on the not-installed path. The exit code (1) is the only thing the menu's condition gate looks at. Installed callers that want the tier name still get it via stdout (echo of the .tier file contents) and a 0 exit code.
The 'login' command's gdm3 check used an unanchored regex
matching the substring 'AutomaticLoginEnable\s*=\s*true' anywhere
on a line. The stock /etc/gdm3/custom.conf template that Ubuntu
noble's gdm3 ships contains:
# Enabling automatic login
# AutomaticLoginEnable = true
# AutomaticLogin = user1
The commented sample line matches the substring, so 'login' would
return 0 ('autologin enabled') on every fresh noble install where
the user had never touched autologin. The user-visible symptom:
the dialog menu's "Disable autologin" entry was always visible
(because its condition uses 'login' returning 0) and "Enable
autologin" was never visible — even though autologin was actually
off.
Anchor the regex at line start so the comment lines don't match.
Use the same character class form ([[:space:]]) as the manual
sed call for consistency.
Verified across four cases:
- stock template with commented sample -> no match (correct)
- enabled config 'AutomaticLoginEnable = true' -> match
- disabled config 'AutomaticLoginEnable = false' -> no match
- no whitespace 'AutomaticLoginEnable=true' -> match
The 'status' command was printing the installed tier name on
stdout to make it useful from the CLI. But status is also called
from every dialog menu entry's `condition` field, dozens of
times per render — and any stdout output leaks into the dialog,
producing strings of "full / full / full / mid / ..." between
the menu items.
Split the responsibility:
- status : silent exit-code query. Returns 0 if installed,
1 if not. No output on either path. This is
what menu condition gates use.
- tier (NEW) : value-returning getter. Prints the installed
tier name (minimal/mid/full) when installed,
prints "not installed" otherwise. Use from
the CLI when you want the actual value.
This is the same fix pattern as the previous "not installed"
silence — extending it from one path to the other.
Add a way to upgrade or downgrade an installed desktop's tier
from the dialog menu. Two new bash commands plus 18 new menu
entries (6 supported DEs x 3 target tiers).
New bash commands
-----------------
at-tier — silent gate. Exit 0 if a desktop is installed AND its
current tier marker matches the given target. Used
by the menu's `condition` field to hide the
"Change to <tier>" entry that matches the currently
installed tier (so the user only sees the OTHER two
tiers as switch options).
Pure exit-code query, no stdout output, same shape
as `status` and `login`.
set-tier — direction-agnostic tier change. Reads the current
tier from the marker, dispatches to upgrade or
downgrade based on whether the target is higher or
lower. Refuses with a friendly message if the
desktop isn't installed or already at the target.
Used by the menu's `command` field so a single
button can switch in either direction without the
menu having to know the current state.
Internally set-tier reuses _module_desktops_change_tier (the
upgrade/downgrade implementation) — set-tier is just a
front-end that figures out the direction.
Menu entries
------------
For each of the 6 supported DEs in the menu (cinnamon, gnome,
mate, i3-wm, kde-plasma, xfce), 3 entries:
*07 Change <DE> to minimal
*08 Change <DE> to mid
*09 Change <DE> to full
Each entry's condition is
module_desktops status de=<X> && ! module_desktops at-tier de=<X> tier=<target>
so the entry is visible only when the DE is installed AND not
already at the target tier. With xfce installed at minimal,
the user sees XFCE08 (Change XFCE to mid) and XFCE09 (Change
XFCE to full), but NOT XFCE07 (Change to minimal). After
running XFCE08, the menu re-renders showing XFCE07 and XFCE09
instead.
ID slot allocation
------------------
Existing desktop slot usage:
*01 install minimal
*02 uninstall
*03 enable autologin
*04 disable autologin
*05 install mid
*06 install full
New slots:
*07 change to minimal
*08 change to mid
*09 change to full
Verified across the file: zero duplicate IDs after the change.
KDE Neon is intentionally not in the list — it's status:
unsupported and only has its single KDEN01 install entry.
xmonad and enlightenment also aren't in the list because they
don't have install entries in the menu yet either; adding
them is a separate UX call.
|
Caution Review failedPull request was closed or merged during review WalkthroughThis pull request introduces a tiered desktop installation and auditing system. It adds a GitHub Actions workflow that periodically audits desktop configurations against supported releases and package availability, using Claude to propose fixes. The desktop module system is refactored from flat package lists to a tier-based model (minimal, mid, full), with new shell functions supporting tier upgrades, downgrades, and tier transitions. Desktop YAML files are restructured to define packages per tier, while the configuration menu is expanded with tier-specific install and tier-change options. Supporting scripts are updated to parse and apply tier selections, and two new Python tools implement package availability checking and Claude-driven remediation. Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…n empty CI was failing on every desktop test with two errors: expr: non-integer argument Error: specify tier=minimal|mid|full The first is a pre-existing init bug — `apt-cache policy zfs-dkms` returns nothing in test containers (no zfs-dkms in the apt sources), ZFS_DKMS_VERSION is empty, and the previous-version probe pipes empty input through `expr` which errors out. Guard the probe: only run it when ZFS_DKMS_VERSION matches the expected N.N.N format. The second is the breaking change from the tier system PR (install now requires tier=). Update every desktop test to pass `tier=full` for the supported DEs (xfce, mate, cinnamon, gnome, i3-wm, xmonad, enlightenment, kde-plasma) so the test exercises the largest install path. Unsupported DEs (budgie, deepin, kde-neon — also disabled or not really meant to work) keep `tier=minimal` since their YAML doesn't expect the common-tier mid/full extras. KDE Neon specifically has a comment explaining why it stays at minimal: its YAML only declares a tiers.minimal block, and pushing common's mid/full extras (chromium, libreoffice, gimp, etc.) into it would just add cruft a kde-neon user would not expect.
Two line continuations were aligned with spaces instead of tabs, which trips the editorconfig-checker step in CI: module_desktops.sh:140 Wrong indentation type (spaces instead of tabs) module_desktops.sh:199 Wrong indentation type (spaces instead of tabs) Both were the same pattern: an `if [[ ... \\` continuation where the next line was indented to align under the opening condition with literal spaces. Reformat them to put the operator at the start of a tab-indented continuation line instead, which both reads cleanly and uses tabs only. No behavior change; bash parses both forms identically.
fonts-ubuntu is an Ubuntu-only package — Debian doesn't ship it. Five DE YAMLs (xfce, mate, cinnamon, kde-plasma, i3-wm) list it in their tiers.minimal.packages, and on Debian bookworm/trixie that produces E: Package 'fonts-ubuntu' has no installation candidate aborting the entire desktop install. Add a tier_overrides.minimal block to common.yaml that strips fonts-ubuntu on every Debian release (bookworm and trixie, all arches). The Ubuntu releases (noble, plucky) keep it so the Ubuntu branding font stays on Ubuntu installs. Doing this in common.yaml means we don't have to touch each of the five DE YAMLs individually — the parser walks common's tier_overrides for every DE in addition to the DE's own tier_overrides. Verified across the matrix: bookworm and trixie on amd64/arm64/armhf (and trixie on riscv64) drop fonts-ubuntu for all five DEs; noble and plucky keep it. The fallback font stack on Debian (fonts-noto, fonts-dejavu, etc.) is pulled in transitively by the GTK / DE packages.
Adds a scheduled GitHub Actions workflow that audits the desktop
YAML matrix for two kinds of staleness and proposes fixes via the
Anthropic API.
Pieces
------
tools/desktops/audit.py
Deterministic auditor. Checks out armbian/build alongside
configng (from CI; from local CLI when run by hand) and
cross-references three things:
- releases declared in armbian/build's config/distributions/
(with their support status — 'supported', 'csc', 'eos', ...)
- the 'releases:' blocks declared in every DE YAML
- the per-(release, arch, tier) resolved DESKTOP_PACKAGES set
from parse_desktop_yaml.py
For each DESKTOP_PACKAGES entry, fetches packages.debian.org or
packages.ubuntu.com and decides whether the package actually
exists in that (release, arch). Outputs a JSON report listing
'missing_releases' (build supports it, no DE YAML covers it,
not EOS) and 'package_holes' (DESKTOP_PACKAGES names a package
that the upstream archive doesn't ship for that combination).
Pure logic, no LLM.
tools/desktops/audit_apply.py
Reads the JSON report and hands it to Claude via the Anthropic
Python SDK. Claude is given file Read/Write tool access scoped
strictly to tools/modules/desktops/yaml/ and is told to propose
minimal edits to common.yaml's tier_overrides for package
holes and to add release blocks to per-DE YAMLs for missing
releases. After Claude finishes, post-edit validation re-parses
every YAML and spot-checks the parser to ensure nothing was
broken. Short-circuits on no findings (so 'no work' runs cost
nothing in API tokens). Also has --dry-run mode that prints the
prompt without calling the API, useful for local testing.
.github/workflows/maintenance-desktop-audit.yml
Schedule: Mondays 06:00 UTC. Also workflow_dispatch with
optional tier / release filter inputs and a dry_run toggle.
Job: checkout both repos, run audit.py, surface findings as
a job summary (markdown tables), upload the report as an
artifact, run audit_apply.py if there's anything actionable,
open or update a draft PR via peter-evans/create-pull-request
on a fixed branch (bot/desktop-matrix-audit) so weekly runs
update the existing PR rather than spamming new ones.
Operational notes
-----------------
- The Claude apply step is gated on actionable findings AND on
ANTHROPIC_API_KEY being present. Without the secret it logs a
warning and exits cleanly, so the workflow can run as a smoke
test without any API access.
- Sandboxed file access: Claude can only read files inside the
configng checkout (path traversal blocked) and can only write
files inside tools/modules/desktops/yaml/. No bash, no network.
- Token cap: --max-tokens 50000 by default, configurable.
- PR is opened as a draft so a human can review before merging.
Labels applied: bot, desktops, documentation.
Local validation
----------------
Tested locally against the live build + configng checkouts:
python3 tools/desktops/audit.py \\
--build-repo /home/igorp/Development/build \\
--configng-repo . \\
--skip-network --output /tmp/r.json
Found 5 missing CSC releases (forky, jammy, questing, resolute,
sid) and 0 package holes — exactly what's expected given the
current YAML matrix.
Summary
A long working branch that started as "fix the desktop install on a fresh box" and grew into a complete reshape of the desktop submodule. Roughly three layers, summarised below.
Layer 1 — bug fixes that stop the existing install/remove from breaking real users
The user reported
Error: YAML parser not found at /usr/bin/../tools/modules/desktops/scripts/parse_desktop_yaml.pyon a fresh apt install. Tracking that down opened a series of related correctness bugs:tools/modules/desktops/(which holds the YAML, parser, branding assets, postinst hooks, skel, greeters) was never listed indebian.conf, so the .deb only containedlib/,bin/,share/. On installed systems the parser path resolved to/usr/tools/...which doesn't exist. Fixed: ship the directory under/usr/share/armbian-config/desktops/and introduce adesktops_dirglobal inbin/armbian-configthat resolves dev vs installed.module_update_skellost its recursivechown -Rsafety net during the refactor that added the per-file find loop. Other package postinst scripts (caja, nemo, gnome-keyring) routinely leak root-owned files into~/.config, and without the recursive chown, MATE on first login pops up "The path for the directory containing Caja settings needs read and write permissions" — caja and nemo refuse to start because their config dir is root-owned. Restored the recursive chown.ACTUALLY_INSTALLEDtracker inpkg_installbut dropped both the persistence to disk and the file-based remove path that had been added by feat: track desktop packages for clean removal #799. Theremovecommand was reduced topkg_remove $DESKTOP_PRIMARY_PKGwith a comment claiming "autopurge handles dependencies" — it does not, because every package in$DESKTOP_PACKAGESwas manually-installed by name. Result:module_desktops removeleft ~340 packages on disk. Restored/etc/armbian/desktop/<de>.packagesand the file-based remove path.packages_uninstallcascade traps: listing a package that is a hardDepends:of a metapackage we install causes apt's autoremove to yank the parent and a chunk of the desktop. Hit three real cases:xfce4-goodiesplugins (xfce4-clipman-plugin, etc.) → yanksxfce4-goodies→ cascade toxserver-xorg,pulseaudio,cups,evince,mousepad.language-selector-gnome→ yanksgnome-control-center→ user loses Settings on every gnome install.kdeconnect/khelpcenterinkde-neon.yaml→ would yankneon-desktop(latent — kde-neon is unsupported but the YAML is loaded).Dropped all three, added warning comments.
default.target = graphicalleft the next boot pinned to graphical with no DM. Nowpkg_installfailuresreturn 1cleanly without changing system state, andset-default graphical.targetonly fires aftersystemctl start display-managerreturns 0.module_desktops autoclobbered/etc/gdm3/custom.confwithcat > $file <<EOF, losing any user customization (WaylandEnable=false, debug, etc.). Replaced with sed-based in-place edit that preserves the rest of the file. Also fixed: it was branching on release codename (wrong on bookworm — both bookworm and trixie usedaemon.conf) when it should branch onID=from os-release.module_desktops loginregex: unanchoredgrepforAutomaticLoginEnable\\s*=\\s*truematched the commented sample line in noble's stockcustom.conf, returning 0 (autologin enabled) on every fresh install. Anchored at^.conditionfield, dozens of times per render, and printing "minimal" / "mid" / "full" / "not installed" between the menu items). Made it silent on both paths; added a separatetiercommand for the value-returning getter.cp -r --update=noneinmodule_update_skelwas broken on Debian bookworm (coreutils 9.1;--update=nonewas added in 9.3). Replaced with a portable per-file find loop.default.targetto/fromgraphical.targetexplicitly, and the remove path additionallysystemctl isolate multi-user.targetso the running session drops to a console login on tty1 immediately without needing a reboot.apt-get cleanafter remove: a full DE removal frees ~hundreds of MB of installed files but leaves the matching .debs sitting in/var/cache/apt/archives. Added apkg_cleanwrapper alongside the existingpkg_*family and call it after every desktop remove.armbian-plymouth-themeis in Armbian's own apt repo, not in Debian/Ubuntu. Listing it in common.yaml's flatpackages:would have caused 'apt: Unable to locate package' on every non-Armbian install. Moved it out of the YAML and gated it behind the presence of/etc/apt/sources.list.d/armbian.{list,sources}.Layer 2 — desktop module audit cleanup
Pulled out the audit results from the in-session review:
packages_removecruft: every DE YAML's noble/plucky release block carried 5-6 dead entries (qalculate-gtk,hplip,indicator-printers,libfontembed1,policykit-1,printer-driver-all) copied verbatim from a long-gone explicit package list. Five of those six are no-ops (the package isn't in any install list anywhere). The sixth,printer-driver-all, was actively in xfce.yaml and mate.yaml's package lists, so thepackages_removewas preventing it from being installed on noble/plucky — meaning printing was effectively non-functional on Ubuntu xfce/mate out of the box. Cleaned up the cruft and restoredprinter-driver-allfor the DEs that ship it.network-manager,bluez,gnome-bluetooth-sendto,power-profiles-daemonto gnome.yaml.printer-driver-alladded to gnome.yaml so the Settings → Printers panel can actually add a printer.module_desktops installno longer auto-installsarmbian-imager— the AppImage mechanism stays available via explicitmodule_appimage install app=armbian-imagerbut isn't unconditional anymore.tools/modules/desktops/branding/(12 unused icon-menu PNGs, 9 unreferenced wallpapers, 2 dead.desktophider stubs, thescrcpy.svgthat was misfiled here). Most importantly: removedskel/.local/share/applications/system-config-printer.desktopwhich was aHidden=truestub blocking the printer-add wizard from appearing in any DE menu. Fixed three broken<filename>references inbranding/armbian.xml.readmode, dropped the verbose--item-helptext from menu rendering so the menu output isn't 3-segment-per-entry noise.language-selector-gnomewarning: see Layer 1, added a warning comment in every DE YAML that touchedpackages_uninstallso future edits don't reintroduce the cascade.Layer 3 — tier system
The big one. Replace the flat single-package-list-per-DE model with a tiered model where every install picks one of three tiers and can move between them later:
minimalmidminimal+ browser + everyday user apps (text editor, calculator, image/PDF viewer, media player, archive tool, torrent client).fullmid+ office suite + creative tools (LibreOffice, GIMP, Inkscape, Thunderbird, Audacity).YAML schema
tiers:map keyed byminimal/mid/full, replacing the flatpackages:list. Each tier is additive:full = minimal + mid + full. The parser walks tiers in order.tier_overrides:for per-arch and per-release-per-arch package availability holes. Common holes (e.g.loupemissing on bookworm,thunderbirdmissing on Ubuntu armhf/riscv64) live incommon.yaml. Per-DE overrides (e.g. KDE Plasma swappinggnome-text-editorforkateat the mid tier) live in the DE YAML.browser:virtual token. The literal stringbrowserinside any tier resolves to a real package name fromcommon.yaml'sbrowser:map at parse time. Map is keyed by(release, arch)because the same arch can resolve differently across releases:chromiumon amd64/arm64/armhf,firefox-esron riscv64 (Debian doesn't have afirefoxpackage).epiphany-browsereverywhere — Ubuntu'schromium/firefoxdebs are snap-shim wrappers that requiresnapd, which Armbian doesn't ship.common.yaml'stier_overrides.Bash side
module_desktops installnow requirestier=. The parser refuses to run without--tier./etc/armbian/desktop/<de>.tier(one line:minimal/mid/full) alongside the existing<de>.packages.upgrade de=X tier=Y— install the delta from current → target. Refuses to "upgrade" to a same/lower tier.downgrade de=X tier=Y— remove the delta from current → target, intersected with the install manifest so user-installed packages are never touched.set-tier de=X tier=Y— direction-agnostic, auto-detects upgrade vs downgrade. Used by the dialog menu.tier de=X— value-returning getter, prints the installed tier name on stdout.at-tier de=X tier=Y— silent gate, exit 0 if installed AND at the given tier. Used by dialog menu condition gates.statusis now silent on both paths (was leaking text into the menu).Dialog menu
*01(minimal),*05(mid),*06(full) — flat menu, "always all three per DE" per the agreed UX.*07(to minimal),*08(to mid),*09(to full), each gated to be visible only when the DE is installed AND not already at that tier.*03,*04) and uninstall entry (*02) are unchanged.What's still pending (out of scope for this PR)
libreoffice-style-breezeso the GTK frontend looks native; needs verification.module_desktopsthat aren't fixed here (sudoers in-place edit,autoclobbering gdm3 if no[daemon]section exists, container detection too narrow, etc.). Tracking issue or follow-up PR.Testing
This PR has been tested live on a QEMU x86 noble VM through repeated install/remove/upgrade/downgrade cycles for xfce + gnome:
module_desktops install de=xfce tier=minimal— installs cleanly, manifest written, default.target switchedmodule_desktops install de=xfce tier=mid— same, with browser + 6 mid-tier appsmodule_desktops install de=xfce tier=full— same, with office + creative toolsmodule_desktops upgrade de=xfce tier=midfrom minimal — installs the 7-package deltamodule_desktops upgrade de=xfce tier=fullfrom mid — installs the 6-package deltamodule_desktops downgrade de=xfce tier=minimalfrom full — removes 13 packages, intersection with manifest verified to not touch unrelated packagesmodule_desktops remove de=xfce— manifest-driven, console drops to login prompt without rebootmodule_desktops install de=gnome tier=minimal— Quick Settings shows Wi-Fi/Bluetooth/Power Profiles tiles, Settings → Printers can add a printer, autologin enable/disable works without clobbering user customizationVerified
module_desktops auto/manual/logincycle on gnome with the in-place gdm3 edit.How to test
Companion changes in other repos
splash=verbosefrom grub.sh / grub-riscv64.sh / 24 U-Boot bootscripts, addsGRUB_GFXPAYLOAD_LINUX=textto disable Ubuntu's vt.handoff=7 injection that broke the framebuffer console after Plymouth quit on CLI installs, and fixes the agetty--noclearso first-boot gets a clean login prompt instead of stacked kernel boot messages.developer-guide-desktops(started in a previous commit; updated in this session) — full developer guide rewrite covering the new tier model, schema, parser CLI, bash module API, lifecycle sections, common pitfalls, and security notes.Commit list
38 commits, 49 files changed (+1835 / -1490). The commit history is intentionally not squashed: each commit is a small focused change with a descriptive message, so any individual fix can be reverted in isolation if it turns out to be wrong.