1/*
2
3 This file is for options that NixOS and nix-darwin have in common.
4
5 Platform-specific code is in the respective default.nix files.
6
7*/
8
9{ config, lib, options, pkgs, ... }:
10let
11 inherit (lib)
12 filterAttrs
13 literalDocBook
14 literalExpression
15 mkIf
16 mkOption
17 mkRemovedOptionModule
18 mkRenamedOptionModule
19 types
20 ;
21
22 cfg =
23 config.services.hercules-ci-agent;
24
25 format = pkgs.formats.toml { };
26
27 settingsModule = { config, ... }: {
28 freeformType = format.type;
29 options = {
30 apiBaseUrl = mkOption {
31 description = ''
32 API base URL that the agent will connect to.
33
34 When using Hercules CI Enterprise, set this to the URL where your
35 Hercules CI server is reachable.
36 '';
37 type = types.str;
38 default = "https://hercules-ci.com";
39 };
40 baseDirectory = mkOption {
41 type = types.path;
42 default = "/var/lib/hercules-ci-agent";
43 description = ''
44 State directory (secrets, work directory, etc) for agent
45 '';
46 };
47 concurrentTasks = mkOption {
48 description = ''
49 Number of tasks to perform simultaneously.
50
51 A task is a single derivation build, an evaluation or an effect run.
52 At minimum, you need 2 concurrent tasks for <literal>x86_64-linux</literal>
53 in your cluster, to allow for import from derivation.
54
55 <literal>concurrentTasks</literal> can be around the CPU core count or lower if memory is
56 the bottleneck.
57
58 The optimal value depends on the resource consumption characteristics of your workload,
59 including memory usage and in-task parallelism. This is typically determined empirically.
60
61 When scaling, it is generally better to have a double-size machine than two machines,
62 because each split of resources causes inefficiencies; particularly with regards
63 to build latency because of extra downloads.
64 '';
65 type = types.either types.ints.positive (types.enum [ "auto" ]);
66 default = "auto";
67 };
68 labels = mkOption {
69 description = ''
70 A key-value map of user data.
71
72 This data will be available to organization members in the dashboard and API.
73
74 The values can be of any TOML type that corresponds to a JSON type, but arrays
75 can not contain tables/objects due to limitations of the TOML library. Values
76 involving arrays of non-primitive types may not be representable currently.
77 '';
78 type = format.type;
79 defaultText = literalExpression ''
80 {
81 agent.source = "..."; # One of "nixpkgs", "flake", "override"
82 lib.version = "...";
83 pkgs.version = "...";
84 }
85 '';
86 };
87 workDirectory = mkOption {
88 description = ''
89 The directory in which temporary subdirectories are created for task state. This includes sources for Nix evaluation.
90 '';
91 type = types.path;
92 default = config.baseDirectory + "/work";
93 defaultText = literalExpression ''baseDirectory + "/work"'';
94 };
95 staticSecretsDirectory = mkOption {
96 description = ''
97 This is the default directory to look for statically configured secrets like <literal>cluster-join-token.key</literal>.
98
99 See also <literal>clusterJoinTokenPath</literal> and <literal>binaryCachesPath</literal> for fine-grained configuration.
100 '';
101 type = types.path;
102 default = config.baseDirectory + "/secrets";
103 defaultText = literalExpression ''baseDirectory + "/secrets"'';
104 };
105 clusterJoinTokenPath = mkOption {
106 description = ''
107 Location of the cluster-join-token.key file.
108
109 You can retrieve the contents of the file when creating a new agent via
110 <link xlink:href="https://hercules-ci.com/dashboard">https://hercules-ci.com/dashboard</link>.
111
112 As this value is confidential, it should not be in the store, but
113 installed using other means, such as agenix, NixOps
114 <literal>deployment.keys</literal>, or manual installation.
115
116 The contents of the file are used for authentication between the agent and the API.
117 '';
118 type = types.path;
119 default = config.staticSecretsDirectory + "/cluster-join-token.key";
120 defaultText = literalExpression ''staticSecretsDirectory + "/cluster-join-token.key"'';
121 };
122 binaryCachesPath = mkOption {
123 description = ''
124 Path to a JSON file containing binary cache secret keys.
125
126 As these values are confidential, they should not be in the store, but
127 copied over using other means, such as agenix, NixOps
128 <literal>deployment.keys</literal>, or manual installation.
129
130 The format is described on <link xlink:href="https://docs.hercules-ci.com/hercules-ci-agent/binary-caches-json/">https://docs.hercules-ci.com/hercules-ci-agent/binary-caches-json/</link>.
131 '';
132 type = types.path;
133 default = config.staticSecretsDirectory + "/binary-caches.json";
134 defaultText = literalExpression ''staticSecretsDirectory + "/binary-caches.json"'';
135 };
136 secretsJsonPath = mkOption {
137 description = ''
138 Path to a JSON file containing secrets for effects.
139
140 As these values are confidential, they should not be in the store, but
141 copied over using other means, such as agenix, NixOps
142 <literal>deployment.keys</literal>, or manual installation.
143
144 The format is described on <link xlink:href="https://docs.hercules-ci.com/hercules-ci-agent/secrets-json/">https://docs.hercules-ci.com/hercules-ci-agent/secrets-json/</link>.
145
146 '';
147 type = types.path;
148 default = config.staticSecretsDirectory + "/secrets.json";
149 defaultText = literalExpression ''staticSecretsDirectory + "/secrets.json"'';
150 };
151 };
152 };
153
154 # TODO (roberth, >=2022) remove
155 checkNix =
156 if !cfg.checkNix
157 then ""
158 else if lib.versionAtLeast config.nix.package.version "2.3.10"
159 then ""
160 else
161 pkgs.stdenv.mkDerivation {
162 name = "hercules-ci-check-system-nix-src";
163 inherit (config.nix.package) src patches;
164 dontConfigure = true;
165 buildPhase = ''
166 echo "Checking in-memory pathInfoCache expiry"
167 if ! grep 'PathInfoCacheValue' src/libstore/store-api.hh >/dev/null; then
168 cat 1>&2 <<EOF
169
170 You are deploying Hercules CI Agent on a system with an incompatible
171 nix-daemon. Please make sure nix.package is set to a Nix version of at
172 least 2.3.10 or a master version more recent than Mar 12, 2020.
173 EOF
174 exit 1
175 fi
176 '';
177 installPhase = "touch $out";
178 };
179
180in
181{
182 imports = [
183 (mkRenamedOptionModule [ "services" "hercules-ci-agent" "extraOptions" ] [ "services" "hercules-ci-agent" "settings" ])
184 (mkRenamedOptionModule [ "services" "hercules-ci-agent" "baseDirectory" ] [ "services" "hercules-ci-agent" "settings" "baseDirectory" ])
185 (mkRenamedOptionModule [ "services" "hercules-ci-agent" "concurrentTasks" ] [ "services" "hercules-ci-agent" "settings" "concurrentTasks" ])
186 (mkRemovedOptionModule [ "services" "hercules-ci-agent" "patchNix" ] "Nix versions packaged in this version of Nixpkgs don't need a patched nix-daemon to work correctly in Hercules CI Agent clusters.")
187 ];
188
189 options.services.hercules-ci-agent = {
190 enable = mkOption {
191 type = types.bool;
192 default = false;
193 description = ''
194 Enable to run Hercules CI Agent as a system service.
195
196 <link xlink:href="https://hercules-ci.com">Hercules CI</link> is a
197 continuous integation service that is centered around Nix.
198
199 Support is available at <link xlink:href="mailto:help@hercules-ci.com">help@hercules-ci.com</link>.
200 '';
201 };
202 checkNix = mkOption {
203 type = types.bool;
204 default = true;
205 description = ''
206 Whether to make sure that the system's Nix (nix-daemon) is compatible.
207
208 If you set this to false, please keep up with the change log.
209 '';
210 };
211 package = mkOption {
212 description = ''
213 Package containing the bin/hercules-ci-agent executable.
214 '';
215 type = types.package;
216 default = pkgs.hercules-ci-agent;
217 defaultText = literalExpression "pkgs.hercules-ci-agent";
218 };
219 settings = mkOption {
220 description = ''
221 These settings are written to the <literal>agent.toml</literal> file.
222
223 Not all settings are listed as options, can be set nonetheless.
224
225 For the exhaustive list of settings, see <link xlink:href="https://docs.hercules-ci.com/hercules-ci/reference/agent-config/"/>.
226 '';
227 type = types.submoduleWith { modules = [ settingsModule ]; };
228 };
229
230 /*
231 Internal and/or computed values.
232
233 These are written as options instead of let binding to allow sharing with
234 default.nix on both NixOS and nix-darwin.
235 */
236 tomlFile = mkOption {
237 type = types.path;
238 internal = true;
239 defaultText = literalDocBook "generated <literal>hercules-ci-agent.toml</literal>";
240 description = ''
241 The fully assembled config file.
242 '';
243 };
244 };
245
246 config = mkIf cfg.enable {
247 nix.extraOptions = lib.addContextFrom checkNix ''
248 # A store path that was missing at first may well have finished building,
249 # even shortly after the previous lookup. This *also* applies to the daemon.
250 narinfo-cache-negative-ttl = 0
251 '';
252 services.hercules-ci-agent = {
253 tomlFile =
254 format.generate "hercules-ci-agent.toml" cfg.settings;
255
256 settings.labels = {
257 agent.source =
258 if options.services.hercules-ci-agent.package.highestPrio == (lib.modules.mkOptionDefault { }).priority
259 then "nixpkgs"
260 else lib.mkOptionDefault "override";
261 pkgs.version = pkgs.lib.version;
262 lib.version = lib.version;
263 };
264 };
265 };
266}