typescript-virtual-container 1.2.8 → 1.3.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.
- package/.vscode/settings.json +0 -1
- package/README.md +462 -44
- package/biome.json +7 -0
- package/dist/SSHMimic/exec.d.ts.map +1 -1
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +35 -21
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +20 -6
- package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
- package/dist/VirtualFileSystem/binaryPack.js +29 -6
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +36 -13
- package/dist/VirtualPackageManager/index.d.ts +202 -0
- package/dist/VirtualPackageManager/index.d.ts.map +1 -0
- package/dist/VirtualPackageManager/index.js +825 -0
- package/dist/VirtualShell/index.d.ts +93 -12
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +95 -13
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +3 -1
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.d.ts +52 -20
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +54 -20
- package/dist/commands/adduser.d.ts +6 -0
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +6 -0
- package/dist/commands/alias.d.ts +9 -0
- package/dist/commands/alias.d.ts.map +1 -0
- package/dist/commands/alias.js +63 -0
- package/dist/commands/apt.d.ts +9 -0
- package/dist/commands/apt.d.ts.map +1 -0
- package/dist/commands/apt.js +205 -0
- package/dist/commands/awk.d.ts +11 -0
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +15 -2
- package/dist/commands/base64.d.ts +5 -0
- package/dist/commands/base64.d.ts.map +1 -1
- package/dist/commands/base64.js +9 -1
- package/dist/commands/cat.d.ts +5 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +35 -8
- package/dist/commands/cd.d.ts +5 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +5 -0
- package/dist/commands/chmod.d.ts +5 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +57 -3
- package/dist/commands/command-helpers.d.ts +78 -4
- package/dist/commands/command-helpers.d.ts.map +1 -1
- package/dist/commands/command-helpers.js +78 -4
- package/dist/commands/cp.d.ts +5 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +5 -0
- package/dist/commands/curl.d.ts +5 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +106 -26
- package/dist/commands/cut.d.ts +5 -0
- package/dist/commands/cut.d.ts.map +1 -1
- package/dist/commands/cut.js +8 -1
- package/dist/commands/date.d.ts +5 -0
- package/dist/commands/date.d.ts.map +1 -1
- package/dist/commands/date.js +7 -1
- package/dist/commands/declare.d.ts +3 -0
- package/dist/commands/declare.d.ts.map +1 -0
- package/dist/commands/declare.js +39 -0
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +5 -0
- package/dist/commands/dpkg.d.ts +9 -0
- package/dist/commands/dpkg.d.ts.map +1 -0
- package/dist/commands/dpkg.js +161 -0
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +8 -2
- package/dist/commands/echo.d.ts +5 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +33 -12
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +11 -1
- package/dist/commands/exit.d.ts +5 -0
- package/dist/commands/exit.d.ts.map +1 -1
- package/dist/commands/exit.js +12 -2
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +3 -1
- package/dist/commands/find.d.ts +5 -0
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +5 -0
- package/dist/commands/free.d.ts +8 -0
- package/dist/commands/free.d.ts.map +1 -0
- package/dist/commands/free.js +43 -0
- package/dist/commands/grep.d.ts +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +12 -2
- package/dist/commands/gzip.d.ts +5 -0
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +18 -2
- package/dist/commands/head.d.ts +5 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +5 -0
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +98 -45
- package/dist/commands/helpers.d.ts +3 -0
- package/dist/commands/helpers.d.ts.map +1 -1
- package/dist/commands/helpers.js +3 -0
- package/dist/commands/history.d.ts +8 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +26 -0
- package/dist/commands/hostname.d.ts +5 -0
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +5 -0
- package/dist/commands/id.d.ts.map +1 -1
- package/dist/commands/id.js +4 -1
- package/dist/commands/index.d.ts +2 -10
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -231
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +6 -3
- package/dist/commands/lsb-release.d.ts +3 -0
- package/dist/commands/lsb-release.d.ts.map +1 -0
- package/dist/commands/lsb-release.js +56 -0
- package/dist/commands/man.d.ts +3 -0
- package/dist/commands/man.d.ts.map +1 -0
- package/dist/commands/man.js +155 -0
- package/dist/commands/nano.js +1 -1
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +6 -1
- package/dist/commands/node.d.ts +9 -0
- package/dist/commands/node.d.ts.map +1 -0
- package/dist/commands/node.js +316 -0
- package/dist/commands/npm.d.ts +19 -0
- package/dist/commands/npm.d.ts.map +1 -0
- package/dist/commands/npm.js +109 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +7 -2
- package/dist/commands/printf.d.ts +3 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +113 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +30 -6
- package/dist/commands/python.d.ts +30 -0
- package/dist/commands/python.d.ts.map +1 -0
- package/dist/commands/python.js +2058 -0
- package/dist/commands/read.d.ts +3 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +34 -0
- package/dist/commands/registry.d.ts +8 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +229 -0
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -0
- package/dist/commands/runtime.js +280 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +11 -3
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +9 -3
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +69 -30
- package/dist/commands/shift.d.ts +5 -0
- package/dist/commands/shift.d.ts.map +1 -0
- package/dist/commands/shift.js +52 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +4 -2
- package/dist/commands/source.d.ts +3 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +34 -0
- package/dist/commands/sudo.js +1 -1
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +11 -3
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +8 -6
- package/dist/commands/test.d.ts +3 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +114 -0
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +3 -1
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +14 -0
- package/dist/commands/type.d.ts +3 -0
- package/dist/commands/type.d.ts.map +1 -0
- package/dist/commands/type.js +34 -0
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +4 -1
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uptime.d.ts +3 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +43 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +92 -96
- package/dist/commands/which.d.ts +3 -0
- package/dist/commands/which.d.ts.map +1 -0
- package/dist/commands/which.js +32 -0
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +1 -1
- package/dist/index.d.ts +15 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/modules/linuxRootfs.d.ts +41 -0
- package/dist/modules/linuxRootfs.d.ts.map +1 -0
- package/dist/modules/linuxRootfs.js +440 -0
- package/dist/modules/neofetch.d.ts.map +1 -1
- package/dist/modules/neofetch.js +1 -0
- package/dist/standalone-wo-sftp.d.ts +2 -0
- package/dist/standalone-wo-sftp.d.ts.map +1 -0
- package/dist/standalone-wo-sftp.js +30 -0
- package/dist/utils/expand.d.ts +50 -0
- package/dist/utils/expand.d.ts.map +1 -0
- package/dist/utils/expand.js +183 -0
- package/dist/utils/vfsDiff.d.ts +90 -0
- package/dist/utils/vfsDiff.d.ts.map +1 -0
- package/dist/utils/vfsDiff.js +177 -0
- package/package.json +3 -1
- package/src/SSHMimic/exec.ts +10 -1
- package/src/SSHMimic/executor.ts +105 -21
- package/src/SSHMimic/index.ts +49 -15
- package/src/VirtualFileSystem/binaryPack.ts +35 -8
- package/src/VirtualFileSystem/index.ts +78 -28
- package/src/VirtualPackageManager/index.ts +979 -0
- package/src/VirtualShell/index.ts +133 -14
- package/src/VirtualShell/shell.ts +23 -3
- package/src/VirtualShell/shellParser.ts +134 -36
- package/src/VirtualUserManager/index.ts +62 -22
- package/src/commands/adduser.ts +6 -0
- package/src/commands/alias.ts +64 -0
- package/src/commands/apt.ts +228 -0
- package/src/commands/awk.ts +20 -6
- package/src/commands/base64.ts +13 -2
- package/src/commands/cat.ts +40 -8
- package/src/commands/cd.ts +5 -0
- package/src/commands/chmod.ts +53 -3
- package/src/commands/command-helpers.ts +78 -4
- package/src/commands/cp.ts +5 -0
- package/src/commands/curl.ts +118 -33
- package/src/commands/cut.ts +8 -1
- package/src/commands/date.ts +7 -1
- package/src/commands/declare.ts +44 -0
- package/src/commands/diff.ts +17 -3
- package/src/commands/dpkg.ts +180 -0
- package/src/commands/du.ts +17 -5
- package/src/commands/echo.ts +41 -12
- package/src/commands/env.ts +11 -1
- package/src/commands/exit.ts +12 -2
- package/src/commands/export.ts +3 -1
- package/src/commands/find.ts +5 -0
- package/src/commands/free.ts +47 -0
- package/src/commands/grep.ts +12 -2
- package/src/commands/gzip.ts +28 -4
- package/src/commands/head.ts +5 -0
- package/src/commands/help.ts +121 -47
- package/src/commands/helpers.ts +8 -0
- package/src/commands/history.ts +34 -0
- package/src/commands/hostname.ts +5 -0
- package/src/commands/id.ts +4 -1
- package/src/commands/index.ts +9 -255
- package/src/commands/ls.ts +6 -3
- package/src/commands/lsb-release.ts +58 -0
- package/src/commands/man.ts +166 -0
- package/src/commands/nano.ts +1 -1
- package/src/commands/neofetch.ts +6 -1
- package/src/commands/node.ts +341 -0
- package/src/commands/npm.ts +132 -0
- package/src/commands/ping.ts +10 -3
- package/src/commands/printf.ts +112 -0
- package/src/commands/ps.ts +40 -6
- package/src/commands/python.ts +2229 -0
- package/src/commands/read.ts +41 -0
- package/src/commands/registry.ts +244 -0
- package/src/commands/runtime.ts +353 -0
- package/src/commands/sed.ts +27 -9
- package/src/commands/set.ts +9 -3
- package/src/commands/sh.ts +170 -44
- package/src/commands/shift.ts +53 -0
- package/src/commands/sleep.ts +2 -1
- package/src/commands/sort.ts +10 -6
- package/src/commands/source.ts +47 -0
- package/src/commands/sudo.ts +1 -1
- package/src/commands/tar.ts +28 -7
- package/src/commands/tee.ts +7 -1
- package/src/commands/test.ts +135 -0
- package/src/commands/tr.ts +3 -1
- package/src/commands/true.ts +17 -0
- package/src/commands/type.ts +43 -0
- package/src/commands/uname.ts +5 -1
- package/src/commands/uniq.ts +8 -2
- package/src/commands/uptime.ts +49 -0
- package/src/commands/wget.ts +105 -119
- package/src/commands/which.ts +37 -0
- package/src/commands/xargs.ts +11 -2
- package/src/index.ts +27 -18
- package/src/modules/linuxRootfs.ts +642 -0
- package/src/modules/neofetch.ts +1 -0
- package/src/standalone-wo-sftp.ts +38 -0
- package/src/utils/expand.ts +238 -0
- package/src/utils/vfsDiff.ts +275 -0
- package/standalone-wo-sftp.js +507 -0
- package/standalone-wo-sftp.js.map +7 -0
- package/standalone.js +486 -109
- package/standalone.js.map +4 -4
- package/tests/bun-test-shim.ts +9 -1
- package/tests/command-helpers.test.ts +1 -5
- package/tests/new-features.test.ts +1036 -0
- package/tests/parser-executor.test.ts +27 -27
- package/tests/sftp.test.ts +122 -42
- package/tests/users.test.ts +23 -5
- package/CHANGELOG.md +0 -150
package/src/SSHMimic/executor.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runCommandDirect } from "../commands";
|
|
2
2
|
import { resolvePath } from "../commands/helpers";
|
|
3
3
|
import type { CommandMode, CommandResult, ShellEnv } from "../types/commands";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
Pipeline,
|
|
6
|
+
PipelineCommand,
|
|
7
|
+
Script,
|
|
8
|
+
Statement,
|
|
9
|
+
} from "../types/pipeline";
|
|
5
10
|
import type { VirtualShell } from "../VirtualShell";
|
|
6
11
|
|
|
7
12
|
// ── Script executor (handles &&/||/;) ────────────────────────────────────────
|
|
@@ -15,17 +20,30 @@ export async function executeScript(
|
|
|
15
20
|
shell: VirtualShell,
|
|
16
21
|
env: ShellEnv,
|
|
17
22
|
): Promise<CommandResult> {
|
|
18
|
-
if (!script.isValid)
|
|
23
|
+
if (!script.isValid)
|
|
24
|
+
return { stderr: script.error || "Syntax error", exitCode: 1 };
|
|
19
25
|
|
|
20
26
|
let lastResult: CommandResult = { exitCode: 0 };
|
|
21
27
|
|
|
22
28
|
for (const stmt of script.statements) {
|
|
23
29
|
// Decide whether to run this statement based on previous op
|
|
24
|
-
lastResult = await executePipeline(
|
|
30
|
+
lastResult = await executePipeline(
|
|
31
|
+
stmt.pipeline,
|
|
32
|
+
authUser,
|
|
33
|
+
hostname,
|
|
34
|
+
mode,
|
|
35
|
+
cwd,
|
|
36
|
+
shell,
|
|
37
|
+
env,
|
|
38
|
+
);
|
|
25
39
|
env.lastExitCode = lastResult.exitCode ?? 0;
|
|
26
40
|
|
|
27
41
|
// Propagate session-control signals
|
|
28
|
-
if (
|
|
42
|
+
if (
|
|
43
|
+
lastResult.closeSession ||
|
|
44
|
+
lastResult.switchUser ||
|
|
45
|
+
lastResult.nextCwd
|
|
46
|
+
) {
|
|
29
47
|
break;
|
|
30
48
|
}
|
|
31
49
|
}
|
|
@@ -48,7 +66,15 @@ export async function executeStatements(
|
|
|
48
66
|
|
|
49
67
|
while (i < statements.length) {
|
|
50
68
|
const stmt = statements[i]!;
|
|
51
|
-
last = await executePipeline(
|
|
69
|
+
last = await executePipeline(
|
|
70
|
+
stmt.pipeline,
|
|
71
|
+
authUser,
|
|
72
|
+
hostname,
|
|
73
|
+
mode,
|
|
74
|
+
cwd,
|
|
75
|
+
shell,
|
|
76
|
+
env,
|
|
77
|
+
);
|
|
52
78
|
env.lastExitCode = last.exitCode ?? 0;
|
|
53
79
|
|
|
54
80
|
if (last.closeSession || last.switchUser) return last;
|
|
@@ -83,7 +109,8 @@ export async function executePipeline(
|
|
|
83
109
|
shell: VirtualShell,
|
|
84
110
|
env?: ShellEnv,
|
|
85
111
|
): Promise<CommandResult> {
|
|
86
|
-
if (!pipeline.isValid)
|
|
112
|
+
if (!pipeline.isValid)
|
|
113
|
+
return { stderr: pipeline.error || "Syntax error", exitCode: 1 };
|
|
87
114
|
if (pipeline.commands.length === 0) return { exitCode: 0 };
|
|
88
115
|
|
|
89
116
|
const shellEnv: ShellEnv = env ?? { vars: {}, lastExitCode: 0 };
|
|
@@ -91,13 +118,23 @@ export async function executePipeline(
|
|
|
91
118
|
if (pipeline.commands.length === 1) {
|
|
92
119
|
return executeSingleCommandWithRedirections(
|
|
93
120
|
pipeline.commands[0] as PipelineCommand,
|
|
94
|
-
authUser,
|
|
121
|
+
authUser,
|
|
122
|
+
hostname,
|
|
123
|
+
mode,
|
|
124
|
+
cwd,
|
|
125
|
+
shell,
|
|
126
|
+
shellEnv,
|
|
95
127
|
);
|
|
96
128
|
}
|
|
97
129
|
|
|
98
130
|
return executePipelineChain(
|
|
99
131
|
pipeline.commands as PipelineCommand[],
|
|
100
|
-
authUser,
|
|
132
|
+
authUser,
|
|
133
|
+
hostname,
|
|
134
|
+
mode,
|
|
135
|
+
cwd,
|
|
136
|
+
shell,
|
|
137
|
+
shellEnv,
|
|
101
138
|
);
|
|
102
139
|
}
|
|
103
140
|
|
|
@@ -113,26 +150,51 @@ async function executeSingleCommandWithRedirections(
|
|
|
113
150
|
let stdin: string | undefined;
|
|
114
151
|
if (cmd.inputFile) {
|
|
115
152
|
const inputPath = resolvePath(cwd, cmd.inputFile);
|
|
116
|
-
try {
|
|
117
|
-
|
|
153
|
+
try {
|
|
154
|
+
stdin = shell.vfs.readFile(inputPath);
|
|
155
|
+
} catch {
|
|
156
|
+
return {
|
|
157
|
+
stderr: `${cmd.inputFile}: No such file or directory`,
|
|
158
|
+
exitCode: 1,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
118
161
|
}
|
|
119
162
|
|
|
120
|
-
const
|
|
121
|
-
|
|
163
|
+
const result = await runCommandDirect(
|
|
164
|
+
cmd.name,
|
|
165
|
+
cmd.args,
|
|
166
|
+
authUser,
|
|
167
|
+
hostname,
|
|
168
|
+
mode,
|
|
169
|
+
cwd,
|
|
170
|
+
shell,
|
|
171
|
+
stdin,
|
|
172
|
+
env,
|
|
173
|
+
);
|
|
122
174
|
|
|
123
175
|
if (cmd.outputFile) {
|
|
124
176
|
const outputPath = resolvePath(cwd, cmd.outputFile);
|
|
125
177
|
const output = result.stdout || "";
|
|
126
178
|
try {
|
|
127
179
|
if (cmd.appendOutput) {
|
|
128
|
-
const existing = (() => {
|
|
180
|
+
const existing = (() => {
|
|
181
|
+
try {
|
|
182
|
+
return shell.vfs.readFile(outputPath);
|
|
183
|
+
} catch {
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
129
187
|
shell.writeFileAsUser(authUser, outputPath, existing + output);
|
|
130
188
|
} else {
|
|
131
189
|
shell.writeFileAsUser(authUser, outputPath, output);
|
|
132
190
|
}
|
|
133
191
|
return { ...result, stdout: "" };
|
|
134
192
|
} catch {
|
|
135
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
...result,
|
|
195
|
+
stderr: `Failed to write to ${cmd.outputFile}`,
|
|
196
|
+
exitCode: 1,
|
|
197
|
+
};
|
|
136
198
|
}
|
|
137
199
|
}
|
|
138
200
|
|
|
@@ -156,12 +218,27 @@ async function executePipelineChain(
|
|
|
156
218
|
|
|
157
219
|
if (i === 0 && cmd.inputFile) {
|
|
158
220
|
const inputPath = resolvePath(cwd, cmd.inputFile);
|
|
159
|
-
try {
|
|
160
|
-
|
|
221
|
+
try {
|
|
222
|
+
currentOutput = shell.vfs.readFile(inputPath);
|
|
223
|
+
} catch {
|
|
224
|
+
return {
|
|
225
|
+
stderr: `${cmd.inputFile}: No such file or directory`,
|
|
226
|
+
exitCode: 1,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
161
229
|
}
|
|
162
230
|
|
|
163
|
-
const
|
|
164
|
-
|
|
231
|
+
const result = await runCommandDirect(
|
|
232
|
+
cmd.name,
|
|
233
|
+
cmd.args,
|
|
234
|
+
authUser,
|
|
235
|
+
hostname,
|
|
236
|
+
mode,
|
|
237
|
+
cwd,
|
|
238
|
+
shell,
|
|
239
|
+
currentOutput,
|
|
240
|
+
env,
|
|
241
|
+
);
|
|
165
242
|
exitCode = result.exitCode ?? 0;
|
|
166
243
|
|
|
167
244
|
if (i === commands.length - 1 && cmd.outputFile) {
|
|
@@ -169,7 +246,13 @@ async function executePipelineChain(
|
|
|
169
246
|
const output = result.stdout || "";
|
|
170
247
|
try {
|
|
171
248
|
if (cmd.appendOutput) {
|
|
172
|
-
const existing = (() => {
|
|
249
|
+
const existing = (() => {
|
|
250
|
+
try {
|
|
251
|
+
return shell.vfs.readFile(outputPath);
|
|
252
|
+
} catch {
|
|
253
|
+
return "";
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
173
256
|
shell.writeFileAsUser(authUser, outputPath, existing + output);
|
|
174
257
|
} else {
|
|
175
258
|
shell.writeFileAsUser(authUser, outputPath, output);
|
|
@@ -182,7 +265,8 @@ async function executePipelineChain(
|
|
|
182
265
|
currentOutput = result.stdout || "";
|
|
183
266
|
}
|
|
184
267
|
|
|
185
|
-
if (result.stderr && exitCode !== 0)
|
|
268
|
+
if (result.stderr && exitCode !== 0)
|
|
269
|
+
return { stderr: result.stderr, exitCode };
|
|
186
270
|
if (result.closeSession || result.switchUser) return result;
|
|
187
271
|
}
|
|
188
272
|
|
package/src/SSHMimic/index.ts
CHANGED
|
@@ -140,7 +140,11 @@ class SshMimic extends EventEmitter {
|
|
|
140
140
|
|
|
141
141
|
// Rate-limit check
|
|
142
142
|
if (this.isLockedOut(remoteAddress)) {
|
|
143
|
-
this.emit("auth:failure", {
|
|
143
|
+
this.emit("auth:failure", {
|
|
144
|
+
username: candidateUser,
|
|
145
|
+
remoteAddress,
|
|
146
|
+
reason: "lockout",
|
|
147
|
+
});
|
|
144
148
|
ctx.reject();
|
|
145
149
|
return;
|
|
146
150
|
}
|
|
@@ -152,7 +156,10 @@ class SshMimic extends EventEmitter {
|
|
|
152
156
|
`User ${candidateUser} has no password set, allowing login without verification`,
|
|
153
157
|
);
|
|
154
158
|
authUser = candidateUser;
|
|
155
|
-
sessionId = shell.users.registerSession(
|
|
159
|
+
sessionId = shell.users.registerSession(
|
|
160
|
+
authUser,
|
|
161
|
+
remoteAddress,
|
|
162
|
+
).id;
|
|
156
163
|
this.recordSuccess(remoteAddress);
|
|
157
164
|
this.emit("auth:success", { username: authUser, remoteAddress });
|
|
158
165
|
this.ensureHomeDir(authUser);
|
|
@@ -166,7 +173,10 @@ class SshMimic extends EventEmitter {
|
|
|
166
173
|
!shell.users.verifyPassword(candidateUser, ctx.password)
|
|
167
174
|
) {
|
|
168
175
|
this.recordFailure(remoteAddress);
|
|
169
|
-
this.emit("auth:failure", {
|
|
176
|
+
this.emit("auth:failure", {
|
|
177
|
+
username: candidateUser,
|
|
178
|
+
remoteAddress,
|
|
179
|
+
});
|
|
170
180
|
ctx.reject();
|
|
171
181
|
return;
|
|
172
182
|
}
|
|
@@ -192,13 +202,16 @@ class SshMimic extends EventEmitter {
|
|
|
192
202
|
const incomingKey = ctx.key;
|
|
193
203
|
const keyMatches = authorizedKeys.some(
|
|
194
204
|
(k) =>
|
|
195
|
-
k.algo === incomingKey.algo &&
|
|
196
|
-
k.data.equals(incomingKey.data),
|
|
205
|
+
k.algo === incomingKey.algo && k.data.equals(incomingKey.data),
|
|
197
206
|
);
|
|
198
207
|
|
|
199
208
|
if (!keyMatches) {
|
|
200
209
|
this.recordFailure(remoteAddress);
|
|
201
|
-
this.emit("auth:failure", {
|
|
210
|
+
this.emit("auth:failure", {
|
|
211
|
+
username: candidateUser,
|
|
212
|
+
remoteAddress,
|
|
213
|
+
method: "publickey",
|
|
214
|
+
});
|
|
202
215
|
ctx.reject();
|
|
203
216
|
return;
|
|
204
217
|
}
|
|
@@ -206,9 +219,16 @@ class SshMimic extends EventEmitter {
|
|
|
206
219
|
// Key matched — if this is a signature check step, accept
|
|
207
220
|
if (ctx.signature) {
|
|
208
221
|
authUser = candidateUser;
|
|
209
|
-
sessionId = shell.users.registerSession(
|
|
222
|
+
sessionId = shell.users.registerSession(
|
|
223
|
+
authUser,
|
|
224
|
+
remoteAddress,
|
|
225
|
+
).id;
|
|
210
226
|
this.recordSuccess(remoteAddress);
|
|
211
|
-
this.emit("auth:success", {
|
|
227
|
+
this.emit("auth:success", {
|
|
228
|
+
username: authUser,
|
|
229
|
+
remoteAddress,
|
|
230
|
+
method: "publickey",
|
|
231
|
+
});
|
|
212
232
|
this.ensureHomeDir(authUser);
|
|
213
233
|
ctx.accept();
|
|
214
234
|
} else {
|
|
@@ -238,20 +258,35 @@ class SshMimic extends EventEmitter {
|
|
|
238
258
|
acceptPty();
|
|
239
259
|
});
|
|
240
260
|
|
|
241
|
-
session.on(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
261
|
+
session.on(
|
|
262
|
+
"window-change",
|
|
263
|
+
(_acceptChange, _rejectChange, info) => {
|
|
264
|
+
terminalSize.cols = info?.cols ?? terminalSize.cols;
|
|
265
|
+
terminalSize.rows = info?.rows ?? terminalSize.rows;
|
|
266
|
+
},
|
|
267
|
+
);
|
|
245
268
|
|
|
246
269
|
session.on("shell", (acceptShell) => {
|
|
247
270
|
const stream = acceptShell();
|
|
248
|
-
shell?.startInteractiveSession(
|
|
271
|
+
shell?.startInteractiveSession(
|
|
272
|
+
stream,
|
|
273
|
+
authUser,
|
|
274
|
+
sessionId,
|
|
275
|
+
remoteAddress,
|
|
276
|
+
terminalSize,
|
|
277
|
+
);
|
|
249
278
|
});
|
|
250
279
|
|
|
251
280
|
session.on("exec", (acceptExec, _rejectExec, info) => {
|
|
252
281
|
const stream = acceptExec();
|
|
253
282
|
if (stream) {
|
|
254
|
-
runExec(
|
|
283
|
+
runExec(
|
|
284
|
+
stream,
|
|
285
|
+
info.command.trim(),
|
|
286
|
+
authUser,
|
|
287
|
+
shell.hostname,
|
|
288
|
+
shell,
|
|
289
|
+
);
|
|
255
290
|
}
|
|
256
291
|
});
|
|
257
292
|
});
|
|
@@ -293,4 +328,3 @@ class SshMimic extends EventEmitter {
|
|
|
293
328
|
|
|
294
329
|
export { SftpMimic } from "./sftp";
|
|
295
330
|
export { SshMimic };
|
|
296
|
-
|
|
@@ -31,19 +31,25 @@
|
|
|
31
31
|
* Binary pack : ~1.00 MB + ~40 bytes/node header → ~27% smaller, no string parsing
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
InternalDirectoryNode,
|
|
36
|
+
InternalFileNode,
|
|
37
|
+
InternalNode,
|
|
38
|
+
} from "./internalTypes";
|
|
35
39
|
|
|
36
40
|
const MAGIC = Buffer.from([0x56, 0x46, 0x53, 0x21]); // "VFS!"
|
|
37
41
|
const VERSION = 0x01;
|
|
38
42
|
const TYPE_FILE = 0x01;
|
|
39
|
-
const TYPE_DIR
|
|
43
|
+
const TYPE_DIR = 0x02;
|
|
40
44
|
|
|
41
45
|
// ── Encoder ───────────────────────────────────────────────────────────────────
|
|
42
46
|
|
|
43
47
|
class Encoder {
|
|
44
48
|
private chunks: Buffer[] = [];
|
|
45
49
|
|
|
46
|
-
write(buf: Buffer): void {
|
|
50
|
+
write(buf: Buffer): void {
|
|
51
|
+
this.chunks.push(buf);
|
|
52
|
+
}
|
|
47
53
|
|
|
48
54
|
writeUint8(n: number): void {
|
|
49
55
|
const b = Buffer.allocUnsafe(1);
|
|
@@ -80,7 +86,9 @@ class Encoder {
|
|
|
80
86
|
this.chunks.push(bytes);
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
toBuffer(): Buffer {
|
|
89
|
+
toBuffer(): Buffer {
|
|
90
|
+
return Buffer.concat(this.chunks);
|
|
91
|
+
}
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
function encodeNode(enc: Encoder, node: InternalNode): void {
|
|
@@ -124,7 +132,9 @@ class Decoder {
|
|
|
124
132
|
private pos = 0;
|
|
125
133
|
constructor(private readonly buf: Buffer) {}
|
|
126
134
|
|
|
127
|
-
readUint8(): number {
|
|
135
|
+
readUint8(): number {
|
|
136
|
+
return this.buf.readUInt8(this.pos++);
|
|
137
|
+
}
|
|
128
138
|
|
|
129
139
|
readUint16(): number {
|
|
130
140
|
const v = this.buf.readUInt16LE(this.pos);
|
|
@@ -158,7 +168,9 @@ class Decoder {
|
|
|
158
168
|
return b;
|
|
159
169
|
}
|
|
160
170
|
|
|
161
|
-
remaining(): number {
|
|
171
|
+
remaining(): number {
|
|
172
|
+
return this.buf.length - this.pos;
|
|
173
|
+
}
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
function decodeNode(dec: Decoder): InternalNode {
|
|
@@ -171,7 +183,15 @@ function decodeNode(dec: Decoder): InternalNode {
|
|
|
171
183
|
if (type === TYPE_FILE) {
|
|
172
184
|
const compressed = dec.readUint8() === 0x01;
|
|
173
185
|
const content = dec.readBytes();
|
|
174
|
-
return {
|
|
186
|
+
return {
|
|
187
|
+
type: "file",
|
|
188
|
+
name,
|
|
189
|
+
mode,
|
|
190
|
+
createdAt,
|
|
191
|
+
updatedAt,
|
|
192
|
+
compressed,
|
|
193
|
+
content,
|
|
194
|
+
} satisfies InternalFileNode;
|
|
175
195
|
}
|
|
176
196
|
|
|
177
197
|
if (type === TYPE_DIR) {
|
|
@@ -181,7 +201,14 @@ function decodeNode(dec: Decoder): InternalNode {
|
|
|
181
201
|
const child = decodeNode(dec);
|
|
182
202
|
children.set(child.name, child);
|
|
183
203
|
}
|
|
184
|
-
return {
|
|
204
|
+
return {
|
|
205
|
+
type: "directory",
|
|
206
|
+
name,
|
|
207
|
+
mode,
|
|
208
|
+
createdAt,
|
|
209
|
+
updatedAt,
|
|
210
|
+
children,
|
|
211
|
+
} satisfies InternalDirectoryNode;
|
|
185
212
|
}
|
|
186
213
|
|
|
187
214
|
throw new Error(`[VFS binary] Unknown node type: 0x${type.toString(16)}`);
|
|
@@ -172,7 +172,9 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
172
172
|
// Legacy JSON fallback — auto-migrates on next flushMirror()
|
|
173
173
|
const snapshot: VfsSnapshot = JSON.parse(raw.toString("utf8"));
|
|
174
174
|
this.root = this.deserializeDir(snapshot.root, "");
|
|
175
|
-
console.info(
|
|
175
|
+
console.info(
|
|
176
|
+
"[VirtualFileSystem] Migrating legacy JSON snapshot to binary format.",
|
|
177
|
+
);
|
|
176
178
|
}
|
|
177
179
|
this.emit("snapshot:restore", { path: this.snapshotFile });
|
|
178
180
|
} catch (err) {
|
|
@@ -220,7 +222,11 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
220
222
|
public mkdir(targetPath: string, mode: number = 0o755): void {
|
|
221
223
|
const normalized = normalizePath(targetPath);
|
|
222
224
|
const existing = (() => {
|
|
223
|
-
try {
|
|
225
|
+
try {
|
|
226
|
+
return getNode(this.root, normalized);
|
|
227
|
+
} catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
224
230
|
})();
|
|
225
231
|
if (existing && existing.type !== "directory") {
|
|
226
232
|
throw new Error(
|
|
@@ -311,7 +317,9 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
311
317
|
try {
|
|
312
318
|
getNode(this.root, normalizePath(targetPath));
|
|
313
319
|
return true;
|
|
314
|
-
} catch {
|
|
320
|
+
} catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
315
323
|
}
|
|
316
324
|
|
|
317
325
|
/** Updates mode bits on a node. */
|
|
@@ -327,15 +335,24 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
327
335
|
if (node.type === "file") {
|
|
328
336
|
const f = node as InternalFileNode;
|
|
329
337
|
return {
|
|
330
|
-
type: "file",
|
|
331
|
-
|
|
332
|
-
|
|
338
|
+
type: "file",
|
|
339
|
+
name,
|
|
340
|
+
path: normalized,
|
|
341
|
+
mode: f.mode,
|
|
342
|
+
createdAt: f.createdAt,
|
|
343
|
+
updatedAt: f.updatedAt,
|
|
344
|
+
compressed: f.compressed,
|
|
345
|
+
size: f.content.length,
|
|
333
346
|
};
|
|
334
347
|
}
|
|
335
348
|
const d = node as InternalDirectoryNode;
|
|
336
349
|
return {
|
|
337
|
-
type: "directory",
|
|
338
|
-
|
|
350
|
+
type: "directory",
|
|
351
|
+
name,
|
|
352
|
+
path: normalized,
|
|
353
|
+
mode: d.mode,
|
|
354
|
+
createdAt: d.createdAt,
|
|
355
|
+
updatedAt: d.updatedAt,
|
|
339
356
|
childrenCount: d.children.size,
|
|
340
357
|
};
|
|
341
358
|
}
|
|
@@ -355,9 +372,7 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
355
372
|
const normalized = normalizePath(dirPath);
|
|
356
373
|
const node = getNode(this.root, normalized);
|
|
357
374
|
if (node.type !== "directory") {
|
|
358
|
-
throw new Error(
|
|
359
|
-
`Cannot render tree for '${dirPath}': not a directory.`,
|
|
360
|
-
);
|
|
375
|
+
throw new Error(`Cannot render tree for '${dirPath}': not a directory.`);
|
|
361
376
|
}
|
|
362
377
|
const label = dirPath === "/" ? "/" : path.posix.basename(normalized);
|
|
363
378
|
return this.renderTreeLines(node as InternalDirectoryNode, label);
|
|
@@ -375,7 +390,8 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
375
390
|
lines.push(`${connector}${name}`);
|
|
376
391
|
if (child.type === "directory") {
|
|
377
392
|
const sub = this.renderTreeLines(child as InternalDirectoryNode, "")
|
|
378
|
-
.split("\n")
|
|
393
|
+
.split("\n")
|
|
394
|
+
.slice(1)
|
|
379
395
|
.map((l) => `${nextPrefix}${l}`);
|
|
380
396
|
lines.push(...sub);
|
|
381
397
|
}
|
|
@@ -400,7 +416,8 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
400
416
|
/** Compresses a file's content with gzip in place. */
|
|
401
417
|
public compressFile(targetPath: string): void {
|
|
402
418
|
const node = getNode(this.root, normalizePath(targetPath));
|
|
403
|
-
if (node.type !== "file")
|
|
419
|
+
if (node.type !== "file")
|
|
420
|
+
throw new Error(`Cannot compress '${targetPath}': not a file.`);
|
|
404
421
|
const f = node as InternalFileNode;
|
|
405
422
|
if (!f.compressed) {
|
|
406
423
|
f.content = gzipSync(f.content);
|
|
@@ -412,7 +429,8 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
412
429
|
/** Decompresses a gzip-compressed file in place. */
|
|
413
430
|
public decompressFile(targetPath: string): void {
|
|
414
431
|
const node = getNode(this.root, normalizePath(targetPath));
|
|
415
|
-
if (node.type !== "file")
|
|
432
|
+
if (node.type !== "file")
|
|
433
|
+
throw new Error(`Cannot decompress '${targetPath}': not a file.`);
|
|
416
434
|
const f = node as InternalFileNode;
|
|
417
435
|
if (f.compressed) {
|
|
418
436
|
f.content = gunzipSync(f.content);
|
|
@@ -431,18 +449,25 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
431
449
|
? normalizePath(targetPath)
|
|
432
450
|
: targetPath;
|
|
433
451
|
const { parent, name } = getParentDirectory(
|
|
434
|
-
this.root,
|
|
452
|
+
this.root,
|
|
453
|
+
normalizedLink,
|
|
454
|
+
true,
|
|
435
455
|
(p) => this.mkdirRecursive(p, 0o755),
|
|
436
456
|
);
|
|
437
457
|
const symNode: InternalFileNode = {
|
|
438
|
-
type: "file",
|
|
458
|
+
type: "file",
|
|
459
|
+
name,
|
|
439
460
|
content: Buffer.from(normalizedTarget, "utf8"),
|
|
440
461
|
mode: 0o120777,
|
|
441
462
|
compressed: false,
|
|
442
|
-
createdAt: new Date(),
|
|
463
|
+
createdAt: new Date(),
|
|
464
|
+
updatedAt: new Date(),
|
|
443
465
|
};
|
|
444
466
|
parent.children.set(name, symNode);
|
|
445
|
-
this.emit("symlink:create", {
|
|
467
|
+
this.emit("symlink:create", {
|
|
468
|
+
link: normalizedLink,
|
|
469
|
+
target: normalizedTarget,
|
|
470
|
+
});
|
|
446
471
|
}
|
|
447
472
|
|
|
448
473
|
/** Returns true when the path is a symbolic link node. */
|
|
@@ -450,7 +475,9 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
450
475
|
try {
|
|
451
476
|
const node = getNode(this.root, normalizePath(targetPath));
|
|
452
477
|
return node.type === "file" && node.mode === 0o120777;
|
|
453
|
-
} catch {
|
|
478
|
+
} catch {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
454
481
|
}
|
|
455
482
|
|
|
456
483
|
/**
|
|
@@ -466,10 +493,14 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
466
493
|
const target = (node as InternalFileNode).content.toString("utf8");
|
|
467
494
|
current = target.startsWith("/")
|
|
468
495
|
? target
|
|
469
|
-
: normalizePath(
|
|
496
|
+
: normalizePath(
|
|
497
|
+
path.posix.join(path.posix.dirname(current), target),
|
|
498
|
+
);
|
|
470
499
|
continue;
|
|
471
500
|
}
|
|
472
|
-
} catch {
|
|
501
|
+
} catch {
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
473
504
|
return current;
|
|
474
505
|
}
|
|
475
506
|
throw new Error(`Too many levels of symbolic links: ${linkPath}`);
|
|
@@ -488,7 +519,12 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
488
519
|
);
|
|
489
520
|
}
|
|
490
521
|
}
|
|
491
|
-
const { parent, name } = getParentDirectory(
|
|
522
|
+
const { parent, name } = getParentDirectory(
|
|
523
|
+
this.root,
|
|
524
|
+
normalized,
|
|
525
|
+
false,
|
|
526
|
+
() => {},
|
|
527
|
+
);
|
|
492
528
|
parent.children.delete(name);
|
|
493
529
|
this.emit("node:remove", { path: normalized });
|
|
494
530
|
}
|
|
@@ -506,10 +542,16 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
506
542
|
}
|
|
507
543
|
this.mkdirRecursive(path.posix.dirname(toNormalized), 0o755);
|
|
508
544
|
const { parent: destParent, name: destName } = getParentDirectory(
|
|
509
|
-
this.root,
|
|
545
|
+
this.root,
|
|
546
|
+
toNormalized,
|
|
547
|
+
false,
|
|
548
|
+
() => {},
|
|
510
549
|
);
|
|
511
550
|
const { parent: srcParent, name: srcName } = getParentDirectory(
|
|
512
|
-
this.root,
|
|
551
|
+
this.root,
|
|
552
|
+
fromNormalized,
|
|
553
|
+
false,
|
|
554
|
+
() => {},
|
|
513
555
|
);
|
|
514
556
|
srcParent.children.delete(srcName);
|
|
515
557
|
node.name = destName;
|
|
@@ -538,7 +580,9 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
538
580
|
);
|
|
539
581
|
}
|
|
540
582
|
return {
|
|
541
|
-
type: "directory",
|
|
583
|
+
type: "directory",
|
|
584
|
+
name: dir.name,
|
|
585
|
+
mode: dir.mode,
|
|
542
586
|
createdAt: dir.createdAt.toISOString(),
|
|
543
587
|
updatedAt: dir.updatedAt.toISOString(),
|
|
544
588
|
children,
|
|
@@ -547,7 +591,9 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
547
591
|
|
|
548
592
|
private serializeFile(file: InternalFileNode): VfsSnapshotFileNode {
|
|
549
593
|
return {
|
|
550
|
-
type: "file",
|
|
594
|
+
type: "file",
|
|
595
|
+
name: file.name,
|
|
596
|
+
mode: file.mode,
|
|
551
597
|
createdAt: file.createdAt.toISOString(),
|
|
552
598
|
updatedAt: file.updatedAt.toISOString(),
|
|
553
599
|
compressed: file.compressed,
|
|
@@ -588,7 +634,9 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
588
634
|
name: string,
|
|
589
635
|
): InternalDirectoryNode {
|
|
590
636
|
const dir: InternalDirectoryNode = {
|
|
591
|
-
type: "directory",
|
|
637
|
+
type: "directory",
|
|
638
|
+
name,
|
|
639
|
+
mode: snap.mode,
|
|
592
640
|
createdAt: new Date(snap.createdAt),
|
|
593
641
|
updatedAt: new Date(snap.updatedAt),
|
|
594
642
|
children: new Map(),
|
|
@@ -597,7 +645,9 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
597
645
|
if (child.type === "file") {
|
|
598
646
|
const f = child as VfsSnapshotFileNode;
|
|
599
647
|
dir.children.set(f.name, {
|
|
600
|
-
type: "file",
|
|
648
|
+
type: "file",
|
|
649
|
+
name: f.name,
|
|
650
|
+
mode: f.mode,
|
|
601
651
|
createdAt: new Date(f.createdAt),
|
|
602
652
|
updatedAt: new Date(f.updatedAt),
|
|
603
653
|
compressed: f.compressed,
|