Centralize Tensamin packages modules and release automation
This commit is contained in:
parent
423ee32a37
commit
123205c97d
28 changed files with 32292 additions and 10 deletions
199
modules/server.nix
Normal file
199
modules/server.nix
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
{ 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 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue