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.
Files changed (364) 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/commands/basename.d.ts +13 -0
  5. package/dist/commands/basename.js +45 -0
  6. package/dist/commands/file.d.ts +8 -0
  7. package/dist/commands/file.js +57 -0
  8. package/dist/commands/fun.d.ts +32 -0
  9. package/dist/commands/fun.js +172 -0
  10. package/dist/commands/ifconfig.d.ts +7 -0
  11. package/dist/commands/ifconfig.js +52 -0
  12. package/dist/commands/last.d.ts +13 -0
  13. package/dist/commands/last.js +68 -0
  14. package/dist/commands/manuals-bundle.js +598 -6
  15. package/dist/commands/registry.js +24 -2
  16. package/dist/commands/runtime.js +159 -106
  17. package/dist/commands/sh.js +5 -0
  18. package/dist/commands/tput.d.ts +13 -0
  19. package/dist/commands/tput.js +76 -0
  20. package/dist/commands/w.d.ts +7 -0
  21. package/dist/commands/w.js +38 -0
  22. package/dist/utils/expand.d.ts +12 -0
  23. package/dist/utils/expand.js +84 -0
  24. package/package.json +9 -3
  25. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  26. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  27. package/.github/dependabot.yml +0 -27
  28. package/.github/pull_request_template.md +0 -21
  29. package/.github/workflows/create-pull-request.yml +0 -85
  30. package/.github/workflows/publish.yml +0 -25
  31. package/.github/workflows/test-battery.yml +0 -102
  32. package/.vscode/settings.json +0 -20
  33. package/CODE_OF_CONDUCT.md +0 -39
  34. package/CONTRIBUTING.md +0 -59
  35. package/HONEYPOT.md +0 -358
  36. package/SECURITY.md +0 -33
  37. package/benchmark-results.txt +0 -40
  38. package/benchmark-virtualshell.ts +0 -88
  39. package/biome.json +0 -37
  40. package/build.js +0 -22
  41. package/builds/fortune-nyx-v1.5.1-directbash-k6.1.0.mjs +0 -1768
  42. package/builds/fortune-nyx-v1.5.1-ssh-nosftp.js +0 -1768
  43. package/builds/fortune-nyx-v1.5.1-ssh.cjs +0 -1769
  44. package/builds/fortune-nyx-v1.5.1-web.min.js +0 -17022
  45. package/bun.lock +0 -244
  46. package/docs/.nojekyll +0 -1
  47. package/docs/app.js +0 -1755
  48. package/docs/assets/hierarchy.js +0 -1
  49. package/docs/assets/highlight.css +0 -162
  50. package/docs/assets/icons.js +0 -18
  51. package/docs/assets/icons.svg +0 -1
  52. package/docs/assets/main.js +0 -60
  53. package/docs/assets/navigation.js +0 -1
  54. package/docs/assets/search.js +0 -1
  55. package/docs/assets/style.css +0 -1633
  56. package/docs/classes/HoneyPot.html +0 -31
  57. package/docs/classes/IdleManager.html +0 -162
  58. package/docs/classes/SshClient.html +0 -66
  59. package/docs/classes/VirtualFileSystem.html +0 -279
  60. package/docs/classes/VirtualPackageManager.html +0 -63
  61. package/docs/classes/VirtualSftpServer.html +0 -169
  62. package/docs/classes/VirtualShell.html +0 -285
  63. package/docs/classes/VirtualSshServer.html +0 -182
  64. package/docs/classes/VirtualUserManager.html +0 -276
  65. package/docs/demo.html +0 -82
  66. package/docs/functions/assertDiff.html +0 -6
  67. package/docs/functions/diffSnapshots.html +0 -7
  68. package/docs/functions/formatDiff.html +0 -6
  69. package/docs/functions/getArg.html +0 -13
  70. package/docs/functions/getFlag.html +0 -15
  71. package/docs/functions/ifFlag.html +0 -11
  72. package/docs/hierarchy.html +0 -1
  73. package/docs/index.html +0 -1869
  74. package/docs/interfaces/AuditLogEntry.html +0 -6
  75. package/docs/interfaces/CommandContext.html +0 -22
  76. package/docs/interfaces/CommandResult.html +0 -26
  77. package/docs/interfaces/ExecStream.html +0 -11
  78. package/docs/interfaces/HoneyPotStats.html +0 -16
  79. package/docs/interfaces/IdleManagerOptions.html +0 -7
  80. package/docs/interfaces/InstalledPackage.html +0 -20
  81. package/docs/interfaces/NanoEditorSession.html +0 -8
  82. package/docs/interfaces/PackageDefinition.html +0 -30
  83. package/docs/interfaces/PackageFile.html +0 -8
  84. package/docs/interfaces/PasswordChallenge.html +0 -16
  85. package/docs/interfaces/RemoveOptions.html +0 -4
  86. package/docs/interfaces/ShellEnv.html +0 -6
  87. package/docs/interfaces/ShellModule.html +0 -14
  88. package/docs/interfaces/ShellProperties.html +0 -14
  89. package/docs/interfaces/ShellStream.html +0 -11
  90. package/docs/interfaces/SudoChallenge.html +0 -24
  91. package/docs/interfaces/VfsBaseNode.html +0 -12
  92. package/docs/interfaces/VfsDiff.html +0 -10
  93. package/docs/interfaces/VfsDiffEntry.html +0 -6
  94. package/docs/interfaces/VfsDiffModified.html +0 -10
  95. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  96. package/docs/interfaces/VfsFileNode.html +0 -17
  97. package/docs/interfaces/VfsOptions.html +0 -26
  98. package/docs/interfaces/VfsSnapshot.html +0 -3
  99. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  100. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  101. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  102. package/docs/interfaces/VirtualActiveSession.html +0 -12
  103. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  104. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  105. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  106. package/docs/interfaces/WriteFileOptions.html +0 -6
  107. package/docs/media/LICENSE +0 -21
  108. package/docs/modules.html +0 -1
  109. package/docs/types/ArgParseOptions.html +0 -4
  110. package/docs/types/CommandMode.html +0 -2
  111. package/docs/types/CommandOutcome.html +0 -2
  112. package/docs/types/IdleState.html +0 -1
  113. package/docs/types/VfsNodeStats.html +0 -2
  114. package/docs/types/VfsNodeType.html +0 -2
  115. package/docs/types/VfsPersistenceMode.html +0 -5
  116. package/docs/types/VfsSnapshotNode.html +0 -2
  117. package/examples/README.md +0 -288
  118. package/examples/app.js +0 -1755
  119. package/examples/app.ts +0 -299
  120. package/examples/build.js +0 -27
  121. package/examples/demo.html +0 -33
  122. package/examples/honeypot-audit.ts +0 -180
  123. package/examples/honeypot-export.ts +0 -253
  124. package/examples/honeypot-quickstart.ts +0 -110
  125. package/examples/index.html +0 -82
  126. package/examples/server.js +0 -55
  127. package/polyfills/buffer.js +0 -117
  128. package/polyfills/node_child_process/index.js +0 -2
  129. package/polyfills/node_crypto/index.js +0 -167
  130. package/polyfills/node_events/index.js +0 -9
  131. package/polyfills/node_fs/index.js +0 -202
  132. package/polyfills/node_fs/promises.js +0 -4
  133. package/polyfills/node_os/index.js +0 -9
  134. package/polyfills/node_path/index.js +0 -28
  135. package/polyfills/node_vm/index.js +0 -7
  136. package/polyfills/node_zlib/index.js +0 -3
  137. package/polyfills/process.js +0 -14
  138. package/polyfills/ssh2/index.js +0 -75
  139. package/scripts/build-all.mjs +0 -226
  140. package/scripts/build-names.mjs +0 -43
  141. package/scripts/generate-manuals-bundle.mjs +0 -49
  142. package/scripts/postinstall.js +0 -42
  143. package/scripts/publish-package.sh +0 -70
  144. package/src/Honeypot/index.ts +0 -457
  145. package/src/SSHClient/index.ts +0 -270
  146. package/src/SSHMimic/exec.ts +0 -49
  147. package/src/SSHMimic/executor.ts +0 -251
  148. package/src/SSHMimic/hostKey.ts +0 -21
  149. package/src/SSHMimic/index.ts +0 -337
  150. package/src/SSHMimic/loginBanner.ts +0 -36
  151. package/src/SSHMimic/loginFormat.ts +0 -10
  152. package/src/SSHMimic/prompt.ts +0 -14
  153. package/src/SSHMimic/sftp.ts +0 -883
  154. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  155. package/src/VirtualFileSystem/index.ts +0 -1193
  156. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  157. package/src/VirtualFileSystem/journal.ts +0 -171
  158. package/src/VirtualFileSystem/path.ts +0 -74
  159. package/src/VirtualPackageManager/index.ts +0 -1006
  160. package/src/VirtualShell/idleManager.ts +0 -137
  161. package/src/VirtualShell/index.ts +0 -475
  162. package/src/VirtualShell/shell.ts +0 -700
  163. package/src/VirtualShell/shellParser.ts +0 -285
  164. package/src/VirtualUserManager/index.ts +0 -758
  165. package/src/bun.d.ts +0 -1
  166. package/src/commands/adduser.ts +0 -103
  167. package/src/commands/alias.ts +0 -69
  168. package/src/commands/apt.ts +0 -233
  169. package/src/commands/awk.ts +0 -168
  170. package/src/commands/base64.ts +0 -29
  171. package/src/commands/cat.ts +0 -52
  172. package/src/commands/cd.ts +0 -25
  173. package/src/commands/chmod.ts +0 -85
  174. package/src/commands/clear.ts +0 -15
  175. package/src/commands/command-helpers.ts +0 -286
  176. package/src/commands/cp.ts +0 -83
  177. package/src/commands/curl.ts +0 -147
  178. package/src/commands/cut.ts +0 -36
  179. package/src/commands/date.ts +0 -30
  180. package/src/commands/declare.ts +0 -49
  181. package/src/commands/deluser.ts +0 -98
  182. package/src/commands/df.ts +0 -23
  183. package/src/commands/diff.ts +0 -43
  184. package/src/commands/dpkg.ts +0 -180
  185. package/src/commands/du.ts +0 -56
  186. package/src/commands/echo.ts +0 -58
  187. package/src/commands/env.ts +0 -23
  188. package/src/commands/exit.ts +0 -18
  189. package/src/commands/export.ts +0 -34
  190. package/src/commands/find.ts +0 -68
  191. package/src/commands/free.ts +0 -47
  192. package/src/commands/grep.ts +0 -116
  193. package/src/commands/groups.ts +0 -19
  194. package/src/commands/gzip.ts +0 -88
  195. package/src/commands/head.ts +0 -52
  196. package/src/commands/help.ts +0 -152
  197. package/src/commands/helpers.ts +0 -234
  198. package/src/commands/history.ts +0 -34
  199. package/src/commands/hostname.ts +0 -14
  200. package/src/commands/htop.ts +0 -20
  201. package/src/commands/id.ts +0 -19
  202. package/src/commands/index.ts +0 -9
  203. package/src/commands/kill.ts +0 -19
  204. package/src/commands/ln.ts +0 -71
  205. package/src/commands/ls.ts +0 -243
  206. package/src/commands/lsb-release.ts +0 -63
  207. package/src/commands/man.ts +0 -31
  208. package/src/commands/manuals/adduser.txt +0 -11
  209. package/src/commands/manuals/apt-cache.txt +0 -12
  210. package/src/commands/manuals/apt.txt +0 -20
  211. package/src/commands/manuals/awk.txt +0 -13
  212. package/src/commands/manuals/cat.txt +0 -14
  213. package/src/commands/manuals/cd.txt +0 -16
  214. package/src/commands/manuals/chmod.txt +0 -16
  215. package/src/commands/manuals/clear.txt +0 -10
  216. package/src/commands/manuals/cp.txt +0 -10
  217. package/src/commands/manuals/curl.txt +0 -20
  218. package/src/commands/manuals/date.txt +0 -14
  219. package/src/commands/manuals/declare.txt +0 -12
  220. package/src/commands/manuals/deluser.txt +0 -10
  221. package/src/commands/manuals/df.txt +0 -10
  222. package/src/commands/manuals/dpkg-query.txt +0 -11
  223. package/src/commands/manuals/dpkg.txt +0 -14
  224. package/src/commands/manuals/du.txt +0 -11
  225. package/src/commands/manuals/echo.txt +0 -11
  226. package/src/commands/manuals/false.txt +0 -10
  227. package/src/commands/manuals/find.txt +0 -11
  228. package/src/commands/manuals/free.txt +0 -12
  229. package/src/commands/manuals/grep.txt +0 -13
  230. package/src/commands/manuals/groups.txt +0 -10
  231. package/src/commands/manuals/gzip.txt +0 -11
  232. package/src/commands/manuals/head.txt +0 -10
  233. package/src/commands/manuals/help.txt +0 -11
  234. package/src/commands/manuals/history.txt +0 -11
  235. package/src/commands/manuals/hostname.txt +0 -10
  236. package/src/commands/manuals/id.txt +0 -10
  237. package/src/commands/manuals/kill.txt +0 -13
  238. package/src/commands/manuals/ls.txt +0 -20
  239. package/src/commands/manuals/lsb_release.txt +0 -14
  240. package/src/commands/manuals/mkdir.txt +0 -10
  241. package/src/commands/manuals/mv.txt +0 -10
  242. package/src/commands/manuals/nano.txt +0 -11
  243. package/src/commands/manuals/neofetch.txt +0 -10
  244. package/src/commands/manuals/node.txt +0 -13
  245. package/src/commands/manuals/npm.txt +0 -13
  246. package/src/commands/manuals/npx.txt +0 -13
  247. package/src/commands/manuals/passwd.txt +0 -11
  248. package/src/commands/manuals/ping.txt +0 -10
  249. package/src/commands/manuals/printf.txt +0 -11
  250. package/src/commands/manuals/ps.txt +0 -10
  251. package/src/commands/manuals/pwd.txt +0 -10
  252. package/src/commands/manuals/python3.txt +0 -13
  253. package/src/commands/manuals/readlink.txt +0 -10
  254. package/src/commands/manuals/return.txt +0 -10
  255. package/src/commands/manuals/rm.txt +0 -10
  256. package/src/commands/manuals/sed.txt +0 -11
  257. package/src/commands/manuals/set.txt +0 -11
  258. package/src/commands/manuals/shift.txt +0 -10
  259. package/src/commands/manuals/sleep.txt +0 -10
  260. package/src/commands/manuals/sort.txt +0 -12
  261. package/src/commands/manuals/source.txt +0 -11
  262. package/src/commands/manuals/ssh.txt +0 -11
  263. package/src/commands/manuals/stat.txt +0 -10
  264. package/src/commands/manuals/su.txt +0 -13
  265. package/src/commands/manuals/sudo.txt +0 -11
  266. package/src/commands/manuals/tail.txt +0 -10
  267. package/src/commands/manuals/tar.txt +0 -19
  268. package/src/commands/manuals/tee.txt +0 -10
  269. package/src/commands/manuals/test.txt +0 -11
  270. package/src/commands/manuals/touch.txt +0 -11
  271. package/src/commands/manuals/tr.txt +0 -10
  272. package/src/commands/manuals/trap.txt +0 -10
  273. package/src/commands/manuals/true.txt +0 -10
  274. package/src/commands/manuals/type.txt +0 -10
  275. package/src/commands/manuals/uname.txt +0 -12
  276. package/src/commands/manuals/uniq.txt +0 -12
  277. package/src/commands/manuals/unset.txt +0 -10
  278. package/src/commands/manuals/uptime.txt +0 -11
  279. package/src/commands/manuals/wc.txt +0 -12
  280. package/src/commands/manuals/wget.txt +0 -12
  281. package/src/commands/manuals/which.txt +0 -10
  282. package/src/commands/manuals/whoami.txt +0 -10
  283. package/src/commands/manuals/xargs.txt +0 -10
  284. package/src/commands/manuals-bundle.ts +0 -898
  285. package/src/commands/mkdir.ts +0 -31
  286. package/src/commands/mv.ts +0 -50
  287. package/src/commands/nano.ts +0 -38
  288. package/src/commands/neofetch.ts +0 -53
  289. package/src/commands/node.ts +0 -341
  290. package/src/commands/npm.ts +0 -132
  291. package/src/commands/passwd.ts +0 -50
  292. package/src/commands/ping.ts +0 -32
  293. package/src/commands/printf.ts +0 -129
  294. package/src/commands/ps.ts +0 -58
  295. package/src/commands/pwd.ts +0 -9
  296. package/src/commands/python.ts +0 -2229
  297. package/src/commands/read.ts +0 -46
  298. package/src/commands/registry.ts +0 -249
  299. package/src/commands/rm.ts +0 -42
  300. package/src/commands/runtime.ts +0 -378
  301. package/src/commands/sed.ts +0 -68
  302. package/src/commands/seq.ts +0 -43
  303. package/src/commands/set.ts +0 -29
  304. package/src/commands/sh.ts +0 -467
  305. package/src/commands/shift.ts +0 -63
  306. package/src/commands/sleep.ts +0 -20
  307. package/src/commands/sort.ts +0 -46
  308. package/src/commands/source.ts +0 -52
  309. package/src/commands/stat.ts +0 -61
  310. package/src/commands/su.ts +0 -72
  311. package/src/commands/sudo.ts +0 -76
  312. package/src/commands/tail.ts +0 -53
  313. package/src/commands/tar.ts +0 -102
  314. package/src/commands/tee.ts +0 -36
  315. package/src/commands/test.ts +0 -137
  316. package/src/commands/touch.ts +0 -28
  317. package/src/commands/tr.ts +0 -70
  318. package/src/commands/tree.ts +0 -20
  319. package/src/commands/true.ts +0 -27
  320. package/src/commands/type.ts +0 -48
  321. package/src/commands/uname.ts +0 -29
  322. package/src/commands/uniq.ts +0 -39
  323. package/src/commands/unset.ts +0 -17
  324. package/src/commands/uptime.ts +0 -54
  325. package/src/commands/wc.ts +0 -55
  326. package/src/commands/wget.ts +0 -148
  327. package/src/commands/which.ts +0 -37
  328. package/src/commands/who.ts +0 -25
  329. package/src/commands/whoami.ts +0 -14
  330. package/src/commands/xargs.ts +0 -31
  331. package/src/index.ts +0 -67
  332. package/src/modules/linuxRootfs.ts +0 -1961
  333. package/src/modules/neofetch.ts +0 -358
  334. package/src/modules/shellInteractive.ts +0 -57
  335. package/src/modules/shellRuntime.ts +0 -76
  336. package/src/self-standalone.ts +0 -542
  337. package/src/standalone-wo-sftp.ts +0 -38
  338. package/src/standalone.ts +0 -72
  339. package/src/types/commands.ts +0 -146
  340. package/src/types/pipeline.ts +0 -52
  341. package/src/types/streams.ts +0 -32
  342. package/src/types/tar-stream.d.ts +0 -38
  343. package/src/types/vfs.ts +0 -98
  344. package/src/utils/expand.ts +0 -491
  345. package/src/utils/perfLogger.ts +0 -72
  346. package/src/utils/tokenize.ts +0 -98
  347. package/src/utils/vfsDiff.ts +0 -275
  348. package/tests/command-helpers.test.ts +0 -116
  349. package/tests/commands-admin-net.test.ts +0 -441
  350. package/tests/commands-advanced.test.ts +0 -456
  351. package/tests/commands-core.test.ts +0 -562
  352. package/tests/commands-missing.test.ts +0 -570
  353. package/tests/commands-specific-units.test.ts +0 -327
  354. package/tests/commands-text-sys.test.ts +0 -445
  355. package/tests/expand.test.ts +0 -170
  356. package/tests/helpers.test.ts +0 -97
  357. package/tests/new-features.test.ts +0 -1036
  358. package/tests/parser-executor.test.ts +0 -37
  359. package/tests/sftp.test.ts +0 -323
  360. package/tests/ssh-exec.test.ts +0 -45
  361. package/tests/test-helper.ts +0 -79
  362. package/tests/users.test.ts +0 -86
  363. package/tsconfig.json +0 -49
  364. 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);