at 25.11-pre 13 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.services.mediagoblin; 10 11 mkSubSectionKeyValue = 12 depth: k: v: 13 if lib.isAttrs v then 14 let 15 inherit (lib.strings) replicate; 16 in 17 "${replicate depth "["}${k}${replicate depth "]"}\n" 18 + lib.generators.toINIWithGlobalSection { 19 mkKeyValue = mkSubSectionKeyValue (depth + 1); 20 } { globalSection = v; } 21 else 22 lib.generators.mkKeyValueDefault { 23 mkValueString = v: if lib.isString v then ''"${v}"'' else lib.generators.mkValueStringDefault { } v; 24 } " = " k v; 25 26 iniFormat = pkgs.formats.ini { }; 27 28 # we need to build our own GI_TYPELIB_PATH because celery and paster need this information, too and cannot easily be re-wrapped 29 GI_TYPELIB_PATH = 30 let 31 needsGst = 32 (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.audio") 33 || (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.video"); 34 in 35 lib.makeSearchPathOutput "out" "lib/girepository-1.0" ( 36 with pkgs.gst_all_1; 37 [ 38 pkgs.glib 39 gst-plugins-base 40 gstreamer 41 ] 42 # audio and video share most dependencies, so we can just take audio 43 ++ lib.optionals needsGst cfg.package.optional-dependencies.audio 44 ); 45 46 finalPackage = cfg.package.python.buildEnv.override { 47 extraLibs = 48 with cfg.package.python.pkgs; 49 [ 50 (toPythonModule cfg.package) 51 ] 52 ++ cfg.pluginPackages 53 # not documented in extras... 54 ++ lib.optional (lib.hasPrefix "postgresql://" cfg.settings.mediagoblin.sql_engine) psycopg2 55 ++ ( 56 let 57 inherit (cfg.settings.mediagoblin) plugins; 58 in 59 with cfg.package.optional-dependencies; 60 lib.optionals (plugins ? "mediagoblin.media_types.audio") audio 61 ++ lib.optionals (plugins ? "mediagoblin.media_types.video") video 62 ++ lib.optionals (plugins ? "mediagoblin.media_types.raw_image") raw_image 63 ++ lib.optionals (plugins ? "mediagoblin.media_types.ascii") ascii 64 ++ lib.optionals (plugins ? "mediagoblin.plugins.ldap") ldap 65 ); 66 }; 67in 68{ 69 options = { 70 services.mediagoblin = { 71 enable = lib.mkOption { 72 type = lib.types.bool; 73 default = false; 74 description = '' 75 Whether to enable MediaGoblin. 76 77 After the initial deployment, make sure to add an admin account: 78 ``` 79 mediagoblin-gmg adduser --username admin --email admin@example.com 80 mediagoblin-gmg makeadmin admin 81 ``` 82 ''; 83 }; 84 85 domain = lib.mkOption { 86 type = lib.types.str; 87 example = "mediagoblin.example.com"; 88 description = "Domain under which mediagoblin will be served."; 89 }; 90 91 createDatabaseLocally = lib.mkOption { 92 type = lib.types.bool; 93 default = true; 94 example = false; 95 description = "Whether to configure a local postgres database and connect to it."; 96 }; 97 98 package = lib.mkPackageOption pkgs "mediagoblin" { }; 99 100 pluginPackages = lib.mkOption { 101 type = with lib.types; listOf package; 102 default = [ ]; 103 description = "Plugins to add to the environment of MediaGoblin. They still need to be enabled in the config."; 104 }; 105 106 settings = lib.mkOption { 107 description = "Settings which are written into `mediagoblin.ini`."; 108 default = { }; 109 type = lib.types.submodule { 110 freeformType = lib.types.anything; 111 112 options = { 113 mediagoblin = { 114 allow_registration = lib.mkOption { 115 type = lib.types.bool; 116 default = false; 117 description = '' 118 Whether to enable user self registration. This is generally not recommend due to spammers. 119 See [upstream FAQ](https://docs.mediagoblin.org/en/stable/siteadmin/production-deployments.html#should-i-keep-open-registration-enabled). 120 ''; 121 }; 122 123 email_debug_mode = lib.mkOption { 124 type = lib.types.bool; 125 default = true; 126 example = false; 127 description = '' 128 Disable email debug mode to start sending outgoing mails. 129 This requires configuring SMTP settings, 130 see the [upstream docs](https://docs.mediagoblin.org/en/stable/siteadmin/configuration.html#enabling-email-notifications) 131 for details. 132 ''; 133 }; 134 135 email_sender_address = lib.mkOption { 136 type = lib.types.str; 137 example = "noreply@example.org"; 138 description = "Email address which notices are sent from."; 139 }; 140 141 sql_engine = lib.mkOption { 142 type = lib.types.str; 143 default = "sqlite:///var/lib/mediagoblin/mediagoblin.db"; 144 example = "postgresql:///mediagoblin"; 145 description = "Database to use."; 146 }; 147 148 plugins = lib.mkOption { 149 defaultText = '' 150 { 151 "mediagoblin.plugins.geolocation" = { }; 152 "mediagoblin.plugins.processing_info" = { }; 153 "mediagoblin.plugins.basic_auth" = { }; 154 "mediagoblin.media_types.image" = { }; 155 } 156 ''; 157 description = '' 158 Plugins to enable. See [upstream docs](https://docs.mediagoblin.org/en/stable/siteadmin/plugins.html) for details. 159 Extra dependencies are automatically enabled. 160 ''; 161 }; 162 }; 163 }; 164 }; 165 }; 166 167 paste = { 168 port = lib.mkOption { 169 type = lib.types.port; 170 default = 6543; 171 description = "Port under which paste will listen."; 172 }; 173 174 settings = lib.mkOption { 175 description = "Settings which are written into `paste.ini`."; 176 default = { }; 177 type = lib.types.submodule { 178 freeformType = iniFormat.type; 179 }; 180 }; 181 }; 182 }; 183 }; 184 185 config = lib.mkIf cfg.enable { 186 environment.systemPackages = [ 187 (pkgs.writeShellScriptBin "mediagoblin-gmg" '' 188 sudo=exec 189 if [[ "$USER" != mediagoblin ]]; then 190 sudo='exec /run/wrappers/bin/sudo -u mediagoblin' 191 fi 192 $sudo sh -c "cd /var/lib/mediagoblin; env GI_TYPELIB_PATH=${GI_TYPELIB_PATH} ${lib.getExe' finalPackage "gmg"} $@" 193 '') 194 ]; 195 196 services = { 197 mediagoblin.settings.mediagoblin = { 198 plugins = { 199 "mediagoblin.media_types.image" = { }; 200 "mediagoblin.plugins.basic_auth" = { }; 201 "mediagoblin.plugins.geolocation" = { }; 202 "mediagoblin.plugins.processing_info" = { }; 203 }; 204 sql_engine = lib.mkIf cfg.createDatabaseLocally "postgresql:///mediagoblin"; 205 }; 206 207 nginx = { 208 enable = true; 209 recommendedGzipSettings = true; 210 recommendedProxySettings = true; 211 virtualHosts = { 212 # see https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/nginx.conf.template 213 "${cfg.domain}" = { 214 forceSSL = true; 215 extraConfig = '' 216 # https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/Dockerfile.nginx.in#L5 217 client_max_body_size 100M; 218 219 more_set_headers X-Content-Type-Options nosniff; 220 ''; 221 locations = { 222 "/".proxyPass = "http://127.0.0.1:${toString cfg.paste.port}"; 223 "/mgoblin_static/".alias = 224 "${finalPackage}/${finalPackage.python.sitePackages}/mediagoblin/static/"; 225 "/mgoblin_media/".alias = "/var/lib/mediagoblin/user_dev/media/public/"; 226 "/theme_static/".alias = "/var/lib/mediagoblin/user_dev/theme_static/"; 227 "/plugin_static/".alias = "/var/lib/mediagoblin/user_dev/plugin_static/"; 228 }; 229 }; 230 }; 231 }; 232 233 postgresql = lib.mkIf cfg.createDatabaseLocally { 234 enable = true; 235 ensureDatabases = [ "mediagoblin" ]; 236 ensureUsers = [ 237 { 238 name = "mediagoblin"; 239 ensureDBOwnership = true; 240 } 241 ]; 242 }; 243 244 rabbitmq.enable = true; 245 }; 246 247 systemd.services = 248 let 249 serviceDefaults = { 250 wantedBy = [ "multi-user.target" ]; 251 path = 252 lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.stl") [ pkgs.blender ] 253 ++ lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.pdf") ( 254 with pkgs; 255 [ 256 poppler-utils 257 unoconv 258 ] 259 ); 260 serviceConfig = { 261 AmbientCapabilities = ""; 262 CapabilityBoundingSet = [ "" ]; 263 DevicePolicy = "closed"; 264 Group = "mediagoblin"; 265 LockPersonality = true; 266 MemoryDenyWriteExecute = true; 267 NoNewPrivileges = true; 268 PrivateDevices = true; 269 PrivateTmp = true; 270 ProcSubset = "pid"; 271 ProtectControlGroups = true; 272 ProtectHome = true; 273 ProtectHostname = true; 274 ProtectKernelLogs = true; 275 ProtectKernelModules = true; 276 ProtectKernelTunables = true; 277 ProtectProc = "invisible"; 278 ProtectSystem = "strict"; 279 RestrictAddressFamilies = [ 280 "AF_INET" 281 "AF_INET6" 282 "AF_UNIX" 283 ]; 284 RemoveIPC = true; 285 StateDirectory = "mediagoblin"; 286 StateDirectoryMode = "0750"; 287 User = "mediagoblin"; 288 WorkingDirectory = "/var/lib/mediagoblin/"; 289 RestrictNamespaces = true; 290 RestrictRealtime = true; 291 RestrictSUIDSGID = true; 292 SystemCallArchitectures = "native"; 293 SystemCallFilter = [ 294 "@system-service" 295 "~@privileged" 296 "@chown" 297 ]; 298 UMask = "0027"; 299 }; 300 }; 301 302 generatedPasteConfig = iniFormat.generate "paste.ini" cfg.paste.settings; 303 pasteConfig = pkgs.runCommand "paste-combined.ini" { nativeBuildInputs = [ pkgs.crudini ]; } '' 304 cp ${cfg.package.src}/paste.ini $out 305 chmod +w $out 306 crudini --merge $out < ${generatedPasteConfig} 307 ''; 308 in 309 { 310 mediagoblin-celeryd = lib.recursiveUpdate serviceDefaults { 311 # we cannot change DEFAULT.data_dir inside mediagoblin.ini because of an annoying bug 312 # https://todo.sr.ht/~mediagoblin/mediagoblin/57 313 preStart = '' 314 cp --remove-destination ${ 315 pkgs.writeText "mediagoblin.ini" ( 316 lib.generators.toINI { } (lib.filterAttrsRecursive (n: v: n != "plugins") cfg.settings) 317 + "\n" 318 + lib.generators.toINI { mkKeyValue = mkSubSectionKeyValue 2; } { 319 inherit (cfg.settings.mediagoblin) plugins; 320 } 321 ) 322 } /var/lib/mediagoblin/mediagoblin.ini 323 ''; 324 serviceConfig = { 325 Environment = [ 326 "CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_celery" 327 "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}" 328 "MEDIAGOBLIN_CONFIG=/var/lib/mediagoblin/mediagoblin.ini" 329 "PASTE_CONFIG=${pasteConfig}" 330 ]; 331 ExecStart = "${lib.getExe' finalPackage "celery"} worker --loglevel=INFO"; 332 }; 333 unitConfig.Description = "MediaGoblin Celery"; 334 }; 335 336 mediagoblin-paster = lib.recursiveUpdate serviceDefaults { 337 after = [ 338 "mediagoblin-celeryd.service" 339 "postgresql.service" 340 ]; 341 requires = [ 342 "mediagoblin-celeryd.service" 343 "postgresql.service" 344 ]; 345 preStart = '' 346 cp --remove-destination ${pasteConfig} /var/lib/mediagoblin/paste.ini 347 ${lib.getExe' finalPackage "gmg"} dbupdate 348 ''; 349 serviceConfig = { 350 Environment = [ 351 "CELERY_ALWAYS_EAGER=false" 352 "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}" 353 ]; 354 ExecStart = "${lib.getExe' finalPackage "paster"} serve /var/lib/mediagoblin/paste.ini"; 355 }; 356 unitConfig.Description = "Mediagoblin"; 357 }; 358 }; 359 360 systemd.tmpfiles.settings."mediagoblin"."/var/lib/mediagoblin/user_dev".d = { 361 group = "mediagoblin"; 362 mode = "2750"; 363 user = "mediagoblin"; 364 }; 365 366 users = { 367 groups.mediagoblin = { }; 368 users = { 369 mediagoblin = { 370 group = "mediagoblin"; 371 home = "/var/lib/mediagoblin"; 372 isSystemUser = true; 373 }; 374 nginx.extraGroups = [ "mediagoblin" ]; 375 }; 376 }; 377 }; 378}