Compare commits

...

4 Commits

Author SHA1 Message Date
stornic56 e0404ffa21 KDE: add Custom packages option with verified package list
Mirror the LXDE/XFCE Custom pattern: fixed base
(kde-plasma-desktop + sddm + konsole, plus
plasma-workspace-wayland on bookworm via the existing
gate) and a 17-entry checklist of extras whose names
are identical on bookworm and trixie (verified against
packages.debian.org). konsole is pinned because it is
only a Recommends of kde-baseapps on trixie. Empty
selection returns without installing; base + selection
are confirmed before any debconf or apt transaction.
knotes (gone on trixie) and konqueror/kwrite (legacy)
are excluded.
2026-10-20 07:39:35 -05:00
stornic56 afac6c7876 KDE: add bookworm Wayland gate and release-aware SDDM session probe
Bookworm's plasma-workspace ships only the X11 session; the Wayland
session (plasmawayland.desktop) lives in the separate
plasma-workspace-wayland package (a Recommends of plasma-desktop, now
listed explicitly for the same determinism as the explicit sddm). The
package does not exist on trixie, where plasma-workspace integrates
Wayland natively, so naming it off-bookworm would fail the apt
transaction: resolve it once per installer with _kde_wayland_pkg() and
build the package list in a conditional array so neither the pre-apply
summary nor the apt command ever shows an empty argument. Also correct
the header comment: bookworm still defaults SDDM to the X11 session,
while trixie makes Wayland primary (plasma.desktop in
wayland-sessions) with plasmax11.desktop as the X11 fallback.

Replace the hard-coded SDDM autologin session cascade with an
existence-based ordered probe (_sddm_session_candidates) that prefers
Wayland across releases, keeps the LXQt sessions last for when the
module starts offering LXQt, and reads its session roots from
SESSION_DIR_WAYLAND / SESSION_DIR_X11 for hermetic testing.

Extend tests/desktops.bats with 13 offline tests: gate helper output on
bookworm/trixie, captured apt commands for both installers, summary
content (no empty package line), and the ordered session probe against
fixture trees.
2026-10-20 07:39:35 -05:00
stornic56 3f7535f5d0 GNOME: use ptyxis instead of gnome-terminal on trixie+
gnome-core declares the OR-dependency "gnome-terminal (>= 3.56)
or gnome-console (>= 46) or ptyxis (>= 47)" (packages.debian.org
/trixie/gnome-core, verified 2026-10-16) and apt resolves an
unset OR-dep with the FIRST alternative, so the desktop pull would
install gnome-terminal by default. Listing the terminal package
explicitly in the same apt transaction steers the resolver to
ptyxis (48.5-1~deb13u1, GTK4) on trixie.

- _gnome_terminal_pkg(): trixie -> ptyxis, anything else or
  empty -> gnome-terminal (ptyxis does not exist in bookworm).
- _install_gnome_full/_install_gnome_core list the resolved
  package in the pre-apply summary and in the apt command.
- No purge or apt-mark: an already-installed gnome-terminal is
  left alone and both can coexist.
- 4 new BATS tests capture the apt command via the existing
  sudo/_run_cmd mocks: ptyxis on trixie (Full and Core),
  gnome-terminal fallback on bookworm and on empty codename.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 07:39:35 -05:00
stornic56 e3a749f38a LXDE: add Custom packages option and respect existing display manager
- New 'LXDE Custom (Choose packages)' menu entry: fixed base
  (lxde-core + xserver-xorg, lightdm only when no display manager
  is configured) plus a data-driven checklist of 16 extras.
- lxterminal, polkitd, notification-daemon and light-locker stay out
  of the checklist: they arrive via lxde-core Recommends (verified
  against packages.debian.org/trixie, 2026-10-16).
- Full/Core/Custom now honor an already-configured display manager
  via _existing_display_manager (GNOME/KDE pattern): no debconf
  preseed, no lightdm install, no enable over an existing DM.
- Extract _select_packages_checklist helper to utils.sh; XFCE
  Custom consumes it with no functional change.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 07:39:35 -05:00
7 changed files with 581 additions and 55 deletions
+24 -9
View File
@@ -299,6 +299,29 @@ configure_gdm3() {
done done
} }
# Ordered session candidates for SDDM autologin:
# Wayland first, then X11, per release. Existence
# decides (no codename gates). The LXQt sessions
# stay last for when the module starts offering LXQt.
_sddm_session_candidates() {
local wayland_dir="${SESSION_DIR_WAYLAND:-/usr/share/wayland-sessions}"
local x11_dir="${SESSION_DIR_X11:-/usr/share/xsessions}"
local candidate
for candidate in \
"$wayland_dir/plasmawayland.desktop" \
"$wayland_dir/plasma.desktop" \
"$x11_dir/plasma.desktop" \
"$x11_dir/plasmax11.desktop" \
"$wayland_dir/lxqt-wayland.desktop" \
"$x11_dir/lxqt.desktop"; do
if [ -f "$candidate" ]; then
basename "$candidate" .desktop
return 0
fi
done
return 0
}
configure_sddm() { configure_sddm() {
while true; do while true; do
local choice local choice
@@ -317,19 +340,11 @@ configure_sddm() {
;; ;;
2) 2)
local sddm_session="" local sddm_session=""
sddm_session=$(_sddm_session_candidates)
local sddm_user="${SUDO_USER:-}" local sddm_user="${SUDO_USER:-}"
if [ -z "$sddm_user" ] || [ "$sddm_user" = "root" ]; then if [ -z "$sddm_user" ] || [ "$sddm_user" = "root" ]; then
sddm_user=$(awk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd | head -1) sddm_user=$(awk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd | head -1)
fi fi
if [ -f /usr/share/wayland-sessions/plasmawayland.desktop ]; then
sddm_session="plasmawayland"
elif [ -f /usr/share/wayland-sessions/lxqt-wayland.desktop ]; then
sddm_session="lxqt-wayland"
elif [ -f /usr/share/xsessions/plasma.desktop ]; then
sddm_session="plasma"
elif [ -f /usr/share/xsessions/lxqt.desktop ]; then
sddm_session="lxqt"
fi
sudo mkdir -p /etc/sddm.conf.d sudo mkdir -p /etc/sddm.conf.d
cat <<EOF | sudo tee /etc/sddm.conf.d/autologin.conf >/dev/null cat <<EOF | sudo tee /etc/sddm.conf.d/autologin.conf >/dev/null
[Autologin] [Autologin]
+31 -6
View File
@@ -4,6 +4,23 @@
# Debian ships GNOME 48 (13), 43 (12) and 3.38 (11). # Debian ships GNOME 48 (13), 43 (12) and 3.38 (11).
# License GPL v3 # License GPL v3
# Pick the terminal emulator for this release. gnome-core
# declares the OR-dependency "gnome-terminal (>= 3.56) or
# gnome-console (>= 46) or ptyxis (>= 47)" (packages.debian.org
# /trixie/gnome-core, verified 2026-10-16) and apt resolves an
# unset OR-dep with the FIRST alternative, so gnome-core alone
# would pull gnome-terminal. Listing ptyxis in the same apt
# transaction satisfies the OR-dep with ptyxis instead. ptyxis
# (GTK4) exists in trixie (48.5-1~deb13u1) but NOT in bookworm
# (packages.debian.org/bookworm/ptyxis → no such package), hence
# the trixie-only gate. An already-installed gnome-terminal is
# left alone: both can coexist; no purge is ever attempted.
_gnome_terminal_pkg() {
case "${DEBIAN_CODENAME:-}" in
trixie) printf '%s\n' "ptyxis" ;;
*) printf '%s\n' "gnome-terminal" ;;
esac
}
gnome_menu() { gnome_menu() {
while true; do while true; do
local -a gn_items=() local -a gn_items=()
@@ -26,8 +43,12 @@ gnome_menu() {
_install_gnome_full() { _install_gnome_full() {
# Pre-apply summary: confirm the selection before any # Pre-apply summary: confirm the selection before any
# debconf or apt transaction starts. # debconf or apt transaction starts. The terminal package
_confirm_install_list "GNOME Full Desktop" gnome gdm3 || return # is resolved once (ptyxis on trixie, gnome-terminal
# elsewhere) so the summary lists what apt will install.
local terminal_pkg
terminal_pkg=$(_gnome_terminal_pkg)
_confirm_install_list "GNOME Full Desktop" gnome gdm3 "$terminal_pkg" || return
echo -e "${GREEN}Installing GNOME (Full Desktop)...${NC}" echo -e "${GREEN}Installing GNOME (Full Desktop)...${NC}"
@@ -41,7 +62,7 @@ _install_gnome_full() {
fi fi
echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections
_ensure_apt_updated _ensure_apt_updated
_run_cmd "GNOME Full" "sudo apt install -y gnome gdm3" \ _run_cmd "GNOME Full" "sudo apt install -y gnome gdm3 $terminal_pkg" \
"Installing GNOME (Full Meta Package) + GDM3..." "Installing GNOME (Full Meta Package) + GDM3..."
if [ "$target_dm" = "gdm3" ]; then if [ "$target_dm" = "gdm3" ]; then
sudo systemctl enable gdm3 sudo systemctl enable gdm3
@@ -55,8 +76,12 @@ _install_gnome_full() {
_install_gnome_core() { _install_gnome_core() {
# Pre-apply summary: confirm the selection before any # Pre-apply summary: confirm the selection before any
# debconf or apt transaction starts. # debconf or apt transaction starts. The terminal package
_confirm_install_list "GNOME Core" gnome-core gdm3 || return # is resolved once (ptyxis on trixie, gnome-terminal
# elsewhere) so the summary lists what apt will install.
local terminal_pkg
terminal_pkg=$(_gnome_terminal_pkg)
_confirm_install_list "GNOME Core" gnome-core gdm3 "$terminal_pkg" || return
echo -e "${GREEN}Installing GNOME Core (Minimal)...${NC}" echo -e "${GREEN}Installing GNOME Core (Minimal)...${NC}"
@@ -70,7 +95,7 @@ _install_gnome_core() {
fi fi
echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections
_ensure_apt_updated _ensure_apt_updated
_run_cmd "GNOME Core" "sudo apt install -y gnome-core gdm3" \ _run_cmd "GNOME Core" "sudo apt install -y gnome-core gdm3 $terminal_pkg" \
"Installing GNOME Core (Minimal) + GDM3..." "Installing GNOME Core (Minimal) + GDM3..."
if [ "$target_dm" = "gdm3" ]; then if [ "$target_dm" = "gdm3" ]; then
sudo systemctl enable gdm3 sudo systemctl enable gdm3
+120 -10
View File
@@ -1,16 +1,40 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# desktop_kde.sh — KDE Plasma desktop installer (SP2) # desktop_kde.sh — KDE Plasma desktop installer (SP2)
# Full: kde-standard + sddm | Minimal: kde-plasma-desktop + sddm # Full: kde-standard + sddm | Minimal: kde-plasma-desktop + sddm
# Bookworm ships Plasma 5.27.5; Wayland is the default # Custom: kde-plasma-desktop + sddm + konsole + extras elegidos
# session since Debian 12. # bookworm: Plasma 5.27.5 / KF5 / Qt5; SDDM defaults to
# X11 (plasma.desktop in xsessions); Wayland is optional
# via plasma-workspace-wayland (plasmawayland.desktop).
# trixie: Plasma 6.3.6 / KF6 / Qt6; Wayland is the
# primary session (plasma.desktop in wayland-sessions),
# X11 the alternative (plasmax11.desktop). sddm is only
# Recommends in the meta-kde chain, so the explicit sddm
# in both routes is what guarantees the DM (and
# xserver-xorg/xauth, hard deps of sddm in both releases).
# License GPL v3 # License GPL v3
# plasma-workspace-wayland exists only on bookworm:
# bookworm's plasma-workspace ships the X11 session only,
# and the Wayland session (plasmawayland.desktop) lives
# in this separate package (a Recommends of plasma-desktop,
# listed explicitly for the same determinism as the
# explicit sddm). trixie integrates Wayland into
# plasma-workspace itself and the package does not exist
# there (packages.debian.org/trixie/plasma-workspace-wayland),
# so naming it off-bookworm would fail the apt transaction.
_kde_wayland_pkg() {
case "${DEBIAN_CODENAME:-}" in
bookworm) printf '%s\n' "plasma-workspace-wayland" ;;
*) printf '%s\n' "" ;;
esac
}
kde_menu() { kde_menu() {
while true; do while true; do
local -a kd_items=() local -a kd_items=()
kd_items+=("1" "KDE Plasma (Full Desktop)") kd_items+=("1" "KDE Plasma (Full Desktop)")
kd_items+=("2" "KDE Plasma Desktop (Minimal)") kd_items+=("2" "KDE Plasma Desktop (Minimal)")
kd_items+=("3" "Back") kd_items+=("3" "KDE Custom (Choose packages)")
kd_items+=("4" "Back")
local choice local choice
choice=$(_menu "KDE Plasma" \ choice=$(_menu "KDE Plasma" \
"Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ "Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \
@@ -20,15 +44,23 @@ kde_menu() {
case "$choice" in case "$choice" in
1) _install_kde_full ;; 1) _install_kde_full ;;
2) _install_kde_minimal ;; 2) _install_kde_minimal ;;
3) break ;; 3) _install_kde_custom ;;
4) break ;;
esac esac
done done
} }
_install_kde_full() { _install_kde_full() {
# Pre-apply summary: confirm the selection before any # Pre-apply summary: confirm the selection before any
# debconf or apt transaction starts. # debconf or apt transaction starts. The Wayland
_confirm_install_list "KDE Plasma Full" kde-standard sddm || return # session package is resolved once (bookworm only;
# the package does not exist on trixie) so the
# summary lists exactly what apt will install.
local wayland_pkg
wayland_pkg=$(_kde_wayland_pkg)
local -a pkgs=(kde-standard sddm)
[ -n "$wayland_pkg" ] && pkgs+=("$wayland_pkg")
_confirm_install_list "KDE Plasma Full" "${pkgs[@]}" || return
echo -e "${GREEN}Installing KDE Plasma (Full)...${NC}" echo -e "${GREEN}Installing KDE Plasma (Full)...${NC}"
@@ -42,7 +74,7 @@ _install_kde_full() {
fi fi
echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections
_ensure_apt_updated _ensure_apt_updated
_run_cmd "KDE Full" "sudo apt install -y kde-standard sddm" \ _run_cmd "KDE Full" "sudo apt install -y ${pkgs[*]}" \
"Installing KDE Plasma (Full Meta Package) + SDDM..." "Installing KDE Plasma (Full Meta Package) + SDDM..."
if [ "$target_dm" = "sddm" ]; then if [ "$target_dm" = "sddm" ]; then
sudo systemctl enable sddm sudo systemctl enable sddm
@@ -56,8 +88,15 @@ _install_kde_full() {
_install_kde_minimal() { _install_kde_minimal() {
# Pre-apply summary: confirm the selection before any # Pre-apply summary: confirm the selection before any
# debconf or apt transaction starts. # debconf or apt transaction starts. The Wayland
_confirm_install_list "KDE Plasma Minimal" kde-plasma-desktop sddm || return # session package is resolved once (bookworm only;
# the package does not exist on trixie) so the
# summary lists exactly what apt will install.
local wayland_pkg
wayland_pkg=$(_kde_wayland_pkg)
local -a pkgs=(kde-plasma-desktop sddm)
[ -n "$wayland_pkg" ] && pkgs+=("$wayland_pkg")
_confirm_install_list "KDE Plasma Minimal" "${pkgs[@]}" || return
echo -e "${GREEN}Installing KDE Plasma Desktop (Minimal)...${NC}" echo -e "${GREEN}Installing KDE Plasma Desktop (Minimal)...${NC}"
@@ -71,7 +110,7 @@ _install_kde_minimal() {
fi fi
echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections
_ensure_apt_updated _ensure_apt_updated
_run_cmd "KDE Minimal" "sudo apt install -y kde-plasma-desktop sddm" \ _run_cmd "KDE Minimal" "sudo apt install -y ${pkgs[*]}" \
"Installing KDE Plasma Desktop (Minimal) + SDDM..." "Installing KDE Plasma Desktop (Minimal) + SDDM..."
if [ "$target_dm" = "sddm" ]; then if [ "$target_dm" = "sddm" ]; then
sudo systemctl enable sddm sudo systemctl enable sddm
@@ -82,3 +121,74 @@ _install_kde_minimal() {
echo -e "${GREEN}KDE Plasma Desktop installed. Default display manager: ${target_dm}.${NC}" echo -e "${GREEN}KDE Plasma Desktop installed. Default display manager: ${target_dm}.${NC}"
_offer_pipewire_after_desktop _offer_pipewire_after_desktop
} }
_install_kde_custom() {
# Fixed base (not selectable); extras chosen from the
# checklist. konsole is pinned because it is only a
# Recommends of kde-baseapps on trixie. All 17 extra
# names are identical on bookworm and trixie
# (packages.debian.org), so no per-release handling is
# needed beyond the shared Wayland gate. knotes is gone
# on trixie and konqueror/kwrite are legacy: excluded.
local cleaned
cleaned=$(_select_packages_checklist "KDE Custom" \
"Select the extra KDE packages to install:" \
"kate|advanced text editor" \
"ark|archive manager" \
"gwenview|image viewer" \
"okular|document viewer" \
"kcalc|calculator" \
"kde-spectacle|screenshot capture" \
"khelpcenter|documentation center" \
"sweeper|history cleaner" \
"print-manager|printer management" \
"kwalletmanager|password manager" \
"plasma-nm|network applet (needs NetworkManager)" \
"juk|music player" \
"dragonplayer|video player" \
"kmail|email client" \
"korganizer|calendar and tasks" \
"kaddressbook|contacts address book" \
"akregator|RSS feed reader")
[ -z "$cleaned" ] && return
# BH-004: reparse the space-separated selection into an array.
local -a kde_pkgs=()
local _pkg
while IFS= read -r _pkg; do
[ -n "$_pkg" ] && kde_pkgs+=("$_pkg")
done < <(echo "$cleaned" | tr ' ' '\n')
# Fixed base: kde-plasma-desktop + sddm + konsole, plus
# the Wayland session package on bookworm only.
local wayland_pkg
wayland_pkg=$(_kde_wayland_pkg)
local -a base_pkgs=(kde-plasma-desktop sddm konsole)
[ -n "$wayland_pkg" ] && base_pkgs+=("$wayland_pkg")
# Pre-apply summary: confirm base + selection before any
# debconf or apt transaction starts.
_confirm_install_list "KDE Custom" "${base_pkgs[@]}" "${kde_pkgs[@]}" || return
echo -e "${GREEN}Installing KDE Plasma (Custom)...${NC}"
# Keep an already-configured display manager intact.
local target_dm="sddm"
local keep_dm
keep_dm=$(_existing_display_manager)
if [ -n "$keep_dm" ] && [ "$keep_dm" != "sddm" ]; then
echo -e "${YELLOW}Display manager '${keep_dm}' is already configured — keeping it as default.${NC}"
target_dm="$keep_dm"
fi
echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections
_ensure_apt_updated
_run_cmd "KDE Custom" "sudo apt install -y ${base_pkgs[*]} ${kde_pkgs[*]}" \
"Installing KDE Plasma (Custom) + SDDM..."
if [ "$target_dm" = "sddm" ]; then
sudo systemctl enable sddm
fi
# Selection confirmed once above; release the
# per-package prompt suppression.
_SELECTION_CONFIRMED=0
echo -e "${GREEN}KDE Plasma installed. Default display manager: ${target_dm}.${NC}"
_offer_pipewire_after_desktop
}
+122 -8
View File
@@ -9,7 +9,8 @@ lxde_menu() {
local -a lx_items=() local -a lx_items=()
lx_items+=("1" "LXDE (Full Desktop)") lx_items+=("1" "LXDE (Full Desktop)")
lx_items+=("2" "LXDE Core (Minimal Install)") lx_items+=("2" "LXDE Core (Minimal Install)")
lx_items+=("3" "Back") lx_items+=("3" "LXDE Custom (Choose packages)")
lx_items+=("4" "Back")
local choice local choice
choice=$(_menu "LXDE" \ choice=$(_menu "LXDE" \
"Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ "Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \
@@ -19,27 +20,140 @@ lxde_menu() {
case "$choice" in case "$choice" in
1) _install_lxde_full ;; 1) _install_lxde_full ;;
2) _install_lxde_core ;; 2) _install_lxde_core ;;
3) break ;; 3) _install_lxde_custom ;;
4) break ;;
esac esac
done done
} }
_install_lxde_full() { _install_lxde_full() {
echo -e "${GREEN}Installing LXDE (Full) + LightDM...${NC}" # Pre-apply summary: confirm the selection before any
echo "lightdm shared/default-x-display-manager select lightdm" | sudo debconf-set-selections # debconf or apt transaction starts.
_confirm_install_list "LXDE Full Desktop" lxde lightdm || return
echo -e "${GREEN}Installing LXDE (Full Desktop)...${NC}"
# Keep an already-configured display manager intact.
local target_dm="lightdm"
local keep_dm
keep_dm=$(_existing_display_manager)
if [ -n "$keep_dm" ] && [ "$keep_dm" != "lightdm" ]; then
echo -e "${YELLOW}Display manager '${keep_dm}' is already configured — keeping it as default.${NC}"
target_dm="$keep_dm"
fi
echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections
_ensure_apt_updated
if [ "$target_dm" = "lightdm" ]; then
_run_cmd "LXDE Full" "sudo apt install -y lxde lightdm" \ _run_cmd "LXDE Full" "sudo apt install -y lxde lightdm" \
"Installing LXDE (Full Meta Package) + LightDM..." "Installing LXDE (Full Meta Package) + LightDM..."
sudo systemctl enable lightdm sudo systemctl enable lightdm
echo -e "${GREEN}LXDE installed. LightDM enabled.${NC}" else
_run_cmd "LXDE Full" "sudo apt install -y lxde" \
"Installing LXDE (Full Meta Package)..."
fi
# Selection confirmed once above; release the
# per-package prompt suppression.
_SELECTION_CONFIRMED=0
echo -e "${GREEN}LXDE installed. Default display manager: ${target_dm}.${NC}"
_offer_pipewire_after_desktop _offer_pipewire_after_desktop
} }
_install_lxde_core() { _install_lxde_core() {
echo -e "${GREEN}Installing LXDE Core + LightDM...${NC}" # Pre-apply summary: confirm the selection before any
echo "lightdm shared/default-x-display-manager select lightdm" | sudo debconf-set-selections # debconf or apt transaction starts.
_confirm_install_list "LXDE Core" lxde-core lightdm || return
echo -e "${GREEN}Installing LXDE Core (Minimal)...${NC}"
# Keep an already-configured display manager intact.
local target_dm="lightdm"
local keep_dm
keep_dm=$(_existing_display_manager)
if [ -n "$keep_dm" ] && [ "$keep_dm" != "lightdm" ]; then
echo -e "${YELLOW}Display manager '${keep_dm}' is already configured — keeping it as default.${NC}"
target_dm="$keep_dm"
fi
echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections
_ensure_apt_updated
if [ "$target_dm" = "lightdm" ]; then
_run_cmd "LXDE Core" "sudo apt install -y lxde-core lightdm" \ _run_cmd "LXDE Core" "sudo apt install -y lxde-core lightdm" \
"Installing LXDE Core (Minimal) + LightDM..." "Installing LXDE Core (Minimal) + LightDM..."
sudo systemctl enable lightdm sudo systemctl enable lightdm
echo -e "${GREEN}LXDE Core installed. LightDM enabled.${NC}" else
_run_cmd "LXDE Core" "sudo apt install -y lxde-core" \
"Installing LXDE Core (Minimal)..."
fi
# Selection confirmed once above; release the
# per-package prompt suppression.
_SELECTION_CONFIRMED=0
echo -e "${GREEN}LXDE Core installed. Default display manager: ${target_dm}.${NC}"
_offer_pipewire_after_desktop
}
_install_lxde_custom() {
# Fixed base (not selectable); extras chosen from the checklist.
# lxterminal, polkitd, notification-daemon and light-locker are
# intentionally absent: they arrive via lxde-core's Recommends
# (verified on packages.debian.org/trixie, 2026-10-16).
local cleaned
cleaned=$(_select_packages_checklist "LXDE Custom" \
"Select the extra LXDE packages to install:" \
"lxappearance|Theme/appearance config" \
"lxappearance-obconf|Openbox theme settings" \
"lxrandr|Monitor configuration" \
"lxinput|Keyboard/mouse settings" \
"lxsession-edit|Session editor" \
"lxhotkey-gtk|Keyboard shortcuts config" \
"lxde-icon-theme|LXDE icon theme" \
"gpicview|Image viewer" \
"xarchiver|Archive manager" \
"galculator|Calculator" \
"mousepad|Text editor" \
"lxpolkit|PolicyKit authentication agent" \
"lxtask|Task manager" \
"numlockx|Enable NumLock on startup" \
"network-manager-applet|NetworkManager applet" \
"gnome-disk-utility|Disk utility")
[ -z "$cleaned" ] && return
# BH-004: reparse the space-separated selection into an array.
local -a lxde_pkgs=()
local _pkg
while IFS= read -r _pkg; do
[ -n "$_pkg" ] && lxde_pkgs+=("$_pkg")
done < <(echo "$cleaned" | tr ' ' '\n')
if [ ${#lxde_pkgs[@]} -eq 0 ]; then
_msg "LXDE Custom" "No extra packages selected — installing the\nfixed base only (equivalent to LXDE Core)." 10 60
fi
# Fixed base: lxde-core + xserver-xorg always; lightdm only
# when no display manager is configured yet — never force it
# over an existing one.
local -a base_pkgs=(lxde-core xserver-xorg)
local install_dm=0 keep_dm
keep_dm=$(_existing_display_manager)
if [ -z "$keep_dm" ]; then
base_pkgs+=(lightdm)
install_dm=1
else
echo -e "${YELLOW}Display manager '${keep_dm}' is already configured — keeping it as default.${NC}"
fi
# Pre-apply summary: confirm base + selection before any
# debconf or apt transaction starts.
_confirm_install_list "LXDE Custom" "${base_pkgs[@]}" "${lxde_pkgs[@]}" || return
if [ "$install_dm" -eq 1 ]; then
echo "lightdm shared/default-x-display-manager select lightdm" | sudo debconf-set-selections
fi
_ensure_apt_updated
_run_cmd "LXDE Custom" "sudo apt install -y ${base_pkgs[*]} ${lxde_pkgs[*]}" \
"Installing LXDE base + selected packages..."
if [ "$install_dm" -eq 1 ]; then
sudo systemctl enable lightdm
fi
# Selection confirmed once above; release the
# per-package prompt suppression.
_SELECTION_CONFIRMED=0
echo -e "${GREEN}LXDE installed (base: ${base_pkgs[*]}).${NC}"
_offer_pipewire_after_desktop _offer_pipewire_after_desktop
} }
+16 -15
View File
@@ -51,27 +51,28 @@ _install_xfce_wayland() {
} }
_install_xfce_custom() { _install_xfce_custom() {
local -a items=() # BH-004: helper returns a space-separated selection;
for pkg in thunar xfdesktop4 xfwm4 xfce4-panel xfce4-terminal \ # reparsed into an array to avoid word splitting and injection.
xfce4-screenshooter ristretto mousepad xfce4-session \
xfce4-settings xfce4-power-manager; do
items+=("$pkg" "$pkg" "$(_state "$pkg")")
done
local choices
choices=$(_checklist "XFCE Custom" \
"Select the XFCE packages to install:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \
"${items[@]}")
[ -z "$choices" ] && return
local cleaned local cleaned
cleaned=$(echo "$choices" | tr -d '"') cleaned=$(_select_packages_checklist "XFCE Custom" \
"Select the XFCE packages to install:" \
"thunar|File manager" \
"xfdesktop4|Desktop manager" \
"xfwm4|Window manager" \
"xfce4-panel|Panel" \
"xfce4-terminal|Terminal emulator" \
"xfce4-screenshooter|Screenshot tool" \
"ristretto|Image viewer" \
"mousepad|Text editor" \
"xfce4-session|Session manager" \
"xfce4-settings|Settings manager" \
"xfce4-power-manager|Power manager")
[ -z "$cleaned" ] && return [ -z "$cleaned" ] && return
# BH-004: Convert to array to avoid word splitting and injection.
local -a xfce_pkgs=() local -a xfce_pkgs=()
local _pkg
while IFS= read -r _pkg; do while IFS= read -r _pkg; do
[ -n "$_pkg" ] && xfce_pkgs+=("$_pkg") [ -n "$_pkg" ] && xfce_pkgs+=("$_pkg")
done < <(echo "$cleaned" | tr ' ' '\n') done < <(echo "$cleaned" | tr ' ' '\n')
# Pre-apply summary: confirm the selection before # Pre-apply summary: confirm the selection before
# the batch apt transaction starts. # the batch apt transaction starts.
_confirm_install_list "XFCE Custom" "${xfce_pkgs[@]}" || return _confirm_install_list "XFCE Custom" "${xfce_pkgs[@]}" || return
+22
View File
@@ -805,6 +805,28 @@ _confirm_install_list() {
return 1 return 1
} }
# Show a multi-select checklist of packages and return the
# selection as a space-separated string ("" on cancel/Esc).
# Args: title prompt "pkg|description" [pkg|description ...]
# Already-installed packages are pre-checked via _state.
_select_packages_checklist() {
local title="$1" prompt="$2"
shift 2
local -a items=() entry
for entry in "$@"; do
local pkg="${entry%%|*}"
items+=("$pkg" "${entry#*|}" "$(_state "$pkg")")
done
local choices
# _checklist swallows whiptail's cancel (Esc) exit code
# via `|| true`, so a cancelled dialog looks like an empty
# selection here; an empty result means "nothing to install".
choices=$(_checklist "$title" "$prompt" \
$TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA "${items[@]}")
choices=$(echo "$choices" | tr -d '"')
echo "$choices"
}
# Install a package with confirmation prompt. # Install a package with confirmation prompt.
# Handles set -e: failures are caught and reported, not fatal. # Handles set -e: failures are caught and reported, not fatal.
_install_pkg() { _install_pkg() {
+240 -1
View File
@@ -34,9 +34,13 @@ setup() {
# Re-declare it globally for the _load_module tests. # Re-declare it globally for the _load_module tests.
declare -gA _LOADED_MODULES=() declare -gA _LOADED_MODULES=()
# Only function definitions at the top level: safe to source.
# Only function definitions at the top level: safe to source. # Only function definitions at the top level: safe to source.
source "${TEST_PROJECT_ROOT}/modules/desktop_display.sh" source "${TEST_PROJECT_ROOT}/modules/desktop_display.sh"
# The GNOME installers are pure function definitions too;
# desktop_display.sh (their DM/pipewire collaborators) is
# already sourced above.
source "${TEST_PROJECT_ROOT}/modules/desktops/desktop_gnome.sh"
# Fixture redirect for /etc/X11/default-display-manager: # Fixture redirect for /etc/X11/default-display-manager:
# the file exists but is empty, so `-s` fails and the # the file exists but is empty, so `-s` fails and the
# systemctl fallback is exercised deterministically. # systemctl fallback is exercised deterministically.
@@ -56,6 +60,10 @@ teardown() {
if [ -n "${MOCK_APT_ROOT:-}" ]; then if [ -n "${MOCK_APT_ROOT:-}" ]; then
rm -rf "$MOCK_APT_ROOT" rm -rf "$MOCK_APT_ROOT"
fi fi
if [ -n "${SESSION_FIXTURE:-}" ]; then
rm -rf "$SESSION_FIXTURE"
fi
unset SESSION_DIR_WAYLAND SESSION_DIR_X11
} }
# ── _existing_display_manager ──────────────────────── # ── _existing_display_manager ────────────────────────
@@ -188,3 +196,234 @@ _load_module_with_deps_marked() {
run _load_module no_such_module run _load_module no_such_module
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
} }
# ── terminal selection: ptyxis on trixie ──────────────
# gnome-core declares the OR-dep "gnome-terminal or
# gnome-console or ptyxis"; apt resolves an unset OR-dep
# with the first alternative, so listing the terminal
# explicitly in the same apt transaction is what steers
# the resolver. These tests assert the exact package
# that lands in the apt command.
#
# _gnome_capture_apt_cmd runs one installer with every
# external effect stubbed: _run_cmd is replaced so the
# apt command is captured instead of executed, and the
# PATH sudo mock stays in dry-run mode so debconf and
# systemctl invocations are logged, not executed.
_gnome_capture_apt_cmd() {
local installer="$1"
GNOME_APT_CMD=""
_confirm_install_list() { return 0; }
_ensure_apt_updated() { :; }
_run_cmd() { GNOME_APT_CMD="$2"; }
"$installer"
printf '%s\n' "$GNOME_APT_CMD"
}
@test "GNOME Full installs ptyxis on trixie" {
export DEBIAN_CODENAME="trixie"
export MOCK_SUDO_DRY_RUN=1
run _gnome_capture_apt_cmd _install_gnome_full
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y gnome gdm3 ptyxis"* ]]
[[ "$output" != *gnome-terminal* ]]
}
@test "GNOME Core installs ptyxis on trixie" {
export DEBIAN_CODENAME="trixie"
export MOCK_SUDO_DRY_RUN=1
run _gnome_capture_apt_cmd _install_gnome_core
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y gnome-core gdm3 ptyxis"* ]]
[[ "$output" != *gnome-terminal* ]]
}
@test "GNOME Full falls back to gnome-terminal off trixie" {
export DEBIAN_CODENAME="bookworm"
export MOCK_SUDO_DRY_RUN=1
run _gnome_capture_apt_cmd _install_gnome_full
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y gnome gdm3 gnome-terminal"* ]]
[[ "$output" != *ptyxis* ]]
}
@test "GNOME falls back to gnome-terminal when the codename is empty" {
# /etc/os-release missing or without VERSION_CODENAME
# leaves DEBIAN_CODENAME unset: the safe default is the
# historical behaviour.
export DEBIAN_CODENAME=""
export MOCK_SUDO_DRY_RUN=1
run _gnome_capture_apt_cmd _install_gnome_full
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y gnome gdm3 gnome-terminal"* ]]
[[ "$output" != *ptyxis* ]]
}
# ── KDE Plasma: bookworm Wayland gate ─────────────
# plasma-workspace-wayland exists only on bookworm:
# bookworm's plasma-workspace ships the X11 session
# only, so the explicit listing keeps the Wayland
# session in the transaction deterministically; off
# bookworm the package does not exist and nothing is
# named in the apt command.
# _kde_capture_apt_cmd runs one KDE installer with
# every external effect stubbed (same shape as the
# GNOME capture helper): the apt command is captured
# instead of executed, and the PATH sudo mock stays
# in dry-run mode so debconf and systemctl invocations
# are logged, not executed.
_kde_capture_apt_cmd() {
local installer="$1"
_load_module_with_deps_marked kde
KDE_APT_CMD=""
_confirm_install_list() { return 0; }
_ensure_apt_updated() { :; }
_run_cmd() { KDE_APT_CMD="$2"; }
"$installer"
printf '%s\n' "$KDE_APT_CMD"
}
# _kde_capture_confirm runs one KDE installer with the
# pre-apply summary captured instead of shown: _confirm
# records the list _confirm_install_list builds, so the
# summary content (never an empty package line) can be
# asserted directly. The pipewire follow-up offer is
# stubbed so the recorded list is the package summary.
_kde_capture_confirm() {
local installer="$1"
_load_module_with_deps_marked kde
KDE_CONFIRM_LIST=""
_confirm() { KDE_CONFIRM_LIST="$2"; return 0; }
_ensure_apt_updated() { :; }
_run_cmd() { :; }
_offer_pipewire_after_desktop() { :; }
"$installer"
printf '%s\n' "$KDE_CONFIRM_LIST"
}
@test "_kde_wayland_pkg prints plasma-workspace-wayland on bookworm" {
export DEBIAN_CODENAME="bookworm"
_load_module_with_deps_marked kde
run _kde_wayland_pkg
[ "$status" -eq 0 ]
[ "$output" = "plasma-workspace-wayland" ]
}
@test "_kde_wayland_pkg prints nothing off bookworm" {
export DEBIAN_CODENAME="trixie"
_load_module_with_deps_marked kde
run _kde_wayland_pkg
[ "$status" -eq 0 ]
[ -z "$output" ]
}
@test "KDE Full installs the Wayland session package on bookworm" {
export DEBIAN_CODENAME="bookworm"
export MOCK_SUDO_DRY_RUN=1
run _kde_capture_apt_cmd _install_kde_full
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y kde-standard sddm plasma-workspace-wayland"* ]]
}
@test "KDE Full omits the Wayland session package off bookworm" {
export DEBIAN_CODENAME="trixie"
export MOCK_SUDO_DRY_RUN=1
run _kde_capture_apt_cmd _install_kde_full
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y kde-standard sddm"* ]]
[[ "$output" != *"plasma-workspace-wayland"* ]]
}
@test "KDE Minimal installs the Wayland session package on bookworm" {
export DEBIAN_CODENAME="bookworm"
export MOCK_SUDO_DRY_RUN=1
run _kde_capture_apt_cmd _install_kde_minimal
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y kde-plasma-desktop sddm plasma-workspace-wayland"* ]]
}
@test "KDE Minimal omits the Wayland session package off bookworm" {
export DEBIAN_CODENAME="trixie"
export MOCK_SUDO_DRY_RUN=1
run _kde_capture_apt_cmd _install_kde_minimal
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y kde-plasma-desktop sddm"* ]]
[[ "$output" != *"plasma-workspace-wayland"* ]]
}
@test "KDE Minimal summary lists the Wayland session package on bookworm" {
export DEBIAN_CODENAME="bookworm"
export MOCK_SUDO_DRY_RUN=1
run _kde_capture_confirm _install_kde_minimal
[ "$status" -eq 0 ]
[[ "$output" == *"plasma-workspace-wayland"* ]]
}
@test "KDE Minimal summary shows no empty package line off bookworm" {
export DEBIAN_CODENAME="trixie"
export MOCK_SUDO_DRY_RUN=1
run _kde_capture_confirm _install_kde_minimal
[ "$status" -eq 0 ]
[[ "$output" == *"kde-plasma-desktop"* ]]
[[ "$output" == *"sddm"* ]]
[[ "$output" != *"plasma-workspace-wayland"* ]]
[[ "$output" != *"- "$'\n'* ]]
}
# ── _sddm_session_candidates: ordered session probe ──
# Existence decides (no codename gates) and Wayland is
# preferred. The roots are redirected to a per-test
# fixture tree through SESSION_DIR_WAYLAND /
# SESSION_DIR_X11 (same spirit as the DM_FILE fixture),
# so the probe never touches the real /usr/share tree.
_sddm_session_fixture() {
SESSION_FIXTURE="$(mktemp -d)"
mkdir -p "$SESSION_FIXTURE/wayland" "$SESSION_FIXTURE/x11"
SESSION_DIR_WAYLAND="$SESSION_FIXTURE/wayland"
SESSION_DIR_X11="$SESSION_FIXTURE/x11"
export SESSION_DIR_WAYLAND SESSION_DIR_X11
}
@test "SDDM session probe prefers bookworm Wayland (plasmawayland)" {
_sddm_session_fixture
: >"$SESSION_DIR_WAYLAND/plasmawayland.desktop"
: >"$SESSION_DIR_X11/plasma.desktop"
run _sddm_session_candidates
[ "$status" -eq 0 ]
[ "$output" = "plasmawayland" ]
}
@test "SDDM session probe picks trixie Wayland (plasma.desktop)" {
_sddm_session_fixture
: >"$SESSION_DIR_WAYLAND/plasma.desktop"
: >"$SESSION_DIR_X11/plasmax11.desktop"
run _sddm_session_candidates
[ "$status" -eq 0 ]
[ "$output" = "plasma" ]
}
@test "SDDM session probe falls back to trixie X11 (plasmax11)" {
_sddm_session_fixture
: >"$SESSION_DIR_X11/plasmax11.desktop"
run _sddm_session_candidates
[ "$status" -eq 0 ]
[ "$output" = "plasmax11" ]
}
@test "SDDM session probe keeps LXQt last when no KDE session exists" {
_sddm_session_fixture
: >"$SESSION_DIR_WAYLAND/lxqt-wayland.desktop"
: >"$SESSION_DIR_X11/lxqt.desktop"
run _sddm_session_candidates
[ "$status" -eq 0 ]
[ "$output" = "lxqt-wayland" ]
}
@test "SDDM session probe returns empty when no session file exists" {
_sddm_session_fixture
run _sddm_session_candidates
[ "$status" -eq 0 ]
[ -z "$output" ]
}