diff options
author | Herwig Hochleitner <herwig@bendlas.net> | 2023-09-12 22:10:01 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-12 22:10:01 +0200 |
commit | 30bcc6c39d1eddfca5eedebe2c2c785abcd7daa5 (patch) | |
tree | ded3e66e610dd9d4d574a88392396110ce1ee262 /nixos/modules | |
parent | 1dd3b051e957919fbdd8f678700c0a67ab31ac78 (diff) | |
parent | 7b786b39cb0d42949720482b78c31fcfe35b41c7 (diff) | |
download | nixlib-30bcc6c39d1eddfca5eedebe2c2c785abcd7daa5.tar nixlib-30bcc6c39d1eddfca5eedebe2c2c785abcd7daa5.tar.gz nixlib-30bcc6c39d1eddfca5eedebe2c2c785abcd7daa5.tar.bz2 nixlib-30bcc6c39d1eddfca5eedebe2c2c785abcd7daa5.tar.lz nixlib-30bcc6c39d1eddfca5eedebe2c2c785abcd7daa5.tar.xz nixlib-30bcc6c39d1eddfca5eedebe2c2c785abcd7daa5.tar.zst nixlib-30bcc6c39d1eddfca5eedebe2c2c785abcd7daa5.zip |
Merge pull request #248310 from emilylange/nixos/gitea-forgejo-split
nixos/forgejo: fork from nixos/gitea (split) close https://github.com/NixOS/nixpkgs/issues/244866
Diffstat (limited to 'nixos/modules')
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/misc/forgejo.nix | 668 |
2 files changed, 669 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 05cf792e3fcb..6479fd6e7df7 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -646,6 +646,7 @@ ./services/misc/etesync-dav.nix ./services/misc/evdevremapkeys.nix ./services/misc/felix.nix + ./services/misc/forgejo.nix ./services/misc/freeswitch.nix ./services/misc/fstrim.nix ./services/misc/gammu-smsd.nix diff --git a/nixos/modules/services/misc/forgejo.nix b/nixos/modules/services/misc/forgejo.nix new file mode 100644 index 000000000000..f26658b7bcb4 --- /dev/null +++ b/nixos/modules/services/misc/forgejo.nix @@ -0,0 +1,668 @@ +{ config, lib, options, pkgs, ... }: + +let + cfg = config.services.forgejo; + opt = options.services.forgejo; + format = pkgs.formats.ini { }; + + exe = lib.getExe cfg.package; + + pg = config.services.postgresql; + useMysql = cfg.database.type == "mysql"; + usePostgresql = cfg.database.type == "postgres"; + useSqlite = cfg.database.type == "sqlite3"; + + inherit (lib) + literalExpression + mdDoc + mkChangedOptionModule + mkDefault + mkEnableOption + mkIf + mkMerge + mkOption + mkPackageOptionMD + mkRemovedOptionModule + mkRenamedOptionModule + optionalAttrs + optionals + optionalString + types + ; +in +{ + imports = [ + (mkRenamedOptionModule [ "services" "forgejo" "appName" ] [ "services" "forgejo" "settings" "DEFAULT" "APP_NAME" ]) + (mkRemovedOptionModule [ "services" "forgejo" "extraConfig" ] "services.forgejo.extraConfig has been removed. Please use the freeform services.forgejo.settings option instead") + (mkRemovedOptionModule [ "services" "forgejo" "database" "password" ] "services.forgejo.database.password has been removed. Please use services.forgejo.database.passwordFile instead") + + # copied from services.gitea; remove at some point + (mkRenamedOptionModule [ "services" "forgejo" "cookieSecure" ] [ "services" "forgejo" "settings" "session" "COOKIE_SECURE" ]) + (mkRenamedOptionModule [ "services" "forgejo" "disableRegistration" ] [ "services" "forgejo" "settings" "service" "DISABLE_REGISTRATION" ]) + (mkRenamedOptionModule [ "services" "forgejo" "domain" ] [ "services" "forgejo" "settings" "server" "DOMAIN" ]) + (mkRenamedOptionModule [ "services" "forgejo" "httpAddress" ] [ "services" "forgejo" "settings" "server" "HTTP_ADDR" ]) + (mkRenamedOptionModule [ "services" "forgejo" "httpPort" ] [ "services" "forgejo" "settings" "server" "HTTP_PORT" ]) + (mkRenamedOptionModule [ "services" "forgejo" "log" "level" ] [ "services" "forgejo" "settings" "log" "LEVEL" ]) + (mkRenamedOptionModule [ "services" "forgejo" "log" "rootPath" ] [ "services" "forgejo" "settings" "log" "ROOT_PATH" ]) + (mkRenamedOptionModule [ "services" "forgejo" "rootUrl" ] [ "services" "forgejo" "settings" "server" "ROOT_URL" ]) + (mkRenamedOptionModule [ "services" "forgejo" "ssh" "clonePort" ] [ "services" "forgejo" "settings" "server" "SSH_PORT" ]) + (mkRenamedOptionModule [ "services" "forgejo" "staticRootPath" ] [ "services" "forgejo" "settings" "server" "STATIC_ROOT_PATH" ]) + (mkChangedOptionModule [ "services" "forgejo" "enableUnixSocket" ] [ "services" "forgejo" "settings" "server" "PROTOCOL" ] ( + config: if config.services.forgejo.enableUnixSocket then "http+unix" else "http" + )) + (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") + ]; + + options = { + services.forgejo = { + enable = mkEnableOption (mdDoc "Forgejo"); + + package = mkPackageOptionMD pkgs "forgejo" { }; + + useWizard = mkOption { + default = false; + type = types.bool; + description = mdDoc '' + Whether to use the built-in installation wizard instead of + declaratively managing the {file}`app.ini` config file in nix. + ''; + }; + + stateDir = mkOption { + default = "/var/lib/forgejo"; + type = types.str; + description = mdDoc "Forgejo data directory."; + }; + + customDir = mkOption { + default = "${cfg.stateDir}/custom"; + defaultText = literalExpression ''"''${config.${opt.stateDir}}/custom"''; + type = types.str; + description = mdDoc '' + Base directory for custom templates and other options. + + If {option}`${opt.useWizard}` is disabled (default), this directory will also + hold secrets and the resulting {file}`app.ini` config at runtime. + ''; + }; + + user = mkOption { + type = types.str; + default = "forgejo"; + description = mdDoc "User account under which Forgejo runs."; + }; + + group = mkOption { + type = types.str; + default = "forgejo"; + description = mdDoc "Group under which Forgejo runs."; + }; + + database = { + type = mkOption { + type = types.enum [ "sqlite3" "mysql" "postgres" ]; + example = "mysql"; + default = "sqlite3"; + description = mdDoc "Database engine to use."; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = mdDoc "Database host address."; + }; + + port = mkOption { + type = types.port; + default = if !usePostgresql then 3306 else pg.port; + defaultText = literalExpression '' + if config.${opt.database.type} != "postgresql" + then 3306 + else config.${options.services.postgresql.port} + ''; + description = mdDoc "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "forgejo"; + description = mdDoc "Database name."; + }; + + user = mkOption { + type = types.str; + default = "forgejo"; + description = mdDoc "Database user."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/forgejo-dbpassword"; + description = mdDoc '' + A file containing the password corresponding to + {option}`${opt.database.user}`. + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null; + defaultText = literalExpression "null"; + example = "/run/mysqld/mysqld.sock"; + description = mdDoc "Path to the unix socket file to use for authentication."; + }; + + path = mkOption { + type = types.str; + default = "${cfg.stateDir}/data/forgejo.db"; + defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/forgejo.db"''; + description = mdDoc "Path to the sqlite3 database file."; + }; + + createDatabase = mkOption { + type = types.bool; + default = true; + description = mdDoc "Whether to create a local database automatically."; + }; + }; + + dump = { + enable = mkEnableOption (mdDoc "periodic dumps via the [built-in {command}`dump` command](https://forgejo.org/docs/latest/admin/command-line/#dump)"); + + interval = mkOption { + type = types.str; + default = "04:31"; + example = "hourly"; + description = mdDoc '' + Run a Forgejo dump at this interval. Runs by default at 04:31 every day. + + The format is described in + {manpage}`systemd.time(7)`. + ''; + }; + + backupDir = mkOption { + type = types.str; + default = "${cfg.stateDir}/dump"; + defaultText = literalExpression ''"''${config.${opt.stateDir}}/dump"''; + description = mdDoc "Path to the directory where the dump archives will be stored."; + }; + + type = mkOption { + type = types.enum [ "zip" "tar" "tar.sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" "tar.zst" ]; + default = "zip"; + description = mdDoc "Archive format used to store the dump file."; + }; + + file = mkOption { + type = types.nullOr types.str; + default = null; + description = mdDoc "Filename to be used for the dump. If `null` a default name is chosen by forgejo."; + example = "forgejo-dump"; + }; + }; + + lfs = { + enable = mkOption { + type = types.bool; + default = false; + description = mdDoc "Enables git-lfs support."; + }; + + contentDir = mkOption { + type = types.str; + default = "${cfg.stateDir}/data/lfs"; + defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/lfs"''; + description = mdDoc "Where to store LFS files."; + }; + }; + + repositoryRoot = mkOption { + type = types.str; + default = "${cfg.stateDir}/repositories"; + defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"''; + description = mdDoc "Path to the git repositories."; + }; + + mailerPasswordFile = mkOption { + type = types.nullOr types.str; + default = null; + example = "/run/keys/forgejo-mailpw"; + description = mdDoc "Path to a file containing the SMTP password."; + }; + + settings = mkOption { + default = { }; + description = mdDoc '' + Free-form settings written directly to the `app.ini` configfile file. + Refer to <https://forgejo.org/docs/latest/admin/config-cheat-sheet/> for supported values. + ''; + example = literalExpression '' + { + DEFAULT = { + RUN_MODE = "dev"; + }; + "cron.sync_external_users" = { + RUN_AT_START = true; + SCHEDULE = "@every 24h"; + UPDATE_EXISTING = true; + }; + mailer = { + ENABLED = true; + MAILER_TYPE = "sendmail"; + FROM = "do-not-reply@example.org"; + SENDMAIL_PATH = "''${pkgs.system-sendmail}/bin/sendmail"; + }; + other = { + SHOW_FOOTER_VERSION = false; + }; + } + ''; + type = types.submodule { + freeformType = format.type; + options = { + log = { + ROOT_PATH = mkOption { + default = "${cfg.stateDir}/log"; + defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"''; + type = types.str; + description = mdDoc "Root path for log files."; + }; + LEVEL = mkOption { + default = "Info"; + type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ]; + description = mdDoc "General log level."; + }; + }; + + server = { + PROTOCOL = mkOption { + type = types.enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ]; + default = "http"; + description = mdDoc ''Listen protocol. `+unix` means "over unix", not "in addition to."''; + }; + + HTTP_ADDR = mkOption { + type = types.either types.str types.path; + default = if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/forgejo/forgejo.sock" else "0.0.0.0"; + defaultText = literalExpression ''if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/forgejo/forgejo.sock" else "0.0.0.0"''; + description = mdDoc "Listen address. Must be a path when using a unix socket."; + }; + + HTTP_PORT = mkOption { + type = types.port; + default = 3000; + description = mdDoc "Listen port. Ignored when using a unix socket."; + }; + + DOMAIN = mkOption { + type = types.str; + default = "localhost"; + description = mdDoc "Domain name of your server."; + }; + + ROOT_URL = mkOption { + type = types.str; + default = "http://${cfg.settings.server.DOMAIN}:${toString cfg.settings.server.HTTP_PORT}/"; + defaultText = literalExpression ''"http://''${config.services.forgejo.settings.server.DOMAIN}:''${toString config.services.forgejo.settings.server.HTTP_PORT}/"''; + description = mdDoc "Full public URL of Forgejo server."; + }; + + STATIC_ROOT_PATH = mkOption { + type = types.either types.str types.path; + default = cfg.package.data; + defaultText = literalExpression "config.${opt.package}.data"; + example = "/var/lib/forgejo/data"; + description = mdDoc "Upper level of template and static files path."; + }; + + DISABLE_SSH = mkOption { + type = types.bool; + default = false; + description = mdDoc "Disable external SSH feature."; + }; + + SSH_PORT = mkOption { + type = types.port; + default = 22; + example = 2222; + description = mdDoc '' + SSH port displayed in clone URL. + The option is required to configure a service when the external visible port + differs from the local listening port i.e. if port forwarding is used. + ''; + }; + }; + + session = { + COOKIE_SECURE = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Marks session cookies as "secure" as a hint for browsers to only send + them via HTTPS. This option is recommend, if Forgejo is being served over HTTPS. + ''; + }; + }; + }; + }; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user; + message = "services.forgejo.database.user must match services.forgejo.user if the database is to be automatically provisioned"; + } + ]; + + services.forgejo.settings = { + DEFAULT = { + RUN_MODE = mkDefault "prod"; + RUN_USER = mkDefault cfg.user; + WORK_PATH = mkDefault cfg.stateDir; + }; + + database = mkMerge [ + { + DB_TYPE = cfg.database.type; + } + (mkIf (useMysql || usePostgresql) { + HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port; + NAME = cfg.database.name; + USER = cfg.database.user; + PASSWD = "#dbpass#"; + }) + (mkIf useSqlite { + PATH = cfg.database.path; + }) + (mkIf usePostgresql { + SSL_MODE = "disable"; + }) + ]; + + repository = { + ROOT = cfg.repositoryRoot; + }; + + server = mkIf cfg.lfs.enable { + LFS_START_SERVER = true; + LFS_JWT_SECRET = "#lfsjwtsecret#"; + }; + + session = { + COOKIE_NAME = mkDefault "session"; + }; + + security = { + SECRET_KEY = "#secretkey#"; + INTERNAL_TOKEN = "#internaltoken#"; + INSTALL_LOCK = true; + }; + + mailer = mkIf (cfg.mailerPasswordFile != null) { + PASSWD = "#mailerpass#"; + }; + + oauth2 = { + JWT_SECRET = "#oauth2jwtsecret#"; + }; + + lfs = mkIf cfg.lfs.enable { + PATH = cfg.lfs.contentDir; + }; + }; + + services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) { + enable = mkDefault true; + + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; + } + ]; + }; + + services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) { + enable = mkDefault true; + package = mkDefault pkgs.mariadb; + + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + } + ]; + }; + + systemd.tmpfiles.rules = [ + "d '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} ${cfg.group} - -" + "z '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -" + + # If we have a folder or symlink with Forgejo locales, remove it + # And symlink the current Forgejo locales in place + "L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale" + + ] ++ optionals cfg.lfs.enable [ + "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.forgejo = { + description = "Forgejo (Beyond coding. We forge.)"; + after = [ + "network.target" + ] ++ optionals usePostgresql [ + "postgresql.service" + ] ++ optionals useMysql [ + "mysql.service" + ]; + requires = optionals (cfg.database.createDatabase && usePostgresql) [ + "postgresql.service" + ] ++ optionals (cfg.database.createDatabase && useMysql) [ + "mysql.service" + ]; + wantedBy = [ "multi-user.target" ]; + path = [ cfg.package pkgs.git pkgs.gnupg ]; + + # In older versions the secret naming for JWT was kind of confusing. + # The file jwt_secret hold the value for LFS_JWT_SECRET and JWT_SECRET + # wasn't persistent at all. + # To fix that, there is now the file oauth2_jwt_secret containing the + # values for JWT_SECRET and the file jwt_secret gets renamed to + # lfs_jwt_secret. + # We have to consider this to stay compatible with older installations. + preStart = + let + runConfig = "${cfg.customDir}/conf/app.ini"; + secretKey = "${cfg.customDir}/conf/secret_key"; + oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret"; + oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET + lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET + internalToken = "${cfg.customDir}/conf/internal_token"; + replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret"; + in + '' + # copy custom configuration and generate random secrets if needed + ${lib.optionalString (!cfg.useWizard) '' + function forgejo_setup { + cp -f '${format.generate "app.ini" cfg.settings}' '${runConfig}' + + if [ ! -s '${secretKey}' ]; then + ${exe} generate secret SECRET_KEY > '${secretKey}' + fi + + # Migrate LFS_JWT_SECRET filename + if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then + mv '${oldLfsJwtSecret}' '${lfsJwtSecret}' + fi + + if [ ! -s '${oauth2JwtSecret}' ]; then + ${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}' + fi + + ${optionalString cfg.lfs.enable '' + if [ ! -s '${lfsJwtSecret}' ]; then + ${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}' + fi + ''} + + if [ ! -s '${internalToken}' ]; then + ${exe} generate secret INTERNAL_TOKEN > '${internalToken}' + fi + + chmod u+w '${runConfig}' + ${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}' + ${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}' + ${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}' + + ${optionalString cfg.lfs.enable '' + ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}' + ''} + + ${optionalString (cfg.database.passwordFile != null) '' + ${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}' + ''} + + ${optionalString (cfg.mailerPasswordFile != null) '' + ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}' + ''} + chmod u-w '${runConfig}' + } + (umask 027; forgejo_setup) + ''} + + # run migrations/init the database + ${exe} migrate + + # update all hooks' binary paths + ${exe} admin regenerate hooks + + # update command option in authorized_keys + if [ -r ${cfg.stateDir}/.ssh/authorized_keys ] + then + ${exe} admin regenerate keys + fi + ''; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.stateDir; + ExecStart = "${exe} web --pid /run/forgejo/forgejo.pid"; + Restart = "always"; + # Runtime directory and mode + RuntimeDirectory = "forgejo"; + RuntimeDirectoryMode = "0755"; + # Proc filesystem + ProcSubset = "pid"; + ProtectProc = "invisible"; + # Access write directories + ReadWritePaths = [ cfg.customDir cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ]; + UMask = "0027"; + # Capabilities + CapabilityBoundingSet = ""; + # Security + NoNewPrivileges = true; + # Sandboxing + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; + PrivateMounts = true; + # System Call Filtering + SystemCallArchitectures = "native"; + SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" "setrlimit" ]; + }; + + environment = { + USER = cfg.user; + HOME = cfg.stateDir; + # `GITEA_` prefix until https://codeberg.org/forgejo/forgejo/issues/497 + # is resolved. + GITEA_WORK_DIR = cfg.stateDir; + GITEA_CUSTOM = cfg.customDir; + }; + }; + + users.users = mkIf (cfg.user == "forgejo") { + forgejo = { + home = cfg.stateDir; + useDefaultShell = true; + group = cfg.group; + isSystemUser = true; + }; + }; + + users.groups = mkIf (cfg.group == "forgejo") { + forgejo = { }; + }; + + systemd.services.forgejo-dump = mkIf cfg.dump.enable { + description = "forgejo dump"; + after = [ "forgejo.service" ]; + path = [ cfg.package ]; + + environment = { + USER = cfg.user; + HOME = cfg.stateDir; + # `GITEA_` prefix until https://codeberg.org/forgejo/forgejo/issues/497 + # is resolved. + GITEA_WORK_DIR = cfg.stateDir; + GITEA_CUSTOM = cfg.customDir; + }; + + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + ExecStart = "${exe} dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}"; + WorkingDirectory = cfg.dump.backupDir; + }; + }; + + systemd.timers.forgejo-dump = mkIf cfg.dump.enable { + description = "Forgejo dump timer"; + partOf = [ "forgejo-dump.service" ]; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = cfg.dump.interval; + }; + }; + + meta.maintainers = with lib.maintainers; [ bendlas emilylange ]; +} |