summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSophie Tauchert <sophie@999eagle.moe>2023-11-06 13:40:46 +0100
committerSophie Tauchert <sophie@999eagle.moe>2023-12-17 19:48:02 +0100
commit45bd4b1159bb29d4a60c1d811f24b3487acbcab6 (patch)
tree18a58b62371efcc6b3f6871be6284fdf449cafc9
parentnixos/tests/invidious: move postgres-tcp into second machine and fix tests (diff)
downloadnixpkgs-45bd4b1159bb29d4a60c1d811f24b3487acbcab6.tar.gz
nixos/invidious: add option to run more invidious instances
-rw-r--r--nixos/modules/services/web-apps/invidious.nix209
1 files changed, 130 insertions, 79 deletions
diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix
index 9294e33dfea3..b281345db197 100644
--- a/nixos/modules/services/web-apps/invidious.nix
+++ b/nixos/modules/services/web-apps/invidious.nix
@@ -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" ];
-
- preStart = lib.optionalString generateHmac ''
- if [[ ! -e "${generatedHmacKeyFile}" ]]; then
- ${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}"
- chmod 0600 "${generatedHmacKeyFile}"
- fi
- '';
+ 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" ];
- 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
- '';
-
- serviceConfig = {
- RestartSec = "2s";
- DynamicUser = true;
- StateDirectory = "invidious";
- StateDirectoryMode = "0750";
+ serviceConfig = {
+ RestartSec = "2s";
+ DynamicUser = true;
+ User = lib.mkIf (cfg.database.createLocally || cfg.serviceScale > 1) "invidious";
+ 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" ];
+ 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
+ '';
+ }
+ ];
- services.invidious.settings = {
- inherit (cfg) port;
+ 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 = {
# 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).