1{ config, lib, pkgs, ... }:
2with lib;
3let
4 cfg = config.services.nomad;
5 format = pkgs.formats.json { };
6in
7{
8 ##### interface
9 options = {
10 services.nomad = {
11 enable = mkEnableOption "Nomad, a distributed, highly available, datacenter-aware scheduler";
12
13 package = mkOption {
14 type = types.package;
15 default = pkgs.nomad;
16 defaultText = "pkgs.nomad";
17 description = ''
18 The package used for the Nomad agent and CLI.
19 '';
20 };
21
22 extraPackages = mkOption {
23 type = types.listOf types.package;
24 default = [ ];
25 description = ''
26 Extra packages to add to <envar>PATH</envar> for the Nomad agent process.
27 '';
28 example = literalExample ''
29 with pkgs; [ cni-plugins ]
30 '';
31 };
32
33 dropPrivileges = mkOption {
34 type = types.bool;
35 default = true;
36 description = ''
37 Whether the nomad agent should be run as a non-root nomad user.
38 '';
39 };
40
41 enableDocker = mkOption {
42 type = types.bool;
43 default = true;
44 description = ''
45 Enable Docker support. Needed for Nomad's docker driver.
46
47 Note that the docker group membership is effectively equivalent
48 to being root, see https://github.com/moby/moby/issues/9976.
49 '';
50 };
51
52 extraSettingsPaths = mkOption {
53 type = types.listOf types.path;
54 default = [];
55 description = ''
56 Additional settings paths used to configure nomad. These can be files or directories.
57 '';
58 example = literalExample ''
59 [ "/etc/nomad-mutable.json" "/run/keys/nomad-with-secrets.json" "/etc/nomad/config.d" ]
60 '';
61 };
62
63 settings = mkOption {
64 type = format.type;
65 default = {};
66 description = ''
67 Configuration for Nomad. See the <link xlink:href="https://www.nomadproject.io/docs/configuration">documentation</link>
68 for supported values.
69
70 Notes about <literal>data_dir</literal>:
71
72 If <literal>data_dir</literal> is set to a value other than the
73 default value of <literal>"/var/lib/nomad"</literal> it is the Nomad
74 cluster manager's responsibility to make sure that this directory
75 exists and has the appropriate permissions.
76
77 Additionally, if <literal>dropPrivileges</literal> is
78 <literal>true</literal> then <literal>data_dir</literal>
79 <emphasis>cannot</emphasis> be customized. Setting
80 <literal>dropPrivileges</literal> to <literal>true</literal> enables
81 the <literal>DynamicUser</literal> feature of systemd which directly
82 manages and operates on <literal>StateDirectory</literal>.
83 '';
84 example = literalExample ''
85 {
86 # A minimal config example:
87 server = {
88 enabled = true;
89 bootstrap_expect = 1; # for demo; no fault tolerance
90 };
91 client = {
92 enabled = true;
93 };
94 }
95 '';
96 };
97 };
98 };
99
100 ##### implementation
101 config = mkIf cfg.enable {
102 services.nomad.settings = {
103 # Agrees with `StateDirectory = "nomad"` set below.
104 data_dir = mkDefault "/var/lib/nomad";
105 };
106
107 environment = {
108 etc."nomad.json".source = format.generate "nomad.json" cfg.settings;
109 systemPackages = [ cfg.package ];
110 };
111
112 systemd.services.nomad = {
113 description = "Nomad";
114 wantedBy = [ "multi-user.target" ];
115 wants = [ "network-online.target" ];
116 after = [ "network-online.target" ];
117 restartTriggers = [ config.environment.etc."nomad.json".source ];
118
119 path = cfg.extraPackages ++ (with pkgs; [
120 # Client mode requires at least the following:
121 coreutils
122 iproute2
123 iptables
124 ]);
125
126 serviceConfig = mkMerge [
127 {
128 DynamicUser = cfg.dropPrivileges;
129 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
130 ExecStart = "${cfg.package}/bin/nomad agent -config=/etc/nomad.json" +
131 concatMapStrings (path: " -config=${path}") cfg.extraSettingsPaths;
132 KillMode = "process";
133 KillSignal = "SIGINT";
134 LimitNOFILE = 65536;
135 LimitNPROC = "infinity";
136 OOMScoreAdjust = -1000;
137 Restart = "on-failure";
138 RestartSec = 2;
139 TasksMax = "infinity";
140 }
141 (mkIf cfg.enableDocker {
142 SupplementaryGroups = "docker"; # space-separated string
143 })
144 (mkIf (cfg.settings.data_dir == "/var/lib/nomad") {
145 StateDirectory = "nomad";
146 })
147 ];
148
149 unitConfig = {
150 StartLimitIntervalSec = 10;
151 StartLimitBurst = 3;
152 };
153 };
154
155 assertions = [
156 {
157 assertion = cfg.dropPrivileges -> cfg.settings.data_dir == "/var/lib/nomad";
158 message = "settings.data_dir must be equal to \"/var/lib/nomad\" if dropPrivileges is true";
159 }
160 ];
161
162 # Docker support requires the Docker daemon to be running.
163 virtualisation.docker.enable = mkIf cfg.enableDocker true;
164 };
165}