at 22.05-pre 8.7 kB view raw
1{ lib, config, pkgs, options, ... }: 2let 3 cfg = config.services.invidious; 4 # To allow injecting secrets with jq, json (instead of yaml) is used 5 settingsFormat = pkgs.formats.json { }; 6 inherit (lib) types; 7 8 settingsFile = settingsFormat.generate "invidious-settings" cfg.settings; 9 10 serviceConfig = { 11 systemd.services.invidious = { 12 description = "Invidious (An alternative YouTube front-end)"; 13 wants = [ "network-online.target" ]; 14 after = [ "syslog.target" "network-online.target" ]; 15 wantedBy = [ "multi-user.target" ]; 16 17 script = 18 let 19 jqFilter = "." 20 + lib.optionalString (cfg.database.host != null) "[0].db.password = \"'\"'\"$(cat ${lib.escapeShellArg cfg.database.passwordFile})\"'\"'\"" 21 + " | .[0]" 22 + lib.optionalString (cfg.extraSettingsFile != null) " * .[1]"; 23 jqFiles = [ settingsFile ] ++ lib.optional (cfg.extraSettingsFile != null) cfg.extraSettingsFile; 24 in 25 '' 26 export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s "${jqFilter}" ${lib.escapeShellArgs jqFiles})" 27 exec ${cfg.package}/bin/invidious 28 ''; 29 30 serviceConfig = { 31 RestartSec = "2s"; 32 DynamicUser = true; 33 34 CapabilityBoundingSet = ""; 35 PrivateDevices = true; 36 PrivateUsers = true; 37 ProtectHome = true; 38 ProtectKernelLogs = true; 39 ProtectProc = "invisible"; 40 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; 41 RestrictNamespaces = true; 42 SystemCallArchitectures = "native"; 43 SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ]; 44 }; 45 }; 46 47 services.invidious.settings = { 48 inherit (cfg) port; 49 50 # Automatically initialises and migrates the database if necessary 51 check_tables = true; 52 53 db = { 54 user = lib.mkDefault "kemal"; 55 dbname = lib.mkDefault "invidious"; 56 port = cfg.database.port; 57 # Blank for unix sockets, see 58 # https://github.com/will/crystal-pg/blob/1548bb255210/src/pq/conninfo.cr#L100-L108 59 host = if cfg.database.host == null then "" else cfg.database.host; 60 # Not needed because peer authentication is enabled 61 password = lib.mkIf (cfg.database.host == null) ""; 62 }; 63 } // (lib.optionalAttrs (cfg.domain != null) { 64 inherit (cfg) domain; 65 }); 66 67 assertions = [{ 68 assertion = cfg.database.host != null -> cfg.database.passwordFile != null; 69 message = "If database host isn't null, database password needs to be set"; 70 }]; 71 }; 72 73 # Settings necessary for running with an automatically managed local database 74 localDatabaseConfig = lib.mkIf cfg.database.createLocally { 75 # Default to using the local database if we create it 76 services.invidious.database.host = lib.mkDefault null; 77 78 services.postgresql = { 79 enable = true; 80 ensureDatabases = lib.singleton cfg.settings.db.dbname; 81 ensureUsers = lib.singleton { 82 name = cfg.settings.db.user; 83 ensurePermissions = { 84 "DATABASE ${cfg.settings.db.dbname}" = "ALL PRIVILEGES"; 85 }; 86 }; 87 # This is only needed because the unix user invidious isn't the same as 88 # the database user. This tells postgres to map one to the other. 89 identMap = '' 90 invidious invidious ${cfg.settings.db.user} 91 ''; 92 # And this specifically enables peer authentication for only this 93 # database, which allows passwordless authentication over the postgres 94 # unix socket for the user map given above. 95 authentication = '' 96 local ${cfg.settings.db.dbname} ${cfg.settings.db.user} peer map=invidious 97 ''; 98 }; 99 100 systemd.services.invidious-db-clean = { 101 description = "Invidious database cleanup"; 102 documentation = [ "https://docs.invidious.io/Database-Information-and-Maintenance.md" ]; 103 startAt = lib.mkDefault "weekly"; 104 path = [ config.services.postgresql.package ]; 105 script = '' 106 psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "DELETE FROM nonces * WHERE expire < current_timestamp" 107 psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "TRUNCATE TABLE videos" 108 ''; 109 serviceConfig = { 110 DynamicUser = true; 111 User = "invidious"; 112 }; 113 }; 114 115 systemd.services.invidious = { 116 requires = [ "postgresql.service" ]; 117 after = [ "postgresql.service" ]; 118 119 serviceConfig = { 120 User = "invidious"; 121 }; 122 }; 123 }; 124 125 nginxConfig = lib.mkIf cfg.nginx.enable { 126 services.invidious.settings = { 127 https_only = config.services.nginx.virtualHosts.${cfg.domain}.forceSSL; 128 external_port = 80; 129 }; 130 131 services.nginx = { 132 enable = true; 133 virtualHosts.${cfg.domain} = { 134 locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; 135 136 enableACME = lib.mkDefault true; 137 forceSSL = lib.mkDefault true; 138 }; 139 }; 140 141 assertions = [{ 142 assertion = cfg.domain != null; 143 message = "To use services.invidious.nginx, you need to set services.invidious.domain"; 144 }]; 145 }; 146in 147{ 148 options.services.invidious = { 149 enable = lib.mkEnableOption "Invidious"; 150 151 package = lib.mkOption { 152 type = types.package; 153 default = pkgs.invidious; 154 defaultText = "pkgs.invidious"; 155 description = "The Invidious package to use."; 156 }; 157 158 settings = lib.mkOption { 159 type = settingsFormat.type; 160 default = { }; 161 description = '' 162 The settings Invidious should use. 163 164 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. 165 ''; 166 }; 167 168 extraSettingsFile = lib.mkOption { 169 type = types.nullOr types.str; 170 default = null; 171 description = '' 172 A file including Invidious settings. 173 174 It gets merged with the setttings specified in <option>services.invidious.settings</option> 175 and can be used to store secrets like <literal>hmac_key</literal> outside of the nix store. 176 ''; 177 }; 178 179 # This needs to be outside of settings to avoid infinite recursion 180 # (determining if nginx should be enabled and therefore the settings 181 # modified). 182 domain = lib.mkOption { 183 type = types.nullOr types.str; 184 default = null; 185 description = '' 186 The FQDN Invidious is reachable on. 187 188 This is used to configure nginx and for building absolute URLs. 189 ''; 190 }; 191 192 port = lib.mkOption { 193 type = types.port; 194 # Default from https://docs.invidious.io/Configuration.md 195 default = 3000; 196 description = '' 197 The port Invidious should listen on. 198 199 To allow access from outside, 200 you can use either <option>services.invidious.nginx</option> 201 or add <literal>config.services.invidious.port</literal> to <option>networking.firewall.allowedTCPPorts</option>. 202 ''; 203 }; 204 205 database = { 206 createLocally = lib.mkOption { 207 type = types.bool; 208 default = true; 209 description = '' 210 Whether to create a local database with PostgreSQL. 211 ''; 212 }; 213 214 host = lib.mkOption { 215 type = types.nullOr types.str; 216 default = null; 217 description = '' 218 The database host Invidious should use. 219 220 If <literal>null</literal>, the local unix socket is used. Otherwise 221 TCP is used. 222 ''; 223 }; 224 225 port = lib.mkOption { 226 type = types.port; 227 default = options.services.postgresql.port.default; 228 description = '' 229 The port of the database Invidious should use. 230 231 Defaults to the the default postgresql port. 232 ''; 233 }; 234 235 passwordFile = lib.mkOption { 236 type = types.nullOr types.str; 237 apply = lib.mapNullable toString; 238 default = null; 239 description = '' 240 Path to file containing the database password. 241 ''; 242 }; 243 }; 244 245 nginx.enable = lib.mkOption { 246 type = types.bool; 247 default = false; 248 description = '' 249 Whether to configure nginx as a reverse proxy for Invidious. 250 251 It serves it under the domain specified in <option>services.invidious.settings.domain</option> with enabled TLS and ACME. 252 Further configuration can be done through <option>services.nginx.virtualHosts.''${config.services.invidious.settings.domain}.*</option>, 253 which can also be used to disable AMCE and TLS. 254 ''; 255 }; 256 }; 257 258 config = lib.mkIf cfg.enable (lib.mkMerge [ 259 serviceConfig 260 localDatabaseConfig 261 nginxConfig 262 ]); 263}