From 44856ba40d2170d946ccec07112feb26c609b972 Mon Sep 17 00:00:00 2001 From: Jonas Ã…dahl Date: Apr 08 2024 10:49:37 +0000 Subject: Add headless session helper files Originating from script and service file linked to in https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/215. --- diff --git a/0001-Add-headless-session-files.patch b/0001-Add-headless-session-files.patch new file mode 100644 index 0000000..9ea57eb --- /dev/null +++ b/0001-Add-headless-session-files.patch @@ -0,0 +1,219 @@ +From a8a0d952293337544da4681f0c896052eafd9d0f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 5 Apr 2024 16:44:07 +0200 +Subject: [PATCH] Add headless session files + +It consists of a python script for running the session, and a systemd +system service template. +--- + data/gnome-headless-session@.service | 6 + + data/meson.build | 4 + + utils/gdm-headless-login-session | 157 +++++++++++++++++++++++++++ + utils/meson.build | 5 + + 4 files changed, 172 insertions(+) + create mode 100644 data/gnome-headless-session@.service + create mode 100644 utils/gdm-headless-login-session + +diff --git a/data/gnome-headless-session@.service b/data/gnome-headless-session@.service +new file mode 100644 +index 000000000..269d16288 +--- /dev/null ++++ b/data/gnome-headless-session@.service +@@ -0,0 +1,6 @@ ++[Unit] ++Description=Headless desktop session ++ ++[Service] ++ExecStart=/usr/libexec/gdm-headless-login-session --user=%i ++Restart=on-failure +diff --git a/data/meson.build b/data/meson.build +index 2211e98b5..2df07cd32 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -221,3 +221,7 @@ if get_option('gdm-xsession') + install_dir: gdmconfdir, + ) + endif ++ ++headless_session_service = install_data('gnome-headless-session@.service', ++ install_dir: systemd_systemunitdir, ++ ) +diff --git a/utils/gdm-headless-login-session b/utils/gdm-headless-login-session +new file mode 100644 +index 000000000..e108be523 +--- /dev/null ++++ b/utils/gdm-headless-login-session +@@ -0,0 +1,157 @@ ++#!/usr/bin/env python3 ++ ++import argparse ++import pam ++import pwd ++import os ++import signal ++import sys ++ ++import gi ++gi.require_version('AccountsService', '1.0') ++from gi.repository import AccountsService, GLib ++ ++def run_desktop_in_new_session(pam_environment, user, session_desktop, tty_input, tty_output): ++ keyfile = GLib.KeyFile() ++ keyfile.load_from_data_dirs(f'wayland-sessions/{session_desktop}.desktop', ++ GLib.KeyFileFlags.NONE) ++ ++ try: ++ can_run_headless = keyfile.get_boolean(GLib.KEY_FILE_DESKTOP_GROUP, ++ 'X-GDM-CanRunHeadless') ++ except GLib.GError: ++ raise Exception(f"Session {session_desktop} can't run headlessly") ++ ++ if not can_run_headless: ++ raise Exception(f"Session {session_desktop} can't run headlessly") ++ ++ executable = keyfile.get_string(GLib.KEY_FILE_DESKTOP_GROUP, ++ GLib.KEY_FILE_DESKTOP_KEY_TRY_EXEC) ++ if GLib.find_program_in_path(executable) is None: ++ raise Exception(f"Invalid session {session_desktop}") ++ ++ command = keyfile.get_string(GLib.KEY_FILE_DESKTOP_GROUP, ++ GLib.KEY_FILE_DESKTOP_KEY_EXEC) ++ [success, args] = GLib.shell_parse_argv(command) ++ ++ pam_handle = pam.pam() ++ ++ for key, value in pam_environment.items(): ++ pam_handle.putenv(f'{key}={value}') ++ ++ if not pam_handle.authenticate(user, '', service='gdm-autologin', call_end=False): ++ raise Exception("Authentication failed") ++ ++ for key, value in pam_environment.items(): ++ pam_handle.putenv(f'{key}={value}') ++ ++ if pam_handle.open_session() != pam.PAM_SUCCESS: ++ raise Exception("Failed to open PAM session") ++ ++ session_environment = os.environ.copy() ++ session_environment.update(pam_handle.getenvlist()) ++ ++ user_info = pwd.getpwnam(user) ++ uid = user_info.pw_uid ++ gid = user_info.pw_gid ++ ++ old_tty_output = os.fdopen(os.dup(2), 'w') ++ ++ pid = os.fork() ++ if pid == 0: ++ try: ++ os.setsid() ++ except OSError as e: ++ print(f"Could not create new pid session: {e}", file=old_tty_output) ++ ++ try: ++ os.dup2(tty_input.fileno(), 0) ++ os.dup2(tty_output.fileno(), 1) ++ os.dup2(tty_output.fileno(), 2) ++ except OSError as e: ++ print(f"Could not set up standard i/o: {e}", file=old_tty_output) ++ ++ try: ++ os.initgroups(user, gid) ++ os.setgid(gid) ++ os.setuid(uid); ++ except OSError as e: ++ print(f"Could not become user {user} (uid={uid}): {e}", file=old_tty_output) ++ ++ try: ++ os.execvpe(args[0], args, session_environment) ++ except OSError as e: ++ print(f"Could not run program \"{' '.join(arguments)}\": {e}", file=old_tty_output) ++ os._exit(1) ++ ++ ++ def signal_handler(sig, frame): ++ os.kill(pid, sig) ++ ++ signal.signal(signal.SIGTERM, signal_handler) ++ ++ try: ++ (_, exit_code) = os.waitpid(pid, 0); ++ except KeyboardInterrupt: ++ os.kill(pid, signal.SIGTERM) ++ except OSError as e: ++ print(f"Could not wait for program to finish: {e}", file=old_tty_output) ++ ++ if os.WIFEXITED(exit_code): ++ exit_code = os.WEXITSTATUS(exit_code) ++ else: ++ os.kill(os.getpid(), os.WTERMSIG(exit_code)) ++ old_tty_output.close() ++ ++ if pam_handle.close_session() != pam.PAM_SUCCESS: ++ raise Exception("Failed to close PAM session") ++ ++ pam_handle.end() ++ ++ return exit_code ++ ++def wait_for_user_data(user): ++ main_context = GLib.MainContext.default() ++ while not user.is_loaded(): ++ main_context.iteration(True) ++ ++def main(): ++ parser = argparse.ArgumentParser(description='Run a desktop session in a PAM session as a specified user.') ++ parser.add_argument('--user', help='Username for which to run the session') ++ ++ args = parser.parse_args() ++ ++ if args.user is None: ++ parser.print_usage() ++ sys.exit(1) ++ ++ try: ++ tty_path = '/dev/null' ++ ++ tty_input = open(tty_path, 'r') ++ tty_output = open(tty_path, 'w') ++ except OSError as e: ++ raise Exception(f"Error opening /dev/null as tty associated with VT {vt}: {e}") ++ ++ user_manager = AccountsService.UserManager().get_default() ++ user = user_manager.get_user(args.user) ++ wait_for_user_data(user) ++ session_desktop = user.get_session() ++ if not session_desktop: ++ session_desktop = 'gnome' ++ ++ pam_environment = {} ++ pam_environment['XDG_SESSION_TYPE'] = 'wayland' ++ pam_environment['XDG_SESSION_CLASS'] = 'user' ++ pam_environment['XDG_SESSION_DESKTOP'] = session_desktop ++ ++ try: ++ result = run_desktop_in_new_session(pam_environment, args.user, session_desktop, tty_input, tty_output) ++ except Exception as e: ++ raise Exception(f"Error running desktop session \"{session_desktop}\": {e}") ++ tty_input.close() ++ tty_output.close() ++ sys.exit(result) ++ ++if __name__ == '__main__': ++ main() +diff --git a/utils/meson.build b/utils/meson.build +index e4141fb13..57dd6519f 100644 +--- a/utils/meson.build ++++ b/utils/meson.build +@@ -65,3 +65,8 @@ if distro != 'none' + install_dir: get_option('libexecdir'), + ) + endif ++ ++gdm_headless_login_session = install_data('gdm-headless-login-session', ++ install_mode: 'rwxr-xr-x', ++ install_dir: get_option('libexecdir'), ++ ) +-- +2.44.0 + diff --git a/gdm.spec b/gdm.spec index 6972ae4..386993c 100644 --- a/gdm.spec +++ b/gdm.spec @@ -25,6 +25,7 @@ Patch: 0001-udev-Stick-with-wayland-on-hybrid-nvidia-with-vendor.patch Patch: 0001-Honor-initial-setup-being-disabled-by-distro-install.patch Patch: 0001-data-add-system-dconf-databases-to-gdm-profile.patch Patch: 0001-xorg-detect.patch +Patch: 0001-Add-headless-session-files.patch BuildRequires: dconf BuildRequires: desktop-file-utils @@ -72,6 +73,7 @@ Requires: systemd >= 186 Requires: system-logos Requires: xhost xmodmap xrdb Requires: xorg-x11-xinit +Requires: python3-pam # Until the greeter gets dynamic user support, it can't # use a user bus @@ -242,6 +244,7 @@ fi %{_libexecdir}/gdm-simple-chooser %{_libexecdir}/gdm-wayland-session %{_libexecdir}/gdm-x-session +%{_libexecdir}/gdm-headless-login-session %{_sbindir}/gdm %{_bindir}/gdmflexiserver %{_bindir}/gdm-config @@ -273,6 +276,7 @@ fi %{_sysconfdir}/pam.d/gdm-launch-environment %{_udevrulesdir}/61-gdm.rules %{_unitdir}/gdm.service +%{_unitdir}/gnome-headless-session@.service %dir %{_userunitdir}/gnome-session@gnome-login.target.d/ %{_userunitdir}/gnome-session@gnome-login.target.d/session.conf %{_sysusersdir}/%{name}.conf