Files
Pathfinder f069026a7c libvirt group: check at summary time (final state) + OR-semantics gate
Fresh-VM timeline explained: on run 1 virt-manager installs AFTER the
niri module's prereq check ran (menu dispatch order), so the group add
was evaluated against a package state that did not include it yet. A
second run 'fixed' it — matching the observed behavior.

- New ensure_libvirt_group runs in the Summary section, seeing the FINAL
  package state; menu/module-time check kept as an early pass.
- pkg_installed is AND-semantics (all must be installed); the gate needs
  OR. New pkg_installed_any helper; both gates switched to it.
- Also fixed a set -e trap: '$done_any && ok' returned 1 (killing the
  script) when no optional tools were picked — replaced with an if.
All four gate cases harness-proven: virt-only, menu-pick, no-virt,
already-in-group.
2026-10-27 15:50:30 +01:00

146 lines
5.4 KiB
Bash

# shellcheck shell=bash
#------------------------------------------
# lib/session.sh — display manager and graphical session wiring
#
# DM policy:
# * A DE module leaves its native DM alone (GNOME→gdm, Plasma→sddm, XFCE→sddm).
# * A TWM module enforces SDDM, because SDDM is the only DM that presents both
# Wayland compositors and X11 sessions in one menu. One greeter, no surprises.
#------------------------------------------
if [[ -n "${_SB_SESSION_LOADED:-}" ]]; then
return 0
fi
_SB_SESSION_LOADED=1
# Name of the active display manager ("sddm", "gdm", "lightdm"), or "" if none.
detect_display_manager() {
local link=/etc/systemd/system/display-manager.service
if [[ -L "$link" ]]; then
basename "$(readlink "$link")" | sed 's/\.service$//'
else
echo ""
fi
}
enable_graphical_target() {
sudo systemctl set-default graphical.target
}
# Make $1 the display manager, disabling whatever is active now.
switch_display_manager() {
local target="$1"
local current
current=$(detect_display_manager)
if [[ "$current" == "$target" ]]; then
ok "Display manager already $target."
else
if [[ -n "$current" ]]; then
info "Disabling current display manager: $current"
# disable may fail if the unit vanished; that is not fatal
sudo systemctl disable "$current" 2>/dev/null || true
fi
info "Enabling display manager: $target"
sudo systemctl enable "$target"
fi
enable_graphical_target
}
# Register a Wayland session so the DM can list it.
# $1 = identifier, $2 = display name, $3 = command
write_wayland_session() {
local id="$1" name="$2" exec_cmd="$3"
local file="/usr/share/wayland-sessions/${id}.desktop"
if [[ -f "$file" ]]; then
ok "Wayland session already registered: $id"
return 0
fi
info "Registering Wayland session: $id"
sudo mkdir -p "$(dirname "$file")"
printf '[Desktop Entry]\nName=%s\nComment=%s\nExec=%s\nType=Application\nDesktopNames=%s\n' \
"$name" "$name session" "$exec_cmd" "$id" | sudo tee "$file" >/dev/null
sudo chmod 644 "$file"
}
# Register an X11 session so the DM can list it.
write_x11_session() {
local id="$1" name="$2" exec_cmd="$3"
local file="/usr/share/xsessions/${id}.desktop"
if [[ -f "$file" ]]; then
ok "X11 session already registered: $id"
return 0
fi
info "Registering X11 session: $id"
# A bare Fedora has no X session yet, so the directory may not exist.
sudo mkdir -p "$(dirname "$file")"
printf '[Desktop Entry]\nName=%s\nComment=%s\nExec=%s\nType=Application\n' \
"$name" "$name session" "$exec_cmd" | sudo tee "$file" >/dev/null
sudo chmod 644 "$file"
}
# Common login-time plumbing every Wayland compositor wants on a bare install.
install_session_prereqs() {
# polkit-kde ships the agent (polkit-gnome is orphaned, EPEL-only).
# BUT Fedora's autostart .desktop carries X-systemd-skip=true (and a
# OnlyShowIn on some versions): niri starts apps via systemd's
# xdg-desktop-autostart.target, which honors those flags -> agent never
# starts in a niri session (VM-proven twice). Fighting .desktop semantics
# is fragile, so we ship our own user unit bound to
# graphical-session.target, which niri/Hyprland/MangoWC all bring up.
install_packages \
xdg-desktop-portal \
polkit \
polkit-kde \
pipewire \
pipewire-pulseaudio \
wireplumber
local unit_dir="/etc/systemd/user"
if [[ ! -f "$unit_dir/polkit-agent.service" ]]; then
info "Installing polkit-agent user unit (compositor-safe)..."
sudo mkdir -p "$unit_dir"
sudo tee "$unit_dir/polkit-agent.service" >/dev/null <<'UNIT'
[Unit]
Description=PolicyKit Authentication Agent (for bare compositors)
PartOf=graphical-session.target
[Service]
# polkit-kde's binary on Fedora (kf6 path). Restart in case the agent dies
# before the session settles.
ExecStart=/usr/libexec/kf6/polkit-kde-authentication-agent-1
Restart=on-failure
RestartSec=2
[Install]
WantedBy=graphical-session.target
UNIT
fi
# Enable per-user: preset-all would also do, but be explicit. This is a
# global unit dir; each user links it at first login via the preset.
if [[ ! -f /etc/systemd/user/graphical-session.target.wants/polkit-agent.service ]]; then
sudo mkdir -p /etc/systemd/user/graphical-session.target.wants
sudo ln -sf ../polkit-agent.service \
/etc/systemd/user/graphical-session.target.wants/polkit-agent.service
fi
# libvirt group: members manage qemu:///system without a polkit prompt.
# Without this, virt-manager dies on "access denied by policy" even with
# a working agent (VM-proven). Idempotent; applies after re-login.
#
# Gate on the DAEMON, not the 'libvirt' metapkg name: virt-manager only
# requires libvirt-glib client-side, so on a fresh VM libvirt can be
# functionally present via libvirt-daemon* without rpm -q libvirt passing
# (VM-proven: user stuck in yuri+wheel with virt-manager installed).
if pkg_installed_any libvirt libvirt-daemon libvirt-daemon-driver-qemu virt-manager \
|| [[ -x /usr/sbin/libvirtd ]]; then
local real_user="${SUDO_USER:-$USER}"
if ! id -nG "$real_user" 2>/dev/null | grep -qw libvirt; then
info "Adding $real_user to the libvirt group (re-login to apply)..."
sudo usermod -aG libvirt "$real_user"
fi
fi
}