Update from update-inputs-2024-10-02-04-20

This commit is contained in:
Update Bot 2024-10-02 04:20:27 +02:00
commit a4d0125e29
No known key found for this signature in database
5 changed files with 406 additions and 315 deletions

View file

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

View file

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

View file

@ -155,11 +155,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1727531434, "lastModified": 1727809780,
"narHash": "sha256-b+GBgCWd2N6pkiTkRZaMFOPztPO4IVTaclYPrQl2uLk=", "narHash": "sha256-7W5HE2IRiZglMBKcn9JtC6bveE6/F7IzQyV2XDanGFA=",
"owner": "nix-community", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "b709e1cc33fcde71c7db43850a55ebe6449d0959", "rev": "6c5ba9ec9d470c1ca29e7735762c9c366e28f7f5",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,337 +1,427 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.services.librespeed; cfg = config.services.librespeed;
in { in
options.services.librespeed = let {
inherit (lib) mkOption types; options.services.librespeed =
in { let
enable = lib.mkEnableOption "LibreSpeed server"; inherit (lib) mkOption types;
package = lib.mkPackageOption pkgs "librespeed-rust" {}; in
domain = mkOption { {
description = '' enable = lib.mkEnableOption "LibreSpeed server";
If not `null`, this will add an entry to `services.librespeed.servers` and package = lib.mkPackageOption pkgs "librespeed-rust" { };
configure librespeed to use TLS. domain = mkOption {
'';
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.
'';
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.
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 "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 = '' description = ''
Configure nginx for the LibreSpeed frontend. If not `null`, this will add an entry to `services.librespeed.servers` and
This will only create a virtual host for the frontend and won't proxy all requests because, configure librespeed to use TLS.
the reported upload and download speeds are inaccurate if proxied.
''; '';
default = cfg.domain != null; 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; 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 { settings = mkOption {
description = '' description = ''
Override default test parameters. LibreSpeed configuration written as Nix expression.
See [speedtest_worker.js][link] for a list of possible values. 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.
[link]: https://github.com/librespeed/speedtest/blob/master/speedtest_worker.js#L39 See [github.com/librespeed][librespeed] for configuration help.
[librespeed]: https://github.com/librespeed/speedtest-rust
''; '';
default = {}; default = { };
type = with types; nullOr (attrsOf (oneOf [ type =
bool with types;
int nullOr (
str attrsOf (oneOf [
float (nullOr bool)
])); int
str
package
])
);
}; };
servers = mkOption { frontend = {
description = "LibreSpeed servers that should apper in the server list."; enable = lib.mkEnableOption ''
type = types.listOf (types.submodule { Enables the LibreSpeed frontend and adds a nginx virtual host if
options = let not explicetly disabled and `services.librespeed.domain` is not `null`.
inherit (types) nonEmptyStr; '';
in { contactEmail = mkOption {
name = mkOption { description = "Email address listed in the privacy policy.";
description = "Name shown in the server list."; default =
type = nonEmptyStr; if (cfg.domain != null) then "webmaster@${cfg.domain}" else "webmaster@${config.networking.fqdn}";
}; defaultText = lib.literalExpression ''
server = mkOption { if (config.services.librespeed.domain != null) then
description = "URL to the server. You may use `//` instead of `http://` or `https://`."; "webmaster@''${config.services.librespeed.domain}"
type = nonEmptyStr; else
}; "webmaster@''${config.networking.fqdn}";
dlURL = mkOption { '';
description = '' type = types.str;
URL path to download test on this server. };
Append `.php` to the default value if the server uses the php implementation. pageTitle = mkOption {
''; description = "Title of the webpage.";
default = "backend/garbage"; default = "LibreSpeed";
type = nonEmptyStr; type = types.str;
}; };
ulURL = mkOption { useNginx = mkOption {
description = '' description = ''
URL path to upload test on this server. Configure nginx for the LibreSpeed frontend.
Append `.php` to the default value if the server uses the php implementation. 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 = "backend/empty"; '';
type = nonEmptyStr; default = cfg.domain != null;
}; defaultText = lib.literalExpression "config.services.librespeed.domain != null";
pingURL = mkOption { type = types.bool;
description = '' };
URL path to latency/jitter test on this server. settings = mkOption {
Append `.php` to the default value if the server uses the php implementation. description = ''
''; Override default settings of the speedtest web client.
default = "backend/empty"; See [speedtest_worker.js][link] for a list of possible values.
type = nonEmptyStr;
}; [link]: https://github.com/librespeed/speedtest/blob/master/speedtest_worker.js#L39
getIpURL = mkOption { '';
description = '' default = {
URL path to IP lookup on this server. telemetry_level = "basic";
Append `.php` to the default value if the server uses the php implementation.
'';
default = "backend/getIP";
type = nonEmptyStr;
};
}; };
}); 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
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 let
librespeedAssets = pkgs.runCommand "librespeed-assets" (let librespeedAssets =
mapValue = arg: if (lib.isBool arg) then pkgs.runCommand "librespeed-assets"
lib.boolToString arg (
else if ((lib.isInt arg) || (lib.isFloat arg)) then let
toString arg mapValue =
else arg:
"\"${lib.escape [ "\"" ] (toString 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 [ mapSettings = lib.pipe cfg.frontend.settings [
(lib.mapAttrs (name: val: " s.setParameter(\"${lib.escape [ "\"" ] name}\",${mapValue val});")) (lib.mapAttrs (name: val: " s.setParameter(\"${lib.escape [ "\"" ] name}\",${mapValue val});"))
(lib.attrValues) (lib.attrValues)
(lib.concatLines) (lib.concatLines)
]; ];
in { in
preferLocal = true; {
preferLocal = true;
serversList = '' serversList = ''
function get_servers() { function get_servers() {
return ${builtins.toJSON cfg.frontend.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.
'';
} }
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."/backend/".extraConfig = "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-challange";
};
};
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.frontend.settings = lib.mkIf cfg.frontend.enable {
telemetry_level = lib.mkDefault "basic";
};
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 networking.firewall = lib.mkIf cfg.openFirewall {
script = pkgs.writeShellApplication { allowedTCPPorts = [ cfg.settings.listen_port ];
name = "librespeed-secrets"; };
runtimeInputs = [ pkgs.coreutils ]; services.nginx.virtualHosts = lib.mkIf (cfg.frontend.enable && cfg.frontend.useNginx) {
text = '' ${cfg.domain} = {
cp ${configFile} ''${RUNTIME_DIRECTORY%%:*}/config.toml locations."/".root = librespeedAssets;
'' + lib.pipe cfg.secrets [ locations."/backend/" = {
(lib.mapAttrs (name: file: '' proxyPass = "http://${cfg.settings.bind_address}:${toString cfg.settings.listen_port}/backend/";
cat >>''${RUNTIME_DIRECTORY%%:*}/config.toml <<EOF extraConfig = ''
${name}="$(<${lib.escapeShellArg file})" # add_header Cache-Control 'no-store, no-cache, max-age=0, no-transform';
EOF # add_header Last-Modified $date_gmt;
'')) if_modified_since off;
(lib.concatLines lib.attrValues) 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_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 (cfg.domain != null && !cfg.frontend.useNginx);
};
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";
};
}; };
in lib.getExe script; librespeed = {
description = "LibreSpeed server daemon";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
requires = lib.optionals (cfg.secrets != { }) [ "librespeed-secrets.service" ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "simple";
RemainAfterExit = true; Restart = "always";
RuntimeDirectory = "librespeed";
UMask = "u=rw"; DynamicUser = true;
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;
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;
};
};
}; };
}; }
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 ]; meta.maintainers = with lib.maintainers; [ snaki ];
} }

View file

@ -4,6 +4,7 @@
rustPlatform, rustPlatform,
}: }:
let let
# https://github.com/librespeed/speedtest-rust/pull/7
version = "unstable-2024-09-28"; version = "unstable-2024-09-28";
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "librespeed"; owner = "librespeed";