typescript-virtual-container 1.0.7 → 1.1.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/CHANGELOG.md +4 -0
- package/README.md +138 -87
- package/package.json +1 -1
- package/src/SSHMimic/client.ts +15 -18
- package/src/SSHMimic/exec.ts +5 -16
- package/src/SSHMimic/executor.ts +18 -29
- package/src/SSHMimic/index.ts +23 -85
- package/src/VirtualFileSystem/index.ts +3 -1
- package/src/VirtualShell/commands/adduser.ts +2 -2
- package/src/VirtualShell/commands/cat.ts +3 -3
- package/src/VirtualShell/commands/cd.ts +2 -2
- package/src/VirtualShell/commands/command-helpers.ts +64 -0
- package/src/VirtualShell/commands/curl.ts +14 -92
- package/src/VirtualShell/commands/deluser.ts +2 -2
- package/src/VirtualShell/commands/echo.ts +5 -12
- package/src/VirtualShell/commands/grep.ts +8 -16
- package/src/VirtualShell/commands/helpers.ts +74 -0
- package/src/VirtualShell/commands/index.ts +46 -112
- package/src/VirtualShell/commands/ls.ts +6 -4
- package/src/VirtualShell/commands/mkdir.ts +2 -2
- package/src/VirtualShell/commands/nano.ts +3 -3
- package/src/VirtualShell/commands/neofetch.ts +2 -2
- package/src/VirtualShell/commands/rm.ts +2 -2
- package/src/VirtualShell/commands/sh.ts +2 -13
- package/src/VirtualShell/commands/su.ts +2 -1
- package/src/VirtualShell/commands/sudo.ts +12 -25
- package/src/VirtualShell/commands/touch.ts +3 -3
- package/src/VirtualShell/commands/tree.ts +2 -2
- package/src/VirtualShell/commands/wget.ts +19 -29
- package/src/VirtualShell/commands/who.ts +2 -2
- package/src/VirtualShell/index.ts +114 -25
- package/src/VirtualShell/shell.ts +28 -35
- package/src/{SSHMimic/users.ts → VirtualUserManager/index.ts} +6 -3
- package/src/index.ts +4 -4
- package/src/standalone.ts +19 -14
- package/src/types/commands.ts +3 -11
- package/tests/parser-executor.test.ts +37 -0
- package/tests/users.test.ts +1 -1
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
import type { ShellModule } from "../../types/commands";
|
|
2
|
-
import {
|
|
2
|
+
import { parseArgs } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
5
|
export const grepCommand: ShellModule = {
|
|
6
6
|
name: "grep",
|
|
7
7
|
params: ["[-i] [-v] <pattern> [file...]"],
|
|
8
|
-
run: ({ authUser,
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const pattern =
|
|
13
|
-
const files
|
|
14
|
-
for (let index = 1; ; index += 1) {
|
|
15
|
-
const file = getArg(args, index, parserOptions);
|
|
16
|
-
if (!file) {
|
|
17
|
-
break;
|
|
18
|
-
}
|
|
19
|
-
files.push(file);
|
|
20
|
-
}
|
|
8
|
+
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
9
|
+
const { flags, positionals } = parseArgs(args, { flags: ["-i", "-v"] });
|
|
10
|
+
const caseInsensitive = flags.has("-i");
|
|
11
|
+
const invertMatch = flags.has("-v");
|
|
12
|
+
const pattern = positionals[0];
|
|
13
|
+
const files = positionals.slice(1);
|
|
21
14
|
|
|
22
15
|
if (!pattern) {
|
|
23
16
|
return { stderr: "grep: no pattern specified", exitCode: 1 };
|
|
@@ -33,7 +26,6 @@ export const grepCommand: ShellModule = {
|
|
|
33
26
|
|
|
34
27
|
const results: string[] = [];
|
|
35
28
|
|
|
36
|
-
// If no files specified, read from stdin (pipe/input redirection).
|
|
37
29
|
if (files.length === 0) {
|
|
38
30
|
if (!stdin) {
|
|
39
31
|
return { stdout: "", exitCode: 1 };
|
|
@@ -60,7 +52,7 @@ export const grepCommand: ShellModule = {
|
|
|
60
52
|
const target = resolvePath(cwd, file);
|
|
61
53
|
try {
|
|
62
54
|
assertPathAccess(authUser, target, "grep");
|
|
63
|
-
const content = vfs.readFile(target);
|
|
55
|
+
const content = shell.vfs.readFile(target);
|
|
64
56
|
const lines = content.split("\n");
|
|
65
57
|
|
|
66
58
|
for (const line of lines) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import type VirtualFileSystem from "../../VirtualFileSystem";
|
|
3
4
|
|
|
@@ -76,6 +77,79 @@ export async function fetchResource(
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Run a host command like curl or wget and capture its output.
|
|
82
|
+
* @param binary - The binary to execute (e.g., "curl", "wget").
|
|
83
|
+
* @param args - Arguments to pass to the binary.
|
|
84
|
+
* @returns Promise resolving with stdout, stderr, and exit code.
|
|
85
|
+
*/
|
|
86
|
+
export function runHostCommand(
|
|
87
|
+
binary: string,
|
|
88
|
+
args: string[],
|
|
89
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
let childProcess: ReturnType<typeof spawn>;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
childProcess = spawn(binary, args, {
|
|
95
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
resolve({
|
|
99
|
+
stdout: "",
|
|
100
|
+
stderr: `${binary}: ${error instanceof Error ? error.message : String(error)}`,
|
|
101
|
+
exitCode: 1,
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let stdout = "";
|
|
107
|
+
let stderr = "";
|
|
108
|
+
const stdoutStream = childProcess.stdout;
|
|
109
|
+
const stderrStream = childProcess.stderr;
|
|
110
|
+
|
|
111
|
+
if (!stdoutStream || !stderrStream) {
|
|
112
|
+
resolve({
|
|
113
|
+
stdout: "",
|
|
114
|
+
stderr: `${binary}: failed to capture process output`,
|
|
115
|
+
exitCode: 1,
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
stdoutStream.setEncoding("utf8");
|
|
121
|
+
stderrStream.setEncoding("utf8");
|
|
122
|
+
|
|
123
|
+
stdoutStream.on("data", (chunk: string) => {
|
|
124
|
+
stdout += chunk;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
stderrStream.on("data", (chunk: string) => {
|
|
128
|
+
stderr += chunk;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
childProcess.on("error", (error) => {
|
|
132
|
+
const errorCode =
|
|
133
|
+
error instanceof Error && "code" in error
|
|
134
|
+
? String((error as NodeJS.ErrnoException).code ?? "")
|
|
135
|
+
: "";
|
|
136
|
+
resolve({
|
|
137
|
+
stdout: "",
|
|
138
|
+
stderr: `${binary}: ${error.message}`,
|
|
139
|
+
exitCode: errorCode === "ENOENT" ? 127 : 1,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
childProcess.on("close", (code) => {
|
|
144
|
+
resolve({
|
|
145
|
+
stdout,
|
|
146
|
+
stderr,
|
|
147
|
+
exitCode: code ?? 1,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
79
153
|
function levenshtein(a: string, b: string): number {
|
|
80
154
|
const dp: number[][] = Array.from({ length: a.length + 1 }, () =>
|
|
81
155
|
Array<number>(b.length + 1).fill(0),
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { VirtualUserManager } from "../../SSHMimic/users";
|
|
1
|
+
import type { VirtualShell } from "..";
|
|
3
2
|
import type {
|
|
4
3
|
CommandContext,
|
|
5
4
|
CommandMode,
|
|
6
|
-
CommandOutcome,
|
|
7
5
|
CommandResult,
|
|
8
6
|
ShellModule,
|
|
9
7
|
} from "../../types/commands";
|
|
10
|
-
import type VirtualFileSystem from "../../VirtualFileSystem";
|
|
11
8
|
import { adduserCommand } from "./adduser";
|
|
12
9
|
import { catCommand } from "./cat";
|
|
13
10
|
import { cdCommand } from "./cd";
|
|
@@ -77,19 +74,28 @@ const helpCommand = createHelpCommand(() =>
|
|
|
77
74
|
getCommandModules().map((cmd) => cmd.name),
|
|
78
75
|
);
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
77
|
+
const commandRegistry = new Map<string, ShellModule>();
|
|
78
|
+
let cachedCommandNames: string[] | null = null;
|
|
83
79
|
|
|
84
|
-
function
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
taken.add(mod.name);
|
|
80
|
+
function buildCache(): void {
|
|
81
|
+
for (const mod of getCommandModules()) {
|
|
82
|
+
commandRegistry.set(mod.name, mod);
|
|
88
83
|
for (const alias of mod.aliases ?? []) {
|
|
89
|
-
|
|
84
|
+
commandRegistry.set(alias, mod);
|
|
90
85
|
}
|
|
91
86
|
}
|
|
92
|
-
|
|
87
|
+
cachedCommandNames = Array.from(commandRegistry.keys()).sort();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getCommandModules(): ShellModule[] {
|
|
91
|
+
// console.log("Loading command modules...");
|
|
92
|
+
// console.log(
|
|
93
|
+
// `Base commands: ${BASE_COMMANDS.map((cmd) => cmd.name).join(", ")}`,
|
|
94
|
+
// );
|
|
95
|
+
// console.log(
|
|
96
|
+
// `Custom commands: ${customCommands.map((cmd) => cmd.name).join(", ")}`,
|
|
97
|
+
// );
|
|
98
|
+
return [...BASE_COMMANDS, ...customCommands, helpCommand];
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
export function registerCommand(module: ShellModule): void {
|
|
@@ -106,13 +112,14 @@ export function registerCommand(module: ShellModule): void {
|
|
|
106
112
|
);
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
for (const name of names) {
|
|
116
|
+
if (commandRegistry.has(name)) {
|
|
117
|
+
throw new Error(`Command '${name}' already exists`);
|
|
118
|
+
}
|
|
119
|
+
commandRegistry.set(name, normalized);
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
|
|
122
|
+
buildCache();
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
export function createCustomCommand(
|
|
@@ -128,17 +135,14 @@ export function createCustomCommand(
|
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
export function getCommandNames(): string[] {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
if (!cachedCommandNames) {
|
|
139
|
+
buildCache();
|
|
140
|
+
}
|
|
141
|
+
return cachedCommandNames!;
|
|
135
142
|
}
|
|
136
143
|
|
|
137
|
-
function resolveModule(name: string): ShellModule | undefined {
|
|
138
|
-
|
|
139
|
-
return getCommandModules().find(
|
|
140
|
-
(cmd) => cmd.name === lowered || cmd.aliases?.includes(lowered),
|
|
141
|
-
);
|
|
144
|
+
export function resolveModule(name: string): ShellModule | undefined {
|
|
145
|
+
return commandRegistry.get(name.toLowerCase());
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
function splitArgsRespectingQuotes(input: string): string[] {
|
|
@@ -192,28 +196,27 @@ function parseInput(rawInput: string): { commandName: string; args: string[] } {
|
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
// Internal async function for pipeline execution
|
|
195
|
-
|
|
199
|
+
|
|
200
|
+
export async function runCommand(
|
|
196
201
|
rawInput: string,
|
|
197
202
|
authUser: string,
|
|
198
203
|
hostname: string,
|
|
199
|
-
users: VirtualUserManager,
|
|
200
204
|
mode: CommandMode,
|
|
201
205
|
cwd: string,
|
|
202
|
-
|
|
203
|
-
vfs: VirtualFileSystem,
|
|
206
|
+
shell: VirtualShell,
|
|
204
207
|
stdin?: string,
|
|
205
208
|
): Promise<CommandResult> {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
const trimmed = rawInput.trim();
|
|
210
|
+
|
|
211
|
+
if (trimmed.length === 0) {
|
|
212
|
+
return { exitCode: 0 };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (trimmed.includes("|") || trimmed.includes(">") || trimmed.includes("<")) {
|
|
213
216
|
const { parseShellPipeline } = await import("../shellParser");
|
|
214
217
|
const { executePipeline } = await import("../../SSHMimic/executor");
|
|
215
218
|
|
|
216
|
-
const pipeline = parseShellPipeline(
|
|
219
|
+
const pipeline = parseShellPipeline(trimmed);
|
|
217
220
|
if (!pipeline.isValid) {
|
|
218
221
|
return {
|
|
219
222
|
stderr: pipeline.error || "Syntax error",
|
|
@@ -226,10 +229,9 @@ async function runCommandInternal(
|
|
|
226
229
|
pipeline,
|
|
227
230
|
authUser,
|
|
228
231
|
hostname,
|
|
229
|
-
users,
|
|
230
232
|
mode,
|
|
231
233
|
cwd,
|
|
232
|
-
|
|
234
|
+
shell,
|
|
233
235
|
);
|
|
234
236
|
} catch (error: unknown) {
|
|
235
237
|
const message =
|
|
@@ -238,72 +240,6 @@ async function runCommandInternal(
|
|
|
238
240
|
}
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
// Regular command execution
|
|
242
|
-
const { commandName, args } = parseInput(rawInput);
|
|
243
|
-
const mod = resolveModule(commandName);
|
|
244
|
-
|
|
245
|
-
if (!mod) {
|
|
246
|
-
return {
|
|
247
|
-
stderr: `Command '${rawInput}' not found`,
|
|
248
|
-
exitCode: 127,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
const result = mod.run({
|
|
254
|
-
authUser,
|
|
255
|
-
hostname,
|
|
256
|
-
users,
|
|
257
|
-
activeSessions: users.listActiveSessions(),
|
|
258
|
-
rawInput,
|
|
259
|
-
mode,
|
|
260
|
-
args,
|
|
261
|
-
shellProps,
|
|
262
|
-
stdin,
|
|
263
|
-
cwd,
|
|
264
|
-
vfs,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
return await Promise.resolve(result);
|
|
268
|
-
} catch (error: unknown) {
|
|
269
|
-
const message = error instanceof Error ? error.message : "Command failed";
|
|
270
|
-
return { stderr: message, exitCode: 1 };
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export function runCommand(
|
|
275
|
-
rawInput: string,
|
|
276
|
-
authUser: string,
|
|
277
|
-
hostname: string,
|
|
278
|
-
users: VirtualUserManager,
|
|
279
|
-
mode: CommandMode,
|
|
280
|
-
cwd: string,
|
|
281
|
-
shellProps: ShellProperties,
|
|
282
|
-
vfs: VirtualFileSystem,
|
|
283
|
-
stdin?: string,
|
|
284
|
-
): CommandOutcome {
|
|
285
|
-
const trimmed = rawInput.trim();
|
|
286
|
-
|
|
287
|
-
if (trimmed.length === 0) {
|
|
288
|
-
return { exitCode: 0 };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Check if input contains pipes or redirections - use async version
|
|
292
|
-
if (trimmed.includes("|") || trimmed.includes(">") || trimmed.includes("<")) {
|
|
293
|
-
return runCommandInternal(
|
|
294
|
-
trimmed,
|
|
295
|
-
authUser,
|
|
296
|
-
hostname,
|
|
297
|
-
users,
|
|
298
|
-
mode,
|
|
299
|
-
cwd,
|
|
300
|
-
shellProps,
|
|
301
|
-
vfs,
|
|
302
|
-
stdin,
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Regular synchronous command execution
|
|
307
243
|
const { commandName, args } = parseInput(trimmed);
|
|
308
244
|
const mod = resolveModule(commandName);
|
|
309
245
|
|
|
@@ -315,18 +251,16 @@ export function runCommand(
|
|
|
315
251
|
}
|
|
316
252
|
|
|
317
253
|
try {
|
|
318
|
-
return mod.run({
|
|
254
|
+
return await mod.run({
|
|
319
255
|
authUser,
|
|
320
256
|
hostname,
|
|
321
|
-
users,
|
|
322
|
-
activeSessions: users.listActiveSessions(),
|
|
257
|
+
activeSessions: shell.users.listActiveSessions(),
|
|
323
258
|
rawInput: trimmed,
|
|
324
259
|
mode,
|
|
325
260
|
args,
|
|
326
261
|
stdin,
|
|
327
262
|
cwd,
|
|
328
|
-
|
|
329
|
-
shellProps,
|
|
263
|
+
shell,
|
|
330
264
|
});
|
|
331
265
|
} catch (error: unknown) {
|
|
332
266
|
const message = error instanceof Error ? error.message : "Command failed";
|
|
@@ -29,22 +29,24 @@ function formatDate(date: Date): string {
|
|
|
29
29
|
export const lsCommand: ShellModule = {
|
|
30
30
|
name: "ls",
|
|
31
31
|
params: ["[path]"],
|
|
32
|
-
run: ({ authUser,
|
|
32
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
33
33
|
const longFormat = ifFlag(args, ["-l", "--long"]);
|
|
34
34
|
const targetArg = getArg(args, 0, { flags: ["-l", "--long"] });
|
|
35
35
|
const target = resolvePath(cwd, targetArg ?? cwd);
|
|
36
36
|
assertPathAccess(authUser, target, "ls");
|
|
37
|
-
const items = vfs
|
|
37
|
+
const items = shell.vfs
|
|
38
|
+
.list(target)
|
|
39
|
+
.filter((name) => !name.startsWith("."));
|
|
38
40
|
const rendered = longFormat
|
|
39
41
|
? items
|
|
40
42
|
.map((name) => {
|
|
41
43
|
const childPath = resolvePath(target, name);
|
|
42
|
-
const stat = vfs.stat(childPath);
|
|
44
|
+
const stat = shell.vfs.stat(childPath);
|
|
43
45
|
const size = stat.type === "file" ? stat.size : stat.childrenCount;
|
|
44
46
|
return `${formatPermissions(stat.mode, stat.type === "directory")} 1 ${size} ${formatDate(stat.updatedAt)} ${name}${stat.type === "directory" ? "/" : ""}`;
|
|
45
47
|
})
|
|
46
48
|
.join("\n")
|
|
47
|
-
: joinListWithType(target, items, (p) => vfs.stat(p));
|
|
49
|
+
: joinListWithType(target, items, (p) => shell.vfs.stat(p));
|
|
48
50
|
return { stdout: rendered, exitCode: 0 };
|
|
49
51
|
},
|
|
50
52
|
};
|
|
@@ -5,7 +5,7 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
5
5
|
export const mkdirCommand: ShellModule = {
|
|
6
6
|
name: "mkdir",
|
|
7
7
|
params: ["<dir>"],
|
|
8
|
-
run: ({ authUser,
|
|
8
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
9
9
|
if (args.length === 0) {
|
|
10
10
|
return { stderr: "mkdir: missing operand", exitCode: 1 };
|
|
11
11
|
}
|
|
@@ -17,7 +17,7 @@ export const mkdirCommand: ShellModule = {
|
|
|
17
17
|
}
|
|
18
18
|
const target = resolvePath(cwd, dir);
|
|
19
19
|
assertPathAccess(authUser, target, "mkdir");
|
|
20
|
-
vfs.mkdir(target);
|
|
20
|
+
shell.vfs.mkdir(target);
|
|
21
21
|
}
|
|
22
22
|
return { exitCode: 0 };
|
|
23
23
|
},
|
|
@@ -5,7 +5,7 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
5
5
|
export const nanoCommand: ShellModule = {
|
|
6
6
|
name: "nano",
|
|
7
7
|
params: ["<file>"],
|
|
8
|
-
run: ({ authUser,
|
|
8
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
9
9
|
const fileArg = args[0];
|
|
10
10
|
if (!fileArg) {
|
|
11
11
|
return { stderr: "nano: missing file operand", exitCode: 1 };
|
|
@@ -13,8 +13,8 @@ export const nanoCommand: ShellModule = {
|
|
|
13
13
|
|
|
14
14
|
const targetPath = resolvePath(cwd, fileArg);
|
|
15
15
|
assertPathAccess(authUser, targetPath, "nano");
|
|
16
|
-
const initialContent = vfs.exists(targetPath)
|
|
17
|
-
? vfs.readFile(targetPath)
|
|
16
|
+
const initialContent = shell.vfs.exists(targetPath)
|
|
17
|
+
? shell.vfs.readFile(targetPath)
|
|
18
18
|
: "";
|
|
19
19
|
const safeName = path.posix.basename(targetPath) || "buffer";
|
|
20
20
|
const tempPath = `/tmp/sshmimic-nano-${Date.now()}-${safeName}.tmp`;
|
|
@@ -6,7 +6,7 @@ import { getAllEnvVars } from "./set";
|
|
|
6
6
|
export const neofetchCommand: ShellModule = {
|
|
7
7
|
name: "neofetch",
|
|
8
8
|
params: ["[--off]"],
|
|
9
|
-
run: ({ args, authUser, hostname,
|
|
9
|
+
run: ({ args, authUser, hostname, shell }) => {
|
|
10
10
|
const env = getAllEnvVars(authUser);
|
|
11
11
|
|
|
12
12
|
if (ifFlag(args, "--help")) {
|
|
@@ -28,7 +28,7 @@ export const neofetchCommand: ShellModule = {
|
|
|
28
28
|
user: authUser,
|
|
29
29
|
host: hostname,
|
|
30
30
|
shell: env.SHELL,
|
|
31
|
-
shellProps:
|
|
31
|
+
shellProps: shell.properties,
|
|
32
32
|
terminal: env.TERM,
|
|
33
33
|
}),
|
|
34
34
|
exitCode: 0,
|
|
@@ -5,7 +5,7 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
5
5
|
export const rmCommand: ShellModule = {
|
|
6
6
|
name: "rm",
|
|
7
7
|
params: ["[-r|-rf] <path>"],
|
|
8
|
-
run: ({ authUser,
|
|
8
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
9
9
|
if (args.length === 0) {
|
|
10
10
|
return { stderr: "rm: missing operand", exitCode: 1 };
|
|
11
11
|
}
|
|
@@ -27,7 +27,7 @@ export const rmCommand: ShellModule = {
|
|
|
27
27
|
for (const target of targets) {
|
|
28
28
|
const resolvedTarget = resolvePath(cwd, target);
|
|
29
29
|
assertPathAccess(authUser, resolvedTarget, "rm");
|
|
30
|
-
vfs.remove(resolvedTarget, { recursive });
|
|
30
|
+
shell.vfs.remove(resolvedTarget, { recursive });
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
return { exitCode: 0 };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { defaultShellProperties } from "..";
|
|
2
1
|
import type { CommandContext, ShellModule } from "../../types/commands";
|
|
3
2
|
import { getArg, getFlag } from "./command-helpers";
|
|
4
3
|
import { runCommand } from "./index";
|
|
@@ -9,8 +8,7 @@ export const shCommand: ShellModule = {
|
|
|
9
8
|
params: ["-c <script>", "[<file>]"],
|
|
10
9
|
aliases: ["bash"],
|
|
11
10
|
run: async (ctx: CommandContext) => {
|
|
12
|
-
const {
|
|
13
|
-
|
|
11
|
+
const { args, authUser, hostname, mode, cwd } = ctx;
|
|
14
12
|
// Handle -c option: sh -c "command"
|
|
15
13
|
if (getFlag(args, "-c") && args.length >= 2) {
|
|
16
14
|
const script = getArg(args, 1) ?? "";
|
|
@@ -39,16 +37,7 @@ export const shCommand: ShellModule = {
|
|
|
39
37
|
|
|
40
38
|
// Execute the command
|
|
41
39
|
const result = await Promise.resolve(
|
|
42
|
-
runCommand(
|
|
43
|
-
command,
|
|
44
|
-
authUser,
|
|
45
|
-
hostname,
|
|
46
|
-
users,
|
|
47
|
-
mode,
|
|
48
|
-
cwd,
|
|
49
|
-
defaultShellProperties,
|
|
50
|
-
vfs,
|
|
51
|
-
),
|
|
40
|
+
runCommand(command, authUser, hostname, mode, cwd, ctx.shell),
|
|
52
41
|
);
|
|
53
42
|
|
|
54
43
|
if (result.stdout) {
|
|
@@ -4,7 +4,8 @@ import { getArg } from "./command-helpers";
|
|
|
4
4
|
export const suCommand: ShellModule = {
|
|
5
5
|
name: "su",
|
|
6
6
|
params: ["- <username>"],
|
|
7
|
-
run: ({ authUser,
|
|
7
|
+
run: ({ authUser, shell, args }) => {
|
|
8
|
+
const users = shell.users!;
|
|
8
9
|
const targetUser = getArg(args, 0, { flags: ["-"] });
|
|
9
10
|
|
|
10
11
|
if (!targetUser) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { defaultShellProperties } from "..";
|
|
2
1
|
import type { ShellModule } from "../../types/commands";
|
|
3
|
-
import {
|
|
2
|
+
import { parseArgs } from "./command-helpers";
|
|
4
3
|
import { runCommand } from "./index";
|
|
5
4
|
|
|
6
5
|
function parseSudoArgs(args: string[]): {
|
|
@@ -8,35 +7,25 @@ function parseSudoArgs(args: string[]): {
|
|
|
8
7
|
loginShell: boolean;
|
|
9
8
|
commandLine: string | null;
|
|
10
9
|
} {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
? targetUserValue
|
|
16
|
-
: "root";
|
|
10
|
+
const { flags, flagsWithValues, positionals } = parseArgs(args, {
|
|
11
|
+
flags: ["-i", "-S"],
|
|
12
|
+
flagsWithValue: ["-u", "--user"],
|
|
13
|
+
});
|
|
17
14
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
flagsWithValue: ["-u", "--user"],
|
|
23
|
-
});
|
|
24
|
-
if (!part) {
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
commandParts.push(part);
|
|
28
|
-
}
|
|
15
|
+
const loginShell = flags.has("-i");
|
|
16
|
+
const targetUser =
|
|
17
|
+
flagsWithValues.get("-u") || flagsWithValues.get("--user") || "root";
|
|
18
|
+
const commandLine = positionals.length > 0 ? positionals.join(" ") : null;
|
|
29
19
|
|
|
30
|
-
const commandLine = commandParts.length > 0 ? commandParts.join(" ") : null;
|
|
31
20
|
return { targetUser, loginShell, commandLine };
|
|
32
21
|
}
|
|
33
22
|
export const sudoCommand: ShellModule = {
|
|
34
23
|
name: "sudo",
|
|
35
24
|
params: ["<command...>"],
|
|
36
|
-
run: async ({ authUser, hostname,
|
|
25
|
+
run: async ({ authUser, hostname, mode, cwd, shell, args }) => {
|
|
37
26
|
const { targetUser, loginShell, commandLine } = parseSudoArgs(args);
|
|
38
27
|
|
|
39
|
-
if (authUser !== "root" && !users.isSudoer(authUser)) {
|
|
28
|
+
if (authUser !== "root" && !shell.users.isSudoer(authUser)) {
|
|
40
29
|
return { stderr: "sudo: permission denied", exitCode: 1 };
|
|
41
30
|
}
|
|
42
31
|
|
|
@@ -60,11 +49,9 @@ export const sudoCommand: ShellModule = {
|
|
|
60
49
|
commandLine,
|
|
61
50
|
effectiveUser,
|
|
62
51
|
hostname,
|
|
63
|
-
users,
|
|
64
52
|
mode,
|
|
65
53
|
loginShell ? `/home/${effectiveUser}` : cwd,
|
|
66
|
-
|
|
67
|
-
vfs,
|
|
54
|
+
shell,
|
|
68
55
|
);
|
|
69
56
|
}
|
|
70
57
|
|
|
@@ -4,7 +4,7 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
4
4
|
export const touchCommand: ShellModule = {
|
|
5
5
|
name: "touch",
|
|
6
6
|
params: ["<file>"],
|
|
7
|
-
run: ({ authUser,
|
|
7
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
8
8
|
if (args.length === 0) {
|
|
9
9
|
return { stderr: "touch: missing file operand", exitCode: 1 };
|
|
10
10
|
}
|
|
@@ -12,8 +12,8 @@ export const touchCommand: ShellModule = {
|
|
|
12
12
|
for (const file of args) {
|
|
13
13
|
const target = resolvePath(cwd, file);
|
|
14
14
|
assertPathAccess(authUser, target, "touch");
|
|
15
|
-
if (!vfs.exists(target)) {
|
|
16
|
-
vfs.writeFile(target, "");
|
|
15
|
+
if (!shell.vfs.exists(target)) {
|
|
16
|
+
shell.vfs.writeFile(target, "");
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
return { exitCode: 0 };
|
|
@@ -5,9 +5,9 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
5
5
|
export const treeCommand: ShellModule = {
|
|
6
6
|
name: "tree",
|
|
7
7
|
params: ["[path]"],
|
|
8
|
-
run: ({ authUser,
|
|
8
|
+
run: ({ authUser, shell, cwd, args }) => {
|
|
9
9
|
const target = resolvePath(cwd, getArg(args, 0) ?? cwd);
|
|
10
10
|
assertPathAccess(authUser, target, "tree");
|
|
11
|
-
return { stdout: vfs.tree(target), exitCode: 0 };
|
|
11
|
+
return { stdout: shell.vfs.tree(target), exitCode: 0 };
|
|
12
12
|
},
|
|
13
13
|
};
|