diff --git a/CODING_STYLE b/CODING_STYLE index 9341b48d..b3828dc8 100644 --- a/CODING_STYLE +++ b/CODING_STYLE @@ -14,14 +14,3 @@ - error codes are returned as negative Exxx. i.e. return -EINVAL. There are some exceptions: for constructors its is OK to return NULL on OOM. For lookup functions NULL is fine too for "not found". - -- Do not issue NSS requests (that includes user name and host name - lookups) from the main daemon as this might trigger deadlocks when - we those lookups involve synchronously talking to services that we - would need to start up. - -- Do not access any directories outside of /etc/, /dev, /lib from the - init daemon to avoid deadlocks with the automounter. - -- Don't synchronously talk to any other service, due to risk of - deadlocks. diff --git a/README b/README index d5823493..6f584afa 100644 --- a/README +++ b/README @@ -1,30 +1,30 @@ systemd System and Service Manager UI DETAILS: - http://0pointer.de/blog/projects/systemd.html + https://0pointer.de/blog/projects/systemd.html WEB SITE: - http://www.freedesktop.org/wiki/Software/systemd + https://systemd.io GIT: - git://anongit.freedesktop.org/systemd/systemd-ui - ssh://git.freedesktop.org/git/systemd/systemd-ui + https://github.com/systemd/systemd-ui.git + ssh://git@github.com/systemd/systemd-ui.git GITWEB: - http://cgit.freedesktop.org/systemd/systemd-ui + https://github.com/systemd/systemd-ui MAILING LIST: - http://lists.freedesktop.org/mailman/listinfo/systemd-devel - http://lists.freedesktop.org/mailman/listinfo/systemd-commits + https://lists.freedesktop.org/mailman/listinfo/systemd-devel IRC: - #systemd on irc.freenode.org + #systemd on libera.chat BUG REPORTS: - https://bugs.freedesktop.org/enter_bug.cgi?product=systemd + https://github.com/systemd/systemd-ui/issues AUTHOR: Lennart Poettering with major support from Kay Sievers LICENSE: - GPLv2+ for all code. + GPLv2+ for all code, + CC-BY-3.0-US for the desktop icon (from the GNOME Project). diff --git a/man/meson.build b/man/meson.build index f4d43765..9de06788 100644 --- a/man/meson.build +++ b/man/meson.build @@ -4,7 +4,7 @@ xsltproc_flags = ['--nonet', xsl_file = 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl' xsltproc_cmdline = [xsltproc, - '-o', '@OUTPUT', + '-o', '@OUTPUT@', xsltproc_flags, xsl_file, '@INPUT@'] diff --git a/meson.build b/meson.build index f690e677..e9c5f98e 100644 --- a/meson.build +++ b/meson.build @@ -18,20 +18,11 @@ else project_minor_version = '0' endif -# Meson ignores the preceding arguments when joining paths if an absolute -# component is encountered, so this should canonicalize various paths when they -# are absolute or relative. prefixdir = get_option('prefix') -if not prefixdir.startswith('/') - error('Prefix is not absolute: "@0@"'.format(prefixdir)) -endif - -prefixdir_noslash = '/' + prefixdir.strip('/') -bindir = prefixdir / get_option('bindir') -libdir = prefixdir / get_option('libdir') -datadir = prefixdir / get_option('datadir') +datadir = get_option('datadir') applicationsdir = datadir / 'applications' -appdatadir = datadir / 'appdata' +iconsdir = datadir / 'icons/hicolor/scalable/apps' +metainfodir = datadir / 'metainfo' userunitdir = prefixdir / 'lib/systemd/user' polkitrulesdir = datadir / 'polkit-1/rules.d' @@ -92,8 +83,6 @@ possible_common_link_flags = [ '-fstack-protector', ] -c_args = get_option('c_args') - # --as-needed and --no-undefined are provided by meson by default, # run 'meson configure' to see what is enabled possible_link_flags = [ @@ -136,8 +125,6 @@ add_project_link_arguments( userspace_c_args += cc.get_supported_arguments(possible_cc_flags) userspace_c_ld_args += cc.get_supported_link_arguments(possible_link_flags) -cpp = ' '.join(cc.cmd_array() + get_option('c_args')) + ' -E' - ##################################################################### # compilation result tests @@ -148,7 +135,7 @@ endif ##################################################################### -xsltproc = find_program('xsltproc', required: get_option('man').allowed()) +xsltproc = find_program('xsltproc', required: get_option('man')) ##################################################################### @@ -157,50 +144,23 @@ glib = dependency('glib-2.0', version: '> 2.26') gio_unix = dependency('gio-unix-2.0') gee = dependency('gee-0.8') gtk3 = dependency('gtk+-3.0') -libnotify = dependency('libnotify') -linux = meson.get_compiler('vala').find_library('linux') posix = meson.get_compiler('vala').find_library('posix') ##################################################################### -# meson_render_jinja2 = find_program('tools/meson-render-jinja2.py') - -##################################################################### - -# jinja2_cmdline = [meson_render_jinja2] - -userspace = declare_dependency( +common_flags = declare_dependency( compile_args : userspace_c_args, link_args : userspace_c_ld_args, ) ##################################################################### -executable_template = { - 'install' : true, -} - -executable_additional_kwargs = { - 'dependencies' : userspace, -} - -##################################################################### - subdir('src') ##################################################################### -if xsltproc.found() - subdir('man') -endif +subdir('man', if_found: xsltproc) install_data('LICENSE', 'CODING_STYLE', install_dir : docdir) - -##################################################################### - -summary({ - 'prefix directory' : prefixdir, - 'lib directory' : libdir, - }) diff --git a/meson_options.txt b/meson_options.txt index 1b4474b5..f9486a3d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,7 +2,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later option('man', type : 'feature', - value : 'disabled', + value : 'auto', description : 'build and install man pages') option('docdir', type : 'string', diff --git a/src/gnome-ask-password-agent.vala b/src/gnome-ask-password-agent.vala index f3887ddb..027d7907 100644 --- a/src/gnome-ask-password-agent.vala +++ b/src/gnome-ask-password-agent.vala @@ -2,6 +2,7 @@ This file is part of systemd. Copyright 2010 Lennart Poettering + Copyright 2024 Ben Boeckel systemd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,11 +18,10 @@ along with systemd; If not, see . ***/ +using Gee; using Gtk; using GLib; -using Linux; using Posix; -using Notify; [CCode (cheader_filename = "time.h")] extern int clock_gettime(int id, out timespec ts); @@ -30,14 +30,14 @@ public class PasswordDialog : Dialog { public Entry entry; - public PasswordDialog(string message, string icon) { - set_title("System Password"); + public PasswordDialog(string domain, string message, string icon) { + set_title("%s Password".printf(domain)); set_border_width(8); set_default_response(ResponseType.OK); set_icon_name(icon); - add_button(Stock.CANCEL, ResponseType.CANCEL); - add_button(Stock.OK, ResponseType.OK); + add_button("_Cancel", ResponseType.CANCEL); + add_button("_OK", ResponseType.OK); Container content = (Container) get_content_area(); @@ -69,99 +69,88 @@ public class PasswordDialog : Dialog { } } -public class MyStatusIcon : StatusIcon { - +class Watch : GLib.Object { File directory; - File current; FileMonitor file_monitor; - string message; - string icon; - string socket; + private weak Application app; - PasswordDialog password_dialog; - Notify.Notification n; + string title; + string domain_display; + string domain; - public MyStatusIcon() throws GLib.Error { - GLib.Object(icon_name : "dialog-password"); - set_title("System Password Request"); + public Watch(Application gapp, string domain, string path) throws GLib.Error { + app = gapp; - directory = File.new_for_path("/run/systemd/ask-password/"); + directory = File.new_for_path(path); file_monitor = directory.monitor_directory(0); file_monitor.changed.connect(file_monitor_changed); - current = null; - look_for_password(); + domain_display = "%s%s".printf(domain.ascii_up(1), domain.substring(1)); + title = "Password Request (%s)".printf(domain_display); + this.domain = domain; - activate.connect(status_icon_activate); + look_in_directory(directory); } - void file_monitor_changed(GLib.File file, GLib.File? other_file, GLib.FileMonitorEvent event_type) { + void look_in_directory(File dir) throws GLib.Error { + FileEnumerator enumerator = dir.enumerate_children("standard::name", FileQueryInfoFlags.NOFOLLOW_SYMLINKS); + + FileInfo i; + while ((i = enumerator.next_file()) != null) { + if (!i.get_name().has_prefix("ask.")) { + continue; + } + + load_password(dir.get_child(i.get_name())); + } + } - if (!file.get_basename().has_prefix("ask.")) + void file_monitor_changed(GLib.File file, GLib.File? other_file, GLib.FileMonitorEvent event_type) { + if (!file.get_basename().has_prefix("ask.")) { return; + } if (event_type == FileMonitorEvent.CREATED || event_type == FileMonitorEvent.DELETED) { try { - look_for_password(); + load_password(file); } catch (Error e) { show_error(e.message); } } } - void look_for_password() throws GLib.Error { - - if (current != null) { - if (!current.query_exists()) { - current = null; - if (password_dialog != null) - password_dialog.response(ResponseType.REJECT); - } - } - - if (current == null) { - FileEnumerator enumerator = directory.enumerate_children("standard::name", FileQueryInfoFlags.NOFOLLOW_SYMLINKS); - - FileInfo i; - while ((i = enumerator.next_file()) != null) { - if (!i.get_name().has_prefix("ask.")) - continue; - - current = directory.get_child(i.get_name()); - - if (load_password()) - break; - - current = null; - } - } - - if (current == null) - set_visible(false); - } - - bool load_password() throws GLib.Error { - + bool load_password(File file) throws GLib.Error { KeyFile key_file = new KeyFile(); + int? timeout = null; + string socket; + string message; + string icon; try { timespec ts; - key_file.load_from_file(current.get_path(), KeyFileFlags.NONE); + key_file.load_from_file(file.get_path(), KeyFileFlags.NONE); string not_after_as_string = key_file.get_string("Ask", "NotAfter"); clock_gettime(1, out ts); uint64 now = (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000); - uint64 not_after; - if (not_after_as_string.scanf("%llu", out not_after) != 1) + uint64 not_after = uint64.parse(not_after_as_string);; + if ((not_after == 0 && GLib.errno == Posix.EINVAL) || + (not_after == int64.MAX && GLib.errno == Posix.ERANGE)) { return false; + } - if (not_after > 0 && not_after < now) + if (not_after > 0 && not_after < now) { return false; + } + + if (not_after > 0) { + timeout = (int)(not_after - now) / 1000; + } socket = key_file.get_string("Ask", "Socket"); } catch (GLib.Error e) { @@ -171,51 +160,160 @@ public class MyStatusIcon : StatusIcon { try { message = key_file.get_string("Ask", "Message").compress(); } catch (GLib.Error e) { - message = "Please Enter System Password!"; + message = "Please Enter %s Password!".printf(domain_display); } - set_tooltip_text(message); - try { icon = key_file.get_string("Ask", "Icon"); } catch (GLib.Error e) { icon = "dialog-password"; } - set_from_icon_name(icon); - n = new Notify.Notification(title, message, icon); - n.set_timeout(5000); - n.closed.connect(() => { - set_visible(true); + GLib.Notification n = new GLib.Notification(title); + n.set_category("password.request." + domain); + n.set_body(message); + n.set_priority(GLib.NotificationPriority.NORMAL); + n.set_icon(new ThemedIcon(icon)); + n.add_button_with_target("Enter password", "app.password-request", "(ssss)", domain, message, icon, socket); + + string n_id = "password-request-%s".printf(socket); + app.send_notification(n_id, n); + if (timeout != null) { + uint s = GLib.Timeout.add_once((!) timeout, () => { + app.withdraw_notification(n_id); + app.timeouts.unset(socket); }); - n.add_action("enter_pw", "Enter password", status_icon_activate); - n.show(); + app.timeouts[socket] = s; + } return true; } +} + +void show_error(string e) { + Posix.stderr.printf("%s\n", e); + var m = new MessageDialog(null, 0, MessageType.ERROR, ButtonsType.CLOSE, "%s", e); + m.run(); + m.destroy(); +} + +class Application : Gtk.Application { + private static bool system = false; + private static bool user = false; + + private Watch? system_watch = null; + private Watch? user_watch = null; + + public Gee.HashMap timeouts; + + private const OptionEntry entries[] = { + { "system", 's', OptionFlags.NONE, OptionArg.NONE, ref system, "Watch for system requests", null }, + { "user", 'u', OptionFlags.NONE, OptionArg.NONE, ref user, "Watch for user requests", null }, + { null } + }; - void status_icon_activate() { + private const GLib.ActionEntry actions[] = { + { "password-request", password_request, "(ssss)" }, + }; - if (current == null) + public Application() { + Object(application_id: "org.freedesktop.systemd.gnome-ask-password-agent", + flags: GLib.ApplicationFlags.IS_SERVICE); + add_main_option_entries(entries); + add_action_entries(actions, this); + set_default(this); + + timeouts = new Gee.HashMap(); + } + + protected override void startup() { + if (system) { + system_watch = add_watch("system", "/run/systemd/ask-password/"); + } + + if (user) { + string? xdg_runtime_dir = Environment.get_variable("XDG_RUNTIME_DIR"); + if (xdg_runtime_dir == null) { + show_error("no user XDG runtime directory"); + } else { + string ask_pass_dir = (!) xdg_runtime_dir + "/systemd/ask-password/"; + bool ok = true; + if (!FileUtils.test(ask_pass_dir, FileTest.IS_DIR)) { + File ask_pass_dir_file = File.new_for_path(ask_pass_dir); + try { + if (!ask_pass_dir_file.make_directory_with_parents()) { + show_error("failed to create user directory"); + ok = false; + } + } catch (GLib.Error e) { + show_error("failed to create user directory %s: %s".printf(ask_pass_dir, e.message)); + ok = false; + } + } + if (ok) { + user_watch = add_watch("user", ask_pass_dir); + } + } + } + + if (system_watch != null || user_watch != null) { + hold(); + } else { + show_error("no watches requested"); + } + + base.startup(); + } + + private Watch? add_watch(string domain, string path) { + try { + return new Watch(this, domain, path); + } catch (IOError e) { + show_error("failed to set up %s watches on %s: %s".printf(domain, path, e.message)); + } catch (GLib.Error e) { + show_error("failed to set up %s watches on %s: %s".printf(domain, path, e.message)); + } + + return null; + } + + private static void password_request(GLib.SimpleAction action, GLib.Variant? variant) { + var gapp = GLib.Application.get_default(); + if (gapp == null) { return; + } + var app = (Application) (!) gapp; - if (password_dialog != null) { - password_dialog.present(); + if (variant.n_children() != 4) { return; } - password_dialog = new PasswordDialog(message, icon); + string domain = variant.get_child_value(0).get_string(); + string message = variant.get_child_value(1).get_string(); + string icon = variant.get_child_value(2).get_string(); + string socket = variant.get_child_value(3).get_string(); + + if (domain.length == 0 || message.length == 0 || icon.length == 0 || socket.length == 0) { + show_error("invalid password request (domain: '%s', message: '%s', icon: '%s', socket: '%s')".printf(domain, message, icon, socket)); + return; + } + + PasswordDialog password_dialog = new PasswordDialog(domain, message, icon); int result = password_dialog.run(); string password = password_dialog.entry.get_text(); - password_dialog.destroy(); - password_dialog = null; + + uint n_id; + if (app.timeouts.unset(socket, out n_id)) { + GLib.Source.remove(n_id); + } if (result == ResponseType.REJECT || result == ResponseType.DELETE_EVENT || - result == ResponseType.CANCEL) + result == ResponseType.CANCEL) { return; + } Pid child_pid; int to_process; @@ -236,39 +334,20 @@ public class MyStatusIcon : StatusIcon { }); OutputStream stream = new UnixOutputStream(to_process, true); -#if VALA_0_12 stream.write(password.data, null); -#else - stream.write(password, password.length, null); -#endif } catch (Error e) { show_error(e.message); } } -} -static const OptionEntry entries[] = { - { null } -}; - -void show_error(string e) { - var m = new MessageDialog(null, 0, MessageType.ERROR, ButtonsType.CLOSE, "%s", e); - m.run(); - m.destroy(); -} - -int main(string[] args) { - try { - Gtk.init_with_args(ref args, "[OPTION...]", entries, "systemd-ask-password-agent"); - Notify.init("Password Agent"); - - MyStatusIcon i = new MyStatusIcon(); - Gtk.main(); - } catch (IOError e) { - show_error(e.message); - } catch (GLib.Error e) { - Posix.stderr.printf("%s\n", e.message); + public static int main(string[] args) { + try { + Gtk.init_with_args(ref args, "[OPTION...]", entries, "systemd-ask-password-agent"); + } catch (GLib.Error e) { + Posix.stderr.printf("%s\n", e.message); + return 1; + } + Application app = new Application(); + return app.run(args); } - - return 0; } diff --git a/src/systemadm.desktop b/src/io.systemd.systemadm.desktop similarity index 65% rename from src/systemadm.desktop rename to src/io.systemd.systemadm.desktop index a8c71a0e..7b697370 100644 --- a/src/systemadm.desktop +++ b/src/io.systemd.systemadm.desktop @@ -1,8 +1,8 @@ [Desktop Entry] Name=systemadm -Comment=Manage Systemd Units +Comment=Manage systemd units Exec=systemadm -Icon=applications-system +Icon=io.systemd.systemadm Terminal=false Type=Application Categories=System;Settings; diff --git a/src/io.systemd.systemadm.metainfo.xml b/src/io.systemd.systemadm.metainfo.xml new file mode 100644 index 00000000..d16a3ad3 --- /dev/null +++ b/src/io.systemd.systemadm.metainfo.xml @@ -0,0 +1,41 @@ + + + + io.systemd.systemadm + CC0-1.0 + GPL-2.0-or-later + systemadm + + systemd + + Manage systemd units + +

+ systemadm is the graphical browser for systemd units. It + can show the list of units, possibly filtered by type. + For individual units, name, description and dependencies + with other units will be shown. +

+ +

+ This tool has been largely obsoleted by the commandline + tools like systemctl and systemd-analyze. Bugs are likely + to be present. +

+
+ + + https://in.waw.pl/~zbyszek/systemd/systemadm-main-window.png + Main window + + + https://in.waw.pl/~zbyszek/systemd/systemadm-sockets-target.png + Unit details + + + https://github.com/systemd/systemd-ui/issues + https://systemd.io + + io.systemd.systemadm.desktop + zbyszek@in.waw.pl +
diff --git a/src/io.systemd.systemadm.svg b/src/io.systemd.systemadm.svg new file mode 100644 index 00000000..523b01f0 --- /dev/null +++ b/src/io.systemd.systemadm.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 540965dc..1b9e7e23 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,14 +1,18 @@ systemadm_files = files('systemadm.vala', 'systemd-interfaces.vala') systemadm = executable('systemadm', systemadm_files, - dependencies: [gtk3, gee, posix], - install: true, - install_dir: bindir) -install_data('systemadm.desktop', install_dir: applicationsdir) -install_data('systemadm.appdata.xml', install_dir: appdatadir) + dependencies: [common_flags, gtk3, gee, posix], + install: true) +install_data('io.systemd.systemadm.desktop', install_dir: applicationsdir) +install_data('io.systemd.systemadm.svg', install_dir: iconsdir) +install_data('io.systemd.systemadm.metainfo.xml', install_dir: metainfodir) sgapa_files = files('gnome-ask-password-agent.vala') sgapa = executable('systemd-gnome-ask-password-agent', sgapa_files, - dependencies: [gtk3, gee, gio_unix, libnotify, linux, posix], - install: true, - install_dir: bindir) + dependencies: [common_flags, gtk3, gee, gio_unix, posix], + install: true) +install_data('org.freedesktop.systemd.gnome-ask-password-agent.desktop', install_dir: applicationsdir) +install_data('systemd-gnome-ask-password-agent.rules', install_dir: polkitrulesdir) +sgapa_units = files('systemd-gnome-ask-password-agent.path', + 'systemd-gnome-ask-password-agent.service') +install_data(sgapa_units, install_dir: userunitdir) diff --git a/src/org.freedesktop.systemd.gnome-ask-password-agent.desktop b/src/org.freedesktop.systemd.gnome-ask-password-agent.desktop new file mode 100644 index 00000000..a05b514c --- /dev/null +++ b/src/org.freedesktop.systemd.gnome-ask-password-agent.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=systemd GNOME ask password agent +Comment=Agent for system- and user-level password requests +Exec=systemd-gnome-ask-password-agent +Icon=org.freedesktop.systemd.gnome-ask-password-agent +NoDisplay=true +Terminal=false +Type=Application +Categories=Utility +StartupNotify=true +DBusActivatable=true +X-GNOME-UsesNotifications=true diff --git a/src/systemadm.appdata.xml b/src/systemadm.appdata.xml deleted file mode 100644 index 03f4e272..00000000 --- a/src/systemadm.appdata.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - systemadm.desktop - CC0 - -

- systemadm is the graphical browser for systemd units. It - can show the list of units, possibly filtered by type. - For individual units, name, description and dependencies - with other units will be shown. -

- -

- This tool has been largely obsoleted by the commandline - tools like systemctl and systemd-analyze. Bugs are likely - to be present. -

-
- - http://in.waw.pl/~zbyszek/systemd/systemadm-main-window.png - http://in.waw.pl/~zbyszek/systemd/systemadm-sockets-target.png - - http://www.freedesktop.org/wiki/Software/systemd/ - zbyszek@in.waw.pl -
diff --git a/src/systemadm.vala b/src/systemadm.vala index e20c359e..caff1398 100644 --- a/src/systemadm.vala +++ b/src/systemadm.vala @@ -1151,7 +1151,7 @@ int dbus_error_to_errno(string error) { } } -static const OptionEntry entries[] = { +const OptionEntry entries[] = { { "user", 0, 0, OptionArg.NONE, out user, "Connect to user service manager", null }, { "system", 0, OptionFlags.REVERSE, OptionArg.NONE, out user, "Connect to system manager", null }, { null } diff --git a/src/systemd-gnome-ask-password-agent.path b/src/systemd-gnome-ask-password-agent.path new file mode 100644 index 00000000..ca3d4d0c --- /dev/null +++ b/src/systemd-gnome-ask-password-agent.path @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Forward Password Requests to GNOME Password Agent Directory Watch + +DefaultDependencies=no +Before=paths.target cryptsetup.target +Conflicts=emergency.service +Before=emergency.service +Conflicts=shutdown.target +Before=shutdown.target + +[Path] +DirectoryNotEmpty=/run/systemd/ask-password +MakeDirectory=yes diff --git a/src/systemd-gnome-ask-password-agent.rules b/src/systemd-gnome-ask-password-agent.rules new file mode 100644 index 00000000..c2960b3f --- /dev/null +++ b/src/systemd-gnome-ask-password-agent.rules @@ -0,0 +1,11 @@ +/** + * Polkit permissions for systemd-gnome-ask-password-agent. + */ +polkit.addRule(function(action, subject) { + // Allow the `wheel` group to run `systemd-reply-password` without passwords. + if (action.id == "org.freedesktop.policykit.exec" && + action.lookup("program") == "/usr/lib/systemd/systemd-reply-password" && + subject.isInGroup("wheel")) { + return polkit.Result.YES; + } +}); diff --git a/src/systemd-gnome-ask-password-agent.service b/src/systemd-gnome-ask-password-agent.service new file mode 100644 index 00000000..e8b627e9 --- /dev/null +++ b/src/systemd-gnome-ask-password-agent.service @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Forward Password Requests to GNOME Password Agent +After=systemd-user-sessions.service + +[Service] +ExecStart=systemd-gnome-ask-password-agent --system --user