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