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