Skip to content

Commit ce6924c

Browse files
committed
Use runuser or run0 to connect to systemd --user instance
Create a new function `systemd::systemctl_user` to construct array of command used to contact `systemd --user` instance so `systemctl --user status my.service` can be executed. In addition we switch to using `runuser` or `run0` for systemd >= 256 to become the user. * Using `systemctl` or `systemd-run` with --machinectl [email protected] always fails when puppet is ran inside its own systemd unit. I believe systemd services can not access `systemd --user` instances. * We use `runuser -u $user` rather than a `user => $user` on the exec since the $(id -u $user) must be resolved late within the exec itself. Now the command is generated inside a function this will make future improvements to this method much simpler.
1 parent c7c3960 commit ce6924c

File tree

12 files changed

+229
-51
lines changed

12 files changed

+229
-51
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,25 @@ class { 'systemd':
484484
}
485485
```
486486

487+
### User Services
488+
489+
User services can be managed
490+
491+
```puppet
492+
493+
systemd::user_service { 'hour.service':
494+
ensure => true,
495+
enable => true,
496+
user => 'higgs',
497+
}
498+
```
499+
500+
This will run `systemctl --user enable hour.service` and `systemctl --user start hour.service` as the user higgs.
501+
502+
On Debian 12 and RHEL9 the `runuser` command is used for `systemd::user_service` which can be installed with
503+
the `runuser => true` parameter to the main class. Newer operating systems use `run0` which is always available with `systemd`.
504+
505+
487506
### journald configuration
488507

489508
It also allows you to manage journald settings. You can manage journald settings through setting the `journald_settings` parameter. If you want a parameter to be removed, you can pass its value as params.

REFERENCE.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
### Functions
5454

5555
* [`systemd::escape`](#systemd--escape): Escape strings as systemd-escape does.
56+
* [`systemd::systemctl_user`](#systemd--systemctl_user): Construct command array for running `systemctl --user` for particular arguments
5657
* [`systemd::systemd_escape`](#systemd--systemd_escape): Escape strings by call the `systemd-escape` command in the background.
5758

5859
### Data types
@@ -269,6 +270,7 @@ The following parameters are available in the `systemd` class:
269270
* [`system_settings`](#-systemd--system_settings)
270271
* [`manage_user_conf`](#-systemd--manage_user_conf)
271272
* [`user_settings`](#-systemd--user_settings)
273+
* [`install_runuser`](#-systemd--install_runuser)
272274

273275
##### <a name="-systemd--default_target"></a>`default_target`
274276

@@ -896,6 +898,14 @@ the settings.
896898

897899
Default value: `{}`
898900

901+
##### <a name="-systemd--install_runuser"></a>`install_runuser`
902+
903+
Data type: `Boolean`
904+
905+
If true, the util-linux package is installed, for runuser command.
906+
907+
Default value: `false`
908+
899909
### <a name="systemd--tmpfiles"></a>`systemd::tmpfiles`
900910

901911
Update the systemd temp files
@@ -2793,6 +2803,9 @@ Manage a user service running under systemd --user
27932803
##### Enable a service for all users
27942804

27952805
```puppet
2806+
class { 'systemd':
2807+
install_runuser => true,
2808+
}
27962809
systemd::user_service { 'systemd-tmpfiles-clean.timer':
27972810
enable => true,
27982811
global => true,
@@ -2972,6 +2985,50 @@ Data type: `Boolean`
29722985

29732986
Use path (-p) ornon-path style escaping.
29742987

2988+
### <a name="systemd--systemctl_user"></a>`systemd::systemctl_user`
2989+
2990+
Type: Puppet Language
2991+
2992+
Construct command array for running `systemctl --user` for particular arguments
2993+
2994+
#### Examples
2995+
2996+
##### Start a user service with an exec
2997+
2998+
```puppet
2999+
exec { 'start_service':
3000+
command => systemd::systemctl_user('santa', 'start myservice.service'),
3001+
}
3002+
```
3003+
3004+
#### `systemd::systemctl_user(String[1] $user, Array[String[1],1] $arguments)`
3005+
3006+
The systemd::systemctl_user function.
3007+
3008+
Returns: `Array` Array Array of command and arguments
3009+
3010+
##### Examples
3011+
3012+
###### Start a user service with an exec
3013+
3014+
```puppet
3015+
exec { 'start_service':
3016+
command => systemd::systemctl_user('santa', 'start myservice.service'),
3017+
}
3018+
```
3019+
3020+
##### `user`
3021+
3022+
Data type: `String[1]`
3023+
3024+
User instance of `systemd --user` to connect to.
3025+
3026+
##### `arguments`
3027+
3028+
Data type: `Array[String[1],1]`
3029+
3030+
Arguments to run after `systemctl --user`
3031+
29753032
### <a name="systemd--systemd_escape"></a>`systemd::systemd_escape`
29763033

29773034
Type: Ruby 4.x API

functions/systemctl_user.pp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# @summary Construct command array for running `systemctl --user` for particular arguments
2+
# @param user User instance of `systemd --user` to connect to.
3+
# @param arguments Arguments to run after `systemctl --user`
4+
#
5+
# @return Array Array of command and arguments
6+
# @example Start a user service with an exec
7+
# exec { 'start_service':
8+
# command => systemd::systemctl_user('santa', 'start myservice.service'),
9+
# }
10+
function systemd::systemctl_user(String[1] $user, Array[String[1],1] $arguments) >> Array {
11+
# Why is runuser being used here for older systemds?
12+
# More obvious command arrays to return would be:
13+
# * ['run0','--user',$user,'systemctl','--user'] + $arguments
14+
# * ['systemctl', '--user', '--machine', "${user}@.host"] + $arguments
15+
# * ['systemd-run','--wait','--pipe', 'systemctl', '--user', '--machine', "${user}@.host"] + $arguments
16+
# However none of these work when puppet is run as a background service. They only work with
17+
# puppet apply in the foreground. Reason is unclear, possibly polkit blocking access
18+
# https://github.com/voxpupuli/puppet-systemd/issues/459
19+
20+
$_cmd_array = Integer($facts['systemd_version']) < 256 ? {
21+
true => [
22+
'runuser', '-u', $user, '--' ,'/usr/bin/bash', '-c',
23+
"env XDG_RUNTIME_DIR=/run/user/\$(id -u) /usr/bin/systemctl --user ${arguments.join(' ')}",
24+
],
25+
default => ['run0','--user',$user,'/usr/bin/systemctl','--user'] + $arguments,
26+
}
27+
28+
return $_cmd_array
29+
}

manifests/daemon_reload.pp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
}
4343

4444
$_title = "${module_name}-${name}-systemctl-user-${user}-daemon-reload"
45-
$_command = ['systemd-run', '--pipe', '--wait', '--user', '--machine', "${user}@.host", 'systemctl', '--user', 'daemon-reload']
45+
$_command = systemd::systemctl_user($user, ['daemon-reload'])
4646
} else {
4747
$_title = "${module_name}-${name}-systemctl-daemon-reload"
4848
$_command = ['systemctl', 'daemon-reload']

manifests/init.pp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@
256256
# Config Hash that is used to configure settings in user.conf
257257
# NOTE: It's currently impossible to have multiple entries of the same key in
258258
# the settings.
259+
#
260+
# @param install_runuser
261+
# If true, the util-linux package is installed, for runuser command.
262+
#
259263
class systemd (
260264
Optional[Pattern['^.+\.target$']] $default_target = undef,
261265
Hash[String,String] $accounting = {},
@@ -331,6 +335,7 @@
331335
Systemd::ServiceManagerSettings $system_settings = {},
332336
Boolean $manage_user_conf = false,
333337
Systemd::ServiceManagerSettings $user_settings = {},
338+
Boolean $install_runuser = false,
334339
) {
335340
contain systemd::install
336341

manifests/install.pp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,10 @@
3939
ensure => present,
4040
}
4141
}
42+
43+
if $systemd::install_runuser {
44+
package { 'util-linux':
45+
ensure => installed,
46+
}
47+
}
4248
}

manifests/user_service.pp

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# @summary Manage a user service running under systemd --user
22
#
33
# @example Enable a service for all users
4+
# class { 'systemd':
5+
# install_runuser => true,
6+
# }
47
# systemd::user_service { 'systemd-tmpfiles-clean.timer':
58
# enable => true,
69
# global => true,
@@ -74,31 +77,26 @@
7477
}
7578
} else { # per user services
7679

77-
$_systemctl_user = [
78-
'systemd-run', '--pipe', '--wait', '--user', '--machine', "${user}@.host",
79-
'systemctl', '--user',
80-
]
81-
8280
# To accept notifies of this type.
8381
exec { "try-reload-or-restart-${user}-${unit}":
84-
command => $_systemctl_user + ['try-reload-or-restart', $unit],
82+
command => systemd::systemctl_user($user, ['try-reload-or-restart', $unit]),
8583
refreshonly => true,
8684
path => $facts['path'],
8785
}
8886

8987
if $_ensure {
9088
$_ensure_title = "Start user service ${unit} for user ${user}"
91-
$_ensure_command = $_systemctl_user + ['start', $unit]
92-
$_ensure_unless = [$_systemctl_user + ['is-active', $unit]]
89+
$_ensure_command = systemd::systemctl_user($user, ['start',$unit])
90+
$_ensure_unless = [systemd::systemctl_user($user, ['is-active',$unit])]
9391
$_ensure_onlyif = undef
9492

9593
# Don't reload just after starting
9694
Exec["try-reload-or-restart-${user}-${unit}"] -> Exec[$_ensure_title]
9795
} else {
9896
$_ensure_title = "Stop user service ${unit} for user ${user}"
99-
$_ensure_command = $_systemctl_user + ['stop', $unit]
97+
$_ensure_command = systemd::systemctl_user($user, ['stop', $unit])
10098
$_ensure_unless = undef
101-
$_ensure_onlyif = [$_systemctl_user + ['is-active', $unit]]
99+
$_ensure_onlyif = [systemd::systemctl_user($user, ['is-active',$unit])]
102100
}
103101

104102
exec { $_ensure_title:
@@ -110,8 +108,8 @@
110108

111109
if $enable {
112110
$_enable_title = "Enable user service ${unit} for user ${user}"
113-
$_enable_command = $_systemctl_user + ['enable', $unit]
114-
$_enable_unless = [$_systemctl_user + ['is-enabled', $unit]]
111+
$_enable_command = systemd::systemctl_user($user, ['enable', $unit])
112+
$_enable_unless = [systemd::systemctl_user($user, ['is-enabled', $unit])]
115113
$_enable_onlyif = undef
116114

117115
# Puppet does this for services so lets copy that logic
@@ -121,9 +119,9 @@
121119
}
122120
} else {
123121
$_enable_title = "Disable user service ${unit} for user ${user}"
124-
$_enable_command = $_systemctl_user + ['disable', $unit]
122+
$_enable_command = systemd::systemctl_user($user, ['disable', $unit])
125123
$_enable_unless = undef
126-
$_enable_onlyif = [$_systemctl_user + ['is-enabled', $unit]]
124+
$_enable_onlyif = [systemd::systemctl_user($user, ['is-enabled', $unit])]
127125
}
128126

129127
exec { $_enable_title:

spec/acceptance/user_service_spec.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
1010
# systemd-logind.service must be running.
1111
class{ 'systemd':
12-
manage_logind => true,
12+
manage_logind => true,
13+
install_runuser => true,
1314
}
1415
1516
user { 'higgs' :
@@ -21,6 +22,13 @@
2122
linger => enabled,
2223
}
2324
25+
# https://github.com/voxpupuli/puppet-systemd/issues/578
26+
exec{'/usr/bin/sleep 10 && touch /tmp/sleep-only-once':
27+
creates => '/tmp/sleep-only-once',
28+
require => Loginctl_user['higgs'],
29+
before => File['/home/higgs/.config'],
30+
}
31+
2432
# Assumes home directory was created as /home/higgs
2533
file{['/home/higgs/.config', '/home/higgs/.config/systemd','/home/higgs/.config/systemd/user']:
2634
ensure => directory,

spec/classes/init_spec.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
it { is_expected.not_to contain_package('systemd-timesyncd') }
2222
it { is_expected.not_to contain_package('systemd-resolved') }
2323
it { is_expected.not_to contain_package('systemd-container') }
24+
it { is_expected.not_to contain_package('util-linux') }
2425
it { is_expected.not_to contain_class('systemd::coredump') }
2526
it { is_expected.not_to contain_class('systemd::oomd') }
2627
it { is_expected.not_to contain_exec('systemctl set-default multi-user.target') }
@@ -1188,6 +1189,14 @@
11881189
it { is_expected.to contain_systemd__dropin_file('coredump_backtrace.conf').with_content(%r{^ExecStart=.*--backtrace$}) }
11891190
end
11901191
end
1192+
1193+
context 'with install_runuser true' do
1194+
let :params do
1195+
{ install_runuser: true }
1196+
end
1197+
1198+
it { is_expected.to contain_package('util-linux').with_ensure('installed') }
1199+
end
11911200
end
11921201
end
11931202
end

spec/defines/daemon_reload_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
context 'supported operating systems' do
77
on_supported_os.each do |os, facts|
88
context "on #{os}" do
9-
let(:facts) { facts }
9+
let(:facts) { facts.merge(systemd_version: '256') }
1010
let(:title) { 'irregardless' }
1111

1212
it { is_expected.to compile.with_all_deps }
@@ -36,7 +36,7 @@
3636

3737
it {
3838
is_expected.to contain_exec('systemd-irregardless-systemctl-user-steve-daemon-reload').
39-
with_command(['systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', 'systemctl', '--user', 'daemon-reload']).
39+
with_command(['run0', '--user', 'steve', '/usr/bin/systemctl', '--user', 'daemon-reload']).
4040
with_refreshonly(true)
4141
}
4242

0 commit comments

Comments
 (0)