at 23.11-pre 8.8 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 = [ "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 (lib.mdDoc "Invidious"); 150 151 package = lib.mkOption { 152 type = types.package; 153 default = pkgs.invidious; 154 defaultText = lib.literalExpression "pkgs.invidious"; 155 description = lib.mdDoc "The Invidious package to use."; 156 }; 157 158 settings = lib.mkOption { 159 type = settingsFormat.type; 160 default = { }; 161 description = lib.mdDoc '' 162 The settings Invidious should use. 163 164 See [config.example.yml](https://github.com/iv-org/invidious/blob/master/config/config.example.yml) 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 = lib.mdDoc '' 172 A file including Invidious settings. 173 174 It gets merged with the settings specified in {option}`services.invidious.settings` 175 and can be used to store secrets like `hmac_key` 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 = lib.mdDoc '' 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 = lib.mdDoc '' 197 The port Invidious should listen on. 198 199 To allow access from outside, 200 you can use either {option}`services.invidious.nginx` 201 or add `config.services.invidious.port` to {option}`networking.firewall.allowedTCPPorts`. 202 ''; 203 }; 204 205 database = { 206 createLocally = lib.mkOption { 207 type = types.bool; 208 default = true; 209 description = lib.mdDoc '' 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 = lib.mdDoc '' 218 The database host Invidious should use. 219 220 If `null`, 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 defaultText = lib.literalExpression "options.services.postgresql.port.default"; 229 description = lib.mdDoc '' 230 The port of the database Invidious should use. 231 232 Defaults to the the default postgresql port. 233 ''; 234 }; 235 236 passwordFile = lib.mkOption { 237 type = types.nullOr types.str; 238 apply = lib.mapNullable toString; 239 default = null; 240 description = lib.mdDoc '' 241 Path to file containing the database password. 242 ''; 243 }; 244 }; 245 246 nginx.enable = lib.mkOption { 247 type = types.bool; 248 default = false; 249 description = lib.mdDoc '' 250 Whether to configure nginx as a reverse proxy for Invidious. 251 252 It serves it under the domain specified in {option}`services.invidious.settings.domain` with enabled TLS and ACME. 253 Further configuration can be done through {option}`services.nginx.virtualHosts.''${config.services.invidious.settings.domain}.*`, 254 which can also be used to disable AMCE and TLS. 255 ''; 256 }; 257 }; 258 259 config = lib.mkIf cfg.enable (lib.mkMerge [ 260 serviceConfig 261 localDatabaseConfig 262 nginxConfig 263 ]); 264}