# TODO: # - [ ] Check that types.oneOf is the correct way to define options with multiple values { config, lib, pkgs, ... }: let cfg = config.services.marge-bot; inherit (lib) mkOption mkEnableOption types ; margeBotConfig = cfg.settings // { gitlab-url = cfg.gitlabUrl; auth-token-file = cfg.authTokenFile; ssh-key-file = cfg.sshKeyFile; }; margeBotConfigFile = pkgs.writeText "marge-bot-config.yaml" ( pkgs.lib.generators.toYAML { } margeBotConfig ); in { options = { services.marge-bot = { enable = mkEnableOption "GitLab Marge Bot service"; package = mkOption { type = types.package; default = pkgs.marge-bot; defaultText = "pkgs.marge-bot"; description = "Marge Bot package to use"; }; gitlabUrl = mkOption { type = types.str; default = "https://gitlab.com"; description = "GitLab instance URL"; example = "https://gitlab.example.com"; }; authTokenFile = mkOption { type = types.str; description = "Path to file containing GitLab authentication token"; }; sshKeyFile = mkOption { type = types.str; description = "Path to SSH private key file for Git operations"; }; user = mkOption { type = types.str; default = "gitlab-marge-bot"; description = "User account under which marge-bot runs"; }; group = mkOption { type = types.str; default = "gitlab-marge-bot"; description = "Group account under which marge-bot runs"; }; homeDirectory = mkOption { type = types.str; default = "/var/lib/marge-bot"; description = "Home directory for the marge-bot user"; }; settings = mkOption { default = { }; type = types.submodule { freeformType = with types; attrsOf (oneOf [ str bool int float (listOf str) ]); options = { # Based on https://marge-bot.readthedocs.io/en/latest/configuration use-https = mkOption { type = types.bool; default = false; description = "Use HTTP(S) instead of SSH for GIT repository access"; }; embargo = mkOption { type = types.nullOr types.str; default = null; example = "Friday 1pm - Monday 9am"; description = '' Time(s) during which no merging is to take place. Example: "Friday 1pm - Monday 9am" ''; }; use-merge-strategy = mkOption { type = types.bool; default = false; description = '' Use git merge instead of git rebase to update the source branch (EXPERIMENTAL). If you need to use a strict no-rebase workflow. ''; }; rebase-remotely = mkOption { type = types.bool; default = false; description = '' Instead of rebasing in a local clone of the repository, use GitLab's built-in rebase functionality, via their API. Note that Marge can't add information in the commits in this case. ''; }; add-tested = mkOption { type = types.bool; default = false; description = '' Add "Tested: marge-bot <$MR_URL>" for the final commit on branch after it passed CI. ''; }; batch = mkOption { type = types.bool; default = false; description = "Enable processing MRs in batches"; }; add-part-of = mkOption { type = types.bool; default = false; description = '' Add "Part-of: <$MR_URL>" to each commit in MR. ''; }; batch-branch-name = mkOption { type = types.str; default = "marge_bot_batch_merge_job"; description = "Branch name when batching is enabled"; }; add-reviewers = mkOption { type = types.bool; default = false; description = '' Add "Reviewed-by: $approver" for each approver of MR to each commit in MR. ''; }; keep-committers = mkOption { type = types.bool; default = false; description = "Keep the original commit info during rebases"; }; keep-reviewers = mkOption { type = types.bool; default = false; description = '' Ensure previous "Reviewed-by: $approver" aren't dropped by --add-reviewers ''; }; impersonate-approvers = mkOption { type = types.bool; default = false; description = "Marge-bot pushes effectively don't change approval status"; }; merge-order = mkOption { type = types.enum [ "created_at" "updated_at" "assigned_at" ]; default = "created_at"; description = '' Order marge merges assigned requests. Options: created_at (default), updated_at or assigned_at. ''; }; approval-reset-timeout = mkOption { type = types.str; default = "0s"; description = '' How long to wait for approvals to reset after pushing. Only useful with the "new commits remove all approvals" option in a project's settings. This is to handle the potential race condition where approvals don't reset in GitLab after a force push due to slow processing of the event. ''; }; project-regexp = mkOption { type = types.str; default = ".*"; example = "some_group/.*"; description = '' Only process projects that match; e.g. 'some_group/.*' or '(?!exclude/me)'. ''; }; ci-timeout = mkOption { type = types.str; default = "15min"; description = "How long to wait for CI to pass"; }; git-timeout = mkOption { type = types.str; default = "120s"; description = "How long a single git operation can take"; }; git-reference-repo = mkOption { type = types.nullOr types.str; default = null; description = "A reference repo to be used when git cloning"; }; branch-regexp = mkOption { type = types.str; default = ".*"; description = '' Only process MRs whose target branches match the given regular expression. ''; }; source-branch-regexp = mkOption { type = types.str; default = ".*"; description = '' Only process MRs whose source branches match the given regular expression. ''; }; debug = mkOption { type = types.bool; default = false; description = "Debug logging (includes all HTTP requests etc)"; }; run-manual-jobs = mkOption { type = types.bool; default = false; description = "Add this flag to have Marge run on manual jobs within the pipeline"; }; use-no-ff-batches = mkOption { type = types.bool; default = false; description = "Disable fast forwarding when merging MR batches"; }; use-merge-commit-batches = mkOption { type = types.bool; default = false; description = '' Use merge commit when creating batches, so that the commits in the batch MR will be the same with in individual MRs. Requires sudo scope in the access token. ''; }; skip-ci-batches = mkOption { type = types.bool; default = false; description = "Skip CI when updating individual MRs when using batches"; }; cli = mkOption { type = types.bool; default = false; description = "Run marge-bot as a single CLI command, not a service"; }; guarantee-final-pipeline = mkOption { type = types.bool; default = false; description = "Guaranteed final pipeline when assigned to marge-bot"; }; exc-comment = mkOption { type = types.nullOr types.str; default = null; description = '' Provide additional text, like a log URL, to append to some exception-related MR comments. ''; }; custom-approver = mkOption { type = types.listOf types.str; default = [ ]; description = '' Specify one or more approver usernames to accept instead of asking GitLab. For CE approval use. ''; }; custom-approvals-required = mkOption { type = types.int; default = 0; description = '' Required number of approvals from --custom-approval. For CE approval use. ''; }; hooks-directory = mkOption { type = types.nullOr types.str; default = null; description = "Path to the directory where your custom hooks are located"; }; }; }; description = '' Settings for the marge-bot configuration. See https://marge-bot.readthedocs.io/en/latest/configuration for detailed information about all available settings. ''; }; }; }; config = lib.mkIf cfg.enable { warnings = (lib.optional (lib.isStorePath cfg.authTokenFile) '' services.marge-bot.authTokenFile points to a file in the Nix store. You should use a quoted absolute path instead. '') ++ (lib.optional (lib.isStorePath cfg.sshKeyFile) '' services.marge-bot.sshKeyFile points to a file in the Nix store. You should use a quoted absolute path instead. ''); users.users.${cfg.user} = { isSystemUser = true; group = cfg.group; home = cfg.homeDirectory; createHome = true; }; users.groups.${cfg.group} = { }; systemd.services.gitlab-marge-bot = { description = "GitLab Marge Bot Service"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { Type = "simple"; User = cfg.user; Group = cfg.group; WorkingDirectory = cfg.homeDirectory; ExecStart = "${lib.getExe cfg.package} --config-file ${margeBotConfigFile}"; Restart = "on-failure"; RestartSec = "10s"; }; }; }; }