summary refs log tree commit diff
path: root/nixos/modules/testing/service-runner.nix
blob: 99a9f979068daad2651ddc1539b02945440be8a6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
{ lib, pkgs, ... }:

with lib;

let

  makeScript = name: service: pkgs.writeScript "${name}-runner"
    ''
      #! ${pkgs.perl}/bin/perl -w -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix}

      use File::Slurp;

      sub run {
          my ($cmd) = @_;
          my @args = ();
          while ($cmd =~ /([^ \t\n']+)|(\'([^'])\')\s*/g) {
            push @args, $1;
          }
          my $prog;
          if (substr($args[0], 0, 1) eq "@") {
              $prog = substr($args[0], 1);
              shift @args;
          } else {
              $prog = $args[0];
          }
          my $pid = fork;
          if ($pid == 0) {
              setpgrp; # don't receive SIGINT etc. from terminal
              exec { $prog } @args;
              die "failed to exec $prog\n";
          } elsif (!defined $pid) {
              die "failed to fork: $!\n";
          }
          return $pid;
      };

      sub run_wait {
          my ($cmd) = @_;
          my $pid = run $cmd;
          die if waitpid($pid, 0) != $pid;
          return $?;
      };

      # Set the environment.  FIXME: escaping.
      foreach my $key (keys %ENV) {
          next if $key eq 'LOCALE_ARCHIVE';
          delete $ENV{$key};
      }
      ${concatStrings (mapAttrsToList (n: v: ''
        $ENV{'${n}'} = '${v}';
      '') service.environment)}

      # Run the ExecStartPre program.  FIXME: this could be a list.
      my $preStart = <<END_CMD;
      ${service.serviceConfig.ExecStartPre or ""}
      END_CMD
      if (defined $preStart && $preStart ne "\n") {
          print STDERR "running ExecStartPre: $preStart\n";
          my $res = run_wait $preStart;
          die "$0: ExecStartPre failed with status $res\n" if $res;
      };

      # Run the ExecStart program.
      my $cmd = <<END_CMD;
      ${service.serviceConfig.ExecStart}
      END_CMD

      print STDERR "running ExecStart: $cmd\n";
      my $mainPid = run $cmd;
      $ENV{'MAINPID'} = $mainPid;

      # Catch SIGINT, propagate to the main program.
      sub intHandler {
          print STDERR "got SIGINT, stopping service...\n";
          kill 'INT', $mainPid;
      };
      $SIG{'INT'} = \&intHandler;
      $SIG{'QUIT'} = \&intHandler;

      # Run the ExecStartPost program.
      my $postStart = <<END_CMD;
      ${service.serviceConfig.ExecStartPost or ""}
      END_CMD
      if (defined $postStart && $postStart ne "\n") {
          print STDERR "running ExecStartPost: $postStart\n";
          my $res = run_wait $postStart;
          die "$0: ExecStartPost failed with status $res\n" if $res;
      }

      # Wait for the main program to exit.
      die if waitpid($mainPid, 0) != $mainPid;
      my $mainRes = $?;

      # Run the ExecStopPost program.
      my $postStop = <<END_CMD;
      ${service.serviceConfig.ExecStopPost or ""}
      END_CMD
      if (defined $postStop && $postStop ne "\n") {
          print STDERR "running ExecStopPost: $postStop\n";
          my $res = run_wait $postStop;
          die "$0: ExecStopPost failed with status $res\n" if $res;
      }

      exit($mainRes & 127 ? 255 : $mainRes << 8);
    '';

  opts = { config, name, ... }: {
    options.runner = mkOption {
    internal = true;
    description = ''
        A script that runs the service outside of systemd,
        useful for testing or for using NixOS services outside
        of NixOS.
    '';
    };
    config.runner = makeScript name config;
  };

in

{
  options = {
    systemd.services = mkOption {
      type = with types; attrsOf (submodule opts);
    };
  };
}