nixos/db-rest: init

This commit is contained in:
Marie Ramlow 2023-11-14 21:58:11 +01:00
parent c9ff971b19
commit 8785ef0a2d
6 changed files with 297 additions and 0 deletions

View file

@ -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. Matter Controller Server exposing websocket connections for use with other services, notably Home Assistant.
Available as [services.matter-server](#opt-services.matter-server.enable) 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). - [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. 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.

View file

@ -690,6 +690,7 @@
./services/misc/clipmenu.nix ./services/misc/clipmenu.nix
./services/misc/confd.nix ./services/misc/confd.nix
./services/misc/cpuminer-cryptonight.nix ./services/misc/cpuminer-cryptonight.nix
./services/misc/db-rest.nix
./services/misc/devmon.nix ./services/misc/devmon.nix
./services/misc/dictd.nix ./services/misc/dictd.nix
./services/misc/disnix.nix ./services/misc/disnix.nix

View file

@ -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 ];
}

View file

@ -236,6 +236,7 @@ in {
darling = handleTest ./darling.nix {}; darling = handleTest ./darling.nix {};
dae = handleTest ./dae.nix {}; dae = handleTest ./dae.nix {};
davis = handleTest ./davis.nix {}; davis = handleTest ./davis.nix {};
db-rest = handleTest ./db-rest.nix {};
dconf = handleTest ./dconf.nix {}; dconf = handleTest ./dconf.nix {};
deconz = handleTest ./deconz.nix {}; deconz = handleTest ./deconz.nix {};
deepin = handleTest ./deepin.nix {}; deepin = handleTest ./deepin.nix {};

107
nixos/tests/db-rest.nix Normal file
View file

@ -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'")
'';
})

View file

@ -4,6 +4,7 @@
, nodejs_18 , nodejs_18
, nix-update-script , nix-update-script
, fetchpatch , fetchpatch
, nixosTests
}: }:
buildNpmPackage rec { buildNpmPackage rec {
pname = "db-rest"; pname = "db-rest";
@ -25,6 +26,9 @@ buildNpmPackage rec {
''; '';
passthru.updateScript = nix-update-script { }; passthru.updateScript = nix-update-script { };
passthru.tests = {
inherit (nixosTests) db-rest;
};
meta = { meta = {
description = "A clean REST API wrapping around the Deutsche Bahn API"; description = "A clean REST API wrapping around the Deutsche Bahn API";