199 lines
6.5 KiB
Nix
199 lines
6.5 KiB
Nix
{ self, name }:
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.tensamin.${name};
|
|
isOmikron = name == "omikron";
|
|
inherit (lib) mkOption types;
|
|
cert = if cfg.acmeCertDir != null then "${cfg.acmeCertDir}/fullchain.pem" else cfg.certFile;
|
|
key = if cfg.acmeCertDir != null then "${cfg.acmeCertDir}/key.pem" else cfg.keyFile;
|
|
setup = pkgs.writeShellScript "${name}-setup" ''
|
|
set -eu
|
|
umask 077
|
|
install -d -m 0750 -o ${name} -g ${name} ${lib.escapeShellArg cfg.stateDir}
|
|
cd ${lib.escapeShellArg cfg.stateDir}
|
|
install -d -m 0700 -o ${name} -g ${name} certs
|
|
install -m 0644 -o ${name} -g ${name} ${
|
|
lib.escapeShellArg (if cert == null then "" else cert)
|
|
} certs/cert.pem
|
|
${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \
|
|
-in ${lib.escapeShellArg (if key == null then "" else key)} -out certs/key.pem
|
|
chown ${name}:${name} certs/key.pem
|
|
chmod 0600 certs/key.pem
|
|
${lib.optionalString isOmikron ''
|
|
install -m 0644 -o ${name} -g ${name} ${lib.escapeShellArg cfg.omegaTrustFile} omega.mpkb
|
|
''}
|
|
${lib.optionalString (cfg.identityFile != null) ''
|
|
install -m 0600 -o ${name} -g ${name} ${lib.escapeShellArg cfg.identityFile} ${name}.mk
|
|
''}
|
|
${lib.optionalString (cfg.publicIdentityFile != null) ''
|
|
install -m 0644 -o ${name} -g ${name} ${lib.escapeShellArg cfg.publicIdentityFile} ${name}.mpkb
|
|
''}
|
|
'';
|
|
in
|
|
{
|
|
options.tensamin.${name} = {
|
|
enable = lib.mkEnableOption "Tensamin ${name}";
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = self.packages.${pkgs.stdenv.hostPlatform.system}.${name};
|
|
description = "Central ${name} package, or an explicit replacement.";
|
|
};
|
|
stateDir = mkOption {
|
|
type = types.str;
|
|
default = "/var/lib/${name}";
|
|
description = "Persistent working directory, including identities and certificates.";
|
|
};
|
|
bindAddress = mkOption {
|
|
type = types.str;
|
|
default = "0.0.0.0";
|
|
};
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 443;
|
|
};
|
|
openFirewall = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
certFile = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Runtime TLS certificate path. Set together with keyFile.";
|
|
};
|
|
keyFile = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Runtime TLS private key path. Never copied into the Nix store.";
|
|
};
|
|
acmeCertDir = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Runtime directory containing fullchain.pem and key.pem. Restart the service after renewal.";
|
|
};
|
|
identityFile = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Existing private keyring to install at startup, or null to retain or generate state.";
|
|
};
|
|
publicIdentityFile = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Matching public key bundle to install at startup.";
|
|
};
|
|
environment = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = { };
|
|
description = "Additional non-secret runtime settings. Listener options take precedence.";
|
|
};
|
|
environmentFiles = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
description =
|
|
if isOmikron then
|
|
"Runtime environment files, including optional LiveKit credentials."
|
|
else
|
|
"Runtime environment files. Supply DB_URL here; identities are file-based.";
|
|
};
|
|
}
|
|
// lib.optionalAttrs isOmikron {
|
|
id = mkOption {
|
|
type = types.ints.positive;
|
|
description = "Omikron ID assigned by Omega.";
|
|
};
|
|
omegaHost = mkOption {
|
|
type = types.str;
|
|
default = "tensamin.net";
|
|
};
|
|
omegaPort = mkOption {
|
|
type = types.port;
|
|
default = 9187;
|
|
};
|
|
omegaTrustFile = mkOption {
|
|
type = types.str;
|
|
description = "Runtime path to Omega's trusted public key bundle.";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion =
|
|
(cfg.acmeCertDir != null && cfg.certFile == null && cfg.keyFile == null)
|
|
|| (cfg.acmeCertDir == null && cfg.certFile != null && cfg.keyFile != null);
|
|
message = "tensamin.${name}: set either acmeCertDir or both certFile and keyFile.";
|
|
}
|
|
{
|
|
assertion = (cfg.identityFile == null) == (cfg.publicIdentityFile == null);
|
|
message = "tensamin.${name}: identityFile and publicIdentityFile must be supplied together.";
|
|
}
|
|
{
|
|
assertion = lib.hasPrefix "/" cfg.stateDir;
|
|
message = "tensamin.${name}.stateDir must be absolute.";
|
|
}
|
|
];
|
|
users.users.${name} = {
|
|
isSystemUser = true;
|
|
group = name;
|
|
home = cfg.stateDir;
|
|
};
|
|
users.groups.${name} = { };
|
|
systemd.tmpfiles.rules = [ "d ${cfg.stateDir} 0750 ${name} ${name} -" ];
|
|
systemd.services.${name} = {
|
|
description = "Tensamin ${name}";
|
|
wantedBy = [ "multi-user.target" ];
|
|
wants = [ "network-online.target" ];
|
|
after = [ "network-online.target" ];
|
|
environment =
|
|
cfg.environment
|
|
// {
|
|
BIND_ADDRESS = cfg.bindAddress;
|
|
}
|
|
// (
|
|
if isOmikron then
|
|
{
|
|
RHO_PORT = toString cfg.port;
|
|
OMEGA_HOST = cfg.omegaHost;
|
|
OMEGA_PORT = toString cfg.omegaPort;
|
|
ID = toString cfg.id;
|
|
}
|
|
else
|
|
{ PORT = toString cfg.port; }
|
|
);
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = name;
|
|
Group = name;
|
|
WorkingDirectory = cfg.stateDir;
|
|
ExecStart = "${cfg.package}/bin/${name}";
|
|
ExecStartPre = [ "+${setup}" ];
|
|
EnvironmentFile = cfg.environmentFiles;
|
|
Restart = "always";
|
|
RestartSec = "5s";
|
|
UMask = "0077";
|
|
AmbientCapabilities = lib.optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
|
|
CapabilityBoundingSet = lib.optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
PrivateTmp = true;
|
|
NoNewPrivileges = true;
|
|
ReadWritePaths = [ cfg.stateDir ];
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
LockPersonality = true;
|
|
MemoryDenyWriteExecute = true;
|
|
};
|
|
};
|
|
networking.firewall = lib.mkIf cfg.openFirewall {
|
|
allowedTCPPorts = [ cfg.port ];
|
|
allowedUDPPorts = [ cfg.port ];
|
|
};
|
|
};
|
|
}
|