oxipng: re-enable tests (#375349)
[NixPkgs.git] / nixos / modules / services / networking / webhook.nix
blob7a7c8e1e7095810f8507e6a5cf8f31021a1f854b
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 with lib;
10 let
11   cfg = config.services.webhook;
12   defaultUser = "webhook";
14   hookFormat = pkgs.formats.json { };
16   hookType = types.submodule (
17     { name, ... }:
18     {
19       freeformType = hookFormat.type;
20       options = {
21         id = mkOption {
22           type = types.str;
23           default = name;
24           description = ''
25             The ID of your hook. This value is used to create the HTTP endpoint (`protocol://yourserver:port/prefix/''${id}`).
26           '';
27         };
28         execute-command = mkOption {
29           type = types.str;
30           description = "The command that should be executed when the hook is triggered.";
31         };
32       };
33     }
34   );
36   hookFiles =
37     mapAttrsToList (name: hook: hookFormat.generate "webhook-${name}.json" [ hook ]) cfg.hooks
38     ++ mapAttrsToList (
39       name: hook: pkgs.writeText "webhook-${name}.json.tmpl" "[${hook}]"
40     ) cfg.hooksTemplated;
44   options = {
45     services.webhook = {
46       enable = mkEnableOption ''
47         [Webhook](https://github.com/adnanh/webhook), a server written in Go that allows you to create HTTP endpoints (hooks),
48         which execute configured commands for any person or service that knows the URL
49       '';
51       package = mkPackageOption pkgs "webhook" { };
52       user = mkOption {
53         type = types.str;
54         default = defaultUser;
55         description = ''
56           Webhook will be run under this user.
58           If set, you must create this user yourself!
59         '';
60       };
61       group = mkOption {
62         type = types.str;
63         default = defaultUser;
64         description = ''
65           Webhook will be run under this group.
67           If set, you must create this group yourself!
68         '';
69       };
70       ip = mkOption {
71         type = types.str;
72         default = "0.0.0.0";
73         description = ''
74           The IP webhook should serve hooks on.
76           The default means it can be reached on any interface if `openFirewall = true`.
77         '';
78       };
79       port = mkOption {
80         type = types.port;
81         default = 9000;
82         description = "The port webhook should be reachable from.";
83       };
84       openFirewall = mkOption {
85         type = types.bool;
86         default = false;
87         description = ''
88           Open the configured port in the firewall for external ingress traffic.
89           Preferably the Webhook server is instead put behind a reverse proxy.
90         '';
91       };
92       enableTemplates = mkOption {
93         type = types.bool;
94         default = cfg.hooksTemplated != { };
95         defaultText = literalExpression "hooksTemplated != {}";
96         description = ''
97           Enable the generated hooks file to be parsed as a Go template.
98           See [the documentation](https://github.com/adnanh/webhook/blob/master/docs/Templates.md) for more information.
99         '';
100       };
101       urlPrefix = mkOption {
102         type = types.str;
103         default = "hooks";
104         description = ''
105           The URL path prefix to use for served hooks (`protocol://yourserver:port/''${prefix}/hook-id`).
106         '';
107       };
108       hooks = mkOption {
109         type = types.attrsOf hookType;
110         default = { };
111         example = {
112           echo = {
113             execute-command = "echo";
114             response-message = "Webhook is reachable!";
115           };
116           redeploy-webhook = {
117             execute-command = "/var/scripts/redeploy.sh";
118             command-working-directory = "/var/webhook";
119           };
120         };
121         description = ''
122           The actual configuration of which hooks will be served.
124           Read more on the [project homepage] and on the [hook definition] page.
125           At least one hook needs to be configured.
127           [hook definition]: https://github.com/adnanh/webhook/blob/master/docs/Hook-Definition.md
128           [project homepage]: https://github.com/adnanh/webhook#configuration
129         '';
130       };
131       hooksTemplated = mkOption {
132         type = types.attrsOf types.str;
133         default = { };
134         example = {
135           echo-template = ''
136             {
137               "id": "echo-template",
138               "execute-command": "echo",
139               "response-message": "{{ getenv "MESSAGE" }}"
140             }
141           '';
142         };
143         description = ''
144           Same as {option}`hooks`, but these hooks are specified as literal strings instead of Nix values,
145           and hence can include [template syntax](https://github.com/adnanh/webhook/blob/master/docs/Templates.md)
146           which might not be representable as JSON.
148           Template syntax requires the {option}`enableTemplates` option to be set to `true`, which is
149           done by default if this option is set.
150         '';
151       };
152       verbose = mkOption {
153         type = types.bool;
154         default = true;
155         description = "Whether to show verbose output.";
156       };
157       extraArgs = mkOption {
158         type = types.listOf types.str;
159         default = [ ];
160         example = [ "-secure" ];
161         description = ''
162           These are arguments passed to the webhook command in the systemd service.
163           You can find the available arguments and options in the [documentation][parameters].
165           [parameters]: https://github.com/adnanh/webhook/blob/master/docs/Webhook-Parameters.md
166         '';
167       };
168       environment = mkOption {
169         type = types.attrsOf types.str;
170         default = { };
171         description = "Extra environment variables passed to webhook.";
172       };
173     };
174   };
176   config = mkIf cfg.enable {
177     assertions =
178       let
179         overlappingHooks = builtins.intersectAttrs cfg.hooks cfg.hooksTemplated;
180       in
181       [
182         {
183           assertion = hookFiles != [ ];
184           message = "At least one hook needs to be configured for webhook to run.";
185         }
186         {
187           assertion = overlappingHooks == { };
188           message = "`services.webhook.hooks` and `services.webhook.hooksTemplated` have overlapping attribute(s): ${concatStringsSep ", " (builtins.attrNames overlappingHooks)}";
189         }
190       ];
192     users.users = mkIf (cfg.user == defaultUser) {
193       ${defaultUser} = {
194         isSystemUser = true;
195         group = cfg.group;
196         description = "Webhook daemon user";
197       };
198     };
200     users.groups = mkIf (cfg.user == defaultUser && cfg.group == defaultUser) {
201       ${defaultUser} = { };
202     };
204     networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
206     systemd.services.webhook = {
207       description = "Webhook service";
208       after = [ "network.target" ];
209       wantedBy = [ "multi-user.target" ];
210       environment = config.networking.proxy.envVars // cfg.environment;
211       script =
212         let
213           args =
214             [
215               "-ip"
216               cfg.ip
217               "-port"
218               (toString cfg.port)
219               "-urlprefix"
220               cfg.urlPrefix
221             ]
222             ++ concatMap (hook: [
223               "-hooks"
224               hook
225             ]) hookFiles
226             ++ optional cfg.enableTemplates "-template"
227             ++ optional cfg.verbose "-verbose"
228             ++ cfg.extraArgs;
229         in
230         ''
231           ${cfg.package}/bin/webhook ${escapeShellArgs args}
232         '';
233       serviceConfig = {
234         Restart = "on-failure";
235         User = cfg.user;
236         Group = cfg.group;
237       };
238     };
239   };