typescript-virtual-container 1.5.3 → 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.
Files changed (365) hide show
  1. package/README.md +43 -23
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/SSHMimic/executor.js +23 -5
  4. package/dist/VirtualPackageManager/index.js +10 -0
  5. package/dist/commands/basename.d.ts +13 -0
  6. package/dist/commands/basename.js +45 -0
  7. package/dist/commands/file.d.ts +8 -0
  8. package/dist/commands/file.js +57 -0
  9. package/dist/commands/fun.d.ts +32 -0
  10. package/dist/commands/fun.js +172 -0
  11. package/dist/commands/ifconfig.d.ts +7 -0
  12. package/dist/commands/ifconfig.js +52 -0
  13. package/dist/commands/last.d.ts +13 -0
  14. package/dist/commands/last.js +68 -0
  15. package/dist/commands/manuals-bundle.js +598 -6
  16. package/dist/commands/registry.js +24 -2
  17. package/dist/commands/runtime.js +22 -2
  18. package/dist/commands/sh.js +5 -0
  19. package/dist/commands/tput.d.ts +13 -0
  20. package/dist/commands/tput.js +76 -0
  21. package/dist/commands/w.d.ts +7 -0
  22. package/dist/commands/w.js +38 -0
  23. package/dist/utils/expand.d.ts +12 -0
  24. package/dist/utils/expand.js +84 -0
  25. package/package.json +9 -3
  26. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  27. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  28. package/.github/dependabot.yml +0 -27
  29. package/.github/pull_request_template.md +0 -21
  30. package/.github/workflows/create-pull-request.yml +0 -85
  31. package/.github/workflows/publish.yml +0 -25
  32. package/.github/workflows/test-battery.yml +0 -102
  33. package/.vscode/settings.json +0 -20
  34. package/CODE_OF_CONDUCT.md +0 -39
  35. package/CONTRIBUTING.md +0 -59
  36. package/HONEYPOT.md +0 -358
  37. package/SECURITY.md +0 -33
  38. package/benchmark-results.txt +0 -40
  39. package/benchmark-virtualshell.ts +0 -88
  40. package/biome.json +0 -37
  41. package/build.js +0 -22
  42. package/builds/fortune-nyx-v1.5.3-directbash-k6.1.0.mjs +0 -1764
  43. package/builds/fortune-nyx-v1.5.3-ssh-nosftp.js +0 -1764
  44. package/builds/fortune-nyx-v1.5.3-ssh.cjs +0 -1765
  45. package/builds/fortune-nyx-v1.5.3-web.min.js +0 -17036
  46. package/bun.lock +0 -244
  47. package/docs/.nojekyll +0 -1
  48. package/docs/app.js +0 -1751
  49. package/docs/assets/hierarchy.js +0 -1
  50. package/docs/assets/highlight.css +0 -162
  51. package/docs/assets/icons.js +0 -18
  52. package/docs/assets/icons.svg +0 -1
  53. package/docs/assets/main.js +0 -60
  54. package/docs/assets/navigation.js +0 -1
  55. package/docs/assets/search.js +0 -1
  56. package/docs/assets/style.css +0 -1633
  57. package/docs/classes/HoneyPot.html +0 -31
  58. package/docs/classes/IdleManager.html +0 -162
  59. package/docs/classes/SshClient.html +0 -66
  60. package/docs/classes/VirtualFileSystem.html +0 -279
  61. package/docs/classes/VirtualPackageManager.html +0 -63
  62. package/docs/classes/VirtualSftpServer.html +0 -169
  63. package/docs/classes/VirtualShell.html +0 -285
  64. package/docs/classes/VirtualSshServer.html +0 -182
  65. package/docs/classes/VirtualUserManager.html +0 -276
  66. package/docs/demo.html +0 -82
  67. package/docs/functions/assertDiff.html +0 -6
  68. package/docs/functions/diffSnapshots.html +0 -7
  69. package/docs/functions/formatDiff.html +0 -6
  70. package/docs/functions/getArg.html +0 -13
  71. package/docs/functions/getFlag.html +0 -15
  72. package/docs/functions/ifFlag.html +0 -11
  73. package/docs/hierarchy.html +0 -1
  74. package/docs/index.html +0 -1869
  75. package/docs/interfaces/AuditLogEntry.html +0 -6
  76. package/docs/interfaces/CommandContext.html +0 -22
  77. package/docs/interfaces/CommandResult.html +0 -26
  78. package/docs/interfaces/ExecStream.html +0 -11
  79. package/docs/interfaces/HoneyPotStats.html +0 -16
  80. package/docs/interfaces/IdleManagerOptions.html +0 -7
  81. package/docs/interfaces/InstalledPackage.html +0 -20
  82. package/docs/interfaces/NanoEditorSession.html +0 -8
  83. package/docs/interfaces/PackageDefinition.html +0 -30
  84. package/docs/interfaces/PackageFile.html +0 -8
  85. package/docs/interfaces/PasswordChallenge.html +0 -16
  86. package/docs/interfaces/RemoveOptions.html +0 -4
  87. package/docs/interfaces/ShellEnv.html +0 -6
  88. package/docs/interfaces/ShellModule.html +0 -14
  89. package/docs/interfaces/ShellProperties.html +0 -14
  90. package/docs/interfaces/ShellStream.html +0 -11
  91. package/docs/interfaces/SudoChallenge.html +0 -24
  92. package/docs/interfaces/VfsBaseNode.html +0 -12
  93. package/docs/interfaces/VfsDiff.html +0 -10
  94. package/docs/interfaces/VfsDiffEntry.html +0 -6
  95. package/docs/interfaces/VfsDiffModified.html +0 -10
  96. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  97. package/docs/interfaces/VfsFileNode.html +0 -17
  98. package/docs/interfaces/VfsOptions.html +0 -26
  99. package/docs/interfaces/VfsSnapshot.html +0 -3
  100. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  101. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  102. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  103. package/docs/interfaces/VirtualActiveSession.html +0 -12
  104. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  105. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  106. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  107. package/docs/interfaces/WriteFileOptions.html +0 -6
  108. package/docs/media/LICENSE +0 -21
  109. package/docs/modules.html +0 -1
  110. package/docs/types/ArgParseOptions.html +0 -4
  111. package/docs/types/CommandMode.html +0 -2
  112. package/docs/types/CommandOutcome.html +0 -2
  113. package/docs/types/IdleState.html +0 -1
  114. package/docs/types/VfsNodeStats.html +0 -2
  115. package/docs/types/VfsNodeType.html +0 -2
  116. package/docs/types/VfsPersistenceMode.html +0 -5
  117. package/docs/types/VfsSnapshotNode.html +0 -2
  118. package/examples/README.md +0 -288
  119. package/examples/app.js +0 -1751
  120. package/examples/app.ts +0 -299
  121. package/examples/build.js +0 -27
  122. package/examples/demo.html +0 -33
  123. package/examples/honeypot-audit.ts +0 -180
  124. package/examples/honeypot-export.ts +0 -253
  125. package/examples/honeypot-quickstart.ts +0 -110
  126. package/examples/index.html +0 -82
  127. package/examples/server.js +0 -55
  128. package/polyfills/buffer.js +0 -117
  129. package/polyfills/node_child_process/index.js +0 -2
  130. package/polyfills/node_crypto/index.js +0 -167
  131. package/polyfills/node_events/index.js +0 -9
  132. package/polyfills/node_fs/index.js +0 -202
  133. package/polyfills/node_fs/promises.js +0 -4
  134. package/polyfills/node_os/index.js +0 -9
  135. package/polyfills/node_path/index.js +0 -28
  136. package/polyfills/node_vm/index.js +0 -7
  137. package/polyfills/node_zlib/index.js +0 -3
  138. package/polyfills/process.js +0 -14
  139. package/polyfills/ssh2/index.js +0 -75
  140. package/scripts/build-all.mjs +0 -226
  141. package/scripts/build-names.mjs +0 -43
  142. package/scripts/generate-manuals-bundle.mjs +0 -49
  143. package/scripts/postinstall.js +0 -42
  144. package/scripts/publish-package.sh +0 -70
  145. package/src/Honeypot/index.ts +0 -457
  146. package/src/SSHClient/index.ts +0 -270
  147. package/src/SSHMimic/exec.ts +0 -49
  148. package/src/SSHMimic/executor.ts +0 -251
  149. package/src/SSHMimic/hostKey.ts +0 -21
  150. package/src/SSHMimic/index.ts +0 -337
  151. package/src/SSHMimic/loginBanner.ts +0 -36
  152. package/src/SSHMimic/loginFormat.ts +0 -10
  153. package/src/SSHMimic/prompt.ts +0 -14
  154. package/src/SSHMimic/sftp.ts +0 -883
  155. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  156. package/src/VirtualFileSystem/index.ts +0 -1193
  157. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  158. package/src/VirtualFileSystem/journal.ts +0 -171
  159. package/src/VirtualFileSystem/path.ts +0 -74
  160. package/src/VirtualPackageManager/index.ts +0 -996
  161. package/src/VirtualShell/idleManager.ts +0 -137
  162. package/src/VirtualShell/index.ts +0 -475
  163. package/src/VirtualShell/shell.ts +0 -700
  164. package/src/VirtualShell/shellParser.ts +0 -285
  165. package/src/VirtualUserManager/index.ts +0 -758
  166. package/src/bun.d.ts +0 -1
  167. package/src/commands/adduser.ts +0 -103
  168. package/src/commands/alias.ts +0 -69
  169. package/src/commands/apt.ts +0 -233
  170. package/src/commands/awk.ts +0 -168
  171. package/src/commands/base64.ts +0 -29
  172. package/src/commands/cat.ts +0 -52
  173. package/src/commands/cd.ts +0 -25
  174. package/src/commands/chmod.ts +0 -85
  175. package/src/commands/clear.ts +0 -15
  176. package/src/commands/command-helpers.ts +0 -286
  177. package/src/commands/cp.ts +0 -83
  178. package/src/commands/curl.ts +0 -147
  179. package/src/commands/cut.ts +0 -36
  180. package/src/commands/date.ts +0 -30
  181. package/src/commands/declare.ts +0 -49
  182. package/src/commands/deluser.ts +0 -98
  183. package/src/commands/df.ts +0 -23
  184. package/src/commands/diff.ts +0 -43
  185. package/src/commands/dpkg.ts +0 -180
  186. package/src/commands/du.ts +0 -56
  187. package/src/commands/echo.ts +0 -58
  188. package/src/commands/env.ts +0 -23
  189. package/src/commands/exit.ts +0 -18
  190. package/src/commands/export.ts +0 -34
  191. package/src/commands/find.ts +0 -68
  192. package/src/commands/free.ts +0 -47
  193. package/src/commands/grep.ts +0 -116
  194. package/src/commands/groups.ts +0 -19
  195. package/src/commands/gzip.ts +0 -88
  196. package/src/commands/head.ts +0 -52
  197. package/src/commands/help.ts +0 -152
  198. package/src/commands/helpers.ts +0 -234
  199. package/src/commands/history.ts +0 -34
  200. package/src/commands/hostname.ts +0 -14
  201. package/src/commands/htop.ts +0 -20
  202. package/src/commands/id.ts +0 -19
  203. package/src/commands/index.ts +0 -9
  204. package/src/commands/kill.ts +0 -19
  205. package/src/commands/ln.ts +0 -71
  206. package/src/commands/ls.ts +0 -243
  207. package/src/commands/lsb-release.ts +0 -63
  208. package/src/commands/man.ts +0 -31
  209. package/src/commands/manuals/adduser.txt +0 -11
  210. package/src/commands/manuals/apt-cache.txt +0 -12
  211. package/src/commands/manuals/apt.txt +0 -20
  212. package/src/commands/manuals/awk.txt +0 -13
  213. package/src/commands/manuals/cat.txt +0 -14
  214. package/src/commands/manuals/cd.txt +0 -16
  215. package/src/commands/manuals/chmod.txt +0 -16
  216. package/src/commands/manuals/clear.txt +0 -10
  217. package/src/commands/manuals/cp.txt +0 -10
  218. package/src/commands/manuals/curl.txt +0 -20
  219. package/src/commands/manuals/date.txt +0 -14
  220. package/src/commands/manuals/declare.txt +0 -12
  221. package/src/commands/manuals/deluser.txt +0 -10
  222. package/src/commands/manuals/df.txt +0 -10
  223. package/src/commands/manuals/dpkg-query.txt +0 -11
  224. package/src/commands/manuals/dpkg.txt +0 -14
  225. package/src/commands/manuals/du.txt +0 -11
  226. package/src/commands/manuals/echo.txt +0 -11
  227. package/src/commands/manuals/false.txt +0 -10
  228. package/src/commands/manuals/find.txt +0 -11
  229. package/src/commands/manuals/free.txt +0 -12
  230. package/src/commands/manuals/grep.txt +0 -13
  231. package/src/commands/manuals/groups.txt +0 -10
  232. package/src/commands/manuals/gzip.txt +0 -11
  233. package/src/commands/manuals/head.txt +0 -10
  234. package/src/commands/manuals/help.txt +0 -11
  235. package/src/commands/manuals/history.txt +0 -11
  236. package/src/commands/manuals/hostname.txt +0 -10
  237. package/src/commands/manuals/id.txt +0 -10
  238. package/src/commands/manuals/kill.txt +0 -13
  239. package/src/commands/manuals/ls.txt +0 -20
  240. package/src/commands/manuals/lsb_release.txt +0 -14
  241. package/src/commands/manuals/mkdir.txt +0 -10
  242. package/src/commands/manuals/mv.txt +0 -10
  243. package/src/commands/manuals/nano.txt +0 -11
  244. package/src/commands/manuals/neofetch.txt +0 -10
  245. package/src/commands/manuals/node.txt +0 -13
  246. package/src/commands/manuals/npm.txt +0 -13
  247. package/src/commands/manuals/npx.txt +0 -13
  248. package/src/commands/manuals/passwd.txt +0 -11
  249. package/src/commands/manuals/ping.txt +0 -10
  250. package/src/commands/manuals/printf.txt +0 -11
  251. package/src/commands/manuals/ps.txt +0 -10
  252. package/src/commands/manuals/pwd.txt +0 -10
  253. package/src/commands/manuals/python3.txt +0 -13
  254. package/src/commands/manuals/readlink.txt +0 -10
  255. package/src/commands/manuals/return.txt +0 -10
  256. package/src/commands/manuals/rm.txt +0 -10
  257. package/src/commands/manuals/sed.txt +0 -11
  258. package/src/commands/manuals/set.txt +0 -11
  259. package/src/commands/manuals/shift.txt +0 -10
  260. package/src/commands/manuals/sleep.txt +0 -10
  261. package/src/commands/manuals/sort.txt +0 -12
  262. package/src/commands/manuals/source.txt +0 -11
  263. package/src/commands/manuals/ssh.txt +0 -11
  264. package/src/commands/manuals/stat.txt +0 -10
  265. package/src/commands/manuals/su.txt +0 -13
  266. package/src/commands/manuals/sudo.txt +0 -11
  267. package/src/commands/manuals/tail.txt +0 -10
  268. package/src/commands/manuals/tar.txt +0 -19
  269. package/src/commands/manuals/tee.txt +0 -10
  270. package/src/commands/manuals/test.txt +0 -11
  271. package/src/commands/manuals/touch.txt +0 -11
  272. package/src/commands/manuals/tr.txt +0 -10
  273. package/src/commands/manuals/trap.txt +0 -10
  274. package/src/commands/manuals/true.txt +0 -10
  275. package/src/commands/manuals/type.txt +0 -10
  276. package/src/commands/manuals/uname.txt +0 -12
  277. package/src/commands/manuals/uniq.txt +0 -12
  278. package/src/commands/manuals/unset.txt +0 -10
  279. package/src/commands/manuals/uptime.txt +0 -11
  280. package/src/commands/manuals/wc.txt +0 -12
  281. package/src/commands/manuals/wget.txt +0 -12
  282. package/src/commands/manuals/which.txt +0 -10
  283. package/src/commands/manuals/whoami.txt +0 -10
  284. package/src/commands/manuals/xargs.txt +0 -10
  285. package/src/commands/manuals-bundle.ts +0 -898
  286. package/src/commands/mkdir.ts +0 -31
  287. package/src/commands/mv.ts +0 -50
  288. package/src/commands/nano.ts +0 -38
  289. package/src/commands/neofetch.ts +0 -53
  290. package/src/commands/node.ts +0 -341
  291. package/src/commands/npm.ts +0 -132
  292. package/src/commands/passwd.ts +0 -50
  293. package/src/commands/ping.ts +0 -32
  294. package/src/commands/printf.ts +0 -129
  295. package/src/commands/ps.ts +0 -58
  296. package/src/commands/pwd.ts +0 -9
  297. package/src/commands/python.ts +0 -2229
  298. package/src/commands/read.ts +0 -46
  299. package/src/commands/registry.ts +0 -249
  300. package/src/commands/rm.ts +0 -42
  301. package/src/commands/runtime.ts +0 -421
  302. package/src/commands/sed.ts +0 -68
  303. package/src/commands/seq.ts +0 -43
  304. package/src/commands/set.ts +0 -29
  305. package/src/commands/sh.ts +0 -467
  306. package/src/commands/shift.ts +0 -63
  307. package/src/commands/sleep.ts +0 -20
  308. package/src/commands/sort.ts +0 -46
  309. package/src/commands/source.ts +0 -52
  310. package/src/commands/stat.ts +0 -61
  311. package/src/commands/su.ts +0 -72
  312. package/src/commands/sudo.ts +0 -76
  313. package/src/commands/tail.ts +0 -53
  314. package/src/commands/tar.ts +0 -102
  315. package/src/commands/tee.ts +0 -36
  316. package/src/commands/test.ts +0 -137
  317. package/src/commands/touch.ts +0 -28
  318. package/src/commands/tr.ts +0 -70
  319. package/src/commands/tree.ts +0 -20
  320. package/src/commands/true.ts +0 -27
  321. package/src/commands/type.ts +0 -48
  322. package/src/commands/uname.ts +0 -29
  323. package/src/commands/uniq.ts +0 -39
  324. package/src/commands/unset.ts +0 -17
  325. package/src/commands/uptime.ts +0 -54
  326. package/src/commands/wc.ts +0 -55
  327. package/src/commands/wget.ts +0 -148
  328. package/src/commands/which.ts +0 -37
  329. package/src/commands/who.ts +0 -25
  330. package/src/commands/whoami.ts +0 -14
  331. package/src/commands/xargs.ts +0 -31
  332. package/src/index.ts +0 -67
  333. package/src/modules/linuxRootfs.ts +0 -1961
  334. package/src/modules/neofetch.ts +0 -358
  335. package/src/modules/shellInteractive.ts +0 -57
  336. package/src/modules/shellRuntime.ts +0 -76
  337. package/src/self-standalone.ts +0 -542
  338. package/src/standalone-wo-sftp.ts +0 -38
  339. package/src/standalone.ts +0 -72
  340. package/src/types/commands.ts +0 -146
  341. package/src/types/pipeline.ts +0 -52
  342. package/src/types/streams.ts +0 -32
  343. package/src/types/tar-stream.d.ts +0 -38
  344. package/src/types/vfs.ts +0 -98
  345. package/src/utils/expand.ts +0 -491
  346. package/src/utils/perfLogger.ts +0 -72
  347. package/src/utils/tokenize.ts +0 -98
  348. package/src/utils/vfsDiff.ts +0 -275
  349. package/tests/command-helpers.test.ts +0 -116
  350. package/tests/commands-admin-net.test.ts +0 -441
  351. package/tests/commands-advanced.test.ts +0 -456
  352. package/tests/commands-core.test.ts +0 -562
  353. package/tests/commands-missing.test.ts +0 -570
  354. package/tests/commands-specific-units.test.ts +0 -327
  355. package/tests/commands-text-sys.test.ts +0 -445
  356. package/tests/expand.test.ts +0 -170
  357. package/tests/helpers.test.ts +0 -97
  358. package/tests/new-features.test.ts +0 -1036
  359. package/tests/parser-executor.test.ts +0 -37
  360. package/tests/sftp.test.ts +0 -323
  361. package/tests/ssh-exec.test.ts +0 -45
  362. package/tests/test-helper.ts +0 -79
  363. package/tests/users.test.ts +0 -86
  364. package/tsconfig.json +0 -49
  365. package/typedoc.json +0 -47
@@ -1,542 +0,0 @@
1
- import { readFile, unlink, writeFile } from "node:fs/promises";
2
- import * as path from "node:path";
3
- import { basename } from "node:path";
4
- import { stdin, stdout } from "node:process";
5
- import { createInterface, type Interface } from "node:readline";
6
-
7
- import { getCommandNames } from "./commands/registry";
8
- import { makeDefaultEnv, runCommand, userHome } from "./commands/runtime";
9
- import { spawnNanoEditorProcess } from "./modules/shellInteractive";
10
- import { resolvePath } from "./modules/shellRuntime";
11
- import { buildLoginBanner, type LoginBannerState } from "./SSHMimic/loginBanner";
12
- import { buildPrompt } from "./SSHMimic/prompt";
13
- import type { CommandResult, PasswordChallenge, SudoChallenge } from "./types/commands";
14
- import type VirtualFileSystem from "./VirtualFileSystem";
15
- import { VirtualShell } from "./VirtualShell";
16
-
17
- const hostname = process.env.SSH_MIMIC_HOSTNAME ?? "typescript-vm";
18
- const argv = process.argv.slice(2);
19
-
20
- // ── CLI args ──────────────────────────────────────────────────────────────────
21
- console.clear();
22
- function readUserArg(): string {
23
- for (let index = 0; index < argv.length; index += 1) {
24
- const current = argv[index];
25
- if (current === "--user") {
26
- const next = argv[index + 1];
27
- if (!next || next.startsWith("--")) {
28
- throw new Error("self-standalone: --user requires a value");
29
- }
30
- return next;
31
- }
32
- if (current?.startsWith("--user=")) {
33
- return current.slice("--user=".length) || "root";
34
- }
35
- }
36
- return "root";
37
- }
38
-
39
- const initialUser = readUserArg();
40
- const virtualShell = new VirtualShell(hostname, undefined, {
41
- mode: "fs",
42
- snapshotPath: ".vfs",
43
- });
44
-
45
- // ── VFS helpers ───────────────────────────────────────────────────────────────
46
-
47
- function readLastLogin(username: string): LoginBannerState | null {
48
- const lastlogPath = `/home/${username}/.lastlog`;
49
- if (!virtualShell.vfs.exists(lastlogPath)) return null;
50
- try {
51
- return JSON.parse(virtualShell.vfs.readFile(lastlogPath)) as LoginBannerState;
52
- } catch {
53
- return null;
54
- }
55
- }
56
-
57
- function writeLastLogin(username: string, from: string): void {
58
- virtualShell.vfs.writeFile(
59
- `/home/${username}/.lastlog`,
60
- JSON.stringify({ at: new Date().toISOString(), from }),
61
- );
62
- }
63
-
64
- async function flushVfs(): Promise<void> {
65
- await virtualShell.vfs.stopAutoFlush();
66
- }
67
-
68
- function loadHistory(authUser: string): string[] {
69
- const historyPath = `${userHome(authUser)}/.bash_history`;
70
- if (!virtualShell.vfs.exists(historyPath)) {
71
- virtualShell.vfs.writeFile(historyPath, "");
72
- return [];
73
- }
74
- return virtualShell.vfs
75
- .readFile(historyPath)
76
- .split("\n")
77
- .map((line) => line.trim())
78
- .filter((line) => line.length > 0);
79
- }
80
-
81
- function saveHistory(history: string[], authUser: string): void {
82
- const data = history.length > 0 ? `${history.join("\n")}\n` : "";
83
- virtualShell.vfs.writeFile(`${userHome(authUser)}/.bash_history`, data);
84
- }
85
-
86
- // ── Tab completion ────────────────────────────────────────────────────────────
87
-
88
- function listPathCompletions(vfs: VirtualFileSystem, cwd: string, prefix: string): string[] {
89
- const slashIndex = prefix.lastIndexOf("/");
90
- const dirPart = slashIndex >= 0 ? prefix.slice(0, slashIndex + 1) : "";
91
- const namePart = slashIndex >= 0 ? prefix.slice(slashIndex + 1) : prefix;
92
- const basePath = resolvePath(cwd, dirPart || ".");
93
- try {
94
- return vfs
95
- .list(basePath)
96
- .filter((e) => !e.startsWith(".") && e.startsWith(namePart))
97
- .map((e) => {
98
- const fullPath = path.posix.join(basePath, e);
99
- const st = vfs.stat(fullPath);
100
- return `${dirPart}${e}${st.type === "directory" ? "/" : ""}`;
101
- })
102
- .sort();
103
- } catch {
104
- return [];
105
- }
106
- }
107
-
108
- function makeCompleter(getState: () => { cwd: string }) {
109
- const commandNames = Array.from(new Set(getCommandNames())).sort();
110
- return (line: string, cb: (err: null, result: [string[], string]) => void): void => {
111
- const { cwd } = getState();
112
- // Extract the token under/before cursor (last whitespace-separated word)
113
- const token = line.split(/\s+/).at(-1) ?? "";
114
- const isFirstToken = line.trimStart() === token;
115
- const cmdHits = isFirstToken ? commandNames.filter((n) => n.startsWith(token)) : [];
116
- const pathHits = listPathCompletions(virtualShell.vfs, cwd, token);
117
- const hits = Array.from(new Set([...cmdHits, ...pathHits])).sort();
118
- cb(null, [hits, token]);
119
- };
120
- }
121
-
122
- // ── Hidden password input ─────────────────────────────────────────────────────
123
-
124
- function askHiddenQuestion(rl: Interface, promptText: string): Promise<string> {
125
- return new Promise((resolve) => {
126
- if (!stdin.isTTY || !stdout.isTTY) {
127
- rl.question(promptText, resolve);
128
- return;
129
- }
130
-
131
- const wasRawMode = Boolean(stdin.isRaw);
132
- let buffer = "";
133
-
134
- const cleanup = (): void => {
135
- stdin.off("data", onData);
136
- if (!wasRawMode) stdin.setRawMode(false);
137
- };
138
-
139
- const finish = (value: string): void => {
140
- cleanup();
141
- stdout.write("\n");
142
- resolve(value);
143
- };
144
-
145
- const onData = (chunk: Buffer): void => {
146
- const input = chunk.toString("utf8");
147
- for (let i = 0; i < input.length; i += 1) {
148
- const ch = input[i]!;
149
- if (ch === "\r" || ch === "\n") { finish(buffer); return; }
150
- if (ch === "\u007f" || ch === "\b") { buffer = buffer.slice(0, -1); continue; }
151
- if (ch >= " ") buffer += ch;
152
- }
153
- };
154
-
155
- // Pause readline so it doesn't eat our raw keystrokes
156
- rl.pause();
157
- stdout.write(promptText);
158
- if (!wasRawMode) stdin.setRawMode(true);
159
- stdin.resume();
160
- stdin.on("data", onData);
161
- });
162
- }
163
-
164
- // ── Session state helper ──────────────────────────────────────────────────────
165
-
166
- function applySessionState(
167
- authUserState: string,
168
- cwdState: string,
169
- result: CommandResult,
170
- shellEnvState: ReturnType<typeof makeDefaultEnv>,
171
- ): { authUser: string; cwd: string } {
172
- let authUser = authUserState;
173
- let cwd = cwdState;
174
- if (result.switchUser) {
175
- authUser = result.switchUser;
176
- cwd = result.nextCwd ?? userHome(authUser);
177
- shellEnvState.vars.USER = authUser;
178
- shellEnvState.vars.LOGNAME = authUser;
179
- shellEnvState.vars.HOME = userHome(authUser);
180
- shellEnvState.vars.PWD = cwd;
181
- } else if (result.nextCwd) {
182
- cwd = result.nextCwd;
183
- shellEnvState.vars.PWD = cwd;
184
- }
185
- return { authUser, cwd };
186
- }
187
-
188
- // ── Demo command ──────────────────────────────────────────────────────────────
189
-
190
- virtualShell.addCommand("demo", [], () => ({
191
- stdout: "This is a demo command. It does nothing useful.",
192
- exitCode: 0,
193
- }));
194
-
195
- // ── Main shell ────────────────────────────────────────────────────────────────
196
-
197
- async function runReadlineShell(): Promise<void> {
198
- await virtualShell.ensureInitialized();
199
-
200
- const selectedUser = initialUser.trim() || "root";
201
- if (virtualShell.users.getPasswordHash(selectedUser) === null) {
202
- process.stderr.write(`self-standalone: user '${selectedUser}' does not exist\n`);
203
- process.exit(1);
204
- }
205
-
206
- // Ensure home dir and README.txt exist (mirrors SSHMimic/VirtualUserManager behaviour)
207
- const homePath = selectedUser === "root" ? "/root" : userHome(selectedUser);
208
- if (!virtualShell.vfs.exists(homePath)) {
209
- virtualShell.vfs.mkdir(homePath, selectedUser === "root" ? 0o700 : 0o755);
210
- }
211
- const readmePath = `${homePath}/README.txt`;
212
- if (!virtualShell.vfs.exists(readmePath)) {
213
- virtualShell.vfs.writeFile(
214
- readmePath,
215
- `Welcome to ${hostname}\n`,
216
- );
217
- await virtualShell.vfs.stopAutoFlush();
218
- }
219
-
220
- const shellEnv = makeDefaultEnv(selectedUser, hostname);
221
- let authUser = selectedUser;
222
- let cwd = userHome(authUser);
223
- shellEnv.vars.PWD = cwd;
224
- const remoteAddress = "localhost";
225
- const terminalSize = { cols: stdout.columns ?? 80, rows: stdout.rows ?? 24 };
226
-
227
- let history = loadHistory(authUser);
228
-
229
- // completer reads cwd via closure — always current
230
- const rl = createInterface({
231
- input: stdin,
232
- output: stdout,
233
- terminal: true,
234
- completer: makeCompleter(() => ({ cwd })),
235
- });
236
-
237
- // Sync readline's internal history with our VFS history
238
- const rlWithHistory = rl as Interface & { history: string[] };
239
- rlWithHistory.history = [...history].reverse();
240
-
241
- // ── nano editor ────────────────────────────────────────────────────────────
242
-
243
- async function startNanoEditor(
244
- targetPath: string,
245
- initialContent: string,
246
- tempPath: string,
247
- ): Promise<void> {
248
- if (virtualShell.vfs.exists(targetPath)) {
249
- await writeFile(tempPath, initialContent, "utf8");
250
- }
251
-
252
- rl.pause();
253
-
254
- const editor = spawnNanoEditorProcess(
255
- tempPath,
256
- terminalSize,
257
- {
258
- write: stdout.write.bind(stdout),
259
- exit: () => undefined,
260
- end: () => undefined,
261
- } as unknown as Parameters<typeof spawnNanoEditorProcess>[2],
262
- );
263
-
264
- const wasRawMode = Boolean(stdin.isRaw);
265
- const forwardInput = (chunk: Buffer): void => { editor.stdin.write(chunk); };
266
-
267
- stdin.resume();
268
- if (!wasRawMode) stdin.setRawMode(true);
269
- stdin.on("data", forwardInput);
270
-
271
- await new Promise<void>((resolve) => {
272
- const cleanup = (): void => {
273
- stdin.off("data", forwardInput);
274
- if (!wasRawMode) stdin.setRawMode(false);
275
- rl.resume();
276
- };
277
-
278
- editor.on("error", (error: Error) => {
279
- cleanup();
280
- stdout.write(`nano: ${error.message}\r\n`);
281
- resolve();
282
- });
283
-
284
- editor.on("close", async () => {
285
- cleanup();
286
- rl.write("", { ctrl: true, name: "u" });
287
- try {
288
- const updatedContent = await readFile(tempPath, "utf8");
289
- virtualShell.writeFileAsUser(authUser, targetPath, updatedContent);
290
- await flushVfs();
291
- } catch {
292
- // save skipped or temp file missing
293
- }
294
- await unlink(tempPath).catch(() => undefined);
295
- stdout.write("\r\n");
296
- resolve();
297
- });
298
- });
299
- }
300
-
301
- // ── challenge handlers ─────────────────────────────────────────────────────
302
-
303
- async function handleSudoChallenge(challenge: SudoChallenge): Promise<void> {
304
- if (challenge.onPassword) {
305
- let promptText = challenge.prompt;
306
- while (true) {
307
- const typed = await askHiddenQuestion(rl, promptText);
308
- const step = await challenge.onPassword(typed, virtualShell);
309
- if (step.result === null) {
310
- promptText = step.nextPrompt ?? promptText;
311
- continue;
312
- }
313
- await handleCommandResult(step.result);
314
- return;
315
- }
316
- }
317
-
318
- const password = await askHiddenQuestion(rl, challenge.prompt);
319
- if (!virtualShell.users.verifyPassword(challenge.username, password)) {
320
- process.stderr.write("Sorry, try again.\n");
321
- return;
322
- }
323
-
324
- if (!challenge.commandLine) {
325
- authUser = challenge.targetUser;
326
- cwd = userHome(authUser);
327
- shellEnv.vars.USER = authUser;
328
- shellEnv.vars.LOGNAME = authUser;
329
- shellEnv.vars.HOME = userHome(authUser);
330
- shellEnv.vars.PWD = cwd;
331
- return;
332
- }
333
-
334
- const runCwd = challenge.loginShell ? userHome(challenge.targetUser) : cwd;
335
- const nestedResult = await runCommand(
336
- challenge.commandLine,
337
- challenge.targetUser,
338
- hostname,
339
- "shell",
340
- runCwd,
341
- virtualShell,
342
- undefined,
343
- shellEnv,
344
- );
345
- await handleCommandResult(nestedResult);
346
- }
347
-
348
- async function handlePasswordChallenge(challenge: PasswordChallenge): Promise<void> {
349
- const first = await askHiddenQuestion(rl, challenge.prompt);
350
- if (challenge.confirmPrompt) {
351
- const second = await askHiddenQuestion(rl, challenge.confirmPrompt);
352
- if (second !== first) {
353
- process.stderr.write("passwords do not match\n");
354
- return;
355
- }
356
- }
357
-
358
- switch (challenge.action) {
359
- case "passwd":
360
- await virtualShell.users.setPassword(challenge.targetUsername, first);
361
- stdout.write("passwd: password updated successfully\n");
362
- break;
363
- case "adduser":
364
- if (!challenge.newUsername) {
365
- process.stderr.write("adduser: missing username\n");
366
- return;
367
- }
368
- await virtualShell.users.addUser(challenge.newUsername, first);
369
- stdout.write(`adduser: user '${challenge.newUsername}' created\n`);
370
- break;
371
- case "deluser":
372
- await virtualShell.users.deleteUser(challenge.targetUsername);
373
- stdout.write(`Removing user '${challenge.targetUsername}' ...\ndeluser: done.\n`);
374
- break;
375
- case "su":
376
- authUser = challenge.targetUsername;
377
- cwd = userHome(authUser);
378
- shellEnv.vars.USER = authUser;
379
- shellEnv.vars.LOGNAME = authUser;
380
- shellEnv.vars.HOME = userHome(authUser);
381
- shellEnv.vars.PWD = cwd;
382
- break;
383
- }
384
- }
385
-
386
- // handleCommandResult must be declared before the "line" handler
387
- async function handleCommandResult(result: CommandResult): Promise<void> {
388
- if (result.openEditor) {
389
- await startNanoEditor(
390
- result.openEditor.targetPath,
391
- result.openEditor.initialContent,
392
- result.openEditor.tempPath,
393
- );
394
- return;
395
- }
396
-
397
- if (result.sudoChallenge) {
398
- await handleSudoChallenge(result.sudoChallenge);
399
- return;
400
- }
401
-
402
- if (result.passwordChallenge) {
403
- await handlePasswordChallenge(result.passwordChallenge);
404
- return;
405
- }
406
-
407
- if (result.clearScreen) {
408
- stdout.write("\u001b[2J\u001b[H");
409
- console.clear();
410
- }
411
-
412
- if (result.stdout) {
413
- stdout.write(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}\n`);
414
- }
415
-
416
- if (result.stderr) {
417
- process.stderr.write(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}\n`);
418
- }
419
-
420
- const updated = applySessionState(authUser, cwd, result, shellEnv);
421
- authUser = updated.authUser;
422
- cwd = updated.cwd;
423
-
424
- if (result.closeSession) {
425
- await flushVfs();
426
- rl.close();
427
- process.exit(result.exitCode ?? 0);
428
- }
429
- }
430
-
431
- // ── Prompt helper ──────────────────────────────────────────────────────────
432
-
433
- const renderPrompt = (): string => {
434
- const cwdLabel = cwd === userHome(authUser) ? "~" : basename(cwd) || "/";
435
- return buildPrompt(authUser, hostname, cwdLabel);
436
- };
437
-
438
- const prompt = (): void => {
439
- rl.setPrompt(renderPrompt());
440
- rl.prompt();
441
- };
442
-
443
- // ── Auth (password gate) ───────────────────────────────────────────────────
444
-
445
- if (process.env.USER !== "root" && virtualShell.users.hasPassword(authUser)) {
446
- const password = await askHiddenQuestion(rl, `Password for ${authUser}: `);
447
- if (!virtualShell.users.verifyPassword(authUser, password)) {
448
- process.stderr.write("self-standalone: authentication failed\n");
449
- process.exit(1);
450
- }
451
- }
452
-
453
- // ── Login banner ───────────────────────────────────────────────────────────
454
-
455
- stdout.write(buildLoginBanner(hostname, virtualShell.properties, readLastLogin(authUser)));
456
- writeLastLogin(authUser, remoteAddress);
457
- await flushVfs();
458
-
459
- // ── Event-driven line handler (enables completer) ──────────────────────────
460
- //
461
- // Key insight: readline's completer only fires when readline itself owns
462
- // stdin (i.e. rl is not paused). We use the event-driven "line" pattern
463
- // instead of a while(true)+rl.once("line") loop so readline stays active
464
- // between commands. We pause only while awaiting async work, then resume
465
- // immediately before re-prompting so the next Tab press is caught.
466
-
467
- let busy = false;
468
-
469
- rl.on("line", async (inputLine: string) => {
470
- if (busy) return; // shouldn't happen but guard re-entrancy
471
- busy = true;
472
- rl.pause();
473
-
474
- const trimmed = inputLine.trim();
475
- if (trimmed.length > 0) {
476
- history.push(inputLine);
477
- if (history.length > 500) history = history.slice(history.length - 500);
478
- saveHistory(history, authUser);
479
- rlWithHistory.history = [...history].reverse();
480
- }
481
-
482
- const result = await runCommand(
483
- inputLine,
484
- authUser,
485
- hostname,
486
- "shell",
487
- cwd,
488
- virtualShell,
489
- undefined,
490
- shellEnv,
491
- );
492
- await handleCommandResult(result);
493
- await flushVfs();
494
-
495
- busy = false;
496
- // Resume before prompt so readline can handle Tab on the next input
497
- rl.resume();
498
- prompt();
499
- });
500
-
501
- rl.on("SIGINT", () => {
502
- stdout.write("^C\n");
503
- rl.write("", { ctrl: true, name: "u" });
504
- prompt();
505
- });
506
-
507
- rl.on("close", () => {
508
- void flushVfs().then(() => {
509
- console.log("");
510
- process.exit(0);
511
- });
512
- });
513
-
514
- // Initial prompt — readline is already active, completer live from first keystroke
515
- prompt();
516
- }
517
-
518
- runReadlineShell().catch((error: unknown) => {
519
- console.error("Failed to start readline SSH emulation:", error);
520
- process.exit(1);
521
- });
522
-
523
-
524
- // ── Graceful shutdown (process-level) ────────────────────────────────────────
525
- let _shuttingDown = false;
526
- async function _gracefulShutdown(signal: string): Promise<void> {
527
- if (_shuttingDown) return;
528
- _shuttingDown = true;
529
- process.stdout.write(`\n[${signal}] Saving VFS...\n`);
530
- try { await virtualShell.vfs.stopAutoFlush(); } catch {}
531
- process.exit(0);
532
- }
533
- process.on("SIGTERM", () => { void _gracefulShutdown("SIGTERM"); });
534
- process.on("beforeExit", () => { void virtualShell.vfs.stopAutoFlush(); });
535
-
536
- process.on("uncaughtException", (error) => {
537
- console.error("Uncaught exception:", error);
538
- });
539
-
540
- process.on("unhandledRejection", (error, promise) => {
541
- console.error("Unhandled rejection at:", promise, "error:", error);
542
- });
@@ -1,38 +0,0 @@
1
- import { SshMimic } from "./SSHMimic/index";
2
- import { VirtualShell } from "./VirtualShell";
3
-
4
- const hostname = process.env.SSH_MIMIC_HOSTNAME ?? "typescript-vm";
5
- const virtualShell = new VirtualShell(hostname, undefined, {
6
- mode: "fs",
7
- snapshotPath: ".vfs",
8
- });
9
-
10
- virtualShell.addCommand("demo", [], () => {
11
- return {
12
- stdout: "This is a demo command. It does nothing useful.",
13
- exitCode: 0,
14
- };
15
- });
16
-
17
- new SshMimic({
18
- port: 2222,
19
- hostname,
20
- shell: virtualShell,
21
- })
22
- .start()
23
- .catch((error: unknown) => {
24
- console.error("Failed to start SSH Mimic:", error);
25
- process.exit(1);
26
- });
27
-
28
- process.on("uncaughtException", (error) => {
29
- console.log("Oh my god, something terrible happened: ", error);
30
- });
31
-
32
- process.on("unhandledRejection", (error, promise) => {
33
- console.log(
34
- " Oh Lord! We forgot to handle a promise rejection here: ",
35
- promise,
36
- );
37
- console.log(" The error was: ", error);
38
- });
package/src/standalone.ts DELETED
@@ -1,72 +0,0 @@
1
- import { VirtualSftpServer, VirtualShell, VirtualSshServer } from ".";
2
-
3
- const hostname = process.env.SSH_MIMIC_HOSTNAME ?? "typescript-vm";
4
- const virtualShell = new VirtualShell(hostname, undefined, {
5
- mode: "fs",
6
- snapshotPath: ".vfs",
7
- });
8
-
9
- virtualShell.addCommand("demo", [], () => {
10
- return {
11
- stdout: "This is a demo command. It does nothing useful.",
12
- exitCode: 0,
13
- };
14
- });
15
-
16
- new VirtualSshServer({
17
- port: 2222,
18
- hostname,
19
- shell: virtualShell,
20
- })
21
- .start()
22
- .catch((error: unknown) => {
23
- console.error("Failed to start SSH Mimic:", error);
24
- process.exit(1);
25
- });
26
-
27
- new VirtualSftpServer({ port: 2223, hostname, shell: virtualShell })
28
- .start()
29
- .catch((error: unknown) => {
30
- console.error("Failed to start SFTP Mimic:", error);
31
- process.exit(1);
32
- });
33
-
34
-
35
- // ── Graceful shutdown ─────────────────────────────────────────────────────────
36
- // On SIGINT / SIGTERM: flush the WAL journal to a full checkpoint before exit.
37
- // A kill -9 or OOM crash is unrecoverable here, but the WAL journal on disk
38
- // guarantees all writes since the last checkpoint are replayed on next start.
39
- let isShuttingDown = false;
40
- async function gracefulShutdown(signal: string): Promise<void> {
41
- if (isShuttingDown) return;
42
- isShuttingDown = true;
43
- console.log(`\n[${signal}] Flushing VFS checkpoint before exit...`);
44
- try {
45
- await virtualShell.vfs.stopAutoFlush();
46
- console.log("[shutdown] Checkpoint written. Goodbye.");
47
- } catch (err) {
48
- console.error("[shutdown] Flush failed:", err);
49
- }
50
- process.exit(0);
51
- }
52
-
53
- process.on("SIGINT", () => { void gracefulShutdown("SIGINT"); });
54
- process.on("SIGTERM", () => { void gracefulShutdown("SIGTERM"); });
55
- process.on("beforeExit", () => { void virtualShell.vfs.stopAutoFlush(); });
56
-
57
- process.on("uncaughtException", (error) => {
58
- console.debug("Oh my god, something terrible happened: ", error);
59
- });
60
-
61
- process.on("unhandledRejection", (error, promise) => {
62
- console.debug(
63
- " Oh Lord! We forgot to handle a promise rejection here: ",
64
- promise,
65
- );
66
- console.debug(" The error was: ", error);
67
- });
68
-
69
- setInterval(() => {
70
- const rss = process.memoryUsage().rss; // Just keep the event loop alive and prevent exit
71
- console.debug(`Current memory usage: ${Math.round(rss / 1024 / 1024)} MB`);
72
- }, 1000);