diff options
author | Daniele Nicolodi <daniele@grinta.net> | 2018-06-12 19:47:20 -0600 |
---|---|---|
committer | Daniele Nicolodi <daniele@grinta.net> | 2018-10-14 01:35:49 -0600 |
commit | 9076fc79b27a7b7aa5f8a1e3b1e0f54a781aefba (patch) | |
tree | b4b9481fdd95cdc2129267a350874e450ee6ec0a | |
parent | a69dfe2854a4ba25008fb4d17db561ad33034b8d (diff) | |
download | debhelper-9076fc79b27a7b7aa5f8a1e3b1e0f54a781aefba.tar.gz |
dh_installsystemduser: New helper to handle systemd user instance units
Add a new 'dh_installsystemduser' helper responsible for istalling
package maintainer supplied systemd user instance units and to produce
postinst and postrm maintiner scripts code blocks to appropriately
enable, mask and disable units when the package is installed,
upgraded, or removed.
-rw-r--r-- | autoscripts/postinst-systemd-user-dont-enable | 15 | ||||
-rw-r--r-- | autoscripts/postinst-systemd-user-enable | 15 | ||||
-rw-r--r-- | autoscripts/postrm-systemd-user | 12 | ||||
-rw-r--r-- | debhelper.pod | 5 | ||||
-rw-r--r-- | debian/changelog | 11 | ||||
-rwxr-xr-x | dh | 19 | ||||
-rwxr-xr-x | dh_installsystemduser | 270 | ||||
-rw-r--r-- | t/dh_installsystemduser/debian/baz.user.service | 8 | ||||
-rw-r--r-- | t/dh_installsystemduser/debian/changelog | 5 | ||||
-rw-r--r-- | t/dh_installsystemduser/debian/control | 10 | ||||
-rw-r--r-- | t/dh_installsystemduser/debian/foo.user.service | 8 | ||||
-rw-r--r-- | t/dh_installsystemduser/dh_installsystemduser.t | 75 |
12 files changed, 444 insertions, 9 deletions
diff --git a/autoscripts/postinst-systemd-user-dont-enable b/autoscripts/postinst-systemd-user-dont-enable new file mode 100644 index 00000000..0837257f --- /dev/null +++ b/autoscripts/postinst-systemd-user-dont-enable @@ -0,0 +1,15 @@ +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + if deb-systemd-helper --user debian-installed #UNITFILE# ; then + # This will only remove masks created by d-s-h on package removal. + deb-systemd-helper --user unmask #UNITFILE# >/dev/null || true + + if deb-systemd-helper --quiet --user was-enabled #UNITFILE# ; then + # Create new symlinks, if any. + deb-systemd-helper --user enable #UNITFILE# >/dev/null || true + fi + fi + + # Update the statefile to add new symlinks (if any), which need to be cleaned + # up on purge. Also remove old symlinks. + deb-systemd-helper --user update-state #UNITFILE# >/dev/null || true +fi diff --git a/autoscripts/postinst-systemd-user-enable b/autoscripts/postinst-systemd-user-enable new file mode 100644 index 00000000..b16f61fb --- /dev/null +++ b/autoscripts/postinst-systemd-user-enable @@ -0,0 +1,15 @@ +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + # This will only remove masks created by d-s-h on package removal. + deb-systemd-helper --user unmask #UNITFILE# >/dev/null || true + + # was-enabled defaults to true, so new installations run enable. + if deb-systemd-helper --quiet --user was-enabled #UNITFILE# ; then + # Enables the unit on first installation, creates new + # symlinks on upgrades if the unit file has changed. + deb-systemd-helper --user enable #UNITFILE# >/dev/null || true + else + # Update the statefile to add new symlinks (if any), which need to be + # cleaned up on purge. Also remove old symlinks. + deb-systemd-helper --user update-state #UNITFILE# >/dev/null || true + fi +fi diff --git a/autoscripts/postrm-systemd-user b/autoscripts/postrm-systemd-user new file mode 100644 index 00000000..bf48d1a3 --- /dev/null +++ b/autoscripts/postrm-systemd-user @@ -0,0 +1,12 @@ +if [ "$1" = "remove" ]; then + if [ -x "/usr/bin/deb-systemd-helper" ] ; then + deb-systemd-helper --user mask #UNITFILES# >/dev/null || true + fi +fi + +if [ "$1" = "purge" ]; then + if [ -x "/usr/bin/deb-systemd-helper" ] ; then + deb-systemd-helper --user purge #UNITFILES# >/dev/null || true + deb-systemd-helper --user unmask #UNITFILES# >/dev/null || true + fi +fi diff --git a/debhelper.pod b/debhelper.pod index 177d2f8f..c758a72b 100644 --- a/debhelper.pod +++ b/debhelper.pod @@ -915,6 +915,11 @@ packages and not only during the building process. Please set B<DH_GOLANG_EXCLUDES_ALL> to false to revert to the previous behaviour. See B<Debian::Debhelper::Buildsystem::golang(3pm)> for details and examples. +=item - + +B<dh_installsystemduser> is now included in the B<dh> standard +sequence by default. + =back =back diff --git a/debian/changelog b/debian/changelog index 6b3e3605..f25e531c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +debhelper (11.5) UNRELEASED; urgency=medium + + [ Daniele Nicolodi ] + * dh_installsystemduser: New helper responsible for istalling package + maintainer supplied systemd user instance units and to produce + postinst and postrm maintiner scripts code blocks to appropriately + enable, mask and disable units when the package is installed, + upgraded, or removed. (Closes: #764678) + + -- Daniele Nicolodi <daniele@grinta.net> Sun, 14 Oct 2018 10:13:17 -0600 + debhelper (11.4.1) unstable; urgency=medium [ Niels Thykier ] @@ -115,10 +115,10 @@ easy way to do with is by adding an override target for that command. #!/usr/bin/make -f %: dh $@ - + override_dh_strip: dh_strip -Xfoo - + override_dh_auto_configure: dh_auto_configure -- --with-foo --disable-bar @@ -199,7 +199,7 @@ want it to run, by defining empty override targets for each command. #!/usr/bin/make -f %: dh $@ - + # Commands not to run: override_dh_auto_test override_dh_compress override_dh_fixperms: @@ -210,7 +210,7 @@ These will be skipped when running build-arch and binary-arch sequences. #!/usr/bin/make -f %: dh $@ - + override_dh_auto_build-indep: $(MAKE) -C docs @@ -272,7 +272,7 @@ option to ensure they only work on architecture dependent packages. =head1 DEPRECATED OPTIONS -The following options are deprecated. It's much +The following options are deprecated. It's much better to use override targets instead. They are B<not> available in compat 10. @@ -308,7 +308,7 @@ my @ARGV_orig=@ARGV; my (@addons, @addon_requests); inhibit_log(); - + init(options => { "until=s" => \$dh{UNTIL}, "after=s" => \$dh{AFTER}, @@ -325,7 +325,7 @@ init(options => { "l" => \&list_addons, "list" => \&list_addons, }, - # Disable complaints about unknown options; they are passed on to + # Disable complaints about unknown options; they are passed on to # the debhelper commands. ignore_unknown_options => 1, # Bundling does not work well since there are unknown options. @@ -405,6 +405,7 @@ my @i = (qw{ dh_installinit }, (!compat(10) ? qw(dh_installsystemd) : qw()), + (compat(12) ? qw(dh_installsystemduser) : qw()), qw{ dh_installmenu @@ -499,7 +500,7 @@ sub remove_command { foreach my $sequence (keys %sequences) { $sequences{$sequence}=[grep { $_ ne $command } @{$sequences{$sequence}}]; } - + } sub add_command { my ($command, $sequence) = @_; @@ -864,7 +865,7 @@ foreach my $i (0..$stoppoint) { close($fd) or error("close($stamp_file) failed: $!"); next; } - + # Check for override targets in debian/rules, and run instead of # the usual command. (The non-arch-specific override is tried first, # for simplest semantics; mixing it with arch-specific overrides diff --git a/dh_installsystemduser b/dh_installsystemduser new file mode 100755 index 00000000..bc1d0368 --- /dev/null +++ b/dh_installsystemduser @@ -0,0 +1,270 @@ +#!/usr/bin/perl -w + +=head1 NAME + +dh_installsystemduser - install systemd unit files + +=cut + +use strict; +use warnings; +use Debian::Debhelper::Dh_Lib; + +our $VERSION = DH_BUILTIN_VERSION; + +=head1 SYNOPSIS + +B<dh_installsystemduser> [S<I<debhelper options>>] [B<--no-enable>] [B<--name=>I<name>] [S<I<unit file> ...>] + +=head1 DESCRIPTION + +B<dh_installsystemduser> finds the systemd user instance service files +installed by a package and generates F<postinst>, and F<prerm> code +blocks for enabling and disabling the corresponding systemd user +instance services, when the package is installed, updated, or +removed. These snippets are added to the maintainer scripts by +L<dh_installdeb(1)>. + +L<deb-systemd-helper(1)> is used to enable and disable the systemd +units, thus it is not necessary that the machine actually runs systemd +during package installation time, enabling happens on all machines. + +B<dh_installsystemduser> operates on all user instance unit files +installed by a package. For only generating blocks for specific unit +files, pass them as arguments. Specific unit files can be excluded +from processing using the B<-X> common L<debhelper(1)> option. + +=head1 FILES + +=over 4 + +=item debian/I<package>.user.path, + debian/I<package>@.user.path, + debian/I<package>.user.service, + debian/I<package>@.user.service, + debian/I<package>.user.socket, + debian/I<package>@.user.socket, + debian/I<package>.user.target, + debian/I<package>@.user.target, + debian/I<package>.user.timer, + debian/I<package>@.user.timer + +If any of those files exists, they are installed into +F<usr/lib/systemd/user/> in the package build directory removing the +F<.user> file name part. + +=back + +=head1 OPTIONS + +=over 4 + +=item B<--name=>I<name> + +Install the service file as I<name.service> instead of the default +filename I<package.service>. When this parameter is used, +B<dh_installsystemd> looks for and installs files named +F<debian/package.name.user.service> instead of the usual +F<debian/package.user.service>. Moreover, maintainer scripts are only +generated for units that match the given I<name>. + +=item B<--no-enable> + +Disable the service(s) on purge, but do not enable them on install. + +=back + +=head1 NOTES + +This command is not idempotent. L<dh_prep(1)> should be called between +invocations of this command (with the same arguments). Otherwise, it +may cause multiple instances of the same text to be added to +maintainer scripts. + +=cut + +# PROMISE: DH NOOP WITHOUT tmp(usr/lib/systemd/user) user.service + +init(options => { + "no-enable" => \$dh{NO_ENABLE}, +}); + +sub quote { + # Add single quotes around the argument. + return '\'' . $_[0] . '\''; +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub contains_install_section { + my ($unit_path) = @_; + + open(my $fh, '<', $unit_path) or error("Cannot open($unit_path) to check for [Install]: $!"); + + while (my $line = <$fh>) { + chomp($line); + return 1 if $line =~ /^\s*\[Install\]$/i; + } + close($fh); + return 0; +} + +sub install_user_unit { + my ($package, $name, $suffix) = @_; + + my $tmpdir = tmpdir($package); + my $path = "$tmpdir/usr/lib/systemd/user"; + my $unit = pkgfile($package, "user.$suffix"); + return if $unit eq ''; + + install_dir($path); + install_file($unit, "$path/$name.$suffix"); +} + +# Extracts the directive values from a unit file. Handles repeated +# directives in the same unit file. Assumes values can only be +# composed of lists of unit names. This is good enough for the 'Also=' +# and 'Alias=' directives handled here. +sub extract_key { + my ($unit_path, $key) = @_; + my @values; + + open(my $fh, '<', $unit_path) or error("Cannot open($unit_path): $!"); + + while (my $line = <$fh>) { + chomp($line); + + # Since unit names can't have whitespace in systemd, simply + # use split and strip any leading/trailing quotes. See + # systemd-escape(1) for examples of valid unit names. + if ($line =~ /^\s*$key=(.+)$/i) { + for my $value (split(/\s+/, $1)) { + $value =~ s/^(["'])(.*)\g1$/$2/; + push @values, $value; + } + } + } + + close($fh); + return @values; +} + +sub list_installed_user_units { + my ($tmpdir, $aliases) = @_; + + my $lib_systemd_user = "$tmpdir/usr/lib/systemd/user"; + my @installed; + + return unless -d $lib_systemd_user; + opendir(my $dh, $lib_systemd_user) or error("Cannot opendir($lib_systemd_user): $!"); + + foreach my $name (readdir($dh)) { + my $path = "$lib_systemd_user/$name"; + next unless -f $path; + if (-l $path) { + my $dest = basename(readlink($path)); + $aliases->{$dest} //= [ ]; + push @{$aliases->{$dest}}, $name; + } else { + push @installed, $name; + } + } + + closedir($dh); + return @installed; +} + +# Install package maintainer provided unit files. +foreach my $package (@{$dh{DOPACKAGES}}) { + my $tmpdir = tmpdir($package); + + # unit file name + my $name = $dh{NAME} // $package; + + for my $type (qw(service target socket path timer)) { + install_user_unit($package, $name, $type); + install_user_unit("${package}@", "${name}@", $type); + } +} + +# Generate postinst and prerm code blocks to enable and disable units +foreach my $package (@{$dh{DOPACKAGES}}) { + my (@args, @enable_units, %aliases); + + my $tmpdir = tmpdir($package); + my @units = list_installed_user_units($tmpdir, \%aliases); + + # Handle either only the unit files which were passed as arguments + # or all unit files that are installed in this package. + if (@ARGV) { + @args = @ARGV; + } + elsif ($dh{NAME}) { + # Treat --name flag as if the corresponding units were passed + # in the command line. + @args = grep /(^|\/)$dh{NAME}\.(service|target|socket|path|timer)$/, @units; + } + else { + @args = @units; + } + + # Support excluding units via the -X debhelper common option. + foreach my $x (@{$dh{EXCLUDE}}) { + @args = grep !/(^|\/)$x$/, @args; + } + + # This hash prevents us from looping forever in the following + # while loop. An actual real-world example of such a loop is + # systemd's systemd-readahead-drop.service, which contains + # Also=systemd-readahead-collect.service, and that file in turn + # contains Also=systemd-readahead-drop.service, thus forming an + # endless loop. + my %seen; + + # Must use while and shift because the loop alters the list. + while (@args) { + my $name = shift @args; + my $path = "${tmpdir}/usr/lib/systemd/user/${name}"; + + error("User unit file \"$name\" not found in package \"$package\".") if ! -f $path; + + # Skip template service files. Enabling or disabling those + # services without specifying the instance is not useful. + next if ($name =~ /\@/); + + # Handle all unit files specified via Also= explicitly. This + # is not necessary for enabling, but for disabling, as we + # cannot read the unit file when disabling as it has already + # been deleted. + push @args, $_ for grep { !$seen{$_}++ } extract_key($path, 'Also'); + + push @enable_units, $name if contains_install_section($path); + } + + @enable_units = map { quote($_) } sort uniq @enable_units; + + if (@enable_units) { + # The generated maintainer script code blocks use the --user + # option that was added to deb-systemd-helper in version 1.52. + addsubstvar($package, 'misc:Depends', 'init-system-helpers', ">= 1.52"); + + my $postinst = $dh{NO_ENABLE} ? 'postinst-systemd-user-dont-enable' : 'postinst-systemd-user-enable'; + foreach my $unit (@enable_units) { + autoscript($package, 'postinst', $postinst, { 'UNITFILE' => $unit }); + } + autoscript($package, 'postrm', 'postrm-systemd-user', { 'UNITFILES' => join(' ', @enable_units) }); + } +} + +=head1 SEE ALSO + +L<debhelper(7)>, L<dh_installsystemd(1)>, L<deb-systemd-helper(1)> + +=head1 AUTHORS + +pkg-systemd-maintainers@lists.alioth.debian.org + +=cut diff --git a/t/dh_installsystemduser/debian/baz.user.service b/t/dh_installsystemduser/debian/baz.user.service new file mode 100644 index 00000000..3af041de --- /dev/null +++ b/t/dh_installsystemduser/debian/baz.user.service @@ -0,0 +1,8 @@ +[Unit] +Description=Baz User Unit + +[Service] +ExecStart=/bin/true + +[Install] +WantedBy=default.target diff --git a/t/dh_installsystemduser/debian/changelog b/t/dh_installsystemduser/debian/changelog new file mode 100644 index 00000000..5850f0e2 --- /dev/null +++ b/t/dh_installsystemduser/debian/changelog @@ -0,0 +1,5 @@ +foo (1.0-1) unstable; urgency=low + + * Initial release. (Closes: #XXXXXX) + + -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200 diff --git a/t/dh_installsystemduser/debian/control b/t/dh_installsystemduser/debian/control new file mode 100644 index 00000000..adccbc63 --- /dev/null +++ b/t/dh_installsystemduser/debian/control @@ -0,0 +1,10 @@ +Source: foo +Section: misc +Priority: optional +Maintainer: Test <testing@exampe.org> +Standards-Version: 3.9.8 + +Package: foo +Architecture: all +Description: package foo + Package foo diff --git a/t/dh_installsystemduser/debian/foo.user.service b/t/dh_installsystemduser/debian/foo.user.service new file mode 100644 index 00000000..7ef597d4 --- /dev/null +++ b/t/dh_installsystemduser/debian/foo.user.service @@ -0,0 +1,8 @@ +[Unit] +Description=Foo User Unit + +[Service] +ExecStart=/bin/true + +[Install] +WantedBy=default.target diff --git a/t/dh_installsystemduser/dh_installsystemduser.t b/t/dh_installsystemduser/dh_installsystemduser.t new file mode 100644 index 00000000..894eeb7a --- /dev/null +++ b/t/dh_installsystemduser/dh_installsystemduser.t @@ -0,0 +1,75 @@ +#!/usr/bin/perl +use strict; +use Test::More; +use File::Path qw(make_path); +use File::Basename qw(dirname); +use lib dirname(dirname(__FILE__)); +use Test::DH; +use Debian::Debhelper::Dh_Lib qw(!dirname); + +plan(tests => 1); + +our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw( + debian/changelog + debian/control + debian/foo.user.service + debian/baz.user.service +)); + + +sub read_script { + my ($package, $name) = @_; + my @lines; + + foreach my $script (find_script($package, $name)) { + open(my $fh, '<', $script) or die("open($script): $!"); + push @lines, $_ for <$fh>; + close($fh); + } + + return @lines; +} + +sub _unit_check_user_enabled { + my ($package, $unit, $enabled) = @_; + my $verb = $enabled ? "is" : "isnt"; + my $matches; + + my @postinst = read_script($package, 'postinst'); + # Match exactly one tab character. The "dont-enable" script has + # an "enable" line for re-enabling the service if the admin had it + # enabled, but we do not want to include that in our count. + $matches = grep { m{^\tif deb-systemd-helper( --\w+)* --user was-enabled.*'\Q$unit'} } @postinst; + is($matches, $enabled, "$unit $verb enabled"); + + my @postrm = read_script($package, 'postrm'); + $matches = grep { m{deb-systemd-helper( --\w+)* --user mask.*'\Q$unit'} } @postrm; + is($matches, $enabled, "$unit $verb masked"); +} + +sub is_enabled { _unit_check_user_enabled(@_, 1); } +sub isnt_enabled { _unit_check_user_enabled(@_, 0); } + +each_compat_subtest { + make_path('debian/foo/usr/lib/systemd/user/'); + install_file('debian/foo.user.service', 'debian/foo/usr/lib/systemd/user/bar.service'); + ok(run_dh_tool('dh_installsystemduser')); + ok(-e 'debian/foo/usr/lib/systemd/user/foo.service'); + is_enabled('foo', 'foo.service'); + is_enabled('foo', 'bar.service'); + ok(run_dh_tool('dh_clean')); + + ok(run_dh_tool('dh_installsystemduser')); + ok(-e 'debian/foo/usr/lib/systemd/user/foo.service'); + ok(! -e 'debian/foo/usr/lib/systemd/user/baz.service'); + is_enabled('foo', 'foo.service'); + isnt_enabled('foo', 'baz.service'); + ok(run_dh_tool('dh_clean')); + + ok(run_dh_tool('dh_installsystemduser', '--name', 'baz')); + ok(! -e 'debian/foo/usr/lib/systemd/user/foo.service'); + ok(-e 'debian/foo/usr/lib/systemd/user/baz.service'); + isnt_enabled('foo', 'foo.service'); + is_enabled('foo', 'baz.service'); + ok(run_dh_tool('dh_clean')); +}; |