Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
inherit inputs;
}
{
debug = true;
imports = [
./checks
./ci
Expand Down
329 changes: 164 additions & 165 deletions modules/services/kupo.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,186 +5,185 @@
...
}: let
cfg = config.services.kupo;
inherit (lib) escapeShellArgs flatten types;
in
with lib; {
options.services.kupo = {
enable = mkEnableOption "Kupo Cardano chain-indexer";

package = mkOption {
description = "Kupo package.";
type = types.package;
default = pkgs.kupo or null;
};

user = mkOption {
description = "User to run kupo service as.";
type = types.nonEmptyStr;
default = "kupo";
};

group = mkOption {
description = "Group to run kupo service as.";
type = types.nonEmptyStr;
default = "kupo";
};
inherit (lib) escapeShellArgs flatten types mkOption mkEnableOption mkIf optional;
in {
options.services.kupo = {
enable = mkEnableOption "Kupo Cardano chain-indexer";

package = mkOption {
description = "Kupo package.";
type = types.package;
default = pkgs.kupo or null;
};

workDir = mkOption {
description = "Directory to start the kupo and store its data. Must start with `/var/lib/`.";
type = types.path;
default = "/var/lib/kupo";
};
user = mkOption {
description = "User to run kupo service as.";
type = types.nonEmptyStr;
default = "kupo";
};

host = mkOption {
description = "Host address or name to listen on.";
type = types.nonEmptyStr;
default = "127.0.0.1";
};
group = mkOption {
description = "Group to run kupo service as.";
type = types.nonEmptyStr;
default = "kupo";
};

port = mkOption {
description = "TCP port to listen on.";
type = types.port;
default = 1442;
};
workDir = mkOption {
description = "Directory to start the kupo and store its data. Must start with `/var/lib/`.";
type = types.path;
default = "/var/lib/kupo";
};

nodeSocketPath = mkOption {
description = "Path to cardano-node IPC socket. Ignored if `ogmiosHost` is not `null`.";
type = types.nullOr types.path;
default = "/run/cardano-node/node.socket";
};
host = mkOption {
description = "Host address or name to listen on.";
type = types.nonEmptyStr;
default = "127.0.0.1";
};

nodeConfigPath = mkOption {
description = "Path to cardano-node config.json file. Ignored if `ogmiosHost` is not `null`";
type = types.path;
default = "/etc/cardano-node/config.json";
};
port = mkOption {
description = "TCP port to listen on.";
type = types.port;
default = 1442;
};

ogmiosHost = mkOption {
description = "Ogmios host name. Optional, will connect to cardano-node if `null`.";
type = types.nullOr types.nonEmptyStr;
default = null;
};
nodeSocketPath = mkOption {
description = "Path to cardano-node IPC socket. Ignored if `ogmiosHost` is not `null`.";
type = types.nullOr types.path;
default = "/run/cardano-node/node.socket";
};

ogmiosPort = mkOption {
description = "Ogmios port. Ignored if `ogmiosHost` is `null`.";
type = types.port;
default = 1337;
};
nodeConfigPath = mkOption {
description = "Path to cardano-node config.json file. Ignored if `ogmiosHost` is not `null`";
type = types.path;
default = "/etc/cardano-node/config.json";
};

hydraHost = mkOption {
description = "Hydra host name. Optional.";
type = types.nullOr types.nonEmptyStr;
default = null;
};
ogmiosHost = mkOption {
description = "Ogmios host name. Optional, will connect to cardano-node if `null`.";
type = types.nullOr types.nonEmptyStr;
default = null;
};

hydraPort = mkOption {
description = "Hydra port. Ignored if `hydraHost` is `null`.";
type = types.port;
};
ogmiosPort = mkOption {
description = "Ogmios port. Ignored if `ogmiosHost` is `null`.";
type = types.port;
default = 1337;
};

matches = mkOption {
description = "The list of addresses to watch.";
type = types.listOf types.nonEmptyStr;
default = ["*"];
};
hydraHost = mkOption {
description = "Hydra host name. Optional.";
type = types.nullOr types.nonEmptyStr;
default = null;
};

since = mkOption {
description = "Watching depth.";
type = types.nonEmptyStr;
default = "origin";
};
hydraPort = mkOption {
description = "Hydra port. Ignored if `hydraHost` is `null`.";
type = types.port;
};

pruneUtxo = mkOption {
description = "Automatically remove inputs that are spent on-chain.";
type = types.bool;
default = false;
};
matches = mkOption {
description = "The list of addresses to watch.";
type = types.listOf types.nonEmptyStr;
default = ["*"];
};

extraArgs = mkOption {
description = "Extra arguments to kupo command.";
type = types.listOf types.str;
default = [];
};
since = mkOption {
description = "Watching depth.";
type = types.nonEmptyStr;
default = "origin";
};

config = mkIf cfg.enable {
assertions = [
{
assertion = lib.hasPrefix "/var/lib/" cfg.workDir;
message = "`workDir` must start with `/var/lib/`";
}
];
pruneUtxo = mkOption {
description = "Automatically remove inputs that are spent on-chain.";
type = types.bool;
default = false;
};

users.users.kupo = mkIf (cfg.user == "kupo") {
isSystemUser = true;
inherit (cfg) group;
extraGroups = ["cardano-node"];
};
users.groups.kupo = mkIf (cfg.group == "kupo") {};

systemd.services.kupo = {
enable = true;
after = ["cardano-node.service" "ogmios.service"];
wantedBy = ["multi-user.target"];

script = escapeShellArgs (flatten [
["${cfg.package}/bin/kupo"]
["--workdir" cfg.workDir]
["--host" cfg.host]
["--port" cfg.port]
(optional (cfg.ogmiosHost == null) [
["--node-socket" cfg.nodeSocketPath]
["--node-config" cfg.nodeConfigPath]
])
(optional (cfg.ogmiosHost != null) [
["--ogmios-host" cfg.ogmiosHost]
["--ogmios-port" cfg.ogmiosPort]
])
(optional (cfg.hydraHost != null) [
["--hydra-host" cfg.hydraHost]
["--hydra-port" cfg.hydraPort]
])
["--since" cfg.since]
(map (m: ["--match" m]) cfg.matches)
(optional cfg.pruneUtxo "--prune-utxo")
cfg.extraArgs
]);

serviceConfig = {
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.workDir;
StateDirectory = lib.removePrefix "/var/lib/" cfg.workDir;
# Security
UMask = "0077";
AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = ["~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"];
};
extraArgs = mkOption {
description = "Extra arguments to kupo command.";
type = types.listOf types.str;
default = [];
};
};

config = mkIf cfg.enable {
assertions = [
{
assertion = lib.hasPrefix "/var/lib/" cfg.workDir;
message = "`workDir` must start with `/var/lib/`";
}
];

users.users.kupo = mkIf (cfg.user == "kupo") {
isSystemUser = true;
inherit (cfg) group;
extraGroups = ["cardano-node"];
};
users.groups.kupo = mkIf (cfg.group == "kupo") {};

systemd.services.kupo = {
enable = true;
after = ["cardano-node.service" "ogmios.service"];
wantedBy = ["multi-user.target"];

script = escapeShellArgs (flatten [
["${cfg.package}/bin/kupo"]
["--workdir" cfg.workDir]
["--host" cfg.host]
["--port" cfg.port]
(optional (cfg.ogmiosHost == null) [
["--node-socket" cfg.nodeSocketPath]
["--node-config" cfg.nodeConfigPath]
])
(optional (cfg.ogmiosHost != null) [
["--ogmios-host" cfg.ogmiosHost]
["--ogmios-port" cfg.ogmiosPort]
])
(optional (cfg.hydraHost != null) [
["--hydra-host" cfg.hydraHost]
["--hydra-port" cfg.hydraPort]
])
["--since" cfg.since]
(map (m: ["--match" m]) cfg.matches)
(optional cfg.pruneUtxo "--prune-utxo")
cfg.extraArgs
]);

serviceConfig = {
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.workDir;
StateDirectory = lib.removePrefix "/var/lib/" cfg.workDir;
# Security
UMask = "0077";
AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = ["~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"];
};
};
}
};
}