losslesscut-bin: 3.61.1 -> 3.64.0 (#373227)
[NixPkgs.git] / nixos / modules / services / networking / ncps.nix
blob12b51bf05217d9162cc7f33f38975ec8f3899fb6
2   config,
3   pkgs,
4   lib,
5   ...
6 }:
7 let
8   cfg = config.services.ncps;
10   logLevels = [
11     "trace"
12     "debug"
13     "info"
14     "warn"
15     "error"
16     "fatal"
17     "panic"
18   ];
20   globalFlags = lib.concatStringsSep " " (
21     [ "--log-level='${cfg.logLevel}'" ]
22     ++ (lib.optionals cfg.openTelemetry.enable (
23       [
24         "--otel-enabled"
25       ]
26       ++ (lib.optional (
27         cfg.openTelemetry.grpcURL != null
28       ) "--otel-grpc-url='${cfg.openTelemetry.grpcURL}'")
29     ))
30   );
32   serveFlags = lib.concatStringsSep " " (
33     [
34       "--cache-hostname='${cfg.cache.hostName}'"
35       "--cache-data-path='${cfg.cache.dataPath}'"
36       "--cache-database-url='${cfg.cache.databaseURL}'"
37       "--server-addr='${cfg.server.addr}'"
38     ]
39     ++ (lib.optional cfg.cache.allowDeleteVerb "--cache-allow-delete-verb")
40     ++ (lib.optional cfg.cache.allowPutVerb "--cache-allow-put-verb")
41     ++ (lib.optional (cfg.cache.maxSize != null) "--cache-max-size='${cfg.cache.maxSize}'")
42     ++ (lib.optionals (cfg.cache.lru.schedule != null) [
43       "--cache-lru-schedule='${cfg.cache.lru.schedule}'"
44       "--cache-lru-schedule-timezone='${cfg.cache.lru.scheduleTimeZone}'"
45     ])
46     ++ (lib.optional (cfg.cache.secretKeyPath != null) "--cache-secret-key-path='%d/secretKey'")
47     ++ (lib.forEach cfg.upstream.caches (url: "--upstream-cache='${url}'"))
48     ++ (lib.forEach cfg.upstream.publicKeys (pk: "--upstream-public-key='${pk}'"))
49   );
51   isSqlite = lib.strings.hasPrefix "sqlite:" cfg.cache.databaseURL;
53   dbPath = lib.removePrefix "sqlite:" cfg.cache.databaseURL;
54   dbDir = dirOf dbPath;
57   options = {
58     services.ncps = {
59       enable = lib.mkEnableOption "ncps: Nix binary cache proxy service implemented in Go";
61       package = lib.mkPackageOption pkgs "ncps" { };
63       dbmatePackage = lib.mkPackageOption pkgs "dbmate" { };
65       openTelemetry = {
66         enable = lib.mkEnableOption "Enable OpenTelemetry logs, metrics, and tracing";
68         grpcURL = lib.mkOption {
69           type = lib.types.nullOr lib.types.str;
70           default = null;
71           description = ''
72             Configure OpenTelemetry gRPC URL. Missing or "https" scheme enables
73             secure gRPC, "insecure" otherwise. Omit to emit telemetry to
74             stdout.
75           '';
76         };
77       };
79       logLevel = lib.mkOption {
80         type = lib.types.enum logLevels;
81         default = "info";
82         description = ''
83           Set the level for logging. Refer to
84           <https://pkg.go.dev/github.com/rs/zerolog#readme-leveled-logging> for
85           more information.
86         '';
87       };
89       cache = {
90         allowDeleteVerb = lib.mkEnableOption ''
91           Whether to allow the DELETE verb to delete narinfo and nar files from
92           the cache.
93         '';
95         allowPutVerb = lib.mkEnableOption ''
96           Whether to allow the PUT verb to push narinfo and nar files directly
97           to the cache.
98         '';
100         hostName = lib.mkOption {
101           type = lib.types.str;
102           description = ''
103             The hostname of the cache server. **This is used to generate the
104             private key used for signing store paths (.narinfo)**
105           '';
106         };
108         dataPath = lib.mkOption {
109           type = lib.types.str;
110           default = "/var/lib/ncps";
111           description = ''
112             The local directory for storing configuration and cached store paths
113           '';
114         };
116         databaseURL = lib.mkOption {
117           type = lib.types.str;
118           default = "sqlite:${cfg.cache.dataPath}/db/db.sqlite";
119           defaultText = "sqlite:/var/lib/ncps/db/db.sqlite";
120           description = ''
121             The URL of the database (currently only SQLite is supported)
122           '';
123         };
125         lru = {
126           schedule = lib.mkOption {
127             type = lib.types.nullOr lib.types.str;
128             default = null;
129             example = "0 2 * * *";
130             description = ''
131               The cron spec for cleaning the store to keep it under
132               config.ncps.cache.maxSize. Refer to
133               https://pkg.go.dev/github.com/robfig/cron/v3#hdr-Usage for
134               documentation.
135             '';
136           };
138           scheduleTimeZone = lib.mkOption {
139             type = lib.types.str;
140             default = "Local";
141             example = "America/Los_Angeles";
142             description = ''
143               The name of the timezone to use for the cron schedule. See
144               <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
145               for a comprehensive list of possible values for this setting.
146             '';
147           };
148         };
150         maxSize = lib.mkOption {
151           type = lib.types.nullOr lib.types.str;
152           default = null;
153           example = "100G";
154           description = ''
155             The maximum size of the store. It can be given with units such as
156             5K, 10G etc. Supported units: B, K, M, G, T.
157           '';
158         };
160         secretKeyPath = lib.mkOption {
161           type = lib.types.nullOr lib.types.str;
162           default = null;
163           description = ''
164             The path to load the secretKey for signing narinfos. Leave this
165             empty to automatically generate a private/public key.
166           '';
167         };
168       };
170       server = {
171         addr = lib.mkOption {
172           type = lib.types.str;
173           default = ":8501";
174           description = ''
175             The address and port the server listens on.
176           '';
177         };
178       };
180       upstream = {
181         caches = lib.mkOption {
182           type = lib.types.listOf lib.types.str;
183           example = [ "https://cache.nixos.org" ];
184           description = ''
185             A list of URLs of upstream binary caches.
186           '';
187         };
189         publicKeys = lib.mkOption {
190           type = lib.types.listOf lib.types.str;
191           default = [ ];
192           example = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
193           description = ''
194             A list of public keys of upstream caches in the format
195             `host[-[0-9]*]:public-key`. This flag is used to verify the
196             signatures of store paths downloaded from upstream caches.
197           '';
198         };
199       };
200     };
201   };
203   config = lib.mkIf cfg.enable {
204     assertions = [
205       {
206         assertion = cfg.cache.lru.schedule == null || cfg.cache.maxSize != null;
207         message = "You must specify config.ncps.cache.lru.schedule when config.ncps.cache.maxSize is set";
208       }
210       {
211         assertion = cfg.cache.secretKeyPath == null || (builtins.pathExists cfg.cache.secretKeyPath);
212         message = "config.ncps.cache.secresecretKeyPath=${cfg.cache.secretKeyPath} must exist but does not";
213       }
214     ];
216     users.users.ncps = {
217       isSystemUser = true;
218       group = "ncps";
219     };
220     users.groups.ncps = { };
222     systemd.services.ncps-create-datadirs = {
223       description = "Created required directories by ncps";
224       serviceConfig = {
225         Type = "oneshot";
226         UMask = "0066";
227       };
228       script =
229         (lib.optionalString (cfg.cache.dataPath != "/var/lib/ncps") ''
230           if ! test -d ${cfg.cache.dataPath}; then
231             mkdir -p ${cfg.cache.dataPath}
232             chown ncps:ncps ${cfg.cache.dataPath}
233           fi
234         '')
235         + (lib.optionalString isSqlite ''
236           if ! test -d ${dbDir}; then
237             mkdir -p ${dbDir}
238             chown ncps:ncps ${dbDir}
239           fi
240         '');
241       wantedBy = [ "ncps.service" ];
242       before = [ "ncps.service" ];
243     };
245     systemd.services.ncps = {
246       description = "ncps binary cache proxy service";
248       after = [ "network.target" ];
249       wantedBy = [ "multi-user.target" ];
251       preStart = ''
252         ${lib.getExe cfg.dbmatePackage} --migrations-dir=${cfg.package}/share/ncps/db/migrations --url=${cfg.cache.databaseURL} up
253       '';
255       serviceConfig = lib.mkMerge [
256         {
257           ExecStart = "${lib.getExe cfg.package} ${globalFlags} serve ${serveFlags}";
258           User = "ncps";
259           Group = "ncps";
260           Restart = "on-failure";
261           RuntimeDirectory = "ncps";
262         }
264         # credentials
265         (lib.mkIf (cfg.cache.secretKeyPath != null) {
266           LoadCredential = "secretKey:${cfg.cache.secretKeyPath}";
267         })
269         # ensure permissions on required directories
270         (lib.mkIf (cfg.cache.dataPath != "/var/lib/ncps") {
271           ReadWritePaths = [ cfg.cache.dataPath ];
272         })
273         (lib.mkIf (cfg.cache.dataPath == "/var/lib/ncps") {
274           StateDirectory = "ncps";
275           StateDirectoryMode = "0700";
276         })
277         (lib.mkIf (isSqlite && !lib.strings.hasPrefix "/var/lib/ncps" dbDir) {
278           ReadWritePaths = [ dbDir ];
279         })
281         # Hardening
282         {
283           SystemCallFilter = [
284             "@system-service"
285             "~@privileged"
286             "~@resources"
287           ];
288           CapabilityBoundingSet = "";
289           PrivateUsers = true;
290           DevicePolicy = "closed";
291           DeviceAllow = [ "" ];
292           ProtectKernelModules = true;
293           ProtectKernelTunables = true;
294           ProtectControlGroups = true;
295           ProtectKernelLogs = true;
296           ProtectHostname = true;
297           ProtectClock = true;
298           ProtectProc = "invisible";
299           ProtectSystem = "strict";
300           ProtectHome = true;
301           RestrictSUIDSGID = true;
302           RestrictRealtime = true;
303           MemoryDenyWriteExecute = true;
304           ProcSubset = "pid";
305           RestrictNamespaces = true;
306           SystemCallArchitectures = "native";
307           PrivateNetwork = false;
308           PrivateTmp = true;
309           PrivateDevices = true;
310           PrivateMounts = true;
311           NoNewPrivileges = true;
312           LockPersonality = true;
313           RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
314           LimitNOFILE = 65536;
315           UMask = "0066";
316         }
317       ];
319       unitConfig.RequiresMountsFor = lib.concatStringsSep " " (
320         [ "${cfg.cache.dataPath}" ] ++ lib.optional (isSqlite) dbDir
321       );
322     };
323   };
325   meta.maintainers = with lib.maintainers; [ kalbasit ];