n8n-nodes-whm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +161 -0
  3. package/bridge/n8n-whm-hook-bridge.conf.example +22 -0
  4. package/bridge/n8n-whm-hook-bridge.pl +188 -0
  5. package/bridge/n8n-whm-hooks.sh +152 -0
  6. package/dist/credentials/WhmApi.credentials.d.ts +9 -0
  7. package/dist/credentials/WhmApi.credentials.js +106 -0
  8. package/dist/credentials/WhmApi.credentials.js.map +1 -0
  9. package/dist/nodes/Whm/GenericFunctions.d.ts +17 -0
  10. package/dist/nodes/Whm/GenericFunctions.js +319 -0
  11. package/dist/nodes/Whm/GenericFunctions.js.map +1 -0
  12. package/dist/nodes/Whm/Whm.node.d.ts +5 -0
  13. package/dist/nodes/Whm/Whm.node.js +198 -0
  14. package/dist/nodes/Whm/Whm.node.js.map +1 -0
  15. package/dist/nodes/Whm/WhmTrigger.node.d.ts +12 -0
  16. package/dist/nodes/Whm/WhmTrigger.node.js +165 -0
  17. package/dist/nodes/Whm/WhmTrigger.node.js.map +1 -0
  18. package/dist/nodes/Whm/descriptions/AccountDescription.d.ts +3 -0
  19. package/dist/nodes/Whm/descriptions/AccountDescription.js +188 -0
  20. package/dist/nodes/Whm/descriptions/AccountDescription.js.map +1 -0
  21. package/dist/nodes/Whm/descriptions/ApiTokenDescription.d.ts +3 -0
  22. package/dist/nodes/Whm/descriptions/ApiTokenDescription.js +56 -0
  23. package/dist/nodes/Whm/descriptions/ApiTokenDescription.js.map +1 -0
  24. package/dist/nodes/Whm/descriptions/BackupDescription.d.ts +3 -0
  25. package/dist/nodes/Whm/descriptions/BackupDescription.js +83 -0
  26. package/dist/nodes/Whm/descriptions/BackupDescription.js.map +1 -0
  27. package/dist/nodes/Whm/descriptions/CpanelDatabaseDescription.d.ts +3 -0
  28. package/dist/nodes/Whm/descriptions/CpanelDatabaseDescription.js +45 -0
  29. package/dist/nodes/Whm/descriptions/CpanelDatabaseDescription.js.map +1 -0
  30. package/dist/nodes/Whm/descriptions/CpanelDomainDescription.d.ts +3 -0
  31. package/dist/nodes/Whm/descriptions/CpanelDomainDescription.js +44 -0
  32. package/dist/nodes/Whm/descriptions/CpanelDomainDescription.js.map +1 -0
  33. package/dist/nodes/Whm/descriptions/CpanelEmailDescription.d.ts +3 -0
  34. package/dist/nodes/Whm/descriptions/CpanelEmailDescription.js +100 -0
  35. package/dist/nodes/Whm/descriptions/CpanelEmailDescription.js.map +1 -0
  36. package/dist/nodes/Whm/descriptions/CpanelFtpDescription.d.ts +3 -0
  37. package/dist/nodes/Whm/descriptions/CpanelFtpDescription.js +46 -0
  38. package/dist/nodes/Whm/descriptions/CpanelFtpDescription.js.map +1 -0
  39. package/dist/nodes/Whm/descriptions/CpanelFunctionDescription.d.ts +3 -0
  40. package/dist/nodes/Whm/descriptions/CpanelFunctionDescription.js +33 -0
  41. package/dist/nodes/Whm/descriptions/CpanelFunctionDescription.js.map +1 -0
  42. package/dist/nodes/Whm/descriptions/CustomDescription.d.ts +3 -0
  43. package/dist/nodes/Whm/descriptions/CustomDescription.js +52 -0
  44. package/dist/nodes/Whm/descriptions/CustomDescription.js.map +1 -0
  45. package/dist/nodes/Whm/descriptions/DnsDescription.d.ts +3 -0
  46. package/dist/nodes/Whm/descriptions/DnsDescription.js +138 -0
  47. package/dist/nodes/Whm/descriptions/DnsDescription.js.map +1 -0
  48. package/dist/nodes/Whm/descriptions/DomainDescription.d.ts +3 -0
  49. package/dist/nodes/Whm/descriptions/DomainDescription.js +47 -0
  50. package/dist/nodes/Whm/descriptions/DomainDescription.js.map +1 -0
  51. package/dist/nodes/Whm/descriptions/PackageDescription.d.ts +3 -0
  52. package/dist/nodes/Whm/descriptions/PackageDescription.js +63 -0
  53. package/dist/nodes/Whm/descriptions/PackageDescription.js.map +1 -0
  54. package/dist/nodes/Whm/descriptions/ResellerDescription.d.ts +3 -0
  55. package/dist/nodes/Whm/descriptions/ResellerDescription.js +99 -0
  56. package/dist/nodes/Whm/descriptions/ResellerDescription.js.map +1 -0
  57. package/dist/nodes/Whm/descriptions/ServerDescription.d.ts +3 -0
  58. package/dist/nodes/Whm/descriptions/ServerDescription.js +47 -0
  59. package/dist/nodes/Whm/descriptions/ServerDescription.js.map +1 -0
  60. package/dist/nodes/Whm/descriptions/ServiceDescription.d.ts +3 -0
  61. package/dist/nodes/Whm/descriptions/ServiceDescription.js +71 -0
  62. package/dist/nodes/Whm/descriptions/ServiceDescription.js.map +1 -0
  63. package/dist/nodes/Whm/descriptions/SharedFields.d.ts +7 -0
  64. package/dist/nodes/Whm/descriptions/SharedFields.js +172 -0
  65. package/dist/nodes/Whm/descriptions/SharedFields.js.map +1 -0
  66. package/dist/nodes/Whm/descriptions/SslDescription.d.ts +3 -0
  67. package/dist/nodes/Whm/descriptions/SslDescription.js +89 -0
  68. package/dist/nodes/Whm/descriptions/SslDescription.js.map +1 -0
  69. package/dist/nodes/Whm/descriptions/index.d.ts +17 -0
  70. package/dist/nodes/Whm/descriptions/index.js +36 -0
  71. package/dist/nodes/Whm/descriptions/index.js.map +1 -0
  72. package/dist/nodes/Whm/operations.d.ts +19 -0
  73. package/dist/nodes/Whm/operations.js +286 -0
  74. package/dist/nodes/Whm/operations.js.map +1 -0
  75. package/dist/nodes/Whm/whm.svg +6 -0
  76. package/dist/tsconfig.tsbuildinfo +1 -0
  77. package/index.js +3 -0
  78. package/package.json +81 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anthony Tuberville
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # n8n-nodes-whm
2
+
3
+ A community node package that brings [cPanel & WHM](https://cpanel.net/) (WebHost
4
+ Manager) into [n8n](https://n8n.io/) as native nodes.
5
+
6
+ It ships two nodes:
7
+
8
+ - **WHM** — an action node covering the server-level WHM API (accounts, packages,
9
+ domains, DNS zones, resellers, server, services, SSL, backups, API tokens) **and**
10
+ the account-level cPanel API (email accounts, MySQL databases, addon/parked/sub
11
+ domains, FTP accounts) executed *as* any cPanel user through WHM — no per-account
12
+ passwords needed. Two escape hatches (*Custom WHM Call* and *cPanel Function*) can
13
+ invoke any documented WHM API 1, UAPI or API 2 function.
14
+ - **WHM Trigger** — a webhook node that starts workflows from WHM hook events
15
+ (account created, suspended, email account added, …) via a small, signed hook
16
+ bridge script that you register on the server.
17
+
18
+ > Design, endpoint, authentication and workflow details live in
19
+ > [`WHM-n8n-Node-Technical-Documentation.md`](./WHM-n8n-Node-Technical-Documentation.md).
20
+
21
+ ## Installation
22
+
23
+ ### Via the n8n UI (recommended)
24
+ 1. In n8n go to **Settings → Community Nodes → Install**.
25
+ 2. Enter `n8n-nodes-whm` and confirm.
26
+
27
+ ### Manual / self-hosted
28
+ ```bash
29
+ cd ~/.n8n/nodes # or your N8N_CUSTOM_EXTENSIONS path
30
+ npm install n8n-nodes-whm
31
+ ```
32
+
33
+ ### Build from source
34
+ ```bash
35
+ git clone https://github.com/Falcyn/n8n-nodes-whm.git
36
+ cd n8n-nodes-whm
37
+ npm install
38
+ npm run build # tsc + copies icons into dist/
39
+ npm run lint # eslint with eslint-plugin-n8n-nodes-base
40
+ ```
41
+ The compiled output in `dist/` is what n8n loads (see the `n8n` block in `package.json`).
42
+
43
+ ## Credentials
44
+
45
+ Create an API token in WHM first:
46
+
47
+ 1. **WHM » Development » Manage API Tokens » Generate Token.**
48
+ 2. Give it a name and restrict its privileges (ACLs) to what your workflows need. A
49
+ reseller can generate a token limited to its own accounts.
50
+ 3. Copy the token — WHM shows it only once.
51
+
52
+ Then in n8n add a **WHM API** credential:
53
+
54
+ | Field | Example | Notes |
55
+ |-------|---------|-------|
56
+ | Host | `server.example.com` | Hostname or IP, no scheme or port |
57
+ | Port | `2087` | 2087 = HTTPS (default), 2086 = plain HTTP |
58
+ | Use SSL | on | Keep on; the port must match |
59
+ | Username | `root` | root or a reseller username |
60
+ | Authentication | API Token | Password (HTTP Basic) is also supported |
61
+ | API Token | `U7HMR63FGY…` | From WHM |
62
+ | Ignore SSL Issues | off | Turn on only if WHM still uses its self-signed certificate |
63
+
64
+ The credential **Test** button calls the harmless `version` function.
65
+
66
+ ## What the WHM node can do
67
+
68
+ | Resource | Operations |
69
+ |----------|------------|
70
+ | **Account** | Create, Get, Get Many, Get Suspended, Modify, Suspend, Unsuspend, Terminate, Change Package, Change Password, Change Primary IP, Set Bandwidth Limit, Set Disk Quota, Get Bandwidth Usage, Get Disk Usage, Create Login Session (SSO URL), Force Password Change |
71
+ | **Package** | Create, Update, Delete, Get, Get Many, Get Feature Lists, Get Feature List |
72
+ | **Domain** | Get Many (every domain on the server), Get Owner, Get User Data (vhost), Park, Unpark, Resolve |
73
+ | **DNS Zone** | Get Many, Create Zone, Delete Zone, Get Zone (all records), Reset Zone, Add / Edit / Get / Delete Record, Get MX Records, Save MX Record, Lookup Nameserver IP |
74
+ | **Reseller** | Get Many, Grant / Revoke Privileges, Get Stats, Get Account Counts, Get / Set IP Addresses, Set Main IP, Set Nameservers, Set Limits, Set Package Limit, Set ACLs, Get ACL Lists, Suspend, Unsuspend, Terminate |
75
+ | **Server** | Get Version, Get Hostname, Set Hostname, Get Load Average, Get Disk Usage, Get / Add / Delete IP Addresses, Get / Set Tweak Setting, Get Languages, Get Applications, Reboot |
76
+ | **Service** | Get Status, Restart, Configure (enable / monitor) |
77
+ | **SSL** | Get Many (installed), Install, Delete Installed, Get Certificate Info, Get Vhost Components, List Certificates, Generate Self-Signed, Run AutoSSL (user / all), Get / Set AutoSSL Provider |
78
+ | **Backup** | Get / Set Configuration, Get Backup Dates, Get Backup Users, Get User Backup Sets, Restore Account, Restore From Archive, Toggle User Backups, Skip All Users, Get Skip-All Status |
79
+ | **API Token** | Get Many, Create, Update, Revoke |
80
+ | **cPanel Email** | Get Many, Create, Delete, Change Password, Get / Set Quota, Suspend / Unsuspend Login, Get / Add / Delete Forwarders, Get Mail Domains |
81
+ | **cPanel Database** | Get Many, Create, Delete, Rename, Get Users, Create / Delete User, Set User Password, Get / Set Privileges, Add / Delete Remote Host |
82
+ | **cPanel Domain** | Get Many, Get Domain Data, Add / Delete Addon Domain, Add / Delete Subdomain, Add / Delete Alias (parked) |
83
+ | **cPanel FTP** | Get Many, Create, Delete, Change Password, Set Quota, Set Home Directory |
84
+ | **cPanel Function** | Execute any UAPI or API 2 function as a cPanel user |
85
+ | **Custom WHM Call** | Execute any WHM API 1 function |
86
+
87
+ Every operation also has a **Custom Parameters** list for parameters that are not
88
+ exposed as fields, and an **Options → Raw Response** switch that returns the
89
+ complete API envelope. *Get Many* operations support **Return All / Limit** and, for
90
+ WHM API 1 functions, server-side **Filters** and **Sort** (`api.filter.*`, `api.sort.*`).
91
+
92
+ ## Trigger setup (optional)
93
+
94
+ cPanel & WHM has no outbound webhooks; it has *Standardized Hooks* that run a script
95
+ when something happens. The package ships that script.
96
+
97
+ 1. Add a **WHM Trigger** node, pick the events, set a **Shared Secret**, and copy the
98
+ **Production URL**.
99
+ 2. Copy the `bridge/` folder to the WHM server and run, as root:
100
+ ```bash
101
+ ./n8n-whm-hooks.sh install --url https://n8n.example.com/webhook/<id>/whm --secret '<same secret>'
102
+ ```
103
+ This installs `/opt/n8n-whm-hook-bridge/n8n-whm-hook-bridge.pl`, writes
104
+ `/etc/n8n-whm-hook-bridge.conf` (root-only) and registers a default set of events
105
+ with `manage_hooks`.
106
+ 3. Register extra events with `./n8n-whm-hooks.sh add <Category> <Event> [pre|post]`,
107
+ e.g. `./n8n-whm-hooks.sh add Cpanel UAPI::Email::passwd_pop post`. Add the same
108
+ name to the node's **Custom Events** if it is not in the list.
109
+
110
+ The bridge reads the hook's JSON from STDIN, redacts password fields, signs the body
111
+ with HMAC-SHA256 (`X-WHM-Signature`) and POSTs it to n8n with a short timeout. It
112
+ **always** reports success to cPanel, so an unreachable n8n never blocks account
113
+ creation or any other WHM action. Delivery failures are logged to
114
+ `/usr/local/cpanel/logs/n8n-whm-hook-bridge.log`.
115
+
116
+ ## Quick examples
117
+
118
+ *Create a hosting account when an order comes in:*
119
+
120
+ - Resource **Account** → Operation **Create**
121
+ - Username `{{ $json.username }}`, Domain `{{ $json.domain }}`
122
+ - Additional Fields → Package `starter`, Contact Email `{{ $json.email }}`
123
+
124
+ *List every suspended account owned by reseller `acme`:*
125
+
126
+ - Resource **Account** → **Get Many**
127
+ - Filters → Search Type `Owner`, Search `^acme$`
128
+ - List Options → Filters → Field `suspended`, Type `Equals`, Value `1`
129
+
130
+ *Add a mailbox on a customer's account:*
131
+
132
+ - Resource **cPanel Email** → **Create**
133
+ - cPanel Username `examplecom`, Email Address `support@example.com`, Password …
134
+
135
+ ## Security
136
+
137
+ - **Use API tokens, not passwords**, and restrict their ACLs. A root token can do
138
+ anything on the server; a reseller token can only touch that reseller's accounts.
139
+ - **Always set a Shared Secret on the WHM Trigger.** Without it the webhook is
140
+ unauthenticated and anyone who discovers the URL can inject fake events. The node
141
+ verifies an HMAC-SHA256 signature over the raw body in constant time.
142
+ - **Hook payloads can contain passwords** (account creation, password changes). The
143
+ bridge redacts common password keys before the event leaves the server; extend the
144
+ `redact` list in the config if you register other events.
145
+ - **Use HTTPS end to end.** Point the bridge at an `https://` n8n URL and keep
146
+ *Ignore SSL Issues* off once WHM has a valid certificate.
147
+ - **Credentials** are stored in n8n's encrypted credential store, injected per request
148
+ as an `Authorization` header, and redacted from error output by the node.
149
+
150
+ ## Disclaimer
151
+
152
+ This is an unofficial, community-maintained project. It is **not affiliated with,
153
+ endorsed by, or sponsored by cPanel, L.L.C. or WebPros**. "cPanel" and "WHM" are
154
+ trademarks of cPanel, L.L.C. and are used here only to describe API compatibility. This
155
+ package contains **no cPanel source code** — it communicates with WHM solely through
156
+ its public, documented APIs and hook system. The software is provided "as is", without
157
+ warranty of any kind (see [LICENSE](LICENSE.md)).
158
+
159
+ ## License
160
+
161
+ [MIT](LICENSE.md) © Anthony Tuberville
@@ -0,0 +1,22 @@
1
+ # n8n hook bridge configuration
2
+ # Copy to /etc/n8n-whm-hook-bridge.conf on the WHM server and chmod 600.
3
+
4
+ # Production URL of the WHM Trigger node (required)
5
+ webhook_url = https://n8n.example.com/webhook/REPLACE-ME/whm
6
+
7
+ # Shared secret — must match the WHM Trigger node's "Shared Secret" field.
8
+ # Every request is signed with HMAC-SHA256 over the JSON body (X-WHM-Signature).
9
+ secret = change-me-to-a-long-random-string
10
+
11
+ # HTTP timeout in seconds. Keep it short: hooks run inline with the WHM action.
12
+ timeout = 5
13
+
14
+ # Set to 1 to skip TLS certificate verification (only for trusted internal n8n hosts)
15
+ insecure = 0
16
+
17
+ # Keys whose values are replaced with ***redacted*** before the event leaves the
18
+ # server. Account-creation and password-change hooks carry plaintext passwords.
19
+ redact = pass,password,passwd,pass1,pass2,key,db_pass,rawpass,newpass,oldpass
20
+
21
+ # Delivery failures are appended here (set empty to disable)
22
+ log = /usr/local/cpanel/logs/n8n-whm-hook-bridge.log
@@ -0,0 +1,188 @@
1
+ #!/usr/local/cpanel/3rdparty/bin/perl
2
+ # ---------------------------------------------------------------------------
3
+ # n8n hook bridge for cPanel & WHM
4
+ # ---------------------------------------------------------------------------
5
+ # A cPanel *Standardized Hooks* script that forwards hook events to an n8n
6
+ # "WHM Trigger" webhook.
7
+ #
8
+ # cPanel runs this script at each registered hook point and passes the event
9
+ # as a single JSON document on STDIN:
10
+ #
11
+ # { "context": {...}, "data": {...}, "hook": { "category", "event", "stage", ... } }
12
+ #
13
+ # The script POSTs the event to n8n as JSON, signed with HMAC-SHA256 using the
14
+ # shared secret, and ALWAYS reports success back to cPanel so that a slow or
15
+ # unreachable n8n can never block account creation or any other action.
16
+ #
17
+ # Configuration lives in /etc/n8n-whm-hook-bridge.conf (key = value):
18
+ #
19
+ # webhook_url = https://n8n.example.com/webhook/<id>/whm (required)
20
+ # secret = long-random-string (recommended)
21
+ # timeout = 5 (seconds)
22
+ # insecure = 0 (1 = skip TLS verify)
23
+ # redact = pass,password,passwd,pass1,pass2,key,db_pass,rawpass
24
+ # log = /usr/local/cpanel/logs/n8n-whm-hook-bridge.log
25
+ #
26
+ # Register it with the bundled n8n-whm-hooks.sh, or by hand:
27
+ # /usr/local/cpanel/bin/manage_hooks add script /opt/n8n-whm-hook-bridge/n8n-whm-hook-bridge.pl \
28
+ # --manual --category Whostmgr --event Accounts::Create --stage post
29
+ # ---------------------------------------------------------------------------
30
+ use strict;
31
+ use warnings;
32
+
33
+ use JSON::PP ();
34
+ use HTTP::Tiny ();
35
+ use Digest::SHA qw(hmac_sha256_hex);
36
+ use Sys::Hostname qw(hostname);
37
+ use POSIX qw(strftime);
38
+
39
+ my $CONFIG_FILE = $ENV{N8N_WHM_BRIDGE_CONF} || '/etc/n8n-whm-hook-bridge.conf';
40
+
41
+ # ---- configuration --------------------------------------------------------
42
+ my %cfg = (
43
+ webhook_url => '',
44
+ secret => '',
45
+ timeout => 5,
46
+ insecure => 0,
47
+ redact => 'pass,password,passwd,pass1,pass2,key,db_pass,rawpass,newpass,oldpass',
48
+ log => '/usr/local/cpanel/logs/n8n-whm-hook-bridge.log',
49
+ );
50
+
51
+ if ( open my $fh, '<', $CONFIG_FILE ) {
52
+ while ( my $line = <$fh> ) {
53
+ chomp $line;
54
+ next if $line =~ /^\s*(#|$)/;
55
+ my ( $k, $v ) = $line =~ /^\s*([A-Za-z_]+)\s*=\s*(.*?)\s*$/ or next;
56
+ $v =~ s/^(['"])(.*)\1$/$2/;
57
+ $cfg{ lc $k } = $v;
58
+ }
59
+ close $fh;
60
+ }
61
+
62
+ sub logmsg {
63
+ my ($msg) = @_;
64
+ return unless $cfg{log};
65
+ if ( open my $lf, '>>', $cfg{log} ) {
66
+ print {$lf} strftime( '%Y-%m-%d %H:%M:%S', localtime ), " [$$] $msg\n";
67
+ close $lf;
68
+ }
69
+ return;
70
+ }
71
+
72
+ # Never block cPanel: every exit path prints a success line to STDOUT.
73
+ sub finish {
74
+ my ($msg) = @_;
75
+ print "1 n8n bridge: $msg\n";
76
+ exit 0;
77
+ }
78
+
79
+ # ---- read the hook payload --------------------------------------------------
80
+ my $raw = do { local $/; <STDIN> };
81
+ $raw = '' unless defined $raw;
82
+ $raw =~ s/^\s+|\s+$//g;
83
+
84
+ my $input;
85
+ if ( length $raw ) {
86
+ $input = eval { JSON::PP->new->utf8->decode($raw) };
87
+ if ( !$input || ref $input ne 'HASH' ) {
88
+ logmsg("could not decode hook input: " . ( $@ || 'not a JSON object' ));
89
+ finish('invalid input ignored');
90
+ }
91
+ }
92
+ else {
93
+ $input = { context => {}, data => {}, hook => {} };
94
+ }
95
+
96
+ unless ( $cfg{webhook_url} && $cfg{webhook_url} =~ m{^https?://}i ) {
97
+ logmsg("webhook_url is not configured in $CONFIG_FILE");
98
+ finish('not configured');
99
+ }
100
+
101
+ # ---- build the event ----------------------------------------------------------
102
+ my $hook = ref $input->{hook} eq 'HASH' ? $input->{hook} : {};
103
+ my $category = $hook->{category} // $ENV{HOOK_CATEGORY} // '';
104
+ my $event = $hook->{event} // $ENV{HOOK_EVENT} // '';
105
+ my $stage = $hook->{stage} // $ENV{HOOK_STAGE} // '';
106
+
107
+ my %redact = map { lc($_) => 1 } grep { length } split /\s*,\s*/, $cfg{redact};
108
+
109
+ sub scrub {
110
+ my ($node) = @_;
111
+ if ( ref $node eq 'HASH' ) {
112
+ for my $k ( keys %$node ) {
113
+ if ( $redact{ lc $k } ) { $node->{$k} = '***redacted***'; }
114
+ else { scrub( $node->{$k} ); }
115
+ }
116
+ }
117
+ elsif ( ref $node eq 'ARRAY' ) {
118
+ scrub($_) for @$node;
119
+ }
120
+ return;
121
+ }
122
+ scrub( $input->{data} );
123
+ scrub( $input->{context} );
124
+
125
+ my $payload = {
126
+ event => join( '::', grep { length } $category, $event ),
127
+ category => $category,
128
+ name => $event,
129
+ stage => $stage,
130
+ hook => $hook,
131
+ context => $input->{context} // {},
132
+ data => $input->{data} // {},
133
+ hostname => hostname(),
134
+ timestamp => strftime( '%Y-%m-%dT%H:%M:%S%z', localtime ),
135
+ };
136
+
137
+ my $body = JSON::PP->new->utf8->canonical->encode($payload);
138
+
139
+ my %headers = (
140
+ 'Content-Type' => 'application/json',
141
+ 'User-Agent' => 'n8n-whm-hook-bridge/1.0',
142
+ 'X-WHM-Event' => $payload->{event},
143
+ );
144
+ if ( length $cfg{secret} ) {
145
+ $headers{'X-WHM-Signature'} = 'sha256=' . hmac_sha256_hex( $body, $cfg{secret} );
146
+ }
147
+
148
+ # ---- deliver ----------------------------------------------------------------------
149
+ my $timeout = ( $cfg{timeout} || 5 ) + 0;
150
+ my $ok = 0;
151
+ my $detail = '';
152
+
153
+ if ( HTTP::Tiny->can_ssl || $cfg{webhook_url} =~ m{^http://}i ) {
154
+ my $http = HTTP::Tiny->new(
155
+ timeout => $timeout,
156
+ verify_SSL => $cfg{insecure} ? 0 : 1,
157
+ agent => 'n8n-whm-hook-bridge/1.0',
158
+ );
159
+ my $res = $http->post( $cfg{webhook_url}, { headers => \%headers, content => $body } );
160
+ $ok = $res->{success} ? 1 : 0;
161
+ $detail = "$res->{status} $res->{reason}";
162
+ $detail .= ": $res->{content}" if $res->{status} == 599 && defined $res->{content};
163
+ }
164
+ else {
165
+ # Fallback for a Perl without SSL support: shell out to curl.
166
+ my @cmd = ( 'curl', '-sS', '-o', '/dev/null', '-w', '%{http_code}', '--max-time', $timeout, '-X', 'POST' );
167
+ push @cmd, '-k' if $cfg{insecure};
168
+ push @cmd, '-H', "$_: $headers{$_}" for keys %headers;
169
+ push @cmd, '--data-binary', '@-', $cfg{webhook_url};
170
+ if ( open my $curl, '|-', @cmd ) { # STDIN of curl
171
+ print {$curl} $body;
172
+ close $curl;
173
+ $ok = ( $? == 0 ) ? 1 : 0;
174
+ $detail = "curl exit " . ( $? >> 8 );
175
+ }
176
+ else {
177
+ $detail = "could not run curl: $!";
178
+ }
179
+ }
180
+
181
+ if ($ok) {
182
+ logmsg("forwarded $payload->{event} ($stage) -> $cfg{webhook_url}") if $ENV{N8N_WHM_BRIDGE_DEBUG};
183
+ finish("forwarded $payload->{event}");
184
+ }
185
+ else {
186
+ logmsg("FAILED to forward $payload->{event} ($stage): $detail");
187
+ finish("delivery failed ($detail), ignored");
188
+ }
@@ -0,0 +1,152 @@
1
+ #!/bin/bash
2
+ # ---------------------------------------------------------------------------
3
+ # n8n-whm-hooks.sh — install / register / remove the n8n hook bridge on a
4
+ # cPanel & WHM server. Run as root ON THE WHM SERVER.
5
+ #
6
+ # ./n8n-whm-hooks.sh install [--url URL] [--secret SECRET]
7
+ # Copies the bridge to /opt/n8n-whm-hook-bridge, writes the config
8
+ # (if missing) and registers the DEFAULT_EVENTS below.
9
+ # ./n8n-whm-hooks.sh add <Category> <Event> [pre|post]
10
+ # ./n8n-whm-hooks.sh remove <Category> <Event> [pre|post]
11
+ # ./n8n-whm-hooks.sh list Show every hook registered on the server
12
+ # ./n8n-whm-hooks.sh uninstall Deregister all bridge hooks, keep files
13
+ #
14
+ # Category::Event names are the ones from the cPanel "Guide to Standardized
15
+ # Hooks". For the Cpanel category the bridge is registered with
16
+ # --escalateprivs 1 so it runs as root and can read the config file.
17
+ # ---------------------------------------------------------------------------
18
+ set -euo pipefail
19
+
20
+ INSTALL_DIR="/opt/n8n-whm-hook-bridge"
21
+ SCRIPT_NAME="n8n-whm-hook-bridge.pl"
22
+ SCRIPT_PATH="$INSTALL_DIR/$SCRIPT_NAME"
23
+ CONF_PATH="/etc/n8n-whm-hook-bridge.conf"
24
+ MANAGE_HOOKS="/usr/local/cpanel/bin/manage_hooks"
25
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+
27
+ # Events registered by "install". Format: Category|Event|stage
28
+ DEFAULT_EVENTS=(
29
+ "Whostmgr|Accounts::Create|post"
30
+ "Whostmgr|Accounts::Remove|post"
31
+ "Whostmgr|Accounts::Modify|post"
32
+ "Whostmgr|Accounts::change_package|post"
33
+ "Whostmgr|Accounts::suspendacct|post"
34
+ "Whostmgr|Accounts::unsuspendacct|post"
35
+ "Whostmgr|Domain::park|post"
36
+ "Whostmgr|Domain::unpark|post"
37
+ "Passwd|ChangePasswd|post"
38
+ "Cpanel|UAPI::Email::add_pop|post"
39
+ "Cpanel|UAPI::Email::delete_pop|post"
40
+ "Cpanel|Api2::AddonDomain::addaddondomain|post"
41
+ "Cpanel|Api2::AddonDomain::deladdondomain|post"
42
+ "Cpanel|Api2::SubDomain::addsubdomain|post"
43
+ "Cpanel|Api2::SubDomain::delsubdomain|post"
44
+ "Cpanel|UAPI::Mysql::create_database|post"
45
+ "Cpanel|UAPI::Ftp::add_ftp|post"
46
+ )
47
+
48
+ die() { echo "error: $*" >&2; exit 1; }
49
+
50
+ need_root() { [[ $EUID -eq 0 ]] || die "run this script as root on the WHM server"; }
51
+ need_manage_hooks() { [[ -x "$MANAGE_HOOKS" ]] || die "$MANAGE_HOOKS not found — is this a cPanel & WHM server?"; }
52
+
53
+ hook_args() {
54
+ # $1 category, $2 event, $3 stage
55
+ local args=(--manual --category "$1" --event "$2" --stage "$3")
56
+ [[ "$1" == "Cpanel" ]] && args+=(--escalateprivs 1)
57
+ printf '%s\n' "${args[@]}"
58
+ }
59
+
60
+ add_hook() {
61
+ local category="$1" event="$2" stage="${3:-post}"
62
+ mapfile -t args < <(hook_args "$category" "$event" "$stage")
63
+ echo "+ registering $category::$event ($stage)"
64
+ "$MANAGE_HOOKS" add script "$SCRIPT_PATH" "${args[@]}"
65
+ }
66
+
67
+ remove_hook() {
68
+ local category="$1" event="$2" stage="${3:-post}"
69
+ mapfile -t args < <(hook_args "$category" "$event" "$stage")
70
+ echo "- removing $category::$event ($stage)"
71
+ "$MANAGE_HOOKS" delete script "$SCRIPT_PATH" "${args[@]}" || true
72
+ }
73
+
74
+ write_config() {
75
+ local url="$1" secret="$2"
76
+ if [[ -f "$CONF_PATH" ]]; then
77
+ echo "config $CONF_PATH already exists — leaving it untouched"
78
+ return
79
+ fi
80
+ if [[ -z "$secret" ]]; then
81
+ secret="$(head -c 48 /dev/urandom | base64 | tr -d '/+=' | head -c 48)"
82
+ echo "generated a shared secret — copy it into the WHM Trigger node's Shared Secret field:"
83
+ echo " $secret"
84
+ fi
85
+ umask 077
86
+ cat > "$CONF_PATH" <<EOF
87
+ # n8n hook bridge configuration — root only (mode 600)
88
+ webhook_url = ${url:-https://YOUR-N8N-HOST/webhook/REPLACE-ME/whm}
89
+ secret = $secret
90
+ timeout = 5
91
+ insecure = 0
92
+ redact = pass,password,passwd,pass1,pass2,key,db_pass,rawpass,newpass,oldpass
93
+ log = /usr/local/cpanel/logs/n8n-whm-hook-bridge.log
94
+ EOF
95
+ chmod 600 "$CONF_PATH"
96
+ echo "wrote $CONF_PATH"
97
+ [[ -n "$url" ]] || echo "NOTE: edit $CONF_PATH and set webhook_url to the WHM Trigger node's Production URL"
98
+ }
99
+
100
+ cmd="${1:-}"; shift || true
101
+ case "$cmd" in
102
+ install)
103
+ need_root; need_manage_hooks
104
+ url=""; secret=""
105
+ while [[ $# -gt 0 ]]; do
106
+ case "$1" in
107
+ --url) url="$2"; shift 2 ;;
108
+ --secret) secret="$2"; shift 2 ;;
109
+ *) die "unknown option $1" ;;
110
+ esac
111
+ done
112
+ [[ -f "$HERE/$SCRIPT_NAME" ]] || die "$SCRIPT_NAME not found next to this script"
113
+ mkdir -p "$INSTALL_DIR"
114
+ install -o root -g root -m 0700 "$HERE/$SCRIPT_NAME" "$SCRIPT_PATH"
115
+ echo "installed $SCRIPT_PATH"
116
+ write_config "$url" "$secret"
117
+ for spec in "${DEFAULT_EVENTS[@]}"; do
118
+ IFS='|' read -r category event stage <<< "$spec"
119
+ add_hook "$category" "$event" "$stage"
120
+ done
121
+ echo
122
+ echo "done. Test with:"
123
+ echo " echo '{\"context\":{},\"data\":{\"user\":\"test\"},\"hook\":{\"category\":\"Whostmgr\",\"event\":\"Accounts::Create\",\"stage\":\"post\"}}' | $SCRIPT_PATH"
124
+ ;;
125
+ add)
126
+ need_root; need_manage_hooks
127
+ [[ $# -ge 2 ]] || die "usage: $0 add <Category> <Event> [pre|post]"
128
+ add_hook "$1" "$2" "${3:-post}"
129
+ ;;
130
+ remove)
131
+ need_root; need_manage_hooks
132
+ [[ $# -ge 2 ]] || die "usage: $0 remove <Category> <Event> [pre|post]"
133
+ remove_hook "$1" "$2" "${3:-post}"
134
+ ;;
135
+ list)
136
+ need_manage_hooks
137
+ "$MANAGE_HOOKS" list
138
+ ;;
139
+ uninstall)
140
+ need_root; need_manage_hooks
141
+ for spec in "${DEFAULT_EVENTS[@]}"; do
142
+ IFS='|' read -r category event stage <<< "$spec"
143
+ remove_hook "$category" "$event" "$stage"
144
+ done
145
+ echo "bridge hooks removed. Files in $INSTALL_DIR and $CONF_PATH were kept; delete them by hand if no longer needed."
146
+ echo "Hooks added with '$0 add' for events outside the default list must be removed with '$0 remove'."
147
+ ;;
148
+ *)
149
+ sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
150
+ exit 1
151
+ ;;
152
+ esac
@@ -0,0 +1,9 @@
1
+ import type { ICredentialDataDecryptedObject, ICredentialTestRequest, ICredentialType, IHttpRequestOptions, INodeProperties } from 'n8n-workflow';
2
+ export declare class WhmApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions>;
8
+ test: ICredentialTestRequest;
9
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WhmApi = void 0;
4
+ class WhmApi {
5
+ constructor() {
6
+ this.name = 'whmApi';
7
+ this.displayName = 'WHM API';
8
+ this.documentationUrl = 'https://api.docs.cpanel.net/whm/introduction/';
9
+ this.properties = [
10
+ {
11
+ displayName: 'Host',
12
+ name: 'host',
13
+ type: 'string',
14
+ default: '',
15
+ placeholder: 'server.example.com',
16
+ required: true,
17
+ description: 'Hostname or IP address of the WHM server, without a scheme or port. The node builds the URL from Host, Port and Use SSL.',
18
+ },
19
+ {
20
+ displayName: 'Port',
21
+ name: 'port',
22
+ type: 'number',
23
+ default: 2087,
24
+ required: true,
25
+ description: 'WHM listens on 2087 for HTTPS and 2086 for plain HTTP',
26
+ },
27
+ {
28
+ displayName: 'Use SSL',
29
+ name: 'useSsl',
30
+ type: 'boolean',
31
+ default: true,
32
+ description: 'Whether to connect over HTTPS. Turn this off only for port 2086 on a trusted private network — credentials are sent with every request.',
33
+ },
34
+ {
35
+ displayName: 'Username',
36
+ name: 'username',
37
+ type: 'string',
38
+ default: 'root',
39
+ required: true,
40
+ description: 'The WHM user the token or password belongs to — root or a reseller username',
41
+ },
42
+ {
43
+ displayName: 'Authentication',
44
+ name: 'authMethod',
45
+ type: 'options',
46
+ default: 'apiToken',
47
+ options: [
48
+ { name: 'API Token', value: 'apiToken', description: 'Recommended. Created in WHM under Development » Manage API Tokens.' },
49
+ { name: 'Password', value: 'password', description: 'HTTP Basic authentication with the WHM account password' },
50
+ ],
51
+ },
52
+ {
53
+ displayName: 'API Token',
54
+ name: 'apiToken',
55
+ type: 'string',
56
+ typeOptions: { password: true },
57
+ default: '',
58
+ displayOptions: { show: { authMethod: ['apiToken'] } },
59
+ description: 'The token generated in WHM. Restrict its privileges to the ACLs your workflows need.',
60
+ },
61
+ {
62
+ displayName: 'Password',
63
+ name: 'password',
64
+ type: 'string',
65
+ typeOptions: { password: true },
66
+ default: '',
67
+ displayOptions: { show: { authMethod: ['password'] } },
68
+ },
69
+ {
70
+ displayName: 'Ignore SSL Issues',
71
+ name: 'allowUnauthorizedCerts',
72
+ type: 'boolean',
73
+ default: false,
74
+ description: 'Whether to connect even if SSL certificate validation fails. WHM ships with a self-signed certificate by default, so this is often needed for servers that have not installed a trusted certificate on the hostname.',
75
+ },
76
+ ];
77
+ this.test = {
78
+ request: {
79
+ baseURL: '={{ ($credentials.useSsl ? "https" : "http") + "://" + $credentials.host.replace(/^https?:\\/\\//, "").replace(/\\/.*$/, "") + ":" + $credentials.port }}',
80
+ url: '/json-api/version',
81
+ method: 'GET',
82
+ qs: { 'api.version': 1 },
83
+ skipSslCertificateValidation: '={{ $credentials.allowUnauthorizedCerts }}',
84
+ },
85
+ };
86
+ }
87
+ async authenticate(credentials, requestOptions) {
88
+ var _a, _b, _c;
89
+ const username = String((_a = credentials.username) !== null && _a !== void 0 ? _a : 'root').trim();
90
+ let authorization;
91
+ if (credentials.authMethod === 'password') {
92
+ const basic = Buffer.from(`${username}:${(_b = credentials.password) !== null && _b !== void 0 ? _b : ''}`, 'utf8').toString('base64');
93
+ authorization = `Basic ${basic}`;
94
+ }
95
+ else {
96
+ authorization = `whm ${username}:${String((_c = credentials.apiToken) !== null && _c !== void 0 ? _c : '').trim()}`;
97
+ }
98
+ requestOptions.headers = {
99
+ ...requestOptions.headers,
100
+ Authorization: authorization,
101
+ };
102
+ return requestOptions;
103
+ }
104
+ }
105
+ exports.WhmApi = WhmApi;
106
+ //# sourceMappingURL=WhmApi.credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WhmApi.credentials.js","sourceRoot":"","sources":["../../credentials/WhmApi.credentials.ts"],"names":[],"mappings":"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"}