parent
c5286421bc
commit
f1447fdaa8
@ -0,0 +1,263 @@ |
||||
{ lib, config, pkgs, options, ... }: |
||||
let |
||||
cfg = config.services.invidious; |
||||
# To allow injecting secrets with jq, json (instead of yaml) is used |
||||
settingsFormat = pkgs.formats.json { }; |
||||
inherit (lib) types; |
||||
|
||||
settingsFile = settingsFormat.generate "invidious-settings" cfg.settings; |
||||
|
||||
serviceConfig = { |
||||
systemd.services.invidious = { |
||||
description = "Invidious (An alternative YouTube front-end)"; |
||||
wants = [ "network-online.target" ]; |
||||
after = [ "syslog.target" "network-online.target" ]; |
||||
wantedBy = [ "multi-user.target" ]; |
||||
|
||||
script = |
||||
let |
||||
jqFilter = "." |
||||
+ lib.optionalString (cfg.database.host != null) "[0].db.password = \"'\"'\"$(cat ${lib.escapeShellArg cfg.database.passwordFile})\"'\"'\"" |
||||
+ " | .[0]" |
||||
+ lib.optionalString (cfg.extraSettingsFile != null) " * .[1]"; |
||||
jqFiles = [ settingsFile ] ++ lib.optional (cfg.extraSettingsFile != null) cfg.extraSettingsFile; |
||||
in |
||||
'' |
||||
export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s "${jqFilter}" ${lib.escapeShellArgs jqFiles})" |
||||
exec ${cfg.package}/bin/invidious |
||||
''; |
||||
|
||||
serviceConfig = { |
||||
RestartSec = "2s"; |
||||
DynamicUser = true; |
||||
|
||||
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" ]; |
||||
}; |
||||
}; |
||||
|
||||
services.invidious.settings = { |
||||
inherit (cfg) port; |
||||
|
||||
# Automatically initialises and migrates the database if necessary |
||||
check_tables = true; |
||||
|
||||
db = { |
||||
user = lib.mkDefault "kemal"; |
||||
dbname = lib.mkDefault "invidious"; |
||||
port = cfg.database.port; |
||||
# Blank for unix sockets, see |
||||
# https://github.com/will/crystal-pg/blob/1548bb255210/src/pq/conninfo.cr#L100-L108 |
||||
host = if cfg.database.host == null then "" else cfg.database.host; |
||||
# Not needed because peer authentication is enabled |
||||
password = lib.mkIf (cfg.database.host == null) ""; |
||||
}; |
||||
} // (lib.optionalAttrs (cfg.domain != null) { |
||||
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"; |
||||
}]; |
||||
}; |
||||
|
||||
# Settings necessary for running with an automatically managed local database |
||||
localDatabaseConfig = lib.mkIf cfg.database.createLocally { |
||||
# Default to using the local database if we create it |
||||
services.invidious.database.host = lib.mkDefault null; |
||||
|
||||
services.postgresql = { |
||||
enable = true; |
||||
ensureDatabases = lib.singleton cfg.settings.db.dbname; |
||||
ensureUsers = lib.singleton { |
||||
name = cfg.settings.db.user; |
||||
ensurePermissions = { |
||||
"DATABASE ${cfg.settings.db.dbname}" = "ALL PRIVILEGES"; |
||||
}; |
||||
}; |
||||
# This is only needed because the unix user invidious isn't the same as |
||||
# the database user. This tells postgres to map one to the other. |
||||
identMap = '' |
||||
invidious invidious ${cfg.settings.db.user} |
||||
''; |
||||
# And this specifically enables peer authentication for only this |
||||
# database, which allows passwordless authentication over the postgres |
||||
# unix socket for the user map given above. |
||||
authentication = '' |
||||
local ${cfg.settings.db.dbname} ${cfg.settings.db.user} peer map=invidious |
||||
''; |
||||
}; |
||||
|
||||
systemd.services.invidious-db-clean = { |
||||
description = "Invidious database cleanup"; |
||||
documentation = [ "https://docs.invidious.io/Database-Information-and-Maintenance.md" ]; |
||||
startAt = lib.mkDefault "weekly"; |
||||
path = [ config.services.postgresql.package ]; |
||||
script = '' |
||||
psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "DELETE FROM nonces * WHERE expire < current_timestamp" |
||||
psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "TRUNCATE TABLE videos" |
||||
''; |
||||
serviceConfig = { |
||||
DynamicUser = true; |
||||
User = "invidious"; |
||||
}; |
||||
}; |
||||
|
||||
systemd.services.invidious = { |
||||
requires = [ "postgresql.service" ]; |
||||
after = [ "postgresql.service" ]; |
||||
|
||||
serviceConfig = { |
||||
User = "invidious"; |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
nginxConfig = lib.mkIf cfg.nginx.enable { |
||||
services.invidious.settings = { |
||||
https_only = config.services.nginx.virtualHosts.${cfg.domain}.forceSSL; |
||||
external_port = 80; |
||||
}; |
||||
|
||||
services.nginx = { |
||||
enable = true; |
||||
virtualHosts.${cfg.domain} = { |
||||
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; |
||||
|
||||
enableACME = lib.mkDefault true; |
||||
forceSSL = lib.mkDefault true; |
||||
}; |
||||
}; |
||||
|
||||
assertions = [{ |
||||
assertion = cfg.domain != null; |
||||
message = "To use services.invidious.nginx, you need to set services.invidious.domain"; |
||||
}]; |
||||
}; |
||||
in |
||||
{ |
||||
options.services.invidious = { |
||||
enable = lib.mkEnableOption "Invidious"; |
||||
|
||||
package = lib.mkOption { |
||||
type = types.package; |
||||
default = pkgs.invidious; |
||||
defaultText = "pkgs.invidious"; |
||||
description = "The Invidious package to use."; |
||||
}; |
||||
|
||||
settings = lib.mkOption { |
||||
type = settingsFormat.type; |
||||
default = { }; |
||||
description = '' |
||||
The settings Invidious should use. |
||||
|
||||
See <link xlink:href="https://github.com/iv-org/invidious/blob/master/config/config.example.yml">config.example.yml</link> for a list of all possible options. |
||||
''; |
||||
}; |
||||
|
||||
extraSettingsFile = lib.mkOption { |
||||
type = types.nullOr types.str; |
||||
default = null; |
||||
description = '' |
||||
A file including Invidious settings. |
||||
|
||||
It gets merged with the setttings specified in <option>services.invidious.settings</option> |
||||
and can be used to store secrets like <literal>hmac_key</literal> outside of the nix store. |
||||
''; |
||||
}; |
||||
|
||||
# This needs to be outside of settings to avoid infinite recursion |
||||
# (determining if nginx should be enabled and therefore the settings |
||||
# modified). |
||||
domain = lib.mkOption { |
||||
type = types.nullOr types.str; |
||||
default = null; |
||||
description = '' |
||||
The FQDN Invidious is reachable on. |
||||
|
||||
This is used to configure nginx and for building absolute URLs. |
||||
''; |
||||
}; |
||||
|
||||
port = lib.mkOption { |
||||
type = types.port; |
||||
# Default from https://docs.invidious.io/Configuration.md |
||||
default = 3000; |
||||
description = '' |
||||
The port Invidious should listen on. |
||||
|
||||
To allow access from outside, |
||||
you can use either <option>services.invidious.nginx</option> |
||||
or add <literal>config.services.invidious.port</literal> to <option>networking.firewall.allowedTCPPorts</option>. |
||||
''; |
||||
}; |
||||
|
||||
database = { |
||||
createLocally = lib.mkOption { |
||||
type = types.bool; |
||||
default = true; |
||||
description = '' |
||||
Whether to create a local database with PostgreSQL. |
||||
''; |
||||
}; |
||||
|
||||
host = lib.mkOption { |
||||
type = types.nullOr types.str; |
||||
default = null; |
||||
description = '' |
||||
The database host Invidious should use. |
||||
|
||||
If <literal>null</literal>, the local unix socket is used. Otherwise |
||||
TCP is used. |
||||
''; |
||||
}; |
||||
|
||||
port = lib.mkOption { |
||||
type = types.port; |
||||
default = options.services.postgresql.port.default; |
||||
description = '' |
||||
The port of the database Invidious should use. |
||||
|
||||
Defaults to the the default postgresql port. |
||||
''; |
||||
}; |
||||
|
||||
passwordFile = lib.mkOption { |
||||
type = types.nullOr types.str; |
||||
apply = lib.mapNullable toString; |
||||
default = null; |
||||
description = '' |
||||
Path to file containing the database password. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
nginx.enable = lib.mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = '' |
||||
Whether to configure nginx as a reverse proxy for Invidious. |
||||
|
||||
It serves it under the domain specified in <option>services.invidious.settings.domain</option> with enabled TLS and ACME. |
||||
Further configuration can be done through <option>services.nginx.virtualHosts.''${config.services.invidious.settings.domain}.*</option>, |
||||
which can also be used to disable AMCE and TLS. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [ |
||||
serviceConfig |
||||
localDatabaseConfig |
||||
nginxConfig |
||||
]); |
||||
} |
Loading…
Reference in new issue