about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorChristina Rust <christina@cafkafk.com>2024-05-13 13:14:51 +0200
committerGitHub <noreply@github.com>2024-05-13 13:14:51 +0200
commit31a5a35b7e8515b5941892e1e39a5e8f108f6521 (patch)
tree321919c60f878809e2a3e8fba2c09218817b9645 /nixos
parentc8b2579f1fe502a7cff41fbc55e8958f27650287 (diff)
parent52e0ad744d55f92d0127173d9859aca2d7609944 (diff)
Merge pull request #305286 from cafkafk/devpi-server-init
nixos/devpi-server: init
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/misc/devpi-server.nix128
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/devpi-server.nix35
4 files changed, 165 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 3cbb4617517aa..2a308af6ed2dd 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -699,6 +699,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 0000000000000..0234db4bc2c5b
--- /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 a8f8c470e6e29..cbddae381399f 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -243,6 +243,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 0000000000000..2a16d49724dbc
--- /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}")
+  '';
+})