nixpkgs/nixos/modules/services/web-apps/invidious.nix
pennae 2e751c0772 treewide: automatically md-convert option descriptions
the conversion procedure is simple:

 - find all things that look like options, ie calls to either `mkOption`
   or `lib.mkOption` that take an attrset. remember the attrset as the
   option
 - for all options, find a `description` attribute who's value is not a
   call to `mdDoc` or `lib.mdDoc`
 - textually convert the entire value of the attribute to MD with a few
   simple regexes (the set from mdize-module.sh)
 - if the change produced a change in the manual output, discard
 - if the change kept the manual unchanged, add some text to the
   description to make sure we've actually found an option. if the
   manual changes this time, keep the converted description

this procedure converts 80% of nixos options to markdown. around 2000
options remain to be inspected, but most of those fail the "does not
change the manual output check": currently the MD conversion process
does not faithfully convert docbook tags like <code> and <package>, so
any option using such tags will not be converted at all.
2022-07-30 15:16:34 +02:00

264 lines
8.5 KiB
Nix

{ 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 = [ "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 = lib.mdDoc "The Invidious package to use.";
};
settings = lib.mkOption {
type = settingsFormat.type;
default = { };
description = lib.mdDoc ''
The settings Invidious should use.
See [config.example.yml](https://github.com/iv-org/invidious/blob/master/config/config.example.yml) for a list of all possible options.
'';
};
extraSettingsFile = lib.mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
A file including Invidious settings.
It gets merged with the setttings specified in {option}`services.invidious.settings`
and can be used to store secrets like `hmac_key` 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 = lib.mdDoc ''
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 = lib.mdDoc ''
The port Invidious should listen on.
To allow access from outside,
you can use either {option}`services.invidious.nginx`
or add `config.services.invidious.port` to {option}`networking.firewall.allowedTCPPorts`.
'';
};
database = {
createLocally = lib.mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
Whether to create a local database with PostgreSQL.
'';
};
host = lib.mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
The database host Invidious should use.
If `null`, the local unix socket is used. Otherwise
TCP is used.
'';
};
port = lib.mkOption {
type = types.port;
default = options.services.postgresql.port.default;
defaultText = lib.literalExpression "options.services.postgresql.port.default";
description = lib.mdDoc ''
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 = lib.mdDoc ''
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
]);
}