typescript-virtual-container 1.2.8 → 1.2.9
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/README.md +359 -32
- package/dist/SSHMimic/executor.js +3 -5
- package/dist/VirtualPackageManager/index.d.ts +202 -0
- package/dist/VirtualPackageManager/index.d.ts.map +1 -0
- package/dist/VirtualPackageManager/index.js +676 -0
- package/dist/VirtualShell/index.d.ts +87 -12
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +83 -12
- 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/alias.d.ts +4 -0
- package/dist/commands/alias.d.ts.map +1 -0
- package/dist/commands/alias.js +58 -0
- package/dist/commands/apt.d.ts +4 -0
- package/dist/commands/apt.d.ts.map +1 -0
- package/dist/commands/apt.js +182 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +27 -8
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +52 -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/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +81 -29
- package/dist/commands/dpkg.d.ts +4 -0
- package/dist/commands/dpkg.d.ts.map +1 -0
- package/dist/commands/dpkg.js +144 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +24 -12
- package/dist/commands/free.d.ts +3 -0
- package/dist/commands/free.d.ts.map +1 -0
- package/dist/commands/free.js +38 -0
- 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 +3 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +21 -0
- package/dist/commands/index.d.ts +8 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +120 -11
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +4 -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 +50 -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/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +5 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +5 -2
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +27 -6
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +29 -11
- package/dist/commands/source.d.ts +3 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +31 -0
- package/dist/commands/test.d.ts +3 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +92 -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/uptime.d.ts +3 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +40 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +71 -100
- 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/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/modules/linuxRootfs.d.ts +24 -0
- package/dist/modules/linuxRootfs.d.ts.map +1 -0
- package/dist/modules/linuxRootfs.js +297 -0
- package/dist/modules/neofetch.d.ts.map +1 -1
- package/dist/modules/neofetch.js +1 -0
- package/package.json +2 -1
- package/src/SSHMimic/executor.ts +3 -5
- package/src/VirtualPackageManager/index.ts +820 -0
- package/src/VirtualShell/index.ts +104 -13
- package/src/VirtualUserManager/index.ts +55 -20
- package/src/commands/alias.ts +60 -0
- package/src/commands/apt.ts +198 -0
- package/src/commands/cat.ts +32 -8
- package/src/commands/chmod.ts +48 -3
- package/src/commands/command-helpers.ts +78 -4
- package/src/commands/curl.ts +78 -37
- package/src/commands/dpkg.ts +158 -0
- package/src/commands/echo.ts +30 -14
- package/src/commands/free.ts +40 -0
- package/src/commands/helpers.ts +8 -0
- package/src/commands/history.ts +29 -0
- package/src/commands/index.ts +116 -11
- package/src/commands/ls.ts +5 -4
- package/src/commands/lsb-release.ts +52 -0
- package/src/commands/man.ts +166 -0
- package/src/commands/neofetch.ts +5 -0
- package/src/commands/ping.ts +5 -2
- package/src/commands/ps.ts +28 -6
- package/src/commands/sh.ts +33 -11
- package/src/commands/source.ts +35 -0
- package/src/commands/test.ts +100 -0
- package/src/commands/type.ts +40 -0
- package/src/commands/uptime.ts +46 -0
- package/src/commands/wget.ts +70 -123
- package/src/commands/which.ts +34 -0
- package/src/index.ts +10 -0
- package/src/modules/linuxRootfs.ts +439 -0
- package/src/modules/neofetch.ts +1 -0
- package/standalone.js +418 -103
- package/standalone.js.map +4 -4
- package/tests/new-features.test.ts +626 -0
package/README.md
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
# `typescript-virtual-container`
|
|
2
2
|
|
|
3
|
-
> Pure in-memory SSH/SFTP server with a virtual
|
|
3
|
+
> Pure in-memory SSH/SFTP server with a realistic Linux rootfs, a virtual package manager, a real shell interpreter, and a typed programmatic API for testing, automation, honeypots, and interactive shell scripting in TypeScript/JavaScript.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
> **Release notes — what changed in this drop**
|
|
8
|
+
>
|
|
9
|
+
> **Linux rootfs** — full `/etc`, `/proc`, `/sys`, `/dev`, `/usr`, `/var` hierarchy bootstrapped at init. `neofetch` now shows real uptime and package count. `/proc/meminfo`, `/proc/cpuinfo`, `/proc/version` populated from live system data. `/etc/passwd`, `/etc/group`, `/etc/shadow` synced from `VirtualUserManager`.
|
|
10
|
+
>
|
|
11
|
+
> **Virtual package manager** — `apt install vim git nodejs python3` works. 25 packages in the built-in registry. Dependency resolution, file installation into VFS, `/var/lib/dpkg/status` persistence, `dpkg -l|-s|-L`, `apt-cache search|show|policy`. `neofetch` reflects installed count.
|
|
12
|
+
>
|
|
13
|
+
> **`curl` / `wget` — pure `fetch()`** — host binary no longer spawned. Full flag support (`-o`, `-X`, `-d`, `-H`, `-s`, `-I`, `-L`, `-v` for curl; `-O`, `-P`, `-q` for wget). Zero host filesystem access.
|
|
14
|
+
>
|
|
15
|
+
> **New commands** — `which`, `type`, `man` (with built-in pages), `uptime`, `free`, `lsb_release`, `alias`, `unalias`.
|
|
16
|
+
>
|
|
17
|
+
> **`$(cmd)` command substitution** — `echo $(whoami)`, `mkdir /tmp/$(date +%s)`, nested subs, all work.
|
|
18
|
+
>
|
|
19
|
+
> **Alias expansion** — `alias ll='ls -la'` then `ll /etc` resolves transparently in the dispatcher.
|
|
20
|
+
>
|
|
21
|
+
> *— written by Claude because explaining every change manually is a waste of everyone's time*
|
|
22
|
+
|
|
23
|
+
---
|
|
4
24
|
|
|
5
25
|
[](https://www.npmjs.com/package/typescript-virtual-container)
|
|
6
26
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -23,10 +43,13 @@
|
|
|
23
43
|
- [VirtualFileSystem](#virtualfilesystem)
|
|
24
44
|
- [VFSB Binary Format](#vfsb-binary-format)
|
|
25
45
|
- [VirtualUserManager](#virtualusermanager)
|
|
46
|
+
- [VirtualPackageManager](#virtualpackagemanager)
|
|
26
47
|
- [HoneyPot](#honeypot)
|
|
27
48
|
- [SshClient](#sshclient-programmatic-api)
|
|
28
49
|
- [Key Types](#key-types)
|
|
29
50
|
- [Usage Examples](#usage-examples)
|
|
51
|
+
- [Linux Rootfs](#linux-rootfs)
|
|
52
|
+
- [Package Manager (apt/dpkg)](#package-manager-aptdpkg)
|
|
30
53
|
- [Built-in Commands](#built-in-commands)
|
|
31
54
|
- [Shell Scripting](#shell-scripting)
|
|
32
55
|
- [Configuration](#configuration)
|
|
@@ -57,7 +80,11 @@
|
|
|
57
80
|
- **`.bashrc` support**: Loaded automatically at interactive session start from `/home/<user>/.bashrc`.
|
|
58
81
|
- **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking.
|
|
59
82
|
- **Security Auditing**: Built-in `HoneyPot` utility for comprehensive activity logging, event tracking, statistics collection, and anomaly detection across all components.
|
|
60
|
-
- **
|
|
83
|
+
- **Linux rootfs on boot**: Realistic `/etc`, `/proc`, `/sys`, `/dev`, `/usr`, `/var` hierarchy populated at startup — `os-release`, `passwd`, `hosts`, `resolv.conf`, `/proc/meminfo`, `/proc/cpuinfo`, and more.
|
|
84
|
+
- **Virtual package manager**: `apt install`, `apt remove`, `apt search`, `dpkg -l`, `dpkg -s` — 25 packages in the built-in registry (vim, git, nodejs, python3, curl, openssh, gcc…). Writes files into VFS, tracks state in `/var/lib/dpkg/status`.
|
|
85
|
+
- **90+ Built-in Commands**: Full navigation, text processing, archiving, system info, package management, and user management commands — grouped and documented in the interactive `help` system.
|
|
86
|
+
- **`$(cmd)` command substitution**: Nested command execution in any argument position.
|
|
87
|
+
- **Alias support**: `alias`, `unalias` — persisted in session environment.
|
|
61
88
|
- **Full TypeScript Support**: Complete JSDoc coverage, exported types, and first-class async/await for all operations.
|
|
62
89
|
|
|
63
90
|
---
|
|
@@ -74,10 +101,10 @@
|
|
|
74
101
|
### What This Is Not
|
|
75
102
|
|
|
76
103
|
- Not a fully isolated container runtime.
|
|
77
|
-
- Not a security sandbox — the VFS does not sandbox host filesystem access by spawned child processes (e.g. `wget`, `curl` delegate to the host binary).
|
|
78
104
|
- Not a kernel-level isolation boundary (unlike Docker/VM-based isolation).
|
|
105
|
+
- Package stubs (e.g. `node`, `python3`) write files into the VFS and are visible to `which`/`dpkg -L`, but do not execute real binaries — the shell is pure TypeScript with no `execvp`.
|
|
79
106
|
|
|
80
|
-
This project emulates shell behavior for developer workflows. It is designed for realism and
|
|
107
|
+
This project emulates shell behavior for developer workflows. `curl` and `wget` use the native `fetch()` API (no host binary). All other network and execution primitives are simulated. It is designed for realism and deployability, not kernel-level security isolation.
|
|
81
108
|
|
|
82
109
|
---
|
|
83
110
|
|
|
@@ -382,12 +409,14 @@ new VirtualShell(
|
|
|
382
409
|
|
|
383
410
|
```typescript
|
|
384
411
|
interface ShellProperties {
|
|
385
|
-
kernel: string; //
|
|
386
|
-
os: string; //
|
|
387
|
-
arch: string; //
|
|
412
|
+
kernel: string; // Kernel version string — shown by uname, neofetch, /proc/version
|
|
413
|
+
os: string; // Full OS description — shown by neofetch, /etc/os-release, lsb_release
|
|
414
|
+
arch: string; // CPU architecture label — shown by uname -m, neofetch
|
|
388
415
|
}
|
|
389
416
|
```
|
|
390
417
|
|
|
418
|
+
Fields map directly to the values reported by `uname -a`, `neofetch`, `lsb_release`, `/etc/os-release`, and `/proc/version` inside the shell. Changing them after construction has no effect — pass them to the constructor.
|
|
419
|
+
|
|
391
420
|
**Example:**
|
|
392
421
|
|
|
393
422
|
```typescript
|
|
@@ -407,13 +436,26 @@ const shell = new VirtualShell("typescript-vm", {
|
|
|
407
436
|
|--------|-------------|
|
|
408
437
|
| `ensureInitialized(): Promise<void>` | Await this before using the shell programmatically. |
|
|
409
438
|
| `addCommand(name, params, callback)` | Register a custom shell command. |
|
|
410
|
-
| `executeCommand(rawInput, authUser, cwd)` | Run a raw command string. |
|
|
411
|
-
| `startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize)` |
|
|
412
|
-
| `writeFileAsUser(authUser, path, content)` | Write a file with quota enforcement. |
|
|
439
|
+
| `executeCommand(rawInput, authUser, cwd)` | Run a raw command string (supports `&&`, `\|`, `$(cmd)`, aliases). |
|
|
440
|
+
| `startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize)` | Attach an interactive PTY session to this shell. |
|
|
441
|
+
| `writeFileAsUser(authUser, path, content)` | Write a file on behalf of a user with quota enforcement. |
|
|
442
|
+
| `refreshProcFs(): void` | Refresh `/proc/uptime`, `/proc/meminfo`, `/proc/cpuinfo`, etc. from live host data. |
|
|
443
|
+
| `syncPasswd(): void` | Sync `/etc/passwd`, `/etc/group`, `/etc/shadow` from `VirtualUserManager` state. |
|
|
413
444
|
| `getVfs(): VirtualFileSystem \| null` | Access the VFS instance. |
|
|
414
445
|
| `getUsers(): VirtualUserManager \| null` | Access the user manager. |
|
|
415
446
|
| `getHostname(): string` | Returns the configured hostname. |
|
|
416
447
|
|
|
448
|
+
#### Public fields
|
|
449
|
+
|
|
450
|
+
| Field | Type | Description |
|
|
451
|
+
|-------|------|-------------|
|
|
452
|
+
| `vfs` | `VirtualFileSystem` | Backing virtual filesystem — use for direct path operations. |
|
|
453
|
+
| `users` | `VirtualUserManager` | Virtual user database — auth, quotas, and session tracking. |
|
|
454
|
+
| `packageManager` | `VirtualPackageManager` | APT/dpkg package manager backed by the built-in registry. |
|
|
455
|
+
| `hostname` | `string` | Hostname shown in the shell prompt and SSH ident string. |
|
|
456
|
+
| `properties` | `ShellProperties` | Distro identity strings surfaced by `uname`, `neofetch`, etc. |
|
|
457
|
+
| `startTime` | `number` | Unix ms timestamp of shell creation — used by `uptime` and `/proc/uptime`. |
|
|
458
|
+
|
|
417
459
|
**Custom command example:**
|
|
418
460
|
|
|
419
461
|
```typescript
|
|
@@ -665,25 +707,27 @@ new VirtualUserManager(
|
|
|
665
707
|
|--------|-------------|
|
|
666
708
|
| `initialize(): Promise<void>` | Load users/sudoers from VFS, ensure root exists. Call once on startup. |
|
|
667
709
|
| `verifyPassword(username, password): boolean` | Check plaintext password against stored hash. |
|
|
668
|
-
| `hasPassword(username): boolean` | Returns true if a password is set for the user. |
|
|
669
|
-
| `hashPassword(password): string` | Hash a password using
|
|
710
|
+
| `hasPassword(username): boolean` | Returns `true` if a non-empty password is set for the user. |
|
|
711
|
+
| `hashPassword(password): string` | Hash a password using scrypt (or SHA-256 with `SSH_MIMIC_FAST_PASSWORD_HASH=1`). |
|
|
712
|
+
| `getPasswordHash(username): string \| null` | Returns the raw stored hash for a user, or `null` if not found. |
|
|
670
713
|
| `addUser(username, password): Promise<void>` | Create user with home directory. |
|
|
671
|
-
| `deleteUser(username): Promise<void>` | Delete user.
|
|
672
|
-
| `setPassword(username, password): Promise<void>` | Update password for an existing user. |
|
|
673
|
-
| `isSudoer(username): boolean` |
|
|
674
|
-
| `addSudoer(username): Promise<void>` | Grant sudo privileges. |
|
|
675
|
-
| `removeSudoer(username): Promise<void>` | Revoke sudo privileges.
|
|
714
|
+
| `deleteUser(username): Promise<void>` | Delete user. Throws when user is `root` or does not exist. |
|
|
715
|
+
| `setPassword(username, password): Promise<void>` | Update password for an existing user. Throws when user does not exist. |
|
|
716
|
+
| `isSudoer(username): boolean` | Returns `true` when the user has sudo privileges. |
|
|
717
|
+
| `addSudoer(username): Promise<void>` | Grant sudo privileges. Throws when user does not exist. |
|
|
718
|
+
| `removeSudoer(username): Promise<void>` | Revoke sudo privileges. Throws when username is `root`. |
|
|
676
719
|
| `setQuotaBytes(username, maxBytes): Promise<void>` | Set per-user write quota (bytes under `/home/<user>`). |
|
|
677
|
-
| `clearQuota(username): Promise<void>` | Remove quota limit. |
|
|
678
|
-
| `getQuotaBytes(username): number \| null` | Returns quota in bytes, or null if unlimited. |
|
|
720
|
+
| `clearQuota(username): Promise<void>` | Remove quota limit for a user. |
|
|
721
|
+
| `getQuotaBytes(username): number \| null` | Returns quota in bytes, or `null` if unlimited. |
|
|
679
722
|
| `getUsageBytes(username): number` | Returns current usage in bytes under `/home/<user>`. |
|
|
680
723
|
| `assertWriteWithinQuota(username, path, content)` | Throws if the write would exceed the user's quota. |
|
|
724
|
+
| `listUsers(): string[]` | Returns a sorted list of all registered usernames. |
|
|
681
725
|
| `addAuthorizedKey(username, algo, data)` | Register an SSH public key for the user. |
|
|
682
726
|
| `getAuthorizedKeys(username)` | Returns the list of authorized keys for a user. |
|
|
683
727
|
| `removeAuthorizedKeys(username)` | Revoke all authorized keys for a user. |
|
|
684
|
-
| `registerSession(username, remoteAddress): VirtualActiveSession` |
|
|
685
|
-
| `unregisterSession(sessionId): void` |
|
|
686
|
-
| `updateSession(sessionId, username, remoteAddress): void` | Update session metadata
|
|
728
|
+
| `registerSession(username, remoteAddress): VirtualActiveSession` | Register an active session and allocate a virtual TTY. |
|
|
729
|
+
| `unregisterSession(sessionId): void` | Remove a session record on disconnect. Safe to call with `null`. |
|
|
730
|
+
| `updateSession(sessionId, username, remoteAddress): void` | Update session metadata after `su`/`sudo` identity change. |
|
|
687
731
|
| `listActiveSessions(): VirtualActiveSession[]` | Returns all active sessions sorted by start time. |
|
|
688
732
|
|
|
689
733
|
#### Events
|
|
@@ -712,6 +756,54 @@ users.on("session:register", ({ sessionId, username, remoteAddress }) => {
|
|
|
712
756
|
|
|
713
757
|
---
|
|
714
758
|
|
|
759
|
+
|
|
760
|
+
### `VirtualPackageManager`
|
|
761
|
+
|
|
762
|
+
Simulates APT/dpkg package management backed by a built-in registry. Accessed via `shell.packageManager`.
|
|
763
|
+
|
|
764
|
+
#### Constructor
|
|
765
|
+
|
|
766
|
+
Instantiated automatically by `VirtualShell`. Not constructed directly.
|
|
767
|
+
|
|
768
|
+
#### Methods
|
|
769
|
+
|
|
770
|
+
| Method | Description |
|
|
771
|
+
|--------|-------------|
|
|
772
|
+
| `load()` | Load installed packages from `/var/lib/dpkg/status` (called on shell init) |
|
|
773
|
+
| `install(names, opts?)` | Install packages (resolves deps, writes files to VFS). Returns `{ output, exitCode }` |
|
|
774
|
+
| `remove(names, opts?)` | Remove packages. `opts.purge` also removes config files |
|
|
775
|
+
| `search(term)` | Search available packages by name or description |
|
|
776
|
+
| `show(name)` | Show dpkg-style metadata block for a package |
|
|
777
|
+
| `listInstalled()` | List all installed packages as `InstalledPackage[]` |
|
|
778
|
+
| `listAvailable()` | List all packages in the registry |
|
|
779
|
+
| `isInstalled(name)` | Returns `true` if package is installed |
|
|
780
|
+
| `installedCount()` | Count of installed packages (used by `neofetch`) |
|
|
781
|
+
| `findInRegistry(name)` | Look up a `PackageDefinition` by name |
|
|
782
|
+
|
|
783
|
+
#### Types
|
|
784
|
+
|
|
785
|
+
```ts
|
|
786
|
+
interface PackageDefinition {
|
|
787
|
+
name: string;
|
|
788
|
+
version: string;
|
|
789
|
+
description: string;
|
|
790
|
+
files?: PackageFile[]; // written to VFS on install
|
|
791
|
+
depends?: string[]; // resolved recursively
|
|
792
|
+
onInstall?: (vfs, users) => void;
|
|
793
|
+
onRemove?: (vfs) => void;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
interface InstalledPackage {
|
|
797
|
+
name: string;
|
|
798
|
+
version: string;
|
|
799
|
+
architecture: string;
|
|
800
|
+
installedAt: string; // ISO-8601
|
|
801
|
+
files: string[]; // paths written to VFS
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
715
807
|
### `HoneyPot`
|
|
716
808
|
|
|
717
809
|
Comprehensive security auditing and event tracking utility. Attaches listeners to all core components to log activity, track statistics, and detect anomalies.
|
|
@@ -966,6 +1058,49 @@ interface VfsSnapshot {
|
|
|
966
1058
|
|
|
967
1059
|
---
|
|
968
1060
|
|
|
1061
|
+
### Command Helpers
|
|
1062
|
+
|
|
1063
|
+
Three utility functions are exported from `typescript-virtual-container` to assist with argument parsing inside custom command handlers.
|
|
1064
|
+
|
|
1065
|
+
#### `ifFlag(args, flags): boolean`
|
|
1066
|
+
|
|
1067
|
+
Returns `true` when any of the given flags appear in the argument array. Matches both standalone tokens (`-s`, `--silent`) and inline forms (`--output=file`).
|
|
1068
|
+
|
|
1069
|
+
```typescript
|
|
1070
|
+
import { ifFlag } from "typescript-virtual-container";
|
|
1071
|
+
|
|
1072
|
+
const recursive = ifFlag(args, ["-r", "--recursive"]);
|
|
1073
|
+
const silent = ifFlag(args, "-s");
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
#### `getFlag(args, flags): string | true | undefined`
|
|
1077
|
+
|
|
1078
|
+
Returns the value of a flag, `true` if the flag has no value, or `undefined` if absent.
|
|
1079
|
+
|
|
1080
|
+
```typescript
|
|
1081
|
+
import { getFlag } from "typescript-virtual-container";
|
|
1082
|
+
|
|
1083
|
+
const output = getFlag(args, ["-o", "--output"]);
|
|
1084
|
+
// args = ["--output", "file.txt"] → "file.txt"
|
|
1085
|
+
// args = ["--output=file.txt"] → "file.txt"
|
|
1086
|
+
// args = ["--verbose"] → true (when "verbose" is in flags list)
|
|
1087
|
+
// args = [] → undefined
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
#### `getArg(args, index, options?): string | undefined`
|
|
1091
|
+
|
|
1092
|
+
Returns the positional argument at a given zero-based index, skipping known flags and their values.
|
|
1093
|
+
|
|
1094
|
+
```typescript
|
|
1095
|
+
import { getArg } from "typescript-virtual-container";
|
|
1096
|
+
|
|
1097
|
+
// args = ["-r", "src", "dest"]
|
|
1098
|
+
const src = getArg(args, 0, { flags: ["-r"] }); // "src"
|
|
1099
|
+
const dest = getArg(args, 1, { flags: ["-r"] }); // "dest"
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
---
|
|
1103
|
+
|
|
969
1104
|
## Usage Examples
|
|
970
1105
|
|
|
971
1106
|
### Example 1: Basic SSH Server
|
|
@@ -1316,6 +1451,145 @@ const [r1, r2] = await Promise.all([
|
|
|
1316
1451
|
]);
|
|
1317
1452
|
```
|
|
1318
1453
|
|
|
1454
|
+
---
|
|
1455
|
+
|
|
1456
|
+
## Linux Rootfs
|
|
1457
|
+
|
|
1458
|
+
On every `VirtualShell` init, a realistic Linux directory hierarchy is bootstrapped into the VFS. All paths are created idempotently — FS-mode snapshots survive restarts without duplication.
|
|
1459
|
+
|
|
1460
|
+
### Directory layout
|
|
1461
|
+
|
|
1462
|
+
```
|
|
1463
|
+
/
|
|
1464
|
+
├── bin -> /usr/bin (symlink, Debian-style)
|
|
1465
|
+
├── dev/ null, zero, random, urandom, pts/, shm/
|
|
1466
|
+
├── etc/
|
|
1467
|
+
│ ├── apt/sources.list Fortune package sources
|
|
1468
|
+
│ ├── debian_version
|
|
1469
|
+
│ ├── group synced from VirtualUserManager
|
|
1470
|
+
│ ├── hostname
|
|
1471
|
+
│ ├── hosts 127.0.0.1 localhost + VM hostname
|
|
1472
|
+
│ ├── motd
|
|
1473
|
+
│ ├── os-release NAME="Fortune GNU/Linux" + ShellProperties
|
|
1474
|
+
│ ├── passwd synced from VirtualUserManager
|
|
1475
|
+
│ ├── resolv.conf 1.1.1.1 + 8.8.8.8
|
|
1476
|
+
│ └── shadow (mode 0o640)
|
|
1477
|
+
├── proc/
|
|
1478
|
+
│ ├── cpuinfo real host CPU info
|
|
1479
|
+
│ ├── loadavg
|
|
1480
|
+
│ ├── meminfo real host memory
|
|
1481
|
+
│ ├── net/dev eth0 + lo
|
|
1482
|
+
│ ├── uptime shell uptime in seconds
|
|
1483
|
+
│ └── version kernel from ShellProperties
|
|
1484
|
+
├── root/ root home + .bashrc
|
|
1485
|
+
├── sys/devices/virtual/dmi/id/
|
|
1486
|
+
│ ├── sys_vendor "Fortune Systems"
|
|
1487
|
+
│ └── product_name "VirtualContainer v1"
|
|
1488
|
+
├── tmp/ (mode 0o1777 sticky)
|
|
1489
|
+
├── usr/bin/ stubs for all built-in commands
|
|
1490
|
+
├── var/
|
|
1491
|
+
│ ├── lib/dpkg/status managed by VirtualPackageManager
|
|
1492
|
+
│ └── log/ syslog, auth.log, dpkg.log, apt/
|
|
1493
|
+
└── ...
|
|
1494
|
+
```
|
|
1495
|
+
|
|
1496
|
+
### API
|
|
1497
|
+
|
|
1498
|
+
```ts
|
|
1499
|
+
shell.refreshProcFs(); // refresh /proc/* with current system state
|
|
1500
|
+
shell.syncPasswd(); // sync /etc/passwd|group|shadow from VirtualUserManager
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
`syncPasswd()` is called automatically on `bootstrapLinuxRootfs`. Call it again after `users.addUser()` / `users.deleteUser()` to keep `/etc/passwd` consistent.
|
|
1504
|
+
|
|
1505
|
+
---
|
|
1506
|
+
|
|
1507
|
+
## Package Manager (apt/dpkg)
|
|
1508
|
+
|
|
1509
|
+
A pure-TypeScript APT/dpkg simulation backed by a built-in package registry. No external process is spawned.
|
|
1510
|
+
|
|
1511
|
+
### Workflow
|
|
1512
|
+
|
|
1513
|
+
```
|
|
1514
|
+
apt install <pkg> → resolves deps → writes files to VFS → updates /var/lib/dpkg/status
|
|
1515
|
+
apt remove <pkg> → removes VFS files → updates status
|
|
1516
|
+
dpkg -l → reads installed packages from VirtualPackageManager
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1519
|
+
### Built-in package registry (25 packages)
|
|
1520
|
+
|
|
1521
|
+
| Package | Version | Section |
|
|
1522
|
+
|---------|---------|---------|
|
|
1523
|
+
| `vim` | 2:9.0.1378-2 | editors |
|
|
1524
|
+
| `git` | 1:2.39.2-1 | vcs |
|
|
1525
|
+
| `python3` | 3.11.2-1 | python |
|
|
1526
|
+
| `nodejs` | 18.19.0 | javascript |
|
|
1527
|
+
| `npm` | 9.2.0 | javascript |
|
|
1528
|
+
| `curl` | 7.88.1-10 | web |
|
|
1529
|
+
| `wget` | 1.21.3-1 | web |
|
|
1530
|
+
| `htop` | 3.2.2-2 | utils |
|
|
1531
|
+
| `openssh-client` | 1:9.2p1-2 | net |
|
|
1532
|
+
| `openssh-server` | 1:9.2p1-2 | net |
|
|
1533
|
+
| `net-tools` | 2.10-0.1 | net |
|
|
1534
|
+
| `iputils-ping` | 3:20221126-1 | net |
|
|
1535
|
+
| `jq` | 1.6-2.1 | utils |
|
|
1536
|
+
| `build-essential` | 12.9 | devel |
|
|
1537
|
+
| `gcc` | 4:12.2.0-3 | devel |
|
|
1538
|
+
| `g++` | 4:12.2.0-3 | devel |
|
|
1539
|
+
| `make` | 4.3-4.1 | devel |
|
|
1540
|
+
| `less` | 590-2 | text |
|
|
1541
|
+
| `unzip` | 6.0-28 | utils |
|
|
1542
|
+
| `rsync` | 3.2.7-1 | net |
|
|
1543
|
+
| `tmux` | 3.3a-3 | utils |
|
|
1544
|
+
| `tree` | 2.1.0-1 | utils |
|
|
1545
|
+
| `ca-certificates` | 20230311 | misc |
|
|
1546
|
+
| `sudo` | 1.9.13p3-1 | admin |
|
|
1547
|
+
| `systemd` | 252.22-1 | admin |
|
|
1548
|
+
|
|
1549
|
+
> **Note on package stubs**: installing `nodejs` or `python3` writes stubs into `/usr/bin/` and makes them discoverable via `which` and `dpkg -L`. The stubs print a version line but do not execute real binaries — the shell runtime is pure TypeScript with no `execvp`. This makes them useful for testing install workflows and `which`/`dpkg -L` queries, not for running actual scripts.
|
|
1550
|
+
|
|
1551
|
+
### `VirtualPackageManager` API
|
|
1552
|
+
|
|
1553
|
+
```ts
|
|
1554
|
+
// Access via shell instance
|
|
1555
|
+
const pm = shell.packageManager;
|
|
1556
|
+
|
|
1557
|
+
pm.install(["vim", "git"], { quiet: false }) // → { output, exitCode }
|
|
1558
|
+
pm.remove(["vim"], { purge: false }) // → { output, exitCode }
|
|
1559
|
+
pm.search("editor") // → PackageDefinition[]
|
|
1560
|
+
pm.show("vim") // → string (dpkg-style block)
|
|
1561
|
+
pm.listInstalled() // → InstalledPackage[]
|
|
1562
|
+
pm.listAvailable() // → PackageDefinition[]
|
|
1563
|
+
pm.isInstalled("vim") // → boolean
|
|
1564
|
+
pm.installedCount() // → number
|
|
1565
|
+
pm.findInRegistry("nodejs") // → PackageDefinition | undefined
|
|
1566
|
+
```
|
|
1567
|
+
|
|
1568
|
+
### Registering custom packages
|
|
1569
|
+
|
|
1570
|
+
```ts
|
|
1571
|
+
import { VirtualPackageManager } from "typescript-virtual-container";
|
|
1572
|
+
|
|
1573
|
+
// Custom packages can be added to the registry before shell init
|
|
1574
|
+
// by extending PACKAGE_REGISTRY or by calling install() with custom defs.
|
|
1575
|
+
|
|
1576
|
+
// Or: register a post-install hook via onInstall
|
|
1577
|
+
const customPkg = {
|
|
1578
|
+
name: "myapp",
|
|
1579
|
+
version: "1.0.0",
|
|
1580
|
+
description: "My application",
|
|
1581
|
+
files: [
|
|
1582
|
+
{ path: "/usr/bin/myapp", content: "#!/bin/sh\necho myapp v1.0.0\n", mode: 0o755 },
|
|
1583
|
+
{ path: "/etc/myapp/config.json", content: JSON.stringify({ port: 3000 }) },
|
|
1584
|
+
],
|
|
1585
|
+
onInstall: (vfs) => {
|
|
1586
|
+
vfs.mkdir("/var/lib/myapp", 0o755);
|
|
1587
|
+
vfs.mkdir("/var/log/myapp", 0o755);
|
|
1588
|
+
},
|
|
1589
|
+
};
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
|
|
1319
1593
|
---
|
|
1320
1594
|
|
|
1321
1595
|
## Built-in Commands
|
|
@@ -1327,7 +1601,7 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
|
|
|
1327
1601
|
| Command | Flags | Description |
|
|
1328
1602
|
|---------|-------|-------------|
|
|
1329
1603
|
| `cd <path>` | | Change directory |
|
|
1330
|
-
| `ls [path]` | `-l` | List directory contents |
|
|
1604
|
+
| `ls [path]` | `-l` `-a` | List directory contents (`-a` shows dotfiles) |
|
|
1331
1605
|
| `pwd` | | Print working directory |
|
|
1332
1606
|
| `tree [path]` | | ASCII directory tree |
|
|
1333
1607
|
|
|
@@ -1335,8 +1609,8 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
|
|
|
1335
1609
|
|
|
1336
1610
|
| Command | Flags | Description |
|
|
1337
1611
|
|---------|-------|-------------|
|
|
1338
|
-
| `cat <path
|
|
1339
|
-
| `chmod <mode> <file>` | | Change file permissions (
|
|
1612
|
+
| `cat <path...>` | `-n` `-b` | Concatenate and print files; `-n` numbers all lines, `-b` numbers non-blank lines |
|
|
1613
|
+
| `chmod <mode> <file>` | | Change file permissions — octal (`755`) or symbolic (`+x`, `u+x`, `go-w`, `a=rx`) |
|
|
1340
1614
|
| `cp <src> <dest>` | `-r` | Copy file or directory |
|
|
1341
1615
|
| `find [path]` | `-name <pat>` `-type f\|d` | Search for files |
|
|
1342
1616
|
| `ln <target> <link>` | `-s` | Create hard or symbolic link |
|
|
@@ -1384,9 +1658,12 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
|
|
|
1384
1658
|
| `htop` | | System monitor (mock) |
|
|
1385
1659
|
| `id [user]` | | Print user identity (uid/gid/groups) |
|
|
1386
1660
|
| `kill [-9] <pid>` | | Send signal to process (mock) |
|
|
1387
|
-
| `
|
|
1661
|
+
| `free` | `-h` `-m` `-g` | Display free and used memory |
|
|
1662
|
+
| `lsb_release` | `-a` `-i` `-d` `-r` `-c` | Print distribution information |
|
|
1663
|
+
| `neofetch` | | System info display (shows real packages/uptime) |
|
|
1664
|
+
| `uptime` | `-p` `-s` | Tell how long the system has been running |
|
|
1388
1665
|
| `ping [-c <n>] <host>` | | Send ICMP ECHO_REQUEST (mock) |
|
|
1389
|
-
| `ps` | `-a` `-u` `-x` | Report process status |
|
|
1666
|
+
| `ps` | `-a` `-u` `-x` `aux` | Report process status; `-u` / `aux` shows USER/PID/%CPU/%MEM columns |
|
|
1390
1667
|
| `sleep <seconds>` | | Delay execution |
|
|
1391
1668
|
| `uname` | `-a` `-r` `-m` | Print system information |
|
|
1392
1669
|
| `who` | | List active sessions |
|
|
@@ -1396,23 +1673,50 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
|
|
|
1396
1673
|
|
|
1397
1674
|
| Command | Flags | Description |
|
|
1398
1675
|
|---------|-------|-------------|
|
|
1399
|
-
| `curl <url>` | | HTTP client (
|
|
1400
|
-
| `wget <url>` | | File downloader (
|
|
1676
|
+
| `curl <url>` | `-o` `-X` `-d` `-H` `-s` `-I` `-L` `-v` | HTTP client (pure `fetch()`, no host binary) |
|
|
1677
|
+
| `wget <url>` | `-O` `-P` `-q` | File downloader (pure `fetch()`, no host binary) |
|
|
1401
1678
|
|
|
1402
1679
|
### Shell
|
|
1403
1680
|
|
|
1404
1681
|
| Command | Flags | Description |
|
|
1405
1682
|
|---------|-------|-------------|
|
|
1683
|
+
| `alias [name=value]` | | Define or display aliases |
|
|
1406
1684
|
| `clear` | | Clear terminal screen (full ANSI reset) |
|
|
1407
|
-
| `echo <text>` | | Display text |
|
|
1685
|
+
| `echo <text>` | `-n` `-e` | Display text; `-n` suppresses newline, `-e` interprets escape sequences (`\n`, `\t`, `\r`, `\\`) |
|
|
1408
1686
|
| `env` | | Print session environment variables |
|
|
1409
1687
|
| `exit [code]` | | Exit session |
|
|
1410
1688
|
| `export NAME=VALUE` | | Set shell variable in current session |
|
|
1411
1689
|
| `help [command]` | | List commands (grouped) or show command details |
|
|
1412
1690
|
| `set [VAR=val]` | | Display or set shell variables |
|
|
1413
|
-
| `sh` | `-c <script>` `[file]` | Execute shell script (supports if/for/while) |
|
|
1691
|
+
| `sh` | `-c <script>` `[file]` | Execute shell script (supports if/for/while/do/done); `$(cmd)` substitution inside single-quoted args is preserved |
|
|
1692
|
+
| `history [n]` | | Display command history (last N entries) |
|
|
1693
|
+
| `source <file>` | | Execute file in current shell environment (aliases and exports persist) |
|
|
1694
|
+
| `. <file>` | | Alias for `source` |
|
|
1695
|
+
| `test <expr>` | | Evaluate conditional expression |
|
|
1696
|
+
| `[ <expr> ]` | | Alias for `test` — supports `-f`, `-d`, `-e`, `-z`, `-n`, `=`, `!=`, `-eq`, `-lt`, `-gt`, etc. |
|
|
1697
|
+
| `unalias <name>` | `-a` | Remove alias definitions |
|
|
1414
1698
|
| `unset <VAR>` | | Remove shell variable |
|
|
1415
1699
|
|
|
1700
|
+
### Package Management
|
|
1701
|
+
|
|
1702
|
+
| Command | Flags | Description |
|
|
1703
|
+
|---------|-------|-------------|
|
|
1704
|
+
| `apt <cmd> [pkg...]` | | Package manager (`install`, `remove`, `purge`, `update`, `upgrade`, `search`, `show`, `list`) |
|
|
1705
|
+
| `apt-get <cmd>` | | Alias for `apt` |
|
|
1706
|
+
| `apt-cache <cmd>` | | Query package cache (`search`, `show`, `policy`) |
|
|
1707
|
+
| `dpkg` | `-l` `-s` `-L` `-r` `-P` | Debian package manager low-level tool |
|
|
1708
|
+
| `dpkg-query` | `-W` `-l` | Show information about installed packages |
|
|
1709
|
+
|
|
1710
|
+
### Shell (extended)
|
|
1711
|
+
|
|
1712
|
+
| Command | Flags | Description |
|
|
1713
|
+
|---------|-------|-------------|
|
|
1714
|
+
| `alias [name=value]` | | Define or display aliases |
|
|
1715
|
+
| `man <command>` | | Display command manual page |
|
|
1716
|
+
| `type <command>` | | Describe how command is interpreted |
|
|
1717
|
+
| `unalias <name>` | `-a` | Remove alias definitions |
|
|
1718
|
+
| `which <command>` | | Locate command in PATH |
|
|
1719
|
+
|
|
1416
1720
|
### Users & Permissions
|
|
1417
1721
|
|
|
1418
1722
|
| Command | Flags | Description |
|
|
@@ -1789,9 +2093,32 @@ MIT — see [LICENSE](./LICENSE).
|
|
|
1789
2093
|
- [x] New commands: `sort`, `uniq`, `tee`, `cut`, `tr`, `xargs`, `diff`, `sed`, `awk`, `tar`, `gzip`, `gunzip`, `base64`, `date`, `sleep`, `id`, `groups`, `uname`, `ps`, `kill`, `df`, `du`, `ping`
|
|
1790
2094
|
- [x] Structured event hooks (session open/close, file write, sudo challenge)
|
|
1791
2095
|
- [x] Binary snapshot format (VFSB) — replaces JSON+base64, ~27% smaller, no string parsing overhead, backward-compatible JSON migration
|
|
2096
|
+
- [x] Linux rootfs on boot — `/etc`, `/proc`, `/sys`, `/dev`, `/usr`, `/var` populated at init; `os-release`, `passwd`, `hosts`, `/proc/meminfo`, `/proc/cpuinfo`, `/proc/version`, `/proc/uptime`, `/sys/devices/virtual/dmi/`, symlinks `/bin`→`/usr/bin`
|
|
2097
|
+
- [x] Virtual package manager — `apt install/remove/purge/update/upgrade/search/show/list`, `apt-get`, `apt-cache`, `dpkg`, `dpkg-query`; 25 packages in registry; writes files to VFS; `/var/lib/dpkg/status` persistence
|
|
2098
|
+
- [x] `curl` / `wget` reimplemented as pure `fetch()` — no host binary spawned, full isolation
|
|
2099
|
+
- [x] New commands: `which`, `type`, `man`, `uptime`, `free`, `lsb_release`, `alias`, `unalias`
|
|
2100
|
+
- [x] `$(cmd)` command substitution in variable expansion
|
|
2101
|
+
- [x] Alias expansion in command dispatch
|
|
2102
|
+
- [x] `neofetch` shows real package count and shell uptime
|
|
2103
|
+
- [x] `syncPasswd()` / `refreshProcFs()` public API on `VirtualShell`
|
|
2104
|
+
- [x] `test` / `[` — full POSIX conditional expressions (`-f`, `-d`, `-e`, `-z`, `-n`, `-x`, `-s`, `-L`, `=`, `!=`, `-eq`, `-lt`, `-gt`, `-le`, `-ge`, `!`, `-a`, `-o`)
|
|
2105
|
+
- [x] `source` / `.` — execute file in current shell env (aliases and exports persist across commands)
|
|
2106
|
+
- [x] `history [n]` — display command history from VFS `.bash_history`
|
|
2107
|
+
- [x] `echo -e` / `echo -n` — escape sequence interpretation and newline suppression
|
|
2108
|
+
- [x] `ls -a` — show dotfiles
|
|
2109
|
+
- [x] `chmod` symbolic modes — `+x`, `u+x`, `go-w`, `a=rx`, comma-separated
|
|
2110
|
+
- [x] `cat -n` / `-b` — line numbering, multi-file concatenation, stdin support
|
|
2111
|
+
- [x] `ping -c N` — respects packet count flag
|
|
2112
|
+
- [x] `ps -u` / `ps aux` — extended format with USER/PID/%CPU/%MEM/VSZ/RSS columns
|
|
2113
|
+
- [x] `$(cmd)` in single-quoted args preserved — `sh -c 'echo $(whoami)'` now works correctly
|
|
2114
|
+
- [x] Pipeline executor: `runCommandDirect` — args with `;`, `|`, `>` no longer re-parsed
|
|
2115
|
+
- [x] `>>` append redirect fixed — was broken because `echo` lacked terminal newline
|
|
2116
|
+
- [x] `export A=1 && echo $A` — env vars visible to subsequent commands in same pipeline
|
|
2117
|
+
- [x] `echo` uses session `env.vars` (not stale global store)
|
|
1792
2118
|
- [ ] Snapshot diff tooling for test assertions
|
|
1793
2119
|
- [ ] WebSocket-based remote shell client (experimental)
|
|
1794
|
-
- [ ]
|
|
2120
|
+
- [ ] Package stubs that simulate REPL behavior (node, python3)
|
|
2121
|
+
- [ ] `/proc/self` and `/proc/<pid>` per-session process entries
|
|
1795
2122
|
|
|
1796
2123
|
---
|
|
1797
2124
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runCommandDirect } from "../commands";
|
|
2
2
|
import { resolvePath } from "../commands/helpers";
|
|
3
3
|
// ── Script executor (handles &&/||/;) ────────────────────────────────────────
|
|
4
4
|
export async function executeScript(script, authUser, hostname, mode, cwd, shell, env) {
|
|
@@ -71,8 +71,7 @@ async function executeSingleCommandWithRedirections(cmd, authUser, hostname, mod
|
|
|
71
71
|
return { stderr: `${cmd.inputFile}: No such file or directory`, exitCode: 1 };
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
const
|
|
75
|
-
const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin, env);
|
|
74
|
+
const result = await runCommandDirect(cmd.name, cmd.args, authUser, hostname, mode, cwd, shell, stdin, env);
|
|
76
75
|
if (cmd.outputFile) {
|
|
77
76
|
const outputPath = resolvePath(cwd, cmd.outputFile);
|
|
78
77
|
const output = result.stdout || "";
|
|
@@ -111,8 +110,7 @@ async function executePipelineChain(commands, authUser, hostname, mode, cwd, she
|
|
|
111
110
|
return { stderr: `${cmd.inputFile}: No such file or directory`, exitCode: 1 };
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
|
-
const
|
|
115
|
-
const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, currentOutput, env);
|
|
113
|
+
const result = await runCommandDirect(cmd.name, cmd.args, authUser, hostname, mode, cwd, shell, currentOutput, env);
|
|
116
114
|
exitCode = result.exitCode ?? 0;
|
|
117
115
|
if (i === commands.length - 1 && cmd.outputFile) {
|
|
118
116
|
const outputPath = resolvePath(cwd, cmd.outputFile);
|