diff --git a/config/hosts/seras/configuration.nix b/config/hosts/seras/configuration.nix index 423281f..9d4b4af 100644 --- a/config/hosts/seras/configuration.nix +++ b/config/hosts/seras/configuration.nix @@ -20,4 +20,11 @@ "2a0f:be01:0:100::169/128" ]; }; + + kyouma.ooklaserver = { + enable = true; + openFirewall = true; + domain = "speedtest.kyouma.net"; + settings.openSSL.server.minimumTLSProtocol = "1.3"; + }; } diff --git a/modules/default.nix b/modules/default.nix index b207a3b..4884415 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -4,6 +4,7 @@ ./graphical ./machine-type ./nginx + ./ooklaserver ./update-nixfiles ]; } diff --git a/modules/ooklaserver/default.nix b/modules/ooklaserver/default.nix new file mode 100644 index 0000000..6232541 --- /dev/null +++ b/modules/ooklaserver/default.nix @@ -0,0 +1,165 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.kyouma.ooklaserver; +in { + options = { + kyouma.ooklaserver = with lib; { + enable = mkEnableOption "ookla speedtest server"; + package = mkPackageOption pkgs "ooklaserver" {}; + domain = mkOption { + description = "Domain to use."; + default = null; + type = types.str; + }; + 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 { + users.users.ooklaserver = { + description = "OoklaServer User"; + group = "ooklaserver"; + createHome = false; + isSystemUser = true; + }; + users.groups.ooklaserver = {}; + security.acme.certs.${cfg.domain} = { + group = "ooklaserver"; + reloadServices = [ "ooklaserver.service" ]; + webroot = "/var/lib/acme/acme-challenge"; + }; + + kyouma.ooklaserver.settings = with lib; { + 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 = "${config.security.acme.certs.${cfg.domain}.directory}/cert.pem"; + privateKeyFile = "${config.security.acme.certs.${cfg.domain}.directory}/key.pem"; + minimumTLSProtocol = mkDefault "1.2"; + }; + logging.loggers.app = { + name = mkDefault "Application"; + channel = { + class = mkDefault "ConsoleChannel"; + pattern = mkDefault "[%p] %t"; + }; + level = mkDefault "information"; + }; + }; + networking.firewall = lib.mkIf cfg.openFirewall { + allowedUDPPorts = cfg.udpPorts; + allowedTCPPorts = cfg.tcpPorts; + }; + 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"; + + 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; + }; + }; + }; +}