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