typescript-virtual-container 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -23
- package/dist/.tsbuildinfo +1 -0
- package/dist/SSHMimic/executor.js +23 -5
- package/dist/commands/basename.d.ts +13 -0
- package/dist/commands/basename.js +45 -0
- package/dist/commands/file.d.ts +8 -0
- package/dist/commands/file.js +57 -0
- package/dist/commands/fun.d.ts +32 -0
- package/dist/commands/fun.js +172 -0
- package/dist/commands/ifconfig.d.ts +7 -0
- package/dist/commands/ifconfig.js +52 -0
- package/dist/commands/last.d.ts +13 -0
- package/dist/commands/last.js +68 -0
- package/dist/commands/manuals-bundle.js +598 -6
- package/dist/commands/registry.js +24 -2
- package/dist/commands/runtime.js +159 -106
- package/dist/commands/sh.js +5 -0
- package/dist/commands/tput.d.ts +13 -0
- package/dist/commands/tput.js +76 -0
- package/dist/commands/w.d.ts +7 -0
- package/dist/commands/w.js +38 -0
- package/dist/utils/expand.d.ts +12 -0
- package/dist/utils/expand.js +84 -0
- package/package.json +9 -3
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
- package/.github/dependabot.yml +0 -27
- package/.github/pull_request_template.md +0 -21
- package/.github/workflows/create-pull-request.yml +0 -85
- package/.github/workflows/publish.yml +0 -25
- package/.github/workflows/test-battery.yml +0 -102
- package/.vscode/settings.json +0 -20
- package/CODE_OF_CONDUCT.md +0 -39
- package/CONTRIBUTING.md +0 -59
- package/HONEYPOT.md +0 -358
- package/SECURITY.md +0 -33
- package/benchmark-results.txt +0 -40
- package/benchmark-virtualshell.ts +0 -88
- package/biome.json +0 -37
- package/build.js +0 -22
- package/builds/fortune-nyx-v1.5.1-directbash-k6.1.0.mjs +0 -1768
- package/builds/fortune-nyx-v1.5.1-ssh-nosftp.js +0 -1768
- package/builds/fortune-nyx-v1.5.1-ssh.cjs +0 -1769
- package/builds/fortune-nyx-v1.5.1-web.min.js +0 -17022
- package/bun.lock +0 -244
- package/docs/.nojekyll +0 -1
- package/docs/app.js +0 -1755
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -162
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/classes/HoneyPot.html +0 -31
- package/docs/classes/IdleManager.html +0 -162
- package/docs/classes/SshClient.html +0 -66
- package/docs/classes/VirtualFileSystem.html +0 -279
- package/docs/classes/VirtualPackageManager.html +0 -63
- package/docs/classes/VirtualSftpServer.html +0 -169
- package/docs/classes/VirtualShell.html +0 -285
- package/docs/classes/VirtualSshServer.html +0 -182
- package/docs/classes/VirtualUserManager.html +0 -276
- package/docs/demo.html +0 -82
- package/docs/functions/assertDiff.html +0 -6
- package/docs/functions/diffSnapshots.html +0 -7
- package/docs/functions/formatDiff.html +0 -6
- package/docs/functions/getArg.html +0 -13
- package/docs/functions/getFlag.html +0 -15
- package/docs/functions/ifFlag.html +0 -11
- package/docs/hierarchy.html +0 -1
- package/docs/index.html +0 -1869
- package/docs/interfaces/AuditLogEntry.html +0 -6
- package/docs/interfaces/CommandContext.html +0 -22
- package/docs/interfaces/CommandResult.html +0 -26
- package/docs/interfaces/ExecStream.html +0 -11
- package/docs/interfaces/HoneyPotStats.html +0 -16
- package/docs/interfaces/IdleManagerOptions.html +0 -7
- package/docs/interfaces/InstalledPackage.html +0 -20
- package/docs/interfaces/NanoEditorSession.html +0 -8
- package/docs/interfaces/PackageDefinition.html +0 -30
- package/docs/interfaces/PackageFile.html +0 -8
- package/docs/interfaces/PasswordChallenge.html +0 -16
- package/docs/interfaces/RemoveOptions.html +0 -4
- package/docs/interfaces/ShellEnv.html +0 -6
- package/docs/interfaces/ShellModule.html +0 -14
- package/docs/interfaces/ShellProperties.html +0 -14
- package/docs/interfaces/ShellStream.html +0 -11
- package/docs/interfaces/SudoChallenge.html +0 -24
- package/docs/interfaces/VfsBaseNode.html +0 -12
- package/docs/interfaces/VfsDiff.html +0 -10
- package/docs/interfaces/VfsDiffEntry.html +0 -6
- package/docs/interfaces/VfsDiffModified.html +0 -10
- package/docs/interfaces/VfsDirectoryNode.html +0 -15
- package/docs/interfaces/VfsFileNode.html +0 -17
- package/docs/interfaces/VfsOptions.html +0 -26
- package/docs/interfaces/VfsSnapshot.html +0 -3
- package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
- package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
- package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
- package/docs/interfaces/VirtualActiveSession.html +0 -12
- package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
- package/docs/interfaces/VirtualShellVfsLike.html +0 -15
- package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
- package/docs/interfaces/WriteFileOptions.html +0 -6
- package/docs/media/LICENSE +0 -21
- package/docs/modules.html +0 -1
- package/docs/types/ArgParseOptions.html +0 -4
- package/docs/types/CommandMode.html +0 -2
- package/docs/types/CommandOutcome.html +0 -2
- package/docs/types/IdleState.html +0 -1
- package/docs/types/VfsNodeStats.html +0 -2
- package/docs/types/VfsNodeType.html +0 -2
- package/docs/types/VfsPersistenceMode.html +0 -5
- package/docs/types/VfsSnapshotNode.html +0 -2
- package/examples/README.md +0 -288
- package/examples/app.js +0 -1755
- package/examples/app.ts +0 -299
- package/examples/build.js +0 -27
- package/examples/demo.html +0 -33
- package/examples/honeypot-audit.ts +0 -180
- package/examples/honeypot-export.ts +0 -253
- package/examples/honeypot-quickstart.ts +0 -110
- package/examples/index.html +0 -82
- package/examples/server.js +0 -55
- package/polyfills/buffer.js +0 -117
- package/polyfills/node_child_process/index.js +0 -2
- package/polyfills/node_crypto/index.js +0 -167
- package/polyfills/node_events/index.js +0 -9
- package/polyfills/node_fs/index.js +0 -202
- package/polyfills/node_fs/promises.js +0 -4
- package/polyfills/node_os/index.js +0 -9
- package/polyfills/node_path/index.js +0 -28
- package/polyfills/node_vm/index.js +0 -7
- package/polyfills/node_zlib/index.js +0 -3
- package/polyfills/process.js +0 -14
- package/polyfills/ssh2/index.js +0 -75
- package/scripts/build-all.mjs +0 -226
- package/scripts/build-names.mjs +0 -43
- package/scripts/generate-manuals-bundle.mjs +0 -49
- package/scripts/postinstall.js +0 -42
- package/scripts/publish-package.sh +0 -70
- package/src/Honeypot/index.ts +0 -457
- package/src/SSHClient/index.ts +0 -270
- package/src/SSHMimic/exec.ts +0 -49
- package/src/SSHMimic/executor.ts +0 -251
- package/src/SSHMimic/hostKey.ts +0 -21
- package/src/SSHMimic/index.ts +0 -337
- package/src/SSHMimic/loginBanner.ts +0 -36
- package/src/SSHMimic/loginFormat.ts +0 -10
- package/src/SSHMimic/prompt.ts +0 -14
- package/src/SSHMimic/sftp.ts +0 -883
- package/src/VirtualFileSystem/binaryPack.ts +0 -258
- package/src/VirtualFileSystem/index.ts +0 -1193
- package/src/VirtualFileSystem/internalTypes.ts +0 -43
- package/src/VirtualFileSystem/journal.ts +0 -171
- package/src/VirtualFileSystem/path.ts +0 -74
- package/src/VirtualPackageManager/index.ts +0 -1006
- package/src/VirtualShell/idleManager.ts +0 -137
- package/src/VirtualShell/index.ts +0 -475
- package/src/VirtualShell/shell.ts +0 -700
- package/src/VirtualShell/shellParser.ts +0 -285
- package/src/VirtualUserManager/index.ts +0 -758
- package/src/bun.d.ts +0 -1
- package/src/commands/adduser.ts +0 -103
- package/src/commands/alias.ts +0 -69
- package/src/commands/apt.ts +0 -233
- package/src/commands/awk.ts +0 -168
- package/src/commands/base64.ts +0 -29
- package/src/commands/cat.ts +0 -52
- package/src/commands/cd.ts +0 -25
- package/src/commands/chmod.ts +0 -85
- package/src/commands/clear.ts +0 -15
- package/src/commands/command-helpers.ts +0 -286
- package/src/commands/cp.ts +0 -83
- package/src/commands/curl.ts +0 -147
- package/src/commands/cut.ts +0 -36
- package/src/commands/date.ts +0 -30
- package/src/commands/declare.ts +0 -49
- package/src/commands/deluser.ts +0 -98
- package/src/commands/df.ts +0 -23
- package/src/commands/diff.ts +0 -43
- package/src/commands/dpkg.ts +0 -180
- package/src/commands/du.ts +0 -56
- package/src/commands/echo.ts +0 -58
- package/src/commands/env.ts +0 -23
- package/src/commands/exit.ts +0 -18
- package/src/commands/export.ts +0 -34
- package/src/commands/find.ts +0 -68
- package/src/commands/free.ts +0 -47
- package/src/commands/grep.ts +0 -116
- package/src/commands/groups.ts +0 -19
- package/src/commands/gzip.ts +0 -88
- package/src/commands/head.ts +0 -52
- package/src/commands/help.ts +0 -152
- package/src/commands/helpers.ts +0 -234
- package/src/commands/history.ts +0 -34
- package/src/commands/hostname.ts +0 -14
- package/src/commands/htop.ts +0 -20
- package/src/commands/id.ts +0 -19
- package/src/commands/index.ts +0 -9
- package/src/commands/kill.ts +0 -19
- package/src/commands/ln.ts +0 -71
- package/src/commands/ls.ts +0 -243
- package/src/commands/lsb-release.ts +0 -63
- package/src/commands/man.ts +0 -31
- package/src/commands/manuals/adduser.txt +0 -11
- package/src/commands/manuals/apt-cache.txt +0 -12
- package/src/commands/manuals/apt.txt +0 -20
- package/src/commands/manuals/awk.txt +0 -13
- package/src/commands/manuals/cat.txt +0 -14
- package/src/commands/manuals/cd.txt +0 -16
- package/src/commands/manuals/chmod.txt +0 -16
- package/src/commands/manuals/clear.txt +0 -10
- package/src/commands/manuals/cp.txt +0 -10
- package/src/commands/manuals/curl.txt +0 -20
- package/src/commands/manuals/date.txt +0 -14
- package/src/commands/manuals/declare.txt +0 -12
- package/src/commands/manuals/deluser.txt +0 -10
- package/src/commands/manuals/df.txt +0 -10
- package/src/commands/manuals/dpkg-query.txt +0 -11
- package/src/commands/manuals/dpkg.txt +0 -14
- package/src/commands/manuals/du.txt +0 -11
- package/src/commands/manuals/echo.txt +0 -11
- package/src/commands/manuals/false.txt +0 -10
- package/src/commands/manuals/find.txt +0 -11
- package/src/commands/manuals/free.txt +0 -12
- package/src/commands/manuals/grep.txt +0 -13
- package/src/commands/manuals/groups.txt +0 -10
- package/src/commands/manuals/gzip.txt +0 -11
- package/src/commands/manuals/head.txt +0 -10
- package/src/commands/manuals/help.txt +0 -11
- package/src/commands/manuals/history.txt +0 -11
- package/src/commands/manuals/hostname.txt +0 -10
- package/src/commands/manuals/id.txt +0 -10
- package/src/commands/manuals/kill.txt +0 -13
- package/src/commands/manuals/ls.txt +0 -20
- package/src/commands/manuals/lsb_release.txt +0 -14
- package/src/commands/manuals/mkdir.txt +0 -10
- package/src/commands/manuals/mv.txt +0 -10
- package/src/commands/manuals/nano.txt +0 -11
- package/src/commands/manuals/neofetch.txt +0 -10
- package/src/commands/manuals/node.txt +0 -13
- package/src/commands/manuals/npm.txt +0 -13
- package/src/commands/manuals/npx.txt +0 -13
- package/src/commands/manuals/passwd.txt +0 -11
- package/src/commands/manuals/ping.txt +0 -10
- package/src/commands/manuals/printf.txt +0 -11
- package/src/commands/manuals/ps.txt +0 -10
- package/src/commands/manuals/pwd.txt +0 -10
- package/src/commands/manuals/python3.txt +0 -13
- package/src/commands/manuals/readlink.txt +0 -10
- package/src/commands/manuals/return.txt +0 -10
- package/src/commands/manuals/rm.txt +0 -10
- package/src/commands/manuals/sed.txt +0 -11
- package/src/commands/manuals/set.txt +0 -11
- package/src/commands/manuals/shift.txt +0 -10
- package/src/commands/manuals/sleep.txt +0 -10
- package/src/commands/manuals/sort.txt +0 -12
- package/src/commands/manuals/source.txt +0 -11
- package/src/commands/manuals/ssh.txt +0 -11
- package/src/commands/manuals/stat.txt +0 -10
- package/src/commands/manuals/su.txt +0 -13
- package/src/commands/manuals/sudo.txt +0 -11
- package/src/commands/manuals/tail.txt +0 -10
- package/src/commands/manuals/tar.txt +0 -19
- package/src/commands/manuals/tee.txt +0 -10
- package/src/commands/manuals/test.txt +0 -11
- package/src/commands/manuals/touch.txt +0 -11
- package/src/commands/manuals/tr.txt +0 -10
- package/src/commands/manuals/trap.txt +0 -10
- package/src/commands/manuals/true.txt +0 -10
- package/src/commands/manuals/type.txt +0 -10
- package/src/commands/manuals/uname.txt +0 -12
- package/src/commands/manuals/uniq.txt +0 -12
- package/src/commands/manuals/unset.txt +0 -10
- package/src/commands/manuals/uptime.txt +0 -11
- package/src/commands/manuals/wc.txt +0 -12
- package/src/commands/manuals/wget.txt +0 -12
- package/src/commands/manuals/which.txt +0 -10
- package/src/commands/manuals/whoami.txt +0 -10
- package/src/commands/manuals/xargs.txt +0 -10
- package/src/commands/manuals-bundle.ts +0 -898
- package/src/commands/mkdir.ts +0 -31
- package/src/commands/mv.ts +0 -50
- package/src/commands/nano.ts +0 -38
- package/src/commands/neofetch.ts +0 -53
- package/src/commands/node.ts +0 -341
- package/src/commands/npm.ts +0 -132
- package/src/commands/passwd.ts +0 -50
- package/src/commands/ping.ts +0 -32
- package/src/commands/printf.ts +0 -129
- package/src/commands/ps.ts +0 -58
- package/src/commands/pwd.ts +0 -9
- package/src/commands/python.ts +0 -2229
- package/src/commands/read.ts +0 -46
- package/src/commands/registry.ts +0 -249
- package/src/commands/rm.ts +0 -42
- package/src/commands/runtime.ts +0 -378
- package/src/commands/sed.ts +0 -68
- package/src/commands/seq.ts +0 -43
- package/src/commands/set.ts +0 -29
- package/src/commands/sh.ts +0 -467
- package/src/commands/shift.ts +0 -63
- package/src/commands/sleep.ts +0 -20
- package/src/commands/sort.ts +0 -46
- package/src/commands/source.ts +0 -52
- package/src/commands/stat.ts +0 -61
- package/src/commands/su.ts +0 -72
- package/src/commands/sudo.ts +0 -76
- package/src/commands/tail.ts +0 -53
- package/src/commands/tar.ts +0 -102
- package/src/commands/tee.ts +0 -36
- package/src/commands/test.ts +0 -137
- package/src/commands/touch.ts +0 -28
- package/src/commands/tr.ts +0 -70
- package/src/commands/tree.ts +0 -20
- package/src/commands/true.ts +0 -27
- package/src/commands/type.ts +0 -48
- package/src/commands/uname.ts +0 -29
- package/src/commands/uniq.ts +0 -39
- package/src/commands/unset.ts +0 -17
- package/src/commands/uptime.ts +0 -54
- package/src/commands/wc.ts +0 -55
- package/src/commands/wget.ts +0 -148
- package/src/commands/which.ts +0 -37
- package/src/commands/who.ts +0 -25
- package/src/commands/whoami.ts +0 -14
- package/src/commands/xargs.ts +0 -31
- package/src/index.ts +0 -67
- package/src/modules/linuxRootfs.ts +0 -1961
- package/src/modules/neofetch.ts +0 -358
- package/src/modules/shellInteractive.ts +0 -57
- package/src/modules/shellRuntime.ts +0 -76
- package/src/self-standalone.ts +0 -542
- package/src/standalone-wo-sftp.ts +0 -38
- package/src/standalone.ts +0 -72
- package/src/types/commands.ts +0 -146
- package/src/types/pipeline.ts +0 -52
- package/src/types/streams.ts +0 -32
- package/src/types/tar-stream.d.ts +0 -38
- package/src/types/vfs.ts +0 -98
- package/src/utils/expand.ts +0 -491
- package/src/utils/perfLogger.ts +0 -72
- package/src/utils/tokenize.ts +0 -98
- package/src/utils/vfsDiff.ts +0 -275
- package/tests/command-helpers.test.ts +0 -116
- package/tests/commands-admin-net.test.ts +0 -441
- package/tests/commands-advanced.test.ts +0 -456
- package/tests/commands-core.test.ts +0 -562
- package/tests/commands-missing.test.ts +0 -570
- package/tests/commands-specific-units.test.ts +0 -327
- package/tests/commands-text-sys.test.ts +0 -445
- package/tests/expand.test.ts +0 -170
- package/tests/helpers.test.ts +0 -97
- package/tests/new-features.test.ts +0 -1036
- package/tests/parser-executor.test.ts +0 -37
- package/tests/sftp.test.ts +0 -323
- package/tests/ssh-exec.test.ts +0 -45
- package/tests/test-helper.ts +0 -79
- package/tests/users.test.ts +0 -86
- package/tsconfig.json +0 -49
- package/typedoc.json +0 -47
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IdleManager — cold-start / freeze-thaw system for VirtualShell.
|
|
3
|
-
*
|
|
4
|
-
* When a shell has had no activity for `idleThresholdMs`, the VFS tree is
|
|
5
|
-
* serialised to a compact binary buffer and the in-memory tree is released.
|
|
6
|
-
* On the next command the tree is reconstructed in ~0.1 ms.
|
|
7
|
-
*
|
|
8
|
-
* Memory freed per idle shell: the full JS object graph (~250+ InternalNodes
|
|
9
|
-
* for a typical rootfs + user files). The frozen buffer itself stays in RAM
|
|
10
|
-
* but is ~27% smaller than the live tree and GC-friendly (one flat Buffer).
|
|
11
|
-
*
|
|
12
|
-
* CPU freed: the VFS auto-flush setInterval is suspended while frozen.
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* ```ts
|
|
16
|
-
* const idle = new IdleManager(shell, { idleThresholdMs: 60_000 });
|
|
17
|
-
* idle.start();
|
|
18
|
-
* // call idle.ping() on every shell activity (exec, keypress, …)
|
|
19
|
-
* // call idle.stop() on shell destroy
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { EventEmitter } from "node:events";
|
|
24
|
-
import type VirtualFileSystem from "../VirtualFileSystem";
|
|
25
|
-
import { decodeVfs } from "../VirtualFileSystem/binaryPack";
|
|
26
|
-
|
|
27
|
-
export interface IdleManagerOptions {
|
|
28
|
-
/**
|
|
29
|
-
* Milliseconds of inactivity before the shell is frozen.
|
|
30
|
-
* Default: 60_000 (1 minute).
|
|
31
|
-
*/
|
|
32
|
-
idleThresholdMs?: number;
|
|
33
|
-
/**
|
|
34
|
-
* How often the idle check runs.
|
|
35
|
-
* Default: 15_000 (15 seconds).
|
|
36
|
-
*/
|
|
37
|
-
checkIntervalMs?: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type IdleState = "active" | "frozen";
|
|
41
|
-
|
|
42
|
-
export class IdleManager extends EventEmitter {
|
|
43
|
-
private readonly vfs: VirtualFileSystem;
|
|
44
|
-
private readonly idleThresholdMs: number;
|
|
45
|
-
private readonly checkIntervalMs: number;
|
|
46
|
-
|
|
47
|
-
private _state: IdleState = "active";
|
|
48
|
-
private _lastActivity = Date.now();
|
|
49
|
-
private _frozenBuffer: Buffer | null = null;
|
|
50
|
-
private _checkTimer: ReturnType<typeof setInterval> | null = null;
|
|
51
|
-
|
|
52
|
-
/** Emitted when the shell is frozen (VFS tree released). */
|
|
53
|
-
declare on: ((event: "freeze", listener: () => void) => this) &
|
|
54
|
-
((event: "thaw", listener: () => void) => this) &
|
|
55
|
-
((event: string, listener: (...args: unknown[]) => void) => this);
|
|
56
|
-
|
|
57
|
-
constructor(vfs: VirtualFileSystem, options: IdleManagerOptions = {}) {
|
|
58
|
-
super();
|
|
59
|
-
this.vfs = vfs;
|
|
60
|
-
this.idleThresholdMs = options.idleThresholdMs ?? 60_000;
|
|
61
|
-
this.checkIntervalMs = options.checkIntervalMs ?? 15_000;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Start monitoring for idle. Call once after shell initialisation. */
|
|
65
|
-
public start(): void {
|
|
66
|
-
if (this._checkTimer) return;
|
|
67
|
-
this._lastActivity = Date.now();
|
|
68
|
-
this._checkTimer = setInterval(() => this._check(), this.checkIntervalMs);
|
|
69
|
-
// Don't block process exit
|
|
70
|
-
if (typeof this._checkTimer === "object" && this._checkTimer !== null && "unref" in this._checkTimer) {
|
|
71
|
-
(this._checkTimer as NodeJS.Timeout).unref();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Stop monitoring and thaw if frozen. Call on shell destroy. */
|
|
76
|
-
public async stop(): Promise<void> {
|
|
77
|
-
if (this._checkTimer) {
|
|
78
|
-
clearInterval(this._checkTimer);
|
|
79
|
-
this._checkTimer = null;
|
|
80
|
-
}
|
|
81
|
-
if (this._state === "frozen") this._thaw();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Signal activity — resets the idle clock and thaws synchronously if frozen.
|
|
86
|
-
* Call this before every exec / keypress / session event.
|
|
87
|
-
*
|
|
88
|
-
* Thaw is intentionally synchronous: decodeVfs() is a pure CPU operation
|
|
89
|
-
* (~0.07 ms) with no I/O, so it is safe to block briefly on the hot path.
|
|
90
|
-
* This guarantees the VFS tree is fully restored before any command runs.
|
|
91
|
-
*/
|
|
92
|
-
public ping(): void {
|
|
93
|
-
this._lastActivity = Date.now();
|
|
94
|
-
if (this._state === "frozen") this._thaw();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Current idle state. */
|
|
98
|
-
public get state(): IdleState {
|
|
99
|
-
return this._state;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Ms since last activity. */
|
|
103
|
-
public get idleMs(): number {
|
|
104
|
-
return Date.now() - this._lastActivity;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ── Internal ──────────────────────────────────────────────────────────────
|
|
108
|
-
|
|
109
|
-
private _check(): void {
|
|
110
|
-
if (this._state === "frozen") return; // already frozen
|
|
111
|
-
if (Date.now() - this._lastActivity >= this.idleThresholdMs) {
|
|
112
|
-
void this._freeze();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private async _freeze(): Promise<void> {
|
|
117
|
-
if (this._state === "frozen") return;
|
|
118
|
-
// Flush any pending writes before freezing
|
|
119
|
-
await this.vfs.stopAutoFlush();
|
|
120
|
-
// Serialise the live tree to a compact binary buffer
|
|
121
|
-
this._frozenBuffer = this.vfs.encodeBinary();
|
|
122
|
-
// Release the live tree — GC can now collect all InternalNode objects
|
|
123
|
-
this.vfs.releaseTree();
|
|
124
|
-
this._state = "frozen";
|
|
125
|
-
this.emit("freeze");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private _thaw(): void {
|
|
129
|
-
if (this._state !== "frozen" || !this._frozenBuffer) return;
|
|
130
|
-
// Reconstruct the tree from the frozen buffer (~0.07 ms — pure CPU, no I/O)
|
|
131
|
-
const root = decodeVfs(this._frozenBuffer);
|
|
132
|
-
this.vfs.importRootTree(root);
|
|
133
|
-
this._frozenBuffer = null;
|
|
134
|
-
this._state = "active";
|
|
135
|
-
this.emit("thaw");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "node:events";
|
|
2
|
-
import { createCustomCommand, registerCommand } from "../commands/registry";
|
|
3
|
-
import { runCommand } from "../commands/runtime";
|
|
4
|
-
import {
|
|
5
|
-
bootstrapLinuxRootfs,
|
|
6
|
-
refreshProc,
|
|
7
|
-
syncEtcPasswd,
|
|
8
|
-
} from "../modules/linuxRootfs";
|
|
9
|
-
import type { CommandContext, CommandResult } from "../types/commands";
|
|
10
|
-
import type { ShellStream } from "../types/streams";
|
|
11
|
-
import type { VfsNodeStats } from "../types/vfs";
|
|
12
|
-
import type { PerfLogger } from "../utils/perfLogger";
|
|
13
|
-
import { createPerfLogger } from "../utils/perfLogger";
|
|
14
|
-
import VirtualFileSystem, { type VfsOptions } from "../VirtualFileSystem";
|
|
15
|
-
import { VirtualPackageManager } from "../VirtualPackageManager";
|
|
16
|
-
import { VirtualUserManager } from "../VirtualUserManager";
|
|
17
|
-
import { IdleManager, type IdleManagerOptions } from "./idleManager";
|
|
18
|
-
import { startShell } from "./shell";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Virtual machine identity strings surfaced by system-info commands
|
|
22
|
-
* (`uname`, `neofetch`, `lsb_release`, `/proc/version`, `/etc/os-release`).
|
|
23
|
-
*
|
|
24
|
-
* Pass this as the second argument to `new VirtualShell()` to customise the
|
|
25
|
-
* distro name, kernel version, and CPU architecture reported inside the shell.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* const shell = new VirtualShell("my-vm", {
|
|
30
|
-
* kernel: "6.1.0+custom-amd64",
|
|
31
|
-
* os: "Acme GNU/Linux x64",
|
|
32
|
-
* arch: "x86_64",
|
|
33
|
-
* });
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export interface ShellProperties {
|
|
37
|
-
/** Kernel version string (e.g. `"1.0.0+itsrealfortune+1-amd64"`). */
|
|
38
|
-
kernel: string;
|
|
39
|
-
/** Full OS description (e.g. `"Fortune GNU/Linux x64"`). */
|
|
40
|
-
os: string;
|
|
41
|
-
/** CPU architecture label (e.g. `"x86_64"`, `"aarch64"`). */
|
|
42
|
-
arch: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Minimal VFS interface accepted by {@link VirtualShell} as a drop-in replacement
|
|
47
|
-
* for the built-in {@link VirtualFileSystem}.
|
|
48
|
-
*/
|
|
49
|
-
export interface VirtualShellVfsLike {
|
|
50
|
-
restoreMirror(): Promise<void>;
|
|
51
|
-
flushMirror(): Promise<void>;
|
|
52
|
-
writeFile(targetPath: string, content: string | Uint8Array): void;
|
|
53
|
-
readFile(targetPath: string): string;
|
|
54
|
-
mkdir(targetPath: string, mode?: number): void;
|
|
55
|
-
exists(targetPath: string): boolean;
|
|
56
|
-
stat(targetPath: string): VfsNodeStats;
|
|
57
|
-
list(targetPath: string): string[];
|
|
58
|
-
remove(targetPath: string, options?: { recursive?: boolean }): void;
|
|
59
|
-
chmod?(targetPath: string, mode: number): void;
|
|
60
|
-
symlink?(targetPath: string, linkPath: string): void;
|
|
61
|
-
getUsageBytes?(targetPath?: string): number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Constructor options for {@link VirtualShell} when passing an existing VFS instance.
|
|
66
|
-
*/
|
|
67
|
-
export interface VirtualShellVfsOptions {
|
|
68
|
-
vfsInstance?: VirtualShellVfsLike;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function hasVfsInstance(obj: unknown): obj is { vfsInstance: VirtualShellVfsLike } {
|
|
72
|
-
return (
|
|
73
|
-
typeof obj === "object" &&
|
|
74
|
-
obj !== null &&
|
|
75
|
-
"vfsInstance" in obj &&
|
|
76
|
-
isVirtualShellVfsLike((obj as Record<string, unknown>).vfsInstance)
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isVirtualShellVfsLike(value: unknown): value is VirtualShellVfsLike {
|
|
81
|
-
if (typeof value !== "object" || value === null) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const candidate = value as Record<string, unknown>;
|
|
86
|
-
return (
|
|
87
|
-
typeof candidate.restoreMirror === "function" &&
|
|
88
|
-
typeof candidate.flushMirror === "function" &&
|
|
89
|
-
typeof candidate.writeFile === "function" &&
|
|
90
|
-
typeof candidate.readFile === "function" &&
|
|
91
|
-
typeof candidate.mkdir === "function" &&
|
|
92
|
-
typeof candidate.exists === "function" &&
|
|
93
|
-
typeof candidate.stat === "function" &&
|
|
94
|
-
typeof candidate.list === "function" &&
|
|
95
|
-
typeof candidate.remove === "function" &&
|
|
96
|
-
typeof candidate.copy === "function" &&
|
|
97
|
-
typeof candidate.move === "function" &&
|
|
98
|
-
typeof candidate.touch === "function"
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const defaultShellProperties: ShellProperties = {
|
|
103
|
-
kernel: "1.0.0+itsrealfortune+1-amd64",
|
|
104
|
-
os: "Fortune GNU/Linux x64",
|
|
105
|
-
arch: "x86_64",
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const perf: PerfLogger = createPerfLogger("VirtualShell");
|
|
109
|
-
|
|
110
|
-
function resolveAutoSudoForNewUsers(): boolean {
|
|
111
|
-
const configured = process.env.SSH_MIMIC_AUTO_SUDO_NEW_USERS;
|
|
112
|
-
if (!configured) {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return !["0", "false", "no", "off"].includes(configured.toLowerCase());
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Coordinates the virtual filesystem, user manager, package manager, and
|
|
121
|
-
* command runtime for a single isolated shell environment.
|
|
122
|
-
*
|
|
123
|
-
* Each instance owns its own VFS tree, user database, package registry, and
|
|
124
|
-
* session state — multiple instances are fully independent.
|
|
125
|
-
*
|
|
126
|
-
* Instances are consumed both by the SSH/SFTP server facades and directly via
|
|
127
|
-
* the programmatic `SshClient` API.
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* ```ts
|
|
131
|
-
* const shell = new VirtualShell("my-vm");
|
|
132
|
-
* await shell.ensureInitialized();
|
|
133
|
-
* const client = new SshClient(shell, "root");
|
|
134
|
-
* const result = await client.exec("uname -a");
|
|
135
|
-
* ```
|
|
136
|
-
*
|
|
137
|
-
* @fires VirtualShell#initialized Emitted once the VFS and users are ready.
|
|
138
|
-
* @fires VirtualShell#command Emitted after every command execution.
|
|
139
|
-
* @fires VirtualShell#session:start Emitted when an interactive session opens.
|
|
140
|
-
*/
|
|
141
|
-
class VirtualShell extends EventEmitter {
|
|
142
|
-
/** Backing virtual filesystem — use for direct path operations. */
|
|
143
|
-
vfs: VirtualFileSystem;
|
|
144
|
-
/** Virtual user database — use for auth, quotas, and session tracking. */
|
|
145
|
-
users: VirtualUserManager;
|
|
146
|
-
/** APT/dpkg package manager backed by the built-in package registry. */
|
|
147
|
-
packageManager: VirtualPackageManager;
|
|
148
|
-
/** Hostname shown in the shell prompt and SSH ident string. */
|
|
149
|
-
hostname: string;
|
|
150
|
-
/** Distro identity strings surfaced by `uname`, `neofetch`, etc. */
|
|
151
|
-
properties: ShellProperties;
|
|
152
|
-
/** Unix ms timestamp of shell creation — used by `uptime` and `/proc/uptime`. */
|
|
153
|
-
startTime: number;
|
|
154
|
-
/** Idle / cold-start manager — null until `enableIdleManagement()` is called. */
|
|
155
|
-
private _idle: IdleManager | null = null;
|
|
156
|
-
private initialized: Promise<void>;
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Creates a new virtual shell instance.
|
|
160
|
-
*
|
|
161
|
-
* @param hostname Virtual hostname used for prompts and idents.
|
|
162
|
-
* @param properties Customizable properties shown in `uname -a` and similar commands.
|
|
163
|
-
* @param vfsOptionsOrInstance Optional VFS persistence options (mode, snapshotPath) or an existing VFS instance.
|
|
164
|
-
*/
|
|
165
|
-
constructor(
|
|
166
|
-
hostname: string,
|
|
167
|
-
properties?: ShellProperties,
|
|
168
|
-
vfsOptionsOrInstance?: VfsOptions | VirtualShellVfsLike | VirtualShellVfsOptions,
|
|
169
|
-
) {
|
|
170
|
-
super();
|
|
171
|
-
perf.mark("constructor");
|
|
172
|
-
this.hostname = hostname;
|
|
173
|
-
this.properties = properties || defaultShellProperties;
|
|
174
|
-
this.startTime = Date.now();
|
|
175
|
-
|
|
176
|
-
if (isVirtualShellVfsLike(vfsOptionsOrInstance)) {
|
|
177
|
-
this.vfs = vfsOptionsOrInstance as unknown as VirtualFileSystem;
|
|
178
|
-
} else if (hasVfsInstance(vfsOptionsOrInstance)) {
|
|
179
|
-
this.vfs = vfsOptionsOrInstance.vfsInstance as unknown as VirtualFileSystem;
|
|
180
|
-
} else {
|
|
181
|
-
this.vfs = new VirtualFileSystem((vfsOptionsOrInstance as VfsOptions) ?? {});
|
|
182
|
-
}
|
|
183
|
-
this.users = new VirtualUserManager(this.vfs, resolveAutoSudoForNewUsers());
|
|
184
|
-
this.packageManager = new VirtualPackageManager(this.vfs, this.users);
|
|
185
|
-
|
|
186
|
-
// Store references to avoid TypeScript "used before assigned" errors
|
|
187
|
-
const vfs = this.vfs;
|
|
188
|
-
const users = this.users;
|
|
189
|
-
const pm = this.packageManager;
|
|
190
|
-
const shellProps = this.properties;
|
|
191
|
-
const shellHostname = this.hostname;
|
|
192
|
-
const startTime = this.startTime;
|
|
193
|
-
|
|
194
|
-
// Initialize both VFS mirror and users, ensuring all is ready before auth
|
|
195
|
-
this.initialized = (async () => {
|
|
196
|
-
await vfs.restoreMirror();
|
|
197
|
-
await users.initialize();
|
|
198
|
-
// Bootstrap Linux rootfs (idempotent)
|
|
199
|
-
bootstrapLinuxRootfs(vfs, users, shellHostname, shellProps, startTime);
|
|
200
|
-
// Load installed packages from dpkg status
|
|
201
|
-
pm.load();
|
|
202
|
-
this.emit("initialized");
|
|
203
|
-
})();
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Ensures initialization is complete before allowing operations.
|
|
208
|
-
* Call this before any authentication or command execution.
|
|
209
|
-
*/
|
|
210
|
-
public async ensureInitialized(): Promise<void> {
|
|
211
|
-
perf.mark("ensureInitialized");
|
|
212
|
-
await this.initialized;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Registers a new command in the shell runtime.
|
|
217
|
-
*
|
|
218
|
-
* @param name Case-insensitive command name (no spaces).
|
|
219
|
-
* @param params List of parameter names for help text (no validation).
|
|
220
|
-
* @param callback Function invoked with command context on execution.
|
|
221
|
-
*/
|
|
222
|
-
addCommand(
|
|
223
|
-
name: string,
|
|
224
|
-
params: string[],
|
|
225
|
-
callback: (ctx: CommandContext) => CommandResult | Promise<CommandResult>,
|
|
226
|
-
): void {
|
|
227
|
-
const normalized = name.trim().toLowerCase();
|
|
228
|
-
if (normalized.length === 0 || /\s/.test(normalized)) {
|
|
229
|
-
throw new Error("Command name must be non-empty and contain no spaces");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
registerCommand(createCustomCommand(normalized, params, callback));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Executes a raw command line string programmatically.
|
|
237
|
-
*
|
|
238
|
-
* Supports the full shell operator set (`&&`, `||`, `;`, `|`, `>`, `<`,
|
|
239
|
-
* `$(cmd)`) and alias expansion. The result is emitted via the
|
|
240
|
-
* `"command"` event but not returned — use `SshClient.exec()` for a
|
|
241
|
-
* result-returning wrapper.
|
|
242
|
-
*
|
|
243
|
-
* @param rawInput Unparsed command line (e.g. `"ls -la /tmp"`).
|
|
244
|
-
* @param authUser Username to run the command as.
|
|
245
|
-
* @param cwd Current working directory for path resolution.
|
|
246
|
-
*
|
|
247
|
-
* @returns CommandResult or a Promise that resolves to CommandResult. The result is also emitted via the "command" event.
|
|
248
|
-
*/
|
|
249
|
-
executeCommand(rawInput: string, authUser: string, cwd: string): CommandResult | Promise<CommandResult> {
|
|
250
|
-
perf.mark("executeCommand");
|
|
251
|
-
this._idle?.ping();
|
|
252
|
-
const result = runCommand(rawInput, authUser, this.hostname, "shell", cwd, this);
|
|
253
|
-
this.emit("command", { command: rawInput, user: authUser, cwd });
|
|
254
|
-
return result;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Attaches an interactive PTY session to this shell instance.
|
|
259
|
-
*
|
|
260
|
-
* Called internally by `SshMimic` when a client opens a shell channel.
|
|
261
|
-
* The session reads from `stream` (user keystrokes) and writes back ANSI
|
|
262
|
-
* output. History, `.bashrc` sourcing, and Ctrl+W/Ctrl+U line editing are
|
|
263
|
-
* handled automatically.
|
|
264
|
-
*
|
|
265
|
-
* @param stream Bidirectional SSH channel stream.
|
|
266
|
-
* @param authUser Authenticated username bound to this session.
|
|
267
|
-
* @param sessionId Stable session UUID (used for `who` output), or `null`.
|
|
268
|
-
* @param remoteAddress IP or hostname of the connecting client.
|
|
269
|
-
* @param terminalSize Initial terminal dimensions in columns and rows.
|
|
270
|
-
*/
|
|
271
|
-
startInteractiveSession(
|
|
272
|
-
stream: ShellStream,
|
|
273
|
-
authUser: string,
|
|
274
|
-
sessionId: string | null,
|
|
275
|
-
remoteAddress: string,
|
|
276
|
-
terminalSize: { cols: number; rows: number },
|
|
277
|
-
): void {
|
|
278
|
-
perf.mark("startInteractiveSession");
|
|
279
|
-
this._idle?.ping();
|
|
280
|
-
// Interactive shell logic
|
|
281
|
-
this.emit("session:start", { user: authUser, sessionId, remoteAddress });
|
|
282
|
-
startShell(
|
|
283
|
-
this.properties,
|
|
284
|
-
stream,
|
|
285
|
-
authUser,
|
|
286
|
-
this.hostname,
|
|
287
|
-
sessionId,
|
|
288
|
-
remoteAddress,
|
|
289
|
-
terminalSize,
|
|
290
|
-
this,
|
|
291
|
-
);
|
|
292
|
-
// Refresh /proc/<pid> and /proc/self after session is registered
|
|
293
|
-
this.refreshProcSessions();
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Refreshes the `/proc` virtual filesystem with current system state.
|
|
298
|
-
*
|
|
299
|
-
* Updates `/proc/uptime`, `/proc/meminfo`, `/proc/cpuinfo`,
|
|
300
|
-
* `/proc/version`, `/proc/loadavg`, `/proc/self`, and per-session
|
|
301
|
-
* `/proc/<pid>` entries from live session and host data.
|
|
302
|
-
*
|
|
303
|
-
* Called automatically during `bootstrapLinuxRootfs`. Call again before
|
|
304
|
-
* reading `/proc` files for up-to-date values.
|
|
305
|
-
*/
|
|
306
|
-
public refreshProcFs(): void {
|
|
307
|
-
refreshProc(
|
|
308
|
-
this.vfs,
|
|
309
|
-
this.properties,
|
|
310
|
-
this.hostname,
|
|
311
|
-
this.startTime,
|
|
312
|
-
this.users.listActiveSessions(),
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Mount a host directory into the VFS at `vPath`.
|
|
318
|
-
*
|
|
319
|
-
* Delegates file operations inside `vPath` to the host filesystem via
|
|
320
|
-
* `node:fs`. Silently ignored in browser environments.
|
|
321
|
-
*
|
|
322
|
-
* @param vPath Absolute path inside the VM (e.g. `"/app"`).
|
|
323
|
-
* @param hostPath Path on the host — relative paths are resolved from `process.cwd()`.
|
|
324
|
-
* @param options `{ readOnly?: boolean }` — default `true`.
|
|
325
|
-
*
|
|
326
|
-
* @example
|
|
327
|
-
* ```ts
|
|
328
|
-
* const shell = new VirtualShell("dev-vm");
|
|
329
|
-
* await shell.ensureInitialized();
|
|
330
|
-
* shell.mount("/workspace", "./my-project");
|
|
331
|
-
* // shell commands can now read ./my-project files via /workspace
|
|
332
|
-
* ```
|
|
333
|
-
*/
|
|
334
|
-
public mount(
|
|
335
|
-
vPath: string,
|
|
336
|
-
hostPath: string,
|
|
337
|
-
options: { readOnly?: boolean } = {},
|
|
338
|
-
): void {
|
|
339
|
-
this.vfs.mount(vPath, hostPath, options);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/** Remove a previously mounted host directory. */
|
|
343
|
-
public unmount(vPath: string): void {
|
|
344
|
-
this.vfs.unmount(vPath);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/** List all active mounts. */
|
|
348
|
-
public getMounts(): Array<{ vPath: string; hostPath: string; readOnly: boolean }> {
|
|
349
|
-
return this.vfs.getMounts();
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Updates only the session-dependent `/proc` entries (`/proc/<pid>`,
|
|
354
|
-
* `/proc/self`). Cheaper than a full `refreshProcFs()` — call this
|
|
355
|
-
* whenever a session is registered or unregistered.
|
|
356
|
-
*/
|
|
357
|
-
public refreshProcSessions(): void {
|
|
358
|
-
refreshProc(
|
|
359
|
-
this.vfs,
|
|
360
|
-
this.properties,
|
|
361
|
-
this.hostname,
|
|
362
|
-
this.startTime,
|
|
363
|
-
this.users.listActiveSessions(),
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Syncs `/etc/passwd`, `/etc/group`, and `/etc/shadow` from the current
|
|
369
|
-
* `VirtualUserManager` state.
|
|
370
|
-
*
|
|
371
|
-
* Called automatically during `bootstrapLinuxRootfs`. Call again after
|
|
372
|
-
* `users.addUser()`, `users.deleteUser()`, or `users.addSudoer()` to keep
|
|
373
|
-
* the classic Unix credential files in sync with the user manager.
|
|
374
|
-
*/
|
|
375
|
-
public syncPasswd(): void {
|
|
376
|
-
syncEtcPasswd(this.vfs, this.users);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Returns virtual filesystem instance after server started.
|
|
381
|
-
*
|
|
382
|
-
* @returns VirtualFileSystem or null when not started.
|
|
383
|
-
*/
|
|
384
|
-
public getVfs(): VirtualFileSystem | null {
|
|
385
|
-
return this?.vfs ?? null;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Returns user manager instance after server started.
|
|
390
|
-
*
|
|
391
|
-
* @returns VirtualUserManager or null when not started.
|
|
392
|
-
*/
|
|
393
|
-
public getUsers(): VirtualUserManager | null {
|
|
394
|
-
return this?.users ?? null;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Returns hostname shown in prompts and idents.
|
|
399
|
-
*
|
|
400
|
-
* @returns Configured hostname label.
|
|
401
|
-
*/
|
|
402
|
-
public getHostname(): string {
|
|
403
|
-
return this?.hostname;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Writes a file on behalf of a user with quota enforcement.
|
|
408
|
-
*
|
|
409
|
-
* @param authUser User performing the write.
|
|
410
|
-
* @param targetPath Destination path.
|
|
411
|
-
* @param content File content.
|
|
412
|
-
*/
|
|
413
|
-
public writeFileAsUser(
|
|
414
|
-
authUser: string,
|
|
415
|
-
targetPath: string,
|
|
416
|
-
content: string | Buffer,
|
|
417
|
-
): void {
|
|
418
|
-
perf.mark("writeFileAsUser");
|
|
419
|
-
this.users.assertWriteWithinQuota(authUser, targetPath, content);
|
|
420
|
-
this.vfs.writeFile(targetPath, content);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Enable idle detection and cold-start freeze/thaw for this shell.
|
|
425
|
-
*
|
|
426
|
-
* After `idleThresholdMs` of inactivity the VFS tree is serialised and
|
|
427
|
-
* released from RAM. The next command transparently restores it in ~0.1 ms.
|
|
428
|
-
*
|
|
429
|
-
* @example
|
|
430
|
-
* ```ts
|
|
431
|
-
* await shell.ensureInitialized();
|
|
432
|
-
* shell.enableIdleManagement({ idleThresholdMs: 60_000 });
|
|
433
|
-
* ```
|
|
434
|
-
*/
|
|
435
|
-
public enableIdleManagement(options?: IdleManagerOptions): void {
|
|
436
|
-
if (this._idle) return; // already enabled
|
|
437
|
-
this._idle = new IdleManager(this.vfs, options);
|
|
438
|
-
this._idle.on("freeze", () => this.emit("shell:freeze"));
|
|
439
|
-
this._idle.on("thaw", () => this.emit("shell:thaw"));
|
|
440
|
-
this._idle.start();
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Disable idle management and thaw the shell if currently frozen.
|
|
445
|
-
* Safe to call even if idle management was never enabled.
|
|
446
|
-
*/
|
|
447
|
-
public async disableIdleManagement(): Promise<void> {
|
|
448
|
-
if (!this._idle) return;
|
|
449
|
-
await this._idle.stop();
|
|
450
|
-
this._idle = null;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Current idle state — `"active"` or `"frozen"`.
|
|
455
|
-
* Returns `"active"` when idle management is disabled.
|
|
456
|
-
*/
|
|
457
|
-
public get idleState(): "active" | "frozen" {
|
|
458
|
-
return this._idle?.state ?? "active";
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/** Milliseconds since last shell activity. 0 when idle management is disabled. */
|
|
462
|
-
public get idleMs(): number {
|
|
463
|
-
return this._idle?.idleMs ?? 0;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Ping the idle manager to signal external activity (e.g. SFTP, direct VFS writes).
|
|
468
|
-
* No-op when idle management is disabled.
|
|
469
|
-
*/
|
|
470
|
-
public pingIdle(): void {
|
|
471
|
-
this._idle?.ping();
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
export { VirtualShell };
|