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
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
12
12
|
- [Overview](#overview)
|
|
13
|
+
- [What This Is / What This Is Not](#what-this-is--what-this-is-not)
|
|
13
14
|
- [Why This Package](#why-this-package)
|
|
14
15
|
- [Installation](#installation)
|
|
15
16
|
- [Compatibility](#compatibility)
|
|
@@ -42,6 +43,22 @@
|
|
|
42
43
|
- **Built-in Commands**: `ls`, `cd`, `pwd`, `cat`, `mkdir`, `touch`, `rm`, `tree`, `whoami`, `hostname`, `who`, `sudo`, `su`, `adduser`, `deluser`, `nano` (text editor), `curl`, `wget`, and a growing set of additional commands. Not everything is implemented yet, and shell compatibility is still being expanded.
|
|
43
44
|
- **Full TypeScript Support**: Complete JSDoc coverage, exported types, and first-class async/await for all operations.
|
|
44
45
|
|
|
46
|
+
## What This Is / What This Is Not
|
|
47
|
+
|
|
48
|
+
### What This Is
|
|
49
|
+
|
|
50
|
+
- A virtual shell runtime written in TypeScript.
|
|
51
|
+
- An in-memory environment with its own virtual filesystem, user management, and command runtime.
|
|
52
|
+
- A practical tool for deterministic testing, automation pipelines, and SSH-like workflows without running real containers.
|
|
53
|
+
|
|
54
|
+
### What This Is Not
|
|
55
|
+
|
|
56
|
+
- Not a fully isolated container runtime.
|
|
57
|
+
- Not a security sandbox.
|
|
58
|
+
- Not a kernel-level isolation boundary (unlike Docker/VM-based isolation).
|
|
59
|
+
|
|
60
|
+
This project emulates shell behavior for developer workflows. It is designed for realism and productivity, not hard security isolation.
|
|
61
|
+
|
|
45
62
|
## Why This Package
|
|
46
63
|
|
|
47
64
|
This package is designed for teams that need a realistic SSH-like runtime without spinning up real containers or VMs.
|
|
@@ -87,10 +104,10 @@ The virtual filesystem and shell behavior are intentionally portable and do not
|
|
|
87
104
|
### Running an SSH Server
|
|
88
105
|
|
|
89
106
|
```typescript
|
|
90
|
-
import {
|
|
107
|
+
import { VirtualSshServer } from "typescript-virtual-container";
|
|
91
108
|
|
|
92
109
|
// Create server on port 2222
|
|
93
|
-
const ssh = new
|
|
110
|
+
const ssh = new VirtualSshServer({
|
|
94
111
|
port: 2222,
|
|
95
112
|
hostname: "my-container"
|
|
96
113
|
});
|
|
@@ -112,13 +129,14 @@ process.on("SIGTERM", () => {
|
|
|
112
129
|
### Using the Programmatic Client API
|
|
113
130
|
|
|
114
131
|
```typescript
|
|
115
|
-
import {
|
|
132
|
+
import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
|
|
116
133
|
|
|
117
|
-
const
|
|
134
|
+
const shell = new VirtualShell("typescript-vm");
|
|
135
|
+
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
118
136
|
await ssh.start();
|
|
119
137
|
|
|
120
138
|
// Create authenticated client for specific user
|
|
121
|
-
const client = new SshClient(
|
|
139
|
+
const client = new SshClient(shell, "root");
|
|
122
140
|
|
|
123
141
|
// Execute commands programmatically
|
|
124
142
|
const list = await client.ls("/home");
|
|
@@ -178,7 +196,7 @@ ssh.stop();
|
|
|
178
196
|
|
|
179
197
|
### SshMimic (SSH Server)
|
|
180
198
|
|
|
181
|
-
Main SSH server class.
|
|
199
|
+
Main SSH server class, exported as `VirtualSshServer` in the package entrypoint. It wires the virtual shell runtime into ssh2 sessions and manages authentication/session handlers.
|
|
182
200
|
|
|
183
201
|
#### Constructor
|
|
184
202
|
|
|
@@ -186,17 +204,25 @@ Main SSH server class. Manages virtual filesystem, user authentication, and sess
|
|
|
186
204
|
new SshMimic(options: {
|
|
187
205
|
port: number; // TCP port to bind on localhost
|
|
188
206
|
hostname?: string; // Virtual hostname (default: "typescript-vm")
|
|
189
|
-
|
|
207
|
+
shell?: VirtualShell; // Optional preconfigured shell instance
|
|
190
208
|
})
|
|
191
209
|
```
|
|
192
210
|
|
|
211
|
+
- `hostname` controls the SSH ident label and the default hostname used by a generated shell.
|
|
212
|
+
- If `shell` is omitted, the server creates `new VirtualShell(hostname)` for you.
|
|
213
|
+
|
|
193
214
|
**Example:**
|
|
194
215
|
|
|
195
216
|
```typescript
|
|
217
|
+
const virtualShell = new VirtualShell("my-lab", {
|
|
218
|
+
kernel: "1.0.0+itsrealfortune+1-amd64",
|
|
219
|
+
os: "Fortune GNU/Linux x64",
|
|
220
|
+
arch: "x86_64",
|
|
221
|
+
}, "./data");
|
|
196
222
|
const ssh = new SshMimic({
|
|
197
223
|
port: 2222,
|
|
198
224
|
hostname: "my-lab",
|
|
199
|
-
|
|
225
|
+
shell: virtualShell
|
|
200
226
|
});
|
|
201
227
|
```
|
|
202
228
|
|
|
@@ -254,21 +280,22 @@ console.log(`Server name: ${ssh.getHostname()}`);
|
|
|
254
280
|
|
|
255
281
|
### SshClient (Programmatic Shell API)
|
|
256
282
|
|
|
257
|
-
Execute shell commands
|
|
283
|
+
Execute shell commands against a `VirtualShell` instance without SSH overhead. Maintains connection state (current working directory) across calls.
|
|
258
284
|
|
|
259
285
|
#### Constructor
|
|
260
286
|
|
|
261
287
|
```typescript
|
|
262
|
-
new SshClient(
|
|
288
|
+
new SshClient(shell: VirtualShell, username: string)
|
|
263
289
|
```
|
|
264
290
|
|
|
265
|
-
- **
|
|
291
|
+
- **shell**: Parent virtual shell instance
|
|
266
292
|
- **username**: User to authenticate as (no password required)
|
|
267
293
|
|
|
268
294
|
**Example:**
|
|
269
295
|
|
|
270
296
|
```typescript
|
|
271
|
-
const
|
|
297
|
+
const shell = new VirtualShell("typescript-vm");
|
|
298
|
+
const client = new SshClient(shell, "alice");
|
|
272
299
|
```
|
|
273
300
|
|
|
274
301
|
#### Methods
|
|
@@ -419,30 +446,57 @@ console.log(client.getUsername()); // Username from constructor
|
|
|
419
446
|
|
|
420
447
|
### VirtualShell
|
|
421
448
|
|
|
422
|
-
Encapsulates shell execution primitives used by the SSH runtime for command dispatch and
|
|
449
|
+
Encapsulates shell execution primitives used by the SSH runtime for command dispatch, interactive sessions, and the programmatic client.
|
|
450
|
+
|
|
451
|
+
#### ShellProperties
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
interface ShellProperties {
|
|
455
|
+
kernel: string;
|
|
456
|
+
os: "Fortune GNU/Linux x64";
|
|
457
|
+
arch: "x86_64";
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const defaultShellProperties: ShellProperties;
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
- `kernel` is displayed in shell/system information output.
|
|
464
|
+
- `os` and `arch` are fixed labels used by the shell runtime.
|
|
423
465
|
|
|
424
466
|
#### Constructor
|
|
425
467
|
|
|
426
468
|
```typescript
|
|
427
469
|
new VirtualShell(
|
|
428
|
-
vfs: VirtualFileSystem,
|
|
429
|
-
users: VirtualUserManager,
|
|
430
470
|
hostname: string,
|
|
471
|
+
properties?: ShellProperties,
|
|
472
|
+
basePath?: string,
|
|
431
473
|
)
|
|
432
474
|
```
|
|
433
475
|
|
|
434
|
-
- **vfs**: Virtual filesystem instance used by shell commands.
|
|
435
|
-
- **users**: User manager for authentication/session-aware command behavior.
|
|
436
476
|
- **hostname**: Hostname injected into command context and prompt behavior.
|
|
477
|
+
- **properties**: Optional shell metadata. Defaults to `defaultShellProperties`.
|
|
478
|
+
- **basePath**: Optional directory used to resolve `.vfs/mirror.tar.gz` (defaults to `.`).
|
|
437
479
|
|
|
438
480
|
**Example:**
|
|
439
481
|
|
|
440
482
|
```typescript
|
|
441
|
-
const shell = new VirtualShell(
|
|
483
|
+
const shell = new VirtualShell("typescript-vm", {
|
|
484
|
+
kernel: "1.0.0+itsrealfortune+1-amd64",
|
|
485
|
+
os: "Fortune GNU/Linux x64",
|
|
486
|
+
arch: "x86_64",
|
|
487
|
+
}, "./data");
|
|
442
488
|
```
|
|
443
489
|
|
|
444
490
|
#### Methods
|
|
445
491
|
|
|
492
|
+
##### `addCommand(name: string, params: string[], callback: (ctx: CommandContext) => CommandResult | Promise<CommandResult>): void`
|
|
493
|
+
|
|
494
|
+
Registers a custom command at runtime.
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
shell.addCommand("hello", [], () => ({ stdout: "hello", exitCode: 0 }));
|
|
498
|
+
```
|
|
499
|
+
|
|
446
500
|
##### `executeCommand(rawInput: string, authUser: string, cwd: string): void`
|
|
447
501
|
|
|
448
502
|
Runs one command input in shell mode for a given user and working directory.
|
|
@@ -617,11 +671,12 @@ User authentication, password hashing (scrypt), sudo privilege management, and s
|
|
|
617
671
|
#### Constructor
|
|
618
672
|
|
|
619
673
|
```typescript
|
|
620
|
-
new VirtualUserManager(vfs: VirtualFileSystem, defaultRootPassword?: string)
|
|
674
|
+
new VirtualUserManager(vfs: VirtualFileSystem, defaultRootPassword?: string, autoSudoForNewUsers?: boolean)
|
|
621
675
|
```
|
|
622
676
|
|
|
623
677
|
- **vfs**: Virtual filesystem (for auth data persistence)
|
|
624
|
-
- **defaultRootPassword**: Root password
|
|
678
|
+
- **defaultRootPassword**: Root password used when root is created (default: "root")
|
|
679
|
+
- **autoSudoForNewUsers**: When true, new users are added to sudoers automatically (default: `true` unless `SSH_MIMIC_AUTO_SUDO_NEW_USERS` disables it)
|
|
625
680
|
|
|
626
681
|
```typescript
|
|
627
682
|
const users = new VirtualUserManager(vfs, "SecureRootPass123");
|
|
@@ -801,9 +856,9 @@ interface VirtualActiveSession {
|
|
|
801
856
|
Minimal server startup that accepts SSH connections:
|
|
802
857
|
|
|
803
858
|
```typescript
|
|
804
|
-
import {
|
|
859
|
+
import { VirtualSshServer } from "typescript-virtual-container";
|
|
805
860
|
|
|
806
|
-
const ssh = new
|
|
861
|
+
const ssh = new VirtualSshServer({
|
|
807
862
|
port: 2222,
|
|
808
863
|
hostname: "lab-environment"
|
|
809
864
|
});
|
|
@@ -835,12 +890,13 @@ ssh root@localhost -p 2222
|
|
|
835
890
|
Create, read, modify files without SSH:
|
|
836
891
|
|
|
837
892
|
```typescript
|
|
838
|
-
import {
|
|
893
|
+
import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
|
|
839
894
|
|
|
840
|
-
const
|
|
895
|
+
const shell = new VirtualShell("typescript-vm");
|
|
896
|
+
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
841
897
|
await ssh.start();
|
|
842
898
|
|
|
843
|
-
const client = new SshClient(
|
|
899
|
+
const client = new SshClient(shell, "root");
|
|
844
900
|
|
|
845
901
|
// Create structure
|
|
846
902
|
await client.mkdir("/app/config", true);
|
|
@@ -874,9 +930,10 @@ ssh.stop();
|
|
|
874
930
|
Create users, manage permissions, session tracking:
|
|
875
931
|
|
|
876
932
|
```typescript
|
|
877
|
-
import {
|
|
933
|
+
import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
|
|
878
934
|
|
|
879
|
-
const
|
|
935
|
+
const shell = new VirtualShell("typescript-vm");
|
|
936
|
+
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
880
937
|
await ssh.start();
|
|
881
938
|
|
|
882
939
|
const users = ssh.getUsers()!;
|
|
@@ -891,11 +948,11 @@ await users.removeSudoer("bob");
|
|
|
891
948
|
await users.addSudoer("alice");
|
|
892
949
|
|
|
893
950
|
// Alice: High privilege
|
|
894
|
-
const alice = new SshClient(
|
|
951
|
+
const alice = new SshClient(shell, "alice");
|
|
895
952
|
await alice.writeFile("/etc/important.conf", "secret=yes");
|
|
896
953
|
|
|
897
954
|
// Bob: Regular user
|
|
898
|
-
const bob = new SshClient(
|
|
955
|
+
const bob = new SshClient(shell, "bob");
|
|
899
956
|
const result = await bob.cat("/etc/important.conf");
|
|
900
957
|
console.log("Bob read file:", result.stderr);
|
|
901
958
|
|
|
@@ -909,12 +966,13 @@ ssh.stop();
|
|
|
909
966
|
Save filesystem state between runs:
|
|
910
967
|
|
|
911
968
|
```typescript
|
|
912
|
-
import {
|
|
969
|
+
import { VirtualSshServer, VirtualShell } from "typescript-virtual-container";
|
|
913
970
|
|
|
914
971
|
// First run: Initialize
|
|
915
|
-
const
|
|
972
|
+
const shell1 = new VirtualShell("typescript-vm", undefined, "./container");
|
|
973
|
+
const ssh1 = new VirtualSshServer({
|
|
916
974
|
port: 2222,
|
|
917
|
-
|
|
975
|
+
shell: shell1
|
|
918
976
|
});
|
|
919
977
|
await ssh1.start();
|
|
920
978
|
const vfs1 = ssh1.getVfs()!;
|
|
@@ -927,9 +985,10 @@ ssh1.stop();
|
|
|
927
985
|
console.log("State saved to ./container/.vfs/mirror.tar.gz");
|
|
928
986
|
|
|
929
987
|
// Later: Reload and continue
|
|
930
|
-
const
|
|
988
|
+
const shell2 = new VirtualShell("typescript-vm", undefined, "./container");
|
|
989
|
+
const ssh2 = new VirtualSshServer({
|
|
931
990
|
port: 2223,
|
|
932
|
-
|
|
991
|
+
shell: shell2
|
|
933
992
|
});
|
|
934
993
|
await ssh2.start();
|
|
935
994
|
const vfs2 = ssh2.getVfs()!;
|
|
@@ -948,13 +1007,14 @@ ssh2.stop();
|
|
|
948
1007
|
Simulate filesystem changes and verify outcomes:
|
|
949
1008
|
|
|
950
1009
|
```typescript
|
|
951
|
-
import {
|
|
1010
|
+
import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
|
|
952
1011
|
|
|
953
1012
|
async function testDeployment() {
|
|
954
|
-
const
|
|
1013
|
+
const shell = new VirtualShell("typescript-vm");
|
|
1014
|
+
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
955
1015
|
await ssh.start();
|
|
956
1016
|
|
|
957
|
-
const client = new SshClient(
|
|
1017
|
+
const client = new SshClient(shell, "root");
|
|
958
1018
|
|
|
959
1019
|
// Pre-deployment: Set up base structure
|
|
960
1020
|
await client.mkdir("/srv/app", true);
|
|
@@ -984,12 +1044,13 @@ testDeployment().catch(console.error);
|
|
|
984
1044
|
Simulate shell workflows:
|
|
985
1045
|
|
|
986
1046
|
```typescript
|
|
987
|
-
import {
|
|
1047
|
+
import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
|
|
988
1048
|
|
|
989
|
-
const
|
|
1049
|
+
const shell = new VirtualShell("typescript-vm");
|
|
1050
|
+
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
990
1051
|
await ssh.start();
|
|
991
1052
|
|
|
992
|
-
const client = new SshClient(
|
|
1053
|
+
const client = new SshClient(shell, "root");
|
|
993
1054
|
|
|
994
1055
|
// Create nested structure
|
|
995
1056
|
await client.mkdir("/home/user/projects/myapp/src", true);
|
|
@@ -1026,12 +1087,13 @@ ssh.stop();
|
|
|
1026
1087
|
Graceful error handling in programmatic workflows:
|
|
1027
1088
|
|
|
1028
1089
|
```typescript
|
|
1029
|
-
import {
|
|
1090
|
+
import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
|
|
1030
1091
|
|
|
1031
|
-
const
|
|
1092
|
+
const shell = new VirtualShell("typescript-vm");
|
|
1093
|
+
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
1032
1094
|
await ssh.start();
|
|
1033
1095
|
|
|
1034
|
-
const client = new SshClient(
|
|
1096
|
+
const client = new SshClient(shell, "root");
|
|
1035
1097
|
|
|
1036
1098
|
// Try read non-existent file
|
|
1037
1099
|
const result = await client.readFile("/etc/nonexistent.conf");
|
|
@@ -1056,32 +1118,42 @@ ssh.stop();
|
|
|
1056
1118
|
|
|
1057
1119
|
## Built-in Commands
|
|
1058
1120
|
|
|
1059
|
-
The following commands are available in both SSH shell mode and via `SshClient.exec()`.
|
|
1121
|
+
The following commands are currently registered and available in both SSH shell mode and via `SshClient.exec()`. Some flags and edge-case behavior are still being expanded for shell compatibility.
|
|
1060
1122
|
|
|
1061
1123
|
| Command | Purpose | Notes |
|
|
1062
1124
|
|---------|---------|-------|
|
|
1063
|
-
| `
|
|
1125
|
+
| `adduser <name> <pass>` | Create user | Root only |
|
|
1126
|
+
| `cat <path>` | Read file | Displays content |
|
|
1064
1127
|
| `cd <path>` | Change directory | Updates client cwd |
|
|
1128
|
+
| `clear` | Clear screen | No args |
|
|
1129
|
+
| `curl <url>` | Fetch URL | Mock implementation |
|
|
1130
|
+
| `deluser <name>` | Delete user | Root only, not root |
|
|
1131
|
+
| `echo <text...>` | Print text | Supports shell-like argument output |
|
|
1132
|
+
| `env` | List environment variables | Shell environment view |
|
|
1133
|
+
| `exit [code]` | Close session | Optional exit code |
|
|
1134
|
+
| `export NAME=VALUE` | Set/export environment variable | Persists in shell env |
|
|
1135
|
+
| `grep <pattern> [path]` | Search for text | Simplified grep behavior |
|
|
1136
|
+
| `help` | List commands | No args |
|
|
1137
|
+
| `hostname` | Server hostname | No args |
|
|
1138
|
+
| `htop` | System monitor | Mock display |
|
|
1139
|
+
| `pwd` | Print working directory | No args |
|
|
1065
1140
|
| `ls [path]` | List directory | Defaults to `.` |
|
|
1066
1141
|
| `mkdir [-p] <path>` | Create directory | `-p` for parents |
|
|
1142
|
+
| `nano <path>` | Text editor | Interactive mode |
|
|
1143
|
+
| `neofetch` | Show system summary | Mock display |
|
|
1067
1144
|
| `touch <path>` | Create empty file | Updates timestamps |
|
|
1068
|
-
| `cat <path>` | Read file | Displays content |
|
|
1069
1145
|
| `rm [-r] <path>` | Remove file/dir | `-r` for recursive |
|
|
1070
|
-
| `
|
|
1071
|
-
| `
|
|
1072
|
-
| `hostname` | Server hostname | No args |
|
|
1073
|
-
| `who` | Active sessions | No args |
|
|
1074
|
-
| `sudo [-i] <cmd>` | Elevation | Requires sudoer status |
|
|
1146
|
+
| `set` | Show shell options/variables | Simplified behavior |
|
|
1147
|
+
| `sh <script>` | Run shell script | Simplified execution model |
|
|
1075
1148
|
| `su <user>` | Switch user | Requires password/sudo |
|
|
1076
|
-
| `
|
|
1077
|
-
| `
|
|
1078
|
-
| `
|
|
1149
|
+
| `sudo [-i] <cmd>` | Elevation | Requires sudoer status |
|
|
1150
|
+
| `tree [path]` | ASCII tree view | Defaults to `.` |
|
|
1151
|
+
| `unset <name>` | Remove environment variable | Shell environment update |
|
|
1079
1152
|
| `wget <url>` | Download | Mock implementation |
|
|
1080
|
-
| `
|
|
1081
|
-
| `
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
| `help` | List commands | No args |
|
|
1153
|
+
| `who` | Active sessions | No args |
|
|
1154
|
+
| `whoami` | Current user | No args |
|
|
1155
|
+
|
|
1156
|
+
Commands can be added via the VirtualShell addCommand() method for custom behavior.
|
|
1085
1157
|
|
|
1086
1158
|
---
|
|
1087
1159
|
|
|
@@ -1105,10 +1177,11 @@ npm run start
|
|
|
1105
1177
|
### Runtime Options
|
|
1106
1178
|
|
|
1107
1179
|
```typescript
|
|
1108
|
-
const
|
|
1180
|
+
const shell = new VirtualShell("my-container", undefined, "./data");
|
|
1181
|
+
const ssh = new VirtualSshServer({
|
|
1109
1182
|
port: 2222, // Required
|
|
1110
1183
|
hostname: "my-container", // Optional
|
|
1111
|
-
|
|
1184
|
+
shell // Optional, prebuilt shell instance
|
|
1112
1185
|
});
|
|
1113
1186
|
```
|
|
1114
1187
|
|
|
@@ -1131,8 +1204,9 @@ const ssh = new VirtualMachine({
|
|
|
1131
1204
|
**Example:**
|
|
1132
1205
|
|
|
1133
1206
|
```typescript
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1207
|
+
const shell = new VirtualShell("typescript-vm");
|
|
1208
|
+
const client1 = new SshClient(shell, "alice");
|
|
1209
|
+
const client2 = new SshClient(shell, "bob");
|
|
1136
1210
|
|
|
1137
1211
|
const [result1, result2] = await Promise.all([
|
|
1138
1212
|
client1.writeFile("/tmp/alice.txt", "..."),
|
|
@@ -1202,7 +1276,7 @@ Error: listen EADDRINUSE :::2222
|
|
|
1202
1276
|
**Solution**: Use a different port
|
|
1203
1277
|
|
|
1204
1278
|
```typescript
|
|
1205
|
-
const ssh = new
|
|
1279
|
+
const ssh = new VirtualSshServer({ port: 3333 });
|
|
1206
1280
|
```
|
|
1207
1281
|
|
|
1208
1282
|
### SSH Authentication Failed
|
|
@@ -1239,29 +1313,6 @@ await ssh.getVfs().flushMirror();
|
|
|
1239
1313
|
|
|
1240
1314
|
---
|
|
1241
1315
|
|
|
1242
|
-
## Migration Guide
|
|
1243
|
-
|
|
1244
|
-
### From v0.0.x to v1.x
|
|
1245
|
-
|
|
1246
|
-
Old API:
|
|
1247
|
-
|
|
1248
|
-
```typescript
|
|
1249
|
-
const ssh = new SshMimic(2222, "hostname");
|
|
1250
|
-
```
|
|
1251
|
-
|
|
1252
|
-
New API:
|
|
1253
|
-
|
|
1254
|
-
```typescript
|
|
1255
|
-
const ssh = new VirtualMachine({ port: 2222, hostname: "hostname" });
|
|
1256
|
-
```
|
|
1257
|
-
|
|
1258
|
-
**Changes**:
|
|
1259
|
-
- Object-based constructor
|
|
1260
|
-
- Optional `basePath` parameter
|
|
1261
|
-
- Exports renamed: `SshMimic` → `VirtualMachine` from main entry
|
|
1262
|
-
|
|
1263
|
-
---
|
|
1264
|
-
|
|
1265
1316
|
## Contributing
|
|
1266
1317
|
|
|
1267
1318
|
1. Fork repository
|
package/package.json
CHANGED
package/src/SSHMimic/client.ts
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import type { CommandResult } from "../types/commands";
|
|
2
|
+
import type { VirtualShell } from "../VirtualShell";
|
|
2
3
|
import { runCommand } from "../VirtualShell/commands";
|
|
3
|
-
import type { SshMimic } from "./index";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Programmatic
|
|
6
|
+
* Programmatic client for executing shell commands against a virtual shell.
|
|
7
7
|
*
|
|
8
|
-
* Maintains
|
|
9
|
-
*
|
|
8
|
+
* Maintains working-directory state across invocations and runs commands as a
|
|
9
|
+
* single authenticated user without SSH transport overhead.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
13
|
-
* const
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* const client = new SshClient(ssh, "alice");
|
|
13
|
+
* const shell = new VirtualShell("typescript-vm");
|
|
14
|
+
* const client = new SshClient(shell, "alice");
|
|
17
15
|
* const result = await client.cd("/tmp");
|
|
18
16
|
* const list = await client.ls();
|
|
19
17
|
* ```
|
|
@@ -22,13 +20,13 @@ export class SshClient {
|
|
|
22
20
|
private currentCwd = "/";
|
|
23
21
|
|
|
24
22
|
/**
|
|
25
|
-
* Creates
|
|
23
|
+
* Creates a programmatic client bound to a virtual shell and user.
|
|
26
24
|
*
|
|
27
|
-
* @param
|
|
25
|
+
* @param shell Parent virtual shell instance.
|
|
28
26
|
* @param username Login user for all commands.
|
|
29
27
|
*/
|
|
30
28
|
constructor(
|
|
31
|
-
private
|
|
29
|
+
private shell: VirtualShell,
|
|
32
30
|
private username: string,
|
|
33
31
|
) {}
|
|
34
32
|
|
|
@@ -39,9 +37,9 @@ export class SshClient {
|
|
|
39
37
|
* @returns Command result with stdout/stderr/exitCode.
|
|
40
38
|
*/
|
|
41
39
|
async exec(command: string): Promise<CommandResult> {
|
|
42
|
-
const vfs = this.
|
|
43
|
-
const users = this.
|
|
44
|
-
const hostname = this.
|
|
40
|
+
const vfs = this.shell.getVfs();
|
|
41
|
+
const users = this.shell.getUsers();
|
|
42
|
+
const hostname = this.shell.getHostname();
|
|
45
43
|
|
|
46
44
|
if (!vfs || !users) {
|
|
47
45
|
throw new Error("SSH client not started");
|
|
@@ -51,10 +49,9 @@ export class SshClient {
|
|
|
51
49
|
command,
|
|
52
50
|
this.username,
|
|
53
51
|
hostname,
|
|
54
|
-
users,
|
|
55
52
|
"exec",
|
|
56
53
|
this.currentCwd,
|
|
57
|
-
|
|
54
|
+
this.shell,
|
|
58
55
|
);
|
|
59
56
|
|
|
60
57
|
// Handle async results
|
|
@@ -151,7 +148,7 @@ export class SshClient {
|
|
|
151
148
|
* @returns Result from touch/write simulation.
|
|
152
149
|
*/
|
|
153
150
|
async writeFile(path: string, content: string): Promise<CommandResult> {
|
|
154
|
-
const vfs = this.
|
|
151
|
+
const vfs = this.shell.getVfs();
|
|
155
152
|
if (!vfs) {
|
|
156
153
|
throw new Error("SSH client not started");
|
|
157
154
|
}
|
|
@@ -174,7 +171,7 @@ export class SshClient {
|
|
|
174
171
|
* @returns File content as string or error in result.
|
|
175
172
|
*/
|
|
176
173
|
async readFile(path: string): Promise<CommandResult> {
|
|
177
|
-
const vfs = this.
|
|
174
|
+
const vfs = this.shell.getVfs();
|
|
178
175
|
if (!vfs) {
|
|
179
176
|
throw new Error("SSH client not started");
|
|
180
177
|
}
|
package/src/SSHMimic/exec.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { ExecStream } from "../types/streams";
|
|
2
|
-
import type
|
|
3
|
-
import { defaultShellProperties } from "../VirtualShell";
|
|
2
|
+
import type { VirtualShell } from "../VirtualShell";
|
|
4
3
|
import { runCommand } from "../VirtualShell/commands";
|
|
5
|
-
import type { VirtualUserManager } from "./users";
|
|
6
4
|
|
|
7
5
|
function toTtyLines(text: string): string {
|
|
8
6
|
return text
|
|
@@ -16,20 +14,10 @@ export function runExec(
|
|
|
16
14
|
cmd: string,
|
|
17
15
|
authUser: string,
|
|
18
16
|
hostname: string,
|
|
19
|
-
|
|
20
|
-
vfs: VirtualFileSystem,
|
|
17
|
+
shell: VirtualShell,
|
|
21
18
|
): void {
|
|
22
19
|
Promise.resolve(
|
|
23
|
-
runCommand(
|
|
24
|
-
cmd,
|
|
25
|
-
authUser,
|
|
26
|
-
hostname,
|
|
27
|
-
users,
|
|
28
|
-
"exec",
|
|
29
|
-
`/home/${authUser}`,
|
|
30
|
-
defaultShellProperties,
|
|
31
|
-
vfs,
|
|
32
|
-
),
|
|
20
|
+
runCommand(cmd, authUser, hostname, "exec", `/home/${authUser}`, shell),
|
|
33
21
|
).then((result) => {
|
|
34
22
|
if (result.stdout) {
|
|
35
23
|
stream.write(`${toTtyLines(result.stdout)}\r\n`);
|
|
@@ -40,7 +28,8 @@ export function runExec(
|
|
|
40
28
|
}
|
|
41
29
|
|
|
42
30
|
stream.exit(result.exitCode ?? 0);
|
|
43
|
-
|
|
31
|
+
console.log(shell.vfs);
|
|
32
|
+
void shell.vfs.flushMirror();
|
|
44
33
|
stream.end();
|
|
45
34
|
});
|
|
46
35
|
}
|