1{ config, lib, options, pkgs, ... }:
2
3let
4 cfg = config.services.forgejo;
5 opt = options.services.forgejo;
6 format = pkgs.formats.ini { };
7
8 exe = lib.getExe cfg.package;
9
10 pg = config.services.postgresql;
11 useMysql = cfg.database.type == "mysql";
12 usePostgresql = cfg.database.type == "postgres";
13 useSqlite = cfg.database.type == "sqlite3";
14
15 inherit (lib)
16 literalExpression
17 mkChangedOptionModule
18 mkDefault
19 mkEnableOption
20 mkIf
21 mkMerge
22 mkOption
23 mkPackageOption
24 mkRemovedOptionModule
25 mkRenamedOptionModule
26 optionalAttrs
27 optionals
28 optionalString
29 types
30 ;
31in
32{
33 imports = [
34 (mkRenamedOptionModule [ "services" "forgejo" "appName" ] [ "services" "forgejo" "settings" "DEFAULT" "APP_NAME" ])
35 (mkRemovedOptionModule [ "services" "forgejo" "extraConfig" ] "services.forgejo.extraConfig has been removed. Please use the freeform services.forgejo.settings option instead")
36 (mkRemovedOptionModule [ "services" "forgejo" "database" "password" ] "services.forgejo.database.password has been removed. Please use services.forgejo.database.passwordFile instead")
37
38 # copied from services.gitea; remove at some point
39 (mkRenamedOptionModule [ "services" "forgejo" "cookieSecure" ] [ "services" "forgejo" "settings" "session" "COOKIE_SECURE" ])
40 (mkRenamedOptionModule [ "services" "forgejo" "disableRegistration" ] [ "services" "forgejo" "settings" "service" "DISABLE_REGISTRATION" ])
41 (mkRenamedOptionModule [ "services" "forgejo" "domain" ] [ "services" "forgejo" "settings" "server" "DOMAIN" ])
42 (mkRenamedOptionModule [ "services" "forgejo" "httpAddress" ] [ "services" "forgejo" "settings" "server" "HTTP_ADDR" ])
43 (mkRenamedOptionModule [ "services" "forgejo" "httpPort" ] [ "services" "forgejo" "settings" "server" "HTTP_PORT" ])
44 (mkRenamedOptionModule [ "services" "forgejo" "log" "level" ] [ "services" "forgejo" "settings" "log" "LEVEL" ])
45 (mkRenamedOptionModule [ "services" "forgejo" "log" "rootPath" ] [ "services" "forgejo" "settings" "log" "ROOT_PATH" ])
46 (mkRenamedOptionModule [ "services" "forgejo" "rootUrl" ] [ "services" "forgejo" "settings" "server" "ROOT_URL" ])
47 (mkRenamedOptionModule [ "services" "forgejo" "ssh" "clonePort" ] [ "services" "forgejo" "settings" "server" "SSH_PORT" ])
48 (mkRenamedOptionModule [ "services" "forgejo" "staticRootPath" ] [ "services" "forgejo" "settings" "server" "STATIC_ROOT_PATH" ])
49 (mkChangedOptionModule [ "services" "forgejo" "enableUnixSocket" ] [ "services" "forgejo" "settings" "server" "PROTOCOL" ] (
50 config: if config.services.forgejo.enableUnixSocket then "http+unix" else "http"
51 ))
52 (mkRemovedOptionModule [ "services" "forgejo" "ssh" "enable" ] "services.forgejo.ssh.enable has been migrated into freeform setting services.forgejo.settings.server.DISABLE_SSH. Keep in mind that the setting is inverted")
53 ];
54
55 options = {
56 services.forgejo = {
57 enable = mkEnableOption "Forgejo, a software forge";
58
59 package = mkPackageOption pkgs "forgejo" { };
60
61 useWizard = mkOption {
62 default = false;
63 type = types.bool;
64 description = ''
65 Whether to use the built-in installation wizard instead of
66 declaratively managing the {file}`app.ini` config file in nix.
67 '';
68 };
69
70 stateDir = mkOption {
71 default = "/var/lib/forgejo";
72 type = types.str;
73 description = "Forgejo data directory.";
74 };
75
76 customDir = mkOption {
77 default = "${cfg.stateDir}/custom";
78 defaultText = literalExpression ''"''${config.${opt.stateDir}}/custom"'';
79 type = types.str;
80 description = ''
81 Base directory for custom templates and other options.
82
83 If {option}`${opt.useWizard}` is disabled (default), this directory will also
84 hold secrets and the resulting {file}`app.ini` config at runtime.
85 '';
86 };
87
88 user = mkOption {
89 type = types.str;
90 default = "forgejo";
91 description = "User account under which Forgejo runs.";
92 };
93
94 group = mkOption {
95 type = types.str;
96 default = "forgejo";
97 description = "Group under which Forgejo runs.";
98 };
99
100 database = {
101 type = mkOption {
102 type = types.enum [ "sqlite3" "mysql" "postgres" ];
103 example = "mysql";
104 default = "sqlite3";
105 description = "Database engine to use.";
106 };
107
108 host = mkOption {
109 type = types.str;
110 default = "127.0.0.1";
111 description = "Database host address.";
112 };
113
114 port = mkOption {
115 type = types.port;
116 default = if usePostgresql then pg.settings.port else 3306;
117 defaultText = literalExpression ''
118 if config.${opt.database.type} != "postgresql"
119 then 3306
120 else 5432
121 '';
122 description = "Database host port.";
123 };
124
125 name = mkOption {
126 type = types.str;
127 default = "forgejo";
128 description = "Database name.";
129 };
130
131 user = mkOption {
132 type = types.str;
133 default = "forgejo";
134 description = "Database user.";
135 };
136
137 passwordFile = mkOption {
138 type = types.nullOr types.path;
139 default = null;
140 example = "/run/keys/forgejo-dbpassword";
141 description = ''
142 A file containing the password corresponding to
143 {option}`${opt.database.user}`.
144 '';
145 };
146
147 socket = mkOption {
148 type = types.nullOr types.path;
149 default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
150 defaultText = literalExpression "null";
151 example = "/run/mysqld/mysqld.sock";
152 description = "Path to the unix socket file to use for authentication.";
153 };
154
155 path = mkOption {
156 type = types.str;
157 default = "${cfg.stateDir}/data/forgejo.db";
158 defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/forgejo.db"'';
159 description = "Path to the sqlite3 database file.";
160 };
161
162 createDatabase = mkOption {
163 type = types.bool;
164 default = true;
165 description = "Whether to create a local database automatically.";
166 };
167 };
168
169 dump = {
170 enable = mkEnableOption "periodic dumps via the [built-in {command}`dump` command](https://forgejo.org/docs/latest/admin/command-line/#dump)";
171
172 interval = mkOption {
173 type = types.str;
174 default = "04:31";
175 example = "hourly";
176 description = ''
177 Run a Forgejo dump at this interval. Runs by default at 04:31 every day.
178
179 The format is described in
180 {manpage}`systemd.time(7)`.
181 '';
182 };
183
184 backupDir = mkOption {
185 type = types.str;
186 default = "${cfg.stateDir}/dump";
187 defaultText = literalExpression ''"''${config.${opt.stateDir}}/dump"'';
188 description = "Path to the directory where the dump archives will be stored.";
189 };
190
191 type = mkOption {
192 type = types.enum [ "zip" "tar" "tar.sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" "tar.zst" ];
193 default = "zip";
194 description = "Archive format used to store the dump file.";
195 };
196
197 file = mkOption {
198 type = types.nullOr types.str;
199 default = null;
200 description = "Filename to be used for the dump. If `null` a default name is chosen by forgejo.";
201 example = "forgejo-dump";
202 };
203 };
204
205 lfs = {
206 enable = mkOption {
207 type = types.bool;
208 default = false;
209 description = "Enables git-lfs support.";
210 };
211
212 contentDir = mkOption {
213 type = types.str;
214 default = "${cfg.stateDir}/data/lfs";
215 defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/lfs"'';
216 description = "Where to store LFS files.";
217 };
218 };
219
220 repositoryRoot = mkOption {
221 type = types.str;
222 default = "${cfg.stateDir}/repositories";
223 defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
224 description = "Path to the git repositories.";
225 };
226
227 mailerPasswordFile = mkOption {
228 type = types.nullOr types.str;
229 default = null;
230 example = "/run/keys/forgejo-mailpw";
231 description = "Path to a file containing the SMTP password.";
232 };
233
234 settings = mkOption {
235 default = { };
236 description = ''
237 Free-form settings written directly to the `app.ini` configfile file.
238 Refer to <https://forgejo.org/docs/latest/admin/config-cheat-sheet/> for supported values.
239 '';
240 example = literalExpression ''
241 {
242 DEFAULT = {
243 RUN_MODE = "dev";
244 };
245 "cron.sync_external_users" = {
246 RUN_AT_START = true;
247 SCHEDULE = "@every 24h";
248 UPDATE_EXISTING = true;
249 };
250 mailer = {
251 ENABLED = true;
252 MAILER_TYPE = "sendmail";
253 FROM = "do-not-reply@example.org";
254 SENDMAIL_PATH = "''${pkgs.system-sendmail}/bin/sendmail";
255 };
256 other = {
257 SHOW_FOOTER_VERSION = false;
258 };
259 }
260 '';
261 type = types.submodule {
262 freeformType = format.type;
263 options = {
264 log = {
265 ROOT_PATH = mkOption {
266 default = "${cfg.stateDir}/log";
267 defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"'';
268 type = types.str;
269 description = "Root path for log files.";
270 };
271 LEVEL = mkOption {
272 default = "Info";
273 type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
274 description = "General log level.";
275 };
276 };
277
278 server = {
279 PROTOCOL = mkOption {
280 type = types.enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ];
281 default = "http";
282 description = ''Listen protocol. `+unix` means "over unix", not "in addition to."'';
283 };
284
285 HTTP_ADDR = mkOption {
286 type = types.either types.str types.path;
287 default = if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/forgejo/forgejo.sock" else "0.0.0.0";
288 defaultText = literalExpression ''if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/forgejo/forgejo.sock" else "0.0.0.0"'';
289 description = "Listen address. Must be a path when using a unix socket.";
290 };
291
292 HTTP_PORT = mkOption {
293 type = types.port;
294 default = 3000;
295 description = "Listen port. Ignored when using a unix socket.";
296 };
297
298 DOMAIN = mkOption {
299 type = types.str;
300 default = "localhost";
301 description = "Domain name of your server.";
302 };
303
304 ROOT_URL = mkOption {
305 type = types.str;
306 default = "http://${cfg.settings.server.DOMAIN}:${toString cfg.settings.server.HTTP_PORT}/";
307 defaultText = literalExpression ''"http://''${config.services.forgejo.settings.server.DOMAIN}:''${toString config.services.forgejo.settings.server.HTTP_PORT}/"'';
308 description = "Full public URL of Forgejo server.";
309 };
310
311 STATIC_ROOT_PATH = mkOption {
312 type = types.either types.str types.path;
313 default = cfg.package.data;
314 defaultText = literalExpression "config.${opt.package}.data";
315 example = "/var/lib/forgejo/data";
316 description = "Upper level of template and static files path.";
317 };
318
319 DISABLE_SSH = mkOption {
320 type = types.bool;
321 default = false;
322 description = "Disable external SSH feature.";
323 };
324
325 SSH_PORT = mkOption {
326 type = types.port;
327 default = 22;
328 example = 2222;
329 description = ''
330 SSH port displayed in clone URL.
331 The option is required to configure a service when the external visible port
332 differs from the local listening port i.e. if port forwarding is used.
333 '';
334 };
335 };
336
337 session = {
338 COOKIE_SECURE = mkOption {
339 type = types.bool;
340 default = false;
341 description = ''
342 Marks session cookies as "secure" as a hint for browsers to only send
343 them via HTTPS. This option is recommend, if Forgejo is being served over HTTPS.
344 '';
345 };
346 };
347 };
348 };
349 };
350 };
351 };
352
353 config = mkIf cfg.enable {
354 assertions = [
355 {
356 assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
357 message = "services.forgejo.database.user must match services.forgejo.user if the database is to be automatically provisioned";
358 }
359 { assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
360 message = ''
361 When creating a database via NixOS, the db user and db name must be equal!
362 If you already have an existing DB+user and this assertion is new, you can safely set
363 `services.forgejo.createDatabase` to `false` because removal of `ensureUsers`
364 and `ensureDatabases` doesn't have any effect.
365 '';
366 }
367 ];
368
369 services.forgejo.settings = {
370 DEFAULT = {
371 RUN_MODE = mkDefault "prod";
372 RUN_USER = mkDefault cfg.user;
373 WORK_PATH = mkDefault cfg.stateDir;
374 };
375
376 database = mkMerge [
377 {
378 DB_TYPE = cfg.database.type;
379 }
380 (mkIf (useMysql || usePostgresql) {
381 HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
382 NAME = cfg.database.name;
383 USER = cfg.database.user;
384 PASSWD = "#dbpass#";
385 })
386 (mkIf useSqlite {
387 PATH = cfg.database.path;
388 })
389 (mkIf usePostgresql {
390 SSL_MODE = "disable";
391 })
392 ];
393
394 repository = {
395 ROOT = cfg.repositoryRoot;
396 };
397
398 server = mkIf cfg.lfs.enable {
399 LFS_START_SERVER = true;
400 LFS_JWT_SECRET = "#lfsjwtsecret#";
401 };
402
403 session = {
404 COOKIE_NAME = mkDefault "session";
405 };
406
407 security = {
408 SECRET_KEY = "#secretkey#";
409 INTERNAL_TOKEN = "#internaltoken#";
410 INSTALL_LOCK = true;
411 };
412
413 mailer = mkIf (cfg.mailerPasswordFile != null) {
414 PASSWD = "#mailerpass#";
415 };
416
417 oauth2 = {
418 JWT_SECRET = "#oauth2jwtsecret#";
419 };
420
421 lfs = mkIf cfg.lfs.enable {
422 PATH = cfg.lfs.contentDir;
423 };
424 };
425
426 services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
427 enable = mkDefault true;
428
429 ensureDatabases = [ cfg.database.name ];
430 ensureUsers = [
431 {
432 name = cfg.database.user;
433 ensureDBOwnership = true;
434 }
435 ];
436 };
437
438 services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
439 enable = mkDefault true;
440 package = mkDefault pkgs.mariadb;
441
442 ensureDatabases = [ cfg.database.name ];
443 ensureUsers = [
444 {
445 name = cfg.database.user;
446 ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
447 }
448 ];
449 };
450
451 systemd.tmpfiles.rules = [
452 "d '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
453 "z '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
454 "d '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
455 "z '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
456 "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
457 "d '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
458 "d '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
459 "d '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
460 "d '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
461 "d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
462 "z '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
463 "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} ${cfg.group} - -"
464 "z '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
465 "z '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
466 "z '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
467 "z '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
468 "z '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
469
470 # If we have a folder or symlink with Forgejo locales, remove it
471 # And symlink the current Forgejo locales in place
472 "L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale"
473
474 ] ++ optionals cfg.lfs.enable [
475 "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
476 "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
477 ];
478
479 systemd.services.forgejo = {
480 description = "Forgejo (Beyond coding. We forge.)";
481 after = [
482 "network.target"
483 ] ++ optionals usePostgresql [
484 "postgresql.service"
485 ] ++ optionals useMysql [
486 "mysql.service"
487 ];
488 requires = optionals (cfg.database.createDatabase && usePostgresql) [
489 "postgresql.service"
490 ] ++ optionals (cfg.database.createDatabase && useMysql) [
491 "mysql.service"
492 ];
493 wantedBy = [ "multi-user.target" ];
494 path = [ cfg.package pkgs.git pkgs.gnupg ];
495
496 # In older versions the secret naming for JWT was kind of confusing.
497 # The file jwt_secret hold the value for LFS_JWT_SECRET and JWT_SECRET
498 # wasn't persistent at all.
499 # To fix that, there is now the file oauth2_jwt_secret containing the
500 # values for JWT_SECRET and the file jwt_secret gets renamed to
501 # lfs_jwt_secret.
502 # We have to consider this to stay compatible with older installations.
503 preStart =
504 let
505 runConfig = "${cfg.customDir}/conf/app.ini";
506 secretKey = "${cfg.customDir}/conf/secret_key";
507 oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret";
508 oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET
509 lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
510 internalToken = "${cfg.customDir}/conf/internal_token";
511 replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
512 in
513 ''
514 # copy custom configuration and generate random secrets if needed
515 ${lib.optionalString (!cfg.useWizard) ''
516 function forgejo_setup {
517 cp -f '${format.generate "app.ini" cfg.settings}' '${runConfig}'
518
519 if [ ! -s '${secretKey}' ]; then
520 ${exe} generate secret SECRET_KEY > '${secretKey}'
521 fi
522
523 # Migrate LFS_JWT_SECRET filename
524 if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then
525 mv '${oldLfsJwtSecret}' '${lfsJwtSecret}'
526 fi
527
528 if [ ! -s '${oauth2JwtSecret}' ]; then
529 ${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}'
530 fi
531
532 ${optionalString cfg.lfs.enable ''
533 if [ ! -s '${lfsJwtSecret}' ]; then
534 ${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}'
535 fi
536 ''}
537
538 if [ ! -s '${internalToken}' ]; then
539 ${exe} generate secret INTERNAL_TOKEN > '${internalToken}'
540 fi
541
542 chmod u+w '${runConfig}'
543 ${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
544 ${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
545 ${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
546
547 ${optionalString cfg.lfs.enable ''
548 ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
549 ''}
550
551 ${optionalString (cfg.database.passwordFile != null) ''
552 ${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
553 ''}
554
555 ${optionalString (cfg.mailerPasswordFile != null) ''
556 ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
557 ''}
558 chmod u-w '${runConfig}'
559 }
560 (umask 027; forgejo_setup)
561 ''}
562
563 # run migrations/init the database
564 ${exe} migrate
565
566 # update all hooks' binary paths
567 ${exe} admin regenerate hooks
568
569 # update command option in authorized_keys
570 if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
571 then
572 ${exe} admin regenerate keys
573 fi
574 '';
575
576 serviceConfig = {
577 Type = "simple";
578 User = cfg.user;
579 Group = cfg.group;
580 WorkingDirectory = cfg.stateDir;
581 ExecStart = "${exe} web --pid /run/forgejo/forgejo.pid";
582 Restart = "always";
583 # Runtime directory and mode
584 RuntimeDirectory = "forgejo";
585 RuntimeDirectoryMode = "0755";
586 # Proc filesystem
587 ProcSubset = "pid";
588 ProtectProc = "invisible";
589 # Access write directories
590 ReadWritePaths = [ cfg.customDir cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
591 UMask = "0027";
592 # Capabilities
593 CapabilityBoundingSet = "";
594 # Security
595 NoNewPrivileges = true;
596 # Sandboxing
597 ProtectSystem = "strict";
598 ProtectHome = true;
599 PrivateTmp = true;
600 PrivateDevices = true;
601 PrivateUsers = true;
602 ProtectHostname = true;
603 ProtectClock = true;
604 ProtectKernelTunables = true;
605 ProtectKernelModules = true;
606 ProtectKernelLogs = true;
607 ProtectControlGroups = true;
608 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
609 RestrictNamespaces = true;
610 LockPersonality = true;
611 MemoryDenyWriteExecute = true;
612 RestrictRealtime = true;
613 RestrictSUIDSGID = true;
614 RemoveIPC = true;
615 PrivateMounts = true;
616 # System Call Filtering
617 SystemCallArchitectures = "native";
618 SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" "setrlimit" ];
619 };
620
621 environment = {
622 USER = cfg.user;
623 HOME = cfg.stateDir;
624 # `GITEA_` prefix until https://codeberg.org/forgejo/forgejo/issues/497
625 # is resolved.
626 GITEA_WORK_DIR = cfg.stateDir;
627 GITEA_CUSTOM = cfg.customDir;
628 };
629 };
630
631 services.openssh.settings.AcceptEnv = mkIf (!cfg.settings.START_SSH_SERVER or false) "GIT_PROTOCOL";
632
633 users.users = mkIf (cfg.user == "forgejo") {
634 forgejo = {
635 home = cfg.stateDir;
636 useDefaultShell = true;
637 group = cfg.group;
638 isSystemUser = true;
639 };
640 };
641
642 users.groups = mkIf (cfg.group == "forgejo") {
643 forgejo = { };
644 };
645
646 systemd.services.forgejo-dump = mkIf cfg.dump.enable {
647 description = "forgejo dump";
648 after = [ "forgejo.service" ];
649 path = [ cfg.package ];
650
651 environment = {
652 USER = cfg.user;
653 HOME = cfg.stateDir;
654 # `GITEA_` prefix until https://codeberg.org/forgejo/forgejo/issues/497
655 # is resolved.
656 GITEA_WORK_DIR = cfg.stateDir;
657 GITEA_CUSTOM = cfg.customDir;
658 };
659
660 serviceConfig = {
661 Type = "oneshot";
662 User = cfg.user;
663 ExecStart = "${exe} dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
664 WorkingDirectory = cfg.dump.backupDir;
665 };
666 };
667
668 systemd.timers.forgejo-dump = mkIf cfg.dump.enable {
669 description = "Forgejo dump timer";
670 partOf = [ "forgejo-dump.service" ];
671 wantedBy = [ "timers.target" ];
672 timerConfig.OnCalendar = cfg.dump.interval;
673 };
674 };
675
676 meta.doc = ./forgejo.md;
677 meta.maintainers = with lib.maintainers; [ bendlas emilylange ];
678}