about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorLily Foster <lily@lily.flowers>2024-04-16 10:26:41 -0400
committerGitHub <noreply@github.com>2024-04-16 10:26:41 -0400
commitc92928fb0baef09bc24b19f85c0f49b8f6b2f54d (patch)
treec025682fe0710ef1777acc9ddffc91d4be67c555 /nixos
parente2ce6703871b62a77f5939d5967833b43c928ed0 (diff)
parent8785ef0a2d4d22af523b0dccabf7a3885f96af4d (diff)
Merge pull request #268133 from NyCodeGHG/nixos/db-rest
nixos/db-rest: init
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/misc/db-rest.nix182
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/db-rest.nix107
5 files changed, 293 insertions, 0 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index 988632fc44349..3f4549911fe49 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -118,6 +118,8 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
   Matter Controller Server exposing websocket connections for use with other services, notably Home Assistant.
   Available as [services.matter-server](#opt-services.matter-server.enable)
 
+- [db-rest](https://github.com/derhuerst/db-rest), a wrapper around Deutsche Bahn's internal API for public transport data. Available as [services.db-rest](#opt-services.db-rest.enable).
+
 - [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable).
 The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been marked deprecated and will be dropped after 24.05 due to lack of maintenance of the anki-sync-server softwares.
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index a45507d5ee3cd..dda647ac19942 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -690,6 +690,7 @@
   ./services/misc/clipmenu.nix
   ./services/misc/confd.nix
   ./services/misc/cpuminer-cryptonight.nix
+  ./services/misc/db-rest.nix
   ./services/misc/devmon.nix
   ./services/misc/dictd.nix
   ./services/misc/disnix.nix
diff --git a/nixos/modules/services/misc/db-rest.nix b/nixos/modules/services/misc/db-rest.nix
new file mode 100644
index 0000000000000..fbf8b327af049
--- /dev/null
+++ b/nixos/modules/services/misc/db-rest.nix
@@ -0,0 +1,182 @@
+{ config, pkgs, lib, ... }:
+let
+  inherit (lib) mkOption types mkIf mkMerge mkDefault mkEnableOption mkPackageOption maintainers;
+  cfg = config.services.db-rest;
+in
+{
+  options = {
+    services.db-rest = {
+      enable = mkEnableOption "db-rest service";
+
+      user = mkOption {
+        type = types.str;
+        default = "db-rest";
+        description = "User account under which db-rest runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "db-rest";
+        description = "Group under which db-rest runs.";
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "The host address the db-rest server should listen on.";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 3000;
+        description = "The port the db-rest server should listen on.";
+      };
+
+      redis = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Enable caching with redis for db-rest.";
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Configure a local redis server for db-rest.";
+        };
+
+        host = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = "Redis host.";
+        };
+
+        port = mkOption {
+          type = with types; nullOr port;
+          default = null;
+          description = "Redis port.";
+        };
+
+        user = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = "Optional username used for authentication with redis.";
+        };
+
+        passwordFile = mkOption {
+          type = with types; nullOr path;
+          default = null;
+          example = "/run/keys/db-rest/pasword-redis-db";
+          description = "Path to a file containing the redis password.";
+        };
+
+        useSSL = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Use SSL if using a redis network connection.";
+        };
+      };
+
+      package = mkPackageOption pkgs "db-rest" { };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.redis.enable && !cfg.redis.createLocally) -> (cfg.redis.host != null && cfg.redis.port != null);
+        message = ''
+          {option}`services.db-rest.redis.createLocally` and redis network connection ({option}`services.db-rest.redis.host` or {option}`services.db-rest.redis.port`) enabled. Disable either of them.
+        '';
+      }
+      {
+        assertion = (cfg.redis.enable && !cfg.redis.createLocally) -> (cfg.redis.passwordFile != null);
+        message = ''
+          {option}`services.db-rest.redis.createLocally` is disabled, but {option}`services.db-rest.redis.passwordFile` is not set.
+        '';
+      }
+    ];
+
+    systemd.services.db-rest = mkMerge [
+      {
+        description = "db-rest service";
+        after = [ "network.target" ]
+          ++ lib.optional cfg.redis.createLocally "redis-db-rest.service";
+        requires = lib.optional cfg.redis.createLocally "redis-db-rest.service";
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          Type = "simple";
+          Restart = "always";
+          RestartSec = 5;
+          WorkingDirectory = cfg.package;
+          User = cfg.user;
+          Group = cfg.group;
+          RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+          MemoryDenyWriteExecute = false;
+          LoadCredential = lib.optional (cfg.redis.enable && cfg.redis.passwordFile != null) "REDIS_PASSWORD:${cfg.redis.passwordFile}";
+          ExecStart = mkDefault "${cfg.package}/bin/db-rest";
+
+          RemoveIPC = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          ProtectClock = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          ProtectKernelModules = true;
+          PrivateMounts = true;
+          SystemCallArchitectures = "native";
+          ProtectHostname = true;
+          LockPersonality = true;
+          ProtectKernelTunables = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          RestrictNamespaces = true;
+          ProtectSystem = "strict";
+          ProtectProc = "invisible";
+          ProcSubset = "pid";
+          ProtectHome = true;
+          PrivateUsers = true;
+          PrivateTmp = true;
+          CapabilityBoundingSet = "";
+        };
+        environment = {
+          NODE_ENV = "production";
+          NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
+          HOSTNAME = cfg.host;
+          PORT = toString cfg.port;
+        };
+      }
+      (mkIf cfg.redis.enable (if cfg.redis.createLocally then
+        { environment.REDIS_URL = config.services.redis.servers.db-rest.unixSocket; }
+      else
+        {
+          script =
+            let
+              username = lib.optionalString (cfg.redis.user != null) (cfg.redis.user);
+              host = cfg.redis.host;
+              port = toString cfg.redis.port;
+              protocol = if cfg.redis.useSSL then "rediss" else "redis";
+            in
+            ''
+              export REDIS_URL="${protocol}://${username}:$(${config.systemd.package}/bin/systemd-creds cat REDIS_PASSWORD)@${host}:${port}"
+              exec ${cfg.package}/bin/db-rest
+            '';
+        }))
+    ];
+
+    users.users = lib.mkMerge [
+      (lib.mkIf (cfg.user == "db-rest") {
+        db-rest = {
+          isSystemUser = true;
+          group = cfg.group;
+        };
+      })
+      (lib.mkIf cfg.redis.createLocally { ${cfg.user}.extraGroups = [ "redis-db-rest" ]; })
+    ];
+
+    users.groups = lib.mkIf (cfg.group == "db-rest") { db-rest = { }; };
+
+    services.redis.servers.db-rest.enable = cfg.redis.enable && cfg.redis.createLocally;
+  };
+  meta.maintainers = with maintainers; [ marie ];
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index fe02a97d6ff1e..a99fedaddd763 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -236,6 +236,7 @@ in {
   darling = handleTest ./darling.nix {};
   dae = handleTest ./dae.nix {};
   davis = handleTest ./davis.nix {};
+  db-rest = handleTest ./db-rest.nix {};
   dconf = handleTest ./dconf.nix {};
   deconz = handleTest ./deconz.nix {};
   deepin = handleTest ./deepin.nix {};
diff --git a/nixos/tests/db-rest.nix b/nixos/tests/db-rest.nix
new file mode 100644
index 0000000000000..9249da904acbe
--- /dev/null
+++ b/nixos/tests/db-rest.nix
@@ -0,0 +1,107 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "db-rest";
+  meta.maintainers = with pkgs.lib.maintainers; [ marie ];
+
+  nodes = {
+    database = {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.10"; prefixLength = 24; }
+          ];
+        };
+        firewall.allowedTCPPorts = [ 31638 ];
+      };
+
+      services.redis.servers.db-rest = {
+        enable = true;
+        bind = "0.0.0.0";
+        requirePass = "choochoo";
+        port = 31638;
+      };
+    };
+
+    serverWithTcp = { pkgs, ... }: {
+      environment = {
+        etc = {
+          "db-rest/password-redis-db".text = ''
+            choochoo
+          '';
+        };
+      };
+
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.11"; prefixLength = 24; }
+          ];
+        };
+        firewall.allowedTCPPorts = [ 3000 ];
+      };
+
+      services.db-rest = {
+        enable = true;
+        host = "0.0.0.0";
+        redis = {
+          enable = true;
+          createLocally = false;
+          host = "192.168.2.10";
+          port = 31638;
+          passwordFile = "/etc/db-rest/password-redis-db";
+          useSSL = false;
+        };
+      };
+    };
+
+    serverWithUnixSocket = { pkgs, ... }: {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.12"; prefixLength = 24; }
+          ];
+        };
+        firewall.allowedTCPPorts = [ 3000 ];
+      };
+
+      services.db-rest = {
+        enable = true;
+        host = "0.0.0.0";
+        redis = {
+          enable = true;
+          createLocally = true;
+        };
+      };
+    };
+
+    client = {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.13"; prefixLength = 24; }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("db-rest redis with TCP socket"):
+      database.wait_for_unit("redis-db-rest.service")
+      database.wait_for_open_port(31638)
+
+      serverWithTcp.wait_for_unit("db-rest.service")
+      serverWithTcp.wait_for_open_port(3000)
+
+      client.succeed("curl --fail --get http://192.168.2.11:3000/stations --data-urlencode 'query=Köln Hbf' | jq -r '.\"8000207\".name' | grep 'Köln Hbf'")
+
+    with subtest("db-rest redis with Unix socket"):
+      serverWithUnixSocket.wait_for_unit("db-rest.service")
+      serverWithUnixSocket.wait_for_open_port(3000)
+
+      client.succeed("curl --fail --get http://192.168.2.12:3000/stations --data-urlencode 'query=Köln Hbf' | jq -r '.\"8000207\".name' | grep 'Köln Hbf'")
+  '';
+})