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 literalMD
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 = lib.mdDoc ''
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 = lib.mdDoc ''
44 State directory (secrets, work directory, etc) for agent
45 '';
46 };
47 concurrentTasks = mkOption {
48 description = lib.mdDoc ''
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 `x86_64-linux`
53 in your cluster, to allow for import from derivation.
54
55 `concurrentTasks` 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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
97 This is the default directory to look for statically configured secrets like `cluster-join-token.key`.
98
99 See also `clusterJoinTokenPath` and `binaryCachesPath` 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 = lib.mdDoc ''
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 <https://hercules-ci.com/dashboard>.
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 `deployment.keys`, 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 = lib.mdDoc ''
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 `deployment.keys`, or manual installation.
129
130 The format is described on <https://docs.hercules-ci.com/hercules-ci-agent/binary-caches-json/>.
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 = lib.mdDoc ''
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 `deployment.keys`, or manual installation.
143
144 The format is described on <https://docs.hercules-ci.com/hercules-ci-agent/secrets-json/>.
145 '';
146 type = types.path;
147 default = config.staticSecretsDirectory + "/secrets.json";
148 defaultText = literalExpression ''staticSecretsDirectory + "/secrets.json"'';
149 };
150 };
151 };
152
153 # TODO (roberth, >=2022) remove
154 checkNix =
155 if !cfg.checkNix
156 then ""
157 else if lib.versionAtLeast config.nix.package.version "2.3.10"
158 then ""
159 else
160 pkgs.stdenv.mkDerivation {
161 name = "hercules-ci-check-system-nix-src";
162 inherit (config.nix.package) src patches;
163 dontConfigure = true;
164 buildPhase = ''
165 echo "Checking in-memory pathInfoCache expiry"
166 if ! grep 'PathInfoCacheValue' src/libstore/store-api.hh >/dev/null; then
167 cat 1>&2 <<EOF
168
169 You are deploying Hercules CI Agent on a system with an incompatible
170 nix-daemon. Please make sure nix.package is set to a Nix version of at
171 least 2.3.10 or a master version more recent than Mar 12, 2020.
172 EOF
173 exit 1
174 fi
175 '';
176 installPhase = "touch $out";
177 };
178
179in
180{
181 imports = [
182 (mkRenamedOptionModule [ "services" "hercules-ci-agent" "extraOptions" ] [ "services" "hercules-ci-agent" "settings" ])
183 (mkRenamedOptionModule [ "services" "hercules-ci-agent" "baseDirectory" ] [ "services" "hercules-ci-agent" "settings" "baseDirectory" ])
184 (mkRenamedOptionModule [ "services" "hercules-ci-agent" "concurrentTasks" ] [ "services" "hercules-ci-agent" "settings" "concurrentTasks" ])
185 (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.")
186 ];
187
188 options.services.hercules-ci-agent = {
189 enable = mkOption {
190 type = types.bool;
191 default = false;
192 description = lib.mdDoc ''
193 Enable to run Hercules CI Agent as a system service.
194
195 [Hercules CI](https://hercules-ci.com) is a
196 continuous integation service that is centered around Nix.
197
198 Support is available at [help@hercules-ci.com](mailto:help@hercules-ci.com).
199 '';
200 };
201 checkNix = mkOption {
202 type = types.bool;
203 default = true;
204 description = lib.mdDoc ''
205 Whether to make sure that the system's Nix (nix-daemon) is compatible.
206
207 If you set this to false, please keep up with the change log.
208 '';
209 };
210 package = mkOption {
211 description = lib.mdDoc ''
212 Package containing the bin/hercules-ci-agent executable.
213 '';
214 type = types.package;
215 default = pkgs.hercules-ci-agent;
216 defaultText = literalExpression "pkgs.hercules-ci-agent";
217 };
218 settings = mkOption {
219 description = lib.mdDoc ''
220 These settings are written to the `agent.toml` file.
221
222 Not all settings are listed as options, can be set nonetheless.
223
224 For the exhaustive list of settings, see <https://docs.hercules-ci.com/hercules-ci/reference/agent-config/>.
225 '';
226 type = types.submoduleWith { modules = [ settingsModule ]; };
227 };
228
229 /*
230 Internal and/or computed values.
231
232 These are written as options instead of let binding to allow sharing with
233 default.nix on both NixOS and nix-darwin.
234 */
235 tomlFile = mkOption {
236 type = types.path;
237 internal = true;
238 defaultText = literalMD "generated `hercules-ci-agent.toml`";
239 description = lib.mdDoc ''
240 The fully assembled config file.
241 '';
242 };
243 };
244
245 config = mkIf cfg.enable {
246 nix.extraOptions = lib.addContextFrom checkNix ''
247 # A store path that was missing at first may well have finished building,
248 # even shortly after the previous lookup. This *also* applies to the daemon.
249 narinfo-cache-negative-ttl = 0
250 '';
251 services.hercules-ci-agent = {
252 tomlFile =
253 format.generate "hercules-ci-agent.toml" cfg.settings;
254
255 settings.labels = {
256 agent.source =
257 if options.services.hercules-ci-agent.package.highestPrio == (lib.modules.mkOptionDefault { }).priority
258 then "nixpkgs"
259 else lib.mkOptionDefault "override";
260 pkgs.version = pkgs.lib.version;
261 lib.version = lib.version;
262 };
263 };
264 };
265}