signalbackup-tools: 20241220 -> 20250106 (#371523)
[NixPkgs.git] / nixos / modules / services / web-apps / mediagoblin.nix
bloba6f2e5cdd0a3c6d48abe858e85687e464276eb03
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 let
9   cfg = config.services.mediagoblin;
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;
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
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     );
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   };
69   options = {
70     services.mediagoblin = {
71       enable = lib.mkOption {
72         type = lib.types.bool;
73         default = false;
74         description = ''
75           Whether to enable MediaGoblin.
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       };
85       domain = lib.mkOption {
86         type = lib.types.str;
87         example = "mediagoblin.example.com";
88         description = "Domain under which mediagoblin will be served.";
89       };
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       };
98       package = lib.mkPackageOption pkgs "mediagoblin" { };
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       };
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;
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               };
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               };
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               };
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               };
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       };
167       paste = {
168         port = lib.mkOption {
169           type = lib.types.port;
170           default = 6543;
171           description = "Port under which paste will listen.";
172         };
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   };
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     ];
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       };
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;
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 = "${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/";
227             };
228           };
229         };
230       };
232       postgresql = lib.mkIf cfg.createDatabaseLocally {
233         enable = true;
234         ensureDatabases = [ "mediagoblin" ];
235         ensureUsers = [
236           {
237             name = "mediagoblin";
238             ensureDBOwnership = true;
239           }
240         ];
241       };
243       rabbitmq.enable = true;
244     };
246     systemd.services =
247       let
248         serviceDefaults = {
249           wantedBy = [ "multi-user.target" ];
250           path =
251             lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.stl") [ pkgs.blender ]
252             ++ lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.pdf") (
253               with pkgs;
254               [
255                 poppler_utils
256                 unoconv
257               ]
258             );
259           serviceConfig = {
260             AmbientCapabilities = "";
261             CapabilityBoundingSet = [ "" ];
262             DevicePolicy = "closed";
263             Group = "mediagoblin";
264             LockPersonality = true;
265             MemoryDenyWriteExecute = true;
266             NoNewPrivileges = true;
267             PrivateDevices = true;
268             PrivateTmp = true;
269             ProcSubset = "pid";
270             ProtectControlGroups = true;
271             ProtectHome = true;
272             ProtectHostname = true;
273             ProtectKernelLogs = true;
274             ProtectKernelModules = true;
275             ProtectKernelTunables = true;
276             ProtectProc = "invisible";
277             ProtectSystem = "strict";
278             RestrictAddressFamilies = [
279               "AF_INET"
280               "AF_INET6"
281               "AF_UNIX"
282             ];
283             RemoveIPC = true;
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";
292             SystemCallFilter = [
293               "@system-service"
294               "~@privileged"
295               "@chown"
296             ];
297             UMask = "0027";
298           };
299         };
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
304           chmod +w $out
305           crudini --merge $out < ${generatedPasteConfig}
306         '';
307       in
308       {
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
312           preStart = ''
313             cp --remove-destination ${
314               pkgs.writeText "mediagoblin.ini" (
315                 lib.generators.toINI { } (lib.filterAttrsRecursive (n: v: n != "plugins") cfg.settings)
316                 + "\n"
317                 + lib.generators.toINI { mkKeyValue = mkSubSectionKeyValue 2; } {
318                   inherit (cfg.settings.mediagoblin) plugins;
319                 }
320               )
321             } /var/lib/mediagoblin/mediagoblin.ini
322           '';
323           serviceConfig = {
324             Environment = [
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}"
329             ];
330             ExecStart = "${lib.getExe' finalPackage "celery"} worker --loglevel=INFO";
331           };
332           unitConfig.Description = "MediaGoblin Celery";
333         };
335         mediagoblin-paster = lib.recursiveUpdate serviceDefaults {
336           after = [
337             "mediagoblin-celeryd.service"
338             "postgresql.service"
339           ];
340           requires = [
341             "mediagoblin-celeryd.service"
342             "postgresql.service"
343           ];
344           preStart = ''
345             cp --remove-destination ${pasteConfig} /var/lib/mediagoblin/paste.ini
346             ${lib.getExe' finalPackage "gmg"} dbupdate
347           '';
348           serviceConfig = {
349             Environment = [
350               "CELERY_ALWAYS_EAGER=false"
351               "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}"
352             ];
353             ExecStart = "${lib.getExe' finalPackage "paster"} serve /var/lib/mediagoblin/paste.ini";
354           };
355           unitConfig.Description = "Mediagoblin";
356         };
357       };
359     systemd.tmpfiles.settings."mediagoblin"."/var/lib/mediagoblin/user_dev".d = {
360       group = "mediagoblin";
361       mode = "2750";
362       user = "mediagoblin";
363     };
365     users = {
366       groups.mediagoblin = { };
367       users = {
368         mediagoblin = {
369           group = "mediagoblin";
370           home = "/var/lib/mediagoblin";
371           isSystemUser = true;
372         };
373         nginx.extraGroups = [ "mediagoblin" ];
374       };
375     };
376   };