1 { config, lib, pkgs, ... }:
3 cfg = config.services.athens;
5 athensConfig = lib.flip lib.recursiveUpdate cfg.extraConfig (
7 GoBinary = "${cfg.goBinary}/bin/go";
9 GoBinaryEnvVars = lib.mapAttrsToList (k: v: "${k}=${v}") cfg.goBinaryEnvVars;
10 GoGetWorkers = cfg.goGetWorkers;
11 GoGetDir = cfg.goGetDir;
12 ProtocolWorkers = cfg.protocolWorkers;
13 LogLevel = cfg.logLevel;
14 CloudRuntime = cfg.cloudRuntime;
15 EnablePprof = cfg.enablePprof;
16 PprofPort = ":${toString cfg.pprofPort}";
17 FilterFile = cfg.filterFile;
18 RobotsFile = cfg.robotsFile;
19 Timeout = cfg.timeout;
20 StorageType = cfg.storageType;
21 TLSCertFile = cfg.tlsCertFile;
22 TLSKeyFile = cfg.tlsKeyFile;
23 Port = ":${toString cfg.port}";
24 UnixSocket = cfg.unixSocket;
25 GlobalEndpoint = cfg.globalEndpoint;
26 BasicAuthUser = cfg.basicAuthUser;
27 BasicAuthPass = cfg.basicAuthPass;
28 ForceSSL = cfg.forceSSL;
29 ValidatorHook = cfg.validatorHook;
30 PathPrefix = cfg.pathPrefix;
31 NETRCPath = cfg.netrcPath;
32 GithubToken = cfg.githubToken;
33 HGRCPath = cfg.hgrcPath;
34 TraceExporter = cfg.traceExporter;
35 StatsExporter = cfg.statsExporter;
37 NoSumPatterns = cfg.noSumPatterns;
38 DownloadMode = cfg.downloadMode;
39 NetworkMode = cfg.networkMode;
40 DownloadURL = cfg.downloadURL;
41 SingleFlightType = cfg.singleFlightType;
42 IndexType = cfg.indexType;
43 ShutdownTimeout = cfg.shutdownTimeout;
46 Endpoints = builtins.concatStringsSep "," cfg.singleFlight.etcd.endpoints;
49 Endpoint = cfg.singleFlight.redis.endpoint;
50 Password = cfg.singleFlight.redis.password;
52 TTL = cfg.singleFlight.redis.lockConfig.ttl;
53 Timeout = cfg.singleFlight.redis.lockConfig.timeout;
54 MaxRetries = cfg.singleFlight.redis.lockConfig.maxRetries;
58 Endpoints = cfg.singleFlight.redisSentinel.endpoints;
59 MasterName = cfg.singleFlight.redisSentinel.masterName;
60 SentinelPassword = cfg.singleFlight.redisSentinel.sentinelPassword;
62 TTL = cfg.singleFlight.redisSentinel.lockConfig.ttl;
63 Timeout = cfg.singleFlight.redisSentinel.lockConfig.timeout;
64 MaxRetries = cfg.singleFlight.redisSentinel.lockConfig.maxRetries;
70 Endpoint = cfg.storage.cdn.endpoint;
73 RootPath = cfg.storage.disk.rootPath;
76 ProjectID = cfg.storage.gcp.projectID;
77 Bucket = cfg.storage.gcp.bucket;
78 JSONKey = cfg.storage.gcp.jsonKey;
81 Endpoint = cfg.storage.minio.endpoint;
82 Key = cfg.storage.minio.key;
83 Secret = cfg.storage.minio.secret;
84 EnableSSL = cfg.storage.minio.enableSSL;
85 Bucket = cfg.storage.minio.bucket;
86 region = cfg.storage.minio.region;
89 URL = cfg.storage.mongo.url;
90 DefaultDBName = cfg.storage.mongo.defaultDBName;
91 CertPath = cfg.storage.mongo.certPath;
92 Insecure = cfg.storage.mongo.insecure;
95 Region = cfg.storage.s3.region;
96 Key = cfg.storage.s3.key;
97 Secret = cfg.storage.s3.secret;
98 Token = cfg.storage.s3.token;
99 Bucket = cfg.storage.s3.bucket;
100 ForcePathStyle = cfg.storage.s3.forcePathStyle;
101 UseDefaultConfiguration = cfg.storage.s3.useDefaultConfiguration;
102 CredentialsEndpoint = cfg.storage.s3.credentialsEndpoint;
103 AwsContainerCredentialsRelativeURI = cfg.storage.s3.awsContainerCredentialsRelativeURI;
104 Endpoint = cfg.storage.s3.endpoint;
107 AccountName = cfg.storage.azureblob.accountName;
108 AccountKey = cfg.storage.azureblob.accountKey;
109 ContainerName = cfg.storage.azureblob.containerName;
112 URL = cfg.storage.external.url;
117 Protocol = cfg.index.mysql.protocol;
118 Host = cfg.index.mysql.host;
119 Port = cfg.index.mysql.port;
120 User = cfg.index.mysql.user;
121 Password = cfg.index.mysql.password;
122 Database = cfg.index.mysql.database;
124 parseTime = cfg.index.mysql.params.parseTime;
125 timeout = cfg.index.mysql.params.timeout;
129 Host = cfg.index.postgres.host;
130 Port = cfg.index.postgres.port;
131 User = cfg.index.postgres.user;
132 Password = cfg.index.postgres.password;
133 Database = cfg.index.postgres.database;
135 connect_timeout = cfg.index.postgres.params.connect_timeout;
136 sslmode = cfg.index.postgres.params.sslmode;
143 configFile = pkgs.runCommandLocal "config.toml" { } ''
144 ${pkgs.buildPackages.jq}/bin/jq 'del(..|nulls)' \
145 < ${pkgs.writeText "config.json" (builtins.toJSON athensConfig)} | \
146 ${pkgs.buildPackages.remarshal}/bin/remarshal -if json -of toml \
152 maintainers = pkgs.athens.meta.maintainers;
156 options.services.athens = {
157 enable = lib.mkEnableOption "Go module datastore and proxy";
159 package = lib.mkOption {
160 default = pkgs.athens;
161 defaultText = lib.literalExpression "pkgs.athens";
162 example = "pkgs.athens";
163 description = "Which athens derivation to use";
164 type = lib.types.package;
167 goBinary = lib.mkOption {
168 type = lib.types.package;
170 defaultText = lib.literalExpression "pkgs.go";
171 example = "pkgs.go_1_23";
173 The Go package used by Athens at runtime.
175 Athens primarily runs two Go commands:
176 1. `go mod download -json <module>@<version>`
177 2. `go list -m -json <module>@latest`
181 goEnv = lib.mkOption {
182 type = lib.types.enum [ "development" "production" ];
183 description = "Specifies the type of environment to run. One of 'development' or 'production'.";
184 default = "development";
185 example = "production";
188 goBinaryEnvVars = lib.mkOption {
189 type = lib.types.attrs;
190 description = "Environment variables to pass to the Go binary.";
192 { "GOPROXY" = "direct", "GODEBUG" = "true" }
197 goGetWorkers = lib.mkOption {
198 type = lib.types.int;
199 description = "Number of workers concurrently downloading modules.";
204 goGetDir = lib.mkOption {
205 type = lib.types.nullOr lib.types.path;
207 Temporary directory that Athens will use to
208 fetch modules from VCS prior to persisting
209 them to a storage backend.
211 If the value is empty, Athens will use the
212 default OS temp directory.
215 example = "/tmp/athens";
218 protocolWorkers = lib.mkOption {
219 type = lib.types.int;
220 description = "Number of workers concurrently serving protocol paths.";
224 logLevel = lib.mkOption {
225 type = lib.types.nullOr (lib.types.enum [ "panic" "fatal" "error" "warning" "info" "debug" "trace" ]);
227 Log level for Athens.
228 Supports all logrus log levels (https://github.com/Sirupsen/logrus#level-logging)".
234 cloudRuntime = lib.mkOption {
235 type = lib.types.enum [ "GCP" "none" ];
237 Specifies the Cloud Provider on which the Proxy/registry is running.
243 enablePprof = lib.mkOption {
244 type = lib.types.bool;
245 description = "Enable pprof endpoints.";
249 pprofPort = lib.mkOption {
250 type = lib.types.port;
251 description = "Port number for pprof endpoints.";
256 filterFile = lib.mkOption {
257 type = lib.types.nullOr lib.types.path;
258 description = ''Filename for the include exclude filter.'';
260 example = lib.literalExpression ''
261 pkgs.writeText "filterFile" '''
263 + github.com/azure/azure-sdk-for-go
269 robotsFile = lib.mkOption {
270 type = lib.types.nullOr lib.types.path;
271 description = ''Provides /robots.txt for net crawlers.'';
273 example = lib.literalExpression ''pkgs.writeText "robots.txt" "# my custom robots.txt ..."'';
276 timeout = lib.mkOption {
277 type = lib.types.int;
278 description = "Timeout for external network calls in seconds.";
283 storageType = lib.mkOption {
284 type = lib.types.enum [ "memory" "disk" "mongo" "gcp" "minio" "s3" "azureblob" "external" ];
285 description = "Specifies the type of storage backend to use.";
289 tlsCertFile = lib.mkOption {
290 type = lib.types.nullOr lib.types.path;
291 description = "Path to the TLS certificate file.";
293 example = "/etc/ssl/certs/athens.crt";
296 tlsKeyFile = lib.mkOption {
297 type = lib.types.nullOr lib.types.path;
298 description = "Path to the TLS key file.";
300 example = "/etc/ssl/certs/athens.key";
303 port = lib.mkOption {
304 type = lib.types.port;
307 Port number Athens listens on.
312 unixSocket = lib.mkOption {
313 type = lib.types.nullOr lib.types.path;
315 Path to the unix socket file.
316 If set, Athens will listen on the unix socket instead of TCP socket.
319 example = "/run/athens.sock";
322 globalEndpoint = lib.mkOption {
323 type = lib.types.str;
325 Endpoint for a package registry in case of a proxy cache miss.
328 example = "http://upstream-athens.example.com:3000";
331 basicAuthUser = lib.mkOption {
332 type = lib.types.nullOr lib.types.str;
334 Username for basic auth.
340 basicAuthPass = lib.mkOption {
341 type = lib.types.nullOr lib.types.str;
343 Password for basic auth. Warning: this is stored in plain text in the config file.
346 example = "swordfish";
349 forceSSL = lib.mkOption {
350 type = lib.types.bool;
352 Force SSL redirects for incoming requests.
357 validatorHook = lib.mkOption {
358 type = lib.types.nullOr lib.types.str;
360 Endpoint to validate modules against.
365 example = "https://validation.example.com";
368 pathPrefix = lib.mkOption {
369 type = lib.types.nullOr lib.types.str;
371 Sets basepath for all routes.
377 netrcPath = lib.mkOption {
378 type = lib.types.nullOr lib.types.path;
380 Path to the .netrc file.
383 example = "/home/user/.netrc";
386 githubToken = lib.mkOption {
387 type = lib.types.nullOr lib.types.str;
389 Creates .netrc file with the given token to be used for GitHub.
390 Warning: this is stored in plain text in the config file.
393 example = "ghp_1234567890";
396 hgrcPath = lib.mkOption {
397 type = lib.types.nullOr lib.types.path;
399 Path to the .hgrc file.
402 example = "/home/user/.hgrc";
405 traceExporter = lib.mkOption {
406 type = lib.types.nullOr (lib.types.enum [ "jaeger" "datadog" ]);
408 Trace exporter to use.
413 traceExporterURL = lib.mkOption {
414 type = lib.types.nullOr lib.types.str;
416 URL endpoint that traces will be sent to.
419 example = "http://localhost:14268";
422 statsExporter = lib.mkOption {
423 type = lib.types.nullOr (lib.types.enum [ "prometheus" ]);
424 description = "Stats exporter to use.";
428 sumDBs = lib.mkOption {
429 type = lib.types.listOf lib.types.str;
431 List of fully qualified URLs that Athens will proxy
432 that the go command can use a checksum verifier.
434 default = [ "https://sum.golang.org" ];
437 noSumPatterns = lib.mkOption {
438 type = lib.types.listOf lib.types.str;
440 List of patterns that Athens sum db proxy will return a 403 for.
443 example = [ "github.com/mycompany/*" ];
446 downloadMode = lib.mkOption {
447 type = lib.types.oneOf [ (lib.types.enum [ "sync" "async" "redirect" "async_redirect" "none" ]) (lib.types.strMatching "^file:.*$|^custom:.*$") ];
449 Defines how Athens behaves when a module@version
450 is not found in storage. There are 7 options:
451 1. "sync": download the module synchronously and
452 return the results to the client.
453 2. "async": return 404, but asynchronously store the module
454 in the storage backend.
455 3. "redirect": return a 301 redirect status to the client
456 with the base URL as the DownloadRedirectURL from below.
457 4. "async_redirect": same as option number 3 but it will
458 asynchronously store the module to the backend.
459 5. "none": return 404 if a module is not found and do nothing.
460 6. "file:<path>": will point to an HCL file that specifies
461 any of the 5 options above based on different import paths.
462 7. "custom:<base64-encoded-hcl>" is the same as option 6
463 but the file is fully encoded in the option. This is
464 useful for using an environment variable in serverless
467 default = "async_redirect";
470 networkMode = lib.mkOption {
471 type = lib.types.enum [ "strict" "offline" "fallback" ];
473 Configures how Athens will return the results
474 of the /list endpoint as it can be assembled from both its own
475 storage and the upstream VCS.
477 Note, that for better error messaging, this would also affect how other
481 1. strict: merge VCS versions with storage versions, but fail if either of them fails.
482 2. offline: only get storage versions, never reach out to VCS.
483 3. fallback: only return storage versions, if VCS fails. Note this means that you may
484 see inconsistent results since fallback mode does a best effort of giving you what's
485 available at the time of requesting versions.
490 downloadURL = lib.mkOption {
491 type = lib.types.str;
492 description = "URL used if DownloadMode is set to redirect.";
493 default = "https://proxy.golang.org";
496 singleFlightType = lib.mkOption {
497 type = lib.types.enum [ "memory" "etcd" "redis" "redis-sentinel" "gcp" "azureblob" ];
499 Determines what mechanism Athens uses to manage concurrency flowing into the Athens backend.
504 indexType = lib.mkOption {
505 type = lib.types.enum [ "none" "memory" "mysql" "postgres" ];
507 Type of index backend Athens will use.
512 shutdownTimeout = lib.mkOption {
513 type = lib.types.int;
515 Number of seconds to wait for the server to shutdown gracefully.
523 endpoints = lib.mkOption {
524 type = lib.types.listOf lib.types.str;
525 description = "URLs that determine all distributed etcd servers.";
527 example = [ "localhost:2379" ];
531 endpoint = lib.mkOption {
532 type = lib.types.str;
533 description = "URL of the redis server.";
535 example = "localhost:6379";
537 password = lib.mkOption {
538 type = lib.types.str;
539 description = "Password for the redis server. Warning: this is stored in plain text in the config file.";
541 example = "swordfish";
546 type = lib.types.int;
547 description = "TTL for the lock in seconds.";
551 timeout = lib.mkOption {
552 type = lib.types.int;
553 description = "Timeout for the lock in seconds.";
557 maxRetries = lib.mkOption {
558 type = lib.types.int;
559 description = "Maximum number of retries for the lock.";
567 endpoints = lib.mkOption {
568 type = lib.types.listOf lib.types.str;
569 description = "URLs that determine all distributed redis servers.";
571 example = [ "localhost:26379" ];
573 masterName = lib.mkOption {
574 type = lib.types.str;
575 description = "Name of the sentinel master server.";
579 sentinelPassword = lib.mkOption {
580 type = lib.types.str;
581 description = "Password for the sentinel server. Warning: this is stored in plain text in the config file.";
583 example = "swordfish";
588 type = lib.types.int;
589 description = "TTL for the lock in seconds.";
593 timeout = lib.mkOption {
594 type = lib.types.int;
595 description = "Timeout for the lock in seconds.";
599 maxRetries = lib.mkOption {
600 type = lib.types.int;
601 description = "Maximum number of retries for the lock.";
611 endpoint = lib.mkOption {
612 type = lib.types.nullOr lib.types.str;
613 description = "hostname of the CDN server.";
614 example = "cdn.example.com";
620 rootPath = lib.mkOption {
621 type = lib.types.nullOr lib.types.path;
622 description = "Athens disk root folder.";
623 default = "/var/lib/athens";
628 projectID = lib.mkOption {
629 type = lib.types.nullOr lib.types.str;
630 description = "GCP project ID.";
631 example = "my-project";
634 bucket = lib.mkOption {
635 type = lib.types.nullOr lib.types.str;
636 description = "GCP backend storage bucket.";
637 example = "my-bucket";
640 jsonKey = lib.mkOption {
641 type = lib.types.nullOr lib.types.str;
642 description = "Base64 encoded GCP service account key. Warning: this is stored in plain text in the config file.";
648 endpoint = lib.mkOption {
649 type = lib.types.nullOr lib.types.str;
650 description = "Endpoint of the minio storage backend.";
651 example = "minio.example.com:9001";
655 type = lib.types.nullOr lib.types.str;
656 description = "Access key id for the minio storage backend.";
660 secret = lib.mkOption {
661 type = lib.types.nullOr lib.types.str;
662 description = "Secret key for the minio storage backend. Warning: this is stored in plain text in the config file.";
663 example = "minio123";
666 enableSSL = lib.mkOption {
667 type = lib.types.bool;
668 description = "Enable SSL for the minio storage backend.";
671 bucket = lib.mkOption {
672 type = lib.types.nullOr lib.types.str;
673 description = "Bucket name for the minio storage backend.";
677 region = lib.mkOption {
678 type = lib.types.nullOr lib.types.str;
679 description = "Region for the minio storage backend.";
680 example = "us-east-1";
687 type = lib.types.nullOr lib.types.str;
688 description = "URL of the mongo database.";
689 example = "mongodb://localhost:27017";
692 defaultDBName = lib.mkOption {
693 type = lib.types.nullOr lib.types.str;
694 description = "Name of the mongo database.";
698 certPath = lib.mkOption {
699 type = lib.types.nullOr lib.types.path;
700 description = "Path to the certificate file for the mongo database.";
701 example = "/etc/ssl/mongo.pem";
704 insecure = lib.mkOption {
705 type = lib.types.bool;
706 description = "Allow insecure connections to the mongo database.";
712 region = lib.mkOption {
713 type = lib.types.nullOr lib.types.str;
714 description = "Region of the S3 storage backend.";
715 example = "eu-west-3";
719 type = lib.types.nullOr lib.types.str;
720 description = "Access key id for the S3 storage backend.";
724 secret = lib.mkOption {
725 type = lib.types.str;
726 description = "Secret key for the S3 storage backend. Warning: this is stored in plain text in the config file.";
729 token = lib.mkOption {
730 type = lib.types.nullOr lib.types.str;
731 description = "Token for the S3 storage backend. Warning: this is stored in plain text in the config file.";
734 bucket = lib.mkOption {
735 type = lib.types.nullOr lib.types.str;
736 description = "Bucket name for the S3 storage backend.";
740 forcePathStyle = lib.mkOption {
741 type = lib.types.bool;
742 description = "Force path style for the S3 storage backend.";
745 useDefaultConfiguration = lib.mkOption {
746 type = lib.types.bool;
747 description = "Use default configuration for the S3 storage backend.";
750 credentialsEndpoint = lib.mkOption {
751 type = lib.types.str;
752 description = "Credentials endpoint for the S3 storage backend.";
755 awsContainerCredentialsRelativeURI = lib.mkOption {
756 type = lib.types.nullOr lib.types.str;
757 description = "Container relative url (used by fargate).";
760 endpoint = lib.mkOption {
761 type = lib.types.nullOr lib.types.str;
762 description = "Endpoint for the S3 storage backend.";
768 accountName = lib.mkOption {
769 type = lib.types.nullOr lib.types.str;
770 description = "Account name for the Azure Blob storage backend.";
773 accountKey = lib.mkOption {
774 type = lib.types.nullOr lib.types.str;
775 description = "Account key for the Azure Blob storage backend. Warning: this is stored in plain text in the config file.";
778 containerName = lib.mkOption {
779 type = lib.types.nullOr lib.types.str;
780 description = "Container name for the Azure Blob storage backend.";
787 type = lib.types.nullOr lib.types.str;
788 description = "URL of the backend storage layer.";
789 example = "https://athens.example.com";
797 protocol = lib.mkOption {
798 type = lib.types.str;
799 description = "Protocol for the MySQL database.";
802 host = lib.mkOption {
803 type = lib.types.str;
804 description = "Host for the MySQL database.";
805 default = "localhost";
807 port = lib.mkOption {
808 type = lib.types.int;
809 description = "Port for the MySQL database.";
812 user = lib.mkOption {
813 type = lib.types.str;
814 description = "User for the MySQL database.";
817 password = lib.mkOption {
818 type = lib.types.nullOr lib.types.str;
819 description = "Password for the MySQL database. Warning: this is stored in plain text in the config file.";
822 database = lib.mkOption {
823 type = lib.types.str;
824 description = "Database name for the MySQL database.";
828 parseTime = lib.mkOption {
829 type = lib.types.nullOr lib.types.str;
830 description = "Parse time for the MySQL database.";
833 timeout = lib.mkOption {
834 type = lib.types.nullOr lib.types.str;
835 description = "Timeout for the MySQL database.";
842 host = lib.mkOption {
843 type = lib.types.str;
844 description = "Host for the Postgres database.";
845 default = "localhost";
847 port = lib.mkOption {
848 type = lib.types.int;
849 description = "Port for the Postgres database.";
852 user = lib.mkOption {
853 type = lib.types.str;
854 description = "User for the Postgres database.";
855 default = "postgres";
857 password = lib.mkOption {
858 type = lib.types.nullOr lib.types.str;
859 description = "Password for the Postgres database. Warning: this is stored in plain text in the config file.";
862 database = lib.mkOption {
863 type = lib.types.str;
864 description = "Database name for the Postgres database.";
868 connect_timeout = lib.mkOption {
869 type = lib.types.nullOr lib.types.str;
870 description = "Connect timeout for the Postgres database.";
873 sslmode = lib.mkOption {
874 type = lib.types.nullOr lib.types.str;
875 description = "SSL mode for the Postgres database.";
882 extraConfig = lib.mkOption {
883 type = lib.types.attrs;
885 Extra configuration options for the athens config file.
891 config = lib.mkIf cfg.enable {
892 systemd.services.athens = {
893 description = "Athens Go module proxy";
894 documentation = [ "https://docs.gomods.io" ];
896 wantedBy = [ "multi-user.target" ];
897 after = [ "network-online.target" ];
898 wants = [ "network-online.target" ];
901 Restart = "on-abnormal";
903 ExecStart = ''${cfg.package}/bin/athens -config_file=${configFile}'';
906 KillSignal = "SIGINT";
907 TimeoutStopSec = cfg.shutdownTimeout;
909 LimitNOFILE = 1048576;
914 PrivateDevices = true;
915 ProtectHome = "read-only";
916 ProtectSystem = "full";
918 ReadWritePaths = lib.mkIf (cfg.storage.disk.rootPath != null && (! lib.hasPrefix "/var/lib/" cfg.storage.disk.rootPath)) [ cfg.storage.disk.rootPath ];
919 StateDirectory = lib.mkIf (lib.hasPrefix "/var/lib/" cfg.storage.disk.rootPath) [ (lib.removePrefix "/var/lib/" cfg.storage.disk.rootPath) ];
921 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
922 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
923 NoNewPrivileges = true;
927 networking.firewall = {
928 allowedTCPPorts = lib.optionals (cfg.unixSocket == null) [ cfg.port ]
929 ++ lib.optionals cfg.enablePprof [ cfg.pprofPort ];