librespeed: Fixes
This commit is contained in:
parent
f091338a53
commit
a1f06c7b22
6 changed files with 424 additions and 294 deletions
|
@ -7,6 +7,7 @@
|
|||
../../services/nginx.nix
|
||||
../../services/uptime-kuma.nix
|
||||
../../services/vaultwarden.nix
|
||||
../../services/librespeed.nix
|
||||
./disko.nix
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
../../services/nginx.nix
|
||||
../../services/hydra
|
||||
../../services/update-nixfiles.nix
|
||||
../../services/librespeed.nix
|
||||
];
|
||||
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{ pkgs, ... }: {
|
||||
{ ... }: {
|
||||
services.librespeed = {
|
||||
enable = true;
|
||||
package = pkgs.librespeed-go;
|
||||
openFirewall = true;
|
||||
domain = "speed.kyouma.net";
|
||||
frontend.enable = true;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,43 +1,44 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.librespeed;
|
||||
in {
|
||||
options.services.librespeed = let
|
||||
in
|
||||
{
|
||||
options.services.librespeed =
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
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");
|
||||
defaultText = lib.literalExpression ''!(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,12 +47,90 @@ in {
|
|||
default = { };
|
||||
type = with types; nullOr (attrsOf path);
|
||||
};
|
||||
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 [
|
||||
(nullOr bool)
|
||||
int
|
||||
str
|
||||
package
|
||||
])
|
||||
);
|
||||
};
|
||||
frontend = {
|
||||
enable = lib.mkEnableOption ''
|
||||
Enables the LibreSpeed frontend and adds a nginx virtual host if
|
||||
not explicetly disabled and `services.librespeed.domain` is not `null`.
|
||||
'';
|
||||
contactEmail = mkOption {
|
||||
description = "Email address listed in the privacy policy.";
|
||||
default =
|
||||
if (cfg.domain != null) then "webmaster@${cfg.domain}" else "webmaster@${config.networking.fqdn}";
|
||||
defaultText = lib.literalExpression ''
|
||||
if (config.services.librespeed.domain != null) then
|
||||
"webmaster@''${config.services.librespeed.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;
|
||||
defaultText = lib.literalExpression "config.services.librespeed.domain != null";
|
||||
type = types.bool;
|
||||
};
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Override default settings of the speedtest web client.
|
||||
See [speedtest_worker.js][link] for a list of possible values.
|
||||
|
||||
[link]: https://github.com/librespeed/speedtest/blob/master/speedtest_worker.js#L39
|
||||
'';
|
||||
default = {
|
||||
telemetry_level = "basic";
|
||||
};
|
||||
type =
|
||||
with types;
|
||||
nullOr (
|
||||
attrsOf (oneOf [
|
||||
bool
|
||||
int
|
||||
str
|
||||
float
|
||||
])
|
||||
);
|
||||
};
|
||||
servers = mkOption {
|
||||
description = "LibreSpeed servers that should apper in the server list.";
|
||||
type = types.listOf (types.submodule {
|
||||
options = let
|
||||
type = types.listOf (
|
||||
types.submodule {
|
||||
options =
|
||||
let
|
||||
inherit (types) nonEmptyStr;
|
||||
in {
|
||||
in
|
||||
{
|
||||
name = mkOption {
|
||||
description = "Name shown in the server list.";
|
||||
type = nonEmptyStr;
|
||||
|
@ -93,148 +172,195 @@ in {
|
|||
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 [
|
||||
(nullOr bool)
|
||||
int
|
||||
str
|
||||
package
|
||||
]));
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.configureNginx -> cfg.domain != null;
|
||||
message = ''
|
||||
`services.librespeed.configureNginx` requires `services.librespeed.domain` to be set.
|
||||
'';
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable (
|
||||
let
|
||||
librespeedAssets =
|
||||
pkgs.runCommand "librespeed-assets"
|
||||
(
|
||||
let
|
||||
mapValue =
|
||||
arg:
|
||||
if (lib.isBool arg) then
|
||||
lib.boolToString arg
|
||||
else if ((lib.isInt arg) || (lib.isFloat arg)) then
|
||||
toString arg
|
||||
else
|
||||
"\"${lib.escape [ "\"" ] (toString arg)}\"";
|
||||
|
||||
mapSettings = lib.pipe cfg.frontend.settings [
|
||||
(lib.mapAttrs (name: val: " s.setParameter(\"${lib.escape [ "\"" ] name}\",${mapValue val});"))
|
||||
(lib.attrValues)
|
||||
(lib.concatLines)
|
||||
];
|
||||
|
||||
networking.firewall = lib.mkIf (cfg.openFirewall) {
|
||||
allowedTCPPorts = [ cfg.settings.listen_port ];
|
||||
};
|
||||
services.nginx.virtualHosts = lib.mkIf cfg.configureNginx {
|
||||
${cfg.domain} = {
|
||||
locations."/" = {
|
||||
proxyPass = "http://[::1]:${toString cfg.settings.listen_port}";
|
||||
recommendedProxySettings = true;
|
||||
extraConfig = ''
|
||||
proxy_cache off;
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
'';
|
||||
};
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
gzip off;
|
||||
'';
|
||||
};
|
||||
};
|
||||
security.acme.certs = lib.mkIf cfg.configureNginx {
|
||||
${cfg.domain} = {};
|
||||
};
|
||||
|
||||
services.librespeed.servers = lib.mkIf (cfg.domain != null) [
|
||||
in
|
||||
{
|
||||
name = cfg.domain;
|
||||
server = "//${cfg.domain}${lib.optionalString (!cfg.configureNginx) ":${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}
|
||||
return ${builtins.toJSON cfg.frontend.servers}
|
||||
}
|
||||
function override_settings () {
|
||||
${mapSettings}
|
||||
}
|
||||
'';
|
||||
} ''
|
||||
}
|
||||
)
|
||||
''
|
||||
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)}
|
||||
--replace-fail "s.setParameter(\"telemetry_level\",\"basic\"); //enable telemetry" "override_settings();" \
|
||||
--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 {
|
||||
#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";
|
||||
in
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.frontend.useNginx -> cfg.domain != null;
|
||||
message = ''
|
||||
`services.librespeed.frontend.useNginx` requires `services.librespeed.frontend.domain` to be set.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
server_lat = 0;
|
||||
server_lng = 0;
|
||||
proxyprotocol_port = 0;
|
||||
redact_ip_addresses = false;
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.settings.listen_port ];
|
||||
};
|
||||
services.nginx.virtualHosts = lib.mkIf (cfg.frontend.enable && cfg.frontend.useNginx) {
|
||||
${cfg.domain} = {
|
||||
locations."/".root = librespeedAssets;
|
||||
locations."/backend/" = {
|
||||
proxyPass = "http://${cfg.settings.bind_address}:${toString cfg.settings.listen_port}/backend/";
|
||||
extraConfig = ''
|
||||
# add_header Cache-Control 'no-store, no-cache, max-age=0, no-transform';
|
||||
# add_header Last-Modified $date_gmt;
|
||||
if_modified_since off;
|
||||
expires off;
|
||||
etag off;
|
||||
|
||||
access_log off;
|
||||
gzip off;
|
||||
log_not_found off;
|
||||
server_tokens off;
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
sendfile on;
|
||||
client_max_body_size 50M;
|
||||
proxy_read_timeout 999;
|
||||
proxy_buffers 16 128k;
|
||||
'';
|
||||
};
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
};
|
||||
};
|
||||
security.acme.certs = lib.mkIf (cfg.domain != null) {
|
||||
${cfg.domain} = lib.mkIf (!cfg.frontend.useNginx) {
|
||||
reloadServices = [ "librespeed.service" ];
|
||||
webroot = "/var/lib/acme/acme-challenge";
|
||||
};
|
||||
};
|
||||
|
||||
services.librespeed.frontend.servers = lib.mkIf (cfg.frontend.enable && (cfg.domain != null)) [
|
||||
{
|
||||
name = cfg.domain;
|
||||
server = "//${cfg.domain}${lib.optionalString (!cfg.frontend.useNginx) ":${toString cfg.settings.listen_port}"}";
|
||||
}
|
||||
];
|
||||
|
||||
services.librespeed.settings =
|
||||
let
|
||||
inherit (lib) mkDefault mkIf;
|
||||
in
|
||||
{
|
||||
assets_path =
|
||||
if (cfg.frontend.enable && !cfg.frontend.useNginx) then
|
||||
librespeedAssets
|
||||
else
|
||||
pkgs.writeTextDir "index.html" "";
|
||||
|
||||
bind_address = mkDefault "127.0.0.1";
|
||||
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 && !cfg.frontend.useNginx) then
|
||||
(mkDefault "/run/credentials/librespeed.service/cert.pem")
|
||||
else
|
||||
(mkDefault "");
|
||||
tls_key_file =
|
||||
if (cfg.domain != null && !cfg.frontend.useNginx) then
|
||||
(mkDefault "/run/credentials/librespeed.service/key.pem")
|
||||
else
|
||||
(mkDefault "");
|
||||
|
||||
enable_tls = mkDefault false;
|
||||
} // rec {
|
||||
database_type = mkDefault "none";
|
||||
database_file = mkIf (database_type == "sqlite") (mkDefault "/var/lib/librespeed/speedtest.sqlite");
|
||||
enable_tls = mkDefault (cfg.domain != null && !cfg.frontend.useNginx);
|
||||
};
|
||||
|
||||
systemd.services = let
|
||||
configFile = let
|
||||
mapValue = arg: if (lib.isBool arg) then
|
||||
systemd.services =
|
||||
let
|
||||
configFile =
|
||||
let
|
||||
mapValue =
|
||||
arg:
|
||||
if (lib.isBool arg) then
|
||||
lib.boolToString arg
|
||||
else if (lib.isInt arg) then
|
||||
toString arg
|
||||
else "\"${lib.escape [ "\"" ] (toString arg)}\"";
|
||||
else
|
||||
"\"${lib.escape [ "\"" ] (toString arg)}\"";
|
||||
in
|
||||
with lib; pipe cfg.settings [
|
||||
with lib;
|
||||
pipe cfg.settings [
|
||||
(filterAttrs (_: val: val != null))
|
||||
(mapAttrs (name: val: "${name}=${mapValue val}"))
|
||||
(attrValues)
|
||||
(concatLines)
|
||||
(pkgs.writeText "${cfg.package.name}-config.toml")
|
||||
];
|
||||
in {
|
||||
in
|
||||
{
|
||||
librespeed-secrets = lib.mkIf (cfg.secrets != { }) {
|
||||
description = "LibreSpeed secret helper";
|
||||
|
||||
ExecStart = let
|
||||
ExecStart =
|
||||
let
|
||||
script = pkgs.writeShellApplication {
|
||||
name = "librespeed-secrets";
|
||||
runtimeInputs = [ pkgs.coreutils ];
|
||||
text = ''
|
||||
text =
|
||||
''
|
||||
cp ${configFile} ''${RUNTIME_DIRECTORY%%:*}/config.toml
|
||||
'' + lib.pipe cfg.secrets [
|
||||
(lib.mapAttrs (name: file: ''
|
||||
''
|
||||
+ lib.pipe cfg.secrets [
|
||||
(lib.mapAttrs (
|
||||
name: file: ''
|
||||
cat >>''${RUNTIME_DIRECTORY%%:*}/config.toml <<EOF
|
||||
${name}="$(<${lib.escapeShellArg file})"
|
||||
EOF
|
||||
''))
|
||||
''
|
||||
))
|
||||
(lib.concatLines lib.attrValues)
|
||||
];
|
||||
};
|
||||
in lib.getExe script;
|
||||
in
|
||||
lib.getExe script;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
|
@ -255,8 +381,15 @@ in {
|
|||
|
||||
DynamicUser = true;
|
||||
|
||||
#ExecStartPre = lib.mkIf (!cfg.secrets ? "ipinfo_api_key") "${lib.getExe cfg.package} --update-ipdb";
|
||||
ExecStart = "${lib.getExe cfg.package} -c ${if (cfg.secrets == {}) then configFile else "\${RUNTIME_DIRECTORY%%:*}/config.toml"}";
|
||||
LoadCredential = lib.mkIf (cfg.domain != null && !cfg.frontend.useNginx) [
|
||||
"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";
|
||||
RuntimeDirectoryPreserve = true;
|
||||
|
@ -287,7 +420,8 @@ in {
|
|||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ snaki ];
|
||||
}
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
rustPlatform,
|
||||
}:
|
||||
let
|
||||
version = "1.3.2";
|
||||
# https://github.com/librespeed/speedtest-rust/pull/7
|
||||
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 {
|
||||
|
|
Loading…
Reference in a new issue