at 24.11-pre 25 kB view raw
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}