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