nixfiles/modules/ooklaserver/default.nix
2024-09-12 00:39:29 +02:00

169 lines
5.4 KiB
Nix

{ config, lib, pkgs, ... }:
let
cfg = config.kyouma.ooklaserver;
in {
options = {
kyouma.ooklaserver = let
inherit (lib) mkOption types;
in {
enable = lib.mkEnableOption "ookla speedtest server";
package = lib.mkPackageOption pkgs "ooklaserver" {};
domain = mkOption {
description = "Domain to use.";
default = null;
type = with types; nullOr nonEmptyStr;
};
openFirewall = mkOption {
description = "Whether to open the firewall for the specified ports.";
default = false;
type = types.bool;
};
tcpPorts = mkOption {
description = ''
The server listens on TCP port 5060 and 8080 by default. These ports are required for
speedtest.net servers, although more can be added.
'';
default = [ 5060 8080 ];
type = with types; listOf port;
};
udpPorts = mkOption {
description = ''
The server listens on UDP port 5060 and 8080 by default. These ports are required for
speedtest.net servers, although more can be added.
'';
default = [ 5060 8080 ];
type = with types; listOf port;
};
settings = mkOption {
description = ''
OoklaServer configuration written as Nix expression.
Comma seperated values should be written as list.
'';
default = {};
type = with lib.types; let
valueType = nullOr (oneOf [
bool
int
str
(attrsOf valueType)
(listOf (oneOf [ port str ]))
]);
in valueType;
};
};
};
config = lib.mkIf cfg.enable {
security.acme.certs.${cfg.domain} = {
reloadServices = [ "ooklaserver.service" ];
webroot = "/var/lib/acme/acme-challenge";
};
networking.firewall = lib.mkIf cfg.openFirewall {
allowedUDPPorts = cfg.udpPorts;
allowedTCPPorts = cfg.tcpPorts;
};
kyouma.ooklaserver.settings = let
inherit (lib) mkDefault;
in {
OoklaServer = {
inherit (cfg) tcpPorts udpPorts;
enableAutoUpdate = false;
ssl.useLetsEncrypt = false;
useIPv6 = mkDefault true;
allowedDomains = mkDefault [ "*.ookla.com" "*.speedtest.net" ];
userAgentFilterEnabled = mkDefault true;
workerThreadPool = {
capacity = mkDefault 30000;
stackSizeBytes = mkDefault 102400;
};
ipTracking = {
gcIntervalMinutes = mkDefault 5;
maxIdleAgeMinutes = mkDefault 35;
slidingWindowBucketLengthMinutes = mkDefault 5;
metricTopIpCount = mkDefault 5;
maxConnPerIp = mkDefault 500;
maxConnPerBucketPerIp = mkDefault 20000;
};
clientAuthToken.denyInvalid = mkDefault true;
websocket.frameSizeLimitBytes = mkDefault 5242880;
http.maxHeadersSize = mkDefault 65536;
};
openSSL.server = {
certificateFile = "/run/credentials/${config.systemd.services.ooklaserver.name}/cert.pem";
privateKeyFile = "/run/credentials/${config.systemd.services.ooklaserver.name}/key.pem";
minimumTLSProtocol = mkDefault "1.2";
};
logging.loggers.app = {
name = mkDefault "Application";
channel = {
class = mkDefault "ConsoleChannel";
pattern = mkDefault "[%p] %t";
};
level = mkDefault "information";
};
};
systemd.services.ooklaserver = let
configFile = let
anyToString = arg: if (lib.isBool arg) then
lib.boolToString arg
else if (lib.isList arg) then
lib.concatStringsSep "," (map (val: toString val) arg)
else toString arg;
in
with lib; lib.pipe cfg.settings [
(mapAttrsRecursive (path: val: "${concatStringsSep "." path} = ${anyToString val}"))
(collect isString)
(concatLines)
(pkgs.writeTextDir "bin/OoklaServer.properties")
];
packageWithCfg = pkgs.symlinkJoin {
name = "${cfg.package.name}-with-config";
paths = [ cfg.package configFile ];
};
in {
description = "Ookla speedtest server daemon";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "simple";
Restart = "always";
User = "ooklaserver";
Group = "ooklaserver";
DynamicUser = true;
LoadCredential = [
"cert.pem:${config.security.acme.certs.${cfg.domain}.directory}/cert.pem"
"key.pem:${config.security.acme.certs.${cfg.domain}.directory}/key.pem"
];
ExecStart = "${packageWithCfg}/bin/OoklaServer";
WorkingDirectory = packageWithCfg;
SyslogIdentifier = "ooklaserver";
ReadOnlyPaths = [ packageWithCfg ];
RestrictSUIDSGID = true;
RestrictNamespaces = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
ProtectSystem = "strict";
ProtectHome = true;
ProtectProc = "invisible";
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
SystemCallErrorNumber = "EPERM";
LockPersonality = true;
NoNewPrivileges = true;
};
};
};
}