nixos/invidious: add option to run more invidious instances

This commit is contained in:
Sophie Tauchert 2023-11-06 13:40:46 +01:00
parent 460e34b273
commit 45bd4b1159
No known key found for this signature in database
GPG Key ID: 52701DE5F5F51125

View File

@ -10,77 +10,106 @@ let
generatedHmacKeyFile = "/var/lib/invidious/hmac_key";
generateHmac = cfg.hmacKeyFile == null;
serviceConfig = {
systemd.services.invidious = {
description = "Invidious (An alternative YouTube front-end)";
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
commonInvidousServiceConfig = {
description = "Invidious (An alternative YouTube front-end)";
wants = [ "network-online.target" ];
after = [ "network-online.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
requires = lib.optional cfg.database.createLocally "postgresql.service";
wantedBy = [ "multi-user.target" ];
preStart = lib.optionalString generateHmac ''
if [[ ! -e "${generatedHmacKeyFile}" ]]; then
${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}"
chmod 0600 "${generatedHmacKeyFile}"
fi
'';
serviceConfig = {
RestartSec = "2s";
DynamicUser = true;
User = lib.mkIf (cfg.database.createLocally || cfg.serviceScale > 1) "invidious";
StateDirectory = "invidious";
StateDirectoryMode = "0750";
script = ''
configParts=()
''
# autogenerated hmac_key
+ lib.optionalString generateHmac ''
configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")")
''
# generated settings file
+ ''
configParts+=("$(< ${lib.escapeShellArg settingsFile})")
''
# optional database password file
+ lib.optionalString (cfg.database.host != null) ''
configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})")
''
# optional extra settings file
+ lib.optionalString (cfg.extraSettingsFile != null) ''
configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})")
''
# explicitly specified hmac key file
+ lib.optionalString (cfg.hmacKeyFile != null) ''
configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})")
''
# merge all parts into a single configuration with later elements overriding previous elements
+ ''
export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")"
exec ${cfg.package}/bin/invidious
'';
CapabilityBoundingSet = "";
PrivateDevices = true;
PrivateUsers = true;
ProtectHome = true;
ProtectKernelLogs = true;
ProtectProc = "invisible";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
serviceConfig = {
RestartSec = "2s";
DynamicUser = true;
StateDirectory = "invidious";
StateDirectoryMode = "0750";
CapabilityBoundingSet = "";
PrivateDevices = true;
PrivateUsers = true;
ProtectHome = true;
ProtectKernelLogs = true;
ProtectProc = "invisible";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
# Because of various issues Invidious must be restarted often, at least once a day, ideally
# every hour.
# This option enables the automatic restarting of the Invidious instance.
Restart = lib.mkDefault "always";
RuntimeMaxSec = lib.mkDefault "1h";
};
# Because of various issues Invidious must be restarted often, at least once a day, ideally
# every hour.
# This option enables the automatic restarting of the Invidious instance.
# To ensure multiple instances of Invidious are not restarted at the exact same time, a
# randomized extra offset of up to 5 minutes is added.
Restart = lib.mkDefault "always";
RuntimeMaxSec = lib.mkDefault "1h";
RuntimeRandomizedExtraSec = lib.mkDefault "5min";
};
};
mkInvidiousService = scaleIndex:
lib.foldl' lib.recursiveUpdate commonInvidousServiceConfig [
# only generate the hmac file in the first service
(lib.optionalAttrs (scaleIndex == 0) {
preStart = lib.optionalString generateHmac ''
if [[ ! -e "${generatedHmacKeyFile}" ]]; then
${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}"
chmod 0600 "${generatedHmacKeyFile}"
fi
'';
})
# configure the secondary services to run after the first service
(lib.optionalAttrs (scaleIndex > 0) {
after = commonInvidousServiceConfig.after ++ [ "invidious.service" ];
wants = commonInvidousServiceConfig.wants ++ [ "invidious.service" ];
})
{
script = ''
configParts=()
''
# autogenerated hmac_key
+ lib.optionalString generateHmac ''
configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")")
''
# generated settings file
+ ''
configParts+=("$(< ${lib.escapeShellArg settingsFile})")
''
# optional database password file
+ lib.optionalString (cfg.database.host != null) ''
configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})")
''
# optional extra settings file
+ lib.optionalString (cfg.extraSettingsFile != null) ''
configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})")
''
# explicitly specified hmac key file
+ lib.optionalString (cfg.hmacKeyFile != null) ''
configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})")
''
# configure threads for secondary instances
+ lib.optionalString (scaleIndex > 0) ''
configParts+=('{"channel_threads":0, "feed_threads":0}')
''
# configure different ports for the instances
+ ''
configParts+=('{"port":${toString (cfg.port + scaleIndex)}}')
''
# merge all parts into a single configuration with later elements overriding previous elements
+ ''
export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")"
exec ${cfg.package}/bin/invidious
'';
}
];
serviceConfig = {
systemd.services = builtins.listToAttrs (builtins.genList
(scaleIndex: {
name = "invidious" + lib.optionalString (scaleIndex > 0) "-${builtins.toString scaleIndex}";
value = mkInvidiousService scaleIndex;
})
cfg.serviceScale);
services.invidious.settings = {
inherit (cfg) port;
# Automatically initialises and migrates the database if necessary
check_tables = true;
@ -98,10 +127,16 @@ let
inherit (cfg) domain;
});
assertions = [{
assertion = cfg.database.host != null -> cfg.database.passwordFile != null;
message = "If database host isn't null, database password needs to be set";
}];
assertions = [
{
assertion = cfg.database.host != null -> cfg.database.passwordFile != null;
message = "If database host isn't null, database password needs to be set";
}
{
assertion = cfg.serviceScale >= 1;
message = "Service can't be scaled below one instance";
}
];
};
# Settings necessary for running with an automatically managed local database
@ -132,15 +167,6 @@ let
local ${cfg.settings.db.dbname} ${cfg.settings.db.user} peer map=invidious
'';
};
systemd.services.invidious = {
requires = [ "postgresql.service" ];
after = [ "postgresql.service" ];
serviceConfig = {
User = "invidious";
};
};
};
nginxConfig = lib.mkIf cfg.nginx.enable {
@ -152,11 +178,22 @@ let
services.nginx = {
enable = true;
virtualHosts.${cfg.domain} = {
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}";
locations."/".proxyPass =
if cfg.serviceScale == 1 then
"http://127.0.0.1:${toString cfg.port}"
else "http://upstream-invidious";
enableACME = lib.mkDefault true;
forceSSL = lib.mkDefault true;
};
upstreams = lib.mkIf (cfg.serviceScale > 1) {
"upstream-invidious".servers = builtins.listToAttrs (builtins.genList
(scaleIndex: {
name = "127.0.0.1:${toString (cfg.port + scaleIndex)}";
value = { };
})
cfg.serviceScale);
};
};
assertions = [{
@ -204,6 +241,20 @@ in
'';
};
serviceScale = lib.mkOption {
type = types.int;
default = 1;
description = lib.mdDoc ''
How many invidious instances to run.
See https://docs.invidious.io/improve-public-instance/#2-multiple-invidious-processes for more details
on how this is intended to work. All instances beyond the first one have the options `channel_threads`
and `feed_threads` set to 0 to avoid conflicts with multiple instances refreshing subscriptions. Instances
will be configured to bind to consecutive ports starting with {option}`services.invidious.port` for the
first instance.
'';
};
# This needs to be outside of settings to avoid infinite recursion
# (determining if nginx should be enabled and therefore the settings
# modified).