n8n-nodes-smbclient 1.1.0 → 1.1.2
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.
- package/.dockerignore +8 -0
- package/CLAUDE.md +59 -0
- package/Readme.md +52 -0
- package/dist/nodes/Smb2/SmbClientWrapper.d.ts +1 -0
- package/dist/nodes/Smb2/SmbClientWrapper.js +25 -31
- package/dist/nodes/Smb2/SmbClientWrapper.js.map +1 -1
- package/dist/package.json +3 -9
- package/docker/Dockerfile +44 -0
- package/docker/entrypoint.sh +31 -0
- package/docker-compose.yml +63 -0
- package/nodes/Smb2/SmbClientWrapper.ts +44 -40
- package/package.json +7 -4
- package/pnpm-workspace.yaml +18 -0
- package/renovate.json +2 -1
package/.dockerignore
ADDED
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
An [n8n](https://n8n.io/) community node package that lets workflows interact with SMB2/SMB3 file shares. Unlike most SMB libraries, this implementation **shells out to the `smbclient` CLI** (the Samba `samba-client` package) rather than speaking the SMB protocol in JS. The `smbclient` binary must be installed in the n8n runtime/container — without it the node fails at runtime.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm run build # rimraf dist + tsc compile + gulp copies icons into dist/
|
|
13
|
+
npm run dev # tsc --watch
|
|
14
|
+
npm run lint # eslint on nodes, credentials, package.json (n8n-nodes-base rules)
|
|
15
|
+
npm run lintfix # eslint --fix
|
|
16
|
+
npm run format # prettier --write on nodes and credentials
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
There is **no test suite** and no test runner configured. Verification is manual against a live SMB server / n8n instance (see "Local development" below).
|
|
20
|
+
|
|
21
|
+
Package manager is **pnpm** (see `pnpm-lock.yaml`), though npm scripts work too.
|
|
22
|
+
|
|
23
|
+
## Architecture
|
|
24
|
+
|
|
25
|
+
The build entrypoints n8n loads are declared in `package.json` under the `n8n` key — they point at compiled files in `dist/`, so you must `npm run build` before n8n can see changes.
|
|
26
|
+
|
|
27
|
+
Source is split into two halves:
|
|
28
|
+
|
|
29
|
+
- **`credentials/Smb2Api.credentials.ts`** — the `smb2Api` credential type (host, share, username, password, domain, port, timeouts). Note: several credential fields (`port`, timeouts, `maxProtocol`) are collected in the UI but **not actually passed to `smbclient`** — `buildBaseArgs()` only uses host/share/username/password/domain. Be aware of this gap when touching connection logic.
|
|
30
|
+
|
|
31
|
+
- **`nodes/Smb2/`** — the node itself, in three layers:
|
|
32
|
+
- **`Smb2.node.ts`** — n8n node definition (all UI properties, operation list) and `execute()`. `execute()` looks up a handler from the `handlers` map by operation name, builds one client, then loops over input items calling the handler per item. The client is closed in a `finally` (currently a no-op, see below).
|
|
33
|
+
- **`SmbEntryHelpers.ts`** — one `OpHandler` function per operation (`handleStat`, `handleList`, `handleGet`, etc.) plus the `handlers` map and `buildClient`. Handlers translate n8n params ↔ `SmbClientWrapper` calls. File transfers (`get`/`put`) go through a **temp file** in `os.tmpdir()` (n8n binary data ⇄ disk ⇄ smbclient), cleaned up after.
|
|
34
|
+
- **`SmbClientWrapper.ts`** — the only place that spawns a process. Each method (`stat`, `list`, `get`, `put`, `mkdir`, `rmdir`, `del`, `rename`) builds an `smbclient` invocation and runs it via `runOne()` → `execFile`. Every command is a **separate process**; there is no persistent connection, so `close()` is intentionally a no-op (kept for API parity).
|
|
35
|
+
|
|
36
|
+
### Key cross-cutting concerns
|
|
37
|
+
|
|
38
|
+
- **`smbclient` invocation shape**: `runOne()` runs `smbclient \\\\host\\share -U user%pass -g -c "<cmd>"`. The `-g` flag forces machine-parseable (pipe-delimited) output. Anonymous access uses `-U %` when no username is given.
|
|
39
|
+
- **Output parsing is fragile by nature** — `list()` parses `smbclient` text output and handles *multiple* formats (pipe-delimited and space-aligned columns) plus footer/artifact filtering, because output varies across Samba builds. If list parsing breaks, this is the place to look, and add a new format branch rather than rewriting.
|
|
40
|
+
- **Secret redaction**: `runOne()` never lets raw credentials reach error messages or logs. `redactCmd()` strips `-U`/`-A` args and host paths; `maskSecrets()` replaces username/password/domain substrings with `***`. **Any new code that surfaces command strings or stderr must route through these helpers.**
|
|
41
|
+
- **Error mapping**: `getReadableError()` + the `SMB_ERROR_HINTS` regex table translate raw errno/NT_STATUS strings into friendly messages. Errors are thrown as `NodeOperationError` / `NodeApiError`.
|
|
42
|
+
- **Debug logging** uses Node's `debuglog('n8n-nodes-smbclient')` — enable with `NODE_DEBUG=n8n-nodes-smbclient`.
|
|
43
|
+
|
|
44
|
+
### Adding a new operation
|
|
45
|
+
|
|
46
|
+
1. Add the value to the `Operation` union in `interfaces.ts`.
|
|
47
|
+
2. Add a method on `SmbClientWrapper` that builds the `smbclient -c` command (quote/escape paths; reuse `escapeSmbArg`).
|
|
48
|
+
3. Add an `OpHandler` in `SmbEntryHelpers.ts` and register it in the `handlers` map.
|
|
49
|
+
4. Add the operation to the `options` list and any UI params (with `displayOptions.show`) in `Smb2.node.ts`.
|
|
50
|
+
|
|
51
|
+
## Local development
|
|
52
|
+
|
|
53
|
+
Build, then mount this repo into an n8n container and reference it from `~/.n8n/nodes/package.json` as `"n8n-nodes-smbclient": "file:/opt/code-temp"`, then restart. Full steps and a docker-compose volume example are in `Readme.md` ("Running local"). Verify the binary with `smbclient --version` inside the container.
|
|
54
|
+
|
|
55
|
+
## Conventions
|
|
56
|
+
|
|
57
|
+
- Tabs for indentation, single quotes, semicolons, 100-char width (`.prettierrc.js` / `.editorconfig`).
|
|
58
|
+
- Lint enforces `eslint-plugin-n8n-nodes-base` community-node rules — run `npm run lint` before publishing; `prepublishOnly` also runs a stricter lint config.
|
|
59
|
+
- Some inline comments are in French (legacy); match the surrounding language when editing nearby, otherwise prefer English.
|
package/Readme.md
CHANGED
|
@@ -94,6 +94,58 @@ The node provides detailed error messages for common SMB issues:
|
|
|
94
94
|
- Quota issues
|
|
95
95
|
- File sharing violations
|
|
96
96
|
|
|
97
|
+
## Local testing with Docker Compose
|
|
98
|
+
|
|
99
|
+
A ready-to-use stack is provided to test the node from the n8n UI against a
|
|
100
|
+
throwaway Samba server. It spins up two services:
|
|
101
|
+
|
|
102
|
+
- **n8n** — built from `docker/Dockerfile` with the `samba-client` CLI baked in
|
|
103
|
+
(the node shells out to it) and this package auto-installed into
|
|
104
|
+
`~/.n8n/nodes` on startup.
|
|
105
|
+
- **smb** — a disposable Samba server (`dperson/samba`) exposing a writable
|
|
106
|
+
share `n8nshare` for the user `n8n` / `n8npass`.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# 1. Build the node (the container loads the compiled dist/, not the .ts)
|
|
110
|
+
npm run build
|
|
111
|
+
|
|
112
|
+
# 2. Start the stack
|
|
113
|
+
docker compose up --build
|
|
114
|
+
|
|
115
|
+
# 3. Open http://localhost:5678
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
In n8n, create an **Smbclient (SMB2) API** credential with:
|
|
119
|
+
|
|
120
|
+
| Field | Value |
|
|
121
|
+
| ---------- | ---------- |
|
|
122
|
+
| Server | `smb` |
|
|
123
|
+
| Share Name | `n8nshare` |
|
|
124
|
+
| User Name | `n8n` |
|
|
125
|
+
| Password | `n8npass` |
|
|
126
|
+
| Domain | _(empty)_ |
|
|
127
|
+
| Port | `445` |
|
|
128
|
+
|
|
129
|
+
Then add the **SMB2 using smbclient** node and try the operations (List, Upload,
|
|
130
|
+
Download, …) against the share.
|
|
131
|
+
|
|
132
|
+
After changing the node code, rebuild and reinstall it into the running n8n:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run build
|
|
136
|
+
REINSTALL_SMB_NODE=true docker compose up -d --force-recreate n8n
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The n8n version is configurable (defaults to the latest stable 2.x):
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
N8N_VERSION=2.23.3 docker compose up --build
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> n8n 2.x ships as a hardened Alpine image without a package manager, so the
|
|
146
|
+
> Dockerfile installs `samba-client` in a matching Alpine stage and copies the
|
|
147
|
+
> binary and its libraries into the final image.
|
|
148
|
+
|
|
97
149
|
## Running local (Development)
|
|
98
150
|
|
|
99
151
|
> Required podman and n8nio image setup
|
|
@@ -7,6 +7,7 @@ export declare class SmbClientWrapper {
|
|
|
7
7
|
private node;
|
|
8
8
|
constructor(auth: Smb2Credentials, smbclientPath: string | undefined, node: INode);
|
|
9
9
|
private buildBaseArgs;
|
|
10
|
+
private fail;
|
|
10
11
|
private runOne;
|
|
11
12
|
stat(remotePath: string): Promise<SmbStat>;
|
|
12
13
|
list(dir: string): Promise<SmbListEntry[]>;
|
|
@@ -8,6 +8,7 @@ const node_util_1 = require("node:util");
|
|
|
8
8
|
const node_child_process_1 = require("node:child_process");
|
|
9
9
|
const util_1 = require("util");
|
|
10
10
|
const debug = (0, util_1.debuglog)('n8n-nodes-smbclient');
|
|
11
|
+
const SMB_FAILURE_RE = /NT_STATUS_(?!OK\b|NO_MORE_FILES\b|NO_MORE_ENTRIES\b)[A-Z0-9_]+/;
|
|
11
12
|
const SMB_ERROR_HINTS = [
|
|
12
13
|
[/EACCES/i, 'Access Denied - Check your permissions for this file/folder'],
|
|
13
14
|
[/ENOENT/i, 'File/Path Not Found'],
|
|
@@ -65,30 +66,39 @@ class SmbClientWrapper {
|
|
|
65
66
|
const hostPart = `\\\\${host}\\${share}`;
|
|
66
67
|
return [hostPart, '-U', userPart, '-g'];
|
|
67
68
|
}
|
|
69
|
+
fail(safeCmd, detail) {
|
|
70
|
+
const { username, password, domain } = this.auth;
|
|
71
|
+
const safeErr = maskSecrets((detail || '').trim() || 'smbclient command failed', [
|
|
72
|
+
username,
|
|
73
|
+
password,
|
|
74
|
+
domain,
|
|
75
|
+
]);
|
|
76
|
+
throw new n8n_workflow_1.NodeOperationError(this.node, `smbclient failed. cmd="${safeCmd}" stderr="${safeErr}"`);
|
|
77
|
+
}
|
|
68
78
|
async runOne(cmd) {
|
|
69
|
-
var _a, _b;
|
|
70
79
|
const args = [...this.buildBaseArgs(), '-c', cmd];
|
|
71
80
|
const { username, password, domain } = this.auth;
|
|
72
81
|
const rawCmd = [this.smbclientPath, ...args].join(' ');
|
|
73
82
|
const safeCmd = maskSecrets(redactCmd(rawCmd), [username, password, domain]);
|
|
83
|
+
let stdout = '';
|
|
84
|
+
let stderr = '';
|
|
74
85
|
try {
|
|
75
|
-
|
|
86
|
+
({ stdout, stderr } = await execFileAsync(this.smbclientPath, args, {
|
|
76
87
|
maxBuffer: 10 * 1024 * 1024,
|
|
77
|
-
});
|
|
78
|
-
if (stderr && /NT_STATUS|Error|failed/i.test(stderr)) {
|
|
79
|
-
const safeErr = maskSecrets(stderr.trim(), [username, password, domain]);
|
|
80
|
-
throw new n8n_workflow_1.NodeOperationError(this.node, {
|
|
81
|
-
message: `smbclient failed. cmd="${safeCmd}" stderr="${safeErr}"`,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
return stdout !== null && stdout !== void 0 ? stdout : '';
|
|
88
|
+
}));
|
|
85
89
|
}
|
|
86
90
|
catch (err) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
})
|
|
91
|
+
stdout = typeof (err === null || err === void 0 ? void 0 : err.stdout) === 'string' ? err.stdout : '';
|
|
92
|
+
stderr = typeof (err === null || err === void 0 ? void 0 : err.stderr) === 'string' ? err.stderr : '';
|
|
93
|
+
const couldNotRun = typeof (err === null || err === void 0 ? void 0 : err.code) !== 'number' || (err === null || err === void 0 ? void 0 : err.killed) === true;
|
|
94
|
+
if (couldNotRun || SMB_FAILURE_RE.test(`${stdout}\n${stderr}`)) {
|
|
95
|
+
this.fail(safeCmd, stderr.trim() || stdout.trim() || (err === null || err === void 0 ? void 0 : err.message));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (SMB_FAILURE_RE.test(`${stdout}\n${stderr}`)) {
|
|
99
|
+
this.fail(safeCmd, stderr.trim() || stdout.trim());
|
|
91
100
|
}
|
|
101
|
+
return stdout !== null && stdout !== void 0 ? stdout : '';
|
|
92
102
|
}
|
|
93
103
|
async stat(remotePath) {
|
|
94
104
|
var _a, _b, _c, _d, _e;
|
|
@@ -122,7 +132,7 @@ class SmbClientWrapper {
|
|
|
122
132
|
.map((l) => l.trim())
|
|
123
133
|
.filter(Boolean);
|
|
124
134
|
const parsed = lines.map((line) => {
|
|
125
|
-
var _a
|
|
135
|
+
var _a;
|
|
126
136
|
if (line.includes('|')) {
|
|
127
137
|
const parts = line.split('|');
|
|
128
138
|
if (parts.length >= 5 && /^\d+$/.test(parts[1])) {
|
|
@@ -141,22 +151,6 @@ class SmbClientWrapper {
|
|
|
141
151
|
isDirectory: attributes.includes('D'),
|
|
142
152
|
};
|
|
143
153
|
}
|
|
144
|
-
if (parts.length >= 5 && /^\d+$/.test(parts[1])) {
|
|
145
|
-
const attr = (_b = parts[0]) !== null && _b !== void 0 ? _b : '';
|
|
146
|
-
const size = Number(parts[1]) || 0;
|
|
147
|
-
const date = parts[2];
|
|
148
|
-
const time = parts[3];
|
|
149
|
-
const name = parts.slice(4).join('|');
|
|
150
|
-
const attributes = attr.split('').filter(Boolean);
|
|
151
|
-
return {
|
|
152
|
-
name,
|
|
153
|
-
size,
|
|
154
|
-
date,
|
|
155
|
-
time,
|
|
156
|
-
attributes,
|
|
157
|
-
isDirectory: attributes.includes('D'),
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
154
|
return {
|
|
161
155
|
name: line,
|
|
162
156
|
size: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SmbClientWrapper.js","sourceRoot":"","sources":["../../../nodes/Smb2/SmbClientWrapper.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"SmbClientWrapper.js","sourceRoot":"","sources":["../../../nodes/Smb2/SmbClientWrapper.ts"],"names":[],"mappings":"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"}
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-smbclient",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Transfer files via Samba (SMB2) and smbclient package with n8n, some of the base work are based on https://github.com/drudge/n8n-nodes-smb work.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@eslint/compat": "^2.0.5",
|
|
42
42
|
"@eslint/eslintrc": "^3.3.5",
|
|
43
43
|
"@eslint/js": "^10.0.1",
|
|
44
|
-
"@types/node": "^24.12.
|
|
44
|
+
"@types/node": "^24.12.4",
|
|
45
45
|
"@typescript-eslint/parser": "^8.59.1",
|
|
46
46
|
"eslint": "^10.3.0",
|
|
47
47
|
"eslint-plugin-n8n-nodes-base": "^1.16.6",
|
|
@@ -58,12 +58,6 @@
|
|
|
58
58
|
},
|
|
59
59
|
"overrides": {
|
|
60
60
|
"form-data": "^4.0.4",
|
|
61
|
-
"axios": "1.
|
|
62
|
-
},
|
|
63
|
-
"pnpm": {
|
|
64
|
-
"onlyBuiltDependencies": [
|
|
65
|
-
"eslint-plugin-n8n-nodes-base",
|
|
66
|
-
"isolated-vm"
|
|
67
|
-
]
|
|
61
|
+
"axios": "1.17.0"
|
|
68
62
|
}
|
|
69
63
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# n8n image preloaded with the samba-client CLI that n8n-nodes-smbclient
|
|
2
|
+
# shells out to. The community node itself is installed at startup by
|
|
3
|
+
# entrypoint.sh from the source mounted at /opt/code-temp.
|
|
4
|
+
#
|
|
5
|
+
# n8n 2.x ships as a Docker Hardened Image (Alpine, no package manager), so we
|
|
6
|
+
# can't `apk add` directly. Instead we install samba-client in a matching plain
|
|
7
|
+
# Alpine and copy the resulting files (smbclient + its dependency libraries)
|
|
8
|
+
# into the final image.
|
|
9
|
+
|
|
10
|
+
# Declared before the first FROM so it can be used in the stage-2 FROM line.
|
|
11
|
+
ARG N8N_VERSION=2.23.3
|
|
12
|
+
|
|
13
|
+
# ---- Stage 1: collect smbclient + its dependency files ----
|
|
14
|
+
FROM alpine:3.23 AS smb
|
|
15
|
+
RUN set -eux; \
|
|
16
|
+
apk add --no-cache samba-client 2>&1 | tee /tmp/apk.log; \
|
|
17
|
+
smbclient --version; \
|
|
18
|
+
mkdir -p /export; \
|
|
19
|
+
# Copy every file owned by samba-client and the packages it pulled in.
|
|
20
|
+
# Base Alpine packages (musl, etc.) are already in the hardened image, so
|
|
21
|
+
# they never appear in the "Installing" lines and aren't copied/clobbered.
|
|
22
|
+
for p in $(grep -oE 'Installing [^ ]+' /tmp/apk.log | awk '{print $2}'); do \
|
|
23
|
+
apk info -L "$p" | grep -E '^(usr/|lib/|etc/)' | while read -r f; do \
|
|
24
|
+
[ -e "/$f" ] || continue; \
|
|
25
|
+
mkdir -p "/export/$(dirname "$f")"; \
|
|
26
|
+
cp -a "/$f" "/export/$f"; \
|
|
27
|
+
done; \
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
# ---- Stage 2: n8n + smbclient ----
|
|
31
|
+
FROM n8nio/n8n:${N8N_VERSION}
|
|
32
|
+
|
|
33
|
+
USER root
|
|
34
|
+
|
|
35
|
+
COPY --from=smb /export/ /
|
|
36
|
+
# Fail the build early if smbclient can't run (missing lib, etc.).
|
|
37
|
+
RUN smbclient --version
|
|
38
|
+
|
|
39
|
+
COPY docker/entrypoint.sh /usr/local/bin/smbclient-node-entrypoint.sh
|
|
40
|
+
RUN chmod +x /usr/local/bin/smbclient-node-entrypoint.sh
|
|
41
|
+
|
|
42
|
+
USER node
|
|
43
|
+
|
|
44
|
+
ENTRYPOINT ["tini", "--", "/usr/local/bin/smbclient-node-entrypoint.sh"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Installs the locally-built n8n-nodes-smbclient package (mounted at
|
|
3
|
+
# /opt/code-temp) into n8n's user nodes directory, then starts n8n.
|
|
4
|
+
#
|
|
5
|
+
# The host must run `npm run build` (or `pnpm build`) first so that
|
|
6
|
+
# /opt/code-temp/dist exists — n8n loads the compiled files, not the .ts.
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
SOURCE_DIR="/opt/code-temp"
|
|
10
|
+
NODES_DIR="/home/node/.n8n/nodes"
|
|
11
|
+
PKG_NAME="n8n-nodes-smbclient"
|
|
12
|
+
|
|
13
|
+
if [ ! -d "$SOURCE_DIR/dist" ]; then
|
|
14
|
+
echo "WARNING: $SOURCE_DIR/dist not found."
|
|
15
|
+
echo " Run 'npm run build' on the host, then restart this container."
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
mkdir -p "$NODES_DIR"
|
|
19
|
+
cd "$NODES_DIR"
|
|
20
|
+
|
|
21
|
+
[ -f package.json ] || echo '{"name":"installed-nodes","private":true}' >package.json
|
|
22
|
+
|
|
23
|
+
if [ ! -d "node_modules/$PKG_NAME" ] || [ "${REINSTALL_SMB_NODE:-false}" = "true" ]; then
|
|
24
|
+
echo "Installing $PKG_NAME from $SOURCE_DIR ..."
|
|
25
|
+
npm install "$SOURCE_DIR" --no-audit --no-fund --loglevel error
|
|
26
|
+
else
|
|
27
|
+
echo "$PKG_NAME already installed (set REINSTALL_SMB_NODE=true to refresh after a rebuild)."
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Hand off to n8n's own entrypoint.
|
|
31
|
+
exec /docker-entrypoint.sh "$@"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: n8n-nodes-smbclient-dev
|
|
2
|
+
|
|
3
|
+
# Local stack to test the SMB2 node end-to-end from the n8n UI:
|
|
4
|
+
# - n8n: built with the samba-client CLI + this node auto-installed
|
|
5
|
+
# - smb: a throwaway Samba server with a ready-to-use share
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# 1. npm run build # produce dist/ on the host
|
|
9
|
+
# 2. docker compose up --build
|
|
10
|
+
# 3. open http://localhost:5678 and create an "Smbclient (SMB2) API" credential:
|
|
11
|
+
# Server: smb
|
|
12
|
+
# Share Name: n8nshare
|
|
13
|
+
# User Name: n8n
|
|
14
|
+
# Password: n8npass
|
|
15
|
+
# Domain: (leave empty)
|
|
16
|
+
# Port: 445
|
|
17
|
+
#
|
|
18
|
+
# After editing the node code: `npm run build`, then
|
|
19
|
+
# REINSTALL_SMB_NODE=true docker compose up -d --force-recreate n8n
|
|
20
|
+
|
|
21
|
+
services:
|
|
22
|
+
n8n:
|
|
23
|
+
build:
|
|
24
|
+
context: .
|
|
25
|
+
dockerfile: docker/Dockerfile
|
|
26
|
+
args:
|
|
27
|
+
N8N_VERSION: ${N8N_VERSION:-2.23.3}
|
|
28
|
+
container_name: n8n-smbclient
|
|
29
|
+
restart: unless-stopped
|
|
30
|
+
ports:
|
|
31
|
+
- "5678:5678"
|
|
32
|
+
environment:
|
|
33
|
+
- N8N_SECURE_COOKIE=false
|
|
34
|
+
- N8N_RUNNERS_ENABLED=true
|
|
35
|
+
- GENERIC_TIMEZONE=${TZ:-UTC}
|
|
36
|
+
- TZ=${TZ:-UTC}
|
|
37
|
+
# set to true to reinstall the node from /opt/code-temp on next start
|
|
38
|
+
- REINSTALL_SMB_NODE=${REINSTALL_SMB_NODE:-false}
|
|
39
|
+
volumes:
|
|
40
|
+
- n8n_data:/home/node/.n8n
|
|
41
|
+
# the built package, installed into ~/.n8n/nodes by the entrypoint
|
|
42
|
+
- ./:/opt/code-temp:ro
|
|
43
|
+
depends_on:
|
|
44
|
+
- smb
|
|
45
|
+
|
|
46
|
+
smb:
|
|
47
|
+
image: dperson/samba
|
|
48
|
+
container_name: n8n-smbclient-samba
|
|
49
|
+
restart: unless-stopped
|
|
50
|
+
# -u "user;password"
|
|
51
|
+
# -s "name;path;browseable;readonly;guest;users;admins"
|
|
52
|
+
command: >-
|
|
53
|
+
-p
|
|
54
|
+
-u "n8n;n8npass"
|
|
55
|
+
-s "n8nshare;/share;yes;no;no;n8n;n8n"
|
|
56
|
+
ports:
|
|
57
|
+
- "1445:445" # host:container — only needed to inspect the share from the host
|
|
58
|
+
volumes:
|
|
59
|
+
- smb_data:/share
|
|
60
|
+
|
|
61
|
+
volumes:
|
|
62
|
+
n8n_data:
|
|
63
|
+
smb_data:
|
|
@@ -11,7 +11,16 @@ import { Smb2Credentials, SmbListEntry, SmbStat } from './interfaces';
|
|
|
11
11
|
import { debuglog } from 'util';
|
|
12
12
|
|
|
13
13
|
const debug = debuglog('n8n-nodes-smbclient');
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
// smbclient's exit code is unreliable — it can exit non-zero even when the
|
|
16
|
+
// requested command actually succeeded (notably `del`/`rename` against some
|
|
17
|
+
// Windows servers, which complete the operation but still return a failure
|
|
18
|
+
// status). The reliable signal is whether its output contains a genuine
|
|
19
|
+
// NT_STATUS_* error code. We therefore key success/failure off this pattern
|
|
20
|
+
// rather than the exit code. Excluded: NT_STATUS_OK (success) and the benign
|
|
21
|
+
// end-of-listing markers emitted during normal `ls`/`del` wildcard expansion.
|
|
22
|
+
const SMB_FAILURE_RE = /NT_STATUS_(?!OK\b|NO_MORE_FILES\b|NO_MORE_ENTRIES\b)[A-Z0-9_]+/;
|
|
23
|
+
|
|
15
24
|
const SMB_ERROR_HINTS: Array<[RegExp, string]> = [
|
|
16
25
|
[/EACCES/i, 'Access Denied - Check your permissions for this file/folder'],
|
|
17
26
|
[/ENOENT/i, 'File/Path Not Found'],
|
|
@@ -74,6 +83,18 @@ export class SmbClientWrapper {
|
|
|
74
83
|
return [hostPart, '-U', userPart, '-g'];
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
private fail(safeCmd: string, detail: string): never {
|
|
87
|
+
const { username, password, domain } = this.auth;
|
|
88
|
+
const safeErr = maskSecrets((detail || '').trim() || 'smbclient command failed', [
|
|
89
|
+
username,
|
|
90
|
+
password,
|
|
91
|
+
domain,
|
|
92
|
+
]);
|
|
93
|
+
// The message must be passed as a string (or via options.message); passing a
|
|
94
|
+
// `{ message }` object as the second arg leaves NodeOperationError.message empty.
|
|
95
|
+
throw new NodeOperationError(this.node, `smbclient failed. cmd="${safeCmd}" stderr="${safeErr}"`);
|
|
96
|
+
}
|
|
97
|
+
|
|
77
98
|
private async runOne(cmd: string): Promise<string> {
|
|
78
99
|
const args = [...this.buildBaseArgs(), '-c', cmd];
|
|
79
100
|
|
|
@@ -82,31 +103,33 @@ export class SmbClientWrapper {
|
|
|
82
103
|
const rawCmd = [this.smbclientPath, ...args].join(' ');
|
|
83
104
|
const safeCmd = maskSecrets(redactCmd(rawCmd), [username, password, domain]);
|
|
84
105
|
|
|
106
|
+
let stdout = '';
|
|
107
|
+
let stderr = '';
|
|
85
108
|
try {
|
|
86
|
-
|
|
109
|
+
({ stdout, stderr } = await execFileAsync(this.smbclientPath, args, {
|
|
87
110
|
maxBuffer: 10 * 1024 * 1024,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
}));
|
|
112
|
+
} catch (err: any) {
|
|
113
|
+
// execFile rejects both when smbclient can't be run (spawn error,
|
|
114
|
+
// timeout/kill) and when it merely exits non-zero. The latter is not a
|
|
115
|
+
// reliable failure signal, so only treat it as an error when smbclient
|
|
116
|
+
// could not run at all, or when its output reports a real NT_STATUS_*
|
|
117
|
+
// failure. Otherwise keep its output and fall through as success.
|
|
118
|
+
stdout = typeof err?.stdout === 'string' ? err.stdout : '';
|
|
119
|
+
stderr = typeof err?.stderr === 'string' ? err.stderr : '';
|
|
120
|
+
const couldNotRun = typeof err?.code !== 'number' || err?.killed === true;
|
|
121
|
+
if (couldNotRun || SMB_FAILURE_RE.test(`${stdout}\n${stderr}`)) {
|
|
122
|
+
this.fail(safeCmd, stderr.trim() || stdout.trim() || err?.message);
|
|
97
123
|
}
|
|
124
|
+
}
|
|
98
125
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
[username, password, domain],
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
throw new NodeOperationError(this.node, {
|
|
107
|
-
message: `smbclient failed. cmd="${safeCmd}" stderr="${safeErr}"`,
|
|
108
|
-
});
|
|
126
|
+
// smbclient sometimes reports a failed command in its output while still
|
|
127
|
+
// exiting cleanly, so check the output regardless of the exit code.
|
|
128
|
+
if (SMB_FAILURE_RE.test(`${stdout}\n${stderr}`)) {
|
|
129
|
+
this.fail(safeCmd, stderr.trim() || stdout.trim());
|
|
109
130
|
}
|
|
131
|
+
|
|
132
|
+
return stdout ?? '';
|
|
110
133
|
}
|
|
111
134
|
|
|
112
135
|
async stat(remotePath: string): Promise<SmbStat> {
|
|
@@ -129,7 +152,6 @@ export class SmbClientWrapper {
|
|
|
129
152
|
}
|
|
130
153
|
|
|
131
154
|
async list(dir: string): Promise<SmbListEntry[]> {
|
|
132
|
-
// const out = await this.runOne(`ls "${dir}"`);
|
|
133
155
|
const normalizedDir = (dir ?? '').trim();
|
|
134
156
|
const safeDir = this.escapeSmbArg(normalizedDir);
|
|
135
157
|
|
|
@@ -168,24 +190,6 @@ export class SmbClientWrapper {
|
|
|
168
190
|
} as SmbListEntry;
|
|
169
191
|
}
|
|
170
192
|
|
|
171
|
-
// Alternate pattern seen on some builds: attr|size|date|time|name
|
|
172
|
-
if (parts.length >= 5 && /^\d+$/.test(parts[1])) {
|
|
173
|
-
const attr = parts[0] ?? '';
|
|
174
|
-
const size = Number(parts[1]) || 0;
|
|
175
|
-
const date = parts[2];
|
|
176
|
-
const time = parts[3];
|
|
177
|
-
const name = parts.slice(4).join('|'); // keep any extra pipes in name
|
|
178
|
-
const attributes = attr.split('').filter(Boolean);
|
|
179
|
-
return {
|
|
180
|
-
name,
|
|
181
|
-
size,
|
|
182
|
-
date,
|
|
183
|
-
time,
|
|
184
|
-
attributes,
|
|
185
|
-
isDirectory: attributes.includes('D'),
|
|
186
|
-
} as SmbListEntry;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
193
|
// Fallback if unexpected pipe layout: treat whole line as name
|
|
190
194
|
return {
|
|
191
195
|
name: line,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-smbclient",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Transfer files via Samba (SMB2) and smbclient package with n8n, some of the base work are based on https://github.com/drudge/n8n-nodes-smb work.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
"engines": {
|
|
18
18
|
"node": ">=20.15"
|
|
19
19
|
},
|
|
20
|
-
"repository":
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/JimmyMtl/n8n-nodes-smbclient"
|
|
23
|
+
},
|
|
21
24
|
"main": "index.js",
|
|
22
25
|
"n8n": {
|
|
23
26
|
"n8nNodesApiVersion": 1,
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
"@eslint/compat": "^2.0.5",
|
|
33
36
|
"@eslint/eslintrc": "^3.3.5",
|
|
34
37
|
"@eslint/js": "^10.0.1",
|
|
35
|
-
"@types/node": "^24.12.
|
|
38
|
+
"@types/node": "^24.12.4",
|
|
36
39
|
"@typescript-eslint/parser": "^8.59.1",
|
|
37
40
|
"eslint": "^10.3.0",
|
|
38
41
|
"eslint-plugin-n8n-nodes-base": "^1.16.6",
|
|
@@ -49,7 +52,7 @@
|
|
|
49
52
|
},
|
|
50
53
|
"overrides": {
|
|
51
54
|
"form-data": "^4.0.4",
|
|
52
|
-
"axios": "1.
|
|
55
|
+
"axios": "1.17.0"
|
|
53
56
|
},
|
|
54
57
|
"scripts": {
|
|
55
58
|
"build": "npx rimraf dist && tsc && gulp build:icons",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# pnpm settings (moved out of the deprecated `pnpm` field in package.json).
|
|
2
|
+
|
|
3
|
+
# Refuse to resolve/lock any package version published less than this many
|
|
4
|
+
# minutes ago. Mirrors the CI supply-chain policy so `pnpm install` locally
|
|
5
|
+
# picks the same compliant versions CI will accept, instead of the newest
|
|
6
|
+
# release (which may still be inside the cutoff and fail --frozen-lockfile).
|
|
7
|
+
minimumReleaseAge: 1440
|
|
8
|
+
|
|
9
|
+
# Packages allowed to run install scripts.
|
|
10
|
+
# pnpm 10 reads `onlyBuiltDependencies`; pnpm 11 renamed it to `allowBuilds`.
|
|
11
|
+
# We keep both so the install passes regardless of the pnpm version CI uses
|
|
12
|
+
# (the workflows pin `pnpm/action-setup` to `version: latest`).
|
|
13
|
+
onlyBuiltDependencies:
|
|
14
|
+
- eslint-plugin-n8n-nodes-base
|
|
15
|
+
- isolated-vm
|
|
16
|
+
allowBuilds:
|
|
17
|
+
eslint-plugin-n8n-nodes-base: true
|
|
18
|
+
isolated-vm: true
|