summaryrefslogtreecommitdiffstats
path: root/dh_installsystemd
diff options
context:
space:
mode:
Diffstat (limited to 'dh_installsystemd')
-rwxr-xr-xdh_installsystemd453
1 files changed, 453 insertions, 0 deletions
diff --git a/dh_installsystemd b/dh_installsystemd
new file mode 100755
index 0000000..aba9d78
--- /dev/null
+++ b/dh_installsystemd
@@ -0,0 +1,453 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+dh_installsystemd - install systemd unit files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use File::Find;
+use Cwd qw(getcwd abs_path);
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installsystemd> [S<I<debhelper options>>] [B<--restart-after-upgrade>] [B<--no-stop-on-upgrade>] [B<--no-enable>] [B<--no-start>] [B<--name=>I<name>] [S<I<unit file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installsystemd> is a debhelper program that is responsible for
+installing package maintainer supplied systemd unit files.
+
+It also finds the service files installed by a package and generates
+F<preinst>, F<postinst>, and F<prerm> code blocks for enabling,
+disabling, starting, stopping, and restarting the corresponding
+systemd 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 systemd units,
+thus it is not necessary that the machine actually runs systemd during
+package installation time, enabling happens on all machines in order
+to be able to switch from sysvinit to systemd and back.
+
+B<dh_installsystemd> operates on all unit files installed by a
+package. For only generating blocks for specific unit files, pass them
+as arguments, C<dh_installsystemd quota.service>. 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>.mount,
+ debian/I<package>.path,
+ debian/I<package>@.path,
+ debian/I<package>.service,
+ debian/I<package>@.service,
+ debian/I<package>.socket,
+ debian/I<package>@.socket,
+ debian/I<package>.target,
+ debian/I<package>@.target,
+ debian/I<package>.timer,
+ debian/I<package>@.timer
+
+If any of those files exists, they are installed into
+F<lib/systemd/system/> in the package build directory.
+
+=item debian/I<package>.tmpfile
+
+Only used in compat 12 or earlier. In compat 13+, this file is
+handled by L<dh_installtmpfiles(1)> instead.
+
+If this exists, it is installed into F<usr/lib/tmpfiles.d/> in the
+package build directory. Note that the C<tmpfiles.d> mechanism is
+currently only used by systemd.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--no-enable>
+
+Disable the service(s) on purge, but do not enable them on install.
+
+B<Note> that this option does not affect whether the services are
+started. Please remember to also use B<--no-start> if the service
+should not be started.
+
+=item B<--name=>I<name>
+
+This option controls several things.
+
+It changes the name that B<dh_installsystemd> uses when it looks for
+maintainer provided systemd unit files as listed in the L</FILES>
+section. As an example, B<dh_installsystemd --name foo> will look for
+F<<< I<debian/package.>B<< I<foo> >>I<.service> >>> instead of
+F<< I<debian/package.service> >>). These unit files are installed as F<<
+I<name.unit-extension> >> (in the example, it would be installed as
+F<<< B<< I<foo> >>I<.service> >>>).
+
+Furthermore, if no unit files are passed explicitly as command line
+arguments, B<dh_installsystemd> will only act on unit files called
+I<name> (rather than all unit files found in the package).
+
+=item B<--restart-after-upgrade>
+
+Do not stop the unit file until after the package upgrade has been completed.
+This is the default behaviour in compat 10.
+
+In earlier compat levels the default was to stop the unit file in the
+F<prerm>, and start it again in the F<postinst>.
+
+This can be useful for daemons that should not have a possibly long
+downtime during upgrade. But you should make sure that the daemon will not
+get confused by the package being upgraded while it's running before using
+this option.
+
+=item B<--no-restart-after-upgrade>
+
+Undo a previous B<--restart-after-upgrade> (or the default of compat
+10). If no other options are given, this will cause the service to be
+stopped in the F<prerm> script and started again in the F<postinst>
+script.
+
+=item B<-r>, B<--no-stop-on-upgrade>, B<--no-restart-on-upgrade>
+
+Do not stop service on upgrade. This has the side-effect of not
+restarting the service as a part of the upgrade.
+
+If you want to restart the service with minimal downtime, please use
+B<--restart-after-upgrade> (default in compat 10 or later). If you want
+the service to be restarted but be stopped during the upgrade, then please
+use B<--no-restart-after-upgrade> (note the "after-upgrade").
+
+Note that the B<--no-restart-on-upgrade> alias is deprecated and will
+be removed in compat 14. This is to avoid confusion with the
+B<--no-restart-after-upgrade> option.
+
+=item B<--no-start>
+
+Do not start the unit file after upgrades and after initial installation (the
+latter is only relevant for services without a corresponding init script).
+
+B<Note> that this option does not affect whether the services are
+enabled. Please remember to also use B<--no-enable> if the services
+should not be enabled.
+
+=item S<B<unit file> ...>
+
+Only process and generate maintscripts for the installed unit files
+with the (base)name I<unit file>.
+
+Note: B<dh_installsystemd> will still install unit files from
+F<debian/> but it will not generate any maintscripts for them unless
+they are explicitly listed in S<B<unit file> ...>
+
+=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
+
+$dh{RESTART_AFTER_UPGRADE} = '';
+$dh{NO_START} = '';
+
+init(options => {
+ "no-enable" => \$dh{NO_ENABLE},
+ "r" => \$dh{R_FLAG},
+ 'no-stop-on-upgrade' => \$dh{R_FLAG},
+ "no-restart-on-upgrade" => \$dh{R_FLAG},
+ "no-start" => \$dh{NO_START},
+ "R|restart-after-upgrade!" => \$dh{RESTART_AFTER_UPGRADE},
+ "no-also" => \$dh{NO_ALSO}, # undocumented option
+});
+
+if ($dh{RESTART_AFTER_UPGRADE} eq '') {
+ $dh{RESTART_AFTER_UPGRADE} = 1 if not defined($dh{R_FLAG}) and $dh{NO_START} eq '';
+}
+
+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): $!");
+
+ while (my $line = <$fh>) {
+ chomp($line);
+ return 1 if $line =~ /^\s*\[Install\]$/i;
+ }
+ close($fh);
+ return 0;
+}
+
+sub has_sysv_equivalent {
+ my ($tmpdir, $unit) = @_;
+
+ $unit =~ s/\.(?:mount|service|socket|target|path)$//g;
+ return -f "$tmpdir/etc/init.d/$unit";
+}
+
+sub install_unit {
+ my ($package, $script, $pkgsuffix, $path, $installsuffix) = @_;
+ $installsuffix = $installsuffix || $pkgsuffix;
+ my $unit = pkgfile($package, $pkgsuffix);
+ return if $unit eq '';
+ install_dir($path);
+ install_file($unit, "${path}/${script}.${installsuffix}");
+}
+
+# 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_units {
+ my ($tmpdir, $aliases) = @_;
+
+ my $lib_systemd_system = "$tmpdir/lib/systemd/system";
+ my @installed;
+
+ return unless -d $lib_systemd_system;
+ opendir(my $dh, "$lib_systemd_system") or error("Cannot opendir($lib_systemd_system): $!");
+
+ foreach my $name (readdir($dh)) {
+ my $path = "$lib_systemd_system/$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;
+}
+
+
+# PROMISE: DH NOOP WITHOUT internal(bug#950723) tmp(lib/systemd/system) tmp(usr/lib/tmpfiles.d) tmp(etc/tmpfiles.d) mount path service socket target tmpfile timer cli-options()
+
+
+# Install package maintainer supplied unit files
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+
+ # Intall all unit files in the debian/ directory with names in the
+ # form $package.(service|target|socket|path|timer|mount|tmpfile)
+ # and their templated version when relevant.
+
+ # This can be modified with the --name option to look for unit
+ # files with names in the form $package.$name.(service|...) and
+ # $name.(service|target|socket|path|timer|mount|tmpfile) and their
+ # templated version when relevant.
+ my $name = $dh{NAME} // $package;
+
+ for my $type (qw(service target socket path timer)) {
+ install_unit($package, $name, $type, "$tmpdir/lib/systemd/system");
+ install_unit("${package}@", "${name}@", $type, "$tmpdir/lib/systemd/system");
+ }
+
+ install_unit($package, $name, 'mount', "$tmpdir/lib/systemd/system");
+ # In compat 13+, this is handled by dh_installtmpfiles
+ install_unit($package, $name, 'tmpfile', "$tmpdir/usr/lib/tmpfiles.d", 'conf') if compat(12);
+}
+
+
+if (compat(12)) {
+ # In compat 13+, this is handled by dh_installtmpfiles
+ # Add postinst code blocks to handle tmpfiles
+ foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my @tmpfiles;
+
+ my @dirs = grep { -d } map { "${tmpdir}/$_" } qw(usr/lib/tmpfiles.d etc/tmpfiles.d);
+
+ find({
+ wanted => sub {
+ my $name = $File::Find::name;
+ return if not -f $name or not $name =~ m{[.]conf$};
+ push(@tmpfiles, basename($name)); },
+ no_chdir => 1,
+ }, @dirs) if @dirs;
+
+ if (@tmpfiles) {
+ autoscript($package, 'postinst', 'postinst-init-tmpfiles', { 'TMPFILES' => join(' ', sort @tmpfiles) });
+ }
+ }
+}
+
+
+# Add postinst, prerm, and postrm code blocks to handle activation,
+# deactivation, start and stopping of services when the package is
+# installed, upgraded or removed.
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my (@args, @start_units, @enable_units, %aliases);
+
+ my @installed_units = list_installed_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 option as if the corresponding unit names were
+ # passed in the command line.
+ @args = grep /(^|\/)$dh{NAME}\.(mount|path|service|socket|target|timer)$/, @installed_units;
+ }
+ else {
+ @args = @installed_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 $unit = shift @args;
+ my $path = "${tmpdir}/lib/systemd/system/${unit}";
+
+ error("Package '$package' does not install unit '$unit'.") unless (-f $path);
+
+ # Skip template service files. Enabling, disabling, starting
+ # or stopping those services without specifying the instance
+ # is not useful.
+ next if ($unit =~ /\@/);
+
+ # 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. The undocumented --no-also option disables
+ # handling of units linked via Also=. This option is provided
+ # only to suport a very specific use case in network-manager.
+ unless ($dh{NO_ALSO}) {
+ push @args, $_ for grep { !$seen{$_}++ } extract_key($path, 'Also');
+ }
+
+ # Extract unit aliases.
+ push @{$aliases{$unit}}, $_ for extract_key($path, 'Alias');
+
+ # In compat 11 (and earlier), dh_installinit will handle services with
+ # a sysv-equivalent service. In compat 12, dh_installsystemd will
+ # take care of it.
+ if (not compat(11) or not grep { has_sysv_equivalent($tmpdir, $_) } ($unit, @{$aliases{$unit}})) {
+ push @start_units, $unit;
+ }
+
+ if (contains_install_section($path)) {
+ push @enable_units, $unit;
+ }
+ }
+
+ @enable_units = map { quote($_) } uniq sort @enable_units;
+ @start_units = map { quote($_) } uniq sort @start_units;
+
+ my %options = ('snippet-order' => 'service');
+
+ if (@enable_units) {
+ for my $unit (@enable_units) {
+ my $snippet = $dh{NO_ENABLE} ? 'postinst-systemd-dont-enable' : 'postinst-systemd-enable';
+ autoscript($package, 'postinst', $snippet, { 'UNITFILE' => $unit }, \%options);
+ }
+ autoscript($package, 'postrm', 'postrm-systemd', {'UNITFILES' => join(' ', @enable_units) });
+ }
+
+ if (@start_units) {
+ my $replace = { 'UNITFILES' => join(' ', @start_units) };
+
+ if ($dh{RESTART_AFTER_UPGRADE}) {
+ my $snippet;
+ if ($dh{NO_START}) {
+ $snippet = 'postinst-systemd-restartnostart';
+ $replace->{RESTART_ACTION} = 'try-restart';
+ } else {
+ $snippet = 'postinst-systemd-restart';
+ $replace->{RESTART_ACTION} = 'restart';
+ }
+ autoscript($package, 'postinst', $snippet, $replace, \%options);
+ } elsif (!$dh{NO_START}) {
+ # (stop|start) service (before|after) upgrade
+ autoscript($package, 'postinst', 'postinst-systemd-start', $replace, \%options);
+ }
+
+ # stop service before upgrade, if requested
+ autoscript($package, 'preinst', 'preinst-systemd-stop', $replace, \%options)
+ unless ($dh{R_FLAG} || $dh{RESTART_AFTER_UPGRADE});
+
+ # stop service only on remove
+ autoscript($package, 'prerm', 'prerm-systemd-restart', $replace, \%options)
+ unless ($dh{NO_START});
+
+ # Run this with "default" order so it is always after other
+ # service related autosnippets.
+ autoscript($package, 'postrm', 'postrm-systemd-reload-only', $replace);
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>, L<dh_installinit(1)>, L<deb-systemd-helper(1)>
+
+=head1 AUTHORS
+
+pkg-systemd-maintainers@lists.alioth.debian.org
+
+=cut