1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.athens;
7
8 athensConfig = flip recursiveUpdate cfg.extraConfig (
9 {
10 GoBinary = "${cfg.goBinary}/bin/go";
11 GoEnv = cfg.goEnv;
12 GoBinaryEnvVars = lib.mapAttrsToList (k: v: "${k}=${v}") cfg.goBinaryEnvVars;
13 GoGetWorkers = cfg.goGetWorkers;
14 GoGetDir = cfg.goGetDir;
15 ProtocolWorkers = cfg.protocolWorkers;
16 LogLevel = cfg.logLevel;
17 CloudRuntime = cfg.cloudRuntime;
18 EnablePprof = cfg.enablePprof;
19 PprofPort = ":${toString cfg.pprofPort}";
20 FilterFile = cfg.filterFile;
21 RobotsFile = cfg.robotsFile;
22 Timeout = cfg.timeout;
23 StorageType = cfg.storageType;
24 TLSCertFile = cfg.tlsCertFile;
25 TLSKeyFile = cfg.tlsKeyFile;
26 Port = ":${toString cfg.port}";
27 UnixSocket = cfg.unixSocket;
28 GlobalEndpoint = cfg.globalEndpoint;
29 BasicAuthUser = cfg.basicAuthUser;
30 BasicAuthPass = cfg.basicAuthPass;
31 ForceSSL = cfg.forceSSL;
32 ValidatorHook = cfg.validatorHook;
33 PathPrefix = cfg.pathPrefix;
34 NETRCPath = cfg.netrcPath;
35 GithubToken = cfg.githubToken;
36 HGRCPath = cfg.hgrcPath;
37 TraceExporter = cfg.traceExporter;
38 StatsExporter = cfg.statsExporter;
39 SumDBs = cfg.sumDBs;
40 NoSumPatterns = cfg.noSumPatterns;
41 DownloadMode = cfg.downloadMode;
42 NetworkMode = cfg.networkMode;
43 DownloadURL = cfg.downloadURL;
44 SingleFlightType = cfg.singleFlightType;
45 IndexType = cfg.indexType;
46 ShutdownTimeout = cfg.shutdownTimeout;
47 SingleFlight = {
48 Etcd = {
49 Endpoints = builtins.concatStringsSep "," cfg.singleFlight.etcd.endpoints;
50 };
51 Redis = {
52 Endpoint = cfg.singleFlight.redis.endpoint;
53 Password = cfg.singleFlight.redis.password;
54 LockConfig = {
55 TTL = cfg.singleFlight.redis.lockConfig.ttl;
56 Timeout = cfg.singleFlight.redis.lockConfig.timeout;
57 MaxRetries = cfg.singleFlight.redis.lockConfig.maxRetries;
58 };
59 };
60 RedisSentinel = {
61 Endpoints = cfg.singleFlight.redisSentinel.endpoints;
62 MasterName = cfg.singleFlight.redisSentinel.masterName;
63 SentinelPassword = cfg.singleFlight.redisSentinel.sentinelPassword;
64 LockConfig = {
65 TTL = cfg.singleFlight.redisSentinel.lockConfig.ttl;
66 Timeout = cfg.singleFlight.redisSentinel.lockConfig.timeout;
67 MaxRetries = cfg.singleFlight.redisSentinel.lockConfig.maxRetries;
68 };
69 };
70 };
71 Storage = {
72 CDN = {
73 Endpoint = cfg.storage.cdn.endpoint;
74 };
75 Disk = {
76 RootPath = cfg.storage.disk.rootPath;
77 };
78 GCP = {
79 ProjectID = cfg.storage.gcp.projectID;
80 Bucket = cfg.storage.gcp.bucket;
81 JSONKey = cfg.storage.gcp.jsonKey;
82 };
83 Minio = {
84 Endpoint = cfg.storage.minio.endpoint;
85 Key = cfg.storage.minio.key;
86 Secret = cfg.storage.minio.secret;
87 EnableSSL = cfg.storage.minio.enableSSL;
88 Bucket = cfg.storage.minio.bucket;
89 region = cfg.storage.minio.region;
90 };
91 Mongo = {
92 URL = cfg.storage.mongo.url;
93 DefaultDBName = cfg.storage.mongo.defaultDBName;
94 CertPath = cfg.storage.mongo.certPath;
95 Insecure = cfg.storage.mongo.insecure;
96 };
97 S3 = {
98 Region = cfg.storage.s3.region;
99 Key = cfg.storage.s3.key;
100 Secret = cfg.storage.s3.secret;
101 Token = cfg.storage.s3.token;
102 Bucket = cfg.storage.s3.bucket;
103 ForcePathStyle = cfg.storage.s3.forcePathStyle;
104 UseDefaultConfiguration = cfg.storage.s3.useDefaultConfiguration;
105 CredentialsEndpoint = cfg.storage.s3.credentialsEndpoint;
106 AwsContainerCredentialsRelativeURI = cfg.storage.s3.awsContainerCredentialsRelativeURI;
107 Endpoint = cfg.storage.s3.endpoint;
108 };
109 AzureBlob = {
110 AccountName = cfg.storage.azureblob.accountName;
111 AccountKey = cfg.storage.azureblob.accountKey;
112 ContainerName = cfg.storage.azureblob.containerName;
113 };
114 External = {
115 URL = cfg.storage.external.url;
116 };
117 };
118 Index = {
119 MySQL = {
120 Protocol = cfg.index.mysql.protocol;
121 Host = cfg.index.mysql.host;
122 Port = cfg.index.mysql.port;
123 User = cfg.index.mysql.user;
124 Password = cfg.index.mysql.password;
125 Database = cfg.index.mysql.database;
126 Params = {
127 parseTime = cfg.index.mysql.params.parseTime;
128 timeout = cfg.index.mysql.params.timeout;
129 };
130 };
131 Postgres = {
132 Host = cfg.index.postgres.host;
133 Port = cfg.index.postgres.port;
134 User = cfg.index.postgres.user;
135 Password = cfg.index.postgres.password;
136 Database = cfg.index.postgres.database;
137 Params = {
138 connect_timeout = cfg.index.postgres.params.connect_timeout;
139 sslmode = cfg.index.postgres.params.sslmode;
140 };
141 };
142 };
143 }
144 );
145
146 configFile = pkgs.runCommandLocal "config.toml" { } ''
147 ${pkgs.buildPackages.jq}/bin/jq 'del(..|nulls)' \
148 < ${pkgs.writeText "config.json" (builtins.toJSON athensConfig)} | \
149 ${pkgs.buildPackages.remarshal}/bin/remarshal -if json -of toml \
150 > $out
151 '';
152in
153{
154 meta = {
155 maintainers = pkgs.athens.meta.maintainers;
156 doc = ./athens.md;
157 };
158
159 options.services.athens = {
160 enable = mkEnableOption "Go module datastore and proxy";
161
162 package = mkOption {
163 default = pkgs.athens;
164 defaultText = literalExpression "pkgs.athens";
165 example = "pkgs.athens";
166 description = "Which athens derivation to use";
167 type = types.package;
168 };
169
170 goBinary = mkOption {
171 type = types.package;
172 default = pkgs.go;
173 defaultText = literalExpression "pkgs.go";
174 example = "pkgs.go_1_21";
175 description = ''
176 The Go package used by Athens at runtime.
177
178 Athens primarily runs two Go commands:
179 1. `go mod download -json <module>@<version>`
180 2. `go list -m -json <module>@latest`
181 '';
182 };
183
184 goEnv = mkOption {
185 type = types.enum [ "development" "production" ];
186 description = "Specifies the type of environment to run. One of 'development' or 'production'.";
187 default = "development";
188 example = "production";
189 };
190
191 goBinaryEnvVars = mkOption {
192 type = types.attrs;
193 description = "Environment variables to pass to the Go binary.";
194 example = ''
195 { "GOPROXY" = "direct", "GODEBUG" = "true" }
196 '';
197 default = { };
198 };
199
200 goGetWorkers = mkOption {
201 type = types.int;
202 description = "Number of workers concurrently downloading modules.";
203 default = 10;
204 example = 32;
205 };
206
207 goGetDir = mkOption {
208 type = types.nullOr types.path;
209 description = ''
210 Temporary directory that Athens will use to
211 fetch modules from VCS prior to persisting
212 them to a storage backend.
213
214 If the value is empty, Athens will use the
215 default OS temp directory.
216 '';
217 default = null;
218 example = "/tmp/athens";
219 };
220
221 protocolWorkers = mkOption {
222 type = types.int;
223 description = "Number of workers concurrently serving protocol paths.";
224 default = 30;
225 };
226
227 logLevel = mkOption {
228 type = types.nullOr (types.enum [ "panic" "fatal" "error" "warning" "info" "debug" "trace" ]);
229 description = ''
230 Log level for Athens.
231 Supports all logrus log levels (https://github.com/Sirupsen/logrus#level-logging)".
232 '';
233 default = "warning";
234 example = "debug";
235 };
236
237 cloudRuntime = mkOption {
238 type = types.enum [ "GCP" "none" ];
239 description = ''
240 Specifies the Cloud Provider on which the Proxy/registry is running.
241 '';
242 default = "none";
243 example = "GCP";
244 };
245
246 enablePprof = mkOption {
247 type = types.bool;
248 description = "Enable pprof endpoints.";
249 default = false;
250 };
251
252 pprofPort = mkOption {
253 type = types.port;
254 description = "Port number for pprof endpoints.";
255 default = 3301;
256 example = 443;
257 };
258
259 filterFile = mkOption {
260 type = types.nullOr types.path;
261 description = ''Filename for the include exclude filter.'';
262 default = null;
263 example = literalExpression ''
264 pkgs.writeText "filterFile" '''
265 - github.com/azure
266 + github.com/azure/azure-sdk-for-go
267 D golang.org/x/tools
268 '''
269 '';
270 };
271
272 robotsFile = mkOption {
273 type = types.nullOr types.path;
274 description = ''Provides /robots.txt for net crawlers.'';
275 default = null;
276 example = literalExpression ''pkgs.writeText "robots.txt" "# my custom robots.txt ..."'';
277 };
278
279 timeout = mkOption {
280 type = types.int;
281 description = "Timeout for external network calls in seconds.";
282 default = 300;
283 example = 3;
284 };
285
286 storageType = mkOption {
287 type = types.enum [ "memory" "disk" "mongo" "gcp" "minio" "s3" "azureblob" "external" ];
288 description = "Specifies the type of storage backend to use.";
289 default = "disk";
290 };
291
292 tlsCertFile = mkOption {
293 type = types.nullOr types.path;
294 description = "Path to the TLS certificate file.";
295 default = null;
296 example = "/etc/ssl/certs/athens.crt";
297 };
298
299 tlsKeyFile = mkOption {
300 type = types.nullOr types.path;
301 description = "Path to the TLS key file.";
302 default = null;
303 example = "/etc/ssl/certs/athens.key";
304 };
305
306 port = mkOption {
307 type = types.port;
308 default = 3000;
309 description = ''
310 Port number Athens listens on.
311 '';
312 example = 443;
313 };
314
315 unixSocket = mkOption {
316 type = types.nullOr types.path;
317 description = ''
318 Path to the unix socket file.
319 If set, Athens will listen on the unix socket instead of TCP socket.
320 '';
321 default = null;
322 example = "/run/athens.sock";
323 };
324
325 globalEndpoint = mkOption {
326 type = types.str;
327 description = ''
328 Endpoint for a package registry in case of a proxy cache miss.
329 '';
330 default = "";
331 example = "http://upstream-athens.example.com:3000";
332 };
333
334 basicAuthUser = mkOption {
335 type = types.nullOr types.str;
336 description = ''
337 Username for basic auth.
338 '';
339 default = null;
340 example = "user";
341 };
342
343 basicAuthPass = mkOption {
344 type = types.nullOr types.str;
345 description = ''
346 Password for basic auth. Warning: this is stored in plain text in the config file.
347 '';
348 default = null;
349 example = "swordfish";
350 };
351
352 forceSSL = mkOption {
353 type = types.bool;
354 description = ''
355 Force SSL redirects for incoming requests.
356 '';
357 default = false;
358 };
359
360 validatorHook = mkOption {
361 type = types.nullOr types.str;
362 description = ''
363 Endpoint to validate modules against.
364
365 Not used if empty.
366 '';
367 default = null;
368 example = "https://validation.example.com";
369 };
370
371 pathPrefix = mkOption {
372 type = types.nullOr types.str;
373 description = ''
374 Sets basepath for all routes.
375 '';
376 default = null;
377 example = "/athens";
378 };
379
380 netrcPath = mkOption {
381 type = types.nullOr types.path;
382 description = ''
383 Path to the .netrc file.
384 '';
385 default = null;
386 example = "/home/user/.netrc";
387 };
388
389 githubToken = mkOption {
390 type = types.nullOr types.str;
391 description = ''
392 Creates .netrc file with the given token to be used for GitHub.
393 Warning: this is stored in plain text in the config file.
394 '';
395 default = null;
396 example = "ghp_1234567890";
397 };
398
399 hgrcPath = mkOption {
400 type = types.nullOr types.path;
401 description = ''
402 Path to the .hgrc file.
403 '';
404 default = null;
405 example = "/home/user/.hgrc";
406 };
407
408 traceExporter = mkOption {
409 type = types.nullOr (types.enum [ "jaeger" "datadog" ]);
410 description = ''
411 Trace exporter to use.
412 '';
413 default = null;
414 };
415
416 traceExporterURL = mkOption {
417 type = types.nullOr types.str;
418 description = ''
419 URL endpoint that traces will be sent to.
420 '';
421 default = null;
422 example = "http://localhost:14268";
423 };
424
425 statsExporter = mkOption {
426 type = types.nullOr (types.enum [ "prometheus" ]);
427 description = "Stats exporter to use.";
428 default = null;
429 };
430
431 sumDBs = mkOption {
432 type = types.listOf types.str;
433 description = ''
434 List of fully qualified URLs that Athens will proxy
435 that the go command can use a checksum verifier.
436 '';
437 default = [ "https://sum.golang.org" ];
438 };
439
440 noSumPatterns = mkOption {
441 type = types.listOf types.str;
442 description = ''
443 List of patterns that Athens sum db proxy will return a 403 for.
444 '';
445 default = [ ];
446 example = [ "github.com/mycompany/*" ];
447 };
448
449 downloadMode = mkOption {
450 type = types.oneOf [ (types.enum [ "sync" "async" "redirect" "async_redirect" "none" ]) (types.strMatching "^file:.*$|^custom:.*$") ];
451 description = ''
452 Defines how Athens behaves when a module@version
453 is not found in storage. There are 7 options:
454 1. "sync": download the module synchronously and
455 return the results to the client.
456 2. "async": return 404, but asynchronously store the module
457 in the storage backend.
458 3. "redirect": return a 301 redirect status to the client
459 with the base URL as the DownloadRedirectURL from below.
460 4. "async_redirect": same as option number 3 but it will
461 asynchronously store the module to the backend.
462 5. "none": return 404 if a module is not found and do nothing.
463 6. "file:<path>": will point to an HCL file that specifies
464 any of the 5 options above based on different import paths.
465 7. "custom:<base64-encoded-hcl>" is the same as option 6
466 but the file is fully encoded in the option. This is
467 useful for using an environment variable in serverless
468 deployments.
469 '';
470 default = "async_redirect";
471 };
472
473 networkMode = mkOption {
474 type = types.enum [ "strict" "offline" "fallback" ];
475 description = ''
476 Configures how Athens will return the results
477 of the /list endpoint as it can be assembled from both its own
478 storage and the upstream VCS.
479
480 Note, that for better error messaging, this would also affect how other
481 endpoints behave.
482
483 Modes:
484 1. strict: merge VCS versions with storage versions, but fail if either of them fails.
485 2. offline: only get storage versions, never reach out to VCS.
486 3. fallback: only return storage versions, if VCS fails. Note this means that you may
487 see inconsistent results since fallback mode does a best effort of giving you what's
488 available at the time of requesting versions.
489 '';
490 default = "strict";
491 };
492
493 downloadURL = mkOption {
494 type = types.str;
495 description = "URL used if DownloadMode is set to redirect.";
496 default = "https://proxy.golang.org";
497 };
498
499 singleFlightType = mkOption {
500 type = types.enum [ "memory" "etcd" "redis" "redis-sentinel" "gcp" "azureblob" ];
501 description = ''
502 Determines what mechanism Athens uses to manage concurrency flowing into the Athens backend.
503 '';
504 default = "memory";
505 };
506
507 indexType = mkOption {
508 type = types.enum [ "none" "memory" "mysql" "postgres" ];
509 description = ''
510 Type of index backend Athens will use.
511 '';
512 default = "none";
513 };
514
515 shutdownTimeout = mkOption {
516 type = types.int;
517 description = ''
518 Number of seconds to wait for the server to shutdown gracefully.
519 '';
520 default = 60;
521 example = 1;
522 };
523
524 singleFlight = {
525 etcd = {
526 endpoints = mkOption {
527 type = types.listOf types.str;
528 description = "URLs that determine all distributed etcd servers.";
529 default = [ ];
530 example = [ "localhost:2379" ];
531 };
532 };
533 redis = {
534 endpoint = mkOption {
535 type = types.str;
536 description = "URL of the redis server.";
537 default = "";
538 example = "localhost:6379";
539 };
540 password = mkOption {
541 type = types.str;
542 description = "Password for the redis server. Warning: this is stored in plain text in the config file.";
543 default = "";
544 example = "swordfish";
545 };
546
547 lockConfig = {
548 ttl = mkOption {
549 type = types.int;
550 description = "TTL for the lock in seconds.";
551 default = 900;
552 example = 1;
553 };
554 timeout = mkOption {
555 type = types.int;
556 description = "Timeout for the lock in seconds.";
557 default = 15;
558 example = 1;
559 };
560 maxRetries = mkOption {
561 type = types.int;
562 description = "Maximum number of retries for the lock.";
563 default = 10;
564 example = 1;
565 };
566 };
567 };
568
569 redisSentinel = {
570 endpoints = mkOption {
571 type = types.listOf types.str;
572 description = "URLs that determine all distributed redis servers.";
573 default = [ ];
574 example = [ "localhost:26379" ];
575 };
576 masterName = mkOption {
577 type = types.str;
578 description = "Name of the sentinel master server.";
579 default = "";
580 example = "redis-1";
581 };
582 sentinelPassword = mkOption {
583 type = types.str;
584 description = "Password for the sentinel server. Warning: this is stored in plain text in the config file.";
585 default = "";
586 example = "swordfish";
587 };
588
589 lockConfig = {
590 ttl = mkOption {
591 type = types.int;
592 description = "TTL for the lock in seconds.";
593 default = 900;
594 example = 1;
595 };
596 timeout = mkOption {
597 type = types.int;
598 description = "Timeout for the lock in seconds.";
599 default = 15;
600 example = 1;
601 };
602 maxRetries = mkOption {
603 type = types.int;
604 description = "Maximum number of retries for the lock.";
605 default = 10;
606 example = 1;
607 };
608 };
609 };
610 };
611
612 storage = {
613 cdn = {
614 endpoint = mkOption {
615 type = types.nullOr types.str;
616 description = "hostname of the CDN server.";
617 example = "cdn.example.com";
618 default = null;
619 };
620 };
621
622 disk = {
623 rootPath = mkOption {
624 type = types.nullOr types.path;
625 description = "Athens disk root folder.";
626 default = "/var/lib/athens";
627 };
628 };
629
630 gcp = {
631 projectID = mkOption {
632 type = types.nullOr types.str;
633 description = "GCP project ID.";
634 example = "my-project";
635 default = null;
636 };
637 bucket = mkOption {
638 type = types.nullOr types.str;
639 description = "GCP backend storage bucket.";
640 example = "my-bucket";
641 default = null;
642 };
643 jsonKey = mkOption {
644 type = types.nullOr types.str;
645 description = "Base64 encoded GCP service account key. Warning: this is stored in plain text in the config file.";
646 default = null;
647 };
648 };
649
650 minio = {
651 endpoint = mkOption {
652 type = types.nullOr types.str;
653 description = "Endpoint of the minio storage backend.";
654 example = "minio.example.com:9001";
655 default = null;
656 };
657 key = mkOption {
658 type = types.nullOr types.str;
659 description = "Access key id for the minio storage backend.";
660 example = "minio";
661 default = null;
662 };
663 secret = mkOption {
664 type = types.nullOr types.str;
665 description = "Secret key for the minio storage backend. Warning: this is stored in plain text in the config file.";
666 example = "minio123";
667 default = null;
668 };
669 enableSSL = mkOption {
670 type = types.bool;
671 description = "Enable SSL for the minio storage backend.";
672 default = false;
673 };
674 bucket = mkOption {
675 type = types.nullOr types.str;
676 description = "Bucket name for the minio storage backend.";
677 example = "gomods";
678 default = null;
679 };
680 region = mkOption {
681 type = types.nullOr types.str;
682 description = "Region for the minio storage backend.";
683 example = "us-east-1";
684 default = null;
685 };
686 };
687
688 mongo = {
689 url = mkOption {
690 type = types.nullOr types.str;
691 description = "URL of the mongo database.";
692 example = "mongodb://localhost:27017";
693 default = null;
694 };
695 defaultDBName = mkOption {
696 type = types.nullOr types.str;
697 description = "Name of the mongo database.";
698 example = "athens";
699 default = null;
700 };
701 certPath = mkOption {
702 type = types.nullOr types.path;
703 description = "Path to the certificate file for the mongo database.";
704 example = "/etc/ssl/mongo.pem";
705 default = null;
706 };
707 insecure = mkOption {
708 type = types.bool;
709 description = "Allow insecure connections to the mongo database.";
710 default = false;
711 };
712 };
713
714 s3 = {
715 region = mkOption {
716 type = types.nullOr types.str;
717 description = "Region of the S3 storage backend.";
718 example = "eu-west-3";
719 default = null;
720 };
721 key = mkOption {
722 type = types.nullOr types.str;
723 description = "Access key id for the S3 storage backend.";
724 example = "minio";
725 default = null;
726 };
727 secret = mkOption {
728 type = types.str;
729 description = "Secret key for the S3 storage backend. Warning: this is stored in plain text in the config file.";
730 default = "";
731 };
732 token = mkOption {
733 type = types.nullOr types.str;
734 description = "Token for the S3 storage backend. Warning: this is stored in plain text in the config file.";
735 default = null;
736 };
737 bucket = mkOption {
738 type = types.nullOr types.str;
739 description = "Bucket name for the S3 storage backend.";
740 example = "gomods";
741 default = null;
742 };
743 forcePathStyle = mkOption {
744 type = types.bool;
745 description = "Force path style for the S3 storage backend.";
746 default = false;
747 };
748 useDefaultConfiguration = mkOption {
749 type = types.bool;
750 description = "Use default configuration for the S3 storage backend.";
751 default = false;
752 };
753 credentialsEndpoint = mkOption {
754 type = types.str;
755 description = "Credentials endpoint for the S3 storage backend.";
756 default = "";
757 };
758 awsContainerCredentialsRelativeURI = mkOption {
759 type = types.nullOr types.str;
760 description = "Container relative url (used by fargate).";
761 default = null;
762 };
763 endpoint = mkOption {
764 type = types.nullOr types.str;
765 description = "Endpoint for the S3 storage backend.";
766 default = null;
767 };
768 };
769
770 azureblob = {
771 accountName = mkOption {
772 type = types.nullOr types.str;
773 description = "Account name for the Azure Blob storage backend.";
774 default = null;
775 };
776 accountKey = mkOption {
777 type = types.nullOr types.str;
778 description = "Account key for the Azure Blob storage backend. Warning: this is stored in plain text in the config file.";
779 default = null;
780 };
781 containerName = mkOption {
782 type = types.nullOr types.str;
783 description = "Container name for the Azure Blob storage backend.";
784 default = null;
785 };
786 };
787
788 external = {
789 url = mkOption {
790 type = types.nullOr types.str;
791 description = "URL of the backend storage layer.";
792 example = "https://athens.example.com";
793 default = null;
794 };
795 };
796 };
797
798 index = {
799 mysql = {
800 protocol = mkOption {
801 type = types.str;
802 description = "Protocol for the MySQL database.";
803 default = "tcp";
804 };
805 host = mkOption {
806 type = types.str;
807 description = "Host for the MySQL database.";
808 default = "localhost";
809 };
810 port = mkOption {
811 type = types.int;
812 description = "Port for the MySQL database.";
813 default = 3306;
814 };
815 user = mkOption {
816 type = types.str;
817 description = "User for the MySQL database.";
818 default = "root";
819 };
820 password = mkOption {
821 type = types.nullOr types.str;
822 description = "Password for the MySQL database. Warning: this is stored in plain text in the config file.";
823 default = null;
824 };
825 database = mkOption {
826 type = types.str;
827 description = "Database name for the MySQL database.";
828 default = "athens";
829 };
830 params = {
831 parseTime = mkOption {
832 type = types.nullOr types.str;
833 description = "Parse time for the MySQL database.";
834 default = "true";
835 };
836 timeout = mkOption {
837 type = types.nullOr types.str;
838 description = "Timeout for the MySQL database.";
839 default = "30s";
840 };
841 };
842 };
843
844 postgres = {
845 host = mkOption {
846 type = types.str;
847 description = "Host for the Postgres database.";
848 default = "localhost";
849 };
850 port = mkOption {
851 type = types.int;
852 description = "Port for the Postgres database.";
853 default = 5432;
854 };
855 user = mkOption {
856 type = types.str;
857 description = "User for the Postgres database.";
858 default = "postgres";
859 };
860 password = mkOption {
861 type = types.nullOr types.str;
862 description = "Password for the Postgres database. Warning: this is stored in plain text in the config file.";
863 default = null;
864 };
865 database = mkOption {
866 type = types.str;
867 description = "Database name for the Postgres database.";
868 default = "athens";
869 };
870 params = {
871 connect_timeout = mkOption {
872 type = types.nullOr types.str;
873 description = "Connect timeout for the Postgres database.";
874 default = "30s";
875 };
876 sslmode = mkOption {
877 type = types.nullOr types.str;
878 description = "SSL mode for the Postgres database.";
879 default = "disable";
880 };
881 };
882 };
883 };
884
885 extraConfig = mkOption {
886 type = types.attrs;
887 description = ''
888 Extra configuration options for the athens config file.
889 '';
890 default = { };
891 };
892 };
893
894 config = mkIf cfg.enable {
895 systemd.services.athens = {
896 description = "Athens Go module proxy";
897 documentation = [ "https://docs.gomods.io" ];
898
899 wantedBy = [ "multi-user.target" ];
900 after = [ "network-online.target" ];
901 wants = [ "network-online.target" ];
902
903 serviceConfig = {
904 Restart = "on-abnormal";
905 Nice = 5;
906 ExecStart = ''${cfg.package}/bin/athens -config_file=${configFile}'';
907
908 KillMode = "mixed";
909 KillSignal = "SIGINT";
910 TimeoutStopSec = cfg.shutdownTimeout;
911
912 LimitNOFILE = 1048576;
913 LimitNPROC = 512;
914
915 DynamicUser = true;
916 PrivateTmp = true;
917 PrivateDevices = true;
918 ProtectHome = "read-only";
919 ProtectSystem = "full";
920
921 ReadWritePaths = mkIf (cfg.storage.disk.rootPath != null && (! hasPrefix "/var/lib/" cfg.storage.disk.rootPath)) [ cfg.storage.disk.rootPath ];
922 StateDirectory = mkIf (hasPrefix "/var/lib/" cfg.storage.disk.rootPath) [ (removePrefix "/var/lib/" cfg.storage.disk.rootPath) ];
923
924 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
925 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
926 NoNewPrivileges = true;
927 };
928 };
929
930 networking.firewall = {
931 allowedTCPPorts = optionals (cfg.unixSocket == null) [ cfg.port ]
932 ++ optionals cfg.enablePprof [ cfg.pprofPort ];
933 };
934 };
935
936}