diff --git a/README.md b/README.md index cb179e9..829a10e 100644 --- a/README.md +++ b/README.md @@ -301,18 +301,27 @@ On distros with older dracut (no `tpm2-tss` module), clevis is used as a fallbac | ----------------- | ------ | -------------- | ----------------------------------------- | | `enabled` | bool | `false` | Install desktop environment | | `environment` | string | `""` | `gnome`, `kde`, `sway`, or `hyprland` | -| `display_manager` | string | auto-detected | Override DM: `gdm`, `sddm`, or `greetd` | +| `display_manager` | string | auto-detected | Override DM: `gdm`, `sddm`, `plasma-login-manager`, `greetd`, or `ly` | | `autologin` | bool \| string | `false` | `false` to disable, or a username from `system.users` to auto-login that user | | `session` | string | auto-from-environment | Session to autologin into; overrides the per-environment default (sddm `.desktop` basename / greetd command) | | `groups` | list | `[]` | Opt-in package groups installed on top of the base set (keys of `desktop_package_groups`, e.g. `dev`) | All desktop environments are Wayland-only. `sway` and `hyprland` are available on Arch only; -`gnome` and `kde` are available on all three families. +`gnome` and `kde` are available on all three families. On enterprise Linux +(almalinux/rocky/rhel) the base desktop installs browser, PDF and image viewers but no +video player - none is packaged in the EL base repositories, and no third-party repo is +pulled in; add one from rpmfusion/flatpak if you need it. When `enabled: true`, the bootstrap installs the desktop environment packages, enables the display manager and bluetooth services, and sets the systemd default target to `graphical.target`. -Display manager auto-detection: gnome to gdm, kde to sddm, sway to greetd, hyprland to greetd. +Display manager auto-detection: gnome to gdm; kde to plasma-login-manager on Arch and +Fedora 44+ (Plasma 6.6), else sddm; sway and hyprland to greetd. + +`ly` is an explicit-only override (never auto-selected), available on Arch only, +and is desktop-agnostic - it can front any environment. It runs on `tty2` with +`getty@tty2` masked, and its autologin is written to `/etc/ly/config.ini`; set `session` +to the target session's `.desktop` basename (sway and hyprland resolve automatically). When `autologin` names a user, the matching display manager is configured to log that user in without a password prompt. `session` is resolved automatically per environment when left empty (gdm picks its default, diff --git a/roles/bootstrap/tasks/_desktop.yml b/roles/bootstrap/tasks/_desktop.yml index 43d1054..93ed346 100644 --- a/roles/bootstrap/tasks/_desktop.yml +++ b/roles/bootstrap/tasks/_desktop.yml @@ -9,6 +9,8 @@ _family_pkgs: "{{ bootstrap_desktop_packages[os_family] | default({}) }}" _de_config: "{{ _family_pkgs[_de] | default({}) }}" _base: "{{ bootstrap_desktop_base_packages[os_family] | default([]) }}" + _dm: "{{ system_cfg.features.desktop.display_manager | default('') }}" + _dm_override_pkg: "{{ (bootstrap_dm_override_packages[_dm] | default({}))[os_family] | default('') }}" _requested_groups: "{{ system_cfg.features.desktop.groups | default([]) }}" _group_pkgs: >- {{ @@ -20,16 +22,10 @@ | sum(start=[]) }} ansible.builtin.set_fact: - # GNOME ships under different dnf environment groups: Fedora uses - # workstation-product-environment, enterprise RHEL/Rocky/Alma use - # graphical-server-environment ("Server with GUI"). - _desktop_groups: >- - {{ ['graphical-server-environment'] - if (_de == 'gnome' and os_family == 'RedHat' and os != 'fedora') - else (_de_config.groups | default([])) }} + _desktop_groups: "{{ _de_config.groups | default([]) }}" _desktop_packages: >- {{ - ((_de_config.packages | default([])) + _base + _group_pkgs) + ((_de_config.packages | default([])) + _base + _group_pkgs + [_dm_override_pkg]) | reject('equalto', '') | unique | list @@ -61,7 +57,8 @@ {{ chroot_command }} dnf --releasever={{ os_version_major }} --setopt=install_weak_deps=False install -y {{ _desktop_packages | join(' ') }} Debian: >- - {{ chroot_command }} apt install -y --install-recommends {{ _desktop_packages | join(' ') }} + {{ chroot_command }} env DEBIAN_FRONTEND=noninteractive + apt install -y --install-recommends {{ _desktop_packages | join(' ') }} Archlinux: >- pacstrap /mnt {{ _desktop_packages | join(' ') }} ansible.builtin.command: "{{ _install_commands[os_family] }}" diff --git a/roles/bootstrap/vars/desktop.yml b/roles/bootstrap/vars/desktop.yml index 66e6de8..66528ad 100644 --- a/roles/bootstrap/vars/desktop.yml +++ b/roles/bootstrap/vars/desktop.yml @@ -1,16 +1,41 @@ --- -# Per-family desktop environment package definitions. -# Keyed by os_family -> environment -> groups (dnf group install) / packages. # Wayland only: gnome, kde, sway, hyprland. No X11/xorg-server, no X11-only DEs. -# DE sets carry the session bits + the DE-specific xdg portal backend; the -# shared base below (fonts/audio/portal core/power/viewer apps) is layered on -# top for every DE via bootstrap_desktop_base_packages. + +# plasma-login-manager on Arch/Fedora44+ (Plasma 6.6), else sddm. +bootstrap_kde_login_manager: >- + {{ + 'plasma-login-manager' + if (os == 'archlinux' or (os == 'fedora' and (os_version | int) >= 44)) + else 'sddm' + }} + +# Native DMs ride in each DE's package set; only explicit non-native overrides +# need a package here. ly is Arch-only (validation rejects it elsewhere first). +bootstrap_dm_override_packages: + ly: + Archlinux: ly + +# EL = non-fedora RedHat. +bootstrap_os_is_el: "{{ os in ['almalinux', 'rocky', 'rhel'] }}" +bootstrap_os_is_el10: "{{ bootstrap_os_is_el | bool and (os_version | default('0') | int) >= 10 }}" +# EL10 renames (evince->papers, eog->loupe, ppd->tuned-ppd); fira-code + mpv absent on EL. +bootstrap_desktop_browser: "{{ 'firefox-esr' if os == 'debian' else 'firefox' }}" +bootstrap_desktop_pdf: "{{ 'papers' if bootstrap_os_is_el10 | bool else 'evince' }}" +bootstrap_desktop_image: "{{ 'loupe' if bootstrap_os_is_el10 | bool else 'eog' }}" +bootstrap_desktop_power: "{{ 'tuned-ppd' if bootstrap_os_is_el10 | bool else 'power-profiles-daemon' }}" +bootstrap_desktop_redhat_codefont: "{{ '' if bootstrap_os_is_el | bool else 'fira-code-fonts' }}" +bootstrap_desktop_redhat_video: "{{ '' if bootstrap_os_is_el | bool else 'mpv' }}" + bootstrap_desktop_packages: RedHat: gnome: - groups: - - workstation-product-environment - packages: [] + groups: [] + packages: + - gnome-shell + - gnome-control-center + - nautilus + - gnome-session + - gdm kde: groups: [] packages: @@ -18,7 +43,7 @@ bootstrap_desktop_packages: - plasma-nm - plasma-pa - plasma-systemmonitor - - sddm + - "{{ bootstrap_kde_login_manager }}" - konsole - dolphin - kate @@ -41,7 +66,7 @@ bootstrap_desktop_packages: - plasma-desktop - plasma-nm - plasma-pa - - sddm + - "{{ bootstrap_kde_login_manager }}" - konsole - dolphin - kate @@ -62,7 +87,7 @@ bootstrap_desktop_packages: - plasma-desktop - plasma-nm - plasma-pa - - sddm + - "{{ bootstrap_kde_login_manager }}" - konsole - dolphin - kate @@ -102,26 +127,23 @@ bootstrap_desktop_packages: - qt6-wayland - bluez -# Shared desktop base, installed for EVERY DE whenever desktop.enabled. -# Fonts (noto + emoji + one nerd font), audio stack (pipewire + wireplumber + -# pipewire-pulse), xdg portal core, power-profiles-daemon, and viewer-only base -# apps (browser, PDF/image/video viewers). DE metas (gnome/plasma) bundle their -# own file manager + settings, so no file manager is added here - the wlroots -# DE sets above carry their own (nautilus). +# Installed for EVERY DE whenever desktop.enabled. No file manager here: DE metas +# bundle their own and the wlroots sets above carry nautilus. bootstrap_desktop_base_packages: RedHat: - google-noto-sans-fonts - google-noto-emoji-fonts - - fira-code-fonts + - "{{ bootstrap_desktop_redhat_codefont }}" - pipewire - wireplumber - pipewire-pulseaudio - xdg-desktop-portal - - power-profiles-daemon + - "{{ bootstrap_desktop_power }}" + - bluez - firefox - - evince - - eog - - mpv + - "{{ bootstrap_desktop_pdf }}" + - "{{ bootstrap_desktop_image }}" + - "{{ bootstrap_desktop_redhat_video }}" Debian: - fonts-noto - fonts-noto-color-emoji @@ -131,7 +153,8 @@ bootstrap_desktop_base_packages: - pipewire-pulse - xdg-desktop-portal - power-profiles-daemon - - firefox-esr + - bluez + - "{{ bootstrap_desktop_browser }}" - evince - eog - mpv @@ -144,14 +167,14 @@ bootstrap_desktop_base_packages: - pipewire-pulse - xdg-desktop-portal - power-profiles-daemon + - bluez - firefox - evince - loupe - mpv -# Opt-in package groups, selected per host via features.desktop.groups (a list -# of group names). _desktop.yml installs the union of the requested groups' -# packages. Empty selection by default. +# Opt-in groups selected per host via features.desktop.groups; the union of the +# requested groups' packages is installed. Empty selection by default. desktop_package_groups: dev: RedHat: diff --git a/roles/configuration/tasks/services.yml b/roles/configuration/tasks/services.yml index 440b162..79ac238 100644 --- a/roles/configuration/tasks/services.yml +++ b/roles/configuration/tasks/services.yml @@ -4,11 +4,20 @@ vars: _autologin: "{{ system_cfg.features.desktop.autologin | default(false) }}" ansible.builtin.set_fact: + # KDE resolves to the plasmalogin unit on Arch/Fedora44+ (Plasma 6.6), else sddm. _desktop_dm: >- {{ - system_cfg.features.desktop.display_manager + ('plasmalogin' + if system_cfg.features.desktop.display_manager == 'plasma-login-manager' + else system_cfg.features.desktop.display_manager) if (system_cfg.features.desktop.display_manager | length > 0) - else (configuration_desktop_dm_map[system_cfg.features.desktop.environment] | default('')) + else ( + ('plasmalogin' + if (os == 'archlinux' or (os == 'fedora' and (os_version | int) >= 44)) + else 'sddm') + if system_cfg.features.desktop.environment == 'kde' + else (configuration_desktop_dm_map[system_cfg.features.desktop.environment] | default('')) + ) }} _desktop_session: "{{ system_cfg.features.desktop.session | default('') }}" # Explicit session wins, else the per-environment command. Single source of @@ -52,6 +61,7 @@ - _configuration_platform.init_system == 'systemd' - system_cfg.features.desktop.enabled | bool - _desktop_dm | length > 0 + - _desktop_dm != 'ly' ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ _desktop_dm }}" register: configuration_enable_dm_result changed_when: configuration_enable_dm_result.rc == 0 @@ -70,14 +80,40 @@ register: _ufw_enable_result changed_when: _ufw_enable_result.rc == 0 failed_when: false - -- name: Set default systemd target to graphical +- name: Enable ly on its tty when: - _configuration_platform.init_system == 'systemd' - system_cfg.features.desktop.enabled | bool - ansible.builtin.command: "{{ chroot_command }} systemctl set-default graphical.target" - register: _desktop_target_result - changed_when: _desktop_target_result.rc == 0 + - _desktop_dm == 'ly' + vars: + _ly_tty: tty2 + block: + - name: Enable ly display manager + ansible.builtin.command: "{{ chroot_command }} systemctl enable ly@{{ _ly_tty }}.service" + register: configuration_enable_ly_result + changed_when: configuration_enable_ly_result.rc == 0 + failed_when: >- + configuration_enable_ly_result.rc != 0 + or 'No such file or directory' in (configuration_enable_ly_result.stderr | default('')) + or 'does not exist' in (configuration_enable_ly_result.stderr | default('')) + + # ly drives the VT itself; mask getty so logind never spawns a login on that tty. + - name: Mask getty on ly's tty + ansible.builtin.command: "{{ chroot_command }} systemctl mask getty@{{ _ly_tty }}.service" + register: configuration_mask_getty_result + changed_when: configuration_mask_getty_result.rc == 0 + failed_when: >- + configuration_mask_getty_result.rc != 0 + and 'No such file or directory' not in (configuration_mask_getty_result.stderr | default('')) + and 'does not exist' not in (configuration_mask_getty_result.stderr | default('')) + +- name: Set default systemd target + when: _configuration_platform.init_system == 'systemd' + vars: + _default_target: "{{ 'graphical.target' if system_cfg.features.desktop.enabled | bool else 'multi-user.target' }}" + ansible.builtin.command: "{{ chroot_command }} systemctl set-default {{ _default_target }}" + register: _set_default_target_result + changed_when: _set_default_target_result.rc == 0 - name: Enable PipeWire user services globally when: @@ -134,8 +170,7 @@ - _desktop_dm == 'gdm' - _desktop_autologin_user | length > 0 vars: - # Debian's gdm3 reads /etc/gdm3/daemon.conf; RedHat/Arch GDM read - # /etc/gdm/custom.conf. The keys are identical, only the path differs. + # Debian gdm3 reads daemon.conf; RedHat/Arch gdm read custom.conf. _gdm_dir: "/mnt/etc/{{ 'gdm3' if os_family == 'Debian' else 'gdm' }}" _gdm_conf: "{{ 'daemon.conf' if os_family == 'Debian' else 'custom.conf' }}" block: @@ -151,16 +186,20 @@ dest: "{{ _gdm_dir }}/{{ _gdm_conf }}" mode: "0644" -- name: Configure SDDM autologin +# SDDM and plasma-login-manager share the [Autologin] format and the KDE Wayland +# session; only the config dir differs (sddm.conf.d vs plasmalogin.conf.d). +- name: Configure SDDM / plasma-login-manager autologin when: - _configuration_platform.init_system == 'systemd' - system_cfg.features.desktop.enabled | bool - - _desktop_dm == 'sddm' + - _desktop_dm in ['sddm', 'plasmalogin'] - _desktop_autologin_user | length > 0 + vars: + _autologin_conf_dir: "/mnt/etc/{{ 'plasmalogin.conf.d' if _desktop_dm == 'plasmalogin' else 'sddm.conf.d' }}" block: - - name: Ensure SDDM config directory exists + - name: Ensure KDE login-manager config directory exists ansible.builtin.file: - path: /mnt/etc/sddm.conf.d + path: "{{ _autologin_conf_dir }}" state: directory mode: "0755" @@ -179,8 +218,29 @@ {%- set names = _kde_wayland_sessions.files | map(attribute='path') | map('basename') | list -%} {{ 'plasma.desktop' if 'plasma.desktop' in names else (names | first | default('')) }} - - name: Write SDDM autologin drop-in + - name: Write KDE login-manager autologin drop-in ansible.builtin.template: src: sddm-autologin.conf.j2 - dest: /mnt/etc/sddm.conf.d/10-autologin.conf + dest: "{{ _autologin_conf_dir }}/10-autologin.conf" mode: "0644" + +# ly ships a flat (sectionless) config.ini; edit it in place to keep upstream +# defaults. Both keys are required: an unresolved session writes 'null', which +# disables autologin rather than leaving it half-configured. +- name: Configure ly autologin + when: + - _configuration_platform.init_system == 'systemd' + - system_cfg.features.desktop.enabled | bool + - _desktop_dm == 'ly' + - _desktop_autologin_user | length > 0 + community.general.ini_file: + path: /mnt/etc/ly/config.ini + option: "{{ item.key }}" + value: "{{ item.value }}" + create: false + mode: "0644" + loop: + - key: auto_login_user + value: "{{ _desktop_autologin_user }}" + - key: auto_login_session + value: "{{ _greetd_session if (_greetd_session | length > 0) else 'null' }}" diff --git a/roles/configuration/vars/main.yml b/roles/configuration/vars/main.yml index ebd4048..03d79bc 100644 --- a/roles/configuration/vars/main.yml +++ b/roles/configuration/vars/main.yml @@ -43,9 +43,7 @@ configuration_desktop_dm_map: sway: greetd hyprland: greetd -# Per-environment session command for greetd-launched compositors (sway/hyprland): -# the executable greetd starts. kde/gnome use a display manager (sddm/gdm) whose -# Wayland session is resolved separately, so they are not in this map. +# greetd session commands for sway/hyprland (gnome/kde use a DM instead). configuration_desktop_session_cmd_map: sway: sway hyprland: Hyprland diff --git a/roles/global_defaults/defaults/main.yml b/roles/global_defaults/defaults/main.yml index c36434f..ac673ca 100644 --- a/roles/global_defaults/defaults/main.yml +++ b/roles/global_defaults/defaults/main.yml @@ -128,7 +128,7 @@ system_defaults: desktop: enabled: false environment: "" # gnome|kde|sway|hyprland - display_manager: "" # auto from environment when empty; override: gdm|sddm|greetd + display_manager: "" # auto from environment when empty; override: gdm|sddm|greetd|plasma-login-manager|ly autologin: false # false | username from system.users session: "" # session name/command for the autologin user groups: [] # opt-in package groups (keys of desktop_package_groups) diff --git a/roles/global_defaults/tasks/validation.yml b/roles/global_defaults/tasks/validation.yml index e143480..c7f3fd5 100644 --- a/roles/global_defaults/tasks/validation.yml +++ b/roles/global_defaults/tasks/validation.yml @@ -262,7 +262,7 @@ or os_family_map[os] | default('') == "Archlinux" - >- system_cfg.features.desktop.display_manager | default('') | length == 0 - or system_cfg.features.desktop.display_manager in ["gdm", "sddm", "greetd", "plasma-login-manager"] + or system_cfg.features.desktop.display_manager in ["gdm", "sddm", "greetd", "plasma-login-manager", "ly"] - >- system_cfg.features.desktop.display_manager | default('') != "greetd" or system_cfg.features.desktop.environment in ["sway", "hyprland"] @@ -275,6 +275,9 @@ - >- system_cfg.features.desktop.display_manager | default('') != "plasma-login-manager" or os == "archlinux" or (os == "fedora" and (os_version | int) >= 44) + - >- + system_cfg.features.desktop.display_manager | default('') != "ly" + or os == "archlinux" fail_msg: >- Invalid desktop config: environment '{{ system_cfg.features.desktop.environment }}' for os_family '{{ os_family_map[os] | default('Unknown') }}', @@ -282,7 +285,8 @@ gnome and kde are available on all families; sway and hyprland are Archlinux only. display_manager must be empty (auto) or match the environment's native DM: gnome->gdm, kde->plasma-login-manager on Arch/Fedora44+ else sddm, - sway/hyprland->greetd. Only that DM's package is installed, so a mismatched + sway/hyprland->greetd. ly is an explicit override on Arch only and may + front any environment. Only that DM's package is installed, so a mismatched override fails at enable time. quiet: true