diff options
author | Ryan Lahfa <masterancpp@gmail.com> | 2022-12-31 22:31:38 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-31 22:31:38 +0100 |
commit | 06542b21a03083b894c0b87486aa67b65280a028 (patch) | |
tree | 9a337e45185ccad9e7778a464a051665e8e1ad17 /nixos/modules/services | |
parent | 6dad2ef140c5585a352c53976d122b7883f6ada3 (diff) | |
parent | e8e932bc80719114ba6f67dcf69109142a15c68c (diff) |
Merge pull request #205636 from LoveIsGrief/webhook-module
nixos/webhook: add support for a webhook service option
Diffstat (limited to 'nixos/modules/services')
-rw-r--r-- | nixos/modules/services/networking/webhook.nix | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/nixos/modules/services/networking/webhook.nix b/nixos/modules/services/networking/webhook.nix new file mode 100644 index 0000000000000..b020db6961c32 --- /dev/null +++ b/nixos/modules/services/networking/webhook.nix @@ -0,0 +1,214 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.webhook; + defaultUser = "webhook"; + + hookFormat = pkgs.formats.json {}; + + hookType = types.submodule ({ name, ... }: { + freeformType = hookFormat.type; + options = { + id = mkOption { + type = types.str; + default = name; + description = mdDoc '' + The ID of your hook. This value is used to create the HTTP endpoint (`protocol://yourserver:port/prefix/''${id}`). + ''; + }; + execute-command = mkOption { + type = types.str; + description = mdDoc "The command that should be executed when the hook is triggered."; + }; + }; + }); + + hookFiles = mapAttrsToList (name: hook: hookFormat.generate "webhook-${name}.json" [ hook ]) cfg.hooks + ++ mapAttrsToList (name: hook: pkgs.writeText "webhook-${name}.json.tmpl" "[${hook}]") cfg.hooksTemplated; + +in { + options = { + services.webhook = { + enable = mkEnableOption (mdDoc '' + [Webhook](https://github.com/adnanh/webhook), a server written in Go that allows you to create HTTP endpoints (hooks), + which execute configured commands for any person or service that knows the URL + ''); + + package = mkPackageOption pkgs "webhook" {}; + user = mkOption { + type = types.str; + default = defaultUser; + description = mdDoc '' + Webhook will be run under this user. + + If set, you must create this user yourself! + ''; + }; + group = mkOption { + type = types.str; + default = defaultUser; + description = mdDoc '' + Webhook will be run under this group. + + If set, you must create this group yourself! + ''; + }; + ip = mkOption { + type = types.str; + default = "0.0.0.0"; + description = mdDoc '' + The IP webhook should serve hooks on. + + The default means it can be reached on any interface if `openFirewall = true`. + ''; + }; + port = mkOption { + type = types.port; + default = 9000; + description = mdDoc "The port webhook should be reachable from."; + }; + openFirewall = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Open the configured port in the firewall for external ingress traffic. + Preferably the Webhook server is instead put behind a reverse proxy. + ''; + }; + enableTemplates = mkOption { + type = types.bool; + default = cfg.hooksTemplated != {}; + defaultText = literalExpression "hooksTemplated != {}"; + description = mdDoc '' + Enable the generated hooks file to be parsed as a Go template. + See [the documentation](https://github.com/adnanh/webhook/blob/master/docs/Templates.md) for more information. + ''; + }; + urlPrefix = mkOption { + type = types.str; + default = "hooks"; + description = mdDoc '' + The URL path prefix to use for served hooks (`protocol://yourserver:port/''${prefix}/hook-id`). + ''; + }; + hooks = mkOption { + type = types.attrsOf hookType; + default = {}; + example = { + echo = { + execute-command = "echo"; + response-message = "Webhook is reachable!"; + }; + redeploy-webhook = { + execute-command = "/var/scripts/redeploy.sh"; + command-working-directory = "/var/webhook"; + }; + }; + description = mdDoc '' + The actual configuration of which hooks will be served. + + Read more on the [project homepage] and on the [hook definition] page. + At least one hook needs to be configured. + + [hook definition]: https://github.com/adnanh/webhook/blob/master/docs/Hook-Definition.md + [project homepage]: https://github.com/adnanh/webhook#configuration + ''; + }; + hooksTemplated = mkOption { + type = types.attrsOf types.str; + default = {}; + example = { + echo-template = '' + { + "id": "echo-template", + "execute-command": "echo", + "response-message": "{{ getenv "MESSAGE" }}" + } + ''; + }; + description = mdDoc '' + Same as {option}`hooks`, but these hooks are specified as literal strings instead of Nix values, + and hence can include [template syntax](https://github.com/adnanh/webhook/blob/master/docs/Templates.md) + which might not be representable as JSON. + + Template syntax requires the {option}`enableTemplates` option to be set to `true`, which is + done by default if this option is set. + ''; + }; + verbose = mkOption { + type = types.bool; + default = true; + description = mdDoc "Whether to show verbose output."; + }; + extraArgs = mkOption { + type = types.listOf types.str; + default = []; + example = [ "-secure" ]; + description = mdDoc '' + These are arguments passed to the webhook command in the systemd service. + You can find the available arguments and options in the [documentation][parameters]. + + [parameters]: https://github.com/adnanh/webhook/blob/master/docs/Webhook-Parameters.md + ''; + }; + environment = mkOption { + type = types.attrsOf types.str; + default = {}; + description = mdDoc "Extra environment variables passed to webhook."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = let + overlappingHooks = builtins.intersectAttrs cfg.hooks cfg.hooksTemplated; + in [ + { + assertion = hookFiles != []; + message = "At least one hook needs to be configured for webhook to run."; + } + { + assertion = overlappingHooks == {}; + message = "`services.webhook.hooks` and `services.webhook.hooksTemplated` have overlapping attribute(s): ${concatStringsSep ", " (builtins.attrNames overlappingHooks)}"; + } + ]; + + users.users = mkIf (cfg.user == defaultUser) { + ${defaultUser} = + { + isSystemUser = true; + group = cfg.group; + description = "Webhook daemon user"; + }; + }; + + users.groups = mkIf (cfg.user == defaultUser && cfg.group == defaultUser) { + ${defaultUser} = {}; + }; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + systemd.services.webhook = { + description = "Webhook service"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = config.networking.proxy.envVars // cfg.environment; + script = let + args = [ "-ip" cfg.ip "-port" (toString cfg.port) "-urlprefix" cfg.urlPrefix ] + ++ concatMap (hook: [ "-hooks" hook ]) hookFiles + ++ optional cfg.enableTemplates "-template" + ++ optional cfg.verbose "-verbose" + ++ cfg.extraArgs; + in '' + ${cfg.package}/bin/webhook ${escapeShellArgs args} + ''; + serviceConfig = { + Restart = "on-failure"; + User = cfg.user; + Group = cfg.group; + }; + }; + }; +} |