diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2a2556104731..2bb04a02f433 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -694,6 +694,7 @@ ./services/misc/cpuminer-cryptonight.nix ./services/misc/db-rest.nix ./services/misc/devmon.nix + ./services/misc/devpi-server.nix ./services/misc/dictd.nix ./services/misc/disnix.nix ./services/misc/docker-registry.nix diff --git a/nixos/modules/services/misc/devpi-server.nix b/nixos/modules/services/misc/devpi-server.nix new file mode 100644 index 000000000000..0234db4bc2c5 --- /dev/null +++ b/nixos/modules/services/misc/devpi-server.nix @@ -0,0 +1,128 @@ +{ + pkgs, + lib, + config, + ... +}: +with lib; +let + cfg = config.services.devpi-server; + + secretsFileName = "devpi-secret-file"; + + stateDirName = "devpi"; + + runtimeDir = "/run/${stateDirName}"; + serverDir = "/var/lib/${stateDirName}"; +in +{ + options.services.devpi-server = { + enable = mkEnableOption "Devpi Server"; + + package = mkPackageOption pkgs "devpi-server" { }; + + primaryUrl = mkOption { + type = types.str; + description = "Url for the primary node. Required option for replica nodes."; + }; + + replica = mkOption { + type = types.bool; + default = false; + description = '' + Run node as a replica. + Requires the secretFile option and the primaryUrl to be enabled. + ''; + }; + + secretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a shared secret file used for synchronization, + Required for all nodes in a replica/primary setup. + ''; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = '' + domain/ip address to listen on + ''; + }; + + port = mkOption { + type = types.port; + default = 3141; + description = "The port on which Devpi Server will listen."; + }; + + openFirewall = mkEnableOption "opening the default ports in the firewall for Devpi Server"; + }; + + config = mkIf cfg.enable { + + systemd.services.devpi-server = { + enable = true; + description = "devpi PyPI-compatible server"; + documentation = [ "https://devpi.net/docs/devpi/devpi/stable/+d/index.html" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + # Since at least devpi-server 6.10.0, devpi requires the secrets file to + # have 0600 permissions. + preStart = + '' + cp ${cfg.secretFile} ${runtimeDir}/${secretsFileName} + chmod 0600 ${runtimeDir}/*${secretsFileName} + + if [ -f ${serverDir}/.nodeinfo ]; then + # already initialized the package index, exit gracefully + exit 0 + fi + ${cfg.package}/bin/devpi-init --serverdir ${serverDir} '' + + strings.optionalString cfg.replica "--role=replica --master-url=${cfg.primaryUrl}"; + + serviceConfig = { + Restart = "always"; + ExecStart = + let + args = + [ + "--request-timeout=5" + "--serverdir=${serverDir}" + "--host=${cfg.host}" + "--port=${builtins.toString cfg.port}" + ] + ++ lib.optionals (! isNull cfg.secretFile) [ + "--secretfile=${runtimeDir}/${secretsFileName}" + ] + ++ ( + if cfg.replica then + [ + "--role=replica" + "--master-url=${cfg.primaryUrl}" + ] + else + [ "--role=master" ] + ); + in + "${cfg.package}/bin/devpi-server ${concatStringsSep " " args}"; + DynamicUser = true; + StateDirectory = stateDirName; + RuntimeDirectory = stateDirName; + PrivateDevices = true; + PrivateTmp = true; + ProtectHome = true; + ProtectSystem = "strict"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + + meta.maintainers = [ cafkafk ]; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 23c5a94ed12c..392335cf88cc 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -242,6 +242,7 @@ in { deepin = handleTest ./deepin.nix {}; deluge = handleTest ./deluge.nix {}; dendrite = handleTest ./matrix/dendrite.nix {}; + devpi-server = handleTest ./devpi-server.nix {}; dex-oidc = handleTest ./dex-oidc.nix {}; dhparams = handleTest ./dhparams.nix {}; disable-installer-tools = handleTest ./disable-installer-tools.nix {}; diff --git a/nixos/tests/devpi-server.nix b/nixos/tests/devpi-server.nix new file mode 100644 index 000000000000..2a16d49724db --- /dev/null +++ b/nixos/tests/devpi-server.nix @@ -0,0 +1,35 @@ +import ./make-test-python.nix ({pkgs, ...}: let + server-port = 3141; +in { + name = "devpi-server"; + meta = with pkgs.lib.maintainers; { + maintainers = [cafkafk]; + }; + + nodes = { + devpi = {...}: { + services.devpi-server = { + enable = true; + host = "0.0.0.0"; + port = server-port; + openFirewall = true; + secretFile = pkgs.writeText "devpi-secret" "v263P+V3YGDYUyfYL/RBURw+tCPMDw94R/iCuBNJrDhaYrZYjpA6XPFVDDH8ViN20j77y2PHoMM/U0opNkVQ2g=="; + }; + }; + + client1 = {...}: { + environment.systemPackages = with pkgs; [ + devpi-client + jq + ]; + }; + }; + + testScript = '' + start_all() + devpi.wait_for_unit("devpi-server.service") + devpi.wait_for_open_port(${builtins.toString server-port}) + + client1.succeed("devpi getjson http://devpi:${builtins.toString server-port}") + ''; +}) diff --git a/pkgs/development/tools/devpi-server/default.nix b/pkgs/development/tools/devpi-server/default.nix index 0923182afa4f..c1dfc4841931 100644 --- a/pkgs/development/tools/devpi-server/default.nix +++ b/pkgs/development/tools/devpi-server/default.nix @@ -23,6 +23,7 @@ , webtest , testers , devpi-server +, nixosTests }: @@ -108,8 +109,11 @@ buildPythonApplication rec { "devpi_server" ]; - passthru.tests.version = testers.testVersion { - package = devpi-server; + passthru.tests = { + devpi-server = nixosTests.devpi-server; + version = testers.testVersion { + package = devpi-server; + }; }; meta = with lib;{