* [PATCH 0/3] Systemd user presets support
@ 2025-01-12 14:31 arturkow2000
2025-01-12 14:31 ` [PATCH 1/3] systemd-systemctl: add support for --global flag Artur Kowalski
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: arturkow2000 @ 2025-01-12 14:31 UTC (permalink / raw)
To: openembedded-core; +Cc: Artur Kowalski
This series brings systemd preset support for user units which
currently are not handled properly resulting in image build failures.
Related issue: https://bugzilla.yoctoproject.org/show_bug.cgi?id=15710
Artur Kowalski (3):
systemd-systemctl: add support for --global flag
systemd.bbclass: add support for user presets
image.bbclass: enable systemd user services
meta/classes-recipe/image.bbclass | 1 +
meta/classes-recipe/systemd.bbclass | 120 ++++++++++++++----
.../systemd/systemd-systemctl/systemctl | 42 +++---
3 files changed, 117 insertions(+), 46 deletions(-)
--
2.47.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 1/3] systemd-systemctl: add support for --global flag 2025-01-12 14:31 [PATCH 0/3] Systemd user presets support arturkow2000 @ 2025-01-12 14:31 ` Artur Kowalski 2025-01-12 15:34 ` [OE-core] " Alex Kiernan 2025-01-12 14:31 ` [PATCH 2/3] systemd.bbclass: add support for user presets Artur Kowalski 2025-01-12 14:31 ` [PATCH 3/3] image.bbclass: enable systemd user services Artur Kowalski 2 siblings, 1 reply; 8+ messages in thread From: Artur Kowalski @ 2025-01-12 14:31 UTC (permalink / raw) To: openembedded-core; +Cc: Artur Kowalski The flag is similar to --user flag as it causes systemctl to operate on user units, but it performs operations globally for all users. This is required to for user presets support. Signed-off-by: Artur Kowalski <arturkow2000@gmail.com> --- .../systemd/systemd-systemctl/systemctl | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/meta/recipes-core/systemd/systemd-systemctl/systemctl b/meta/recipes-core/systemd/systemd-systemctl/systemctl index 2229bc7b6d..9b8fe81459 100755 --- a/meta/recipes-core/systemd/systemd-systemctl/systemctl +++ b/meta/recipes-core/systemd/systemd-systemctl/systemctl @@ -29,15 +29,15 @@ class SystemdFile(): _clearable_keys = ['WantedBy'] - def __init__(self, root, path, instance_unit_name): + def __init__(self, root, path, instance_unit_name, unit_type): self.sections = dict() self._parse(root, path) dirname = os.path.basename(path.name) + ".d" for location in locations: - files = (root / location / "system" / dirname).glob("*.conf") + files = (root / location / unit_type / dirname).glob("*.conf") if instance_unit_name: inst_dirname = instance_unit_name + ".d" - files = chain(files, (root / location / "system" / inst_dirname).glob("*.conf")) + files = chain(files, (root / location / unit_type / inst_dirname).glob("*.conf")) for path2 in sorted(files): self._parse(root, path2) @@ -182,21 +182,22 @@ class SystemdUnitNotFoundError(Exception): class SystemdUnit(): - def __init__(self, root, unit): + def __init__(self, root, unit, unit_type): self.root = root self.unit = unit + self.unit_type = unit_type self.config = None def _path_for_unit(self, unit): for location in locations: - path = self.root / location / "system" / unit + path = self.root / location / self.unit_type / unit if path.exists() or path.is_symlink(): return path raise SystemdUnitNotFoundError(self.root, unit) def _process_deps(self, config, service, location, prop, dirstem, instance): - systemdir = self.root / SYSCONFDIR / "systemd" / "system" + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type target = ROOT / location.relative_to(self.root) try: @@ -229,7 +230,7 @@ class SystemdUnit(): # ignore aliases return - config = SystemdFile(self.root, path, instance_unit_name) + config = SystemdFile(self.root, path, instance_unit_name, self.unit_type) if instance == "": try: default_instance = config.get('Install', 'DefaultInstance')[0] @@ -250,14 +251,14 @@ class SystemdUnit(): try: units_enabled.append(unit) if also not in units_enabled: - SystemdUnit(self.root, also).enable(units_enabled) + SystemdUnit(self.root, also, self.unit_type).enable(units_enabled) except SystemdUnitNotFoundError as e: sys.exit("Error: Systemctl also enable issue with %s (%s)" % (service, e.unit)) except KeyError: pass - systemdir = self.root / SYSCONFDIR / "systemd" / "system" + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type target = ROOT / path.relative_to(self.root) try: for dest in config.get('Install', 'Alias'): @@ -268,15 +269,15 @@ class SystemdUnit(): pass def mask(self): - systemdir = self.root / SYSCONFDIR / "systemd" / "system" + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type add_link(systemdir / self.unit, "/dev/null") -def collect_services(root): +def collect_services(root, unit_type): """Collect list of service files""" services = set() for location in locations: - paths = (root / location / "system").glob("*") + paths = (root / location / unit_type).glob("*") for path in paths: if path.is_dir(): continue @@ -285,16 +286,16 @@ def collect_services(root): return services -def preset_all(root): - presets = Presets('system-preset', root) - services = collect_services(root) +def preset_all(root, unit_type): + presets = Presets('{}-preset'.format(unit_type), root) + services = collect_services(root, unit_type) for service in services: state = presets.state(service) if state == "enable" or state is None: try: - SystemdUnit(root, service).enable() + SystemdUnit(root, service, unit_type).enable() except SystemdUnitNotFoundError: sys.exit("Error: Systemctl preset_all issue in %s" % service) @@ -320,6 +321,7 @@ def main(): parser.add_argument('--preset-mode', choices=['full', 'enable-only', 'disable-only'], default='full') + parser.add_argument('--global', dest="glob", action="store_true", default=False) args = parser.parse_args() @@ -336,16 +338,18 @@ def main(): parser.print_help() return 0 + unit_type = "user" if args.glob else "system" + if command == "mask": for service in args.service: try: - SystemdUnit(root, service).mask() + SystemdUnit(root, service, unit_type).mask() except SystemdUnitNotFoundError as e: sys.exit("Error: Systemctl main mask issue in %s (%s)" % (service, e.unit)) elif command == "enable": for service in args.service: try: - SystemdUnit(root, service).enable() + SystemdUnit(root, service, unit_type).enable() except SystemdUnitNotFoundError as e: sys.exit("Error: Systemctl main enable issue in %s (%s)" % (service, e.unit)) elif command == "preset-all": @@ -353,7 +357,7 @@ def main(): sys.exit("Too many arguments.") if args.preset_mode != "enable-only": sys.exit("Only enable-only is supported as preset-mode.") - preset_all(root) + preset_all(root, unit_type) else: raise RuntimeError() -- 2.47.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [OE-core] [PATCH 1/3] systemd-systemctl: add support for --global flag 2025-01-12 14:31 ` [PATCH 1/3] systemd-systemctl: add support for --global flag Artur Kowalski @ 2025-01-12 15:34 ` Alex Kiernan 2025-01-13 21:18 ` Artur Kowalski 0 siblings, 1 reply; 8+ messages in thread From: Alex Kiernan @ 2025-01-12 15:34 UTC (permalink / raw) To: arturkow2000; +Cc: openembedded-core On Sun, Jan 12, 2025 at 2:40 PM Artur Kowalski via lists.openembedded.org <arturkow2000=gmail.com@lists.openembedded.org> wrote: > > The flag is similar to --user flag as it causes systemctl to operate on > user units, but it performs operations globally for all users. This is > required to for user presets support. "to for"? Drop the "to"? > > Signed-off-by: Artur Kowalski <arturkow2000@gmail.com> > --- > .../systemd/systemd-systemctl/systemctl | 42 ++++++++++--------- > 1 file changed, 23 insertions(+), 19 deletions(-) > > diff --git a/meta/recipes-core/systemd/systemd-systemctl/systemctl b/meta/recipes-core/systemd/systemd-systemctl/systemctl > index 2229bc7b6d..9b8fe81459 100755 > --- a/meta/recipes-core/systemd/systemd-systemctl/systemctl > +++ b/meta/recipes-core/systemd/systemd-systemctl/systemctl > @@ -29,15 +29,15 @@ class SystemdFile(): > > _clearable_keys = ['WantedBy'] > > - def __init__(self, root, path, instance_unit_name): > + def __init__(self, root, path, instance_unit_name, unit_type): > self.sections = dict() > self._parse(root, path) > dirname = os.path.basename(path.name) + ".d" > for location in locations: > - files = (root / location / "system" / dirname).glob("*.conf") > + files = (root / location / unit_type / dirname).glob("*.conf") > if instance_unit_name: > inst_dirname = instance_unit_name + ".d" > - files = chain(files, (root / location / "system" / inst_dirname).glob("*.conf")) > + files = chain(files, (root / location / unit_type / inst_dirname).glob("*.conf")) > for path2 in sorted(files): > self._parse(root, path2) > > @@ -182,21 +182,22 @@ class SystemdUnitNotFoundError(Exception): > > > class SystemdUnit(): > - def __init__(self, root, unit): > + def __init__(self, root, unit, unit_type): > self.root = root > self.unit = unit > + self.unit_type = unit_type > self.config = None > > def _path_for_unit(self, unit): > for location in locations: > - path = self.root / location / "system" / unit > + path = self.root / location / self.unit_type / unit > if path.exists() or path.is_symlink(): > return path > > raise SystemdUnitNotFoundError(self.root, unit) > > def _process_deps(self, config, service, location, prop, dirstem, instance): > - systemdir = self.root / SYSCONFDIR / "systemd" / "system" > + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type > > target = ROOT / location.relative_to(self.root) > try: > @@ -229,7 +230,7 @@ class SystemdUnit(): > # ignore aliases > return > > - config = SystemdFile(self.root, path, instance_unit_name) > + config = SystemdFile(self.root, path, instance_unit_name, self.unit_type) > if instance == "": > try: > default_instance = config.get('Install', 'DefaultInstance')[0] > @@ -250,14 +251,14 @@ class SystemdUnit(): > try: > units_enabled.append(unit) > if also not in units_enabled: > - SystemdUnit(self.root, also).enable(units_enabled) > + SystemdUnit(self.root, also, self.unit_type).enable(units_enabled) > except SystemdUnitNotFoundError as e: > sys.exit("Error: Systemctl also enable issue with %s (%s)" % (service, e.unit)) > > except KeyError: > pass > > - systemdir = self.root / SYSCONFDIR / "systemd" / "system" > + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type > target = ROOT / path.relative_to(self.root) > try: > for dest in config.get('Install', 'Alias'): > @@ -268,15 +269,15 @@ class SystemdUnit(): > pass > > def mask(self): > - systemdir = self.root / SYSCONFDIR / "systemd" / "system" > + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type > add_link(systemdir / self.unit, "/dev/null") > > > -def collect_services(root): > +def collect_services(root, unit_type): > """Collect list of service files""" > services = set() > for location in locations: > - paths = (root / location / "system").glob("*") > + paths = (root / location / unit_type).glob("*") > for path in paths: > if path.is_dir(): > continue > @@ -285,16 +286,16 @@ def collect_services(root): > return services > > > -def preset_all(root): > - presets = Presets('system-preset', root) > - services = collect_services(root) > +def preset_all(root, unit_type): > + presets = Presets('{}-preset'.format(unit_type), root) > + services = collect_services(root, unit_type) > > for service in services: > state = presets.state(service) > > if state == "enable" or state is None: > try: > - SystemdUnit(root, service).enable() > + SystemdUnit(root, service, unit_type).enable() > except SystemdUnitNotFoundError: > sys.exit("Error: Systemctl preset_all issue in %s" % service) > > @@ -320,6 +321,7 @@ def main(): > parser.add_argument('--preset-mode', > choices=['full', 'enable-only', 'disable-only'], > default='full') > + parser.add_argument('--global', dest="glob", action="store_true", default=False) > I guess `glob` because `global` is a keyword? But yuck... makes me think of https://docs.python.org/3/library/glob.html > args = parser.parse_args() > > @@ -336,16 +338,18 @@ def main(): > parser.print_help() > return 0 > > + unit_type = "user" if args.glob else "system" > + > if command == "mask": > for service in args.service: > try: > - SystemdUnit(root, service).mask() > + SystemdUnit(root, service, unit_type).mask() > except SystemdUnitNotFoundError as e: > sys.exit("Error: Systemctl main mask issue in %s (%s)" % (service, e.unit)) > elif command == "enable": > for service in args.service: > try: > - SystemdUnit(root, service).enable() > + SystemdUnit(root, service, unit_type).enable() > except SystemdUnitNotFoundError as e: > sys.exit("Error: Systemctl main enable issue in %s (%s)" % (service, e.unit)) > elif command == "preset-all": > @@ -353,7 +357,7 @@ def main(): > sys.exit("Too many arguments.") > if args.preset_mode != "enable-only": > sys.exit("Only enable-only is supported as preset-mode.") > - preset_all(root) > + preset_all(root, unit_type) > else: > raise RuntimeError() > > -- > 2.47.0 > > > -=-=-=-=-=-=-=-=-=-=-=- > Links: You receive all messages sent to this group. > View/Reply Online (#209686): https://lists.openembedded.org/g/openembedded-core/message/209686 > Mute This Topic: https://lists.openembedded.org/mt/110569454/3618097 > Group Owner: openembedded-core+owner@lists.openembedded.org > Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [alex.kiernan@gmail.com] > -=-=-=-=-=-=-=-=-=-=-=- > -- Alex Kiernan ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [OE-core] [PATCH 1/3] systemd-systemctl: add support for --global flag 2025-01-12 15:34 ` [OE-core] " Alex Kiernan @ 2025-01-13 21:18 ` Artur Kowalski 2025-01-15 14:04 ` Alex Kiernan 0 siblings, 1 reply; 8+ messages in thread From: Artur Kowalski @ 2025-01-13 21:18 UTC (permalink / raw) To: Alex Kiernan; +Cc: openembedded-core W dniu 12.01.2025 o 16:34, Alex Kiernan pisze: > On Sun, Jan 12, 2025 at 2:40 PM Artur Kowalski via > lists.openembedded.org <arturkow2000=gmail.com@lists.openembedded.org> > wrote: >> The flag is similar to --user flag as it causes systemctl to operate on >> user units, but it performs operations globally for all users. This is >> required to for user presets support. > "to for"? Drop the "to"? > >> Signed-off-by: Artur Kowalski <arturkow2000@gmail.com> >> --- >> .../systemd/systemd-systemctl/systemctl | 42 ++++++++++--------- >> 1 file changed, 23 insertions(+), 19 deletions(-) >> >> diff --git a/meta/recipes-core/systemd/systemd-systemctl/systemctl b/meta/recipes-core/systemd/systemd-systemctl/systemctl >> index 2229bc7b6d..9b8fe81459 100755 >> --- a/meta/recipes-core/systemd/systemd-systemctl/systemctl >> +++ b/meta/recipes-core/systemd/systemd-systemctl/systemctl >> @@ -29,15 +29,15 @@ class SystemdFile(): >> >> _clearable_keys = ['WantedBy'] >> >> - def __init__(self, root, path, instance_unit_name): >> + def __init__(self, root, path, instance_unit_name, unit_type): >> self.sections = dict() >> self._parse(root, path) >> dirname = os.path.basename(path.name) + ".d" >> for location in locations: >> - files = (root / location / "system" / dirname).glob("*.conf") >> + files = (root / location / unit_type / dirname).glob("*.conf") >> if instance_unit_name: >> inst_dirname = instance_unit_name + ".d" >> - files = chain(files, (root / location / "system" / inst_dirname).glob("*.conf")) >> + files = chain(files, (root / location / unit_type / inst_dirname).glob("*.conf")) >> for path2 in sorted(files): >> self._parse(root, path2) >> >> @@ -182,21 +182,22 @@ class SystemdUnitNotFoundError(Exception): >> >> >> class SystemdUnit(): >> - def __init__(self, root, unit): >> + def __init__(self, root, unit, unit_type): >> self.root = root >> self.unit = unit >> + self.unit_type = unit_type >> self.config = None >> >> def _path_for_unit(self, unit): >> for location in locations: >> - path = self.root / location / "system" / unit >> + path = self.root / location / self.unit_type / unit >> if path.exists() or path.is_symlink(): >> return path >> >> raise SystemdUnitNotFoundError(self.root, unit) >> >> def _process_deps(self, config, service, location, prop, dirstem, instance): >> - systemdir = self.root / SYSCONFDIR / "systemd" / "system" >> + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type >> >> target = ROOT / location.relative_to(self.root) >> try: >> @@ -229,7 +230,7 @@ class SystemdUnit(): >> # ignore aliases >> return >> >> - config = SystemdFile(self.root, path, instance_unit_name) >> + config = SystemdFile(self.root, path, instance_unit_name, self.unit_type) >> if instance == "": >> try: >> default_instance = config.get('Install', 'DefaultInstance')[0] >> @@ -250,14 +251,14 @@ class SystemdUnit(): >> try: >> units_enabled.append(unit) >> if also not in units_enabled: >> - SystemdUnit(self.root, also).enable(units_enabled) >> + SystemdUnit(self.root, also, self.unit_type).enable(units_enabled) >> except SystemdUnitNotFoundError as e: >> sys.exit("Error: Systemctl also enable issue with %s (%s)" % (service, e.unit)) >> >> except KeyError: >> pass >> >> - systemdir = self.root / SYSCONFDIR / "systemd" / "system" >> + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type >> target = ROOT / path.relative_to(self.root) >> try: >> for dest in config.get('Install', 'Alias'): >> @@ -268,15 +269,15 @@ class SystemdUnit(): >> pass >> >> def mask(self): >> - systemdir = self.root / SYSCONFDIR / "systemd" / "system" >> + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type >> add_link(systemdir / self.unit, "/dev/null") >> >> >> -def collect_services(root): >> +def collect_services(root, unit_type): >> """Collect list of service files""" >> services = set() >> for location in locations: >> - paths = (root / location / "system").glob("*") >> + paths = (root / location / unit_type).glob("*") >> for path in paths: >> if path.is_dir(): >> continue >> @@ -285,16 +286,16 @@ def collect_services(root): >> return services >> >> >> -def preset_all(root): >> - presets = Presets('system-preset', root) >> - services = collect_services(root) >> +def preset_all(root, unit_type): >> + presets = Presets('{}-preset'.format(unit_type), root) >> + services = collect_services(root, unit_type) >> >> for service in services: >> state = presets.state(service) >> >> if state == "enable" or state is None: >> try: >> - SystemdUnit(root, service).enable() >> + SystemdUnit(root, service, unit_type).enable() >> except SystemdUnitNotFoundError: >> sys.exit("Error: Systemctl preset_all issue in %s" % service) >> >> @@ -320,6 +321,7 @@ def main(): >> parser.add_argument('--preset-mode', >> choices=['full', 'enable-only', 'disable-only'], >> default='full') >> + parser.add_argument('--global', dest="glob", action="store_true", default=False) >> > I guess `glob` because `global` is a keyword? But yuck... makes me > think of https://docs.python.org/3/library/glob.html Yes, that's the reason. Would using `global_` be okay? I choose suffixing instead of prefixing as underscore prefix has special meaning for private fields in Python so I guess that also wouldn't be best fit. > >> args = parser.parse_args() >> >> @@ -336,16 +338,18 @@ def main(): >> parser.print_help() >> return 0 >> >> + unit_type = "user" if args.glob else "system" >> + >> if command == "mask": >> for service in args.service: >> try: >> - SystemdUnit(root, service).mask() >> + SystemdUnit(root, service, unit_type).mask() >> except SystemdUnitNotFoundError as e: >> sys.exit("Error: Systemctl main mask issue in %s (%s)" % (service, e.unit)) >> elif command == "enable": >> for service in args.service: >> try: >> - SystemdUnit(root, service).enable() >> + SystemdUnit(root, service, unit_type).enable() >> except SystemdUnitNotFoundError as e: >> sys.exit("Error: Systemctl main enable issue in %s (%s)" % (service, e.unit)) >> elif command == "preset-all": >> @@ -353,7 +357,7 @@ def main(): >> sys.exit("Too many arguments.") >> if args.preset_mode != "enable-only": >> sys.exit("Only enable-only is supported as preset-mode.") >> - preset_all(root) >> + preset_all(root, unit_type) >> else: >> raise RuntimeError() >> >> -- >> 2.47.0 >> >> >> >> > > -- > Alex Kiernan ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [OE-core] [PATCH 1/3] systemd-systemctl: add support for --global flag 2025-01-13 21:18 ` Artur Kowalski @ 2025-01-15 14:04 ` Alex Kiernan 0 siblings, 0 replies; 8+ messages in thread From: Alex Kiernan @ 2025-01-15 14:04 UTC (permalink / raw) To: Artur Kowalski; +Cc: openembedded-core On Mon, Jan 13, 2025 at 9:18 PM Artur Kowalski <arturkow2000@gmail.com> wrote: > > > W dniu 12.01.2025 o 16:34, Alex Kiernan pisze: > > On Sun, Jan 12, 2025 at 2:40 PM Artur Kowalski via > > lists.openembedded.org <arturkow2000=gmail.com@lists.openembedded.org> > > wrote: > >> The flag is similar to --user flag as it causes systemctl to operate on > >> user units, but it performs operations globally for all users. This is > >> required to for user presets support. > > "to for"? Drop the "to"? > > > >> Signed-off-by: Artur Kowalski <arturkow2000@gmail.com> > >> --- > >> .../systemd/systemd-systemctl/systemctl | 42 ++++++++++--------- > >> 1 file changed, 23 insertions(+), 19 deletions(-) > >> > >> diff --git a/meta/recipes-core/systemd/systemd-systemctl/systemctl b/meta/recipes-core/systemd/systemd-systemctl/systemctl > >> index 2229bc7b6d..9b8fe81459 100755 > >> --- a/meta/recipes-core/systemd/systemd-systemctl/systemctl > >> +++ b/meta/recipes-core/systemd/systemd-systemctl/systemctl > >> @@ -29,15 +29,15 @@ class SystemdFile(): > >> > >> _clearable_keys = ['WantedBy'] > >> > >> - def __init__(self, root, path, instance_unit_name): > >> + def __init__(self, root, path, instance_unit_name, unit_type): > >> self.sections = dict() > >> self._parse(root, path) > >> dirname = os.path.basename(path.name) + ".d" > >> for location in locations: > >> - files = (root / location / "system" / dirname).glob("*.conf") > >> + files = (root / location / unit_type / dirname).glob("*.conf") > >> if instance_unit_name: > >> inst_dirname = instance_unit_name + ".d" > >> - files = chain(files, (root / location / "system" / inst_dirname).glob("*.conf")) > >> + files = chain(files, (root / location / unit_type / inst_dirname).glob("*.conf")) > >> for path2 in sorted(files): > >> self._parse(root, path2) > >> > >> @@ -182,21 +182,22 @@ class SystemdUnitNotFoundError(Exception): > >> > >> > >> class SystemdUnit(): > >> - def __init__(self, root, unit): > >> + def __init__(self, root, unit, unit_type): > >> self.root = root > >> self.unit = unit > >> + self.unit_type = unit_type > >> self.config = None > >> > >> def _path_for_unit(self, unit): > >> for location in locations: > >> - path = self.root / location / "system" / unit > >> + path = self.root / location / self.unit_type / unit > >> if path.exists() or path.is_symlink(): > >> return path > >> > >> raise SystemdUnitNotFoundError(self.root, unit) > >> > >> def _process_deps(self, config, service, location, prop, dirstem, instance): > >> - systemdir = self.root / SYSCONFDIR / "systemd" / "system" > >> + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type > >> > >> target = ROOT / location.relative_to(self.root) > >> try: > >> @@ -229,7 +230,7 @@ class SystemdUnit(): > >> # ignore aliases > >> return > >> > >> - config = SystemdFile(self.root, path, instance_unit_name) > >> + config = SystemdFile(self.root, path, instance_unit_name, self.unit_type) > >> if instance == "": > >> try: > >> default_instance = config.get('Install', 'DefaultInstance')[0] > >> @@ -250,14 +251,14 @@ class SystemdUnit(): > >> try: > >> units_enabled.append(unit) > >> if also not in units_enabled: > >> - SystemdUnit(self.root, also).enable(units_enabled) > >> + SystemdUnit(self.root, also, self.unit_type).enable(units_enabled) > >> except SystemdUnitNotFoundError as e: > >> sys.exit("Error: Systemctl also enable issue with %s (%s)" % (service, e.unit)) > >> > >> except KeyError: > >> pass > >> > >> - systemdir = self.root / SYSCONFDIR / "systemd" / "system" > >> + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type > >> target = ROOT / path.relative_to(self.root) > >> try: > >> for dest in config.get('Install', 'Alias'): > >> @@ -268,15 +269,15 @@ class SystemdUnit(): > >> pass > >> > >> def mask(self): > >> - systemdir = self.root / SYSCONFDIR / "systemd" / "system" > >> + systemdir = self.root / SYSCONFDIR / "systemd" / self.unit_type > >> add_link(systemdir / self.unit, "/dev/null") > >> > >> > >> -def collect_services(root): > >> +def collect_services(root, unit_type): > >> """Collect list of service files""" > >> services = set() > >> for location in locations: > >> - paths = (root / location / "system").glob("*") > >> + paths = (root / location / unit_type).glob("*") > >> for path in paths: > >> if path.is_dir(): > >> continue > >> @@ -285,16 +286,16 @@ def collect_services(root): > >> return services > >> > >> > >> -def preset_all(root): > >> - presets = Presets('system-preset', root) > >> - services = collect_services(root) > >> +def preset_all(root, unit_type): > >> + presets = Presets('{}-preset'.format(unit_type), root) > >> + services = collect_services(root, unit_type) > >> > >> for service in services: > >> state = presets.state(service) > >> > >> if state == "enable" or state is None: > >> try: > >> - SystemdUnit(root, service).enable() > >> + SystemdUnit(root, service, unit_type).enable() > >> except SystemdUnitNotFoundError: > >> sys.exit("Error: Systemctl preset_all issue in %s" % service) > >> > >> @@ -320,6 +321,7 @@ def main(): > >> parser.add_argument('--preset-mode', > >> choices=['full', 'enable-only', 'disable-only'], > >> default='full') > >> + parser.add_argument('--global', dest="glob", action="store_true", default=False) > >> > > I guess `glob` because `global` is a keyword? But yuck... makes me > > think of https://docs.python.org/3/library/glob.html > > Yes, that's the reason. Would using `global_` be okay? I choose > suffixing instead of prefixing as underscore prefix has special meaning > for private fields in Python so I guess that also wouldn't be best fit. > How about `opt_global` - makes it obvious it's an option we're talking about? I think the option name is pretty unclear, but clearly it's part of the upstream systemctl ABI so is fixed. -- Alex Kiernan ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/3] systemd.bbclass: add support for user presets 2025-01-12 14:31 [PATCH 0/3] Systemd user presets support arturkow2000 2025-01-12 14:31 ` [PATCH 1/3] systemd-systemctl: add support for --global flag Artur Kowalski @ 2025-01-12 14:31 ` Artur Kowalski 2025-01-13 10:55 ` [OE-core] " Alexander Kanavin 2025-01-12 14:31 ` [PATCH 3/3] image.bbclass: enable systemd user services Artur Kowalski 2 siblings, 1 reply; 8+ messages in thread From: Artur Kowalski @ 2025-01-12 14:31 UTC (permalink / raw) To: openembedded-core; +Cc: Artur Kowalski Previously user units were causing build erros when listed in SYSTEMD_SERVICE and SYSTEMD_AUTO_ENABLE was set. Signed-off-by: Artur Kowalski <arturkow2000@gmail.com> --- meta/classes-recipe/systemd.bbclass | 120 +++++++++++++++++++++------- 1 file changed, 93 insertions(+), 27 deletions(-) diff --git a/meta/classes-recipe/systemd.bbclass b/meta/classes-recipe/systemd.bbclass index 4b4470b7b3..80f4da3bdf 100644 --- a/meta/classes-recipe/systemd.bbclass +++ b/meta/classes-recipe/systemd.bbclass @@ -37,17 +37,29 @@ if systemctl >/dev/null 2>/dev/null; then fi if [ "${SYSTEMD_AUTO_ENABLE}" = "enable" ]; then - for service in ${SYSTEMD_SERVICE_ESCAPED}; do + for service in ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)}; do systemctl ${OPTS} enable "$service" done + + for service in ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)}; do + systemctl --global ${OPTS} enable "$service" + done fi if [ -z "$D" ]; then + # Reload only system service manager + # --global for daemon-reload is not supported: https://github.com/systemd/systemd/issues/19284 systemctl daemon-reload - systemctl preset ${SYSTEMD_SERVICE_ESCAPED} + [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)}" ] && \ + systemctl preset ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)} + + [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)}" ] && \ + systemctl --global preset ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)} if [ "${SYSTEMD_AUTO_ENABLE}" = "enable" ]; then - systemctl --no-block restart ${SYSTEMD_SERVICE_ESCAPED} + # --global flag for restart is not supported by systemd (see above) + [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)}" ] && \ + systemctl --no-block restart ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)} fi fi fi @@ -56,9 +68,14 @@ fi systemd_prerm() { if systemctl >/dev/null 2>/dev/null; then if [ -z "$D" ]; then - systemctl stop ${SYSTEMD_SERVICE_ESCAPED} + # same as above, --global flag is not supported for stop + if [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)}" ]; then + systemctl stop ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)} + systemctl disable ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)} + fi - systemctl disable ${SYSTEMD_SERVICE_ESCAPED} + [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)}" ] && \ + systemctl --global disable ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)} fi fi } @@ -67,6 +84,44 @@ fi systemd_populate_packages[vardeps] += "systemd_prerm systemd_postinst" systemd_populate_packages[vardepsexclude] += "OVERRIDES" +def systemd_service_path(service, searchpaths, d): + path_found = '' + + # Deal with adding, for example, 'ifplugd@eth0.service' from + # 'ifplugd@.service' + base = None + at = service.find('@') + if at != -1: + ext = service.rfind('.') + base = service[:at] + '@' + service[ext:] + + for path in searchpaths: + if os.path.lexists(oe.path.join(d.getVar("D"), path, service)): + path_found = path + break + elif base is not None: + if os.path.exists(oe.path.join(d.getVar("D"), path, base)): + path_found = path + break + + return path_found, base + +def systemd_service_exists(service, user, d): + searchpaths = [ + oe.path.join(d.getVar("sysconfdir"), "systemd", "user"), + d.getVar("systemd_user_unitdir"), + ] if user else [ + oe.path.join(d.getVar("sysconfdir"), "systemd", "system"), + d.getVar("systemd_system_unitdir"), + ] + + path, _ = systemd_service_path(service, searchpaths, d) + + return path != '' + +def systemd_filter_services(services, user, d): + return ' '.join(service for service in services.split() if systemd_service_exists(service, user, d)) + python systemd_populate_packages() { import re @@ -147,7 +202,10 @@ python systemd_populate_packages() { # Check service-files and call systemd_add_files_and_parse for each entry def systemd_check_services(): - searchpaths = [oe.path.join(d.getVar("sysconfdir"), "systemd", "system"),] + searchpaths = [ + oe.path.join(d.getVar("sysconfdir"), "systemd", "system"), + oe.path.join(d.getVar("sysconfdir"), "systemd", "user"), + ] searchpaths.append(d.getVar("systemd_system_unitdir")) searchpaths.append(d.getVar("systemd_user_unitdir")) systemd_packages = d.getVar('SYSTEMD_PACKAGES') @@ -155,24 +213,7 @@ python systemd_populate_packages() { # scan for all in SYSTEMD_SERVICE[] for pkg_systemd in systemd_packages.split(): for service in get_package_var(d, 'SYSTEMD_SERVICE', pkg_systemd).split(): - path_found = '' - - # Deal with adding, for example, 'ifplugd@eth0.service' from - # 'ifplugd@.service' - base = None - at = service.find('@') - if at != -1: - ext = service.rfind('.') - base = service[:at] + '@' + service[ext:] - - for path in searchpaths: - if os.path.lexists(oe.path.join(d.getVar("D"), path, service)): - path_found = path - break - elif base is not None: - if os.path.exists(oe.path.join(d.getVar("D"), path, base)): - path_found = path - break + path_found, base = systemd_service_path(service, searchpaths, d) if path_found != '': systemd_add_files_and_parse(pkg_systemd, path_found, service) @@ -180,13 +221,38 @@ python systemd_populate_packages() { bb.fatal("Didn't find service unit '{0}', specified in SYSTEMD_SERVICE:{1}. {2}".format( service, pkg_systemd, "Also looked for service unit '{0}'.".format(base) if base is not None else "")) - def systemd_create_presets(pkg, action): - presetf = oe.path.join(d.getVar("PKGD"), d.getVar("systemd_unitdir"), "system-preset/98-%s.preset" % pkg) + def _systemd_create_presets(pkg, action, prefix, searchpaths): + # Check there is at least one service of given type (system/user), don't + # create empty files. + needs_preset = False + for service in d.getVar('SYSTEMD_SERVICE:%s' % pkg).split(): + path_found, _ = systemd_service_path(service, searchpaths, d) + if path_found != '': + needs_preset = True + break + + if not needs_preset: + return + + presetf = oe.path.join(d.getVar("PKGD"), d.getVar("systemd_unitdir"), "%s-preset/98-%s.preset" % (prefix, pkg)) bb.utils.mkdirhier(os.path.dirname(presetf)) with open(presetf, 'a') as fd: for service in d.getVar('SYSTEMD_SERVICE:%s' % pkg).split(): + path_found, _ = systemd_service_path(service, searchpaths, d) + if path_found == '': + continue fd.write("%s %s\n" % (action,service)) - d.appendVar("FILES:%s" % pkg, ' ' + oe.path.join(d.getVar("systemd_unitdir"), "system-preset/98-%s.preset" % pkg)) + d.appendVar("FILES:%s" % pkg, ' ' + oe.path.join(d.getVar("systemd_unitdir"), "%s-preset/98-%s.preset" % (prefix, pkg))) + + def systemd_create_presets(pkg, action): + _systemd_create_presets(pkg, action, "system", [ + oe.path.join(d.getVar("sysconfdir"), "systemd", "system"), + d.getVar("systemd_system_unitdir"), + ]) + _systemd_create_presets(pkg, action, "user", [ + oe.path.join(d.getVar("sysconfdir"), "systemd", "user"), + d.getVar("systemd_user_unitdir"), + ]) # Run all modifications once when creating package if os.path.exists(d.getVar("D")): -- 2.47.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [OE-core] [PATCH 2/3] systemd.bbclass: add support for user presets 2025-01-12 14:31 ` [PATCH 2/3] systemd.bbclass: add support for user presets Artur Kowalski @ 2025-01-13 10:55 ` Alexander Kanavin 0 siblings, 0 replies; 8+ messages in thread From: Alexander Kanavin @ 2025-01-13 10:55 UTC (permalink / raw) To: arturkow2000; +Cc: openembedded-core Can this patch be split up to make review easier? Particularly, separate adding new functions into their own commits, with justification for each, and introduce usage of those functions in separate commits as well if possible, again with justification for each. Alex On Sun, 12 Jan 2025 at 15:40, Artur Kowalski via lists.openembedded.org <arturkow2000=gmail.com@lists.openembedded.org> wrote: > > Previously user units were causing build erros when listed in > SYSTEMD_SERVICE and SYSTEMD_AUTO_ENABLE was set. > > Signed-off-by: Artur Kowalski <arturkow2000@gmail.com> > --- > meta/classes-recipe/systemd.bbclass | 120 +++++++++++++++++++++------- > 1 file changed, 93 insertions(+), 27 deletions(-) > > diff --git a/meta/classes-recipe/systemd.bbclass b/meta/classes-recipe/systemd.bbclass > index 4b4470b7b3..80f4da3bdf 100644 > --- a/meta/classes-recipe/systemd.bbclass > +++ b/meta/classes-recipe/systemd.bbclass > @@ -37,17 +37,29 @@ if systemctl >/dev/null 2>/dev/null; then > fi > > if [ "${SYSTEMD_AUTO_ENABLE}" = "enable" ]; then > - for service in ${SYSTEMD_SERVICE_ESCAPED}; do > + for service in ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)}; do > systemctl ${OPTS} enable "$service" > done > + > + for service in ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)}; do > + systemctl --global ${OPTS} enable "$service" > + done > fi > > if [ -z "$D" ]; then > + # Reload only system service manager > + # --global for daemon-reload is not supported: https://github.com/systemd/systemd/issues/19284 > systemctl daemon-reload > - systemctl preset ${SYSTEMD_SERVICE_ESCAPED} > + [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)}" ] && \ > + systemctl preset ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)} > + > + [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)}" ] && \ > + systemctl --global preset ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)} > > if [ "${SYSTEMD_AUTO_ENABLE}" = "enable" ]; then > - systemctl --no-block restart ${SYSTEMD_SERVICE_ESCAPED} > + # --global flag for restart is not supported by systemd (see above) > + [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)}" ] && \ > + systemctl --no-block restart ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)} > fi > fi > fi > @@ -56,9 +68,14 @@ fi > systemd_prerm() { > if systemctl >/dev/null 2>/dev/null; then > if [ -z "$D" ]; then > - systemctl stop ${SYSTEMD_SERVICE_ESCAPED} > + # same as above, --global flag is not supported for stop > + if [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)}" ]; then > + systemctl stop ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)} > + systemctl disable ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", False, d)} > + fi > > - systemctl disable ${SYSTEMD_SERVICE_ESCAPED} > + [ -n "${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)}" ] && \ > + systemctl --global disable ${@systemd_filter_services("${SYSTEMD_SERVICE_ESCAPED}", True, d)} > fi > fi > } > @@ -67,6 +84,44 @@ fi > systemd_populate_packages[vardeps] += "systemd_prerm systemd_postinst" > systemd_populate_packages[vardepsexclude] += "OVERRIDES" > > +def systemd_service_path(service, searchpaths, d): > + path_found = '' > + > + # Deal with adding, for example, 'ifplugd@eth0.service' from > + # 'ifplugd@.service' > + base = None > + at = service.find('@') > + if at != -1: > + ext = service.rfind('.') > + base = service[:at] + '@' + service[ext:] > + > + for path in searchpaths: > + if os.path.lexists(oe.path.join(d.getVar("D"), path, service)): > + path_found = path > + break > + elif base is not None: > + if os.path.exists(oe.path.join(d.getVar("D"), path, base)): > + path_found = path > + break > + > + return path_found, base > + > +def systemd_service_exists(service, user, d): > + searchpaths = [ > + oe.path.join(d.getVar("sysconfdir"), "systemd", "user"), > + d.getVar("systemd_user_unitdir"), > + ] if user else [ > + oe.path.join(d.getVar("sysconfdir"), "systemd", "system"), > + d.getVar("systemd_system_unitdir"), > + ] > + > + path, _ = systemd_service_path(service, searchpaths, d) > + > + return path != '' > + > +def systemd_filter_services(services, user, d): > + return ' '.join(service for service in services.split() if systemd_service_exists(service, user, d)) > + > > python systemd_populate_packages() { > import re > @@ -147,7 +202,10 @@ python systemd_populate_packages() { > > # Check service-files and call systemd_add_files_and_parse for each entry > def systemd_check_services(): > - searchpaths = [oe.path.join(d.getVar("sysconfdir"), "systemd", "system"),] > + searchpaths = [ > + oe.path.join(d.getVar("sysconfdir"), "systemd", "system"), > + oe.path.join(d.getVar("sysconfdir"), "systemd", "user"), > + ] > searchpaths.append(d.getVar("systemd_system_unitdir")) > searchpaths.append(d.getVar("systemd_user_unitdir")) > systemd_packages = d.getVar('SYSTEMD_PACKAGES') > @@ -155,24 +213,7 @@ python systemd_populate_packages() { > # scan for all in SYSTEMD_SERVICE[] > for pkg_systemd in systemd_packages.split(): > for service in get_package_var(d, 'SYSTEMD_SERVICE', pkg_systemd).split(): > - path_found = '' > - > - # Deal with adding, for example, 'ifplugd@eth0.service' from > - # 'ifplugd@.service' > - base = None > - at = service.find('@') > - if at != -1: > - ext = service.rfind('.') > - base = service[:at] + '@' + service[ext:] > - > - for path in searchpaths: > - if os.path.lexists(oe.path.join(d.getVar("D"), path, service)): > - path_found = path > - break > - elif base is not None: > - if os.path.exists(oe.path.join(d.getVar("D"), path, base)): > - path_found = path > - break > + path_found, base = systemd_service_path(service, searchpaths, d) > > if path_found != '': > systemd_add_files_and_parse(pkg_systemd, path_found, service) > @@ -180,13 +221,38 @@ python systemd_populate_packages() { > bb.fatal("Didn't find service unit '{0}', specified in SYSTEMD_SERVICE:{1}. {2}".format( > service, pkg_systemd, "Also looked for service unit '{0}'.".format(base) if base is not None else "")) > > - def systemd_create_presets(pkg, action): > - presetf = oe.path.join(d.getVar("PKGD"), d.getVar("systemd_unitdir"), "system-preset/98-%s.preset" % pkg) > + def _systemd_create_presets(pkg, action, prefix, searchpaths): > + # Check there is at least one service of given type (system/user), don't > + # create empty files. > + needs_preset = False > + for service in d.getVar('SYSTEMD_SERVICE:%s' % pkg).split(): > + path_found, _ = systemd_service_path(service, searchpaths, d) > + if path_found != '': > + needs_preset = True > + break > + > + if not needs_preset: > + return > + > + presetf = oe.path.join(d.getVar("PKGD"), d.getVar("systemd_unitdir"), "%s-preset/98-%s.preset" % (prefix, pkg)) > bb.utils.mkdirhier(os.path.dirname(presetf)) > with open(presetf, 'a') as fd: > for service in d.getVar('SYSTEMD_SERVICE:%s' % pkg).split(): > + path_found, _ = systemd_service_path(service, searchpaths, d) > + if path_found == '': > + continue > fd.write("%s %s\n" % (action,service)) > - d.appendVar("FILES:%s" % pkg, ' ' + oe.path.join(d.getVar("systemd_unitdir"), "system-preset/98-%s.preset" % pkg)) > + d.appendVar("FILES:%s" % pkg, ' ' + oe.path.join(d.getVar("systemd_unitdir"), "%s-preset/98-%s.preset" % (prefix, pkg))) > + > + def systemd_create_presets(pkg, action): > + _systemd_create_presets(pkg, action, "system", [ > + oe.path.join(d.getVar("sysconfdir"), "systemd", "system"), > + d.getVar("systemd_system_unitdir"), > + ]) > + _systemd_create_presets(pkg, action, "user", [ > + oe.path.join(d.getVar("sysconfdir"), "systemd", "user"), > + d.getVar("systemd_user_unitdir"), > + ]) > > # Run all modifications once when creating package > if os.path.exists(d.getVar("D")): > -- > 2.47.0 > > > -=-=-=-=-=-=-=-=-=-=-=- > Links: You receive all messages sent to this group. > View/Reply Online (#209687): https://lists.openembedded.org/g/openembedded-core/message/209687 > Mute This Topic: https://lists.openembedded.org/mt/110569455/1686489 > Group Owner: openembedded-core+owner@lists.openembedded.org > Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [alex.kanavin@gmail.com] > -=-=-=-=-=-=-=-=-=-=-=- > ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/3] image.bbclass: enable systemd user services 2025-01-12 14:31 [PATCH 0/3] Systemd user presets support arturkow2000 2025-01-12 14:31 ` [PATCH 1/3] systemd-systemctl: add support for --global flag Artur Kowalski 2025-01-12 14:31 ` [PATCH 2/3] systemd.bbclass: add support for user presets Artur Kowalski @ 2025-01-12 14:31 ` Artur Kowalski 2 siblings, 0 replies; 8+ messages in thread From: Artur Kowalski @ 2025-01-12 14:31 UTC (permalink / raw) To: openembedded-core; +Cc: Artur Kowalski Run systemctl preset-all with --global flag so user unit's are enabled the same way system units are. Signed-off-by: Artur Kowalski <arturkow2000@gmail.com> --- meta/classes-recipe/image.bbclass | 1 + 1 file changed, 1 insertion(+) diff --git a/meta/classes-recipe/image.bbclass b/meta/classes-recipe/image.bbclass index 84a2017eb5..f08818db03 100644 --- a/meta/classes-recipe/image.bbclass +++ b/meta/classes-recipe/image.bbclass @@ -702,6 +702,7 @@ reproducible_final_image_task () { systemd_preset_all () { if [ -e ${IMAGE_ROOTFS}${root_prefix}/lib/systemd/systemd ]; then systemctl --root="${IMAGE_ROOTFS}" --preset-mode=enable-only preset-all + systemctl --root="${IMAGE_ROOTFS}" --global --preset-mode=enable-only preset-all fi } -- 2.47.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-01-15 14:04 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-01-12 14:31 [PATCH 0/3] Systemd user presets support arturkow2000 2025-01-12 14:31 ` [PATCH 1/3] systemd-systemctl: add support for --global flag Artur Kowalski 2025-01-12 15:34 ` [OE-core] " Alex Kiernan 2025-01-13 21:18 ` Artur Kowalski 2025-01-15 14:04 ` Alex Kiernan 2025-01-12 14:31 ` [PATCH 2/3] systemd.bbclass: add support for user presets Artur Kowalski 2025-01-13 10:55 ` [OE-core] " Alexander Kanavin 2025-01-12 14:31 ` [PATCH 3/3] image.bbclass: enable systemd user services Artur Kowalski
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox