summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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'));
+};