summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniele Nicolodi <daniele@grinta.net>2018-06-12 19:47:20 -0600
committerDaniele Nicolodi <daniele@grinta.net>2018-10-14 01:35:49 -0600
commit9076fc79b27a7b7aa5f8a1e3b1e0f54a781aefba (patch)
treeb4b9481fdd95cdc2129267a350874e450ee6ec0a
parenta69dfe2854a4ba25008fb4d17db561ad33034b8d (diff)
downloaddebhelper-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-enable15
-rw-r--r--autoscripts/postinst-systemd-user-enable15
-rw-r--r--autoscripts/postrm-systemd-user12
-rw-r--r--debhelper.pod5
-rw-r--r--debian/changelog11
-rwxr-xr-xdh19
-rwxr-xr-xdh_installsystemduser270
-rw-r--r--t/dh_installsystemduser/debian/baz.user.service8
-rw-r--r--t/dh_installsystemduser/debian/changelog5
-rw-r--r--t/dh_installsystemduser/debian/control10
-rw-r--r--t/dh_installsystemduser/debian/foo.user.service8
-rw-r--r--t/dh_installsystemduser/dh_installsystemduser.t75
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 ]
diff --git a/dh b/dh
index 0fea4422..10762133 100755
--- a/dh
+++ b/dh
@@ -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'));
+};