diff --git a/data/meson.build b/data/meson.build index ea7db46..22e446a 100644 --- a/data/meson.build +++ b/data/meson.build @@ -41,14 +41,18 @@ desktop_file = i18n.merge_file( install_dir: join_paths(get_option('datadir'), 'applications'), ) -custom_target( - 'install-autostart-desktop', - input: desktop_file, +# System-wide autostart entry (XDG autostart, not copied per-user via skel). +# The app self-gates on launch: with --autostart it exits immediately when the +# per-user completion marker exists or when running in a live session. A +# per-user file of the same name in ~/.config/autostart (from pre-0.2.16 skel +# copies) shadows this entry per the XDG autostart spec. +autostart_file = i18n.merge_file( + input: 'org.frostyard.FirstSetup.autostart.desktop.in', output: 'org.frostyard.FirstSetup.autostart.desktop', - command: ['cp', '@INPUT@', '@OUTPUT@'], - install_dir: join_paths(get_option('sysconfdir'), 'skel', '.config', 'autostart'), - build_by_default: true, - install : true, + type: 'desktop', + po_dir: '../po', + install: true, + install_dir: join_paths(get_option('sysconfdir'), 'xdg', 'autostart'), ) desktop_utils = find_program('desktop-file-validate', required: false) @@ -56,6 +60,9 @@ if desktop_utils.found() test('Validate desktop file', desktop_utils, args: [desktop_file] ) + test('Validate autostart desktop file', desktop_utils, + args: [autostart_file] + ) endif install_data( diff --git a/data/org.frostyard.FirstSetup.autostart.desktop.in b/data/org.frostyard.FirstSetup.autostart.desktop.in new file mode 100644 index 0000000..0e04ec7 --- /dev/null +++ b/data/org.frostyard.FirstSetup.autostart.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=SNOW First Setup +Exec=snow-first-setup --autostart +Icon=org.frostyard.FirstSetup +Terminal=false +Type=Application +Categories=System;GTK; +StartupNotify=true +NoDisplay=true diff --git a/debian/snow-first-setup.maintscript b/debian/snow-first-setup.maintscript new file mode 100644 index 0000000..918d724 --- /dev/null +++ b/debian/snow-first-setup.maintscript @@ -0,0 +1 @@ +rm_conffile /etc/skel/.config/autostart/org.frostyard.FirstSetup.autostart.desktop 0.2.16~ diff --git a/snow_first_setup/application.py b/snow_first_setup/application.py index 9b7af6c..bbb406d 100644 --- a/snow_first_setup/application.py +++ b/snow_first_setup/application.py @@ -100,6 +100,14 @@ def __register_arguments(self): _("Force the regular mode, independant of group."), None, ) + self.add_main_option( + "autostart", + ord("a"), + GLib.OptionFlags.NONE, + GLib.OptionArg.NONE, + _("Launched by the system autostart entry; exits early if setup is already complete (handled in main)."), + None, + ) self.add_main_option( "oem-mode", ord("o"), diff --git a/snow_first_setup/core/backend.py b/snow_first_setup/core/backend.py index 64b00b1..1a6dbef 100644 --- a/snow_first_setup/core/backend.py +++ b/snow_first_setup/core/backend.py @@ -46,6 +46,19 @@ def set_hostname(hostname: str): def set_theme(theme: str) -> str|None: return run_script("theme", [theme]) +def setup_complete_marker_path() -> str: + """Path of the per-user marker written when first setup completes. + + The system-wide autostart entry (installed to /etc/xdg/autostart) launches + the app with --autostart, which exits immediately when this marker exists. + """ + config_home = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config") + return os.path.join(config_home, "snow-first-setup.done") + +def is_setup_complete() -> bool: + """Check whether the current user already completed first setup.""" + return os.path.exists(setup_complete_marker_path()) + def is_live_session() -> bool: """Check if the system is running in live session mode. @@ -83,8 +96,8 @@ def remove_first_setup_user(): def oem_complete(): return run_script("oemcomplete", [], root=True) -def remove_autostart_file(): - return run_script("remove-autostart-file", []) +def mark_setup_complete(): + return run_script("complete-setup", []) def _setup_system(): return run_script("setup-system", []) diff --git a/snow_first_setup/main.py b/snow_first_setup/main.py index 77459fa..ce124ff 100644 --- a/snow_first_setup/main.py +++ b/snow_first_setup/main.py @@ -29,6 +29,15 @@ def main(version, moduledir: str, localedir: str): sys.exit(1) return + # When launched via the system-wide autostart entry (/etc/xdg/autostart), + # exit immediately if this user already completed first setup or if this + # is a live session (the live media runs the installer instead). Checked + # before any GTK/resource loading so the gate is essentially free. + if "--autostart" in sys.argv: + import snow_first_setup.core.backend as early_backend + if early_backend.is_setup_complete() or early_backend.is_live_session(): + return 0 + signal.signal(signal.SIGINT, signal.SIG_DFL) locale.bindtextdomain('snow-first-setup', localedir) locale.textdomain('snow-first-setup') diff --git a/snow_first_setup/scripts/complete-setup b/snow_first_setup/scripts/complete-setup new file mode 100755 index 0000000..f7ee20a --- /dev/null +++ b/snow_first_setup/scripts/complete-setup @@ -0,0 +1,24 @@ +#!/bin/bash +if ! [ -z "$1" ]; then + echo "usage:" + echo "complete-setup" + exit 5 +fi + +if [ "$UID" == "0" ]; then + echo "this script must be run as a regular user" + exit 7 +fi + +config_home="${XDG_CONFIG_HOME:-$HOME/.config}" + +# Record per-user completion. The system-wide autostart entry in +# /etc/xdg/autostart launches the app with --autostart, which exits +# immediately when this marker exists. +mkdir -p "$config_home" +touch "$config_home/snow-first-setup.done" + +# Legacy cleanup: users provisioned before v0.2.16 received a per-user +# autostart copy via /etc/skel; remove it if present so it no longer +# shadows (or duplicates) the system-wide entry. +rm -f "$config_home/autostart/org.frostyard.FirstSetup.autostart.desktop" diff --git a/snow_first_setup/scripts/meson.build b/snow_first_setup/scripts/meson.build index b489d32..e392180 100755 --- a/snow_first_setup/scripts/meson.build +++ b/snow_first_setup/scripts/meson.build @@ -18,7 +18,7 @@ sources = [ 'setup-system', 'user', 'remove-first-setup-user', - 'remove-autostart-file', + 'complete-setup', 'install-to-disk', 'enroll-key', 'recovery-key', diff --git a/snow_first_setup/scripts/remove-autostart-file b/snow_first_setup/scripts/remove-autostart-file deleted file mode 100755 index c282901..0000000 --- a/snow_first_setup/scripts/remove-autostart-file +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -if ! [ -z "$1" ]; then - echo "usage:" - echo "remove-autostart-file" - exit 5 -fi - -if [ "$UID" == "0" ]; then - echo "this script must be run as a regular user" - exit 7 -fi - -rm ~/.config/autostart/org.frostyard.FirstSetup.autostart.desktop diff --git a/snow_first_setup/views/progress.py b/snow_first_setup/views/progress.py index b62966a..5a0a13a 100644 --- a/snow_first_setup/views/progress.py +++ b/snow_first_setup/views/progress.py @@ -33,7 +33,7 @@ class VanillaProgress(Adw.Bin): __not_started = True __finished = False __already_skipped = False - __already_removed_autostart_file = False + __already_marked_complete = False def __init__(self, window, **kwargs): super().__init__(**kwargs) @@ -52,8 +52,8 @@ def set_page_inactive(self): return def finish(self): - if not self.__already_removed_autostart_file: - self.__already_removed_autostart_file = backend.remove_autostart_file() + if not self.__already_marked_complete: + self.__already_marked_complete = backend.mark_setup_complete() return True def __on_items_changed_thread(self, id: str, uid: str, state: backend.ProgressState, info: dict):