nixos/webhook: init
This commit is contained in:
parent
fbc4d64b50
commit
e8e932bc80
7 changed files with 293 additions and 0 deletions
|
@ -37,6 +37,13 @@
|
|||
<link linkend="opt-programs.bash.blesh.enable">programs.bash.blesh</link>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<link xlink:href="https://github.com/adnanh/webhook">webhook</link>,
|
||||
a lightweight webhook server. Available as
|
||||
<link linkend="opt-services.webhook.enable">services.webhook</link>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<link xlink:href="https://github.com/alexivkin/CUPS-PDF-to-PDF">cups-pdf-to-pdf</link>,
|
||||
|
|
|
@ -18,6 +18,8 @@ In addition to numerous new and upgraded packages, this release has the followin
|
|||
|
||||
- [blesh](https://github.com/akinomyoga/ble.sh), a line editor written in pure bash. Available as [programs.bash.blesh](#opt-programs.bash.blesh.enable).
|
||||
|
||||
- [webhook](https://github.com/adnanh/webhook), a lightweight webhook server. Available as [services.webhook](#opt-services.webhook.enable).
|
||||
|
||||
- [cups-pdf-to-pdf](https://github.com/alexivkin/CUPS-PDF-to-PDF), a pdf-generating cups backend based on [cups-pdf](https://www.cups-pdf.de/). Available as [services.printing.cups-pdf](#opt-services.printing.cups-pdf.enable).
|
||||
|
||||
- [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
|
||||
|
|
|
@ -1012,6 +1012,7 @@
|
|||
./services/networking/wasabibackend.nix
|
||||
./services/networking/websockify.nix
|
||||
./services/networking/wg-netmanager.nix
|
||||
./services/networking/webhook.nix
|
||||
./services/networking/wg-quick.nix
|
||||
./services/networking/wireguard.nix
|
||||
./services/networking/wpa_supplicant.nix
|
||||
|
|
214
nixos/modules/services/networking/webhook.nix
Normal file
214
nixos/modules/services/networking/webhook.nix
Normal file
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -709,6 +709,7 @@ in {
|
|||
vsftpd = handleTest ./vsftpd.nix {};
|
||||
warzone2100 = handleTest ./warzone2100.nix {};
|
||||
wasabibackend = handleTest ./wasabibackend.nix {};
|
||||
webhook = runTest ./webhook.nix;
|
||||
wiki-js = handleTest ./wiki-js.nix {};
|
||||
wine = handleTest ./wine.nix {};
|
||||
wireguard = handleTest ./wireguard {};
|
||||
|
|
65
nixos/tests/webhook.nix
Normal file
65
nixos/tests/webhook.nix
Normal file
|
@ -0,0 +1,65 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
forwardedPort = 19000;
|
||||
internalPort = 9000;
|
||||
in
|
||||
{
|
||||
name = "webhook";
|
||||
|
||||
nodes = {
|
||||
webhookMachine = { pkgs, ... }: {
|
||||
virtualisation.forwardPorts = [{
|
||||
host.port = forwardedPort;
|
||||
guest.port = internalPort;
|
||||
}];
|
||||
services.webhook = {
|
||||
enable = true;
|
||||
port = internalPort;
|
||||
openFirewall = true;
|
||||
hooks = {
|
||||
echo = {
|
||||
execute-command = "echo";
|
||||
response-message = "Webhook is reachable!";
|
||||
};
|
||||
};
|
||||
hooksTemplated = {
|
||||
echoTemplate = ''
|
||||
{
|
||||
"id": "echo-template",
|
||||
"execute-command": "echo",
|
||||
"response-message": "{{ getenv "WEBHOOK_MESSAGE" }}"
|
||||
}
|
||||
'';
|
||||
};
|
||||
environment.WEBHOOK_MESSAGE = "Templates are working!";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
extraPythonPackages = p: [
|
||||
p.requests
|
||||
p.types-requests
|
||||
];
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
import requests
|
||||
webhookMachine.wait_for_unit("webhook")
|
||||
webhookMachine.wait_for_open_port(${toString internalPort})
|
||||
|
||||
with subtest("Check that webhooks can be called externally"):
|
||||
response = requests.get("http://localhost:${toString forwardedPort}/hooks/echo")
|
||||
print(f"Response code: {response.status_code}")
|
||||
print("Response: %r" % response.content)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.content == b"Webhook is reachable!"
|
||||
|
||||
with subtest("Check that templated webhooks can be called externally"):
|
||||
response = requests.get("http://localhost:${toString forwardedPort}/hooks/echo-template")
|
||||
print(f"Response code: {response.status_code}")
|
||||
print("Response: %r" % response.content)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.content == b"Templates are working!"
|
||||
'';
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
{ lib
|
||||
, buildGoModule
|
||||
, fetchFromGitHub
|
||||
, nixosTests
|
||||
}:
|
||||
|
||||
buildGoModule rec {
|
||||
|
@ -20,6 +21,8 @@ buildGoModule rec {
|
|||
|
||||
doCheck = false;
|
||||
|
||||
passthru.tests = { inherit (nixosTests) webhook; };
|
||||
|
||||
meta = with lib; {
|
||||
description = "Incoming webhook server that executes shell commands";
|
||||
homepage = "https://github.com/adnanh/webhook";
|
||||
|
|
Loading…
Reference in a new issue