1{ lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 makeScript = name: service: pkgs.writeScript "${name}-runner"
8 ''
9 #! ${pkgs.perl}/bin/perl -w -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl
10
11 use File::Slurp;
12
13 sub run {
14 my ($cmd) = @_;
15 my @args = split " ", $cmd;
16 my $prog;
17 if (substr($args[0], 0, 1) eq "@") {
18 $prog = substr($args[0], 1);
19 shift @args;
20 } else {
21 $prog = $args[0];
22 }
23 my $pid = fork;
24 if ($pid == 0) {
25 setpgrp; # don't receive SIGINT etc. from terminal
26 exec { $prog } @args;
27 die "failed to exec $prog\n";
28 } elsif (!defined $pid) {
29 die "failed to fork: $!\n";
30 }
31 return $pid;
32 };
33
34 sub run_wait {
35 my ($cmd) = @_;
36 my $pid = run $cmd;
37 die if waitpid($pid, 0) != $pid;
38 return $?;
39 };
40
41 # Set the environment. FIXME: escaping.
42 foreach my $key (keys %ENV) {
43 next if $key eq 'LOCALE_ARCHIVE';
44 delete $ENV{$key};
45 }
46 ${concatStrings (mapAttrsToList (n: v: ''
47 $ENV{'${n}'} = '${v}';
48 '') service.environment)}
49
50 # Run the ExecStartPre program. FIXME: this could be a list.
51 my $preStart = '${service.serviceConfig.ExecStartPre or ""}';
52 if ($preStart ne "") {
53 print STDERR "running ExecStartPre: $preStart\n";
54 my $res = run_wait $preStart;
55 die "$0: ExecStartPre failed with status $res\n" if $res;
56 };
57
58 # Run the ExecStart program.
59 my $cmd = '${service.serviceConfig.ExecStart}';
60 print STDERR "running ExecStart: $cmd\n";
61 my $mainPid = run $cmd;
62 $ENV{'MAINPID'} = $mainPid;
63
64 # Catch SIGINT, propagate to the main program.
65 sub intHandler {
66 print STDERR "got SIGINT, stopping service...\n";
67 kill 'INT', $mainPid;
68 };
69 $SIG{'INT'} = \&intHandler;
70 $SIG{'QUIT'} = \&intHandler;
71
72 # Run the ExecStartPost program.
73 my $postStart = '${service.serviceConfig.ExecStartPost or ""}';
74 if ($postStart ne "") {
75 print STDERR "running ExecStartPost: $postStart\n";
76 my $res = run_wait $postStart;
77 die "$0: ExecStartPost failed with status $res\n" if $res;
78 }
79
80 # Wait for the main program to exit.
81 die if waitpid($mainPid, 0) != $mainPid;
82 my $mainRes = $?;
83
84 # Run the ExecStopPost program.
85 my $postStop = '${service.serviceConfig.ExecStopPost or ""}';
86 if ($postStop ne "") {
87 print STDERR "running ExecStopPost: $postStop\n";
88 my $res = run_wait $postStop;
89 die "$0: ExecStopPost failed with status $res\n" if $res;
90 }
91
92 exit($mainRes & 127 ? 255 : $mainRes << 8);
93 '';
94
95in
96
97{
98 options = {
99 systemd.services = mkOption {
100 options =
101 { config, name, ... }:
102 { options.runner = mkOption {
103 internal = true;
104 description = ''
105 A script that runs the service outside of systemd,
106 useful for testing or for using NixOS services outside
107 of NixOS.
108 '';
109 };
110 config.runner = makeScript name config;
111 };
112 };
113 };
114}