diff --git a/config/services/librespeed.nix b/config/services/librespeed.nix index a09cbd0..3490a04 100644 --- a/config/services/librespeed.nix +++ b/config/services/librespeed.nix @@ -1,7 +1,7 @@ -{ pkgs, ... }: { +{ ... }: { services.librespeed = { enable = true; - package = pkgs.librespeed-go; domain = "speed.kyouma.net"; + frontend.enable = true; }; } diff --git a/modules/default.nix b/modules/default.nix index d7bd395..c76c6dd 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,11 +1,5 @@ -{ ... }: { - imports = [ - ./deployment - ./graphical - ./librespeed - ./machine-type - ./nginx - ./ooklaserver - ./update-nixfiles - ]; +{ lib, ... }: let + mapModules = builtins.attrNames (lib.filterAttrs (_: type: type == "directory") (builtins.readDir ./.)); +in { + imports = builtins.map (dir: ./${dir}) mapModules; } diff --git a/modules/librespeed/default.nix b/modules/librespeed/default.nix index f5ad429..14e180f 100644 --- a/modules/librespeed/default.nix +++ b/modules/librespeed/default.nix @@ -7,37 +7,29 @@ in { 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. + configure librespeed to use TLS. ''; default = null; type = with types; nullOr nonEmptyStr; }; + downloadIPDB = mkOption { + description = '' + Whether to download the IP info database before starting librespeed. + Disable this if you want to use the Go implementation. + ''; + default = (!cfg.secrets ? "ipinfo_api_key"); + type = types.bool; + }; 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. @@ -46,55 +38,6 @@ in { 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. @@ -113,91 +56,156 @@ in { package ])); }; + frontend = { + enable = lib.mkEnableOption "LibreSpeed frontend."; + 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; + }; + pageTitle = mkOption { + description = "Title of the webpage."; + default = "LibreSpeed"; + type = types.str; + }; + useNginx = mkOption { + description = '' + Configure nginx for the LibreSpeed frontend. + This will only create a virtual host for the frontend and won't proxy all requests, + because the reported upload and download speeds are inaccurate if proxied. + ''; + default = cfg.domain != null; + type = types.bool; + }; + 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; + }; + }; + }); + }; + }; }; - config = lib.mkIf cfg.enable { + config = lib.mkIf cfg.enable (let + librespeedAssets = pkgs.runCommand "librespeed-assets" { + preferLocal = true; + + serversList = '' + function get_servers() { + return ${builtins.toJSON cfg.frontend.servers} + } + ''; + } '' + cp -r ${pkgs.librespeed-rust}/assets $out + chmod 666 $out/servers_list.js + cat >$out/servers_list.js <<<"$serversList" + substitute ${pkgs.librespeed-rust}/assets/index.html $out/index.html \ + --replace-fail "LibreSpeed Example" ${lib.escapeShellArg (lib.escapeXML cfg.frontend.pageTitle)} \ + --replace-fail "PUT@YOUR_EMAIL.HERE" ${lib.escapeShellArg (lib.escapeXML cfg.frontend.contactEmail)} \ + --replace-fail "TO BE FILLED BY DEVELOPER" ${lib.escapeShellArg (lib.escapeXML cfg.frontend.contactEmail)} + ''; + in { assertions = [ { - assertion = cfg.configureNginx -> cfg.domain != null; + assertion = cfg.frontend.useNginx -> cfg.domain != null; message = '' - `services.librespeed.configureNginx` requires `services.librespeed.domain` to be set. + `services.librespeed.frontend.useNginx` requires `services.librespeed.frontend.domain` to be set. ''; } ]; - networking.firewall = lib.mkIf (cfg.openFirewall) { + networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.settings.listen_port ]; }; - services.nginx.virtualHosts = lib.mkIf cfg.configureNginx { + services.nginx.virtualHosts = lib.mkIf (cfg.frontend.enable && cfg.frontend.useNginx) { ${cfg.domain} = { - locations."/" = { - proxyPass = "http://[::1]:${toString cfg.settings.listen_port}"; - recommendedProxySettings = true; - extraConfig = '' - proxy_cache off; - proxy_buffering off; - proxy_request_buffering off; - ''; - }; + locations."/".root = librespeedAssets; + locations."/backend/".extraConfig = "return 301 https://$host:${toString cfg.settings.listen_port}$request_uri;"; enableACME = true; forceSSL = true; - extraConfig = '' - gzip off; - ''; }; }; - security.acme.certs = lib.mkIf cfg.configureNginx { - ${cfg.domain} = {}; + security.acme.certs = lib.mkIf (cfg.domain != null) { + ${cfg.domain} = { + reloadServices = [ "librespeed.service" ]; + webroot = "/var/lib/acme/acme-challange"; + }; }; - services.librespeed.servers = lib.mkIf (cfg.domain != null) [ + services.librespeed.frontend.servers = lib.mkIf (cfg.frontend.enable && (cfg.domain != null)) [ { name = cfg.domain; - server = "//${cfg.domain}${lib.optionalString (!cfg.configureNginx) ":${toString cfg.settings.listen_port}"}"; + server = "//${cfg.domain}:${toString cfg.settings.listen_port}"; } ]; services.librespeed.settings = let inherit (lib) mkDefault mkIf; - - assets = pkgs.runCommand "librespeed-assets" { - preferLocal = true; - - serversList = '' - function get_servers() { - return ${builtins.toJSON cfg.servers} - } - ''; - } '' - cp -r ${pkgs.librespeed-rust}/assets $out - chmod 666 $out/servers_list.js - cat >$out/servers_list.js <<<"$serversList" - 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; - assets_path = assets; - bind_address = mkDefault (if cfg.configureNginx then "::1" else "::"); - listen_port = mkDefault 8989; - #base_url = mkDefault "backend"; - #worker_threads = mkDefault "auto"; + assets_path = if (cfg.frontend.enable && !cfg.frontend.useNginx) then librespeedAssets + else pkgs.writeTextDir "index.html" '' + + + + ''; - server_lat = 0; - server_lng = 0; - proxyprotocol_port = 0; - redact_ip_addresses = false; + bind_address = mkDefault "::"; + listen_port = mkDefault 8989; + base_url = mkDefault "backend"; + worker_threads = mkDefault "auto"; + + database_type = mkDefault "none"; + database_file = 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 ""; + tls_cert_file = if (cfg.domain != null) then (mkDefault "/run/credentials/librespeed.service/cert.pem") else (mkDefault ""); + tls_key_file = if (cfg.domain != null) then (mkDefault "/run/credentials/librespeed.service/key.pem") else (mkDefault ""); + + enable_tls = mkDefault (cfg.domain != null); - enable_tls = mkDefault false; - } // rec { - database_type = mkDefault "none"; - database_file = mkIf (database_type == "sqlite") (mkDefault "/var/lib/librespeed/speedtest.sqlite"); }; systemd.services = let @@ -255,7 +263,12 @@ in { DynamicUser = true; - #ExecStartPre = lib.mkIf (!cfg.secrets ? "ipinfo_api_key") "${lib.getExe cfg.package} --update-ipdb"; + LoadCredential = lib.mkIf (cfg.domain != null) [ + "cert.pem:${config.security.acme.certs.${cfg.domain}.directory}/cert.pem" + "key.pem:${config.security.acme.certs.${cfg.domain}.directory}/key.pem" + ]; + + ExecStartPre = lib.mkIf cfg.downloadIPDB "${lib.getExe cfg.package} --update-ipdb"; ExecStart = "${lib.getExe cfg.package} -c ${if (cfg.secrets == {}) then configFile else "\${RUNTIME_DIRECTORY%%:*}/config.toml"}"; WorkingDirectory = "/var/cache/librespeed"; RuntimeDirectory = "librespeed"; @@ -287,7 +300,7 @@ in { }; }; }; - }; + }); meta.maintainers = with lib.maintainers; [ snaki ]; } diff --git a/pkgs/librespeed-rust/default.nix b/pkgs/librespeed-rust/default.nix index a09896b..9d26af1 100644 --- a/pkgs/librespeed-rust/default.nix +++ b/pkgs/librespeed-rust/default.nix @@ -4,12 +4,12 @@ rustPlatform, }: let - version = "1.3.2"; + version = "unstable-2024-09-28"; src = fetchFromGitHub { owner = "librespeed"; repo = "speedtest-rust"; - rev = "refs/tags/v${version}"; - hash = "sha256-z3lORjjJ89o+Du4mvKGydwxHU6Ra2jU5ue5Zsl/oIfY="; + rev = "a74f25d07da3eb665ce806e015c537264f7254c9"; + hash = "sha256-+G1DFHQONXXg/5apSBlBkRvuLT4qCJaeFnQSLWt0CD0="; }; in rustPlatform.buildRustPackage {