Compare commits

...

2 commits

Author SHA1 Message Date
Update Bot a2141e6ccf
flake.lock: Update
Flake lock file updates:

• Updated input 'disko':
    'github:nix-community/disko/67dc29be3036cc888f0b9d4f0a788ee0f6768700' (2024-09-26)
  → 'github:nix-community/disko/6c5ba9ec9d470c1ca29e7735762c9c366e28f7f5' (2024-10-01)
• Updated input 'nixos-hardware':
    'github:nixos/nixos-hardware/d830ad47cc992b4a46b342bbc79694cbd0e980b2' (2024-09-27)
  → 'github:nixos/nixos-hardware/11c43c830e533dad1be527ecce379fcf994fbbb5' (2024-09-30)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/1925c603f17fc89f4c8f6bf6f631a802ad85d784' (2024-09-26)
  → 'github:nixos/nixpkgs/06cf0e1da4208d3766d898b7fdab6513366d45b9' (2024-09-29)
• Updated input 'nixvim':
    'github:nix-community/nixvim/b5c19b6abb0fb0156b1cb76793b363e430e2cb47' (2024-09-27)
  → 'github:nix-community/nixvim/5f4a4b47597d3b9ac26c41ff4e8da28fa662f200' (2024-09-29)
• Updated input 'nixvim/git-hooks':
    'github:cachix/git-hooks.nix/4e743a6920eab45e8ba0fbe49dc459f1423a4b74' (2024-09-19)
  → 'github:cachix/git-hooks.nix/85f7a7177c678de68224af3402ab8ee1bcee25c8' (2024-09-28)
• Updated input 'nixvim/nix-darwin':
    'github:lnl7/nix-darwin/bd7d1e3912d40f799c5c0f7e5820ec950f1e0b3d' (2024-09-22)
  → 'github:lnl7/nix-darwin/f2e1c4aa29fc211947c3a7113cba1dd707433b70' (2024-09-28)
• Updated input 'nixvim/nuschtosSearch':
    'github:NuschtOS/search/3b7dd61b365ca45380707453758a45f2e9977be3' (2024-09-22)
  → 'github:NuschtOS/search/9f7426e532ef8dfc839c4a3fcc567b13a20a70d3' (2024-09-27)
• Updated input 'nixvim/treefmt-nix':
    'github:numtide/treefmt-nix/1bff2ba6ec22bc90e9ad3f7e94cca0d37870afa3' (2024-09-25)
  → 'github:numtide/treefmt-nix/879b29ae9a0378904fbbefe0dadaed43c8905754' (2024-09-27)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/127a96f49ddc377be6ba76964411bab11ae27803' (2024-09-27)
  → 'github:Mic92/sops-nix/3198a242e547939c5e659353551b0668ec150268' (2024-09-30)
• Updated input 'stylix':
    'github:danth/stylix/e3eb7fdf8d129ff3676dfbc84ee1262322ca6fb4' (2024-09-26)
  → 'github:danth/stylix/e7e97059776da7e34b739415a7bc8f80f606b803' (2024-09-30)
2024-10-02 12:55:04 +02:00
emily a74c1d8585
librespeed: Fixes 2024-10-02 12:54:44 +02:00
8 changed files with 435 additions and 325 deletions

View file

@ -7,6 +7,7 @@
../../services/nginx.nix
../../services/uptime-kuma.nix
../../services/vaultwarden.nix
../../services/librespeed.nix
./disko.nix
./hardware-configuration.nix
];

View file

@ -8,7 +8,6 @@
../../services/nginx.nix
../../services/hydra
../../services/update-nixfiles.nix
../../services/librespeed.nix
];
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];

View file

@ -1,7 +1,8 @@
{ pkgs, ... }: {
{ ... }: {
services.librespeed = {
enable = true;
package = pkgs.librespeed-go;
openFirewall = true;
domain = "speed.kyouma.net";
frontend.enable = true;
};
}

View file

@ -155,11 +155,11 @@
]
},
"locked": {
"lastModified": 1727359191,
"narHash": "sha256-5PltTychnExFwzpEnY3WhOywaMV/M6NxYI/y3oXuUtw=",
"lastModified": 1727809780,
"narHash": "sha256-7W5HE2IRiZglMBKcn9JtC6bveE6/F7IzQyV2XDanGFA=",
"owner": "nix-community",
"repo": "disko",
"rev": "67dc29be3036cc888f0b9d4f0a788ee0f6768700",
"rev": "6c5ba9ec9d470c1ca29e7735762c9c366e28f7f5",
"type": "github"
},
"original": {
@ -462,11 +462,11 @@
]
},
"locked": {
"lastModified": 1726745158,
"narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=",
"lastModified": 1727514110,
"narHash": "sha256-0YRcOxJG12VGDFH8iS8pJ0aYQQUAgo/r3ZAL+cSh9nk=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74",
"rev": "85f7a7177c678de68224af3402ab8ee1bcee25c8",
"type": "github"
},
"original": {
@ -662,11 +662,11 @@
]
},
"locked": {
"lastModified": 1727003835,
"narHash": "sha256-Cfllbt/ADfO8oxbT984MhPHR6FJBaglsr1SxtDGbpec=",
"lastModified": 1727507295,
"narHash": "sha256-I/FrX1peu4URoj5T5odfuKR2rm4GjYJJpCGF9c0/lDA=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "bd7d1e3912d40f799c5c0f7e5820ec950f1e0b3d",
"rev": "f2e1c4aa29fc211947c3a7113cba1dd707433b70",
"type": "github"
},
"original": {
@ -743,11 +743,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1727437159,
"narHash": "sha256-v4qLwEw5OmprgQZTT7KZMNU7JjXJzRypw8+Cw6++fWk=",
"lastModified": 1727665282,
"narHash": "sha256-oKtfbQB1MBypqIyzkC8QCQcVGOa1soaXaGgcBIoh14o=",
"owner": "nixos",
"repo": "nixos-hardware",
"rev": "d830ad47cc992b4a46b342bbc79694cbd0e980b2",
"rev": "11c43c830e533dad1be527ecce379fcf994fbbb5",
"type": "github"
},
"original": {
@ -874,11 +874,11 @@
},
"nixpkgs_4": {
"locked": {
"lastModified": 1727348695,
"narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
"lastModified": 1727634051,
"narHash": "sha256-S5kVU7U82LfpEukbn/ihcyNt2+EvG7Z5unsKW9H/yFA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
"rev": "06cf0e1da4208d3766d898b7fdab6513366d45b9",
"type": "github"
},
"original": {
@ -905,11 +905,11 @@
"treefmt-nix": "treefmt-nix_2"
},
"locked": {
"lastModified": 1727471696,
"narHash": "sha256-3r/VNQp5aJK9Gj8hKdfSYqeXcc0kqpfFYhEg8ioWttE=",
"lastModified": 1727645871,
"narHash": "sha256-Os3PAThU5XliKkKa+SHsFyV/EsCHogHcYONmpzb6500=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "b5c19b6abb0fb0156b1cb76793b363e430e2cb47",
"rev": "5f4a4b47597d3b9ac26c41ff4e8da28fa662f200",
"type": "github"
},
"original": {
@ -927,11 +927,11 @@
]
},
"locked": {
"lastModified": 1726995581,
"narHash": "sha256-lgsE/CTkZk9OIiFGEIrxXZQ7Feiv41dqlN7pEfTdgew=",
"lastModified": 1727452028,
"narHash": "sha256-ehl/A4HQFRyqj1Fk7cl+dgSf/2Fb1jLwWJtZaMU6RfU=",
"owner": "NuschtOS",
"repo": "search",
"rev": "3b7dd61b365ca45380707453758a45f2e9977be3",
"rev": "9f7426e532ef8dfc839c4a3fcc567b13a20a70d3",
"type": "github"
},
"original": {
@ -985,11 +985,11 @@
"nixpkgs-stable": "nixpkgs-stable_2"
},
"locked": {
"lastModified": 1727423009,
"narHash": "sha256-+4B/dQm2EnORIk0k2wV3aHGaE0WXTBjColXjj7qWh10=",
"lastModified": 1727734513,
"narHash": "sha256-i47LQwoGCVQq4upV2YHV0OudkauHNuFsv306ualB/Sw=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "127a96f49ddc377be6ba76964411bab11ae27803",
"rev": "3198a242e547939c5e659353551b0668ec150268",
"type": "github"
},
"original": {
@ -1019,11 +1019,11 @@
"tinted-tmux": "tinted-tmux"
},
"locked": {
"lastModified": 1727362643,
"narHash": "sha256-Ceiq/aYjRlRBU677lBaemn8ZU2Jpr08Iso6UlBc9nFc=",
"lastModified": 1727723275,
"narHash": "sha256-k4HrG8TJQ0RqDS1tlDz71kvWFBNQ7qZI9T5Z0qLR85Y=",
"owner": "danth",
"repo": "stylix",
"rev": "e3eb7fdf8d129ff3676dfbc84ee1262322ca6fb4",
"rev": "e7e97059776da7e34b739415a7bc8f80f606b803",
"type": "github"
},
"original": {
@ -1170,11 +1170,11 @@
]
},
"locked": {
"lastModified": 1727252110,
"narHash": "sha256-3O7RWiXpvqBcCl84Mvqa8dXudZ1Bol1ubNdSmQt7nF4=",
"lastModified": 1727431250,
"narHash": "sha256-uGRlRT47ecicF9iLD1G3g43jn2e+b5KaMptb59LHnvM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "1bff2ba6ec22bc90e9ad3f7e94cca0d37870afa3",
"rev": "879b29ae9a0378904fbbefe0dadaed43c8905754",
"type": "github"
},
"original": {

View file

@ -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;
}

View file

@ -187,7 +187,6 @@
enable = true;
sources.diagnostics = {
pylint.enable = true;
statix.enable = true;
};
sources.formatting = {
nixfmt.enable = true;

View file

@ -1,293 +1,408 @@
{ config, lib, pkgs, ... }:
{
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 [
(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.
in
{
options.services.librespeed =
let
inherit (lib) mkOption types;
in
{
enable = lib.mkEnableOption "LibreSpeed server";
package = lib.mkPackageOption pkgs "librespeed-rust" { };
domain = mkOption {
description = ''
If not `null`, this will add an entry to `services.librespeed.servers` and
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.
'';
default = false;
type = types.bool;
};
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);
};
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.
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;
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;
};
enableACME = true;
forceSSL = true;
extraConfig = ''
gzip off;
'';
};
};
security.acme.certs = lib.mkIf cfg.configureNginx {
${cfg.domain} = {};
};
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.
services.librespeed.servers = lib.mkIf (cfg.domain != null) [
{
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}
}
'';
} ''
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";
server_lat = 0;
server_lng = 0;
proxyprotocol_port = 0;
redact_ip_addresses = false;
#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;
} // rec {
database_type = mkDefault "none";
database_file = mkIf (database_type == "sqlite") (mkDefault "/var/lib/librespeed/speedtest.sqlite");
};
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)}\"";
in
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 {
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 <<EOF
${name}="$(<${lib.escapeShellArg file})"
EOF
''))
(lib.concatLines lib.attrValues)
];
[link]: https://github.com/librespeed/speedtest/blob/master/speedtest_worker.js#L39
'';
default = {
telemetry_level = "basic";
};
in lib.getExe script;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "librespeed";
UMask = "u=rw";
type =
with types;
nullOr (
attrsOf (oneOf [
bool
int
str
float
])
);
};
};
librespeed = {
description = "LibreSpeed server daemon";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
requires = lib.optionals (cfg.secrets != {}) [ "librespeed-secrets.service" ];
serviceConfig = {
Type = "simple";
Restart = "always";
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"}";
WorkingDirectory = "/var/cache/librespeed";
RuntimeDirectory = "librespeed";
RuntimeDirectoryPreserve = true;
StateDirectory = "librespeed";
CacheDirectory = "librespeed";
SyslogIdentifier = "librespeed";
ReadOnlyPaths = [ cfg.package ];
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;
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 (
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)
];
in
{
preferLocal = true;
serversList = ''
function get_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 "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
{
assertions = [
{
assertion = cfg.frontend.useNginx -> cfg.domain != null;
message = ''
`services.librespeed.frontend.useNginx` requires `services.librespeed.frontend.domain` to be set.
'';
}
];
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."= /servers.json".return = "200 '${builtins.toJSON cfg.frontend.servers}'";
locations."/backend/".return = "301 https://$host:${toString cfg.settings.listen_port}$request_uri";
enableACME = true;
forceSSL = true;
};
};
security.acme.certs = lib.mkIf (cfg.domain != null) {
${cfg.domain} = {
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}:${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 "::";
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_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);
};
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)}\"";
in
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
{
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 <<EOF
${name}="$(<${lib.escapeShellArg file})"
EOF
''
))
(lib.concatLines lib.attrValues)
];
};
in
lib.getExe script;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "librespeed";
UMask = "u=rw";
};
};
librespeed = {
description = "LibreSpeed server daemon";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
requires = lib.optionals (cfg.secrets != { }) [ "librespeed-secrets.service" ];
serviceConfig = {
Type = "simple";
Restart = "always";
DynamicUser = true;
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";
RuntimeDirectoryPreserve = true;
StateDirectory = "librespeed";
CacheDirectory = "librespeed";
SyslogIdentifier = "librespeed";
ReadOnlyPaths = [ cfg.package ];
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;
};
};
};
}
);
meta.maintainers = with lib.maintainers; [ snaki ];
}

View file

@ -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 {