diff --git a/src/modules/displaymanager/displaymanager.conf b/src/modules/displaymanager/displaymanager.conf new file mode 100644 index 0000000..bbde1ef --- /dev/null +++ b/src/modules/displaymanager/displaymanager.conf @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# Configure one or more display managers (e.g. SDDM) +# with a "best effort" approach. +# +# This module also sets up autologin, if the feature is enabled in +# globalstorage (where it would come from the users page). +--- +# The DM module attempts to set up all the DMs found in this list, in the +# precise order listed. The displaymanagers list can also be set in +# globalstorage, and in that case it overrides the setting here. +# +# If *sysconfigSetup* is set to *true* (see below, only relevant for +# openSUSE derivatives) then this list is ignored and only sysconfig +# is attempted. You can also list "sysconfig" in this list instead. +# +displaymanagers: + - slim + - sddm + - lightdm + - gdm + - mdm + - lxdm + - greetd + +# Enable the following settings to force a desktop environment +# in your displaymanager configuration file. This will attempt +# to configure the given DE (without checking if it is installed). +# The DM configuration for each potential DM may **or may not** +# support configuring a default DE, so the keys are mandatory +# but their interpretation is up to the DM configuration. +# +# Subkeys of *defaultDesktopEnvironment* are (all mandatory): +# - *executable* a full path to an executable +# - *desktopFile* a .desktop filename +# +# If this is **not** set, then Calamares will look for installed +# DE's and pick the first one it finds that is actually installed. +# +# If this **is** set, and the *executable* key doesn't point to +# an installed file, then the .desktop file's TryExec key is +# used instead. +# + +#defaultDesktopEnvironment: +# executable: "startkde" +# desktopFile: "plasma" + +#If true, try to ensure that the user, group, /var directory etc. for the +#display manager are set up correctly. This is normally done by the distribution +#packages, and best left to them. Therefore, it is disabled by default. +basicSetup: false + +# If true, setup autologin for openSUSE. This only makes sense on openSUSE +# derivatives or other systems where /etc/sysconfig/displaymanager exists. +# +# The preferred way to pick sysconfig is to just list it in the +# *displaymanagers* list (as the only one). +# +sysconfigSetup: false + +# Some DMs have specific settings. These can be customized here. +# +# greetd has configurable user and group; the user and group is created if it +# does not exist, and the user is set as default-session user. +# +# Some greeters for greetd (e.g gtkgreet or regreet) have support for a user's GTK CSS style to change appearance. +# +# lightdm has a list of greeters to look for, preferring them in order if +# they are installed (if not, picks the alphabetically first greeter that is installed). +# +greetd: + greeter_user: "tom_bombadil" + greeter_group: "wheel" + greeter_css_location: "/etc/greetd/style.css" +lightdm: + preferred_greeters: ["lightdm-greeter.desktop", "slick-greeter.desktop"] +sddm: + configuration_file: "/etc/sddm.conf.d/autologin.conf" diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py new file mode 100644 index 0000000..17f764d --- /dev/null +++ b/src/modules/displaymanager/main.py @@ -0,0 +1,1046 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# === This file is part of Calamares - === +# +# SPDX-FileCopyrightText: 2014-2018 Philip Müller +# SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac +# SPDX-FileCopyrightText: 2014 Kevin Kofler +# SPDX-FileCopyrightText: 2017 Alf Gaida +# SPDX-FileCopyrightText: 2017 Bernhard Landauer +# SPDX-FileCopyrightText: 2017 2019, Adriaan de Groot +# SPDX-FileCopyrightText: 2019 Dominic Hayes +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Calamares is Free Software: see the License-Identifier above. +# + +import abc +import os +import libcalamares + +from libcalamares.utils import gettext_path, gettext_languages + +import gettext +_translation = gettext.translation("calamares-python", + localedir=gettext_path(), + languages=gettext_languages(), + fallback=True) +_ = _translation.gettext +_n = _translation.ngettext + +class DesktopEnvironment: + """ + Desktop Environment -- some utility functions for a desktop + environment (e.g. finding out if it is installed). This + is independent of the *Display Manager*, which is what + we're configuring in this module. + """ + def __init__(self, exec, desktop): + self.executable = exec + self.desktop_file = desktop + + def _search_executable(self, root_mount_point, pathname): + """ + Search for @p pathname within @p root_mount_point . + If the pathname is absolute, just check there inside + the target, otherwise earch in a sort-of-sensible $PATH. + + Returns the full (including @p root_mount_point) path + to that executable, or None. + """ + if pathname.startswith("/"): + path = [""] + else: + path = ["/bin/", "/usr/bin/", "/sbin/", "/usr/local/bin/"] + + for p in path: + absolute_path = "{!s}{!s}{!s}".format(root_mount_point, p, pathname) + if os.path.exists(absolute_path): + return absolute_path + return None + + def _search_tryexec(self, root_mount_point, absolute_desktop_file): + """ + Check @p absolute_desktop_file for a TryExec line and, if that is + found, search for the command (executable pathname) within + @p root_mount_point. The .desktop file must live within the + target root. + + Returns the full (including @p root_mount_point) for the executable + from TryExec, or None. + """ + assert absolute_desktop_file.startswith(root_mount_point) + with open(absolute_desktop_file, "r") as f: + for tryexec_line in [x for x in f.readlines() if x.startswith("TryExec")]: + try: + key, value = tryexec_line.split("=") + if key.strip() == "TryExec": + return self._search_executable(root_mount_point, value.strip()) + except: + pass + return None + + def find_executable(self, root_mount_point): + """ + Returns the full path of the configured executable within @p root_mount_point, + or None if it isn't found. May search in a semi-sensible $PATH. + """ + return self._search_executable(root_mount_point, self.executable) + + def find_desktop_file(self, root_mount_point): + """ + Returns the full path of the .desktop file within @p root_mount_point, + or None if it isn't found. Searches both X11 and Wayland sessions. + """ + x11_sessions = "{!s}/usr/share/xsessions/{!s}.desktop".format(root_mount_point, self.desktop_file) + wayland_sessions = "{!s}/usr/share/wayland-sessions/{!s}.desktop".format(root_mount_point, self.desktop_file) + for candidate in (x11_sessions, wayland_sessions): + if os.path.exists(candidate): + return candidate + return None + + def is_installed(self, root_mount_point): + """ + Check if this environment is installed in the + target system at @p root_mount_point. + """ + desktop_file = self.find_desktop_file(root_mount_point) + if desktop_file is None: + return False + + return (self.find_executable(root_mount_point) is not None or + self._search_tryexec(root_mount_point, desktop_file) is not None) + + def update_from_desktop_file(self, root_mount_point): + """ + Find thie DE in the target system at @p root_mount_point. + This can update the *executable* configuration value if + the configured executable isn't found but the TryExec line + from the .desktop file is. + + The .desktop file is mandatory for a DE. + + Returns True if the DE is installed. + """ + desktop_file = self.find_desktop_file(root_mount_point) + if desktop_file is None: + return False + + executable_file = self.find_executable(root_mount_point) + if executable_file is not None: + # .desktop found and executable as well. + return True + + executable_file = self._search_tryexec(root_mount_point, desktop_file) + if executable_file is not None: + # Found from the .desktop file, so update own executable config + if root_mount_point and executable_file.startswith(root_mount_point): + executable_file = executable_file[len(root_mount_point):] + if not executable_file: + # Somehow chopped down to nothing + return False + + if executable_file[0] != "/": + executable_file = "/" + executable_file + self.executable = executable_file + return True + # This is to double-check + return self.is_installed(root_mount_point) + + +# This is the list of desktop environments that Calamares looks +# for; if no default environment is **explicitly** configured +# in the `displaymanager.conf` then the first one from this list +# that is found, is used. +# +# Each DE has a sample executable to look for, and a .desktop filename. +# If the executable exists, the DE is assumed to be installed +# and to use the given .desktop filename. +# +# If the .desktop file exists and contains a TryExec line and that +# TryExec executable exists (searched in /bin, /usr/bin, /sbin and +# /usr/local/bin) then the DE is assumed to be installed +# and to use that .desktop filename. +desktop_environments = [ + DesktopEnvironment('/usr/bin/startplasma-x11', 'plasma'), # KDE Plasma 5.17+ + DesktopEnvironment('/usr/bin/startkde', 'plasma'), # KDE Plasma 5 + DesktopEnvironment('/usr/bin/startkde', 'kde-plasma'), # KDE Plasma 4 + DesktopEnvironment( + '/usr/bin/budgie-desktop', 'budgie-desktop' # Budgie v10 + ), + DesktopEnvironment( + '/usr/bin/budgie-session', 'budgie-desktop' # Budgie v8 + ), + DesktopEnvironment('/usr/bin/gnome-session', 'gnome'), + DesktopEnvironment('/usr/bin/cinnamon-session-cinnamon', 'cinnamon'), + DesktopEnvironment('/usr/bin/mate-session', 'mate'), + DesktopEnvironment('/usr/bin/enlightenment_start', 'enlightenment'), + DesktopEnvironment('/usr/bin/lxsession', 'LXDE'), + DesktopEnvironment('/usr/bin/startlxde', 'LXDE'), + DesktopEnvironment('/usr/bin/lxqt-session', 'lxqt'), + DesktopEnvironment('/usr/bin/pekwm', 'pekwm'), + DesktopEnvironment('/usr/bin/pantheon-session', 'pantheon'), + DesktopEnvironment('/usr/bin/startdde', 'deepin'), + DesktopEnvironment('/usr/bin/startxfce4', 'xfce'), + DesktopEnvironment('/usr/bin/openbox-session', 'openbox'), + DesktopEnvironment('/usr/bin/i3', 'i3'), + DesktopEnvironment('/usr/bin/awesome', 'awesome'), + DesktopEnvironment('/usr/bin/bspwm', 'bspwm'), + DesktopEnvironment('/usr/bin/herbstluftwm', 'herbstluftwm'), + DesktopEnvironment('/usr/bin/qtile', 'qtile'), + DesktopEnvironment('/usr/bin/xmonad', 'xmonad'), + DesktopEnvironment('/usr/bin/dwm', 'dwm'), + DesktopEnvironment('/usr/bin/jwm', 'jwm'), + DesktopEnvironment('/usr/bin/icewm-session', 'icewm-session'), + DesktopEnvironment('/usr/bin/fvwm3', 'fvwm3'), + DesktopEnvironment('/usr/bin/sway', 'sway'), + DesktopEnvironment('/usr/bin/ukui-session', 'ukui'), + DesktopEnvironment('/usr/bin/cutefish-session', 'cutefish-xsession'), + DesktopEnvironment('/usr/bin/river', 'river'), + DesktopEnvironment('/usr/bin/Hyprland', 'hyprland'), +] + + +def find_desktop_environment(root_mount_point): + """ + Checks which desktop environment is currently installed. + + :param root_mount_point: + :return: + """ + libcalamares.utils.debug("Using rootMountPoint {!r}".format(root_mount_point)) + for desktop_environment in desktop_environments: + if desktop_environment.is_installed(root_mount_point): + libcalamares.utils.debug(".. selected DE {!s}".format(desktop_environment.desktop_file)) + return desktop_environment + return None + + +class DisplayManager(metaclass=abc.ABCMeta): + """ + Display Manager -- a base class for DM configuration. + """ + name = None + executable = None + + def __init__(self, root_mount_point): + self.root_mount_point = root_mount_point + + def have_dm(self): + """ + Is this DM installed in the target system? + The default implementation checks for `executable` + in the target system. + """ + if self.executable is None: + return False + + bin_path = "{!s}/usr/bin/{!s}".format(self.root_mount_point, self.executable) + sbin_path = "{!s}/usr/sbin/{!s}".format(self.root_mount_point, self.executable) + return os.path.exists(bin_path) or os.path.exists(sbin_path) + + # The four abstract methods below are called in the order listed here. + # They must all be implemented by subclasses, but not all of them + # actually do something for all DMs. + + @abc.abstractmethod + def basic_setup(self): + """ + Do basic setup (e.g. users, groups, directory creation) for this DM. + """ + # Some implementations do nothing + + @abc.abstractmethod + def desktop_environment_setup(self, desktop_environment): + """ + Configure the given @p desktop_environment as the default one, in + the configuration files for this DM. + """ + # Many implementations do nothing + + @abc.abstractmethod + def greeter_setup(self): + """ + Additional setup for the greeter. + """ + # Most implementations do nothing + + @abc.abstractmethod + def set_autologin(self, username, do_autologin, default_desktop_environment): + """ + Configure the DM inside the given @p root_mount_point with + autologin (if @p do_autologin is True) for the given @p username. + If the DM supports it, set the default DE to @p default_desktop_environment + as well. + """ + + +class DMmdm(DisplayManager): + name = "mdm" + executable = "mdm" + + def set_autologin(self, username, do_autologin, default_desktop_environment): + # Systems with MDM as Desktop Manager + mdm_conf_path = os.path.join(self.root_mount_point, "etc/mdm/custom.conf") + + if os.path.exists(mdm_conf_path): + with open(mdm_conf_path, 'r') as mdm_conf: + text = mdm_conf.readlines() + + with open(mdm_conf_path, 'w') as mdm_conf: + for line in text: + if 'AutomaticLogin=' in line: + line = "" + if 'AutomaticLoginEnable=True' in line: + line = "" + if '[daemon]' in line: + if do_autologin: + line = ( + "[daemon]\n" + "AutomaticLogin={!s}\n" + "AutomaticLoginEnable=True\n".format(username) + ) + else: + line = ( + "[daemon]\n" + "AutomaticLoginEnable=False\n" + ) + + mdm_conf.write(line) + else: + with open(mdm_conf_path, 'w') as mdm_conf: + mdm_conf.write( + '# Calamares - Configure automatic login for user\n' + ) + mdm_conf.write('[daemon]\n') + + if do_autologin: + mdm_conf.write("AutomaticLogin={!s}\n".format(username)) + mdm_conf.write('AutomaticLoginEnable=True\n') + else: + mdm_conf.write('AutomaticLoginEnable=False\n') + + def basic_setup(self): + if libcalamares.utils.target_env_call( + ['getent', 'group', 'mdm'] + ) != 0: + libcalamares.utils.target_env_call( + ['groupadd', '-g', '128', 'mdm'] + ) + + if libcalamares.utils.target_env_call( + ['getent', 'passwd', 'mdm'] + ) != 0: + libcalamares.utils.target_env_call( + ['useradd', + '-c', '"Linux Mint Display Manager"', + '-u', '128', + '-g', 'mdm', + '-d', '/var/lib/mdm', + '-s', '/usr/bin/nologin', + 'mdm' + ] + ) + + libcalamares.utils.target_env_call( + ['passwd', '-l', 'mdm'] + ) + libcalamares.utils.target_env_call( + ['chown', 'root:mdm', '/var/lib/mdm'] + ) + libcalamares.utils.target_env_call( + ['chmod', '1770', '/var/lib/mdm'] + ) + + def desktop_environment_setup(self, default_desktop_environment): + os.system( + "sed -i \"s|default.desktop|{!s}.desktop|g\" " + "{!s}/etc/mdm/custom.conf".format( + default_desktop_environment.desktop_file, + self.root_mount_point + ) + ) + + def greeter_setup(self): + pass + + +class DMgdm(DisplayManager): + name = "gdm" + executable = "gdm" + config = None # Set by have_dm() + + def have_dm(self): + """ + GDM exists with different executable names, so search + for one of them and use it. + """ + candidates = ( + ( "gdm", "etc/gdm/custom.conf" ), + ( "gdm3", "etc/gdm3/daemon.conf" ), + ( "gdm3", "etc/gdm3/custom.conf" ), + ) + + def have_executable(executable : str): + bin_path = "{!s}/usr/bin/{!s}".format(self.root_mount_point, executable) + sbin_path = "{!s}/usr/sbin/{!s}".format(self.root_mount_point, executable) + return os.path.exists(bin_path) or os.path.exists(sbin_path) + + def have_config(config : str): + config_path = "{!s}/{!s}".format(self.root_mount_point, config) + return os.path.exists(config_path) + + # Look for an existing configuration file as a hint, then + # keep the found-executable name and config around for later. + for executable, config in candidates: + if have_config(config) and have_executable(executable): + self.executable = executable + self.config = config + return True + for executable, config in candidates: + if have_executable(executable): + self.executable = executable + self.config = config + return True + + return False + + def set_autologin(self, username, do_autologin, default_desktop_environment): + if self.config is None: + raise ValueError( "No config file for GDM has been set." ) + + # Systems with GDM as Desktop Manager + gdm_conf_path = os.path.join(self.root_mount_point, self.config) + + if os.path.exists(gdm_conf_path): + with open(gdm_conf_path, 'r') as gdm_conf: + text = gdm_conf.readlines() + + with open(gdm_conf_path, 'w') as gdm_conf: + for line in text: + if 'AutomaticLogin=' in line: + line = "" + if 'AutomaticLoginEnable=True' in line: + line = "" + if '[daemon]' in line: + if do_autologin: + line = ( + "[daemon]\n" + "AutomaticLogin={!s}\n" + "AutomaticLoginEnable=True\n".format(username) + ) + else: + line = "[daemon]\nAutomaticLoginEnable=False\n" + + gdm_conf.write(line) + else: + with open(gdm_conf_path, 'w') as gdm_conf: + gdm_conf.write( + '# Calamares - Enable automatic login for user\n' + ) + gdm_conf.write('[daemon]\n') + + if do_autologin: + gdm_conf.write("AutomaticLogin={!s}\n".format(username)) + gdm_conf.write('AutomaticLoginEnable=True\n') + else: + gdm_conf.write('AutomaticLoginEnable=False\n') + + if (do_autologin): + accountservice_dir = "{!s}/var/lib/AccountsService/users".format( + self.root_mount_point + ) + userfile_path = "{!s}/{!s}".format(accountservice_dir, username) + if os.path.exists(accountservice_dir): + with open(userfile_path, "w") as userfile: + userfile.write("[User]\n") + + if default_desktop_environment is not None: + userfile.write("XSession={!s}\n".format( + default_desktop_environment.desktop_file)) + + userfile.write("Icon=\n") + + def basic_setup(self): + if libcalamares.utils.target_env_call( + ['getent', 'group', 'gdm'] + ) != 0: + libcalamares.utils.target_env_call( + ['groupadd', '-g', '120', 'gdm'] + ) + + if libcalamares.utils.target_env_call( + ['getent', 'passwd', 'gdm'] + ) != 0: + libcalamares.utils.target_env_call( + ['useradd', + '-c', '"Gnome Display Manager"', + '-u', '120', + '-g', 'gdm', + '-d', '/var/lib/gdm', + '-s', '/usr/bin/nologin', + 'gdm' + ] + ) + + libcalamares.utils.target_env_call( + ['passwd', '-l', 'gdm'] + ) + libcalamares.utils.target_env_call( + ['chown', '-R', 'gdm:gdm', '/var/lib/gdm'] + ) + + def desktop_environment_setup(self, desktop_environment): + pass + + def greeter_setup(self): + pass + + +class DMlxdm(DisplayManager): + name = "lxdm" + executable = "lxdm" + + def set_autologin(self, username, do_autologin, default_desktop_environment): + # Systems with LXDM as Desktop Manager + lxdm_conf_path = os.path.join(self.root_mount_point, "etc/lxdm/lxdm.conf") + text = [] + + if os.path.exists(lxdm_conf_path): + with open(lxdm_conf_path, 'r') as lxdm_conf: + text = lxdm_conf.readlines() + + with open(lxdm_conf_path, 'w') as lxdm_conf: + for line in text: + if 'autologin=' in line: + if do_autologin: + line = "autologin={!s}\n".format(username) + else: + line = "# autologin=\n" + + lxdm_conf.write(line) + else: + return ( + _("Cannot write LXDM configuration file"), + _("LXDM config file {!s} does not exist").format(lxdm_conf_path) + ) + + def basic_setup(self): + if libcalamares.utils.target_env_call( + ['getent', 'group', 'lxdm'] + ) != 0: + libcalamares.utils.target_env_call( + ['groupadd', '--system', 'lxdm'] + ) + + libcalamares.utils.target_env_call( + ['chgrp', '-R', 'lxdm', '/var/lib/lxdm'] + ) + libcalamares.utils.target_env_call( + ['chgrp', 'lxdm', '/etc/lxdm/lxdm.conf'] + ) + libcalamares.utils.target_env_call( + ['chmod', '+r', '/etc/lxdm/lxdm.conf'] + ) + + def desktop_environment_setup(self, default_desktop_environment): + os.system( + "sed -i -e \"s|^.*session=.*|session={!s}|\" " + "{!s}/etc/lxdm/lxdm.conf".format( + default_desktop_environment.executable, + self.root_mount_point + ) + ) + + def greeter_setup(self): + pass + + +class DMlightdm(DisplayManager): + name = "lightdm" + executable = "lightdm" + + # Can be overridden in the .conf file. With no value it won't match any + # desktop file in the xgreeters directory and instead we end up picking + # the alphabetically first file there. + preferred_greeters = [] + + def set_autologin(self, username, do_autologin, default_desktop_environment): + # Systems with LightDM as Desktop Manager + # Ideally, we should use configparser for the ini conf file, + # but we just do a simple text replacement for now, as it + # worksforme(tm) + lightdm_conf_path = os.path.join( + self.root_mount_point, "etc/lightdm/lightdm.conf" + ) + text = [] + addseat = False + loopcount = 0 + + if os.path.exists(lightdm_conf_path): + with open(lightdm_conf_path, 'r') as lightdm_conf: + text = lightdm_conf.readlines() + # Check to make sure [SeatDefaults] or [Seat:*] is in the config, + # otherwise we'll risk malforming the config + addseat = '[SeatDefaults]' not in text and '[Seat:*]' not in text + + with open(lightdm_conf_path, 'w') as lightdm_conf: + if addseat: + # Prepend Seat line to start of file rather than leaving it without one + # This keeps the config from being malformed for LightDM + text = ["[Seat:*]\n"] + text + for line in text: + if 'autologin-user=' in line: + if do_autologin: + line = "autologin-user={!s}\n".format(username) + else: + line = "#autologin-user=\n" + + lightdm_conf.write(line) + else: + try: + # Create a new lightdm.conf file; this is documented to be + # read last, after everything in lightdm.conf.d/ + with open(lightdm_conf_path, 'w') as lightdm_conf: + if do_autologin: + lightdm_conf.write( + "[Seat:*]\nautologin-user={!s}\n".format(username)) + else: + lightdm_conf.write( + "[Seat:*]\n#autologin-user=\n") + except FileNotFoundError: + return ( + _("Cannot write LightDM configuration file"), + _("LightDM config file {!s} does not exist").format(lightdm_conf_path) + ) + + def basic_setup(self): + libcalamares.utils.target_env_call( + ['mkdir', '-p', '/run/lightdm'] + ) + + if libcalamares.utils.target_env_call( + ['getent', 'group', 'lightdm'] + ) != 0: + libcalamares.utils.target_env_call( + ['groupadd', '-g', '620', 'lightdm'] + ) + + if libcalamares.utils.target_env_call( + ['getent', 'passwd', 'lightdm'] + ) != 0: + libcalamares.utils.target_env_call( + ['useradd', '-c', + '"LightDM Display Manager"', + '-u', '620', + '-g', 'lightdm', + '-d', '/var/run/lightdm', + '-s', '/usr/bin/nologin', + 'lightdm' + ] + ) + + libcalamares.utils.target_env_call(['passwd', '-l', 'lightdm']) + libcalamares.utils.target_env_call(['chown', '-R', 'lightdm:lightdm', '/run/lightdm']) + libcalamares.utils.target_env_call(['chmod', '+r' '/etc/lightdm/lightdm.conf']) + + def desktop_environment_setup(self, default_desktop_environment): + os.system( + "sed -i -e \"s/^.*user-session=.*/user-session={!s}/\" " + "{!s}/etc/lightdm/lightdm.conf".format( + default_desktop_environment.desktop_file, + self.root_mount_point + ) + ) + + def find_preferred_greeter(self): + """ + On Debian, lightdm-greeter.desktop is typically a symlink managed + by update-alternatives pointing to /etc/alternatives/lightdm-greeter + which is also a symlink to a real .desktop file back in /usr/share/xgreeters/ + + Returns a path *into the mounted target* of the preferred greeter -- usually + a .desktop file that specifies where the actual executable is. May return + None to indicate nothing-was-found. + """ + greeters_dir = "usr/share/xgreeters" + greeters_target_path = os.path.join(self.root_mount_point, greeters_dir) + available_names = os.listdir(greeters_target_path) + available_names.sort() + desktop_names = [n for n in self.preferred_greeters if n in available_names] # Preferred ones + if desktop_names: + return desktop_names[0] + desktop_names = [n for n in available_names if n.endswith(".desktop")] # .. otherwise any .desktop + if desktop_names: + return desktop_names[0] + return None + + def greeter_setup(self): + lightdm_conf_path = os.path.join(self.root_mount_point, "etc/lightdm/lightdm.conf") + greeter_name = self.find_preferred_greeter() + + if greeter_name is not None: + greeter = os.path.basename(greeter_name) # Follow symlinks, hope they are not absolute + if greeter.endswith('.desktop'): + greeter = greeter[:-8] # Remove ".desktop" from end + + libcalamares.utils.debug("found greeter {!s}".format(greeter)) + os.system( + "sed -i -e \"s/^.*greeter-session=.*" + "/greeter-session={!s}/\" {!s}".format( + greeter, + lightdm_conf_path + ) + ) + libcalamares.utils.debug("{!s} configured as greeter.".format(greeter)) + else: + libcalamares.utils.error("No greeter found at all, preferred {!s}".format(self.preferred_greeters)) + return ( + _("Cannot configure LightDM"), + _("No LightDM greeter installed.") + ) + + +class DMslim(DisplayManager): + name = "slim" + executable = "slim" + + def set_autologin(self, username, do_autologin, default_desktop_environment): + # Systems with Slim as Desktop Manager + slim_conf_path = os.path.join(self.root_mount_point, "etc/slim.conf") + text = [] + + if os.path.exists(slim_conf_path): + with open(slim_conf_path, 'r') as slim_conf: + text = slim_conf.readlines() + + with open(slim_conf_path, 'w') as slim_conf: + for line in text: + if 'auto_login' in line: + if do_autologin: + line = 'auto_login yes\n' + else: + line = 'auto_login no\n' + + if do_autologin and 'default_user' in line: + line = "default_user {!s}\n".format(username) + + slim_conf.write(line) + else: + return ( + _("Cannot write SLIM configuration file"), + _("SLIM config file {!s} does not exist").format(slim_conf_path) + ) + + + def basic_setup(self): + pass + + def desktop_environment_setup(self, desktop_environment): + pass + + def greeter_setup(self): + pass + + +class DMsddm(DisplayManager): + name = "sddm" + executable = "sddm" + + configuration_file = "/etc/sddm.conf.d/autologin.conf" + + def set_autologin(self, username, do_autologin, default_desktop_environment): + import configparser + + # Systems with Sddm as Desktop Manager + sddm_conf_path = os.path.join(self.root_mount_point, self.configuration_file.lstrip('/')) + + sddm_config = configparser.ConfigParser(strict=False) + # Make everything case sensitive + sddm_config.optionxform = str + + if os.path.isfile(sddm_conf_path): + sddm_config.read(sddm_conf_path) + + if 'Autologin' not in sddm_config: + sddm_config.add_section('Autologin') + + if do_autologin: + sddm_config.set('Autologin', 'User', username) + elif sddm_config.has_option('Autologin', 'User'): + sddm_config.remove_option('Autologin', 'User') + + if default_desktop_environment is not None: + sddm_config.set( + 'Autologin', + 'Session', + default_desktop_environment.desktop_file + ) + + with open(sddm_conf_path, 'w') as sddm_config_file: + sddm_config.write(sddm_config_file, space_around_delimiters=False) + + + def basic_setup(self): + pass + + def desktop_environment_setup(self, desktop_environment): + pass + + def greeter_setup(self): + pass + + +class DMgreetd(DisplayManager): + name = "greetd" + executable = "greetd" + greeter_user = "greeter" + greeter_group = "greetd" + greeter_css_location = None + config_data = {} + + def os_path(self, path): + return os.path.join(self.root_mount_point, path) + + def config_path(self): + return self.os_path("etc/greetd/config.toml") + + def environments_path(self): + return self.os_path("etc/greetd/environments") + + def config_load(self): + import toml + + if (os.path.exists(self.config_path())): + with open(self.config_path(), "r") as f: + self.config_data = toml.load(f) + + self.config_data['terminal'] = dict(vt = "next") + + default_session_group = self.config_data.get('default_session', None) + if not default_session_group: + self.config_data['default_session'] = {} + + self.config_data['default_session']['user'] = self.greeter_user + + return self.config_data + + def config_write(self): + import toml + with open(self.config_path(), "w") as f: + toml.dump(self.config_data, f) + + def basic_setup(self): + if libcalamares.utils.target_env_call( + ['getent', 'group', self.greeter_group] + ) != 0: + libcalamares.utils.target_env_call( + ['groupadd', self.greeter_group] + ) + + if libcalamares.utils.target_env_call( + ['getent', 'passwd', self.greeter_user] + ) != 0: + libcalamares.utils.target_env_call( + ['useradd', + '-c', '"Greeter User"', + '-g', self.greeter_group, + '-s', '/bin/bash', + self.greeter_user + ] + ) + + def desktop_environment_setup(self, default_desktop_environment): + with open(self.environments_path(), 'w') as envs_file: + envs_file.write(default_desktop_environment.executable) + envs_file.write("\n") + + def greeter_setup(self): + pass + + def set_autologin(self, username, do_autologin, default_desktop_environment): + self.config_load() + + de_command = default_desktop_environment.executable + if os.path.exists(self.os_path("usr/bin/gtkgreet")) and os.path.exists(self.os_path("usr/bin/cage")): + self.config_data['default_session']['command'] = "cage -d -s -- gtkgreet" + if self.greeter_css_location: + self.config_data['default_session']['command'] += f" -s {self.greeter_css_location}" + elif os.path.exists(self.os_path("usr/bin/tuigreet")): + tuigreet_base_cmd = "tuigreet --remember --time --issue --asterisks --cmd " + self.config_data['default_session']['command'] = tuigreet_base_cmd + de_command + elif os.path.exists(self.os_path("usr/bin/ddlm")): + self.config_data['default_session']['command'] = "ddlm --target " + de_command + else: + self.config_data['default_session']['command'] = "agreety --cmd " + de_command + + if do_autologin: + # Log in as user, with given DE + self.config_data['initial_session'] = dict(command = de_command, user = username) + elif 'initial_session' in self.config_data: + # No autologin, remove any autologin that was copied from the live ISO + del self.config_data['initial_session'] + + self.config_write() + + +class DMsysconfig(DisplayManager): + name = "sysconfig" + executable = None + + def set_autologin(self, username, do_autologin, default_desktop_environment): + dmauto = "DISPLAYMANAGER_AUTOLOGIN" + + os.system( + "sed -i -e 's|^{!s}=.*|{!s}=\"{!s}\"|' " + "{!s}/etc/sysconfig/displaymanager".format( + dmauto, dmauto, + username if do_autologin else "", + self.root_mount_point + ) + ) + + + def basic_setup(self): + pass + + def desktop_environment_setup(self, desktop_environment): + pass + + def greeter_setup(self): + pass + + # For openSUSE-derivatives, there is only sysconfig to configure, + # and no special DM configuration for it. Instead, check that + # sysconfig is available in the target. + def have_dm(self): + config = "{!s}/etc/sysconfig/displaymanager".format(self.root_mount_point) + return os.path.exists(config) + + +# Collect all the subclasses of DisplayManager defined above, +# and index them based on the name property of each class. +display_managers = [ + (c.name, c) + for c in globals().values() + if type(c) is abc.ABCMeta and issubclass(c, DisplayManager) and c.name +] + + +def run(): + """ + Configure display managers. + + We acquire a list of displaymanagers, either from config or (overridden) + from globalstorage. This module will try to set up (including autologin) + all the displaymanagers in the list, in that specific order. Most distros + will probably only ship one displaymanager. + If a displaymanager is in the list but not installed, a debugging message + is printed and the entry ignored. + """ + # Get configuration settings for display managers + displaymanagers = None + if "displaymanagers" in libcalamares.job.configuration: + displaymanagers = libcalamares.job.configuration["displaymanagers"] + + if libcalamares.globalstorage.contains("displayManagers"): + displaymanagers = libcalamares.globalstorage.value("displayManagers") + + if ("sysconfigSetup" in libcalamares.job.configuration + and libcalamares.job.configuration["sysconfigSetup"]): + displaymanagers = ["sysconfig"] + + if not displaymanagers: + return ( + _("No display managers selected for the displaymanager module."), + _("The displaymanagers list is empty or undefined in both " + "globalstorage and displaymanager.conf.") + ) + + # Get instances that are actually installed + root_mount_point = libcalamares.globalstorage.value("rootMountPoint") + dm_impl = [] + dm_names = displaymanagers[:] + for dm in dm_names: + # Find the implementation class + dm_instance = None + impl = [ cls for name, cls in display_managers if name == dm ] + if len(impl) == 1: + dm_instance = impl[0](root_mount_point) + if dm_instance.have_dm(): + dm_impl.append(dm_instance) + else: + dm_instance = None + else: + libcalamares.utils.debug("{!s} has {!s} implementation classes.".format(dm, len(impl))) + + if dm_instance is None: + libcalamares.utils.debug("{!s} selected but not installed".format(dm)) + if dm in displaymanagers: + displaymanagers.remove(dm) + + if not dm_impl: + libcalamares.utils.warning( + "No display managers selected for the displaymanager module. " + "The list is empty after checking for installed display managers." + ) + return None + + # Pick up remaining settings + if "defaultDesktopEnvironment" in libcalamares.job.configuration: + entry = libcalamares.job.configuration["defaultDesktopEnvironment"] + default_desktop_environment = DesktopEnvironment( + entry["executable"], entry["desktopFile"] + ) + # Adjust if executable is bad, but desktopFile isn't. + if not default_desktop_environment.update_from_desktop_file(root_mount_point): + libcalamares.utils.warning( + "The configured default desktop environment, {!s}, " + "can not be found.".format(default_desktop_environment.desktop_file)) + else: + default_desktop_environment = find_desktop_environment( + root_mount_point + ) + + if "basicSetup" in libcalamares.job.configuration: + enable_basic_setup = libcalamares.job.configuration["basicSetup"] + else: + enable_basic_setup = False + + username = libcalamares.globalstorage.value("autoLoginUser") + if username is not None: + do_autologin = True + libcalamares.utils.debug("Setting up autologin for user {!s}.".format(username)) + else: + do_autologin = False + libcalamares.utils.debug("Unsetting autologin.") + + libcalamares.globalstorage.insert("displayManagers", displaymanagers) + + # Do the actual configuration and collect messages + dm_setup_message = [] + for dm in dm_impl: + dm_specific_configuration = libcalamares.job.configuration.get(dm.name, None) + if dm_specific_configuration and isinstance(dm_specific_configuration, dict): + for k, v in dm_specific_configuration.items(): + if hasattr(dm, k): + setattr(dm, k, v) + dm_message = None + if enable_basic_setup: + dm_message = dm.basic_setup() + if default_desktop_environment is not None and dm_message is None: + dm_message = dm.desktop_environment_setup(default_desktop_environment) + if dm_message is None: + dm_message = dm.greeter_setup() + if dm_message is None: + dm_message = dm.set_autologin(username, do_autologin, default_desktop_environment) + + if dm_message is not None: + dm_setup_message.append("{!s}: {!s}".format(*dm_message)) + + if dm_setup_message: + return ( + _("Display manager configuration was incomplete"), + "\n".join(dm_setup_message) + ) diff --git a/src/modules/shellprocess/shellprocess_final_chrooted.conf b/src/modules/shellprocess/shellprocess_final_chrooted.conf index cb1c4c8..4644361 100644 --- a/src/modules/shellprocess/shellprocess_final_chrooted.conf +++ b/src/modules/shellprocess/shellprocess_final_chrooted.conf @@ -153,3 +153,5 @@ script: - "-rm /etc/systemd/system/multi-user.target.wants/mount-data-flash.service" - "-rm /etc/systemd/system/dnscrypt-proxy-resolv.conf.service" - "-rm /etc/systemd/system/network-online.target.wants/dnscrypt-proxy-resolv.conf.service" + + - "-chown -R sddm:sddm /var/lib/sddm"