215 lines
7 KiB
Nix
215 lines
7 KiB
Nix
{ 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 = mkPackageOptionMD 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;
|
|
};
|
|
};
|
|
};
|
|
}
|