typescript-virtual-container 1.3.4 → 1.4.1
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 +674 -1504
- package/benchmark-results.txt +21 -21
- package/builds/self-standalone.js +282 -332
- package/builds/self-standalone.js.map +4 -4
- package/builds/standalone-wo-sftp.js +218 -282
- package/builds/standalone-wo-sftp.js.map +4 -4
- package/builds/standalone.js +271 -335
- package/builds/standalone.js.map +4 -4
- package/builds/web-full-api.min.js +3 -3
- package/builds/web-full-api.min.js.map +4 -4
- package/builds/web.min.js +2 -2
- package/builds/web.min.js.map +4 -4
- package/bun.lock +14 -12
- package/dist/SSHClient/index.d.ts.map +1 -1
- package/dist/SSHClient/index.js +5 -3
- package/dist/SSHMimic/executor.d.ts +1 -3
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +20 -22
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +5 -3
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +26 -21
- package/dist/VirtualPackageManager/index.d.ts.map +1 -1
- package/dist/VirtualPackageManager/index.js +29 -1
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +25 -3
- package/dist/VirtualShell/shellParser.d.ts +1 -8
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualShell/shellParser.js +2 -81
- package/dist/VirtualUserManager/index.d.ts +7 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +47 -16
- package/dist/commands/adduser.d.ts +10 -4
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +75 -12
- package/dist/commands/alias.d.ts +5 -0
- package/dist/commands/alias.d.ts.map +1 -1
- package/dist/commands/alias.js +5 -0
- package/dist/commands/apt.d.ts +5 -0
- package/dist/commands/apt.d.ts.map +1 -1
- package/dist/commands/apt.js +5 -0
- package/dist/commands/awk.d.ts +10 -8
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +156 -28
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +0 -3
- package/dist/commands/clear.d.ts +5 -0
- package/dist/commands/clear.d.ts.map +1 -1
- package/dist/commands/clear.js +5 -0
- package/dist/commands/command-helpers.d.ts.map +1 -1
- package/dist/commands/command-helpers.js +8 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +2 -1
- package/dist/commands/declare.d.ts +5 -0
- package/dist/commands/declare.d.ts.map +1 -1
- package/dist/commands/declare.js +5 -0
- package/dist/commands/deluser.d.ts +12 -0
- package/dist/commands/deluser.d.ts.map +1 -1
- package/dist/commands/deluser.js +72 -6
- package/dist/commands/df.d.ts +5 -0
- package/dist/commands/df.d.ts.map +1 -1
- package/dist/commands/df.js +5 -0
- package/dist/commands/du.d.ts +5 -0
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +5 -0
- package/dist/commands/export.d.ts +5 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +22 -4
- package/dist/commands/groups.d.ts +5 -0
- package/dist/commands/groups.d.ts.map +1 -1
- package/dist/commands/groups.js +5 -0
- package/dist/commands/gzip.d.ts +5 -2
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +54 -28
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +12 -3
- package/dist/commands/htop.d.ts +5 -0
- package/dist/commands/htop.d.ts.map +1 -1
- package/dist/commands/htop.js +5 -0
- package/dist/commands/kill.d.ts +5 -0
- package/dist/commands/kill.d.ts.map +1 -1
- package/dist/commands/kill.js +5 -0
- package/dist/commands/ln.d.ts +2 -0
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +22 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +15 -0
- package/dist/commands/lsb-release.d.ts +5 -0
- package/dist/commands/lsb-release.d.ts.map +1 -1
- package/dist/commands/lsb-release.js +5 -0
- package/dist/commands/man.d.ts.map +1 -1
- package/dist/commands/man.js +30 -136
- package/dist/commands/mkdir.d.ts +5 -0
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +5 -0
- package/dist/commands/mv.d.ts +5 -0
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +5 -0
- package/dist/commands/nano.d.ts +5 -0
- package/dist/commands/nano.d.ts.map +1 -1
- package/dist/commands/nano.js +5 -0
- package/dist/commands/neofetch.d.ts +5 -0
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +14 -5
- package/dist/commands/passwd.d.ts +8 -0
- package/dist/commands/passwd.d.ts.map +1 -1
- package/dist/commands/passwd.js +32 -11
- package/dist/commands/ping.d.ts +5 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +5 -0
- package/dist/commands/printf.d.ts +5 -0
- package/dist/commands/printf.d.ts.map +1 -1
- package/dist/commands/printf.js +43 -12
- package/dist/commands/ps.d.ts +5 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +5 -0
- package/dist/commands/read.d.ts +5 -0
- package/dist/commands/read.d.ts.map +1 -1
- package/dist/commands/read.js +5 -0
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +4 -1
- package/dist/commands/rm.d.ts +5 -0
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +5 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +1 -57
- package/dist/commands/sed.d.ts +5 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +5 -0
- package/dist/commands/set.d.ts +5 -6
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +5 -22
- package/dist/commands/sh.d.ts +6 -0
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +6 -0
- package/dist/commands/shift.d.ts +10 -0
- package/dist/commands/shift.d.ts.map +1 -1
- package/dist/commands/shift.js +10 -0
- package/dist/commands/sleep.d.ts +5 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sleep.js +5 -0
- package/dist/commands/sort.d.ts +5 -0
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +5 -0
- package/dist/commands/source.d.ts +5 -0
- package/dist/commands/source.d.ts.map +1 -1
- package/dist/commands/source.js +5 -0
- package/dist/commands/stat.d.ts +7 -0
- package/dist/commands/stat.d.ts.map +1 -0
- package/dist/commands/stat.js +56 -0
- package/dist/commands/su.d.ts +13 -0
- package/dist/commands/su.d.ts.map +1 -1
- package/dist/commands/su.js +45 -14
- package/dist/commands/sudo.d.ts.map +1 -1
- package/dist/commands/sudo.js +5 -0
- package/dist/commands/tail.d.ts +5 -0
- package/dist/commands/tail.d.ts.map +1 -1
- package/dist/commands/tail.js +15 -3
- package/dist/commands/tar.d.ts +5 -0
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +40 -10
- package/dist/commands/tee.d.ts +5 -0
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +5 -0
- package/dist/commands/touch.d.ts +5 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +5 -0
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +45 -10
- package/dist/commands/tree.d.ts +5 -0
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +5 -0
- package/dist/commands/true.d.ts +10 -0
- package/dist/commands/true.d.ts.map +1 -1
- package/dist/commands/true.js +10 -0
- package/dist/commands/type.d.ts +5 -0
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +5 -0
- package/dist/commands/uname.d.ts +5 -0
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +5 -0
- package/dist/commands/uniq.d.ts +5 -0
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uniq.js +5 -0
- package/dist/commands/unset.d.ts +5 -0
- package/dist/commands/unset.d.ts.map +1 -1
- package/dist/commands/unset.js +5 -0
- package/dist/commands/uptime.d.ts +5 -0
- package/dist/commands/uptime.d.ts.map +1 -1
- package/dist/commands/uptime.js +5 -0
- package/dist/commands/wc.d.ts +5 -0
- package/dist/commands/wc.d.ts.map +1 -1
- package/dist/commands/wc.js +5 -0
- package/dist/commands/wget.d.ts +5 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +16 -1
- package/dist/commands/who.d.ts +5 -0
- package/dist/commands/who.d.ts.map +1 -1
- package/dist/commands/who.js +5 -0
- package/dist/commands/whoami.d.ts +5 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +5 -0
- package/dist/commands/xargs.d.ts +5 -0
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +5 -0
- package/dist/self-standalone.js +254 -30
- package/dist/types/commands.d.ts +36 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/utils/tokenize.d.ts +20 -0
- package/dist/utils/tokenize.d.ts.map +1 -0
- package/dist/utils/tokenize.js +74 -0
- package/examples/web.min.js +2 -2
- package/package.json +2 -2
- package/src/SSHClient/index.ts +6 -3
- package/src/SSHMimic/executor.ts +21 -44
- package/src/SSHMimic/index.ts +7 -5
- package/src/SSHMimic/sftp.ts +28 -21
- package/src/VirtualPackageManager/index.ts +29 -1
- package/src/VirtualShell/shell.ts +34 -4
- package/src/VirtualShell/shellParser.ts +2 -103
- package/src/VirtualUserManager/index.ts +43 -19
- package/src/commands/adduser.ts +86 -13
- package/src/commands/alias.ts +5 -0
- package/src/commands/apt.ts +5 -0
- package/src/commands/awk.ts +154 -29
- package/src/commands/cd.ts +0 -4
- package/src/commands/clear.ts +5 -0
- package/src/commands/command-helpers.ts +9 -0
- package/src/commands/curl.ts +2 -1
- package/src/commands/declare.ts +5 -0
- package/src/commands/deluser.ts +84 -7
- package/src/commands/df.ts +5 -0
- package/src/commands/du.ts +5 -0
- package/src/commands/export.ts +5 -0
- package/src/commands/grep.ts +21 -8
- package/src/commands/groups.ts +5 -0
- package/src/commands/gzip.ts +61 -28
- package/src/commands/head.ts +14 -4
- package/src/commands/htop.ts +5 -0
- package/src/commands/kill.ts +5 -0
- package/src/commands/ln.ts +22 -0
- package/src/commands/ls.ts +17 -0
- package/src/commands/lsb-release.ts +5 -0
- package/src/commands/man.ts +38 -143
- package/src/commands/manuals/adduser.txt +11 -0
- package/src/commands/manuals/apt-cache.txt +12 -0
- package/src/commands/manuals/apt.txt +20 -0
- package/src/commands/manuals/awk.txt +13 -0
- package/src/commands/manuals/cat.txt +14 -0
- package/src/commands/manuals/cd.txt +16 -0
- package/src/commands/manuals/chmod.txt +16 -0
- package/src/commands/manuals/clear.txt +10 -0
- package/src/commands/manuals/cp.txt +10 -0
- package/src/commands/manuals/curl.txt +20 -0
- package/src/commands/manuals/date.txt +14 -0
- package/src/commands/manuals/declare.txt +12 -0
- package/src/commands/manuals/deluser.txt +10 -0
- package/src/commands/manuals/df.txt +10 -0
- package/src/commands/manuals/dpkg-query.txt +11 -0
- package/src/commands/manuals/dpkg.txt +14 -0
- package/src/commands/manuals/du.txt +11 -0
- package/src/commands/manuals/echo.txt +11 -0
- package/src/commands/manuals/false.txt +10 -0
- package/src/commands/manuals/find.txt +11 -0
- package/src/commands/manuals/free.txt +12 -0
- package/src/commands/manuals/grep.txt +13 -0
- package/src/commands/manuals/groups.txt +10 -0
- package/src/commands/manuals/gzip.txt +11 -0
- package/src/commands/manuals/head.txt +10 -0
- package/src/commands/manuals/help.txt +11 -0
- package/src/commands/manuals/history.txt +11 -0
- package/src/commands/manuals/hostname.txt +10 -0
- package/src/commands/manuals/id.txt +10 -0
- package/src/commands/manuals/kill.txt +13 -0
- package/src/commands/manuals/ls.txt +20 -0
- package/src/commands/manuals/lsb_release.txt +14 -0
- package/src/commands/manuals/mkdir.txt +10 -0
- package/src/commands/manuals/mv.txt +10 -0
- package/src/commands/manuals/nano.txt +11 -0
- package/src/commands/manuals/neofetch.txt +10 -0
- package/src/commands/manuals/node.txt +13 -0
- package/src/commands/manuals/npm.txt +13 -0
- package/src/commands/manuals/npx.txt +13 -0
- package/src/commands/manuals/passwd.txt +11 -0
- package/src/commands/manuals/ping.txt +10 -0
- package/src/commands/manuals/printf.txt +11 -0
- package/src/commands/manuals/ps.txt +10 -0
- package/src/commands/manuals/pwd.txt +10 -0
- package/src/commands/manuals/python3.txt +13 -0
- package/src/commands/manuals/readlink.txt +10 -0
- package/src/commands/manuals/return.txt +10 -0
- package/src/commands/manuals/rm.txt +10 -0
- package/src/commands/manuals/sed.txt +11 -0
- package/src/commands/manuals/set.txt +11 -0
- package/src/commands/manuals/shift.txt +10 -0
- package/src/commands/manuals/sleep.txt +10 -0
- package/src/commands/manuals/sort.txt +12 -0
- package/src/commands/manuals/source.txt +11 -0
- package/src/commands/manuals/ssh.txt +11 -0
- package/src/commands/manuals/stat.txt +10 -0
- package/src/commands/manuals/su.txt +13 -0
- package/src/commands/manuals/sudo.txt +11 -0
- package/src/commands/manuals/tail.txt +10 -0
- package/src/commands/manuals/tar.txt +19 -0
- package/src/commands/manuals/tee.txt +10 -0
- package/src/commands/manuals/test.txt +11 -0
- package/src/commands/manuals/touch.txt +11 -0
- package/src/commands/manuals/tr.txt +10 -0
- package/src/commands/manuals/trap.txt +10 -0
- package/src/commands/manuals/true.txt +10 -0
- package/src/commands/manuals/type.txt +10 -0
- package/src/commands/manuals/uname.txt +12 -0
- package/src/commands/manuals/uniq.txt +12 -0
- package/src/commands/manuals/unset.txt +10 -0
- package/src/commands/manuals/uptime.txt +11 -0
- package/src/commands/manuals/wc.txt +12 -0
- package/src/commands/manuals/wget.txt +12 -0
- package/src/commands/manuals/which.txt +10 -0
- package/src/commands/manuals/whoami.txt +10 -0
- package/src/commands/manuals/xargs.txt +10 -0
- package/src/commands/mkdir.ts +5 -0
- package/src/commands/mv.ts +5 -0
- package/src/commands/nano.ts +5 -0
- package/src/commands/neofetch.ts +15 -6
- package/src/commands/passwd.ts +35 -12
- package/src/commands/ping.ts +5 -0
- package/src/commands/printf.ts +30 -13
- package/src/commands/ps.ts +5 -0
- package/src/commands/read.ts +5 -0
- package/src/commands/registry.ts +4 -1
- package/src/commands/rm.ts +5 -0
- package/src/commands/runtime.ts +1 -61
- package/src/commands/sed.ts +5 -0
- package/src/commands/set.ts +5 -24
- package/src/commands/sh.ts +9 -3
- package/src/commands/shift.ts +10 -0
- package/src/commands/sleep.ts +5 -0
- package/src/commands/sort.ts +5 -0
- package/src/commands/source.ts +5 -0
- package/src/commands/stat.ts +61 -0
- package/src/commands/su.ts +54 -16
- package/src/commands/sudo.ts +5 -0
- package/src/commands/tail.ts +17 -3
- package/src/commands/tar.ts +38 -15
- package/src/commands/tee.ts +5 -0
- package/src/commands/touch.ts +5 -0
- package/src/commands/tr.ts +54 -10
- package/src/commands/tree.ts +5 -0
- package/src/commands/true.ts +10 -0
- package/src/commands/type.ts +5 -0
- package/src/commands/uname.ts +5 -0
- package/src/commands/uniq.ts +5 -0
- package/src/commands/unset.ts +5 -0
- package/src/commands/uptime.ts +5 -0
- package/src/commands/wc.ts +5 -0
- package/src/commands/wget.ts +17 -1
- package/src/commands/who.ts +5 -0
- package/src/commands/whoami.ts +5 -0
- package/src/commands/xargs.ts +5 -0
- package/src/self-standalone.ts +316 -33
- package/src/types/commands.ts +37 -0
- package/src/utils/tokenize.ts +78 -0
- package/tests/new-features.test.ts +2 -2
- package/builds/web-iife.min.js +0 -13
- package/builds/web-iife.min.js.map +0 -7
package/README.md
CHANGED
|
@@ -1,289 +1,198 @@
|
|
|
1
1
|
# `typescript-virtual-container`
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> A complete virtual Linux environment in pure TypeScript — runs as an SSH/SFTP server, a browser-based web shell, or a standalone CLI. Ships with a realistic Linux rootfs, a virtual package manager, a full shell interpreter, and a typed programmatic API for testing, automation, honeypots, and embedded shell experiences.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/typescript-virtual-container)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
|
|
10
|
+
---
|
|
11
|
+
|
|
10
12
|
## Table of Contents
|
|
11
13
|
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [API Reference](#api-reference)
|
|
21
|
-
- [VirtualSshServer](#virtualsshserver)
|
|
22
|
-
- [VirtualSftpServer](#virtualsftpserver)
|
|
23
|
-
- [VirtualShell](#virtualshell)
|
|
24
|
-
- [VirtualFileSystem](#virtualfilesystem)
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
- [HoneyPot](#honeypot)
|
|
29
|
-
- [SshClient](#sshclient-programmatic-api)
|
|
14
|
+
- [Three ways to run](#three-ways-to-run)
|
|
15
|
+
- [Get Started](#get-started)
|
|
16
|
+
- [Install](#install)
|
|
17
|
+
- [Try instantly (zero install)](#try-instantly-zero-install)
|
|
18
|
+
- [SSH server](#ssh-server)
|
|
19
|
+
- [Web shell (browser)](#web-shell-browser)
|
|
20
|
+
- [Programmatic API](#programmatic-api)
|
|
21
|
+
- [How It Works](#how-it-works)
|
|
22
|
+
- [API Reference](#api-reference) *(open by default)*
|
|
23
|
+
- [`VirtualSshServer`](#virtualsshserver)
|
|
24
|
+
- [`VirtualSftpServer`](#virtualsftpserver)
|
|
25
|
+
- [`VirtualShell`](#virtualshell)
|
|
26
|
+
- [`VirtualFileSystem`](#virtualfilesystem)
|
|
27
|
+
- [`VirtualUserManager`](#virtualusermanager)
|
|
28
|
+
- [`VirtualPackageManager`](#virtualpackagemanager)
|
|
29
|
+
- [Snapshot Diff Tooling](#snapshot-diff-tooling)
|
|
30
|
+
- [`HoneyPot`](#honeypot)
|
|
31
|
+
- [`SshClient`](#sshclient-programmatic-api)
|
|
30
32
|
- [Key Types](#key-types)
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [Built-in Commands](#built-in-commands)
|
|
33
|
+
- [Command Helpers](#command-helpers)
|
|
34
|
+
- [Examples](#examples)
|
|
35
|
+
- [Built-in Commands (91)](#built-in-commands-91)
|
|
35
36
|
- [Shell Scripting](#shell-scripting)
|
|
37
|
+
- [Linux Rootfs & VFS PATH Resolution](#linux-rootfs--vfs-path-resolution)
|
|
36
38
|
- [Configuration](#configuration)
|
|
37
39
|
- [Performance & Scalability](#performance--scalability)
|
|
38
40
|
- [Types & TypeScript](#types--typescript)
|
|
39
|
-
- [FAQ](#faq)
|
|
40
41
|
- [Troubleshooting](#troubleshooting)
|
|
42
|
+
- [FAQ](#faq)
|
|
41
43
|
- [Contributing](#contributing)
|
|
42
44
|
- [Security](#security)
|
|
43
|
-
- [
|
|
45
|
+
- [Compatibility](#compatibility)
|
|
44
46
|
- [License](#license)
|
|
45
47
|
- [Roadmap](#roadmap)
|
|
46
|
-
- [Changelog](#changelog)
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Overview
|
|
51
|
-
|
|
52
|
-
`typescript-virtual-container` is a lightweight, fully-typed SSH/SFTP runtime written in TypeScript that provides:
|
|
53
|
-
|
|
54
|
-
- **Pure in-memory filesystem**: No disk I/O at runtime. All state lives in a fast recursive in-memory tree. Persist via a compact binary snapshot format (`.vfsb`) or JSON for interoperability.
|
|
55
|
-
- **SSH + SFTP Protocol Support**: Serve SSH shell/exec sessions and SFTP file operations on configurable ports.
|
|
56
|
-
- **Password & public-key authentication**: Register SSH public keys per user alongside (or instead of) password auth.
|
|
57
|
-
- **Rate limiting / brute-force protection**: Configurable per-IP lockout after N failed auth attempts.
|
|
58
|
-
- **User Management**: Create, authenticate, and manage virtual users with scrypt password hashing, sudo-like privilege elevation, and optional per-user disk quotas.
|
|
59
|
-
- **Programmatic Shell API**: Execute shell commands and query filesystem state directly from TypeScript without SSH overhead.
|
|
60
|
-
- **Real shell interpreter**: `&&` / `||` / `;` operators, `if`/`elif`/`else`/`fi`, `for`/`do`/`done`, `while`/`do`/`done`, variable expansion (`$VAR`, `${VAR:-default}`), `$?`, per-session environment.
|
|
61
|
-
- **`.bashrc` support**: Loaded automatically at interactive session start from `/home/<user>/.bashrc`.
|
|
62
|
-
- **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking.
|
|
63
|
-
- **Security Auditing**: Built-in `HoneyPot` utility for comprehensive activity logging, event tracking, statistics collection, and anomaly detection across all components.
|
|
64
|
-
- **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.
|
|
65
|
-
- **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`.
|
|
66
|
-
- **87 Built-in Commands**: Full navigation, text processing, archiving, system info, package management, and user management commands — grouped and documented in the interactive `help` system.
|
|
67
|
-
- **`$(cmd)` command substitution**: Nested command execution in any argument position.
|
|
68
|
-
- **Alias support**: `alias`, `unalias` — persisted in session environment.
|
|
69
|
-
- **Full TypeScript Support**: Complete JSDoc coverage, exported types, and first-class async/await for all operations.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## What This Is / What This Is Not
|
|
74
|
-
|
|
75
|
-
### What This Is
|
|
76
|
-
|
|
77
|
-
- A virtual shell runtime written in TypeScript with a **pure in-memory filesystem**.
|
|
78
|
-
- A virtual environment with its own filesystem, user management, and a real shell interpreter.
|
|
79
|
-
- A practical tool for deterministic testing, automation pipelines, and SSH-like workflows without running real containers.
|
|
80
|
-
- A honeypot framework for capturing and auditing attacker behavior.
|
|
81
|
-
|
|
82
|
-
TL;DR: this is a shell emulator and virtual environment for developer workflows, not a security sandbox or container runtime.
|
|
83
|
-
|
|
84
|
-
### What This Is Not
|
|
85
|
-
|
|
86
|
-
- Not a fully isolated container runtime.
|
|
87
|
-
- Not a kernel-level isolation boundary (unlike Docker/VM-based isolation).
|
|
88
|
-
- 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`.
|
|
89
|
-
|
|
90
|
-
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.
|
|
91
|
-
|
|
92
|
-
TL;DR: this is not a secure sandbox for running untrusted code. Do not expose it to untrusted users or the public internet without additional isolation layers.
|
|
93
48
|
|
|
94
49
|
---
|
|
95
50
|
|
|
96
|
-
##
|
|
51
|
+
## Three ways to run
|
|
97
52
|
|
|
98
|
-
|
|
53
|
+
| Mode | Entry point | Use case |
|
|
54
|
+
|------|-------------|----------|
|
|
55
|
+
| **SSH/SFTP server** | `VirtualSshServer` / `VirtualSftpServer` | Honeypots, remote testing, training environments |
|
|
56
|
+
| **Web shell** | `builds/web.min.js` (browser bundle) | Embedded terminals, interactive tutorials, browser demos |
|
|
57
|
+
| **Standalone CLI** | `builds/self-standalone.js` (single file) | Local shell, one-liner demos, no install required |
|
|
99
58
|
|
|
100
|
-
|
|
101
|
-
- **Deterministic test environments**: Repeatable state for CI pipelines and integration tests. Build a fixture snapshot once, hydrate for each test.
|
|
102
|
-
- **Low operational overhead**: No Docker daemon, no kernel namespaces, no privileged setup.
|
|
103
|
-
- **Fast feedback loops**: Programmatic API for command execution and filesystem assertions.
|
|
104
|
-
- **Real shell scripting**: `&&`/`||`/`;`, `if`/`for`/`while`, variable expansion — not just command dispatch.
|
|
105
|
-
- **Developer-friendly internals**: Typed APIs, clear boundaries, composable building blocks, and full JSDoc.
|
|
59
|
+
All three modes share the same core: a pure in-memory VFS, a real shell interpreter, a virtual package manager, and a typed programmatic API.
|
|
106
60
|
|
|
107
61
|
---
|
|
108
62
|
|
|
109
|
-
##
|
|
63
|
+
## Get Started
|
|
110
64
|
|
|
111
|
-
###
|
|
65
|
+
### Install
|
|
112
66
|
|
|
113
67
|
```bash
|
|
114
68
|
npm install typescript-virtual-container
|
|
115
|
-
# or
|
|
116
|
-
yarn add typescript-virtual-container
|
|
117
|
-
# or
|
|
118
|
-
bun add typescript-virtual-container
|
|
69
|
+
# or: yarn add / bun add
|
|
119
70
|
```
|
|
120
71
|
|
|
121
|
-
###
|
|
72
|
+
### Try instantly (zero install)
|
|
122
73
|
|
|
123
74
|
```bash
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
bun run build
|
|
130
|
-
```
|
|
75
|
+
# Interactive local shell — persists VFS in .vfs/ in the current directory
|
|
76
|
+
curl -s https://raw.githubusercontent.com/itsrealfortune/typescript-virtual-container/refs/heads/main/builds/self-standalone.js -o self-standalone.js && node self-standalone.js && rm -f self-standalone.js
|
|
77
|
+
|
|
78
|
+
# SSH server (connect with any SSH client on port 2222)
|
|
79
|
+
curl -s https://raw.githubusercontent.com/itsrealfortune/typescript-virtual-container/refs/heads/main/builds/standalone.js -o standalone.js && node standalone.js && rm -f standalone.js
|
|
131
80
|
|
|
132
|
-
|
|
81
|
+
# SSH server without SFTP (lighter build)
|
|
82
|
+
curl -s https://raw.githubusercontent.com/itsrealfortune/typescript-virtual-container/refs/heads/main/builds/standalone-wo-sftp.js -o standalone.js && node standalone.js && rm -f standalone.js
|
|
83
|
+
```
|
|
133
84
|
|
|
134
|
-
|
|
85
|
+
**`self-standalone.js` options:**
|
|
135
86
|
|
|
136
87
|
```bash
|
|
137
|
-
|
|
88
|
+
node self-standalone.js # boot as root
|
|
89
|
+
node self-standalone.js --user alice # boot as alice (prompts for password if set)
|
|
90
|
+
node self-standalone.js --user=alice # same, inline form
|
|
91
|
+
SSH_MIMIC_HOSTNAME=my-box node self-standalone.js # custom hostname
|
|
138
92
|
```
|
|
139
93
|
|
|
140
|
-
|
|
94
|
+
The VFS is persisted automatically to `.vfs/vfs-snapshot.vfsb` in the current directory — state survives between runs. Delete `.vfs/` to start fresh.
|
|
141
95
|
|
|
142
|
-
|
|
96
|
+
The shell shows a login banner and `Last login:` timestamp on each start.
|
|
143
97
|
|
|
144
|
-
|
|
145
|
-
bun run self-standalone-build
|
|
146
|
-
node builds/self-standalone.js --user root
|
|
147
|
-
```
|
|
98
|
+
### SSH server
|
|
148
99
|
|
|
149
|
-
|
|
100
|
+
```typescript
|
|
101
|
+
import { VirtualSshServer } from "typescript-virtual-container";
|
|
150
102
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
103
|
+
const ssh = new VirtualSshServer({ port: 2222, hostname: "my-container" });
|
|
104
|
+
await ssh.start();
|
|
105
|
+
// ssh root@localhost -p 2222
|
|
154
106
|
```
|
|
155
|
-
---
|
|
156
107
|
|
|
157
|
-
|
|
108
|
+
### Web shell (browser)
|
|
158
109
|
|
|
159
|
-
|
|
160
|
-
It runs the shell fully in-browser and persists the virtual filesystem in IndexedDB.
|
|
110
|
+
Three browser bundles are available — pick the one that fits your deployment:
|
|
161
111
|
|
|
162
|
-
|
|
112
|
+
| Bundle | Format | Entry | Use case |
|
|
113
|
+
|--------|--------|-------|----------|
|
|
114
|
+
| `builds/web.min.js` | ESM | `createWebShell()` | Local dev, modern bundlers |
|
|
115
|
+
| `builds/web-iife.min.js` | IIFE (`WebShellLib`) | `WebShellLib.createWebShell()` | Cloudflare, CDN, reverse proxies |
|
|
116
|
+
| `builds/web-full-api.min.js` | ESM | `createVirtualShellShim()` | API surface close to `VirtualShell` |
|
|
117
|
+
|
|
118
|
+
All bundles persist the VFS in **IndexedDB** — state survives page reloads.
|
|
163
119
|
|
|
164
120
|
```bash
|
|
165
|
-
bun run web-build
|
|
121
|
+
bun run web-build # → builds/web.min.js + examples/web.min.js
|
|
122
|
+
bun run web-build-iife # → builds/web-iife.min.js
|
|
123
|
+
bun run web-full-build # → builds/web-full-api.min.js
|
|
124
|
+
bun run build-all # rebuild everything
|
|
166
125
|
```
|
|
167
126
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
- `builds/web.min.js`
|
|
171
|
-
- `builds/web.min.js.map`
|
|
172
|
-
|
|
173
|
-
Use it from a browser module script:
|
|
127
|
+
**ESM (`web.min.js`)**
|
|
174
128
|
|
|
175
129
|
```html
|
|
176
130
|
<script type="module">
|
|
177
131
|
import { createWebShell } from "./builds/web.min.js";
|
|
178
132
|
|
|
179
133
|
const shell = createWebShell("web-vm", {
|
|
180
|
-
|
|
181
|
-
storeName: "vfs",
|
|
134
|
+
vfs: { databaseName: "virtual-env-js", storeName: "vfs" },
|
|
182
135
|
});
|
|
136
|
+
await shell.ensureInitialized();
|
|
183
137
|
|
|
184
|
-
const out = await shell.executeCommandLine("
|
|
138
|
+
const out = await shell.executeCommandLine("ls /etc && echo hello");
|
|
185
139
|
console.log(out.stdout);
|
|
186
140
|
</script>
|
|
187
141
|
```
|
|
188
142
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
- This build is browser-targeted and does not include SSH/SFTP networking servers.
|
|
192
|
-
- State is mirrored to IndexedDB via the VFS mirror implementation.
|
|
193
|
-
- If you need SSH/SFTP, use the Node standalone builds instead (`standalone.js` or `standalone-wo-sftp.js`). For a local SSH-like terminal, use `builds/self-standalone.js`.
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## Compatibility
|
|
198
|
-
|
|
199
|
-
- **Node.js**: Recommended `>=18`
|
|
200
|
-
- **Bun**: Supported for development and runtime
|
|
201
|
-
- **TypeScript**: Recommended `>=5.0`
|
|
202
|
-
- **OS**: Linux, macOS, and Windows (via Node/Bun runtime)
|
|
203
|
-
|
|
204
|
-
The virtual filesystem and shell behavior are intentionally portable and do not depend on host-specific POSIX syscalls.
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## Quick Start
|
|
209
|
-
|
|
210
|
-
### Running an SSH Server
|
|
143
|
+
**IIFE (`web-iife.min.js`)** — no `type="module"` needed, works through Cloudflare:
|
|
211
144
|
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
await ssh.start();
|
|
221
|
-
console.log("SSH server listening on :2222");
|
|
222
|
-
|
|
223
|
-
// Connect externally:
|
|
224
|
-
// ssh root@localhost -p 2222
|
|
225
|
-
// root has no password by default — login is allowed without verification.
|
|
226
|
-
|
|
227
|
-
process.on("SIGTERM", () => {
|
|
228
|
-
ssh.stop();
|
|
229
|
-
process.exit(0);
|
|
230
|
-
});
|
|
145
|
+
```html
|
|
146
|
+
<script src="./builds/web-iife.min.js"></script>
|
|
147
|
+
<script>
|
|
148
|
+
const shell = WebShellLib.createWebShell("web-vm");
|
|
149
|
+
shell.ensureInitialized().then(() =>
|
|
150
|
+
shell.executeCommandLine("whoami").then(r => console.log(r.stdout))
|
|
151
|
+
);
|
|
152
|
+
</script>
|
|
231
153
|
```
|
|
232
154
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
import { VirtualSftpServer, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
155
|
+
**Full API shim (`web-full-api.min.js`)** — mirrors the `VirtualShell` programmatic API:
|
|
237
156
|
|
|
238
|
-
|
|
157
|
+
```html
|
|
158
|
+
<script type="module">
|
|
159
|
+
import { createVirtualShellShim } from "./builds/web-full-api.min.js";
|
|
239
160
|
|
|
240
|
-
const
|
|
241
|
-
|
|
161
|
+
const shell = createVirtualShellShim("web-vm");
|
|
162
|
+
await shell.ensureInitialized();
|
|
163
|
+
await shell.executeCommandLine("mkdir -p /app && echo hello > /app/file.txt");
|
|
164
|
+
const vfs = shell.getVfs();
|
|
165
|
+
console.log(vfs.readFile("/app/file.txt")); // hello
|
|
166
|
+
</script>
|
|
167
|
+
```
|
|
242
168
|
|
|
243
|
-
|
|
244
|
-
await sftp.start();
|
|
169
|
+
**Run the demo locally:**
|
|
245
170
|
|
|
246
|
-
|
|
171
|
+
```bash
|
|
172
|
+
bun run example-serve
|
|
173
|
+
# Open http://localhost:8787/index.html (ESM)
|
|
174
|
+
# or http://localhost:8787/index-cf.html (IIFE, Cloudflare-compatible)
|
|
247
175
|
```
|
|
248
176
|
|
|
249
|
-
###
|
|
177
|
+
### Programmatic API
|
|
250
178
|
|
|
251
179
|
```typescript
|
|
252
|
-
import { SshClient, VirtualShell
|
|
253
|
-
|
|
254
|
-
const shell = new VirtualShell("typescript-vm");
|
|
255
|
-
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
256
|
-
await ssh.start();
|
|
180
|
+
import { SshClient, VirtualShell } from "typescript-virtual-container";
|
|
257
181
|
|
|
182
|
+
const shell = new VirtualShell("typescript-vm");
|
|
183
|
+
await shell.ensureInitialized();
|
|
258
184
|
const client = new SshClient(shell, "root");
|
|
259
185
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const result = await client.pwd();
|
|
264
|
-
console.log("Current dir:", result.stdout);
|
|
265
|
-
|
|
266
|
-
await client.mkdir("/tmp/work", true);
|
|
267
|
-
await client.cd("/tmp/work");
|
|
268
|
-
|
|
269
|
-
const content = await client.readFile("/etc/hostname");
|
|
270
|
-
console.log("Hostname file:", content.stdout);
|
|
271
|
-
|
|
272
|
-
await client.writeFile("output.txt", "Hello, World!");
|
|
186
|
+
await client.mkdir("/app/config", true);
|
|
187
|
+
await client.writeFile("/app/config/settings.json", JSON.stringify({ env: "dev" }));
|
|
273
188
|
|
|
274
|
-
|
|
189
|
+
const r = await client.exec("ls /app && cat /app/config/settings.json");
|
|
190
|
+
console.log(r.stdout);
|
|
275
191
|
```
|
|
276
192
|
|
|
277
193
|
---
|
|
278
194
|
|
|
279
|
-
##
|
|
280
|
-
|
|
281
|
-
### Execution Modes
|
|
282
|
-
|
|
283
|
-
1. **SSH Shell Mode**: Interactive terminal session over SSH with readline, history, `.bashrc` loading, TTY resizing, `Ctrl+W` word delete, `Ctrl+U` line clear.
|
|
284
|
-
2. **SSH Exec Mode**: Non-interactive command execution (e.g. `ssh user@host "ls -la"`).
|
|
285
|
-
3. **SFTP Mode**: Remote file operations (`readdir`, `stat`, `readFile`, `writeFile`, `mkdir`, `rename`, etc.) with home-directory confinement.
|
|
286
|
-
4. **Programmatic Mode**: Direct TypeScript API via `SshClient` — no SSH protocol overhead.
|
|
195
|
+
## How It Works
|
|
287
196
|
|
|
288
197
|
```
|
|
289
198
|
┌──────────────────────────────────────────────────────────────────────────┐
|
|
@@ -294,7 +203,7 @@ ssh.stop();
|
|
|
294
203
|
│
|
|
295
204
|
┌──────────▼──────────┐
|
|
296
205
|
│ VirtualShell │
|
|
297
|
-
│ script parser │ ← &&/||/; · if/for/while
|
|
206
|
+
│ script parser │ ← &&/||/; · if/for/while/case
|
|
298
207
|
│ command executor │ ← per-session ShellEnv
|
|
299
208
|
│ .bashrc loader │ ← /home/<user>/.bashrc
|
|
300
209
|
│ session manager │
|
|
@@ -315,682 +224,345 @@ ssh.stop();
|
|
|
315
224
|
└─────────────────────────┘
|
|
316
225
|
```
|
|
317
226
|
|
|
318
|
-
|
|
227
|
+
**What it is:** a shell emulator and virtual Linux environment for developer workflows.
|
|
319
228
|
|
|
320
|
-
|
|
229
|
+
**What it is not:** a kernel-level security boundary. `curl`/`wget` use the native `fetch()` API — no host binary spawned. `node`/`python3`/`npm` are virtual REPL stubs, not real runtimes. `execvp` is never called. Do not expose to the public internet without additional isolation.
|
|
321
230
|
|
|
322
|
-
|
|
231
|
+
---
|
|
323
232
|
|
|
324
|
-
|
|
233
|
+
<details open>
|
|
234
|
+
<summary><strong>API Reference</strong></summary>
|
|
325
235
|
|
|
326
|
-
|
|
236
|
+
### `VirtualSshServer`
|
|
327
237
|
|
|
328
238
|
```typescript
|
|
329
239
|
new VirtualSshServer({
|
|
330
|
-
port: number;
|
|
331
|
-
hostname?: string; //
|
|
332
|
-
shell?: VirtualShell; //
|
|
333
|
-
maxAuthAttempts?: number; //
|
|
334
|
-
lockoutDurationMs?: number; //
|
|
240
|
+
port: number;
|
|
241
|
+
hostname?: string; // default: "typescript-vm"
|
|
242
|
+
shell?: VirtualShell; // share state with SFTP server
|
|
243
|
+
maxAuthAttempts?: number; // default: 5
|
|
244
|
+
lockoutDurationMs?: number; // default: 60_000
|
|
335
245
|
})
|
|
336
246
|
```
|
|
337
247
|
|
|
338
248
|
If `shell` is omitted, the server creates `new VirtualShell(hostname)` internally.
|
|
339
249
|
|
|
340
|
-
**
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
const shell = new VirtualShell("my-lab", {
|
|
344
|
-
kernel: "1.0.0+itsrealfortune+1-amd64",
|
|
345
|
-
os: "Fortune GNU/Linux x64",
|
|
346
|
-
arch: "x86_64",
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
const ssh = new VirtualSshServer({ port: 2222, hostname: "my-lab", shell });
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
#### Methods
|
|
250
|
+
**Methods**
|
|
353
251
|
|
|
354
252
|
| Method | Description |
|
|
355
253
|
|--------|-------------|
|
|
356
254
|
| `start(): Promise<number>` | Initialize VFS, users, start listening. Returns bound port. |
|
|
357
255
|
| `stop(): void` | Gracefully close server and all active connections. |
|
|
358
|
-
| `clearLockout(ip
|
|
256
|
+
| `clearLockout(ip): void` | Manually lift a rate-limit lockout for an IP. |
|
|
359
257
|
| `getVfs(): VirtualFileSystem \| null` | Access VFS instance (null before start). |
|
|
360
258
|
| `getUsers(): VirtualUserManager \| null` | Access user manager (null before start). |
|
|
361
259
|
| `getHostname(): string` | Returns configured hostname. |
|
|
362
260
|
|
|
363
|
-
|
|
261
|
+
**Events**
|
|
364
262
|
|
|
365
263
|
| Event | Data | Description |
|
|
366
264
|
|-------|------|-------------|
|
|
367
|
-
| `start` | `{ port
|
|
265
|
+
| `start` | `{ port }` | Server started |
|
|
368
266
|
| `stop` | — | Server stopped |
|
|
369
|
-
| `auth:success` | `{ username, remoteAddress, method? }` |
|
|
267
|
+
| `auth:success` | `{ username, remoteAddress, method? }` | Authenticated |
|
|
370
268
|
| `auth:failure` | `{ username, remoteAddress, reason?, method? }` | Auth failed |
|
|
371
|
-
| `auth:lockout` | `{ ip, until: Date }` | IP locked out
|
|
269
|
+
| `auth:lockout` | `{ ip, until: Date }` | IP locked out |
|
|
372
270
|
| `client:connect` | — | New SSH client connected |
|
|
373
|
-
| `client:disconnect` | `{ user
|
|
374
|
-
|
|
375
|
-
**Example:**
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
ssh.on("auth:success", ({ username, remoteAddress }) => {
|
|
379
|
-
console.log(`[SSH] ${username} authenticated from ${remoteAddress}`);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
ssh.on("auth:lockout", ({ ip, until }) => {
|
|
383
|
-
console.warn(`[SSH] ${ip} locked until ${until}`);
|
|
384
|
-
});
|
|
385
|
-
```
|
|
271
|
+
| `client:disconnect` | `{ user }` | SSH client disconnected |
|
|
386
272
|
|
|
387
273
|
---
|
|
388
274
|
|
|
389
275
|
### `VirtualSftpServer`
|
|
390
276
|
|
|
391
|
-
SFTP server class. Can share a `VirtualShell` with `VirtualSshServer` (recommended) or accept explicit `vfs` + `users` dependencies.
|
|
392
|
-
|
|
393
|
-
#### Constructor
|
|
394
|
-
|
|
395
277
|
```typescript
|
|
396
278
|
new VirtualSftpServer({
|
|
397
279
|
port: number;
|
|
398
280
|
hostname?: string;
|
|
399
|
-
shell?: VirtualShell;
|
|
400
|
-
vfs?: VirtualFileSystem;
|
|
401
|
-
users?: VirtualUserManager;
|
|
281
|
+
shell?: VirtualShell; // share state with SSH server (recommended)
|
|
282
|
+
vfs?: VirtualFileSystem; // explicit if no shell
|
|
283
|
+
users?: VirtualUserManager; // explicit if no shell
|
|
402
284
|
})
|
|
403
285
|
```
|
|
404
286
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
| Method | Description |
|
|
408
|
-
|--------|-------------|
|
|
409
|
-
| `start(): Promise<number>` | Start SFTP server, returns bound port. |
|
|
410
|
-
| `stop(): void` | Stop SFTP server. |
|
|
411
|
-
|
|
412
|
-
#### Behavior Notes
|
|
413
|
-
|
|
414
|
-
- Supports `password` and `keyboard-interactive` authentication. Users without a password set are accepted on any attempt.
|
|
415
|
-
- Resolves relative SFTP paths from `/home/<user>`.
|
|
416
|
-
- Confines all SFTP operations to `/home/<user>` — blocks traversal attempts outside home.
|
|
417
|
-
- Unsupported operations (`READLINK`, `SYMLINK`) return `OP_UNSUPPORTED`.
|
|
287
|
+
Supports `password` and `keyboard-interactive` auth. Confines all operations to `/home/<user>`. Unsupported operations return `OP_UNSUPPORTED`.
|
|
418
288
|
|
|
419
|
-
|
|
289
|
+
**Methods:** `start(): Promise<number>`, `stop(): void`
|
|
420
290
|
|
|
421
|
-
|
|
422
|
-
|-------|------|-------------|
|
|
423
|
-
| `start` | `{ port: number }` | SFTP server started |
|
|
424
|
-
| `stop` | — | SFTP server stopped |
|
|
425
|
-
| `auth:success` | `{ username, remoteAddress }` | User authenticated |
|
|
426
|
-
| `auth:failure` | `{ username, remoteAddress }` | Auth failed |
|
|
427
|
-
| `client:connect` | — | New SFTP client connected |
|
|
428
|
-
| `client:disconnect` | `{ user: string }` | SFTP client disconnected |
|
|
291
|
+
**Events:** `start`, `stop`, `auth:success { username, remoteAddress }`, `auth:failure { username, remoteAddress }`, `client:connect`, `client:disconnect { user }`
|
|
429
292
|
|
|
430
293
|
---
|
|
431
294
|
|
|
432
295
|
### `VirtualShell`
|
|
433
296
|
|
|
434
|
-
Coordinates the
|
|
435
|
-
|
|
436
|
-
#### Constructor
|
|
297
|
+
Coordinates the VFS, user manager, package manager, and command runtime.
|
|
437
298
|
|
|
438
299
|
```typescript
|
|
439
300
|
new VirtualShell(
|
|
440
301
|
hostname: string,
|
|
441
|
-
properties?: ShellProperties,
|
|
442
|
-
vfsOptions?: VfsOptions,
|
|
302
|
+
properties?: ShellProperties, // kernel, os, arch — surfaced by uname/neofetch
|
|
303
|
+
vfsOptions?: VfsOptions, // { mode: "memory"|"fs", snapshotPath?: string }
|
|
443
304
|
)
|
|
444
305
|
```
|
|
445
306
|
|
|
446
|
-
- **hostname**: Injected into command context and prompt.
|
|
447
|
-
- **properties**: Optional shell metadata shown in `uname`-like output. Defaults to `defaultShellProperties`.
|
|
448
|
-
- **vfsOptions**: Optional VFS persistence options — see [VirtualFileSystem](#virtualfilesystem).
|
|
449
|
-
|
|
450
307
|
```typescript
|
|
451
308
|
interface ShellProperties {
|
|
452
|
-
kernel: string; //
|
|
453
|
-
os: string; //
|
|
454
|
-
arch: string; //
|
|
309
|
+
kernel: string; // e.g. "1.0.0+itsrealfortune+1-amd64"
|
|
310
|
+
os: string; // e.g. "Fortune GNU/Linux x64"
|
|
311
|
+
arch: string; // e.g. "x86_64"
|
|
455
312
|
}
|
|
456
313
|
```
|
|
457
314
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
**Example:**
|
|
461
|
-
|
|
462
|
-
```typescript
|
|
463
|
-
const shell = new VirtualShell("typescript-vm", {
|
|
464
|
-
kernel: "1.0.0+itsrealfortune+1-amd64",
|
|
465
|
-
os: "Fortune GNU/Linux x64",
|
|
466
|
-
arch: "x86_64",
|
|
467
|
-
}, {
|
|
468
|
-
mode: "fs",
|
|
469
|
-
snapshotPath: "./data",
|
|
470
|
-
});
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
#### Methods
|
|
315
|
+
**Methods**
|
|
474
316
|
|
|
475
317
|
| Method | Description |
|
|
476
318
|
|--------|-------------|
|
|
477
|
-
| `ensureInitialized(): Promise<void>` | Await
|
|
319
|
+
| `ensureInitialized(): Promise<void>` | Await before using programmatically. |
|
|
478
320
|
| `addCommand(name, params, callback)` | Register a custom shell command. |
|
|
479
|
-
| `executeCommand(rawInput, authUser, cwd)` | Run a raw command string
|
|
480
|
-
| `startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize)` | Attach
|
|
481
|
-
| `writeFileAsUser(authUser, path, content)` | Write
|
|
482
|
-
| `refreshProcFs(): void` | Refresh `/proc
|
|
483
|
-
| `
|
|
484
|
-
| `
|
|
485
|
-
| `
|
|
486
|
-
| `
|
|
321
|
+
| `executeCommand(rawInput, authUser, cwd)` | Run a raw command string. |
|
|
322
|
+
| `startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize)` | Attach a PTY session. |
|
|
323
|
+
| `writeFileAsUser(authUser, path, content)` | Write with quota enforcement. |
|
|
324
|
+
| `refreshProcFs(): void` | Refresh all `/proc/*` files (uptime, meminfo, cpuinfo, per-pid). |
|
|
325
|
+
| `refreshProcSessions(): void` | Lightweight refresh of `/proc/<pid>` and `/proc/self` only. |
|
|
326
|
+
| `syncPasswd(): void` | Sync `/etc/passwd`, `/etc/group`, `/etc/shadow` from user manager. |
|
|
327
|
+
| `getVfs(): VirtualFileSystem \| null` | Access VFS instance. |
|
|
328
|
+
| `getUsers(): VirtualUserManager \| null` | Access user manager. |
|
|
329
|
+
| `getHostname(): string` | Returns configured hostname. |
|
|
487
330
|
|
|
488
|
-
|
|
331
|
+
**Public fields**
|
|
489
332
|
|
|
490
333
|
| Field | Type | Description |
|
|
491
334
|
|-------|------|-------------|
|
|
492
|
-
| `vfs` | `VirtualFileSystem` | Backing virtual filesystem
|
|
493
|
-
| `users` | `VirtualUserManager` | Virtual user database
|
|
494
|
-
| `packageManager` | `VirtualPackageManager` | APT/dpkg package manager
|
|
495
|
-
| `hostname` | `string` | Hostname shown in
|
|
496
|
-
| `properties` | `ShellProperties` | Distro identity strings
|
|
497
|
-
| `startTime` | `number` | Unix ms timestamp of shell creation
|
|
335
|
+
| `vfs` | `VirtualFileSystem` | Backing virtual filesystem. |
|
|
336
|
+
| `users` | `VirtualUserManager` | Virtual user database. |
|
|
337
|
+
| `packageManager` | `VirtualPackageManager` | APT/dpkg package manager. |
|
|
338
|
+
| `hostname` | `string` | Hostname shown in prompt and SSH ident. |
|
|
339
|
+
| `properties` | `ShellProperties` | Distro identity strings. |
|
|
340
|
+
| `startTime` | `number` | Unix ms timestamp of shell creation. |
|
|
341
|
+
|
|
342
|
+
**Events:** `initialized`, `command { command, user, cwd }`, `session:start { user, sessionId, remoteAddress }`
|
|
498
343
|
|
|
499
344
|
**Custom command example:**
|
|
500
345
|
|
|
501
346
|
```typescript
|
|
502
347
|
shell.addCommand("greet", ["[name]"], ({ args, authUser }) => {
|
|
503
348
|
const name = args[0] ?? authUser;
|
|
504
|
-
return { stdout: `Hello, ${name}
|
|
349
|
+
return { stdout: `Hello, ${name}!\n`, exitCode: 0 };
|
|
505
350
|
});
|
|
506
|
-
// Inside the shell: greet world → Hello, world!
|
|
507
351
|
```
|
|
508
352
|
|
|
509
|
-
#### Events
|
|
510
|
-
|
|
511
|
-
| Event | Data | Description |
|
|
512
|
-
|-------|------|-------------|
|
|
513
|
-
| `initialized` | — | Shell initialization complete |
|
|
514
|
-
| `command` | `{ command, user, cwd }` | A command was executed |
|
|
515
|
-
| `session:start` | `{ user, sessionId, remoteAddress }` | Interactive session started |
|
|
516
|
-
|
|
517
353
|
---
|
|
518
354
|
|
|
519
355
|
### `VirtualFileSystem`
|
|
520
356
|
|
|
521
|
-
Pure in-memory virtual filesystem.
|
|
522
|
-
|
|
523
|
-
Two persistence modes are available via the `VfsOptions` constructor argument:
|
|
524
|
-
|
|
525
|
-
```typescript
|
|
526
|
-
// Default — pure in-memory, zero disk I/O
|
|
527
|
-
const vfs = new VirtualFileSystem();
|
|
528
|
-
const vfs = new VirtualFileSystem({ mode: "memory" });
|
|
529
|
-
|
|
530
|
-
// FS mode — binary snapshot (.vfsb) auto-saved to disk on flushMirror()
|
|
531
|
-
const vfs = new VirtualFileSystem({
|
|
532
|
-
mode: "fs",
|
|
533
|
-
snapshotPath: "./data", // writes ./data/vfs-snapshot.vfsb
|
|
534
|
-
});
|
|
535
|
-
await vfs.restoreMirror(); // load from disk (silent no-op if no file yet)
|
|
536
|
-
// ... use vfs ...
|
|
537
|
-
await vfs.flushMirror(); // persist to disk
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
Both modes expose exactly the same API. The tree always lives in memory; `"fs"` mode adds a binary round-trip on `restoreMirror` / `flushMirror`. See [VFSB Binary Format](#vfsb-binary-format) for details.
|
|
541
|
-
|
|
542
|
-
#### Constructor
|
|
357
|
+
Pure in-memory virtual filesystem. No host filesystem access at runtime.
|
|
543
358
|
|
|
544
359
|
```typescript
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
snapshotPath?: string; // required when mode is "fs"
|
|
548
|
-
}
|
|
360
|
+
// Memory mode (default) — ephemeral
|
|
361
|
+
new VirtualFileSystem()
|
|
549
362
|
|
|
550
|
-
|
|
363
|
+
// FS mode — persists to binary .vfsb file
|
|
364
|
+
new VirtualFileSystem({ mode: "fs", snapshotPath: "./data" })
|
|
365
|
+
await vfs.restoreMirror(); // load from disk (no-op if no file yet)
|
|
366
|
+
await vfs.flushMirror(); // save to disk
|
|
551
367
|
```
|
|
552
368
|
|
|
553
|
-
|
|
369
|
+
**Methods**
|
|
554
370
|
|
|
555
371
|
| Method | Description |
|
|
556
372
|
|--------|-------------|
|
|
557
373
|
| `mkdir(path, mode?)` | Create directory and any missing parents. |
|
|
558
|
-
| `writeFile(path, content, options?)` | Write file
|
|
559
|
-
| `readFile(path): string` | Read
|
|
560
|
-
| `readFileRaw(path): Buffer` | Read
|
|
561
|
-
| `exists(path): boolean` | Test
|
|
562
|
-
| `stat(path): VfsNodeStats` | Returns
|
|
563
|
-
| `list(path?): string[]` | List direct children
|
|
374
|
+
| `writeFile(path, content, options?)` | Write file. `options.compress` gzips; `options.mode` sets POSIX mode bits. |
|
|
375
|
+
| `readFile(path): string` | Read as UTF-8. Transparently decompresses gzip. |
|
|
376
|
+
| `readFileRaw(path): Buffer` | Read as Buffer. |
|
|
377
|
+
| `exists(path): boolean` | Test existence. |
|
|
378
|
+
| `stat(path): VfsNodeStats` | Returns metadata. |
|
|
379
|
+
| `list(path?): string[]` | List direct children (sorted). |
|
|
564
380
|
| `tree(path?): string` | Render ASCII directory tree. |
|
|
565
|
-
| `move(from, to)` | Move or rename
|
|
566
|
-
| `remove(path, options?)` | Delete
|
|
381
|
+
| `move(from, to)` | Move or rename. |
|
|
382
|
+
| `remove(path, options?)` | Delete. `options.recursive` required for non-empty dirs. |
|
|
567
383
|
| `chmod(path, mode)` | Update POSIX mode bits. |
|
|
568
|
-
| `compressFile(path)` | Gzip-compress
|
|
569
|
-
| `
|
|
570
|
-
| `
|
|
571
|
-
| `
|
|
572
|
-
| `resolveSymlink(path, maxDepth?): string` | Resolve symlink chain to real path (default max 8 hops). |
|
|
384
|
+
| `compressFile(path)` / `decompressFile(path)` | Gzip-compress / gunzip in place. |
|
|
385
|
+
| `symlink(target, linkPath)` | Create symbolic link. |
|
|
386
|
+
| `isSymlink(path): boolean` | Test if path is a symlink. |
|
|
387
|
+
| `resolveSymlink(path, maxDepth?): string` | Resolve symlink chain (default max 8 hops). |
|
|
573
388
|
| `getUsageBytes(path?): number` | Total stored bytes under a path. |
|
|
574
|
-
| `
|
|
575
|
-
| `
|
|
576
|
-
| `
|
|
577
|
-
| `
|
|
578
|
-
| `
|
|
579
|
-
| `flushMirror(): Promise<void>` | Save to disk (`"fs"` mode) / emit `mirror:flush` (`"memory"` mode). |
|
|
580
|
-
| `VirtualFileSystem.fromSnapshot(snapshot)` | **Static.** Create a new memory-mode instance from a snapshot. |
|
|
581
|
-
|
|
582
|
-
#### Events
|
|
583
|
-
|
|
584
|
-
| Event | Data | Description |
|
|
585
|
-
|-------|------|-------------|
|
|
586
|
-
| `file:write` | `{ path, size }` | File written |
|
|
587
|
-
| `file:read` | `{ path, size }` | File read |
|
|
588
|
-
| `dir:create` | `{ path, mode }` | Directory created |
|
|
589
|
-
| `node:remove` | `{ path }` | File or directory deleted |
|
|
590
|
-
| `symlink:create` | `{ link, target }` | Symlink created |
|
|
591
|
-
| `snapshot:import` | — | `importSnapshot()` called |
|
|
592
|
-
| `snapshot:restore` | `{ path }` | Restored from disk (fs mode) |
|
|
593
|
-
| `mirror:flush` | `{ path? }` | Flushed (path present in fs mode) |
|
|
594
|
-
|
|
595
|
-
**Example:**
|
|
596
|
-
|
|
597
|
-
```typescript
|
|
598
|
-
vfs.on("file:write", ({ path, size }) => {
|
|
599
|
-
console.log(`[VFS] Written: ${path} (${size} bytes)`);
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
vfs.on("dir:create", ({ path, mode }) => {
|
|
603
|
-
console.log(`[VFS] Dir created: ${path} (mode: ${mode.toString(8)})`);
|
|
604
|
-
});
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
#### Memory mode — manual snapshot persistence
|
|
608
|
-
|
|
609
|
-
```typescript
|
|
610
|
-
import { VirtualFileSystem } from "typescript-virtual-container";
|
|
611
|
-
import { writeFileSync, readFileSync } from "node:fs";
|
|
612
|
-
|
|
613
|
-
const vfs = new VirtualFileSystem(); // mode: "memory"
|
|
614
|
-
vfs.writeFile("/etc/config.json", JSON.stringify({ debug: true }));
|
|
615
|
-
|
|
616
|
-
// Export to disk manually as JSON (portable, human-readable)
|
|
617
|
-
// For binary format, use "fs" mode instead — see VFSB Binary Format.
|
|
618
|
-
writeFileSync("vfs-snapshot.json", JSON.stringify(vfs.toSnapshot()));
|
|
619
|
-
|
|
620
|
-
// Restore into a new instance
|
|
621
|
-
const snapshot = JSON.parse(readFileSync("vfs-snapshot.json", "utf8"));
|
|
622
|
-
const restored = VirtualFileSystem.fromSnapshot(snapshot);
|
|
623
|
-
console.log(restored.readFile("/etc/config.json")); // {"debug":true}
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
#### FS mode — automatic persistence across restarts
|
|
627
|
-
|
|
628
|
-
```typescript
|
|
629
|
-
import { VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
630
|
-
|
|
631
|
-
const shell = new VirtualShell("my-vm", undefined, {
|
|
632
|
-
mode: "fs",
|
|
633
|
-
snapshotPath: "./vfs-data",
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
637
|
-
await ssh.start();
|
|
638
|
-
// VFS is restored from ./vfs-data/vfs-snapshot.vfsb on start (if it exists).
|
|
639
|
-
// flushMirror() is called after each write, persisting state to disk automatically.
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
---
|
|
643
|
-
|
|
644
|
-
## VFSB Binary Format
|
|
645
|
-
|
|
646
|
-
When `mode: "fs"` is configured, the VFS persists its state to disk as a compact binary file (`vfs-snapshot.vfsb`) rather than JSON. This is the default and only on-disk format for `"fs"` mode.
|
|
647
|
-
|
|
648
|
-
### Why not JSON?
|
|
649
|
-
|
|
650
|
-
The JSON+base64 approach has two compounding costs: file content is base64-encoded (33% size bloat), and the entire tree must be stringified and parsed on every save/load. For a 10 MB VFS, that means writing ~13.3 MB of base64 data wrapped in JSON — and parsing all of it as a string on restart.
|
|
651
|
-
|
|
652
|
-
The VFSB format eliminates both costs.
|
|
389
|
+
| `toSnapshot(): VfsSnapshot` | Export tree as JSON-serialisable snapshot. |
|
|
390
|
+
| `importSnapshot(snapshot)` | Replace current state from a snapshot. |
|
|
391
|
+
| `restoreMirror(): Promise<void>` | Load from disk (`"fs"` mode) / no-op otherwise. |
|
|
392
|
+
| `flushMirror(): Promise<void>` | Save to disk (`"fs"` mode) / emit event otherwise. |
|
|
393
|
+
| `VirtualFileSystem.fromSnapshot(snapshot)` | **Static.** Create memory-mode instance from snapshot. |
|
|
653
394
|
|
|
654
|
-
|
|
395
|
+
**Events:** `file:write { path, size }`, `file:read { path, size }`, `dir:create { path, mode }`, `node:remove { path }`, `symlink:create { link, target }`, `snapshot:import`, `snapshot:restore { path }`, `mirror:flush { path? }`
|
|
655
396
|
|
|
656
|
-
|
|
397
|
+
#### VFSB Binary Format
|
|
657
398
|
|
|
658
|
-
|
|
659
|
-
File header
|
|
660
|
-
[4] magic = 0x56 0x46 0x53 0x21 ("VFS!")
|
|
661
|
-
[1] version = 0x01
|
|
662
|
-
|
|
663
|
-
Node (recursive)
|
|
664
|
-
[1] type = 0x01 (file) | 0x02 (directory)
|
|
665
|
-
[2] name length (uint16)
|
|
666
|
-
[N] name bytes (UTF-8)
|
|
667
|
-
[4] mode (uint32)
|
|
668
|
-
[8] createdAt ms (float64 big-endian)
|
|
669
|
-
[8] updatedAt ms (float64 big-endian)
|
|
670
|
-
|
|
671
|
-
File node extra
|
|
672
|
-
[1] compressed flag (0x00 | 0x01)
|
|
673
|
-
[4] content length (uint32)
|
|
674
|
-
[N] content bytes (raw — no base64)
|
|
675
|
-
|
|
676
|
-
Directory node extra
|
|
677
|
-
[4] children count (uint32)
|
|
678
|
-
[N] children nodes (recursive)
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
### Performance
|
|
682
|
-
|
|
683
|
-
Measured on a VFS tree with ~50 nodes and mixed file content:
|
|
399
|
+
In `"fs"` mode, state is persisted as a compact binary file (`vfs-snapshot.vfsb`).
|
|
684
400
|
|
|
685
401
|
| Metric | JSON+base64 | VFSB binary |
|
|
686
402
|
|--------|-------------|-------------|
|
|
687
|
-
| File size (10 MB
|
|
403
|
+
| File size (10 MB content) | ~13.7 MB | ~10.0 MB |
|
|
688
404
|
| Encode time | ~12 ms | ~0.04 ms |
|
|
689
405
|
| Decode time | ~18 ms | ~0.07 ms |
|
|
690
|
-
| External dependencies | none | none |
|
|
691
|
-
|
|
692
|
-
Size reduction comes from eliminating base64 encoding (33% overhead on raw bytes) and JSON string wrapping. Speed improvement comes from sequential buffer reads/writes instead of string parsing.
|
|
693
|
-
|
|
694
|
-
### Backward compatibility
|
|
695
|
-
|
|
696
|
-
If a legacy JSON snapshot file is found at the configured `snapshotPath`, it is automatically detected by the absence of the `VFS!` magic bytes and parsed as JSON. A migration notice is logged to `console.info`. On the next `flushMirror()` call, the file is rewritten in VFSB format — no manual migration step needed.
|
|
697
|
-
|
|
698
|
-
```typescript
|
|
699
|
-
// This just works — auto-migrates any existing JSON snapshot on first flush
|
|
700
|
-
const vfs = new VirtualFileSystem({ mode: "fs", snapshotPath: "./data" });
|
|
701
|
-
await vfs.restoreMirror(); // reads JSON if .vfsb contains JSON, binary otherwise
|
|
702
|
-
await vfs.flushMirror(); // always writes VFSB binary
|
|
703
|
-
```
|
|
704
406
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
The encoder and decoder are exported from the VFS module internals for advanced use cases (e.g. replication, custom storage backends):
|
|
407
|
+
Wire format: 5-byte header (`VFS!` magic + version), followed by a recursive node tree with type, name, mode, timestamps, and raw content bytes (no base64). Legacy JSON snapshots are auto-detected and migrated on first `flushMirror()`.
|
|
708
408
|
|
|
409
|
+
Low-level API:
|
|
709
410
|
```typescript
|
|
710
411
|
import { encodeVfs, decodeVfs, isBinarySnapshot } from "typescript-virtual-container/src/VirtualFileSystem/binaryPack";
|
|
711
|
-
|
|
712
|
-
// Encode the current tree to a Buffer
|
|
713
|
-
const buf = encodeVfs(vfs.root);
|
|
714
|
-
|
|
715
|
-
// Detect format
|
|
716
|
-
isBinarySnapshot(buf); // true — starts with "VFS!" magic
|
|
717
|
-
isBinarySnapshot(jsonBuf); // false — JSON or other format
|
|
718
|
-
|
|
719
|
-
// Restore from a Buffer
|
|
412
|
+
const buf = encodeVfs(vfs.root);
|
|
720
413
|
const root = decodeVfs(buf);
|
|
414
|
+
isBinarySnapshot(buf); // true — starts with "VFS!" magic
|
|
721
415
|
```
|
|
722
416
|
|
|
723
|
-
These are low-level APIs. For normal usage, `flushMirror()` and `restoreMirror()` are all you need.
|
|
724
|
-
|
|
725
|
-
|
|
726
417
|
---
|
|
727
418
|
|
|
728
419
|
### `VirtualUserManager`
|
|
729
420
|
|
|
730
|
-
Manages
|
|
731
|
-
|
|
732
|
-
#### Constructor
|
|
421
|
+
Manages users, password hashing (scrypt), sudo privileges, storage quotas, SSH public keys, and session tracking.
|
|
733
422
|
|
|
734
423
|
```typescript
|
|
735
424
|
new VirtualUserManager(
|
|
736
425
|
vfs: VirtualFileSystem,
|
|
737
|
-
autoSudoForNewUsers?: boolean,
|
|
426
|
+
autoSudoForNewUsers?: boolean, // default: true
|
|
738
427
|
)
|
|
739
428
|
```
|
|
740
429
|
|
|
741
|
-
|
|
742
|
-
- `autoSudoForNewUsers`: when true, newly created users are automatically added to sudoers.
|
|
430
|
+
Auth data is stored at `/virtual-env-js/.auth/` inside the VFS.
|
|
743
431
|
|
|
744
|
-
|
|
432
|
+
**Methods**
|
|
745
433
|
|
|
746
434
|
| Method | Description |
|
|
747
435
|
|--------|-------------|
|
|
748
|
-
| `initialize(): Promise<void>` | Load users/sudoers
|
|
749
|
-
| `verifyPassword(username, password): boolean` | Check plaintext password
|
|
750
|
-
| `hasPassword(username): boolean` | Returns `true` if a
|
|
751
|
-
| `hashPassword(password): string` | Hash
|
|
752
|
-
| `getPasswordHash(username): string \| null` |
|
|
436
|
+
| `initialize(): Promise<void>` | Load users/sudoers, ensure root exists. |
|
|
437
|
+
| `verifyPassword(username, password): boolean` | Check plaintext password. |
|
|
438
|
+
| `hasPassword(username): boolean` | Returns `true` if a password is set. |
|
|
439
|
+
| `hashPassword(password): string` | Hash with scrypt (or SHA-256 with `SSH_MIMIC_FAST_PASSWORD_HASH=1`). |
|
|
440
|
+
| `getPasswordHash(username): string \| null` | Raw stored hash. |
|
|
753
441
|
| `addUser(username, password): Promise<void>` | Create user with home directory. |
|
|
754
|
-
| `deleteUser(username): Promise<void>` | Delete user. Throws
|
|
755
|
-
| `setPassword(username, password): Promise<void>` | Update password
|
|
756
|
-
| `isSudoer(username): boolean` | Returns `true`
|
|
757
|
-
| `addSudoer(username): Promise<void>` | Grant sudo
|
|
758
|
-
| `removeSudoer(username): Promise<void>` | Revoke sudo
|
|
759
|
-
| `setQuotaBytes(username, maxBytes): Promise<void>` | Set per-user write quota
|
|
760
|
-
| `clearQuota(username): Promise<void>` | Remove quota limit
|
|
761
|
-
| `getQuotaBytes(username): number \| null` |
|
|
762
|
-
| `getUsageBytes(username): number` |
|
|
763
|
-
| `assertWriteWithinQuota(username, path, content)` | Throws if
|
|
764
|
-
| `listUsers(): string[]` |
|
|
765
|
-
| `addAuthorizedKey(username, algo, data)` | Register
|
|
766
|
-
| `getAuthorizedKeys(username)` |
|
|
767
|
-
| `removeAuthorizedKeys(username)` | Revoke all authorized keys
|
|
768
|
-
| `registerSession(username, remoteAddress): VirtualActiveSession` |
|
|
769
|
-
| `unregisterSession(sessionId): void` | Remove
|
|
770
|
-
| `updateSession(sessionId, username, remoteAddress): void` | Update
|
|
771
|
-
| `listActiveSessions(): VirtualActiveSession[]` |
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
| Event | Data | Description |
|
|
776
|
-
|-------|------|-------------|
|
|
777
|
-
| `initialized` | — | User manager ready, root account ensured |
|
|
778
|
-
| `user:add` | `{ username }` | New user created |
|
|
779
|
-
| `user:delete` | `{ username }` | User deleted |
|
|
780
|
-
| `key:add` | `{ username, algo }` | Public key added |
|
|
781
|
-
| `key:remove` | `{ username }` | Public keys removed |
|
|
782
|
-
| `session:register` | `{ sessionId, username, remoteAddress }` | Session started |
|
|
783
|
-
| `session:unregister` | `{ sessionId, username }` | Session ended |
|
|
784
|
-
|
|
785
|
-
**Example:**
|
|
786
|
-
|
|
787
|
-
```typescript
|
|
788
|
-
users.on("user:add", ({ username }) => {
|
|
789
|
-
console.log(`[USERS] Created: ${username}`);
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
users.on("session:register", ({ sessionId, username, remoteAddress }) => {
|
|
793
|
-
console.log(`[USERS] Session ${sessionId}: ${username} from ${remoteAddress}`);
|
|
794
|
-
});
|
|
795
|
-
```
|
|
442
|
+
| `deleteUser(username): Promise<void>` | Delete user. Throws on `root` or missing user. |
|
|
443
|
+
| `setPassword(username, password): Promise<void>` | Update password. |
|
|
444
|
+
| `isSudoer(username): boolean` | Returns `true` if user has sudo. |
|
|
445
|
+
| `addSudoer(username): Promise<void>` | Grant sudo. |
|
|
446
|
+
| `removeSudoer(username): Promise<void>` | Revoke sudo. Throws on `root`. |
|
|
447
|
+
| `setQuotaBytes(username, maxBytes): Promise<void>` | Set per-user write quota. |
|
|
448
|
+
| `clearQuota(username): Promise<void>` | Remove quota limit. |
|
|
449
|
+
| `getQuotaBytes(username): number \| null` | Quota in bytes, or `null` if unlimited. |
|
|
450
|
+
| `getUsageBytes(username): number` | Current usage under `/home/<user>`. |
|
|
451
|
+
| `assertWriteWithinQuota(username, path, content)` | Throws if write would exceed quota. |
|
|
452
|
+
| `listUsers(): string[]` | Sorted list of all usernames. |
|
|
453
|
+
| `addAuthorizedKey(username, algo, data)` | Register SSH public key. |
|
|
454
|
+
| `getAuthorizedKeys(username)` | List authorized keys. |
|
|
455
|
+
| `removeAuthorizedKeys(username)` | Revoke all authorized keys. |
|
|
456
|
+
| `registerSession(username, remoteAddress): VirtualActiveSession` | Allocate virtual TTY and register session. |
|
|
457
|
+
| `unregisterSession(sessionId): void` | Remove session on disconnect. Safe with `null`. |
|
|
458
|
+
| `updateSession(sessionId, username, remoteAddress): void` | Update after `su`/`sudo` identity change. |
|
|
459
|
+
| `listActiveSessions(): VirtualActiveSession[]` | All active sessions sorted by start time. |
|
|
460
|
+
|
|
461
|
+
**Events:** `initialized`, `user:add { username }`, `user:delete { username }`, `key:add { username, algo }`, `key:remove { username }`, `session:register { sessionId, username, remoteAddress }`, `session:unregister { sessionId, username }`
|
|
796
462
|
|
|
797
463
|
---
|
|
798
464
|
|
|
799
|
-
|
|
800
465
|
### `VirtualPackageManager`
|
|
801
466
|
|
|
802
|
-
Simulates APT/dpkg
|
|
467
|
+
Simulates APT/dpkg backed by a 25-package registry. Accessed via `shell.packageManager`.
|
|
803
468
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
Instantiated automatically by `VirtualShell`. Not constructed directly.
|
|
807
|
-
|
|
808
|
-
#### Methods
|
|
469
|
+
**Methods**
|
|
809
470
|
|
|
810
471
|
| Method | Description |
|
|
811
472
|
|--------|-------------|
|
|
812
|
-
| `load()` | Load installed packages from `/var/lib/dpkg/status` (called on shell init) |
|
|
813
473
|
| `install(names, opts?)` | Install packages (resolves deps, writes files to VFS). Returns `{ output, exitCode }` |
|
|
814
|
-
| `remove(names, opts?)` | Remove
|
|
815
|
-
| `search(term)` | Search
|
|
816
|
-
| `show(name)` |
|
|
817
|
-
| `listInstalled()` |
|
|
818
|
-
| `listAvailable()` |
|
|
819
|
-
| `isInstalled(name)` | Returns `true` if
|
|
820
|
-
| `installedCount()` | Count of installed packages
|
|
821
|
-
| `findInRegistry(name)` | Look up
|
|
822
|
-
|
|
823
|
-
#### Types
|
|
824
|
-
|
|
825
|
-
```ts
|
|
826
|
-
interface PackageDefinition {
|
|
827
|
-
name: string;
|
|
828
|
-
version: string;
|
|
829
|
-
description: string;
|
|
830
|
-
files?: PackageFile[]; // written to VFS on install
|
|
831
|
-
depends?: string[]; // resolved recursively
|
|
832
|
-
onInstall?: (vfs, users) => void;
|
|
833
|
-
onRemove?: (vfs) => void;
|
|
834
|
-
}
|
|
474
|
+
| `remove(names, opts?)` | Remove. `opts.purge` also removes config files. |
|
|
475
|
+
| `search(term)` | Search by name or description. |
|
|
476
|
+
| `show(name)` | dpkg-style metadata block. |
|
|
477
|
+
| `listInstalled()` | All installed packages as `InstalledPackage[]`. |
|
|
478
|
+
| `listAvailable()` | All registry packages. |
|
|
479
|
+
| `isInstalled(name)` | Returns `true` if installed. |
|
|
480
|
+
| `installedCount()` | Count of installed packages. |
|
|
481
|
+
| `findInRegistry(name)` | Look up `PackageDefinition` by name. |
|
|
835
482
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
483
|
+
**Custom packages:**
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
const customPkg = {
|
|
487
|
+
name: "myapp",
|
|
488
|
+
version: "1.0.0",
|
|
489
|
+
description: "My application",
|
|
490
|
+
files: [
|
|
491
|
+
{ path: "/usr/bin/myapp", content: "#!/bin/sh\necho myapp v1.0.0\n", mode: 0o755 },
|
|
492
|
+
{ path: "/etc/myapp/config.json", content: JSON.stringify({ port: 3000 }) },
|
|
493
|
+
],
|
|
494
|
+
onInstall: (vfs) => {
|
|
495
|
+
vfs.mkdir("/var/lib/myapp", 0o755);
|
|
496
|
+
vfs.mkdir("/var/log/myapp", 0o755);
|
|
497
|
+
},
|
|
498
|
+
};
|
|
843
499
|
```
|
|
844
500
|
|
|
845
501
|
---
|
|
846
502
|
|
|
847
503
|
### Snapshot Diff Tooling
|
|
848
504
|
|
|
849
|
-
Three utility functions for comparing VFS snapshots in tests and deployment verification.
|
|
850
|
-
|
|
851
505
|
```typescript
|
|
852
|
-
import {
|
|
853
|
-
diffSnapshots,
|
|
854
|
-
formatDiff,
|
|
855
|
-
assertDiff,
|
|
856
|
-
} from "typescript-virtual-container";
|
|
506
|
+
import { diffSnapshots, formatDiff, assertDiff } from "typescript-virtual-container";
|
|
857
507
|
```
|
|
858
508
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
Compares two snapshots and returns structured diff results.
|
|
509
|
+
**`diffSnapshots(before, after, options?): VfsDiff`**
|
|
862
510
|
|
|
863
|
-
```
|
|
511
|
+
```typescript
|
|
864
512
|
const before = shell.vfs.toSnapshot();
|
|
865
513
|
await client.exec("apt install vim && mkdir -p /app");
|
|
866
514
|
const after = shell.vfs.toSnapshot();
|
|
867
515
|
|
|
868
|
-
const diff = diffSnapshots(before, after, {
|
|
869
|
-
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
diff.clean; // false
|
|
873
|
-
diff.added; // [{ path: "/usr/bin/vim", type: "file" }, ...]
|
|
874
|
-
diff.removed; // []
|
|
875
|
-
diff.modified; // [{ path: "/var/lib/dpkg/status", before: "...", after: "..." }]
|
|
876
|
-
```
|
|
877
|
-
|
|
878
|
-
#### `formatDiff(diff, options?): string`
|
|
879
|
-
|
|
880
|
-
Human-readable output similar to `git diff --stat`.
|
|
881
|
-
|
|
882
|
-
```ts
|
|
883
|
-
console.log(formatDiff(diff));
|
|
884
|
-
// + /app [directory]
|
|
885
|
-
// + /usr/bin/vim [file]
|
|
886
|
-
// ~ /var/lib/dpkg/status [modified]
|
|
887
|
-
//
|
|
888
|
-
// 2 added, 1 modified
|
|
889
|
-
|
|
890
|
-
console.log(formatDiff(diff, { showContent: true, maxContentChars: 80 }));
|
|
516
|
+
const diff = diffSnapshots(before, after, { ignore: ["/proc", "/var/log"] });
|
|
517
|
+
diff.clean; // false
|
|
518
|
+
diff.added; // [{ path: "/usr/bin/vim", type: "file" }, ...]
|
|
519
|
+
diff.modified; // [{ path: "/var/lib/dpkg/status", before: "...", after: "..." }]
|
|
891
520
|
```
|
|
892
521
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
Throws on mismatch — designed for test suites.
|
|
896
|
-
|
|
897
|
-
```ts
|
|
898
|
-
assertDiff(diff, {
|
|
899
|
-
added: ["/app", "/usr/bin/vim"],
|
|
900
|
-
modified: ["/var/lib/dpkg/status"],
|
|
901
|
-
});
|
|
902
|
-
// throws if any expected path is not in the diff
|
|
903
|
-
```
|
|
522
|
+
**`formatDiff(diff, options?): string`** — human-readable output like `git diff --stat`. Options: `showContent: boolean`, `maxContentChars: number`.
|
|
904
523
|
|
|
905
|
-
|
|
524
|
+
**`assertDiff(diff, expected): void`** — throws on mismatch, designed for test suites:
|
|
906
525
|
|
|
907
|
-
```
|
|
908
|
-
|
|
909
|
-
added: VfsDiffEntry[]; // { path, type }
|
|
910
|
-
removed: VfsDiffEntry[]; // { path, type }
|
|
911
|
-
modified: VfsDiffModified[]; // { path, type, before, after }
|
|
912
|
-
clean: boolean;
|
|
913
|
-
}
|
|
526
|
+
```typescript
|
|
527
|
+
assertDiff(diff, { added: ["/app", "/usr/bin/vim"], modified: ["/var/lib/dpkg/status"] });
|
|
914
528
|
```
|
|
915
529
|
|
|
916
530
|
---
|
|
917
531
|
|
|
918
532
|
### `HoneyPot`
|
|
919
533
|
|
|
920
|
-
Comprehensive security auditing
|
|
921
|
-
|
|
922
|
-
#### Constructor
|
|
534
|
+
Comprehensive security auditing. Attaches to all core components to log activity and detect anomalies.
|
|
923
535
|
|
|
924
536
|
```typescript
|
|
925
|
-
new HoneyPot(maxLogSize?: number)
|
|
537
|
+
new HoneyPot(maxLogSize?: number) // default: 10000
|
|
926
538
|
```
|
|
927
539
|
|
|
928
|
-
|
|
540
|
+
**Methods**
|
|
929
541
|
|
|
930
542
|
| Method | Description |
|
|
931
543
|
|--------|-------------|
|
|
932
544
|
| `attach(shell, vfs, users, ssh?, sftp?)` | Subscribe to all event sources. |
|
|
933
|
-
| `getAuditLog(type?, source?): AuditLogEntry[]` | Full log, optionally filtered
|
|
545
|
+
| `getAuditLog(type?, source?): AuditLogEntry[]` | Full log, optionally filtered. |
|
|
934
546
|
| `getStats(): Readonly<HoneyPotStats>` | Aggregated activity counters. |
|
|
935
|
-
| `getRecent(limit?): AuditLogEntry[]` | Most recent entries
|
|
936
|
-
| `detectAnomalies()` |
|
|
937
|
-
| `reset()` | Clear
|
|
938
|
-
| `exportJson(): string` | Serialise full log + stats to
|
|
547
|
+
| `getRecent(limit?): AuditLogEntry[]` | Most recent entries, reverse-chronological. |
|
|
548
|
+
| `detectAnomalies()` | Returns `{ type, severity, message }[]`. |
|
|
549
|
+
| `reset()` | Clear log and reset counters. |
|
|
550
|
+
| `exportJson(): string` | Serialise full log + stats to JSON. |
|
|
939
551
|
|
|
940
|
-
|
|
552
|
+
`detectAnomalies` detects: high auth failure rates, excessive failures, unusual command volume, unusual file write volume.
|
|
941
553
|
|
|
942
554
|
```typescript
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
authSuccesses: number;
|
|
946
|
-
authFailures: number;
|
|
947
|
-
commands: number;
|
|
948
|
-
fileWrites: number;
|
|
949
|
-
fileReads: number;
|
|
950
|
-
sessionStarts: number;
|
|
951
|
-
sessionEnds: number;
|
|
952
|
-
userCreated: number;
|
|
953
|
-
userDeleted: number;
|
|
954
|
-
clientConnects: number;
|
|
955
|
-
clientDisconnects: number;
|
|
956
|
-
}
|
|
957
|
-
```
|
|
555
|
+
const hp = new HoneyPot(50_000);
|
|
556
|
+
hp.attach(shell, shell.vfs, shell.users, ssh);
|
|
958
557
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
interface AuditLogEntry {
|
|
963
|
-
timestamp: string; // ISO-8601
|
|
964
|
-
type: string; // e.g. "auth:failure", "file:write"
|
|
965
|
-
source: string; // e.g. "SshMimic", "VirtualFileSystem"
|
|
966
|
-
details: Record<string, unknown>; // event-specific payload
|
|
967
|
-
}
|
|
968
|
-
```
|
|
969
|
-
|
|
970
|
-
`detectAnomalies` detects: high authentication failure rates, excessive auth failures, unusual command volume, unusual file write volume.
|
|
971
|
-
|
|
972
|
-
#### Example
|
|
973
|
-
|
|
974
|
-
```typescript
|
|
975
|
-
import { HoneyPot, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
976
|
-
|
|
977
|
-
const shell = new VirtualShell("honeypot");
|
|
978
|
-
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
979
|
-
const hp = new HoneyPot(50_000);
|
|
980
|
-
|
|
981
|
-
await ssh.start();
|
|
982
|
-
hp.attach(shell, shell.vfs, shell.users, ssh);
|
|
983
|
-
|
|
984
|
-
// Filter audit log
|
|
985
|
-
const failures = hp.getAuditLog("auth:failure");
|
|
986
|
-
failures.forEach(e => console.log(e.details.username, e.details.remoteAddress));
|
|
558
|
+
hp.getAuditLog("auth:failure").forEach(e =>
|
|
559
|
+
console.log(e.details.username, e.details.remoteAddress)
|
|
560
|
+
);
|
|
987
561
|
|
|
988
|
-
// Detect anomalies
|
|
989
562
|
hp.detectAnomalies().forEach(a =>
|
|
990
563
|
console.log(`[${a.severity.toUpperCase()}] ${a.type}: ${a.message}`)
|
|
991
564
|
);
|
|
992
565
|
|
|
993
|
-
// Export on shutdown
|
|
994
566
|
process.on("SIGINT", () => {
|
|
995
567
|
require("fs").writeFileSync("audit.json", hp.exportJson());
|
|
996
568
|
process.exit(0);
|
|
@@ -1001,68 +573,35 @@ process.on("SIGINT", () => {
|
|
|
1001
573
|
|
|
1002
574
|
### `SshClient` (Programmatic API)
|
|
1003
575
|
|
|
1004
|
-
Execute shell commands against a `VirtualShell` without SSH
|
|
1005
|
-
|
|
1006
|
-
#### Constructor
|
|
576
|
+
Execute shell commands against a `VirtualShell` without SSH overhead. Maintains working-directory state.
|
|
1007
577
|
|
|
1008
578
|
```typescript
|
|
1009
579
|
new SshClient(shell: VirtualShell, username: string)
|
|
1010
580
|
```
|
|
1011
581
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
#### Methods
|
|
582
|
+
**Methods**
|
|
1015
583
|
|
|
1016
584
|
| Method | Description |
|
|
1017
585
|
|--------|-------------|
|
|
1018
|
-
| `exec(command): Promise<CommandResult>` | Run
|
|
1019
|
-
| `ls(path?)` | List directory
|
|
586
|
+
| `exec(command): Promise<CommandResult>` | Run raw command string (supports `&&`, `\|`, etc.). |
|
|
587
|
+
| `ls(path?)` | List directory. |
|
|
1020
588
|
| `pwd()` | Print current working directory. |
|
|
1021
|
-
| `cd(path)` | Change directory. Updates internal cwd
|
|
1022
|
-
| `cat(path)` | Read file
|
|
1023
|
-
| `readFile(path)` | Read file directly from VFS
|
|
1024
|
-
| `writeFile(path, content)` | Write file directly to VFS
|
|
1025
|
-
| `mkdir(path, recursive?)` | Create directory.
|
|
589
|
+
| `cd(path)` | Change directory. Updates internal cwd on success. |
|
|
590
|
+
| `cat(path)` | Read file via `cat` command. |
|
|
591
|
+
| `readFile(path)` | Read file directly from VFS. |
|
|
592
|
+
| `writeFile(path, content)` | Write file directly to VFS. |
|
|
593
|
+
| `mkdir(path, recursive?)` | Create directory. |
|
|
1026
594
|
| `touch(path)` | Create empty file. |
|
|
1027
|
-
| `rm(path, recursive?)` | Remove file or directory.
|
|
595
|
+
| `rm(path, recursive?)` | Remove file or directory. |
|
|
1028
596
|
| `tree(path?)` | Render ASCII directory tree. |
|
|
1029
|
-
| `whoami()` |
|
|
1030
|
-
| `
|
|
1031
|
-
| `who()` | List active sessions. |
|
|
1032
|
-
| `getCwd(): string` | Returns current working directory (local, no I/O). |
|
|
597
|
+
| `whoami()` / `hostname()` / `who()` | System info commands. |
|
|
598
|
+
| `getCwd(): string` | Returns current cwd (no I/O). |
|
|
1033
599
|
| `getUsername(): string` | Returns authenticated username. |
|
|
1034
600
|
|
|
1035
|
-
**Example:**
|
|
1036
|
-
|
|
1037
|
-
```typescript
|
|
1038
|
-
const shell = new VirtualShell("typescript-vm");
|
|
1039
|
-
const client = new SshClient(shell, "alice");
|
|
1040
|
-
|
|
1041
|
-
await client.mkdir("/home/alice/projects", true);
|
|
1042
|
-
await client.cd("/home/alice/projects");
|
|
1043
|
-
|
|
1044
|
-
console.log(client.getCwd()); // /home/alice/projects
|
|
1045
|
-
|
|
1046
|
-
await client.writeFile("notes.txt", "Work in progress");
|
|
1047
|
-
const list = await client.ls();
|
|
1048
|
-
console.log(list.stdout); // notes.txt
|
|
1049
|
-
|
|
1050
|
-
const read = await client.readFile("notes.txt");
|
|
1051
|
-
console.log(read.stdout); // Work in progress
|
|
1052
|
-
|
|
1053
|
-
// Shell operators work in exec()
|
|
1054
|
-
const r = await client.exec("echo hello && echo world");
|
|
1055
|
-
console.log(r.stdout); // hello\nworld
|
|
1056
|
-
```
|
|
1057
|
-
|
|
1058
601
|
---
|
|
1059
602
|
|
|
1060
603
|
### Key Types
|
|
1061
604
|
|
|
1062
|
-
#### `CommandResult`
|
|
1063
|
-
|
|
1064
|
-
Returned by all command executions (shell or programmatic).
|
|
1065
|
-
|
|
1066
605
|
```typescript
|
|
1067
606
|
interface CommandResult {
|
|
1068
607
|
stdout?: string;
|
|
@@ -1076,32 +615,18 @@ interface CommandResult {
|
|
|
1076
615
|
openHtop?: boolean;
|
|
1077
616
|
sudoChallenge?: SudoChallenge;
|
|
1078
617
|
}
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
#### `ShellEnv`
|
|
1082
|
-
|
|
1083
|
-
Per-session shell environment. Passed as `env` in `CommandContext`.
|
|
1084
618
|
|
|
1085
|
-
```typescript
|
|
1086
619
|
interface ShellEnv {
|
|
1087
|
-
vars: Record<string, string>;
|
|
1088
|
-
lastExitCode: number;
|
|
620
|
+
vars: Record<string, string>; // $VAR accessible in expansions
|
|
621
|
+
lastExitCode: number; // $?
|
|
1089
622
|
}
|
|
1090
|
-
```
|
|
1091
|
-
|
|
1092
|
-
Default variables initialized per session: `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TERM`, `HOSTNAME`, `PS1`.
|
|
1093
623
|
|
|
1094
|
-
#### `ShellModule`
|
|
1095
|
-
|
|
1096
|
-
Contract for custom command plugins:
|
|
1097
|
-
|
|
1098
|
-
```typescript
|
|
1099
624
|
interface ShellModule {
|
|
1100
625
|
name: string;
|
|
1101
626
|
params: string[];
|
|
1102
627
|
aliases?: string[];
|
|
1103
|
-
description?: string;
|
|
1104
|
-
category?: string;
|
|
628
|
+
description?: string;
|
|
629
|
+
category?: string; // navigation|files|text|archive|system|package|network|shell|users|misc
|
|
1105
630
|
run: (ctx: CommandContext) => CommandResult | Promise<CommandResult>;
|
|
1106
631
|
}
|
|
1107
632
|
|
|
@@ -1115,157 +640,86 @@ interface CommandContext {
|
|
|
1115
640
|
stdin?: string;
|
|
1116
641
|
cwd: string;
|
|
1117
642
|
shell: VirtualShell;
|
|
1118
|
-
env: ShellEnv;
|
|
1119
|
-
}
|
|
1120
|
-
```
|
|
1121
|
-
|
|
1122
|
-
#### `VfsNodeStats`
|
|
1123
|
-
|
|
1124
|
-
```typescript
|
|
1125
|
-
type VfsNodeStats = VfsFileNode | VfsDirectoryNode;
|
|
1126
|
-
|
|
1127
|
-
interface VfsFileNode {
|
|
1128
|
-
type: "file";
|
|
1129
|
-
name: string;
|
|
1130
|
-
path: string;
|
|
1131
|
-
mode: number;
|
|
1132
|
-
size: number;
|
|
1133
|
-
compressed: boolean;
|
|
1134
|
-
createdAt: Date;
|
|
1135
|
-
updatedAt: Date;
|
|
643
|
+
env: ShellEnv;
|
|
1136
644
|
}
|
|
1137
645
|
|
|
1138
|
-
interface VfsDirectoryNode {
|
|
1139
|
-
type: "directory";
|
|
1140
|
-
name: string;
|
|
1141
|
-
path: string;
|
|
1142
|
-
mode: number;
|
|
1143
|
-
childrenCount: number;
|
|
1144
|
-
createdAt: Date;
|
|
1145
|
-
updatedAt: Date;
|
|
1146
|
-
}
|
|
1147
|
-
```
|
|
1148
|
-
|
|
1149
|
-
#### `VirtualActiveSession`
|
|
1150
|
-
|
|
1151
|
-
```typescript
|
|
1152
646
|
interface VirtualActiveSession {
|
|
1153
647
|
id: string;
|
|
1154
648
|
username: string;
|
|
1155
649
|
tty: string;
|
|
1156
650
|
remoteAddress: string;
|
|
1157
|
-
startedAt: string;
|
|
1158
|
-
}
|
|
1159
|
-
```
|
|
1160
|
-
|
|
1161
|
-
#### `VfsSnapshot`
|
|
1162
|
-
|
|
1163
|
-
```typescript
|
|
1164
|
-
interface VfsSnapshot {
|
|
1165
|
-
root: VfsSnapshotDirectoryNode;
|
|
651
|
+
startedAt: string; // ISO-8601
|
|
1166
652
|
}
|
|
1167
|
-
// File nodes store content as base64 in contentBase64.
|
|
1168
653
|
```
|
|
1169
654
|
|
|
1170
655
|
---
|
|
1171
656
|
|
|
1172
657
|
### Command Helpers
|
|
1173
658
|
|
|
1174
|
-
Three utility functions are exported from `typescript-virtual-container` to assist with argument parsing inside custom command handlers.
|
|
1175
|
-
|
|
1176
|
-
#### `ifFlag(args, flags): boolean`
|
|
1177
|
-
|
|
1178
|
-
Returns `true` when any of the given flags appear in the argument array. Matches both standalone tokens (`-s`, `--silent`) and inline forms (`--output=file`).
|
|
1179
|
-
|
|
1180
659
|
```typescript
|
|
1181
|
-
import { ifFlag } from "typescript-virtual-container";
|
|
660
|
+
import { ifFlag, getFlag, getArg, parseArgs } from "typescript-virtual-container";
|
|
1182
661
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
```
|
|
662
|
+
// ifFlag — true if any given flag appears in args
|
|
663
|
+
ifFlag(args, ["-r", "--recursive"]) // boolean
|
|
1186
664
|
|
|
1187
|
-
|
|
665
|
+
// getFlag — value, true if valueless, undefined if absent
|
|
666
|
+
getFlag(args, ["-o", "--output"])
|
|
667
|
+
// ["--output", "file.txt"] → "file.txt"
|
|
668
|
+
// ["--output=file.txt"] → "file.txt"
|
|
669
|
+
// ["--verbose"] → true
|
|
670
|
+
// [] → undefined
|
|
1188
671
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
672
|
+
// getArg — positional at index N, skipping known flags
|
|
673
|
+
// args = ["-r", "src", "dest"]
|
|
674
|
+
getArg(args, 0, { flags: ["-r"] }) // "src"
|
|
675
|
+
getArg(args, 1, { flags: ["-r"] }) // "dest"
|
|
1193
676
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
677
|
+
// parseArgs — structured parse
|
|
678
|
+
const { flags, flagsWithValues, positionals } = parseArgs(args, {
|
|
679
|
+
flags: ["-r", "--recursive"],
|
|
680
|
+
flagsWithValue: ["-o", "--output"],
|
|
681
|
+
});
|
|
1199
682
|
```
|
|
1200
683
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
Returns the positional argument at a given zero-based index, skipping known flags and their values.
|
|
1204
|
-
|
|
1205
|
-
```typescript
|
|
1206
|
-
import { getArg } from "typescript-virtual-container";
|
|
1207
|
-
|
|
1208
|
-
// args = ["-r", "src", "dest"]
|
|
1209
|
-
const src = getArg(args, 0, { flags: ["-r"] }); // "src"
|
|
1210
|
-
const dest = getArg(args, 1, { flags: ["-r"] }); // "dest"
|
|
1211
|
-
```
|
|
684
|
+
</details>
|
|
1212
685
|
|
|
1213
686
|
---
|
|
1214
687
|
|
|
1215
|
-
|
|
688
|
+
<details>
|
|
689
|
+
<summary><strong>Examples</strong></summary>
|
|
1216
690
|
|
|
1217
|
-
###
|
|
691
|
+
### SSH Server with Events
|
|
1218
692
|
|
|
1219
693
|
```typescript
|
|
1220
694
|
import { VirtualSshServer } from "typescript-virtual-container";
|
|
1221
695
|
|
|
1222
696
|
const ssh = new VirtualSshServer({ port: 2222, hostname: "lab-environment" });
|
|
1223
|
-
await ssh.start();
|
|
1224
|
-
console.log("SSH server ready. Connect: ssh root@localhost -p 2222");
|
|
1225
697
|
|
|
1226
|
-
|
|
1227
|
-
|
|
698
|
+
ssh.on("auth:success", ({ username, remoteAddress }) => {
|
|
699
|
+
console.log(`[SSH] ${username} from ${remoteAddress}`);
|
|
700
|
+
});
|
|
701
|
+
ssh.on("auth:lockout", ({ ip, until }) => {
|
|
702
|
+
console.warn(`[SSH] ${ip} locked until ${until}`);
|
|
703
|
+
});
|
|
1228
704
|
|
|
1229
|
-
|
|
1230
|
-
ssh
|
|
1231
|
-
# $ whoami
|
|
1232
|
-
# root
|
|
705
|
+
await ssh.start();
|
|
706
|
+
process.on("SIGINT", () => { ssh.stop(); process.exit(0); });
|
|
1233
707
|
```
|
|
1234
708
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
### Example 2: Programmatic File Operations
|
|
709
|
+
### SSH + SFTP with Shared State
|
|
1238
710
|
|
|
1239
711
|
```typescript
|
|
1240
|
-
import {
|
|
1241
|
-
|
|
1242
|
-
const shell = new VirtualShell("typescript-vm");
|
|
1243
|
-
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
1244
|
-
await ssh.start();
|
|
1245
|
-
|
|
1246
|
-
const client = new SshClient(shell, "root");
|
|
1247
|
-
|
|
1248
|
-
await client.mkdir("/app/config", true);
|
|
1249
|
-
await client.mkdir("/app/logs", true);
|
|
1250
|
-
|
|
1251
|
-
await client.writeFile("/app/config/settings.json", JSON.stringify({
|
|
1252
|
-
environment: "dev",
|
|
1253
|
-
port: 8080,
|
|
1254
|
-
debug: true,
|
|
1255
|
-
}, null, 2));
|
|
1256
|
-
|
|
1257
|
-
const result = await client.readFile("/app/config/settings.json");
|
|
1258
|
-
console.log("Config:", result.stdout);
|
|
712
|
+
import { VirtualSftpServer, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
1259
713
|
|
|
1260
|
-
const
|
|
1261
|
-
|
|
714
|
+
const shell = new VirtualShell("my-container");
|
|
715
|
+
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
716
|
+
const sftp = new VirtualSftpServer({ port: 2223, shell });
|
|
1262
717
|
|
|
1263
|
-
ssh.
|
|
718
|
+
await ssh.start();
|
|
719
|
+
await sftp.start();
|
|
1264
720
|
```
|
|
1265
721
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
### Example 3: Multi-User Environment with Quotas
|
|
722
|
+
### Multi-User Environment with Quotas
|
|
1269
723
|
|
|
1270
724
|
```typescript
|
|
1271
725
|
import { SshClient, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
@@ -1275,10 +729,8 @@ const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
|
1275
729
|
await ssh.start();
|
|
1276
730
|
|
|
1277
731
|
const users = ssh.getUsers()!;
|
|
1278
|
-
|
|
1279
732
|
await users.addUser("alice", "alice123");
|
|
1280
|
-
await users.addUser("bob",
|
|
1281
|
-
|
|
733
|
+
await users.addUser("bob", "bob456");
|
|
1282
734
|
await users.removeSudoer("bob");
|
|
1283
735
|
await users.setQuotaBytes("bob", 5 * 1024 * 1024); // 5 MB
|
|
1284
736
|
|
|
@@ -1286,59 +738,43 @@ const alice = new SshClient(shell, "alice");
|
|
|
1286
738
|
await alice.writeFile("/etc/important.conf", "secret=yes");
|
|
1287
739
|
|
|
1288
740
|
const bob = new SshClient(shell, "bob");
|
|
1289
|
-
const
|
|
1290
|
-
console.log(
|
|
1291
|
-
|
|
1292
|
-
ssh.stop();
|
|
741
|
+
const r = await bob.cat("/etc/important.conf");
|
|
742
|
+
console.log(r.stderr); // permission denied
|
|
1293
743
|
```
|
|
1294
744
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
### Example 4: Persistent State across Restarts
|
|
1298
|
-
|
|
1299
|
-
#### Memory mode (manual)
|
|
745
|
+
### Persistent State
|
|
1300
746
|
|
|
1301
747
|
```typescript
|
|
748
|
+
// FS mode — automatic .vfsb persistence
|
|
749
|
+
const shell = new VirtualShell("my-vm", undefined, {
|
|
750
|
+
mode: "fs",
|
|
751
|
+
snapshotPath: "./container-data",
|
|
752
|
+
});
|
|
753
|
+
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
754
|
+
await ssh.start();
|
|
755
|
+
// Restored from ./container-data/vfs-snapshot.vfsb on start, saved on every write.
|
|
756
|
+
|
|
757
|
+
// Memory mode — manual JSON snapshot
|
|
1302
758
|
import { VirtualFileSystem } from "typescript-virtual-container";
|
|
1303
759
|
import { writeFileSync, readFileSync } from "node:fs";
|
|
1304
760
|
|
|
1305
761
|
const vfs = new VirtualFileSystem();
|
|
1306
762
|
vfs.writeFile("/data/report.txt", "Baseline data");
|
|
1307
|
-
|
|
1308
|
-
// JSON — portable/human-readable. For binary persistence use mode: "fs".
|
|
1309
763
|
writeFileSync("snapshot.json", JSON.stringify(vfs.toSnapshot()));
|
|
1310
764
|
|
|
1311
|
-
const
|
|
1312
|
-
|
|
765
|
+
const restored = VirtualFileSystem.fromSnapshot(
|
|
766
|
+
JSON.parse(readFileSync("snapshot.json", "utf8"))
|
|
767
|
+
);
|
|
1313
768
|
console.log(restored.readFile("/data/report.txt")); // Baseline data
|
|
1314
769
|
```
|
|
1315
770
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
```typescript
|
|
1319
|
-
import { VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
1320
|
-
|
|
1321
|
-
const shell = new VirtualShell("my-vm", undefined, {
|
|
1322
|
-
mode: "fs",
|
|
1323
|
-
snapshotPath: "./container-data",
|
|
1324
|
-
});
|
|
1325
|
-
|
|
1326
|
-
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
1327
|
-
await ssh.start();
|
|
1328
|
-
process.on("SIGTERM", () => { ssh.stop(); process.exit(0); });
|
|
1329
|
-
```
|
|
1330
|
-
|
|
1331
|
-
---
|
|
1332
|
-
|
|
1333
|
-
### Example 5: Public-Key Authentication
|
|
771
|
+
### Public-Key Authentication
|
|
1334
772
|
|
|
1335
773
|
```typescript
|
|
1336
|
-
import { VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
1337
774
|
import { readFileSync } from "node:fs";
|
|
1338
775
|
|
|
1339
776
|
const shell = new VirtualShell("secure-vm");
|
|
1340
777
|
await shell.ensureInitialized();
|
|
1341
|
-
|
|
1342
778
|
await shell.users.addUser("alice", "fallback-password");
|
|
1343
779
|
|
|
1344
780
|
const pubLine = readFileSync(`${process.env.HOME}/.ssh/id_ed25519.pub`, "utf8").trim();
|
|
@@ -1350,9 +786,7 @@ await ssh.start();
|
|
|
1350
786
|
// ssh -i ~/.ssh/id_ed25519 alice@localhost -p 2222
|
|
1351
787
|
```
|
|
1352
788
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
### Example 6: Rate Limiting
|
|
789
|
+
### Rate Limiting
|
|
1356
790
|
|
|
1357
791
|
```typescript
|
|
1358
792
|
const ssh = new VirtualSshServer({
|
|
@@ -1360,81 +794,26 @@ const ssh = new VirtualSshServer({
|
|
|
1360
794
|
maxAuthAttempts: 3,
|
|
1361
795
|
lockoutDurationMs: 300_000,
|
|
1362
796
|
});
|
|
1363
|
-
|
|
1364
|
-
ssh.on("auth:lockout", ({ ip, until }) => {
|
|
1365
|
-
console.warn(`[SSH] ${ip} locked until ${until}`);
|
|
1366
|
-
});
|
|
1367
|
-
|
|
797
|
+
ssh.on("auth:lockout", ({ ip, until }) => console.warn(`${ip} locked until ${until}`));
|
|
1368
798
|
ssh.clearLockout("192.168.1.100"); // manual override
|
|
1369
799
|
```
|
|
1370
800
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
### Example 7: Shell Operators and Variables
|
|
801
|
+
### Shell Operators and Variables
|
|
1374
802
|
|
|
1375
803
|
```typescript
|
|
1376
|
-
import { SshClient, VirtualShell } from "typescript-virtual-container";
|
|
1377
|
-
|
|
1378
|
-
const shell = new VirtualShell("typescript-vm");
|
|
1379
|
-
await shell.ensureInitialized();
|
|
1380
804
|
const client = new SshClient(shell, "root");
|
|
1381
805
|
|
|
1382
|
-
// && and || operators
|
|
1383
806
|
await client.exec("mkdir /tmp/test && echo created || echo failed");
|
|
807
|
+
await client.exec("export GREETING=hello && echo $GREETING world");
|
|
808
|
+
await client.exec("false; echo exit=$?"); // exit=1
|
|
1384
809
|
|
|
1385
|
-
// Chaining with ;
|
|
1386
|
-
await client.exec("echo a; echo b; echo c");
|
|
1387
|
-
|
|
1388
|
-
// Variable expansion via export then use
|
|
1389
|
-
await client.exec("export GREETING=hello");
|
|
1390
|
-
await client.exec("echo $GREETING world"); // hello world
|
|
1391
|
-
|
|
1392
|
-
// $? last exit code
|
|
1393
|
-
await client.exec("false; echo exit=$?"); // exit=1
|
|
1394
|
-
|
|
1395
|
-
// Piping
|
|
1396
810
|
const r = await client.exec("echo -e 'banana\\napple\\ncherry' | sort");
|
|
1397
811
|
console.log(r.stdout); // apple\nbanana\ncherry
|
|
1398
812
|
```
|
|
1399
813
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
### Example 8: .bashrc
|
|
1403
|
-
|
|
1404
|
-
```typescript
|
|
1405
|
-
import { VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
1406
|
-
|
|
1407
|
-
const shell = new VirtualShell("typescript-vm");
|
|
1408
|
-
await shell.ensureInitialized();
|
|
1409
|
-
|
|
1410
|
-
// Write a .bashrc for the root user
|
|
1411
|
-
shell.vfs.mkdir("/home/root", 0o755);
|
|
1412
|
-
shell.vfs.writeFile("/home/root/.bashrc", `
|
|
1413
|
-
export PS1="\\u@\\h:\\w\\$ "
|
|
1414
|
-
export EDITOR=nano
|
|
1415
|
-
export PATH="/usr/local/bin:/usr/bin:/bin"
|
|
1416
|
-
alias ll="ls -l"
|
|
1417
|
-
echo "Welcome back, root!"
|
|
1418
|
-
`.trim());
|
|
1419
|
-
|
|
1420
|
-
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
1421
|
-
await ssh.start();
|
|
1422
|
-
// On interactive login, .bashrc is sourced automatically.
|
|
1423
|
-
// "Welcome back, root!" is printed, and $EDITOR is set in the session.
|
|
1424
|
-
```
|
|
1425
|
-
|
|
1426
|
-
---
|
|
1427
|
-
|
|
1428
|
-
### Example 9: Shell Scripting
|
|
814
|
+
### Shell Scripting
|
|
1429
815
|
|
|
1430
816
|
```typescript
|
|
1431
|
-
import { SshClient, VirtualShell } from "typescript-virtual-container";
|
|
1432
|
-
|
|
1433
|
-
const shell = new VirtualShell("typescript-vm");
|
|
1434
|
-
await shell.ensureInitialized();
|
|
1435
|
-
const client = new SshClient(shell, "root");
|
|
1436
|
-
|
|
1437
|
-
// Write a script to VFS
|
|
1438
817
|
shell.vfs.writeFile("/usr/local/bin/setup.sh", `
|
|
1439
818
|
#!/bin/sh
|
|
1440
819
|
for dir in config logs tmp; do
|
|
@@ -1443,23 +822,13 @@ for dir in config logs tmp; do
|
|
|
1443
822
|
done
|
|
1444
823
|
if [ -d /app/config ]; then
|
|
1445
824
|
echo "Setup complete"
|
|
1446
|
-
else
|
|
1447
|
-
echo "Setup failed"
|
|
1448
825
|
fi
|
|
1449
826
|
`);
|
|
1450
|
-
|
|
1451
|
-
// Execute it
|
|
1452
827
|
const r = await client.exec("sh /usr/local/bin/setup.sh");
|
|
1453
828
|
console.log(r.stdout);
|
|
1454
|
-
// Created /app/config
|
|
1455
|
-
// Created /app/logs
|
|
1456
|
-
// Created /app/tmp
|
|
1457
|
-
// Setup complete
|
|
1458
829
|
```
|
|
1459
830
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
### Example 10: Snapshot-Based Test Fixtures
|
|
831
|
+
### Snapshot-Based Test Fixtures
|
|
1463
832
|
|
|
1464
833
|
```typescript
|
|
1465
834
|
import { VirtualFileSystem } from "typescript-virtual-container";
|
|
@@ -1469,26 +838,37 @@ function buildFixture(): VfsSnapshot {
|
|
|
1469
838
|
const vfs = new VirtualFileSystem();
|
|
1470
839
|
vfs.mkdir("/app/config");
|
|
1471
840
|
vfs.writeFile("/app/config/settings.json", JSON.stringify({ env: "test" }));
|
|
1472
|
-
vfs.writeFile("/app/README.md", "# My App");
|
|
1473
841
|
return vfs.toSnapshot();
|
|
1474
842
|
}
|
|
1475
843
|
|
|
1476
844
|
const FIXTURE = buildFixture();
|
|
1477
845
|
|
|
1478
846
|
test("reads config file", () => {
|
|
1479
|
-
const vfs
|
|
847
|
+
const vfs = VirtualFileSystem.fromSnapshot(FIXTURE);
|
|
1480
848
|
const content = JSON.parse(vfs.readFile("/app/config/settings.json"));
|
|
1481
849
|
expect(content.env).toBe("test");
|
|
1482
850
|
});
|
|
1483
851
|
```
|
|
1484
852
|
|
|
1485
|
-
|
|
853
|
+
### Snapshot Diff in Tests
|
|
854
|
+
|
|
855
|
+
```typescript
|
|
856
|
+
import { diffSnapshots, assertDiff } from "typescript-virtual-container";
|
|
857
|
+
|
|
858
|
+
const before = shell.vfs.toSnapshot();
|
|
859
|
+
await client.exec("apt install vim && mkdir -p /app");
|
|
860
|
+
const after = shell.vfs.toSnapshot();
|
|
861
|
+
|
|
862
|
+
assertDiff(diffSnapshots(before, after, { ignore: ["/proc"] }), {
|
|
863
|
+
added: ["/app", "/usr/bin/vim"],
|
|
864
|
+
modified: ["/var/lib/dpkg/status"],
|
|
865
|
+
});
|
|
866
|
+
```
|
|
1486
867
|
|
|
1487
|
-
###
|
|
868
|
+
### Symlinks
|
|
1488
869
|
|
|
1489
870
|
```typescript
|
|
1490
871
|
const vfs = new VirtualFileSystem();
|
|
1491
|
-
vfs.mkdir("/usr/local/bin");
|
|
1492
872
|
vfs.writeFile("/opt/myapp/bin/app", "#!/bin/sh\necho hello");
|
|
1493
873
|
vfs.symlink("/opt/myapp/bin/app", "/usr/local/bin/app");
|
|
1494
874
|
|
|
@@ -1496,12 +876,10 @@ console.log(vfs.isSymlink("/usr/local/bin/app")); // true
|
|
|
1496
876
|
console.log(vfs.resolveSymlink("/usr/local/bin/app")); // /opt/myapp/bin/app
|
|
1497
877
|
```
|
|
1498
878
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
### Example 12: Security Auditing with HoneyPot
|
|
879
|
+
### Security Auditing with HoneyPot
|
|
1502
880
|
|
|
1503
881
|
```typescript
|
|
1504
|
-
import { HoneyPot,
|
|
882
|
+
import { HoneyPot, VirtualShell, VirtualSshServer } from "typescript-virtual-container";
|
|
1505
883
|
|
|
1506
884
|
const shell = new VirtualShell("typescript-vm");
|
|
1507
885
|
const ssh = new VirtualSshServer({ port: 2222, shell });
|
|
@@ -1510,46 +888,15 @@ await ssh.start();
|
|
|
1510
888
|
const hp = new HoneyPot(5000);
|
|
1511
889
|
hp.attach(shell, shell.vfs, shell.users, ssh);
|
|
1512
890
|
|
|
1513
|
-
const alice = new SshClient(shell, "alice");
|
|
1514
|
-
await alice.mkdir("/home/alice/projects", true);
|
|
1515
|
-
await alice.writeFile("/home/alice/projects/app.txt", "My application");
|
|
1516
|
-
|
|
1517
891
|
const stats = hp.getStats();
|
|
1518
|
-
console.log(`Commands
|
|
1519
|
-
console.log(`File writes: ${stats.fileWrites}`);
|
|
892
|
+
console.log(`Commands: ${stats.commands}, File writes: ${stats.fileWrites}`);
|
|
1520
893
|
|
|
1521
894
|
hp.detectAnomalies().forEach(a =>
|
|
1522
895
|
console.log(`[${a.severity.toUpperCase()}] ${a.type}: ${a.message}`)
|
|
1523
896
|
);
|
|
1524
|
-
|
|
1525
|
-
const authFailures = hp.getAuditLog("auth:failure");
|
|
1526
|
-
const sshEvents = hp.getAuditLog(undefined, "SshMimic");
|
|
1527
|
-
console.log(`Auth failures: ${authFailures.length}`);
|
|
1528
|
-
console.log(`SSH events: ${sshEvents.length}`);
|
|
1529
|
-
|
|
1530
|
-
ssh.stop();
|
|
1531
897
|
```
|
|
1532
898
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
### Example 13: Error Handling
|
|
1536
|
-
|
|
1537
|
-
```typescript
|
|
1538
|
-
const client = new SshClient(shell, "root");
|
|
1539
|
-
|
|
1540
|
-
const r1 = await client.readFile("/etc/nonexistent.conf");
|
|
1541
|
-
if (r1.exitCode !== 0) console.error("Read error:", r1.stderr);
|
|
1542
|
-
|
|
1543
|
-
const r2 = await client.cd("/invalid/path");
|
|
1544
|
-
if (r2.exitCode !== 0) console.error("cd failed");
|
|
1545
|
-
|
|
1546
|
-
const r3 = await client.rm("/", true);
|
|
1547
|
-
console.log("Remove root:", r3.stderr); // Cannot remove root directory.
|
|
1548
|
-
```
|
|
1549
|
-
|
|
1550
|
-
---
|
|
1551
|
-
|
|
1552
|
-
### Example 14: Concurrent Clients
|
|
899
|
+
### Concurrent Clients
|
|
1553
900
|
|
|
1554
901
|
```typescript
|
|
1555
902
|
const shell = new VirtualShell("typescript-vm");
|
|
@@ -1562,160 +909,33 @@ const [r1, r2] = await Promise.all([
|
|
|
1562
909
|
]);
|
|
1563
910
|
```
|
|
1564
911
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
## Linux Rootfs
|
|
1568
|
-
|
|
1569
|
-
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.
|
|
1570
|
-
|
|
1571
|
-
### Directory layout
|
|
1572
|
-
|
|
1573
|
-
```
|
|
1574
|
-
/
|
|
1575
|
-
├── bin -> /usr/bin (symlink, Debian-style)
|
|
1576
|
-
├── dev/ null, zero, random, urandom, pts/, shm/
|
|
1577
|
-
├── etc/
|
|
1578
|
-
│ ├── apt/sources.list Fortune package sources
|
|
1579
|
-
│ ├── debian_version
|
|
1580
|
-
│ ├── group synced from VirtualUserManager
|
|
1581
|
-
│ ├── hostname
|
|
1582
|
-
│ ├── hosts 127.0.0.1 localhost + VM hostname
|
|
1583
|
-
│ ├── motd
|
|
1584
|
-
│ ├── os-release NAME="Fortune GNU/Linux" + ShellProperties
|
|
1585
|
-
│ ├── passwd synced from VirtualUserManager
|
|
1586
|
-
│ ├── resolv.conf 1.1.1.1 + 8.8.8.8
|
|
1587
|
-
│ └── shadow (mode 0o640)
|
|
1588
|
-
├── proc/
|
|
1589
|
-
│ ├── 1/ init process (cmdline, status, comm, environ, fd/)
|
|
1590
|
-
│ ├── <pid>/ one entry per active session (based on pts/* TTY)
|
|
1591
|
-
│ ├── self/ mirrors the most recent session's /proc/<pid>/
|
|
1592
|
-
│ ├── cpuinfo real host CPU info
|
|
1593
|
-
│ ├── loadavg
|
|
1594
|
-
│ ├── meminfo real host memory
|
|
1595
|
-
│ ├── net/dev eth0 + lo
|
|
1596
|
-
│ ├── uptime shell uptime in seconds
|
|
1597
|
-
│ └── version kernel from ShellProperties
|
|
1598
|
-
├── root/ root home + .bashrc
|
|
1599
|
-
├── sys/devices/virtual/dmi/id/
|
|
1600
|
-
│ ├── sys_vendor "Fortune Systems"
|
|
1601
|
-
│ └── product_name "VirtualContainer v1"
|
|
1602
|
-
├── tmp/ (mode 0o1777 sticky)
|
|
1603
|
-
├── usr/bin/ stubs for all built-in commands
|
|
1604
|
-
├── var/
|
|
1605
|
-
│ ├── lib/dpkg/status managed by VirtualPackageManager
|
|
1606
|
-
│ └── log/ syslog, auth.log, dpkg.log, apt/
|
|
1607
|
-
└── ...
|
|
1608
|
-
```
|
|
1609
|
-
|
|
1610
|
-
### API
|
|
1611
|
-
|
|
1612
|
-
```ts
|
|
1613
|
-
shell.refreshProcFs(); // refresh /proc/* with current system state
|
|
1614
|
-
shell.syncPasswd(); // sync /etc/passwd|group|shadow from VirtualUserManager
|
|
1615
|
-
```
|
|
1616
|
-
|
|
1617
|
-
`syncPasswd()` is called automatically on `bootstrapLinuxRootfs`. Call it again after `users.addUser()` / `users.deleteUser()` to keep `/etc/passwd` consistent.
|
|
1618
|
-
|
|
1619
|
-
---
|
|
1620
|
-
|
|
1621
|
-
## Package Manager (apt/dpkg)
|
|
1622
|
-
|
|
1623
|
-
A pure-TypeScript APT/dpkg simulation backed by a built-in package registry. No external process is spawned.
|
|
1624
|
-
|
|
1625
|
-
### Workflow
|
|
1626
|
-
|
|
1627
|
-
```
|
|
1628
|
-
apt install <pkg> → resolves deps → writes files to VFS → updates /var/lib/dpkg/status
|
|
1629
|
-
apt remove <pkg> → removes VFS files → updates status
|
|
1630
|
-
dpkg -l → reads installed packages from VirtualPackageManager
|
|
1631
|
-
```
|
|
1632
|
-
|
|
1633
|
-
### Built-in package registry (25 packages)
|
|
1634
|
-
|
|
1635
|
-
| Package | Version | Section |
|
|
1636
|
-
|---------|---------|---------|
|
|
1637
|
-
| `vim` | 2:9.0.1378-2 | editors |
|
|
1638
|
-
| `git` | 1:2.39.2-1 | vcs |
|
|
1639
|
-
| `python3` | 3.11.2-1 | python |
|
|
1640
|
-
| `nodejs` | 18.19.0 | javascript |
|
|
1641
|
-
| `npm` | 9.2.0 | javascript |
|
|
1642
|
-
| `curl` | 7.88.1-10 | web |
|
|
1643
|
-
| `wget` | 1.21.3-1 | web |
|
|
1644
|
-
| `htop` | 3.2.2-2 | utils |
|
|
1645
|
-
| `openssh-client` | 1:9.2p1-2 | net |
|
|
1646
|
-
| `openssh-server` | 1:9.2p1-2 | net |
|
|
1647
|
-
| `net-tools` | 2.10-0.1 | net |
|
|
1648
|
-
| `iputils-ping` | 3:20221126-1 | net |
|
|
1649
|
-
| `jq` | 1.6-2.1 | utils |
|
|
1650
|
-
| `build-essential` | 12.9 | devel |
|
|
1651
|
-
| `gcc` | 4:12.2.0-3 | devel |
|
|
1652
|
-
| `g++` | 4:12.2.0-3 | devel |
|
|
1653
|
-
| `make` | 4.3-4.1 | devel |
|
|
1654
|
-
| `less` | 590-2 | text |
|
|
1655
|
-
| `unzip` | 6.0-28 | utils |
|
|
1656
|
-
| `rsync` | 3.2.7-1 | net |
|
|
1657
|
-
| `tmux` | 3.3a-3 | utils |
|
|
1658
|
-
| `tree` | 2.1.0-1 | utils |
|
|
1659
|
-
| `ca-certificates` | 20230311 | misc |
|
|
1660
|
-
| `sudo` | 1.9.13p3-1 | admin |
|
|
1661
|
-
| `systemd` | 252.22-1 | admin |
|
|
1662
|
-
|
|
1663
|
-
> **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.
|
|
1664
|
-
|
|
1665
|
-
### `VirtualPackageManager` API
|
|
1666
|
-
|
|
1667
|
-
```ts
|
|
1668
|
-
// Access via shell instance
|
|
1669
|
-
const pm = shell.packageManager;
|
|
1670
|
-
|
|
1671
|
-
pm.install(["vim", "git"], { quiet: false }) // → { output, exitCode }
|
|
1672
|
-
pm.remove(["vim"], { purge: false }) // → { output, exitCode }
|
|
1673
|
-
pm.search("editor") // → PackageDefinition[]
|
|
1674
|
-
pm.show("vim") // → string (dpkg-style block)
|
|
1675
|
-
pm.listInstalled() // → InstalledPackage[]
|
|
1676
|
-
pm.listAvailable() // → PackageDefinition[]
|
|
1677
|
-
pm.isInstalled("vim") // → boolean
|
|
1678
|
-
pm.installedCount() // → number
|
|
1679
|
-
pm.findInRegistry("nodejs") // → PackageDefinition | undefined
|
|
1680
|
-
```
|
|
1681
|
-
|
|
1682
|
-
### Registering custom packages
|
|
1683
|
-
|
|
1684
|
-
```ts
|
|
1685
|
-
import { VirtualPackageManager } from "typescript-virtual-container";
|
|
1686
|
-
|
|
1687
|
-
// Custom packages can be added to the registry before shell init
|
|
1688
|
-
// by extending PACKAGE_REGISTRY or by calling install() with custom defs.
|
|
912
|
+
### .bashrc
|
|
1689
913
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
],
|
|
1699
|
-
onInstall: (vfs) => {
|
|
1700
|
-
vfs.mkdir("/var/lib/myapp", 0o755);
|
|
1701
|
-
vfs.mkdir("/var/log/myapp", 0o755);
|
|
1702
|
-
},
|
|
1703
|
-
};
|
|
914
|
+
```typescript
|
|
915
|
+
shell.vfs.writeFile("/home/root/.bashrc", `
|
|
916
|
+
export EDITOR=nano
|
|
917
|
+
export PATH="/usr/local/bin:/usr/bin:/bin"
|
|
918
|
+
alias ll="ls -l"
|
|
919
|
+
echo "Welcome back, root!"
|
|
920
|
+
`.trim());
|
|
921
|
+
// On interactive SSH login, .bashrc is sourced automatically.
|
|
1704
922
|
```
|
|
1705
923
|
|
|
924
|
+
</details>
|
|
1706
925
|
|
|
1707
926
|
---
|
|
1708
927
|
|
|
1709
|
-
|
|
928
|
+
<details>
|
|
929
|
+
<summary><strong>Built-in Commands (91)</strong></summary>
|
|
1710
930
|
|
|
1711
|
-
|
|
931
|
+
Type `help` in the shell for a grouped, colorized listing. Type `help <command>` for detailed usage.
|
|
1712
932
|
|
|
1713
933
|
### Navigation
|
|
1714
934
|
|
|
1715
935
|
| Command | Flags | Description |
|
|
1716
936
|
|---------|-------|-------------|
|
|
1717
937
|
| `cd <path>` | | Change directory |
|
|
1718
|
-
| `ls [path]` | `-l` `-a` | List directory
|
|
938
|
+
| `ls [path]` | `-l` `-a` | List directory (`-a` shows dotfiles) |
|
|
1719
939
|
| `pwd` | | Print working directory |
|
|
1720
940
|
| `tree [path]` | | ASCII directory tree |
|
|
1721
941
|
|
|
@@ -1723,29 +943,32 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
|
|
|
1723
943
|
|
|
1724
944
|
| Command | Flags | Description |
|
|
1725
945
|
|---------|-------|-------------|
|
|
1726
|
-
| `cat <path...>` | `-n` `-b` | Concatenate and print
|
|
1727
|
-
| `chmod <mode> <file>` | |
|
|
946
|
+
| `cat <path...>` | `-n` `-b` | Concatenate and print; `-n` numbers lines, `-b` numbers non-blank |
|
|
947
|
+
| `chmod <mode> <file>` | | Octal (`755`) or symbolic (`+x`, `u+x`, `go-w`, `a=rx`) |
|
|
1728
948
|
| `cp <src> <dest>` | `-r` | Copy file or directory |
|
|
1729
|
-
| `find [path]` | `-name
|
|
1730
|
-
| `ln <target> <link>` | `-s` |
|
|
949
|
+
| `find [path]` | `-name` `-type` | Search for files |
|
|
950
|
+
| `ln <target> <link>` | `-s` | Hard or symbolic link |
|
|
951
|
+
| `readlink <path>` | `-f` | Print resolved path of symbolic link |
|
|
1731
952
|
| `mkdir <path>` | `-p` | Create directory |
|
|
1732
953
|
| `mv <src> <dest>` | | Move or rename |
|
|
1733
|
-
| `rm <path>` | `-r` | Remove file or directory |
|
|
1734
954
|
| `nano <path>` | | Interactive text editor |
|
|
955
|
+
| `rm <path>` | `-r` | Remove file or directory |
|
|
956
|
+
| `stat <path>` | `-c <format>` | Display file status (inode, size, mode, timestamps) |
|
|
1735
957
|
| `touch <path>` | | Create or update file |
|
|
1736
958
|
|
|
1737
959
|
### Text Processing
|
|
1738
960
|
|
|
1739
961
|
| Command | Flags | Description |
|
|
1740
962
|
|---------|-------|-------------|
|
|
1741
|
-
| `awk [-F <sep>] '<prog>'` | | Pattern scanning
|
|
1742
|
-
| `
|
|
963
|
+
| `awk [-F <sep>] '<prog>'` | | Pattern scanning |
|
|
964
|
+
| `base64` | `-d` | Encode/decode base64 |
|
|
965
|
+
| `cut` | `-d` `-f` | Remove sections from lines |
|
|
1743
966
|
| `diff <f1> <f2>` | | Compare files line by line |
|
|
1744
967
|
| `grep <pattern> [files]` | `-i` `-v` `-n` `-r` | Search file content |
|
|
1745
|
-
| `head [files]` | `-n <N>` | First N lines
|
|
968
|
+
| `head [files]` | `-n <N>` | First N lines |
|
|
1746
969
|
| `sed -e 's/pat/rep/[g]'` | `-i` | Stream editor |
|
|
1747
970
|
| `sort [files]` | `-r` `-n` `-u` | Sort lines |
|
|
1748
|
-
| `tail [files]` | `-n <N>` | Last N lines
|
|
971
|
+
| `tail [files]` | `-n <N>` | Last N lines |
|
|
1749
972
|
| `tee [files]` | `-a` | Read stdin, write to stdout and files |
|
|
1750
973
|
| `tr <set1> [set2]` | `-d` | Translate or delete characters |
|
|
1751
974
|
| `uniq` | `-c` `-d` `-u` | Filter repeated lines |
|
|
@@ -1756,85 +979,82 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
|
|
|
1756
979
|
|
|
1757
980
|
| Command | Flags | Description |
|
|
1758
981
|
|---------|-------|-------------|
|
|
1759
|
-
| `
|
|
1760
|
-
| `gzip <file>` | | Compress file |
|
|
1761
|
-
| `gunzip <file>` | | Decompress file |
|
|
982
|
+
| `gzip <file>` / `gunzip <file>` | | Compress / decompress |
|
|
1762
983
|
| `tar <archive> [files]` | `-czf` `-xzf` `-tf` | Archive utility |
|
|
1763
984
|
|
|
1764
985
|
### System
|
|
1765
986
|
|
|
1766
987
|
| Command | Flags | Description |
|
|
1767
988
|
|---------|-------|-------------|
|
|
1768
|
-
| `date` | `+format` |
|
|
1769
|
-
| `df` | `-h` | Filesystem disk space
|
|
1770
|
-
| `du [path]` | `-h` `-s` | Estimate file space
|
|
1771
|
-
| `
|
|
989
|
+
| `date` | `+format` | Current date and time |
|
|
990
|
+
| `df` | `-h` | Filesystem disk space |
|
|
991
|
+
| `du [path]` | `-h` `-s` | Estimate file space |
|
|
992
|
+
| `free` | `-h` `-m` `-g` | Memory usage (real host data) |
|
|
993
|
+
| `groups [user]` | | Group memberships |
|
|
1772
994
|
| `hostname` | | Print hostname |
|
|
1773
995
|
| `htop` | | System monitor (mock) |
|
|
1774
|
-
| `id [user]` | |
|
|
1775
|
-
| `kill [-9] <pid>` | | Send signal
|
|
1776
|
-
| `
|
|
1777
|
-
| `
|
|
1778
|
-
| `
|
|
1779
|
-
| `
|
|
1780
|
-
| `
|
|
1781
|
-
| `
|
|
1782
|
-
| `
|
|
1783
|
-
| `
|
|
996
|
+
| `id [user]` | | User identity (uid/gid/groups) |
|
|
997
|
+
| `kill [-9] <pid>` | | Send signal (mock) |
|
|
998
|
+
| `lsb_release` | `-a` `-i` `-d` `-r` `-c` | Distribution info |
|
|
999
|
+
| `neofetch` | | System info (real package count and uptime) |
|
|
1000
|
+
| `node` | `--version` `-e` `-p` | Virtual JS runtime; **requires `apt install nodejs`** |
|
|
1001
|
+
| `npm` | `--version` `list` `version` | Node.js package manager (install/run stubbed); **requires `apt install npm`** |
|
|
1002
|
+
| `npx` | `--version` | Node.js package runner (stubbed); **requires `apt install npm`** |
|
|
1003
|
+
| `ping [-c <n>] <host>` | | ICMP ECHO_REQUEST (mock) |
|
|
1004
|
+
| `ps` | `-a` `-u` `-x` `aux` | Process status |
|
|
1005
|
+
| `python3` | `--version` `-c` `-V` | Virtual Python 3 interpreter; alias `python`; **requires `apt install python3`** |
|
|
1784
1006
|
| `sleep <seconds>` | | Delay execution |
|
|
1785
|
-
| `uname` | `-a` `-r` `-m` |
|
|
1786
|
-
| `
|
|
1787
|
-
| `
|
|
1007
|
+
| `uname` | `-a` `-r` `-m` | System information |
|
|
1008
|
+
| `uptime` | `-p` `-s` | Running time |
|
|
1009
|
+
| `who` | | Active sessions |
|
|
1010
|
+
| `whoami` | | Current user |
|
|
1788
1011
|
|
|
1789
1012
|
### Network
|
|
1790
1013
|
|
|
1791
1014
|
| Command | Flags | Description |
|
|
1792
1015
|
|---------|-------|-------------|
|
|
1793
|
-
| `curl <url>` | `-o` `-X` `-d` `-H` `-s` `-I` `-L` `-v` | HTTP client (pure `fetch()
|
|
1794
|
-
| `wget <url>` | `-O` `-P` `-q` | File downloader (pure `fetch()
|
|
1016
|
+
| `curl <url>` | `-o` `-X` `-d` `-H` `-s` `-I` `-L` `-v` | HTTP client (pure `fetch()`) |
|
|
1017
|
+
| `wget <url>` | `-O` `-P` `-q` | File downloader (pure `fetch()`) |
|
|
1795
1018
|
|
|
1796
|
-
### Shell
|
|
1019
|
+
### Shell & Scripting
|
|
1797
1020
|
|
|
1798
1021
|
| Command | Flags | Description |
|
|
1799
1022
|
|---------|-------|-------------|
|
|
1800
1023
|
| `alias [name=value]` | | Define or display aliases |
|
|
1801
|
-
| `clear` | | Clear terminal screen
|
|
1802
|
-
| `declare [name=value]` | `-i` `-r` `-x` | Declare variables
|
|
1803
|
-
| `echo <text>` | `-n` `-e` | Display text; `-
|
|
1804
|
-
| `env` | | Print session environment
|
|
1805
|
-
| `exit [code]` | | Exit session
|
|
1806
|
-
| `export NAME=VALUE` | | Set shell variable
|
|
1807
|
-
| `
|
|
1808
|
-
| `
|
|
1809
|
-
| `
|
|
1810
|
-
| `
|
|
1811
|
-
| `
|
|
1812
|
-
| `
|
|
1024
|
+
| `clear` | | Clear terminal screen |
|
|
1025
|
+
| `declare [name=value]` | `-i` `-r` `-x` | Declare variables; aliases `local`, `typeset` |
|
|
1026
|
+
| `echo <text>` | `-n` `-e` | Display text; `-e` interprets `\n` `\t` `\r` `\\` |
|
|
1027
|
+
| `env` | | Print session environment |
|
|
1028
|
+
| `exit [code]` | | Exit session |
|
|
1029
|
+
| `export NAME=VALUE` | | Set shell variable |
|
|
1030
|
+
| `false` | | Return exit code 1 |
|
|
1031
|
+
| `help [command]` | | List commands or show command usage |
|
|
1032
|
+
| `history [n]` | | Command history |
|
|
1033
|
+
| `man <command>` | | Command reference manual |
|
|
1034
|
+
| `printf <fmt> [args...]` | | Format and print (`%s` `%d` `%f` `%x` `\n` `\t`) |
|
|
1035
|
+
| `read [-r] <var...>` | `-r` `-p` | Read stdin into variable(s) |
|
|
1036
|
+
| `return [n]` | | Return from shell function |
|
|
1813
1037
|
| `set [VAR=val]` | | Display or set shell variables |
|
|
1814
|
-
| `sh` | `-c <script>` `[file]` | Execute shell script —
|
|
1815
|
-
| `shift [n]` | | Shift positional parameters
|
|
1816
|
-
| `source <file>` | | Execute file in current
|
|
1817
|
-
|
|
|
1818
|
-
| `
|
|
1819
|
-
| `
|
|
1820
|
-
| `
|
|
1821
|
-
| `
|
|
1822
|
-
| `false` | | Return failure exit code (1) |
|
|
1823
|
-
| `type <command>` | | Describe how a command would be interpreted (builtin vs PATH) |
|
|
1824
|
-
| `unalias <name>` | `-a` | Remove alias definitions |
|
|
1038
|
+
| `sh` | `-c <script>` `[file]` | Execute shell script — `if`/`for`/`while`/`case`/functions, `$((expr))`, single-quote-safe |
|
|
1039
|
+
| `shift [n]` | | Shift positional parameters |
|
|
1040
|
+
| `source <file>` | | Execute file in current env; alias `.` |
|
|
1041
|
+
| `test <expr>` / `[ <expr> ]` | | POSIX conditional: `-f` `-d` `-e` `-z` `-n` `-x` `-s` `=` `!=` `-eq` `-lt` `-gt` `-le` `-ge` `!` `-a` `-o` |
|
|
1042
|
+
| `trap [action] [signal]` | | Signal handlers; supports `EXIT` |
|
|
1043
|
+
| `true` | | Return exit code 0 |
|
|
1044
|
+
| `type <command>` | | Describe command interpretation |
|
|
1045
|
+
| `unalias <name>` | `-a` | Remove aliases |
|
|
1825
1046
|
| `unset <VAR>` | | Remove shell variable |
|
|
1826
|
-
| `which <command>` | | Locate
|
|
1047
|
+
| `which <command>` | | Locate command in `$PATH` |
|
|
1827
1048
|
|
|
1828
1049
|
### Package Management
|
|
1829
1050
|
|
|
1830
1051
|
| Command | Flags | Description |
|
|
1831
1052
|
|---------|-------|-------------|
|
|
1832
|
-
| `apt <cmd> [pkg...]` | |
|
|
1833
|
-
| `apt-get
|
|
1834
|
-
| `apt-cache <cmd>` | |
|
|
1835
|
-
| `dpkg` | `-l` `-s` `-L` `-r` `-P` |
|
|
1836
|
-
| `dpkg-query` | `-W` `-l` | Show
|
|
1837
|
-
|
|
1053
|
+
| `apt <cmd> [pkg...]` | | `install`, `remove`, `purge`, `update`, `upgrade`, `search`, `show`, `list` |
|
|
1054
|
+
| `apt-get` | | Alias for `apt` |
|
|
1055
|
+
| `apt-cache <cmd>` | | `search`, `show`, `policy` |
|
|
1056
|
+
| `dpkg` | `-l` `-s` `-L` `-r` `-P` | Low-level package tool |
|
|
1057
|
+
| `dpkg-query` | `-W` `-l` | Show installed package info |
|
|
1838
1058
|
|
|
1839
1059
|
### Users & Permissions
|
|
1840
1060
|
|
|
@@ -1846,29 +1066,29 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
|
|
|
1846
1066
|
| `su [user]` | | Switch user |
|
|
1847
1067
|
| `sudo <cmd>` | `-i` | Run as root |
|
|
1848
1068
|
|
|
1849
|
-
|
|
1069
|
+
**ℹ️ All 91 built-in commands include complete JSDoc documentation** with `@category` and `@params` tags. See [src/commands/](src/commands/) for source code and inline documentation.
|
|
1850
1070
|
|
|
1851
|
-
|
|
1071
|
+
Custom commands: `shell.addCommand(name, params, callback)`.
|
|
1852
1072
|
|
|
1853
|
-
|
|
1073
|
+
</details>
|
|
1854
1074
|
|
|
1855
|
-
|
|
1075
|
+
---
|
|
1856
1076
|
|
|
1857
|
-
|
|
1077
|
+
<details>
|
|
1078
|
+
<summary><strong>Shell Scripting</strong></summary>
|
|
1858
1079
|
|
|
1859
|
-
|
|
1860
|
-
mkdir /app && echo "created" # run second only if first succeeds
|
|
1861
|
-
rm /missing || echo "not found" # run second only if first fails
|
|
1862
|
-
echo a; echo b; echo c # always run all three
|
|
1863
|
-
```
|
|
1080
|
+
The interpreter supports a POSIX sh subset via `sh -c '...'`, `sh <file>`, or interactive session.
|
|
1864
1081
|
|
|
1865
|
-
###
|
|
1082
|
+
### Operators and Redirections
|
|
1866
1083
|
|
|
1867
1084
|
```bash
|
|
1085
|
+
mkdir /app && echo "created" # && — only if previous succeeds
|
|
1086
|
+
rm /missing || echo "not found" # || — only if previous fails
|
|
1087
|
+
echo a; echo b; echo c # ; — always all three
|
|
1088
|
+
|
|
1868
1089
|
cat /etc/hosts | grep local
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
cat /tmp/out.txt >> /tmp/log.txt
|
|
1090
|
+
echo "hello" > /tmp/out.txt # overwrite
|
|
1091
|
+
cat /tmp/out.txt >> /tmp/log.txt # append
|
|
1872
1092
|
```
|
|
1873
1093
|
|
|
1874
1094
|
### Variable Expansion
|
|
@@ -1876,34 +1096,25 @@ cat /tmp/out.txt >> /tmp/log.txt
|
|
|
1876
1096
|
```bash
|
|
1877
1097
|
export NAME=world
|
|
1878
1098
|
echo "Hello $NAME" # Hello world
|
|
1879
|
-
echo "${NAME}" # world (braced form)
|
|
1880
1099
|
echo "${NAME:-fallback}" # world (or fallback if unset)
|
|
1881
|
-
echo "${UNSET:-default}" # default
|
|
1100
|
+
echo "${UNSET:-default}" # default
|
|
1882
1101
|
echo "${NAME:+alternate}" # alternate (only if NAME is set)
|
|
1102
|
+
echo "${UNSET:=assigned}" # assigns and returns "assigned"
|
|
1883
1103
|
echo "${#NAME}" # 5 (string length)
|
|
1884
1104
|
echo "$?" # last exit code
|
|
1885
|
-
echo
|
|
1105
|
+
echo ~ # /home/<user> (tilde expansion)
|
|
1886
1106
|
```
|
|
1887
1107
|
|
|
1888
|
-
|
|
1108
|
+
> **Single-quote isolation** — `$VAR` and `$((...))` are never expanded inside `'...'`.
|
|
1109
|
+
|
|
1110
|
+
### Arithmetic
|
|
1889
1111
|
|
|
1890
1112
|
```bash
|
|
1891
|
-
echo $((2 + 3))
|
|
1892
|
-
echo $((
|
|
1893
|
-
|
|
1894
|
-
i=0; i=$((i + 1)); echo $i # 1
|
|
1895
|
-
|
|
1896
|
-
# In loops (via sh):
|
|
1897
|
-
sh -c 'i=0
|
|
1898
|
-
while [ $i -lt 5 ]; do
|
|
1899
|
-
echo $i
|
|
1900
|
-
i=$((i + 1))
|
|
1901
|
-
done'
|
|
1113
|
+
echo $((2 + 3)) # 5
|
|
1114
|
+
X=4; echo $((X * 2)) # 8
|
|
1115
|
+
i=0; i=$((i + 1)) # increment
|
|
1902
1116
|
```
|
|
1903
1117
|
|
|
1904
|
-
> **Single-quote isolation** — variable and arithmetic expansion never occurs inside single quotes.
|
|
1905
|
-
> `echo '$NAME'` always outputs the literal string `$NAME`.
|
|
1906
|
-
|
|
1907
1118
|
### Conditionals
|
|
1908
1119
|
|
|
1909
1120
|
```bash
|
|
@@ -1915,41 +1126,44 @@ else
|
|
|
1915
1126
|
echo "nothing found"
|
|
1916
1127
|
fi
|
|
1917
1128
|
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
# Numeric comparison
|
|
1922
|
-
if [ $COUNT -gt 10 ]; then echo "large"; fi
|
|
1129
|
+
[ "$USER" = "root" ] && echo "root"
|
|
1130
|
+
[ $COUNT -gt 10 ] && echo "large"
|
|
1923
1131
|
```
|
|
1924
1132
|
|
|
1925
1133
|
### Loops
|
|
1926
1134
|
|
|
1927
1135
|
```bash
|
|
1928
|
-
# for loop
|
|
1929
1136
|
for name in alice bob charlie; do
|
|
1930
1137
|
echo "Hello $name"
|
|
1931
1138
|
done
|
|
1932
1139
|
|
|
1933
|
-
# while loop
|
|
1934
1140
|
COUNT=0
|
|
1935
1141
|
while [ $COUNT -lt 3 ]; do
|
|
1936
1142
|
echo "Count: $COUNT"
|
|
1937
|
-
|
|
1143
|
+
COUNT=$((COUNT + 1))
|
|
1938
1144
|
done
|
|
1939
1145
|
```
|
|
1940
1146
|
|
|
1941
|
-
###
|
|
1942
|
-
|
|
1943
|
-
Write a script to the VFS and execute it:
|
|
1147
|
+
### Functions and case
|
|
1944
1148
|
|
|
1945
1149
|
```bash
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1150
|
+
greet() {
|
|
1151
|
+
echo "Hello, $1!"
|
|
1152
|
+
}
|
|
1153
|
+
greet world
|
|
1154
|
+
|
|
1155
|
+
case "$1" in
|
|
1156
|
+
start) echo "Starting..." ;;
|
|
1157
|
+
stop) echo "Stopping..." ;;
|
|
1158
|
+
*) echo "Usage: $0 {start|stop}" ;;
|
|
1159
|
+
esac
|
|
1160
|
+
```
|
|
1950
1161
|
|
|
1951
|
-
|
|
1952
|
-
|
|
1162
|
+
### Script Files
|
|
1163
|
+
|
|
1164
|
+
```typescript
|
|
1165
|
+
shell.vfs.writeFile("/usr/local/bin/setup.sh", `
|
|
1166
|
+
#!/bin/sh
|
|
1953
1167
|
for dir in config logs tmp; do
|
|
1954
1168
|
mkdir /app/$dir
|
|
1955
1169
|
done
|
|
@@ -1959,252 +1173,247 @@ await client.exec("sh /usr/local/bin/setup.sh");
|
|
|
1959
1173
|
|
|
1960
1174
|
### .bashrc
|
|
1961
1175
|
|
|
1962
|
-
|
|
1176
|
+
Sourced automatically on every interactive session from `/home/<user>/.bashrc`:
|
|
1963
1177
|
|
|
1964
1178
|
```bash
|
|
1965
|
-
# /home/alice/.bashrc
|
|
1966
1179
|
export EDITOR=nano
|
|
1967
|
-
|
|
1968
|
-
echo "Welcome,
|
|
1180
|
+
alias ll="ls -l"
|
|
1181
|
+
echo "Welcome, $USER!"
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
</details>
|
|
1185
|
+
|
|
1186
|
+
---
|
|
1187
|
+
|
|
1188
|
+
<details>
|
|
1189
|
+
<summary><strong>Linux Rootfs & VFS PATH Resolution</strong></summary>
|
|
1190
|
+
|
|
1191
|
+
### Directory Layout
|
|
1192
|
+
|
|
1193
|
+
On every `VirtualShell` init, a realistic Linux hierarchy is bootstrapped idempotently:
|
|
1194
|
+
|
|
1195
|
+
```
|
|
1196
|
+
/
|
|
1197
|
+
├── bin -> /usr/bin (symlink, Debian-style)
|
|
1198
|
+
├── dev/ null, zero, random, urandom, pts/, shm/
|
|
1199
|
+
├── etc/
|
|
1200
|
+
│ ├── group synced from VirtualUserManager
|
|
1201
|
+
│ ├── hostname
|
|
1202
|
+
│ ├── hosts 127.0.0.1 localhost + VM hostname
|
|
1203
|
+
│ ├── os-release NAME="Fortune GNU/Linux" + ShellProperties
|
|
1204
|
+
│ ├── passwd synced from VirtualUserManager
|
|
1205
|
+
│ ├── resolv.conf 1.1.1.1 + 8.8.8.8
|
|
1206
|
+
│ └── shadow (mode 0o640)
|
|
1207
|
+
├── proc/
|
|
1208
|
+
│ ├── 1/ init process (cmdline, status, comm, environ, fd/)
|
|
1209
|
+
│ ├── <pid>/ one entry per active session (pts/* TTY)
|
|
1210
|
+
│ ├── self/ mirrors most recent session's /proc/<pid>/
|
|
1211
|
+
│ ├── cpuinfo real host CPU info
|
|
1212
|
+
│ ├── meminfo real host memory
|
|
1213
|
+
│ └── ...
|
|
1214
|
+
├── sys/devices/virtual/dmi/id/
|
|
1215
|
+
├── tmp/ (mode 0o1777 sticky)
|
|
1216
|
+
├── usr/bin/ stubs for all built-in commands
|
|
1217
|
+
└── var/lib/dpkg/status managed by VirtualPackageManager
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
```typescript
|
|
1221
|
+
shell.refreshProcFs(); // refresh /proc/* with current system state
|
|
1222
|
+
shell.syncPasswd(); // sync /etc/passwd|group|shadow from user manager
|
|
1969
1223
|
```
|
|
1970
1224
|
|
|
1225
|
+
### VFS PATH Resolution
|
|
1226
|
+
|
|
1227
|
+
When a command is not a built-in, the shell searches `$PATH` in the VFS — binaries installed by `apt` become immediately accessible.
|
|
1228
|
+
|
|
1229
|
+
- `/sbin` and `/usr/sbin` are excluded from `$PATH` for non-root users.
|
|
1230
|
+
- Stubs containing `exec builtin <name>` delegate to the named built-in TS command.
|
|
1231
|
+
|
|
1232
|
+
**Package-gated commands** (require `apt install`):
|
|
1233
|
+
|
|
1234
|
+
| Command | Package |
|
|
1235
|
+
|---------|---------|
|
|
1236
|
+
| `node` | `nodejs` |
|
|
1237
|
+
| `python3` / `python` | `python3` |
|
|
1238
|
+
| `npm` / `npx` | `npm` |
|
|
1239
|
+
|
|
1240
|
+
### Built-in Package Registry (25 packages)
|
|
1241
|
+
|
|
1242
|
+
| Package | Version | Section |
|
|
1243
|
+
|---------|---------|---------|
|
|
1244
|
+
| `vim` | 2:9.0.1378-2 | editors |
|
|
1245
|
+
| `git` | 1:2.39.2-1 | vcs |
|
|
1246
|
+
| `python3` | 3.11.2-1 | python |
|
|
1247
|
+
| `nodejs` | 18.19.0 | javascript |
|
|
1248
|
+
| `npm` | 9.2.0 | javascript |
|
|
1249
|
+
| `curl` | 7.88.1-10 | web |
|
|
1250
|
+
| `wget` | 1.21.3-1 | web |
|
|
1251
|
+
| `htop` | 3.2.2-2 | utils |
|
|
1252
|
+
| `openssh-client` | 1:9.2p1-2 | net |
|
|
1253
|
+
| `openssh-server` | 1:9.2p1-2 | net |
|
|
1254
|
+
| `net-tools` | 2.10-0.1 | net |
|
|
1255
|
+
| `iputils-ping` | 3:20221126-1 | net |
|
|
1256
|
+
| `jq` | 1.6-2.1 | utils |
|
|
1257
|
+
| `build-essential` | 12.9 | devel |
|
|
1258
|
+
| `gcc` | 4:12.2.0-3 | devel |
|
|
1259
|
+
| `g++` | 4:12.2.0-3 | devel |
|
|
1260
|
+
| `make` | 4.3-4.1 | devel |
|
|
1261
|
+
| `less` | 590-2 | text |
|
|
1262
|
+
| `unzip` | 6.0-28 | utils |
|
|
1263
|
+
| `rsync` | 3.2.7-1 | net |
|
|
1264
|
+
| `tmux` | 3.3a-3 | utils |
|
|
1265
|
+
| `tree` | 2.1.0-1 | utils |
|
|
1266
|
+
| `ca-certificates` | 20230311 | misc |
|
|
1267
|
+
| `sudo` | 1.9.13p3-1 | admin |
|
|
1268
|
+
| `systemd` | 252.22-1 | admin |
|
|
1269
|
+
|
|
1270
|
+
</details>
|
|
1271
|
+
|
|
1971
1272
|
---
|
|
1972
1273
|
|
|
1973
|
-
|
|
1274
|
+
<details>
|
|
1275
|
+
<summary><strong>Configuration</strong></summary>
|
|
1974
1276
|
|
|
1975
1277
|
### Environment Variables
|
|
1976
1278
|
|
|
1977
1279
|
| Variable | Default | Description |
|
|
1978
1280
|
|----------|---------|-------------|
|
|
1979
|
-
| `SSH_MIMIC_FAST_PASSWORD_HASH` | `""` | Use SHA-256 instead of scrypt (
|
|
1281
|
+
| `SSH_MIMIC_FAST_PASSWORD_HASH` | `""` | Use SHA-256 instead of scrypt (dev only). Set to `1` or `true`. |
|
|
1980
1282
|
| `SSH_MIMIC_AUTO_SUDO_NEW_USERS` | `"true"` | Auto-grant sudo to new users. Set to `0`, `false`, `no`, or `off` to disable. |
|
|
1981
1283
|
| `DEV_MODE` | `""` | Enable performance logging. |
|
|
1982
1284
|
| `RENDER_PERF` | `""` | Enable render performance logging. |
|
|
1983
1285
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
```bash
|
|
1987
|
-
export SSH_MIMIC_FAST_PASSWORD_HASH=1
|
|
1988
|
-
export SSH_MIMIC_AUTO_SUDO_NEW_USERS=false
|
|
1989
|
-
node server.js
|
|
1990
|
-
```
|
|
1991
|
-
|
|
1992
|
-
### Runtime Options Summary
|
|
1286
|
+
### Constructor Options
|
|
1993
1287
|
|
|
1994
1288
|
```typescript
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
hostname,
|
|
1998
|
-
properties?, // kernel, os, arch strings
|
|
1999
|
-
vfsOptions?, // { mode: "memory"|"fs", snapshotPath?: string }
|
|
2000
|
-
)
|
|
1289
|
+
new VirtualShell(hostname, properties?, vfsOptions?)
|
|
1290
|
+
// vfsOptions: { mode: "memory"|"fs", snapshotPath?: string }
|
|
2001
1291
|
|
|
2002
|
-
// VirtualSshServer
|
|
2003
1292
|
new VirtualSshServer({
|
|
2004
|
-
port,
|
|
2005
|
-
hostname?,
|
|
1293
|
+
port, hostname?,
|
|
2006
1294
|
shell?,
|
|
2007
|
-
maxAuthAttempts?,
|
|
2008
|
-
lockoutDurationMs?,
|
|
1295
|
+
maxAuthAttempts?, // default: 5
|
|
1296
|
+
lockoutDurationMs?, // default: 60_000
|
|
2009
1297
|
})
|
|
2010
1298
|
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
port,
|
|
2014
|
-
hostname?,
|
|
2015
|
-
shell?, // or: vfs + users separately
|
|
2016
|
-
})
|
|
1299
|
+
new VirtualSftpServer({ port, hostname?, shell? })
|
|
1300
|
+
// or: { port, vfs, users } without a shell instance
|
|
2017
1301
|
```
|
|
2018
1302
|
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
## Performance & Scalability
|
|
1303
|
+
</details>
|
|
2022
1304
|
|
|
2023
|
-
|
|
1305
|
+
---
|
|
2024
1306
|
|
|
2025
|
-
|
|
1307
|
+
<details>
|
|
1308
|
+
<summary><strong>Performance & Scalability</strong></summary>
|
|
2026
1309
|
|
|
2027
1310
|
```bash
|
|
2028
1311
|
bun ./benchmark-virtualshell.ts
|
|
2029
1312
|
```
|
|
2030
1313
|
|
|
2031
|
-
|
|
2032
|
-
- Shell initialization time by concurrency level
|
|
2033
|
-
- Command execution time across active shells
|
|
2034
|
-
- RSS memory growth during the run
|
|
1314
|
+
Reports shell initialization time, command execution time, and RSS memory by concurrency. Designed to scale to **1000+ parallel environments**.
|
|
2035
1315
|
|
|
2036
|
-
|
|
1316
|
+
- SSH server is event-driven — handles many concurrent connections.
|
|
1317
|
+
- `SshClient` is sequential per instance — create multiple for parallel operations.
|
|
1318
|
+
- Each `VirtualShell` is fully independent.
|
|
2037
1319
|
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
- SSH server is event-driven and handles multiple concurrent connections.
|
|
2041
|
-
- `SshClient` is sequential per instance — create multiple instances for parallel operations.
|
|
2042
|
-
- Each `VirtualShell` instance is fully independent (separate VFS, users, env state).
|
|
2043
|
-
|
|
2044
|
-
### Performance Tips
|
|
2045
|
-
|
|
2046
|
-
- Use `SSH_MIMIC_FAST_PASSWORD_HASH=1` in test environments to skip scrypt overhead.
|
|
1320
|
+
**Tips:**
|
|
1321
|
+
- Use `SSH_MIMIC_FAST_PASSWORD_HASH=1` in tests to skip scrypt overhead.
|
|
2047
1322
|
- Reuse long-lived shell instances for low-latency command bursts.
|
|
2048
|
-
- Keep `DEV_MODE=1` enabled only during development (adds logging overhead).
|
|
2049
|
-
|
|
2050
|
-
**Parallel clients example:**
|
|
2051
|
-
|
|
2052
|
-
```typescript
|
|
2053
|
-
const shell = new VirtualShell("typescript-vm");
|
|
2054
|
-
const client1 = new SshClient(shell, "alice");
|
|
2055
|
-
const client2 = new SshClient(shell, "bob");
|
|
2056
1323
|
|
|
2057
|
-
|
|
2058
|
-
client1.writeFile("/tmp/alice.txt", "..."),
|
|
2059
|
-
client2.writeFile("/tmp/bob.txt", "..."),
|
|
2060
|
-
]);
|
|
2061
|
-
```
|
|
1324
|
+
</details>
|
|
2062
1325
|
|
|
2063
1326
|
---
|
|
2064
1327
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
Full TypeScript support with exported types:
|
|
1328
|
+
<details>
|
|
1329
|
+
<summary><strong>Types & TypeScript</strong></summary>
|
|
2068
1330
|
|
|
2069
1331
|
```typescript
|
|
2070
1332
|
import type {
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
VfsDirectoryNode,
|
|
2079
|
-
WriteFileOptions,
|
|
2080
|
-
RemoveOptions,
|
|
2081
|
-
// Commands
|
|
2082
|
-
CommandContext,
|
|
2083
|
-
CommandResult,
|
|
2084
|
-
CommandMode,
|
|
2085
|
-
CommandOutcome,
|
|
2086
|
-
ShellModule,
|
|
2087
|
-
ShellEnv,
|
|
2088
|
-
SudoChallenge,
|
|
2089
|
-
NanoEditorSession,
|
|
2090
|
-
// Audit
|
|
2091
|
-
AuditLogEntry,
|
|
2092
|
-
HoneyPotStats,
|
|
2093
|
-
// Streams
|
|
2094
|
-
ShellStream,
|
|
2095
|
-
ExecStream,
|
|
1333
|
+
VfsOptions, VfsPersistenceMode,
|
|
1334
|
+
VfsSnapshot, VfsNodeStats, VfsFileNode, VfsDirectoryNode,
|
|
1335
|
+
WriteFileOptions, RemoveOptions,
|
|
1336
|
+
CommandContext, CommandResult, CommandMode, CommandOutcome,
|
|
1337
|
+
ShellModule, ShellEnv, SudoChallenge, NanoEditorSession,
|
|
1338
|
+
AuditLogEntry, HoneyPotStats,
|
|
1339
|
+
ShellStream, ExecStream,
|
|
2096
1340
|
} from "typescript-virtual-container";
|
|
2097
1341
|
```
|
|
2098
1342
|
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
## FAQ
|
|
2102
|
-
|
|
2103
|
-
**Is this a real container runtime?**
|
|
2104
|
-
No. It emulates SSH sessions, users, and filesystem behavior in a virtual runtime. Ideal for testing, simulations, and automation where full OS isolation is not required.
|
|
2105
|
-
|
|
2106
|
-
**Can I use this in production?**
|
|
2107
|
-
You can use it in production-like automation contexts (sandboxed command runners, test harnesses, training environments, honeypots). It is not a security boundary like a real container/VM.
|
|
2108
|
-
|
|
2109
|
-
**Does the VFS touch the host filesystem?**
|
|
2110
|
-
In the default `"memory"` mode: no, all data lives in memory. In `"fs"` mode, it reads/writes a single binary file (`vfs-snapshot.vfsb`) inside the configured `snapshotPath` directory. No other host paths are accessed. See [VFSB Binary Format](#vfsb-binary-format).
|
|
1343
|
+
</details>
|
|
2111
1344
|
|
|
2112
|
-
|
|
2113
|
-
Only if you explicitly use `"fs"` mode or call `toSnapshot()` / `fromSnapshot()` manually. Memory mode is ephemeral. In `"fs"` mode, the snapshot is stored as a binary `.vfsb` file — see [VFSB Binary Format](#vfsb-binary-format).
|
|
1345
|
+
---
|
|
2114
1346
|
|
|
2115
|
-
|
|
2116
|
-
Yes. Each `new VirtualShell(...)` creates a completely independent VFS, user manager, and shell environment.
|
|
1347
|
+
## Troubleshooting
|
|
2117
1348
|
|
|
2118
|
-
|
|
2119
|
-
No. Custom commands registered with `shell.addCommand()` are instance-local.
|
|
1349
|
+
**`Error: listen EADDRINUSE :::2222`** — port already in use. Change port or stop existing process.
|
|
2120
1350
|
|
|
2121
|
-
**
|
|
2122
|
-
Yes. The shell parser handles logical operators, pipes, and redirections. `if`/`elif`/`else`/`fi`, `for`/`do`/`done`, and `while`/`do`/`done` are supported in scripts.
|
|
1351
|
+
**Auth always fails** — root has no password by default. Verify with `users.verifyPassword(username, password)`. Check lockout: `ssh.clearLockout(ip)`.
|
|
2123
1352
|
|
|
2124
|
-
**
|
|
2125
|
-
Yes. When an interactive SSH session starts, `/home/<user>/.bashrc` is loaded automatically and each non-comment line is executed in the session environment.
|
|
1353
|
+
**`Command 'xyz' not found` (exit 127)** — not a registered built-in and not found in VFS `$PATH`. Register with `shell.addCommand()` or `apt install` the package.
|
|
2126
1354
|
|
|
2127
|
-
**
|
|
2128
|
-
`curl` and `wget` delegate to the host binaries. They are intended for realistic workflows, not full GNU tooling parity.
|
|
1355
|
+
**Shell scripting — `if` block not working** — ensure each keyword is on its own line or separated by `;`.
|
|
2129
1356
|
|
|
2130
|
-
**
|
|
2131
|
-
Yes — use `shell.addCommand()` or implement the `ShellModule` interface directly. Set `description` and `category` to appear in the grouped `help` output.
|
|
1357
|
+
**`snapshotPath` required error** — set `mode: "fs"` without `snapshotPath`: `new VirtualFileSystem({ mode: "fs", snapshotPath: "./data" })`.
|
|
2132
1358
|
|
|
2133
|
-
**
|
|
2134
|
-
Core SFTP operations (open, read, write, stat, mkdir, remove, rename) are implemented. Some optional operations (extended attributes, symlinks) return `OP_UNSUPPORTED`.
|
|
1359
|
+
**Variables not persisting between `exec()` calls** — `SshClient` shares one `ShellEnv` per shell instance. `export` in one call is visible in the next. For isolation, create a new `SshClient`.
|
|
2135
1360
|
|
|
2136
|
-
|
|
2137
|
-
Yes — that is one of its primary use-cases. Use `HoneyPot.attach()` to capture all activity, configure `maxAuthAttempts` to throttle scanners, and export audit logs on shutdown.
|
|
1361
|
+
**`Too many levels of symbolic links`** — symlink chain exceeds 8 hops. Check for circular links or increase `maxDepth` in `resolveSymlink()`.
|
|
2138
1362
|
|
|
2139
1363
|
---
|
|
2140
1364
|
|
|
2141
|
-
##
|
|
2142
|
-
|
|
2143
|
-
**`Error: listen EADDRINUSE :::2222`**
|
|
2144
|
-
The port is already in use. Use a different port or stop the existing process.
|
|
2145
|
-
|
|
2146
|
-
**SSH authentication always fails**
|
|
2147
|
-
Check the password (root has no password by default). If you set a password, verify it with `users.verifyPassword(username, password)`. Check if the IP is rate-limited: call `ssh.clearLockout(ip)`.
|
|
1365
|
+
## FAQ
|
|
2148
1366
|
|
|
2149
|
-
**
|
|
2150
|
-
|
|
1367
|
+
**Is this a real container runtime?**
|
|
1368
|
+
No — it emulates SSH sessions, users, and filesystem behavior in a virtual runtime. Ideal for testing, simulations, and automation where full OS isolation is not required.
|
|
2151
1369
|
|
|
2152
|
-
|
|
2153
|
-
|
|
1370
|
+
**Can I use this in production?**
|
|
1371
|
+
Yes for automation contexts (sandboxed command runners, test harnesses, training environments, honeypots). It is not a security boundary like a real container/VM.
|
|
2154
1372
|
|
|
2155
|
-
|
|
2156
|
-
|
|
1373
|
+
**Does the VFS touch the host filesystem?**
|
|
1374
|
+
In `"memory"` mode: no. In `"fs"` mode: one binary file (`vfs-snapshot.vfsb`) inside `snapshotPath` only.
|
|
2157
1375
|
|
|
2158
|
-
**
|
|
2159
|
-
|
|
1376
|
+
**Can I run multiple isolated shells?**
|
|
1377
|
+
Yes. Each `new VirtualShell(...)` is completely independent (separate VFS, users, env state).
|
|
2160
1378
|
|
|
2161
|
-
**
|
|
2162
|
-
|
|
1379
|
+
**Does the shell support `&&`, `||`, and `;`?**
|
|
1380
|
+
Yes — plus pipes, redirections, `if`/`for`/`while`/`case`, and function definitions.
|
|
2163
1381
|
|
|
2164
|
-
|
|
2165
|
-
|
|
1382
|
+
**Can I use this for honeypot deployments?**
|
|
1383
|
+
Yes — use `HoneyPot.attach()` to capture all activity, configure `maxAuthAttempts` to throttle scanners, export on shutdown.
|
|
2166
1384
|
|
|
2167
|
-
**
|
|
2168
|
-
|
|
1385
|
+
**Is SFTP fully supported?**
|
|
1386
|
+
Core operations are implemented. Extended attributes and symlinks return `OP_UNSUPPORTED`.
|
|
2169
1387
|
|
|
2170
1388
|
---
|
|
2171
1389
|
|
|
2172
1390
|
## Contributing
|
|
2173
1391
|
|
|
2174
|
-
1. Fork
|
|
2175
|
-
2.
|
|
2176
|
-
3.
|
|
2177
|
-
4.
|
|
2178
|
-
5. Push and open a PR.
|
|
1392
|
+
1. Fork and create a feature branch: `git checkout -b feat/my-feature`
|
|
1393
|
+
2. Add changes and tests.
|
|
1394
|
+
3. Format and lint: `bun format && bun check`
|
|
1395
|
+
4. Push and open a PR.
|
|
2179
1396
|
|
|
2180
|
-
**
|
|
2181
|
-
- Biome formatting (opinionated, enforced by CI)
|
|
2182
|
-
- Full TypeScript — no `any`
|
|
2183
|
-
- JSDoc comments on all public API surface
|
|
2184
|
-
- Async/await throughout — no callbacks
|
|
2185
|
-
- Tests for new commands and VFS behavior
|
|
2186
|
-
- New commands must include `description` and `category` fields for `help`
|
|
1397
|
+
**Standards:** Biome formatting · full TypeScript (no `any`) · ✅ **JSDoc on all built-in commands** · async/await · `description` and `category` fields on new `ShellModule` · unit tests (prioritized for core features; advanced tests planned).
|
|
2187
1398
|
|
|
2188
1399
|
---
|
|
2189
1400
|
|
|
2190
1401
|
## Security
|
|
2191
1402
|
|
|
2192
|
-
- Passwords
|
|
1403
|
+
- Passwords hashed with scrypt (N=32768, r=8, p=1) with random per-user salt.
|
|
2193
1404
|
- Root account always exists and cannot be deleted.
|
|
2194
|
-
-
|
|
2195
|
-
- Per-IP rate limiting prevents automated brute-force attacks on the SSH server.
|
|
1405
|
+
- Per-IP rate limiting prevents automated brute-force on the SSH server.
|
|
2196
1406
|
- This project does **not** provide kernel-level or process-level isolation.
|
|
2197
|
-
- Do **not** expose
|
|
1407
|
+
- Do **not** expose to the public internet without understanding the risks.
|
|
2198
1408
|
|
|
2199
|
-
|
|
1409
|
+
Vulnerability reports: contact maintainers privately before public disclosure — see `SECURITY.md`.
|
|
2200
1410
|
|
|
2201
1411
|
---
|
|
2202
1412
|
|
|
2203
|
-
##
|
|
1413
|
+
## Compatibility
|
|
2204
1414
|
|
|
2205
|
-
-
|
|
2206
|
-
-
|
|
2207
|
-
- For API questions, include the exact call sequence plus expected vs. actual behavior.
|
|
1415
|
+
- **Node.js**: `>=18` · **Bun**: Supported · **TypeScript**: `>=5.0`
|
|
1416
|
+
- **OS**: Linux, macOS, Windows (via Node/Bun)
|
|
2208
1417
|
|
|
2209
1418
|
---
|
|
2210
1419
|
|
|
@@ -2216,63 +1425,24 @@ MIT — see [LICENSE](./LICENSE).
|
|
|
2216
1425
|
|
|
2217
1426
|
## Roadmap
|
|
2218
1427
|
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
- [x] Improved shell compatibility (pipelines, redirections)
|
|
2222
|
-
- [x] Pure in-memory VFS with snapshot import/export
|
|
2223
|
-
- [x] Symlinks (`ln -s`, `isSymlink`, `resolveSymlink`)
|
|
2224
|
-
- [x] SSH public-key authentication
|
|
2225
|
-
- [x] Per-IP rate limiting and lockout
|
|
2226
|
-
- [x] Shell operators: `&&` / `||` / `;`
|
|
2227
|
-
- [x] Shell scripting: `if`/`elif`/`else`/`fi`, `for`/`do`/`done`, `while`/`do`/`done`
|
|
2228
|
-
- [x] Variable expansion: `$VAR`, `${VAR:-default}`, `$?`
|
|
2229
|
-
- [x] Per-session `ShellEnv` (no more global variable store)
|
|
2230
|
-
- [x] `.bashrc` auto-sourced on interactive login
|
|
2231
|
-
- [x] `clear` with full ANSI screen reset (`\x1b[2J\x1b[H\x1b[3J`)
|
|
2232
|
-
- [x] `Ctrl+W` delete word in interactive shell
|
|
2233
|
-
- [x] Grouped, colorized `help` with per-command detail
|
|
2234
|
-
- [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`
|
|
2235
|
-
- [x] Structured event hooks (session open/close, file write, sudo challenge)
|
|
2236
|
-
- [x] Binary snapshot format (VFSB) — replaces JSON+base64, ~27% smaller, no string parsing overhead, backward-compatible JSON migration
|
|
2237
|
-
- [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`
|
|
2238
|
-
- [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
|
|
2239
|
-
- [x] `curl` / `wget` reimplemented as pure `fetch()` — no host binary spawned, full isolation
|
|
2240
|
-
- [x] New commands: `which`, `type`, `man`, `uptime`, `free`, `lsb_release`, `alias`, `unalias`
|
|
2241
|
-
- [x] `$(cmd)` command substitution in variable expansion
|
|
2242
|
-
- [x] Alias expansion in command dispatch
|
|
2243
|
-
- [x] `neofetch` shows real package count and shell uptime
|
|
2244
|
-
- [x] `syncPasswd()` / `refreshProcFs()` public API on `VirtualShell`
|
|
2245
|
-
- [x] `test` / `[` — full POSIX conditional expressions (`-f`, `-d`, `-e`, `-z`, `-n`, `-x`, `-s`, `-L`, `=`, `!=`, `-eq`, `-lt`, `-gt`, `-le`, `-ge`, `!`, `-a`, `-o`)
|
|
2246
|
-
- [x] `source` / `.` — execute file in current shell env (aliases and exports persist across commands)
|
|
2247
|
-
- [x] `history [n]` — display command history from VFS `.bash_history`
|
|
2248
|
-
- [x] `echo -e` / `echo -n` — escape sequence interpretation and newline suppression
|
|
2249
|
-
- [x] `ls -a` — show dotfiles
|
|
2250
|
-
- [x] `chmod` symbolic modes — `+x`, `u+x`, `go-w`, `a=rx`, comma-separated
|
|
2251
|
-
- [x] `cat -n` / `-b` — line numbering, multi-file concatenation, stdin support
|
|
2252
|
-
- [x] `ping -c N` — respects packet count flag
|
|
2253
|
-
- [x] `ps -u` / `ps aux` — extended format with USER/PID/%CPU/%MEM/VSZ/RSS columns
|
|
2254
|
-
- [x] `$(cmd)` in single-quoted args preserved — `sh -c 'echo $(whoami)'` now works correctly
|
|
2255
|
-
- [x] Pipeline executor: `runCommandDirect` — args with `;`, `|`, `>` no longer re-parsed
|
|
2256
|
-
- [x] `>>` append redirect fixed — was broken because `echo` lacked terminal newline
|
|
2257
|
-
- [x] `export A=1 && echo $A` — env vars visible to subsequent commands in same pipeline
|
|
2258
|
-
- [x] `echo` uses session `env.vars` (not stale global store)
|
|
2259
|
-
- [x] `printf` — format string with `%s` `%d` `%f` `%x` `\n` `\t`
|
|
2260
|
-
- [x] `read` — read stdin into variables (supports multiple vars, splits on whitespace)
|
|
2261
|
-
- [x] `declare` / `local` / `typeset` — variable declaration with `-i` integer, `-r` readonly, `-x` export
|
|
2262
|
-
- [x] `shift [n]` — shift positional parameters
|
|
2263
|
-
- [x] `trap [action] [signal]` — signal handlers with `EXIT` support
|
|
2264
|
-
- [x] `return [n]` — return from shell functions
|
|
2265
|
-
- [x] `exit [code]` — optional exit code
|
|
2266
|
-
- [x] `help` rewrite — Package Management category, aliases shown inline, `help <cmd>` shows category
|
|
2267
|
-
- [x] Category corrections — `neofetch`→system, `nano`→files, `apt`/`dpkg`→package
|
|
2268
|
-
- [x] `src/utils/expand.ts` — centralised expansion module used by `runCommand`, `echo`, and `sh.ts`
|
|
2269
|
-
- [x] `${#VAR}` string length, `$((expr))` arithmetic, `~` tilde expansion
|
|
2270
|
-
- [x] `${VAR:=assign}` assign-on-read, `${VAR:+alternate}` alternate-if-set
|
|
2271
|
-
- [x] Single-quote isolation in expansion — `$VAR` never expanded inside `'...'`
|
|
2272
|
-
- [x] `true` / `false` builtins
|
|
2273
|
-
- [x] Bare `VAR=val` assignments in `sh` scripts (`i=0`, `i=$((i+1))`)
|
|
2274
|
-
- [x] `$?` reflects last command exit code correctly across `&&` / `;` chains
|
|
2275
|
-
- [x] `node` / `python3` virtual REPL stubs — `node -e`, `node <file>`, `python3 -c`, `python3 <file>`, `--version` flags
|
|
2276
|
-
- [x] `/proc/self` and `/proc/<pid>` per-session process entries — `comm`, `status`, `cmdline`, `environ`, `cwd`, `exe`, `fd/`
|
|
2277
|
-
- [x] Snapshot diff tooling — `diffSnapshots()`, `formatDiff()`, `assertDiff()` exported from `typescript-virtual-container`
|
|
1428
|
+
Open:
|
|
1429
|
+
|
|
2278
1430
|
- [ ] WebSocket-based remote shell client (experimental)
|
|
1431
|
+
|
|
1432
|
+
<details>
|
|
1433
|
+
<summary>Completed</summary>
|
|
1434
|
+
|
|
1435
|
+
- [x] Custom command plugin API · per-user quotas · SSH public-key auth · per-IP rate limiting
|
|
1436
|
+
- [x] Pure in-memory VFS · symlinks · binary snapshot format (VFSB, ~27% smaller than JSON+base64)
|
|
1437
|
+
- [x] Linux rootfs on boot — `/etc`, `/proc`, `/sys`, `/dev`, `/usr`, `/var`
|
|
1438
|
+
- [x] Virtual package manager — `apt`/`dpkg`, 25 packages, VFS file writes
|
|
1439
|
+
- [x] 91 built-in commands across 9 categories
|
|
1440
|
+
- [x] Real shell interpreter — `if`/`for`/`while`/`case`/functions, `$(cmd)`, `$((expr))`, `${#VAR}`, single-quote isolation
|
|
1441
|
+
- [x] `curl`/`wget` as pure `fetch()` · VFS PATH resolution · `/sbin` root-only
|
|
1442
|
+
- [x] `/proc/self` and `/proc/<pid>` per-session entries
|
|
1443
|
+
- [x] Snapshot diff tooling — `diffSnapshots`, `formatDiff`, `assertDiff`
|
|
1444
|
+
- [x] `node`/`python3`/`npm`/`npx` — package-gated virtual REPL stubs
|
|
1445
|
+
- [x] Web shell bundle (`web.min.js`) — fully browser-native with IndexedDB VFS
|
|
1446
|
+
- [x] Self-standalone CLI (`self-standalone.js`) — single-file interactive shell, no install
|
|
1447
|
+
|
|
1448
|
+
</details>
|