From 7b2b29aa9f1bd7d007100606cd25ca8ad8308df2 Mon Sep 17 00:00:00 2001 From: emily Date: Tue, 24 Sep 2024 23:56:26 +0200 Subject: [PATCH] librespeed: test module --- config/hosts/seras/configuration.nix | 1 + config/services/librespeed.nix | 6 + modules/default.nix | 1 + modules/librespeed/default.nix | 277 +++++++++++++++++++++++++++ pkgs/librespeed-rust/default.nix | 36 ++++ pkgs/overlay.nix | 1 + 6 files changed, 322 insertions(+) create mode 100644 config/services/librespeed.nix create mode 100644 modules/librespeed/default.nix create mode 100644 pkgs/librespeed-rust/default.nix diff --git a/config/hosts/seras/configuration.nix b/config/hosts/seras/configuration.nix index 9d4b4af..02f74fe 100644 --- a/config/hosts/seras/configuration.nix +++ b/config/hosts/seras/configuration.nix @@ -8,6 +8,7 @@ ../../services/nginx.nix ../../services/hydra ../../services/update-nixfiles.nix + ../../services/librespeed.nix ]; boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; diff --git a/config/services/librespeed.nix b/config/services/librespeed.nix new file mode 100644 index 0000000..8ace17d --- /dev/null +++ b/config/services/librespeed.nix @@ -0,0 +1,6 @@ +{ ... }: { + services.librespeed = { + enable = true; + domain = "speed.kyouma.net"; + }; +} diff --git a/modules/default.nix b/modules/default.nix index 4884415..d7bd395 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -2,6 +2,7 @@ imports = [ ./deployment ./graphical + ./librespeed ./machine-type ./nginx ./ooklaserver diff --git a/modules/librespeed/default.nix b/modules/librespeed/default.nix new file mode 100644 index 0000000..2214525 --- /dev/null +++ b/modules/librespeed/default.nix @@ -0,0 +1,277 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.librespeed; +in { + options.services.librespeed = let + inherit (lib) mkOption types; + in { + enable = lib.mkEnableOption "LibreSpeed server"; + package = lib.mkPackageOption pkgs "librespeed-rust" {}; + configureNginx = mkOption { + description = "Configure nginx as a reverse proxy for LibreSpeed."; + default = if (cfg.domain != null) then true else false; + type = types.bool; + }; + contactEmail = mkOption { + description = "Email address listed in the privacy policy."; + default = if (cfg.domain != null) then "webmaster@${cfg.domain}" else "webmaster@${config.networking.fqdn}"; + type = types.str; + }; + domain = mkOption { + description = '' + If not `null`, this will add an entry to `services.librespeed.servers` and + configure an nginx reverse proxy at the specified FQDN, unless explicitly disabled. + ''; + default = null; + type = with types; nullOr nonEmptyStr; + }; + openFirewall = mkOption { + description = '' + Whether to open the firewall for the specified port. + This is only necessary if no reverse proxy is used. + ''; + default = false; + type = types.bool; + }; + pageTitle = mkOption { + description = "Title of the webpage."; + default = "LibreSpeed"; + type = types.str; + }; + secrets = mkOption { + description = '' + Attribute set of filesystem paths. + The contents of the specified paths will be read at service start time and merged with the attributes provided in `settings`. + ''; + default = {}; + type = with types; nullOr (attrsOf path); + }; + servers = mkOption { + description = "LibreSpeed servers that should apper in the server list."; + type = types.listOf (types.submodule { + options = let + inherit (types) nonEmptyStr; + in { + name = mkOption { + description = "Name shown in the server list."; + type = nonEmptyStr; + }; + server = mkOption { + description = "URL to the server. You may use `//` instead of `http://` or `https://`."; + type = nonEmptyStr; + }; + dlURL = mkOption { + description = '' + URL path to download test on this server. + Append `.php` to the default value if the server uses the php implementation. + ''; + default = "backend/garbage"; + type = nonEmptyStr; + }; + ulURL = mkOption { + description = '' + URL path to upload test on this server. + Append `.php` to the default value if the server uses the php implementation. + ''; + default = "backend/empty"; + type = nonEmptyStr; + }; + pingURL = mkOption { + description = '' + URL path to latency/jitter test on this server. + Append `.php` to the default value if the server uses the php implementation. + ''; + default = "backend/empty"; + type = nonEmptyStr; + }; + getIpURL = mkOption { + description = '' + URL path to IP lookup on this server. + Append `.php` to the default value if the server uses the php implementation. + ''; + default = "backend/getIP"; + type = nonEmptyStr; + }; + }; + }); + }; + settings = mkOption { + description = '' + LibreSpeed configuration written as Nix expression. + All values set to `null` will be excluded from the evaluated config. + This is useful if you want to omit certain defaults when using a different LibreSpeed implementation. + + See [github.com/librespeed][librespeed] for configuration help. + + [librespeed]: https://github.com/librespeed/speedtest-rust + ''; + default = {}; + type = with types; nullOr (attrsOf (oneOf [ + bool + int + str + null + ])); + }; + }; + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.configureNginx -> cfg.domain != null; + message = '' + `services.librespeed.configureNginx` requires `services.librespeed.domain` to be set. + ''; + } + ]; + + networking.firewall = lib.mkIf (cfg.openFirewall) { + allowedTCPPorts = [ cfg.settings.listen_port ]; + }; + services.nginx.virtualHosts = lib.mkIf cfg.configureNginx { + ${cfg.domain} = { + locations."/" = { + proxyPass = "http://${cfg.settings.bind_address}:${toString cfg.settings.listen_port}"; + recommendedProxySettings = true; + }; + enableACME = true; + forceSSL = true; + }; + }; + security.acme.certs = lib.mkIf cfg.configureNginx { + ${cfg.domain} = {}; + }; + + services.librespeed.servers = lib.mkIf (cfg.domain != null) [ + { + name = cfg.domain; + server = "https://${cfg.domain}"; + } + ]; + services.librespeed.settings = let + inherit (lib) mkDefault mkIf; + + assets = pkgs.runCommand "librespeed-assets" { + preferLocal = true; + + serverList = '' + function get_servers() { + return ${builtins.toJSON cfg.servers} + } + ''; + } '' + cp -r ${pkgs.librespeed-rust}/assets $out + cat >$out/server_list.js <<<"$serverList" + substitute ${pkgs.librespeed-rust}/assets/index.html $out/index.html \ + --replace-fail "LibreSpeed Example" ${lib.escapeShellArg (lib.escapeXML cfg.pageTitle)} \ + --replace-fail "PUT@YOUR_EMAIL.HERE" ${lib.escapeShellArg (lib.escapeXML cfg.contactEmail)} \ + --replace-fail "TO BE FILLED BY DEVELOPER" ${lib.escapeShellArg (lib.escapeXML cfg.contactEmail)} + ''; + in { + speed_test_dir = assets; + bind_address = mkDefault (if cfg.configureNginx then "127.0.0.1" else "0.0.0.0"); + listen_port = mkDefault 8989; + base_url = mkDefault "backend"; + worker_threads = mkDefault "auto"; + + database_type = mkDefault "none"; + database_file = mkIf (cfg.settings.database_type == "sqlite") mkDefault "/var/lib/librespeed/speedtest.sqlite"; + + #librespeed-rust will fail to start if the following config parameters are omitted. + ipinfo_api_key = mkIf (!cfg.secrets ? "ipinfo_api_key") ""; + stats_password = mkIf (!cfg.secrets ? "stats_password") ""; + tls_key_file = mkDefault ""; + tls_cet_file = mkDefault ""; + + enable_tls = mkDefault false; + }; + + systemd.services = let + configFile = let + anyToString = arg: if (lib.isBool arg) then + lib.boolToString arg + else if (lib.isInt arg) then + toString arg + else "\"${lib.escape [ "\"" ] (toString arg)}\""; + in + with lib; pipe cfg.settings [ + (filterAttrs (_: val: val != null)) + (mapAttrs (name: val: "${path}=${anyToString val}")) + (concatLines attrValues) + (pkgs.writeText "${cfg.package.name}-config.toml") + ]; + in { + librespeed-secrets = lib.mkIf (cfg.secrets != {}) { + description = "LibreSpeed secret helper"; + + ExecStart = let + script = pkgs.writeShellApplication { + name = "librespeed-secrets"; + runtimeInputs = [ pkgs.coreutils ]; + text = '' + cp ${configFile} ''${RUNTIME_DIRECTORY%%:*}/config.toml + '' + lib.pipe cfg.secrets [ + (lib.mapAttrs (name: file: '' + cat >>''${RUNTIME_DIRECTORY%%:*}/config.toml <