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