server/nix/module.nix
2025-04-15 20:31:25 +02:00

200 lines
5.4 KiB
Nix

{
config,
lib,
pkgs,
...
}: let
cfg = config.services.versia-server;
configFormat = pkgs.formats.toml {};
name = "versia-server";
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf;
in {
options = {
services.versia-server = {
enable = mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable the Versia Server services.
'';
};
dataDir = mkOption {
type = lib.types.path;
default = "/var/lib/${name}";
description = ''
Directory where the server will store its data.
'';
};
user = mkOption {
type = lib.types.str;
default = name;
description = ''
User under which the server will run.
'';
};
group = mkOption {
type = lib.types.str;
default = name;
description = ''
Group under which the server will run.
'';
};
nodes = {
api = mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
configOverrides = mkOption {
type = lib.types.submodule {
freeformType = configFormat.type;
options = {};
};
default = {};
description = "Overrides for the node's configuration file.";
};
};
});
};
worker = mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
configOverrides = mkOption {
type = lib.types.submodule {
freeformType = configFormat.type;
options = {};
};
default = {};
description = "Overrides for the node's configuration file.";
};
};
});
};
};
config = mkOption {
type = lib.types.submodule {
freeformType = configFormat.type;
options = {};
};
description = "Contents of the config file, which is serialized to TOML. Check the Versia Server documentation for information on its contents.";
};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.nodes.api != [];
message = "At least one API node must be defined.";
}
{
assertion = cfg.nodes.worker != [];
message = "At least one worker node must be defined.";
}
];
systemd.services =
lib.mapAttrs' (nodeName: node: let
type = "api";
exe = lib.getExe pkgs.versia-server;
config = lib.recursiveUpdate cfg.config node.configOverrides;
configFile = configFormat.generate "config-${nodeName}.toml" config;
in
lib.nameValuePair "${name}-${type}-${nodeName}" {
description = "Versia Server ${nodeName} (${type})";
wantedBy = ["versia-server-root.target"];
partOf = ["versia-server-root.target"];
serviceConfig = {
ExecStart = "${exe}";
Type = "simple";
Restart = "always";
User = cfg.user;
Group = cfg.group;
StateDirectory = "${name}";
StateDirectoryMode = "0700";
RuntimeDirectory = "${name}";
RuntimeDirectoryMode = "0700";
# Set the working directory to the data directory
WorkingDirectory = cfg.dataDir;
StandardOutput = "journal";
StandardError = "journal";
SyslogIdentifier = "${name}";
Environment = [
"CONFIG_LOCATION=${configFile}"
];
};
}) (cfg.nodes.api)
// lib.mapAttrs' (nodeName: node: let
type = "worker";
exe = lib.getExe pkgs.versia-server-worker;
config = lib.recursiveUpdate cfg.config node.configOverrides;
configFile = configFormat.generate "config-${nodeName}.toml" config;
in
lib.nameValuePair "${name}-${type}-${nodeName}" {
description = "Versia Server ${nodeName} (${type})";
wantedBy = ["versia-server-root.target"];
partOf = ["versia-server-root.target"];
serviceConfig = {
ExecStart = "${exe}";
Type = "simple";
Restart = "always";
User = cfg.user;
Group = cfg.group;
StateDirectory = "${name}";
StateDirectoryMode = "0700";
RuntimeDirectory = "${name}";
RuntimeDirectoryMode = "0700";
# Set the working directory to the data directory
WorkingDirectory = cfg.dataDir;
StandardOutput = "journal";
StandardError = "journal";
SyslogIdentifier = "${name}";
Environment = [
"CONFIG_LOCATION=${configFile}"
];
};
}) (cfg.nodes.worker);
systemd.targets.versia-server-root = {
description = "Versia Server root target, starts and stop all the child nodes.";
wantedBy = ["multi-user.target"];
};
systemd.tmpfiles.rules = ["d ${cfg.dataDir} - - - - ${cfg.user} ${cfg.group}"];
users = {
groups = {
"${cfg.group}" = {};
};
users = {
"${cfg.user}" = {
isSystemUser = true;
group = cfg.group;
home = cfg.dataDir;
packages = [pkgs.versia-server pkgs.versia-server-worker];
};
};
};
};
}