9 cfg = config.services.mediagoblin;
11 mkSubSectionKeyValue =
15 inherit (lib.strings) replicate;
17 "${replicate depth "["}${k}${replicate depth "]"}\n"
18 + lib.generators.toINIWithGlobalSection {
19 mkKeyValue = mkSubSectionKeyValue (depth + 1);
20 } { globalSection = v; }
22 lib.generators.mkKeyValueDefault {
23 mkValueString = v: if lib.isString v then ''"${v}"'' else lib.generators.mkValueStringDefault { } v;
26 iniFormat = pkgs.formats.ini { };
28 # we need to build our own GI_TYPELIB_PATH because celery and paster need this information, too and cannot easily be re-wrapped
32 (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.audio")
33 || (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.video");
35 lib.makeSearchPathOutput "out" "lib/girepository-1.0" (
42 # audio and video share most dependencies, so we can just take audio
43 ++ lib.optionals needsGst cfg.package.optional-dependencies.audio
46 finalPackage = cfg.package.python.buildEnv.override {
48 with cfg.package.python.pkgs;
50 (toPythonModule cfg.package)
53 # not documented in extras...
54 ++ lib.optional (lib.hasPrefix "postgresql://" cfg.settings.mediagoblin.sql_engine) psycopg2
57 inherit (cfg.settings.mediagoblin) plugins;
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
70 services.mediagoblin = {
71 enable = lib.mkOption {
72 type = lib.types.bool;
75 Whether to enable MediaGoblin.
77 After the initial deployment, make sure to add an admin account:
79 mediagoblin-gmg adduser --username admin --email admin@example.com
80 mediagoblin-gmg makeadmin admin
85 domain = lib.mkOption {
87 example = "mediagoblin.example.com";
88 description = "Domain under which mediagoblin will be served.";
91 createDatabaseLocally = lib.mkOption {
92 type = lib.types.bool;
95 description = "Whether to configure a local postgres database and connect to it.";
98 package = lib.mkPackageOption pkgs "mediagoblin" { };
100 pluginPackages = lib.mkOption {
101 type = with lib.types; listOf package;
103 description = "Plugins to add to the environment of MediaGoblin. They still need to be enabled in the config.";
106 settings = lib.mkOption {
107 description = "Settings which are written into `mediagoblin.ini`.";
109 type = lib.types.submodule {
110 freeformType = lib.types.anything;
114 allow_registration = lib.mkOption {
115 type = lib.types.bool;
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).
123 email_debug_mode = lib.mkOption {
124 type = lib.types.bool;
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)
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.";
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.";
148 plugins = lib.mkOption {
151 "mediagoblin.plugins.geolocation" = { };
152 "mediagoblin.plugins.processing_info" = { };
153 "mediagoblin.plugins.basic_auth" = { };
154 "mediagoblin.media_types.image" = { };
158 Plugins to enable. See [upstream docs](https://docs.mediagoblin.org/en/stable/siteadmin/plugins.html) for details.
159 Extra dependencies are automatically enabled.
168 port = lib.mkOption {
169 type = lib.types.port;
171 description = "Port under which paste will listen.";
174 settings = lib.mkOption {
175 description = "Settings which are written into `paste.ini`.";
177 type = lib.types.submodule {
178 freeformType = iniFormat.type;
185 config = lib.mkIf cfg.enable {
186 environment.systemPackages = [
187 (pkgs.writeShellScriptBin "mediagoblin-gmg" ''
189 if [[ "$USER" != mediagoblin ]]; then
190 sudo='exec /run/wrappers/bin/sudo -u mediagoblin'
192 $sudo sh -c "cd /var/lib/mediagoblin; env GI_TYPELIB_PATH=${GI_TYPELIB_PATH} ${lib.getExe' finalPackage "gmg"} $@"
197 mediagoblin.settings.mediagoblin = {
199 "mediagoblin.media_types.image" = { };
200 "mediagoblin.plugins.basic_auth" = { };
201 "mediagoblin.plugins.geolocation" = { };
202 "mediagoblin.plugins.processing_info" = { };
204 sql_engine = lib.mkIf cfg.createDatabaseLocally "postgresql:///mediagoblin";
209 recommendedGzipSettings = true;
210 recommendedProxySettings = true;
212 # see https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/nginx.conf.template
216 # https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/Dockerfile.nginx.in#L5
217 client_max_body_size 100M;
219 more_set_headers X-Content-Type-Options nosniff;
222 "/".proxyPass = "http://127.0.0.1:${toString cfg.paste.port}";
223 "/mgoblin_static/".alias = "${finalPackage}/${finalPackage.python.sitePackages}/mediagoblin/static/";
224 "/mgoblin_media/".alias = "/var/lib/mediagoblin/user_dev/media/public/";
225 "/theme_static/".alias = "/var/lib/mediagoblin/user_dev/theme_static/";
226 "/plugin_static/".alias = "/var/lib/mediagoblin/user_dev/plugin_static/";
232 postgresql = lib.mkIf cfg.createDatabaseLocally {
234 ensureDatabases = [ "mediagoblin" ];
237 name = "mediagoblin";
238 ensureDBOwnership = true;
243 rabbitmq.enable = true;
249 wantedBy = [ "multi-user.target" ];
251 lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.stl") [ pkgs.blender ]
252 ++ lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.pdf") (
260 AmbientCapabilities = "";
261 CapabilityBoundingSet = [ "" ];
262 DevicePolicy = "closed";
263 Group = "mediagoblin";
264 LockPersonality = true;
265 MemoryDenyWriteExecute = true;
266 NoNewPrivileges = true;
267 PrivateDevices = true;
270 ProtectControlGroups = true;
272 ProtectHostname = true;
273 ProtectKernelLogs = true;
274 ProtectKernelModules = true;
275 ProtectKernelTunables = true;
276 ProtectProc = "invisible";
277 ProtectSystem = "strict";
278 RestrictAddressFamilies = [
284 StateDirectory = "mediagoblin";
285 StateDirectoryMode = "0750";
286 User = "mediagoblin";
287 WorkingDirectory = "/var/lib/mediagoblin/";
288 RestrictNamespaces = true;
289 RestrictRealtime = true;
290 RestrictSUIDSGID = true;
291 SystemCallArchitectures = "native";
301 generatedPasteConfig = iniFormat.generate "paste.ini" cfg.paste.settings;
302 pasteConfig = pkgs.runCommand "paste-combined.ini" { nativeBuildInputs = [ pkgs.crudini ]; } ''
303 cp ${cfg.package.src}/paste.ini $out
305 crudini --merge $out < ${generatedPasteConfig}
309 mediagoblin-celeryd = lib.recursiveUpdate serviceDefaults {
310 # we cannot change DEFAULT.data_dir inside mediagoblin.ini because of an annoying bug
311 # https://todo.sr.ht/~mediagoblin/mediagoblin/57
313 cp --remove-destination ${
314 pkgs.writeText "mediagoblin.ini" (
315 lib.generators.toINI { } (lib.filterAttrsRecursive (n: v: n != "plugins") cfg.settings)
317 + lib.generators.toINI { mkKeyValue = mkSubSectionKeyValue 2; } {
318 inherit (cfg.settings.mediagoblin) plugins;
321 } /var/lib/mediagoblin/mediagoblin.ini
325 "CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_celery"
326 "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}"
327 "MEDIAGOBLIN_CONFIG=/var/lib/mediagoblin/mediagoblin.ini"
328 "PASTE_CONFIG=${pasteConfig}"
330 ExecStart = "${lib.getExe' finalPackage "celery"} worker --loglevel=INFO";
332 unitConfig.Description = "MediaGoblin Celery";
335 mediagoblin-paster = lib.recursiveUpdate serviceDefaults {
337 "mediagoblin-celeryd.service"
341 "mediagoblin-celeryd.service"
345 cp --remove-destination ${pasteConfig} /var/lib/mediagoblin/paste.ini
346 ${lib.getExe' finalPackage "gmg"} dbupdate
350 "CELERY_ALWAYS_EAGER=false"
351 "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}"
353 ExecStart = "${lib.getExe' finalPackage "paster"} serve /var/lib/mediagoblin/paste.ini";
355 unitConfig.Description = "Mediagoblin";
359 systemd.tmpfiles.settings."mediagoblin"."/var/lib/mediagoblin/user_dev".d = {
360 group = "mediagoblin";
362 user = "mediagoblin";
366 groups.mediagoblin = { };
369 group = "mediagoblin";
370 home = "/var/lib/mediagoblin";
373 nginx.extraGroups = [ "mediagoblin" ];