at 25.11-pre 7.4 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9 10let 11 cfg = config.services.webhook; 12 defaultUser = "webhook"; 13 14 hookFormat = pkgs.formats.json { }; 15 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 ); 35 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; 41 42in 43{ 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 ''; 50 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. 57 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. 66 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. 75 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. 123 124 Read more on the [project homepage] and on the [hook definition] page. 125 At least one hook needs to be configured. 126 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. 147 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]. 164 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 }; 175 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 ]; 191 192 users.users = mkIf (cfg.user == defaultUser) { 193 ${defaultUser} = { 194 isSystemUser = true; 195 group = cfg.group; 196 description = "Webhook daemon user"; 197 }; 198 }; 199 200 users.groups = mkIf (cfg.user == defaultUser && cfg.group == defaultUser) { 201 ${defaultUser} = { }; 202 }; 203 204 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; 205 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 }; 240}