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