diff options
Diffstat (limited to '')
-rwxr-xr-x | dh_systemd_enable | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/dh_systemd_enable b/dh_systemd_enable new file mode 100755 index 0000000..e7c02ba --- /dev/null +++ b/dh_systemd_enable @@ -0,0 +1,292 @@ +#!/usr/bin/perl -w + +=head1 NAME + +dh_systemd_enable - enable/disable systemd unit files + +=cut + +use strict; +use warnings; +use Debian::Debhelper::Dh_Lib; +use File::Find; + +our $VERSION = DH_BUILTIN_VERSION; + +=head1 SYNOPSIS + +B<dh_systemd_enable> [S<I<debhelper options>>] [B<--no-enable>] [B<--name=>I<name>] [S<I<unit file> ...>] + +=head1 DESCRIPTION + +B<dh_systemd_enable> is a debhelper program that is responsible for enabling +and disabling systemd unit files. + +In the simple case, it finds all unit files installed by a package (e.g. +bacula-fd.service) and enables them. 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. + +In the complex case, you can call B<dh_systemd_enable> and B<dh_systemd_start> +manually (by overwriting the debian/rules targets) and specify flags per unit +file. An example is colord, which ships colord.service, a dbus-activated +service without an [Install] section. This service file cannot be enabled or +disabled (a state called "static" by systemd) because it has no +[Install] section. Therefore, running dh_systemd_enable does not make sense. + +For only generating blocks for specific service files, you need to pass them as +arguments, e.g. B<dh_systemd_enable quota.service> and B<dh_systemd_enable +--name=quotarpc quotarpc.service>. + +=head1 FILES + +=over 4 + +=item debian/I<package>.service, debian/I<package>@.service + +If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.service >> (or +F<< usr/lib/systemd/system/I<package>@.service >>) in the package build directory. + +=item debian/I<package>.tmpfile + +If this exists, it is installed into usr/lib/tmpfiles.d/I<package>.conf in the +package build directory. + +=item debian/I<package>.target, debian/I<package>@.target + +If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.target >> (or +F<< usr/lib/systemd/system/I<package>@.target >>) in the package build directory. + +=item debian/I<package>.socket, debian/I<package>@.socket + +If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.socket >> (or +F<< usr/lib/systemd/system/I<package>@.socket >>) in the package build directory. + +=item debian/I<package>.mount + +If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.mount >> +in the package build directory. + +=item debian/I<package>.path, debian/I<package>@.path + +If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.path >> (or +F<< usr/lib/systemd/system/I<package>@.path >>) in the package build directory. + +=item debian/I<package>.timer, debian/I<package>@.timer + +If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.timer >> (or +F<< usr/lib/systemd/system/I<package>@.timer >>) in the package build directory. + +=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. That is controlled by L<dh_systemd_start(1)> (using e.g. its +B<--no-start> option). + +=item B<--name=>I<name> + +Install the service file as I<name.service> instead of the default filename, +which is the I<package.service>. When this parameter is used, +B<dh_systemd_enable> looks for and installs files named +F<debian/package.name.service> instead of the usual F<debian/package.service>. + +=back + +=head1 NOTES + +Note that 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. + +Note that B<dh_systemd_enable> should be run before B<dh_installinit>. +The default sequence in B<dh> does the right thing, this note is only relevant +when you are calling B<dh_systemd_enable> manually. + +=cut + +if (not compat(10)) { + error("dh_systemd_enable is no longer used in compat >= 11, please use dh_installsystemd instead"); +} + +init(options => { + "no-enable" => \$dh{NO_ENABLE}, +}); + +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_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}"); +} + +# PROMISE: DH NOOP WITHOUT tmp(lib/systemd/system) tmp(usr/lib/systemd/system) mount path service socket target tmpfile timer + +my %requested_files = map { basename($_) => 1 } @ARGV; +my %installed_files; + +foreach my $package (@{$dh{DOPACKAGES}}) { + my $tmpdir = tmpdir($package); + my @installed_units; + my @units; + + # XXX: This is duplicated in dh_installinit, which is unfortunate. + # We do need the service files before running dh_installinit though, + # every other solution makes things much worse for all the maintainers. + + # Figure out what filename to install it as. + my $script; + my $jobfile=$package; + if (defined $dh{NAME}) { + $jobfile=$script=$dh{NAME}; + } + elsif ($dh{D_FLAG}) { + # -d on the command line sets D_FLAG. We will + # remove a trailing 'd' from the package name and + # use that as the name. + $script=$package; + if ($script=~m/(.*)d$/) { + $jobfile=$script=$1; + } + else { + warning("\"$package\" has no final d' in its name, but -d was specified."); + } + } + elsif ($dh{INIT_SCRIPT}) { + $script=$dh{INIT_SCRIPT}; + } + else { + $script=$package; + } + + for my $service_type (qw(service target socket path timer)) { + install_unit($package, $script, $service_type, "$tmpdir/usr/lib/systemd/system"); + install_unit("${package}@", "${script}@", $service_type, "$tmpdir/usr/lib/systemd/system"); + } + + install_unit($package, $script, 'mount', "$tmpdir/usr/lib/systemd/system"); + install_unit($package, $script, 'tmpfile', "$tmpdir/usr/lib/tmpfiles.d", 'conf'); + + foreach my $unitdir ("${tmpdir}/usr/lib/systemd/system", "${tmpdir}/lib/systemd/system") { + find({ + wanted => sub { + my $name = $File::Find::name; + return unless -f $name; + # Skip symbolic links, their only legitimate + # use is for adding an alias, e.g. linking + # smartmontools.service -> smartd.service. + return if -l $name; + return unless $name =~ m,^\Q$unitdir\E/[^/]+$,; + error("Unit $name is installed in both the /usr/lib/systemd/system and /lib/systemd/system directories of $package.") + if (grep { $_ eq $name } @installed_units); + push @installed_units, $name; + }, + no_chdir => 1, + }, $unitdir) if -d $unitdir; + } + + # Handle either only the unit files which were passed as arguments or + # all unit files that are installed in this package. + my @args = @ARGV > 0 ? @ARGV : @installed_units; + + # support excluding units via -X + foreach my $x (@{$dh{EXCLUDE}}) { + @args = grep !/(^|\/)$x$/, @args; + } + + for my $name (@args) { + my $base = basename($name); + # Try to make the path absolute, so that the user can call + # dh_installsystemd bacula-fd.service + if ($base eq $name) { + # NB: This works because each unit in @installed_units + # comes from exactly one directory. + my ($full) = grep { basename($_) eq $base } @installed_units; + if (defined($full)) { + $name = $full; + } elsif (not exists($requested_files{$base})) { + warning(qq|Could not find "$name" in the /lib/systemd/system directory of $package. | . + qq|This could be a typo, or using Also= with a service file from another package. | . + qq|Please check carefully that this message is harmless.|); + } else { + # Ignore an explicitly requested file that is missing; happens when we are acting on + # multiple packages and only a subset of them have the unit file. + next; + } + } + + $installed_files{$base} = 1 if exists($requested_files{$base}); + + # Skip template service files like e.g. getty@.service. + # Enabling, disabling, starting or stopping those services + # without specifying the instance (e.g. getty@ttyS0.service) is + # not useful. + if ($name =~ /\@/) { + next; + } + + # Skip unit files that don’t have an [Install] section. + next unless contains_install_section($name); + + push @units, $name; + } + + next if @units == 0; + + # Wrap the basenames in '' to preserve \x2d when the shell parses the + # name. (#764730) + my $unitargs = join(' ', sort map { q{'} . basename($_) . q{'} } @units); + for my $unit (sort @units) { + # Wrap the basenames in '' to preserve \x2d when the shell parses the + # name. (#764730) + my $base = q{'} . basename($unit) . q{'}; + if ($dh{NO_ENABLE}) { + autoscript($package, 'postinst', 'postinst-systemd-dont-enable', { 'UNITFILE' => $base }); + } else { + autoscript($package, 'postinst', 'postinst-systemd-enable', { 'UNITFILE' => $base }); + } + } + autoscript($package, 'postrm', 'postrm-systemd', {'UNITFILES' => $unitargs }); +} + +if (%requested_files) { + my $any_missing = 0; + for my $name (sort(keys(%requested_files))) { + if (not exists($installed_files{$name})) { + warning(qq{Requested unit "$name" but it was not found in any package acted on.}); + $any_missing = 1; + } + } + error("Could not handle all of the requested services") if $any_missing; +} + +=head1 SEE ALSO + +L<dh_systemd_start(1)>, L<debhelper(7)> + +=head1 AUTHORS + +pkg-systemd-maintainers@lists.alioth.debian.org + +=cut |