typescript-virtual-container 1.4.3 → 1.4.5

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 (382) hide show
  1. package/.vscode/settings.json +2 -1
  2. package/README.md +137 -19
  3. package/benchmark-results.txt +21 -21
  4. package/builds/self-standalone.js +322 -266
  5. package/builds/self-standalone.js.map +4 -4
  6. package/builds/standalone-wo-sftp.js +268 -212
  7. package/builds/standalone-wo-sftp.js.map +4 -4
  8. package/builds/standalone.js +268 -212
  9. package/builds/standalone.js.map +4 -4
  10. package/builds/web-full-api.min.js +5 -5
  11. package/builds/web-full-api.min.js.map +3 -3
  12. package/builds/web.min.js +5 -5
  13. package/builds/web.min.js.map +3 -3
  14. package/bun.lock +101 -6
  15. package/dist/Honeypot/index.js +1 -0
  16. package/dist/Honeypot/index.js.map +1 -0
  17. package/dist/SSHClient/index.js +1 -0
  18. package/dist/SSHClient/index.js.map +1 -0
  19. package/dist/SSHMimic/exec.js +1 -0
  20. package/dist/SSHMimic/exec.js.map +1 -0
  21. package/dist/SSHMimic/executor.js +1 -0
  22. package/dist/SSHMimic/executor.js.map +1 -0
  23. package/dist/SSHMimic/hostKey.js +1 -0
  24. package/dist/SSHMimic/hostKey.js.map +1 -0
  25. package/dist/SSHMimic/index.js +1 -0
  26. package/dist/SSHMimic/index.js.map +1 -0
  27. package/dist/SSHMimic/loginBanner.js +1 -0
  28. package/dist/SSHMimic/loginBanner.js.map +1 -0
  29. package/dist/SSHMimic/loginFormat.js +1 -0
  30. package/dist/SSHMimic/loginFormat.js.map +1 -0
  31. package/dist/SSHMimic/prompt.js +1 -0
  32. package/dist/SSHMimic/prompt.js.map +1 -0
  33. package/dist/SSHMimic/sftp.js +1 -0
  34. package/dist/SSHMimic/sftp.js.map +1 -0
  35. package/dist/VirtualFileSystem/binaryPack.js +1 -0
  36. package/dist/VirtualFileSystem/binaryPack.js.map +1 -0
  37. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  38. package/dist/VirtualFileSystem/index.js +1 -0
  39. package/dist/VirtualFileSystem/index.js.map +1 -0
  40. package/dist/VirtualFileSystem/internalTypes.js +1 -0
  41. package/dist/VirtualFileSystem/internalTypes.js.map +1 -0
  42. package/dist/VirtualFileSystem/path.js +1 -0
  43. package/dist/VirtualFileSystem/path.js.map +1 -0
  44. package/dist/VirtualPackageManager/index.js +1 -0
  45. package/dist/VirtualPackageManager/index.js.map +1 -0
  46. package/dist/VirtualShell/index.js +1 -0
  47. package/dist/VirtualShell/index.js.map +1 -0
  48. package/dist/VirtualShell/shell.js +1 -0
  49. package/dist/VirtualShell/shell.js.map +1 -0
  50. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  51. package/dist/VirtualShell/shellParser.js +3 -1
  52. package/dist/VirtualShell/shellParser.js.map +1 -0
  53. package/dist/VirtualUserManager/index.js +1 -0
  54. package/dist/VirtualUserManager/index.js.map +1 -0
  55. package/dist/commands/adduser.js +1 -0
  56. package/dist/commands/adduser.js.map +1 -0
  57. package/dist/commands/alias.js +1 -0
  58. package/dist/commands/alias.js.map +1 -0
  59. package/dist/commands/apt.js +1 -0
  60. package/dist/commands/apt.js.map +1 -0
  61. package/dist/commands/awk.d.ts.map +1 -1
  62. package/dist/commands/awk.js +2 -2
  63. package/dist/commands/awk.js.map +1 -0
  64. package/dist/commands/base64.js +1 -0
  65. package/dist/commands/base64.js.map +1 -0
  66. package/dist/commands/cat.js +1 -0
  67. package/dist/commands/cat.js.map +1 -0
  68. package/dist/commands/cd.js +3 -2
  69. package/dist/commands/cd.js.map +1 -0
  70. package/dist/commands/chmod.js +1 -0
  71. package/dist/commands/chmod.js.map +1 -0
  72. package/dist/commands/clear.js +1 -0
  73. package/dist/commands/clear.js.map +1 -0
  74. package/dist/commands/command-helpers.js +1 -0
  75. package/dist/commands/command-helpers.js.map +1 -0
  76. package/dist/commands/cp.js +1 -0
  77. package/dist/commands/cp.js.map +1 -0
  78. package/dist/commands/curl.js +1 -0
  79. package/dist/commands/curl.js.map +1 -0
  80. package/dist/commands/cut.js +1 -0
  81. package/dist/commands/cut.js.map +1 -0
  82. package/dist/commands/date.js +1 -0
  83. package/dist/commands/date.js.map +1 -0
  84. package/dist/commands/declare.js +1 -0
  85. package/dist/commands/declare.js.map +1 -0
  86. package/dist/commands/deluser.js +1 -0
  87. package/dist/commands/deluser.js.map +1 -0
  88. package/dist/commands/df.js +1 -0
  89. package/dist/commands/df.js.map +1 -0
  90. package/dist/commands/diff.js +1 -0
  91. package/dist/commands/diff.js.map +1 -0
  92. package/dist/commands/dpkg.js +1 -0
  93. package/dist/commands/dpkg.js.map +1 -0
  94. package/dist/commands/du.js +1 -0
  95. package/dist/commands/du.js.map +1 -0
  96. package/dist/commands/echo.js +1 -0
  97. package/dist/commands/echo.js.map +1 -0
  98. package/dist/commands/env.js +1 -0
  99. package/dist/commands/env.js.map +1 -0
  100. package/dist/commands/exit.js +1 -0
  101. package/dist/commands/exit.js.map +1 -0
  102. package/dist/commands/export.js +1 -0
  103. package/dist/commands/export.js.map +1 -0
  104. package/dist/commands/find.js +1 -0
  105. package/dist/commands/find.js.map +1 -0
  106. package/dist/commands/free.js +1 -0
  107. package/dist/commands/free.js.map +1 -0
  108. package/dist/commands/grep.js +1 -0
  109. package/dist/commands/grep.js.map +1 -0
  110. package/dist/commands/groups.js +1 -0
  111. package/dist/commands/groups.js.map +1 -0
  112. package/dist/commands/gzip.js +1 -0
  113. package/dist/commands/gzip.js.map +1 -0
  114. package/dist/commands/head.js +1 -0
  115. package/dist/commands/head.js.map +1 -0
  116. package/dist/commands/help.js +1 -0
  117. package/dist/commands/help.js.map +1 -0
  118. package/dist/commands/helpers.d.ts.map +1 -1
  119. package/dist/commands/helpers.js +4 -0
  120. package/dist/commands/helpers.js.map +1 -0
  121. package/dist/commands/history.js +1 -0
  122. package/dist/commands/history.js.map +1 -0
  123. package/dist/commands/hostname.js +1 -0
  124. package/dist/commands/hostname.js.map +1 -0
  125. package/dist/commands/htop.js +1 -0
  126. package/dist/commands/htop.js.map +1 -0
  127. package/dist/commands/id.js +1 -0
  128. package/dist/commands/id.js.map +1 -0
  129. package/dist/commands/index.js +1 -0
  130. package/dist/commands/index.js.map +1 -0
  131. package/dist/commands/kill.js +1 -0
  132. package/dist/commands/kill.js.map +1 -0
  133. package/dist/commands/ln.js +1 -0
  134. package/dist/commands/ln.js.map +1 -0
  135. package/dist/commands/ls.d.ts.map +1 -1
  136. package/dist/commands/ls.js +171 -37
  137. package/dist/commands/ls.js.map +1 -0
  138. package/dist/commands/lsb-release.js +1 -0
  139. package/dist/commands/lsb-release.js.map +1 -0
  140. package/dist/commands/man.js +1 -0
  141. package/dist/commands/man.js.map +1 -0
  142. package/dist/commands/mkdir.js +1 -0
  143. package/dist/commands/mkdir.js.map +1 -0
  144. package/dist/commands/mv.js +1 -0
  145. package/dist/commands/mv.js.map +1 -0
  146. package/dist/commands/nano.js +1 -0
  147. package/dist/commands/nano.js.map +1 -0
  148. package/dist/commands/neofetch.js +1 -0
  149. package/dist/commands/neofetch.js.map +1 -0
  150. package/dist/commands/node.js +1 -0
  151. package/dist/commands/node.js.map +1 -0
  152. package/dist/commands/npm.js +1 -0
  153. package/dist/commands/npm.js.map +1 -0
  154. package/dist/commands/passwd.js +1 -0
  155. package/dist/commands/passwd.js.map +1 -0
  156. package/dist/commands/ping.js +1 -0
  157. package/dist/commands/ping.js.map +1 -0
  158. package/dist/commands/printf.js +1 -0
  159. package/dist/commands/printf.js.map +1 -0
  160. package/dist/commands/ps.js +1 -0
  161. package/dist/commands/ps.js.map +1 -0
  162. package/dist/commands/pwd.js +1 -0
  163. package/dist/commands/pwd.js.map +1 -0
  164. package/dist/commands/python.js +1 -0
  165. package/dist/commands/python.js.map +1 -0
  166. package/dist/commands/read.js +1 -0
  167. package/dist/commands/read.js.map +1 -0
  168. package/dist/commands/registry.js +1 -0
  169. package/dist/commands/registry.js.map +1 -0
  170. package/dist/commands/rm.js +1 -0
  171. package/dist/commands/rm.js.map +1 -0
  172. package/dist/commands/runtime.d.ts.map +1 -1
  173. package/dist/commands/runtime.js +37 -0
  174. package/dist/commands/runtime.js.map +1 -0
  175. package/dist/commands/sed.js +1 -0
  176. package/dist/commands/sed.js.map +1 -0
  177. package/dist/commands/seq.js +1 -0
  178. package/dist/commands/seq.js.map +1 -0
  179. package/dist/commands/set.js +1 -0
  180. package/dist/commands/set.js.map +1 -0
  181. package/dist/commands/sh.d.ts.map +1 -1
  182. package/dist/commands/sh.js +9 -4
  183. package/dist/commands/sh.js.map +1 -0
  184. package/dist/commands/shift.js +1 -0
  185. package/dist/commands/shift.js.map +1 -0
  186. package/dist/commands/sleep.js +1 -0
  187. package/dist/commands/sleep.js.map +1 -0
  188. package/dist/commands/sort.js +1 -0
  189. package/dist/commands/sort.js.map +1 -0
  190. package/dist/commands/source.js +1 -0
  191. package/dist/commands/source.js.map +1 -0
  192. package/dist/commands/stat.js +1 -0
  193. package/dist/commands/stat.js.map +1 -0
  194. package/dist/commands/su.js +1 -0
  195. package/dist/commands/su.js.map +1 -0
  196. package/dist/commands/sudo.js +1 -0
  197. package/dist/commands/sudo.js.map +1 -0
  198. package/dist/commands/tail.js +1 -0
  199. package/dist/commands/tail.js.map +1 -0
  200. package/dist/commands/tar.js +1 -0
  201. package/dist/commands/tar.js.map +1 -0
  202. package/dist/commands/tee.js +1 -0
  203. package/dist/commands/tee.js.map +1 -0
  204. package/dist/commands/test.d.ts.map +1 -1
  205. package/dist/commands/test.js +1 -0
  206. package/dist/commands/test.js.map +1 -0
  207. package/dist/commands/touch.js +1 -0
  208. package/dist/commands/touch.js.map +1 -0
  209. package/dist/commands/tr.js +1 -0
  210. package/dist/commands/tr.js.map +1 -0
  211. package/dist/commands/tree.js +1 -0
  212. package/dist/commands/tree.js.map +1 -0
  213. package/dist/commands/true.js +1 -0
  214. package/dist/commands/true.js.map +1 -0
  215. package/dist/commands/type.js +1 -0
  216. package/dist/commands/type.js.map +1 -0
  217. package/dist/commands/uname.js +1 -0
  218. package/dist/commands/uname.js.map +1 -0
  219. package/dist/commands/uniq.js +1 -0
  220. package/dist/commands/uniq.js.map +1 -0
  221. package/dist/commands/unset.js +1 -0
  222. package/dist/commands/unset.js.map +1 -0
  223. package/dist/commands/uptime.js +1 -0
  224. package/dist/commands/uptime.js.map +1 -0
  225. package/dist/commands/wc.js +2 -1
  226. package/dist/commands/wc.js.map +1 -0
  227. package/dist/commands/wget.js +1 -0
  228. package/dist/commands/wget.js.map +1 -0
  229. package/dist/commands/which.js +1 -0
  230. package/dist/commands/which.js.map +1 -0
  231. package/dist/commands/who.js +1 -0
  232. package/dist/commands/who.js.map +1 -0
  233. package/dist/commands/whoami.js +1 -0
  234. package/dist/commands/whoami.js.map +1 -0
  235. package/dist/commands/xargs.js +1 -0
  236. package/dist/commands/xargs.js.map +1 -0
  237. package/dist/index.js +1 -0
  238. package/dist/index.js.map +1 -0
  239. package/dist/modules/linuxRootfs.d.ts +35 -17
  240. package/dist/modules/linuxRootfs.d.ts.map +1 -1
  241. package/dist/modules/linuxRootfs.js +332 -152
  242. package/dist/modules/linuxRootfs.js.map +1 -0
  243. package/dist/modules/neofetch.js +1 -0
  244. package/dist/modules/neofetch.js.map +1 -0
  245. package/dist/modules/shellInteractive.js +1 -0
  246. package/dist/modules/shellInteractive.js.map +1 -0
  247. package/dist/modules/shellRuntime.js +1 -0
  248. package/dist/modules/shellRuntime.js.map +1 -0
  249. package/dist/self-standalone.js +1 -0
  250. package/dist/self-standalone.js.map +1 -0
  251. package/dist/standalone-wo-sftp.js +1 -0
  252. package/dist/standalone-wo-sftp.js.map +1 -0
  253. package/dist/standalone.js +1 -0
  254. package/dist/standalone.js.map +1 -0
  255. package/dist/types/commands.d.ts +1 -1
  256. package/dist/types/commands.d.ts.map +1 -1
  257. package/dist/types/commands.js +1 -0
  258. package/dist/types/commands.js.map +1 -0
  259. package/dist/types/pipeline.js +1 -0
  260. package/dist/types/pipeline.js.map +1 -0
  261. package/dist/types/streams.js +1 -0
  262. package/dist/types/streams.js.map +1 -0
  263. package/dist/types/vfs.js +1 -0
  264. package/dist/types/vfs.js.map +1 -0
  265. package/dist/utils/expand.d.ts +2 -2
  266. package/dist/utils/expand.d.ts.map +1 -1
  267. package/dist/utils/expand.js +336 -124
  268. package/dist/utils/expand.js.map +1 -0
  269. package/dist/utils/perfLogger.js +1 -0
  270. package/dist/utils/perfLogger.js.map +1 -0
  271. package/dist/utils/tokenize.js +1 -0
  272. package/dist/utils/tokenize.js.map +1 -0
  273. package/dist/utils/vfsDiff.js +1 -0
  274. package/dist/utils/vfsDiff.js.map +1 -0
  275. package/dist/web-api.js +1 -0
  276. package/dist/web-api.js.map +1 -0
  277. package/dist/web-full.js +1 -0
  278. package/dist/web-full.js.map +1 -0
  279. package/dist/web.js +1 -0
  280. package/dist/web.js.map +1 -0
  281. package/docs/.nojekyll +1 -0
  282. package/docs/assets/hierarchy.js +1 -0
  283. package/docs/assets/highlight.css +162 -0
  284. package/docs/assets/icons.js +18 -0
  285. package/docs/assets/icons.svg +1 -0
  286. package/docs/assets/main.js +60 -0
  287. package/docs/assets/navigation.js +1 -0
  288. package/docs/assets/search.js +1 -0
  289. package/docs/assets/style.css +1633 -0
  290. package/docs/classes/HoneyPot.html +31 -0
  291. package/docs/classes/SshClient.html +66 -0
  292. package/docs/classes/VirtualFileSystem.html +262 -0
  293. package/docs/classes/VirtualPackageManager.html +63 -0
  294. package/docs/classes/VirtualSftpServer.html +169 -0
  295. package/docs/classes/VirtualShell.html +265 -0
  296. package/docs/classes/VirtualSshServer.html +177 -0
  297. package/docs/classes/VirtualUserManager.html +276 -0
  298. package/docs/docs/.nojekyll +1 -0
  299. package/docs/docs/assets/hierarchy.js +1 -0
  300. package/docs/docs/assets/highlight.css +162 -0
  301. package/docs/docs/assets/icons.js +18 -0
  302. package/docs/docs/assets/icons.svg +1 -0
  303. package/docs/docs/assets/main.js +60 -0
  304. package/docs/docs/assets/navigation.js +1 -0
  305. package/docs/docs/assets/search.js +1 -0
  306. package/docs/docs/assets/style.css +1633 -0
  307. package/docs/docs/hierarchy.html +1 -0
  308. package/docs/docs/index.html +1842 -0
  309. package/docs/docs/media/LICENSE +21 -0
  310. package/docs/docs/modules.html +1 -0
  311. package/docs/functions/assertDiff.html +6 -0
  312. package/docs/functions/diffSnapshots.html +7 -0
  313. package/docs/functions/formatDiff.html +6 -0
  314. package/docs/functions/getArg.html +13 -0
  315. package/docs/functions/getFlag.html +15 -0
  316. package/docs/functions/ifFlag.html +11 -0
  317. package/docs/hierarchy.html +1 -0
  318. package/docs/index.html +1842 -0
  319. package/docs/interfaces/AuditLogEntry.html +6 -0
  320. package/docs/interfaces/CommandContext.html +22 -0
  321. package/docs/interfaces/CommandResult.html +26 -0
  322. package/docs/interfaces/ExecStream.html +11 -0
  323. package/docs/interfaces/HoneyPotStats.html +14 -0
  324. package/docs/interfaces/InstalledPackage.html +20 -0
  325. package/docs/interfaces/NanoEditorSession.html +8 -0
  326. package/docs/interfaces/PackageDefinition.html +30 -0
  327. package/docs/interfaces/PackageFile.html +8 -0
  328. package/docs/interfaces/RemoveOptions.html +4 -0
  329. package/docs/interfaces/ShellEnv.html +6 -0
  330. package/docs/interfaces/ShellModule.html +14 -0
  331. package/docs/interfaces/ShellProperties.html +14 -0
  332. package/docs/interfaces/ShellStream.html +11 -0
  333. package/docs/interfaces/SudoChallenge.html +24 -0
  334. package/docs/interfaces/VfsBaseNode.html +12 -0
  335. package/docs/interfaces/VfsDiff.html +10 -0
  336. package/docs/interfaces/VfsDiffEntry.html +6 -0
  337. package/docs/interfaces/VfsDiffModified.html +10 -0
  338. package/docs/interfaces/VfsDirectoryNode.html +15 -0
  339. package/docs/interfaces/VfsFileNode.html +17 -0
  340. package/docs/interfaces/VfsOptions.html +12 -0
  341. package/docs/interfaces/VfsSnapshot.html +3 -0
  342. package/docs/interfaces/VfsSnapshotBaseNode.html +8 -0
  343. package/docs/interfaces/VfsSnapshotDirectoryNode.html +10 -0
  344. package/docs/interfaces/VfsSnapshotFileNode.html +12 -0
  345. package/docs/interfaces/WriteFileOptions.html +6 -0
  346. package/docs/media/LICENSE +21 -0
  347. package/docs/modules.html +1 -0
  348. package/docs/types/CommandMode.html +2 -0
  349. package/docs/types/CommandOutcome.html +2 -0
  350. package/docs/types/VfsNodeStats.html +2 -0
  351. package/docs/types/VfsNodeType.html +2 -0
  352. package/docs/types/VfsPersistenceMode.html +5 -0
  353. package/docs/types/VfsSnapshotNode.html +2 -0
  354. package/examples/web.min.js +5 -5
  355. package/package.json +7 -4
  356. package/src/VirtualFileSystem/index.ts +11 -9
  357. package/src/VirtualShell/shellParser.ts +3 -2
  358. package/src/bun.d.ts +1 -0
  359. package/src/commands/awk.ts +1 -2
  360. package/src/commands/cd.ts +2 -2
  361. package/src/commands/helpers.ts +3 -0
  362. package/src/commands/ls.ts +210 -41
  363. package/src/commands/runtime.ts +56 -3
  364. package/src/commands/sh.ts +7 -4
  365. package/src/commands/test.ts +4 -2
  366. package/src/commands/wc.ts +1 -1
  367. package/src/modules/linuxRootfs.ts +420 -231
  368. package/src/types/commands.ts +1 -1
  369. package/src/utils/expand.ts +256 -76
  370. package/tests/command-helpers.test.ts +80 -0
  371. package/tests/commands-admin-net.test.ts +441 -0
  372. package/tests/commands-advanced.test.ts +456 -0
  373. package/tests/commands-core.test.ts +562 -0
  374. package/tests/commands-missing.test.ts +570 -0
  375. package/tests/commands-specific-units.test.ts +327 -0
  376. package/tests/commands-text-sys.test.ts +445 -0
  377. package/tests/expand.test.ts +170 -0
  378. package/tests/helpers.test.ts +75 -0
  379. package/tests/test-helper.ts +79 -0
  380. package/tsconfig.json +3 -0
  381. package/typedoc.json +8 -0
  382. package/tests/bun-test-shim.ts +0 -9
@@ -1,16 +1,23 @@
1
- /** biome-ignore-all lint/style/useNamingConvention: ENV VAR KEYS */
1
+ /** biome-ignore-all lint/style/useNamingConvention: ENV VAR KEYS + system names */
2
2
  /**
3
3
  * linuxRootfs.ts
4
4
  *
5
5
  * Bootstraps a realistic Linux directory hierarchy in the VFS.
6
6
  * Called once during VirtualShell initialization. Idempotent — skips
7
7
  * paths that already exist so FS-mode snapshots survive restarts.
8
+ *
9
+ * Public API:
10
+ * - bootstrapLinuxRootfs() one-shot boot (VirtualShell calls this)
11
+ * - refreshProc() refresh /proc/* (call on session changes)
12
+ * - syncEtcPasswd() sync /etc/passwd|group|shadow from UserManager
13
+ * - createLinuxRootfsEngine() returns engine with .boot() + .tick() for
14
+ * runtimes that want a live refresh loop
8
15
  */
9
16
 
10
17
  import * as os from "node:os";
11
18
  import type VirtualFileSystem from "../VirtualFileSystem";
12
19
  import type { ShellProperties } from "../VirtualShell";
13
- import type { VirtualUserManager } from "../VirtualUserManager";
20
+ import type { VirtualActiveSession, VirtualUserManager } from "../VirtualUserManager";
14
21
 
15
22
  // ─── helpers ────────────────────────────────────────────────────────────────
16
23
 
@@ -27,6 +34,20 @@ function ensureFile(
27
34
  if (!vfs.exists(path)) vfs.writeFile(path, content, { mode });
28
35
  }
29
36
 
37
+ function write(vfs: VirtualFileSystem, path: string, content: string): void {
38
+ vfs.writeFile(path, content);
39
+ }
40
+
41
+ /** FNV-1a 32-bit — deterministic seed from any string */
42
+ function fnv1a(str: string): number {
43
+ let h = 2166136261;
44
+ for (let i = 0; i < str.length; i++) {
45
+ h ^= str.charCodeAt(i);
46
+ h = Math.imul(h, 16777619);
47
+ }
48
+ return h >>> 0;
49
+ }
50
+
30
51
  // ─── /etc ────────────────────────────────────────────────────────────────────
31
52
 
32
53
  function bootstrapEtc(
@@ -63,8 +84,7 @@ function bootstrapEtc(
63
84
  ].join("\n")}\n`,
64
85
  );
65
86
 
66
- ensureFile(vfs, "/etc/issue", `Fortune GNU/Linux 1.0 \\n \\l\n`);
67
-
87
+ ensureFile(vfs, "/etc/issue", "Fortune GNU/Linux 1.0 \\n \\l\n");
68
88
  ensureFile(
69
89
  vfs,
70
90
  "/etc/motd",
@@ -84,7 +104,7 @@ function bootstrapEtc(
84
104
  ].join("\n")}\n`,
85
105
  );
86
106
 
87
- // network stubs
107
+ // network
88
108
  ensureDir(vfs, "/etc/network");
89
109
  ensureFile(
90
110
  vfs,
@@ -98,11 +118,7 @@ function bootstrapEtc(
98
118
  ].join("\n")}\n`,
99
119
  );
100
120
 
101
- ensureFile(
102
- vfs,
103
- "/etc/resolv.conf",
104
- "nameserver 1.1.1.1\nnameserver 8.8.8.8\n",
105
- );
121
+ ensureFile(vfs, "/etc/resolv.conf", "nameserver 1.1.1.1\nnameserver 8.8.8.8\n");
106
122
 
107
123
  ensureFile(
108
124
  vfs,
@@ -118,6 +134,69 @@ function bootstrapEtc(
118
134
  ensureDir(vfs, "/etc/init.d");
119
135
  ensureDir(vfs, "/etc/systemd");
120
136
  ensureDir(vfs, "/etc/systemd/system");
137
+
138
+ // fstab
139
+ ensureFile(
140
+ vfs,
141
+ "/etc/fstab",
142
+ `${[
143
+ "# <file system> <mount point> <type> <options> <dump> <pass>",
144
+ "UUID=00000000-0000-0000-0000-000000000001 / ext4 errors=remount-ro 0 1",
145
+ "UUID=00000000-0000-0000-0000-000000000002 /boot ext4 defaults 0 2",
146
+ "UUID=00000000-0000-0000-0000-000000000003 none swap sw 0 0",
147
+ "tmpfs /tmp tmpfs defaults,noatime 0 0",
148
+ "tmpfs /run tmpfs defaults,noatime 0 0",
149
+ ].join("\n")}\n`,
150
+ );
151
+
152
+ // login.defs — useradd/passwd defaults
153
+ ensureFile(
154
+ vfs,
155
+ "/etc/login.defs",
156
+ `${[
157
+ "MAIL_DIR /var/mail",
158
+ "PASS_MAX_DAYS 99999",
159
+ "PASS_MIN_DAYS 0",
160
+ "PASS_WARN_AGE 7",
161
+ "UID_MIN 1000",
162
+ "UID_MAX 60000",
163
+ "GID_MIN 1000",
164
+ "GID_MAX 60000",
165
+ "CREATE_HOME yes",
166
+ "UMASK 022",
167
+ "USERGROUPS_ENAB yes",
168
+ "ENCRYPT_METHOD SHA512",
169
+ ].join("\n")}\n`,
170
+ );
171
+
172
+ // security + pam
173
+ ensureDir(vfs, "/etc/security");
174
+ ensureFile(vfs, "/etc/security/limits.conf", "# /etc/security/limits.conf\n");
175
+ ensureFile(vfs, "/etc/security/access.conf", "# /etc/security/access.conf\n");
176
+
177
+ ensureDir(vfs, "/etc/pam.d");
178
+ ensureFile(vfs, "/etc/pam.d/common-auth", "auth required pam_unix.so\n");
179
+ ensureFile(vfs, "/etc/pam.d/common-account", "account required pam_unix.so\n");
180
+ ensureFile(vfs, "/etc/pam.d/common-password",
181
+ "password required pam_unix.so obscure sha512\n");
182
+ ensureFile(vfs, "/etc/pam.d/common-session", "session required pam_unix.so\n");
183
+ ensureFile(vfs, "/etc/pam.d/sshd",
184
+ "@include common-auth\n@include common-account\n@include common-session\n");
185
+
186
+ // sudo config
187
+ ensureDir(vfs, "/etc/sudoers.d");
188
+ ensureFile(vfs, "/etc/sudoers",
189
+ "root ALL=(ALL:ALL) ALL\n%sudo ALL=(ALL:ALL) ALL\n", 0o440);
190
+
191
+ // ld
192
+ ensureFile(vfs, "/etc/ld.so.conf", "include /etc/ld.so.conf.d/*.conf\n");
193
+ ensureDir(vfs, "/etc/ld.so.conf.d");
194
+ ensureFile(vfs, "/etc/ld.so.conf.d/x86_64-linux-gnu.conf", "/lib/x86_64-linux-gnu\n/usr/lib/x86_64-linux-gnu\n");
195
+
196
+ // locale + timezone
197
+ ensureFile(vfs, "/etc/locale.conf", "LANG=en_US.UTF-8\n");
198
+ ensureFile(vfs, "/etc/timezone", "UTC\n");
199
+ ensureFile(vfs, "/etc/localtime", "UTC\n");
121
200
  }
122
201
 
123
202
  // ─── /etc/passwd + /etc/group + /etc/shadow ─────────────────────────────────
@@ -125,8 +204,6 @@ function bootstrapEtc(
125
204
  /**
126
205
  * Sync `/etc/passwd`, `/etc/group`, and `/etc/shadow` from the
127
206
  * VirtualUserManager's current user list into the VFS.
128
- * @param vfs VirtualFileSystem instance to write files into
129
- * @param users VirtualUserManager to source users from
130
207
  */
131
208
  export function syncEtcPasswd(
132
209
  vfs: VirtualFileSystem,
@@ -159,7 +236,7 @@ export function syncEtcPasswd(
159
236
  ];
160
237
  vfs.writeFile("/etc/group", `${groupLines.join("\n")}\n`);
161
238
 
162
- // shadow — fake hashes, never real
239
+ // shadow — fake hashes, never real credentials
163
240
  const shadowLines = [
164
241
  "root:*:19000:0:99999:7:::",
165
242
  "daemon:*:19000:0:99999:7:::",
@@ -171,9 +248,9 @@ export function syncEtcPasswd(
171
248
  vfs.writeFile("/etc/shadow", `${shadowLines.join("\n")}\n`, { mode: 0o640 });
172
249
  }
173
250
 
174
- // ─── /proc ───────────────────────────────────────────────────────────────────
251
+ // ─── /proc helpers ───────────────────────────────────────────────────────────
175
252
 
176
- /** Derive a stable virtual PID from a tty string like "pts/0" → 1000, "pts/1" → 1001 */
253
+ /** Derive a stable virtual PID from a tty string e.g. "pts/0" → 1000 */
177
254
  function ttyToPid(tty: string): number {
178
255
  const match = tty.match(/(\d+)$/);
179
256
  return 1000 + (match?.[1] ? parseInt(match[1], 10) : 0);
@@ -194,16 +271,16 @@ function writeProcPid(
194
271
  ensureDir(vfs, `${dir}/fd`);
195
272
  ensureDir(vfs, `${dir}/fdinfo`);
196
273
 
197
- const uptimeSec = Math.floor(
198
- (Date.now() - new Date(startedAt).getTime()) / 1000,
199
- );
274
+ const uptimeSec = Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000);
275
+ const comm = cmdline.split(/\s+/)[0] ?? "bash";
200
276
 
201
- vfs.writeFile(`${dir}/cmdline`, `${cmdline.replace(/\s+/g, "\0")}\0`);
202
- vfs.writeFile(`${dir}/comm`, cmdline.split(/\s+/)[0] ?? "bash");
203
- vfs.writeFile(
277
+ write(vfs, `${dir}/cmdline`, `${cmdline.replace(/\s+/g, "\0")}\0`);
278
+ write(vfs, `${dir}/comm`, comm);
279
+ write(
280
+ vfs,
204
281
  `${dir}/status`,
205
282
  `${[
206
- `Name: ${cmdline.split(/\s+/)[0] ?? "bash"}`,
283
+ `Name: ${comm}`,
207
284
  `State: S (sleeping)`,
208
285
  `Pid: ${pid}`,
209
286
  `PPid: 1`,
@@ -214,54 +291,71 @@ function writeProcPid(
214
291
  `Threads: 1`,
215
292
  ].join("\n")}\n`,
216
293
  );
217
- vfs.writeFile(
294
+ write(
295
+ vfs,
218
296
  `${dir}/stat`,
219
- `${pid} (${cmdline.split(/\s+/)[0] ?? "bash"}) S 1 ${pid} ${pid} 0 -1 4194304 0 0 0 0 ${uptimeSec} 0 0 0 20 0 1 0 0 16384 4096 0\n`,
297
+ `${pid} (${comm}) S 1 ${pid} ${pid} 0 -1 4194304 0 0 0 0 ${uptimeSec} 0 0 0 20 0 1 0 0 16384 4096 0\n`,
220
298
  );
221
- vfs.writeFile(
299
+ write(
300
+ vfs,
222
301
  `${dir}/environ`,
223
- `${Object.entries(env)
224
- .map(([k, v]) => `${k}=${v}`)
225
- .join("\0")}\0`,
302
+ `${Object.entries(env).map(([k, v]) => `${k}=${v}`).join("\0")}\0`,
226
303
  );
227
- vfs.writeFile(`${dir}/cwd`, `/home/${username}\0`);
228
- vfs.writeFile(`${dir}/exe`, "/bin/bash\0");
304
+ write(vfs, `${dir}/cwd`, `/home/${username}\0`);
305
+ write(vfs, `${dir}/exe`, "/bin/bash\0");
229
306
 
230
- // Standard fd entries
231
- vfs.writeFile(`${dir}/fd/0`, "");
232
- vfs.writeFile(`${dir}/fd/1`, "");
233
- vfs.writeFile(`${dir}/fd/2`, "");
307
+ // Standard fd stubs (stdin/stdout/stderr)
308
+ for (const fd of ["0", "1", "2"]) {
309
+ ensureFile(vfs, `${dir}/fd/${fd}`, "");
310
+ }
234
311
  }
235
312
 
313
+ // ─── /proc boot log ──────────────────────────────────────────────────────────
314
+
315
+ function bootProcLog(vfs: VirtualFileSystem, props: ShellProperties): void {
316
+ ensureDir(vfs, "/proc/boot");
317
+ ensureFile(
318
+ vfs,
319
+ "/proc/boot/log",
320
+ `${[
321
+ "[ 0.000000] Linux virtual kernel booting...",
322
+ "[ 0.000120] init memory subsystem",
323
+ "[ 0.000240] mount /proc /sys /dev",
324
+ "[ 0.000420] start init",
325
+ "[ 0.000680] system ready",
326
+ ].join("\n")}\n`,
327
+ );
328
+ ensureFile(vfs, "/proc/boot/version", `Linux ${props.kernel} (virtual)\n`);
329
+ }
330
+
331
+ // ─── /proc refresh ───────────────────────────────────────────────────────────
332
+
236
333
  /**
237
334
  * Populate and refresh `/proc` virtual entries based on host stats and
238
- * provided active sessions. Rewrites `/proc/uptime`, `/proc/meminfo`,
239
- * `/proc/cpuinfo`, `/proc/<pid>` entries and `/proc/self` content.
240
- * @param vfs VirtualFileSystem instance
241
- * @param props ShellProperties used for version strings
242
- * @param hostname Hostname to write into /proc/hostname
243
- * @param shellStartTime Start time used to compute uptime
244
- * @param sessions Optional active sessions list to populate per-pid entries
335
+ * provided active sessions. Rewrites uptime, meminfo, cpuinfo, loadavg,
336
+ * per-pid entries, and /proc/self.
337
+ *
338
+ * Safe to call repeatedly acts as a live kernel state snapshot.
245
339
  */
246
340
  export function refreshProc(
247
341
  vfs: VirtualFileSystem,
248
342
  props: ShellProperties,
249
343
  hostname: string,
250
344
  shellStartTime: number,
251
- sessions?: import("../VirtualUserManager").VirtualActiveSession[],
345
+ sessions: VirtualActiveSession[] = [],
252
346
  ): void {
253
347
  ensureDir(vfs, "/proc");
254
348
 
255
349
  const uptimeSec = Math.floor((Date.now() - shellStartTime) / 1000);
256
- vfs.writeFile(
257
- "/proc/uptime",
258
- `${uptimeSec}.00 ${Math.floor(uptimeSec * 0.9)}.00\n`,
259
- );
350
+ const idleSec = Math.floor(uptimeSec * 0.9);
351
+ write(vfs, "/proc/uptime", `${uptimeSec}.00 ${idleSec}.00\n`);
260
352
 
353
+ // meminfo — real host values, Linux-compatible format
261
354
  const totalMemKb = Math.floor(os.totalmem() / 1024);
262
355
  const freeMemKb = Math.floor(os.freemem() / 1024);
263
356
  const availMemKb = Math.floor(freeMemKb * 0.95);
264
- vfs.writeFile(
357
+ write(
358
+ vfs,
265
359
  "/proc/meminfo",
266
360
  `${[
267
361
  `MemTotal: ${String(totalMemKb).padStart(10)} kB`,
@@ -274,36 +368,33 @@ export function refreshProc(
274
368
  ].join("\n")}\n`,
275
369
  );
276
370
 
371
+ // cpuinfo — real host CPU passthrough
277
372
  const cpus = os.cpus();
278
373
  const cpuLines: string[] = [];
279
374
  for (let i = 0; i < cpus.length; i++) {
280
375
  const c = cpus[i];
281
376
  if (!c) continue;
282
- const mhz = c.speed.toFixed(3);
283
377
  cpuLines.push(
284
378
  `processor\t: ${i}`,
285
379
  `model name\t: ${c.model}`,
286
- `cpu MHz\t\t: ${mhz}`,
380
+ `cpu MHz\t\t: ${c.speed.toFixed(3)}`,
287
381
  `cache size\t: 8192 KB`,
288
382
  "",
289
383
  );
290
384
  }
291
- vfs.writeFile("/proc/cpuinfo", `${cpuLines.join("\n")}\n`);
385
+ write(vfs, "/proc/cpuinfo", `${cpuLines.join("\n")}\n`);
292
386
 
293
- vfs.writeFile(
387
+ write(
388
+ vfs,
294
389
  "/proc/version",
295
390
  `Linux version ${props.kernel} (fortune@build) (gcc version 12.2.0) #1 SMP\n`,
296
391
  );
392
+ write(vfs, "/proc/hostname", `${hostname}\n`);
297
393
 
298
- vfs.writeFile("/proc/hostname", `${hostname}\n`);
299
-
300
- // /proc/loadavg
394
+ // loadavg — slightly random but bounded
301
395
  const load = (Math.random() * 0.5).toFixed(2);
302
- const numProcs = 1 + (sessions?.length ?? 0);
303
- vfs.writeFile(
304
- "/proc/loadavg",
305
- `${load} ${load} ${load} ${numProcs}/${numProcs} 1\n`,
306
- );
396
+ const numProcs = 1 + sessions.length;
397
+ write(vfs, "/proc/loadavg", `${load} ${load} ${load} ${numProcs}/${numProcs} 1\n`);
307
398
 
308
399
  // /proc/net stubs
309
400
  ensureDir(vfs, "/proc/net");
@@ -317,113 +408,159 @@ export function refreshProc(
317
408
  " eth0: 131072 1024 0 0 0 0 0 0 65536 512 0 0 0 0 0 0",
318
409
  ].join("\n")}\n`,
319
410
  );
411
+ ensureFile(vfs, "/proc/net/if_inet6", "");
412
+ ensureFile(vfs, "/proc/net/tcp", " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
413
+ ensureFile(vfs, "/proc/net/tcp6", " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
414
+
415
+ // /proc/cmdline — kernel boot args
416
+ write(vfs, "/proc/cmdline", `BOOT_IMAGE=/boot/vmlinuz-${props.kernel} root=/dev/sda2 ro quiet splash\n`);
417
+
418
+ // /proc/filesystems
419
+ ensureFile(
420
+ vfs,
421
+ "/proc/filesystems",
422
+ `${["nodev\tsysfs", "nodev\ttmpfs", "nodev\tproc", "nodev\tdevtmpfs", "\text4", "\tvfat", "nodev\tsquashfs", "nodev\toverlay"].join("\n")}\n`,
423
+ );
424
+
425
+ // /proc/mounts (= /proc/self/mounts)
426
+ const mountsContent = `${[
427
+ "sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0",
428
+ "proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0",
429
+ "devtmpfs /dev devtmpfs rw,nosuid,size=8192k,nr_inodes=4096,mode=755 0 0",
430
+ "tmpfs /run tmpfs rw,nosuid,nodev,noexec,relatime,size=204800k,mode=755 0 0",
431
+ "tmpfs /tmp tmpfs rw,nosuid,nodev,noatime 0 0",
432
+ "/dev/sda2 / ext4 rw,relatime,errors=remount-ro 0 0",
433
+ "/dev/sda1 /boot ext4 rw,relatime 0 0",
434
+ "tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0",
435
+ "devpts /dev/pts devpts rw,nosuid,noexec,relatime,mode=620,ptmxmode=000 0 0",
436
+ "squashfs /snap/core squashfs ro,nodev,relatime 0 0",
437
+ ].join("\n")}\n`;
438
+ write(vfs, "/proc/mounts", mountsContent);
439
+ ensureDir(vfs, "/proc/self");
440
+ write(vfs, "/proc/self/mounts", mountsContent);
441
+
442
+ // /proc/partitions
443
+ write(
444
+ vfs,
445
+ "/proc/partitions",
446
+ `${[
447
+ "major minor #blocks name",
448
+ "",
449
+ " 8 0 41943040 sda",
450
+ " 8 1 524288 sda1",
451
+ " 8 2 41417216 sda2",
452
+ " 7 0 10485760 loop0",
453
+ ].join("\n")}\n`,
454
+ );
320
455
 
321
- // ── /proc/1 — init process ────────────────────────────────────────────────
322
- writeProcPid(
456
+ // /proc/swaps
457
+ write(
323
458
  vfs,
324
- 1,
325
- "root",
326
- "pts/0",
327
- "/sbin/init",
328
- new Date(shellStartTime).toISOString(),
329
- {},
459
+ "/proc/swaps",
460
+ "Filename\t\t\t\tType\t\tSize\t\tUsed\t\tPriority\n" +
461
+ `/dev/sda3\t\t\t\tpartition\t${Math.floor(os.totalmem() / 2048)}\t\t0\t\t-2\n`,
330
462
  );
331
463
 
332
- // ── /proc/<pid> per session ───────────────────────────────────────────────
333
- const activeSessions = sessions ?? [];
334
- for (const session of activeSessions) {
464
+ // /proc/sys sysctl virtual tree
465
+ ensureDir(vfs, "/proc/sys");
466
+ ensureDir(vfs, "/proc/sys/kernel");
467
+ ensureDir(vfs, "/proc/sys/net");
468
+ ensureDir(vfs, "/proc/sys/vm");
469
+ ensureFile(vfs, "/proc/sys/kernel/hostname", `${hostname}\n`);
470
+ ensureFile(vfs, "/proc/sys/kernel/ostype", "Linux\n");
471
+ ensureFile(vfs, "/proc/sys/kernel/osrelease", `${props.kernel}\n`);
472
+ ensureFile(vfs, "/proc/sys/kernel/pid_max", "32768\n");
473
+ ensureFile(vfs, "/proc/sys/kernel/threads-max", "65536\n");
474
+ ensureFile(vfs, "/proc/sys/kernel/randomize_va_space", "2\n");
475
+ ensureFile(vfs, "/proc/sys/kernel/dmesg_restrict", "0\n");
476
+ ensureFile(vfs, "/proc/sys/net/ipv4/ip_forward", "0\n");
477
+ ensureFile(vfs, "/proc/sys/vm/swappiness", "60\n");
478
+ ensureFile(vfs, "/proc/sys/vm/overcommit_memory", "0\n");
479
+
480
+ // init process (PID 1)
481
+ writeProcPid(vfs, 1, "root", "pts/0", "/sbin/init", new Date(shellStartTime).toISOString(), {});
482
+
483
+ // per-session processes
484
+ for (const session of sessions) {
335
485
  const pid = ttyToPid(session.tty);
336
- writeProcPid(
337
- vfs,
338
- pid,
339
- session.username,
340
- session.tty,
341
- "bash",
342
- session.startedAt,
343
- {
344
- USER: session.username,
345
- HOME: `/home/${session.username}`,
346
- TERM: "xterm-256color",
347
- SHELL: "/bin/bash",
348
- },
349
- );
486
+ writeProcPid(vfs, pid, session.username, session.tty, "bash", session.startedAt, {
487
+ USER: session.username,
488
+ HOME: `/home/${session.username}`,
489
+ TERM: "xterm-256color",
490
+ SHELL: "/bin/bash",
491
+ });
350
492
  }
351
493
 
352
- // ── /proc/self — symlink to current session PID or 1 ────────────────────
353
- // We can't know which session is "current" at populate time,
354
- // so /proc/self is a directory that mirrors the most recent session,
355
- // or init if no sessions. Commands that read /proc/self get consistent data.
356
- const selfPid =
357
- activeSessions.length > 0
358
- ? ttyToPid(activeSessions[activeSessions.length - 1]!.tty)
359
- : 1;
494
+ // /proc/self — mirror of most recent session, or init
495
+ const selfPid = sessions.length > 0 ? ttyToPid(sessions[sessions.length - 1]!.tty) : 1;
360
496
 
361
- // Remove existing /proc/self and recreate as content copy
362
497
  if (vfs.exists("/proc/self")) {
363
- try {
364
- vfs.remove("/proc/self");
365
- } catch {}
498
+ try { vfs.remove("/proc/self"); } catch { /* ignore */ }
366
499
  }
367
- // /proc/self is a real directory (not a symlink, which VFS may not support for dirs)
500
+
368
501
  const selfSrc = `/proc/${selfPid}`;
502
+ ensureDir(vfs, "/proc/self");
503
+ ensureDir(vfs, "/proc/self/fd");
504
+
369
505
  if (vfs.exists(selfSrc)) {
370
- ensureDir(vfs, "/proc/self");
371
- ensureDir(vfs, "/proc/self/fd");
372
506
  for (const entry of vfs.list(selfSrc)) {
373
507
  const srcPath = `${selfSrc}/${entry}`;
374
508
  const dstPath = `/proc/self/${entry}`;
375
509
  try {
376
510
  const st = vfs.stat(srcPath);
377
- if (st.type === "file") {
378
- vfs.writeFile(dstPath, vfs.readFile(srcPath));
379
- }
380
- } catch {}
511
+ if (st.type === "file") write(vfs, dstPath, vfs.readFile(srcPath));
512
+ } catch { /* skip unreadable entries */ }
381
513
  }
382
- vfs.writeFile(
383
- "/proc/self/status",
384
- vfs.exists(`${selfSrc}/status`) ? vfs.readFile(`${selfSrc}/status`) : "",
385
- );
386
514
  } else {
387
- // Fallback minimal /proc/self
388
- ensureDir(vfs, "/proc/self");
389
- vfs.writeFile("/proc/self/cmdline", "bash\0");
390
- vfs.writeFile("/proc/self/comm", "bash");
391
- vfs.writeFile(
392
- "/proc/self/status",
393
- "Name:\tbash\nState:\tS (sleeping)\nPid:\t1\nPPid:\t0\n",
394
- );
395
- vfs.writeFile("/proc/self/environ", "");
396
- vfs.writeFile("/proc/self/cwd", "/root\0");
397
- vfs.writeFile("/proc/self/exe", "/bin/bash\0");
515
+ // Minimal fallback
516
+ write(vfs, "/proc/self/cmdline", "bash\0");
517
+ write(vfs, "/proc/self/comm", "bash");
518
+ write(vfs, "/proc/self/status", "Name:\tbash\nState:\tS (sleeping)\nPid:\t1\nPPid:\t0\n");
519
+ write(vfs, "/proc/self/environ", "");
520
+ write(vfs, "/proc/self/cwd", "/root\0");
521
+ write(vfs, "/proc/self/exe", "/bin/bash\0");
398
522
  }
399
523
  }
400
524
 
401
525
  // ─── /sys ─────────────────────────────────────────────────────────────────────
402
526
 
403
- function bootstrapSys(vfs: VirtualFileSystem, props: ShellProperties): void {
527
+ function bootstrapSys(vfs: VirtualFileSystem, hostname: string, props: ShellProperties): void {
404
528
  ensureDir(vfs, "/sys");
405
529
  ensureDir(vfs, "/sys/devices");
406
530
  ensureDir(vfs, "/sys/devices/virtual");
407
531
  ensureDir(vfs, "/sys/devices/virtual/dmi");
408
532
  ensureDir(vfs, "/sys/devices/virtual/dmi/id");
409
533
 
410
- ensureFile(
411
- vfs,
412
- "/sys/devices/virtual/dmi/id/sys_vendor",
413
- "Fortune Systems\n",
414
- );
415
- ensureFile(
416
- vfs,
417
- "/sys/devices/virtual/dmi/id/product_name",
418
- "VirtualContainer v1\n",
419
- );
420
- ensureFile(vfs, "/sys/devices/virtual/dmi/id/board_name", "fortune-board\n");
534
+ const seed = fnv1a(hostname);
535
+ const product = `VirtualNode-${(seed % 10000).toString().padStart(4, "0")}`;
536
+
537
+ // Full DMI table — deterministic, seeded from hostname
538
+ const dmi: Record<string, string> = {
539
+ bios_vendor: "Virtual BIOS",
540
+ bios_version: "1.0",
541
+ bios_date: "01/01/2025",
542
+ sys_vendor: "Fortune Systems",
543
+ product_name: product,
544
+ product_family: "VirtualContainer",
545
+ product_version: "v1",
546
+ product_uuid: `${seed.toString(16).padStart(8, "0")}-0000-0000-0000-000000000000`,
547
+ product_serial: `SN-${seed}`,
548
+ chassis_type: "3",
549
+ chassis_vendor: "Virtual",
550
+ chassis_version: "v1",
551
+ board_name: "fortune-board",
552
+ modalias: `dmi:bvnVirtual:bvr1.0:svnFortune:pn${product}`,
553
+ };
554
+
555
+ for (const [k, v] of Object.entries(dmi)) {
556
+ ensureFile(vfs, `/sys/devices/virtual/dmi/id/${k}`, `${v}\n`);
557
+ }
421
558
 
422
559
  ensureDir(vfs, "/sys/class");
423
560
  ensureDir(vfs, "/sys/class/net");
424
-
425
561
  ensureDir(vfs, "/sys/kernel");
426
- ensureFile(vfs, "/sys/kernel/hostname", "fortune-vm\n");
562
+
563
+ ensureFile(vfs, "/sys/kernel/hostname", `${hostname}\n`);
427
564
  ensureFile(vfs, "/sys/kernel/osrelease", `${props.kernel}\n`);
428
565
  ensureFile(vfs, "/sys/kernel/ostype", "Linux\n");
429
566
  }
@@ -432,12 +569,39 @@ function bootstrapSys(vfs: VirtualFileSystem, props: ShellProperties): void {
432
569
 
433
570
  function bootstrapDev(vfs: VirtualFileSystem): void {
434
571
  ensureDir(vfs, "/dev");
435
- ensureFile(vfs, "/dev/null", "", 0o666);
436
- ensureFile(vfs, "/dev/zero", "", 0o666);
437
- ensureFile(vfs, "/dev/random", "", 0o444);
572
+
573
+ // character devices
574
+ ensureFile(vfs, "/dev/null", "", 0o666);
575
+ ensureFile(vfs, "/dev/zero", "", 0o666);
576
+ ensureFile(vfs, "/dev/full", "", 0o666);
577
+ ensureFile(vfs, "/dev/random", "", 0o444);
438
578
  ensureFile(vfs, "/dev/urandom", "", 0o444);
579
+ ensureFile(vfs, "/dev/mem", "", 0o640);
580
+
581
+ // terminal devices
582
+ ensureFile(vfs, "/dev/console", "", 0o600);
583
+ ensureFile(vfs, "/dev/tty", "", 0o666);
584
+ ensureFile(vfs, "/dev/tty0", "", 0o620);
585
+ ensureFile(vfs, "/dev/tty1", "", 0o620);
586
+ ensureFile(vfs, "/dev/ttyS0", "", 0o660);
587
+
588
+ // loop devices
589
+ for (let i = 0; i < 8; i++) {
590
+ ensureFile(vfs, `/dev/loop${i}`, "", 0o660);
591
+ }
592
+ ensureDir(vfs, "/dev/loop-control");
593
+
594
+ // block device stubs (sda + partitions)
595
+ ensureFile(vfs, "/dev/sda", "", 0o660);
596
+ ensureFile(vfs, "/dev/sda1", "", 0o660);
597
+ ensureFile(vfs, "/dev/sda2", "", 0o660);
598
+
599
+ // misc
439
600
  ensureDir(vfs, "/dev/pts");
440
601
  ensureDir(vfs, "/dev/shm");
602
+ ensureFile(vfs, "/dev/stdin", "", 0o666);
603
+ ensureFile(vfs, "/dev/stdout", "", 0o666);
604
+ ensureFile(vfs, "/dev/stderr", "", 0o666);
441
605
  }
442
606
 
443
607
  // ─── /usr ─────────────────────────────────────────────────────────────────────
@@ -456,70 +620,21 @@ function bootstrapUsr(vfs: VirtualFileSystem): void {
456
620
  ensureDir(vfs, "/usr/share/man/man1");
457
621
  ensureDir(vfs, "/usr/lib");
458
622
 
459
- // Stub binaries so `which` can find built-in commands
623
+ // Stubs so `which` can resolve built-in commands
460
624
  const builtins = [
461
- "sh",
462
- "bash",
463
- "ls",
464
- "cat",
465
- "echo",
466
- "grep",
467
- "find",
468
- "sort",
469
- "head",
470
- "tail",
471
- "cut",
472
- "tr",
473
- "sed",
474
- "awk",
475
- "wc",
476
- "tee",
477
- "tar",
478
- "gzip",
479
- "gunzip",
480
- "touch",
481
- "mkdir",
482
- "rm",
483
- "mv",
484
- "cp",
485
- "chmod",
486
- "ln",
487
- "pwd",
488
- "env",
489
- "date",
490
- "sleep",
491
- "id",
492
- "whoami",
493
- "hostname",
494
- "uname",
495
- "ps",
496
- "kill",
497
- "df",
498
- "du",
499
- "curl",
500
- "wget",
501
- "nano",
502
- "diff",
503
- "uniq",
504
- "xargs",
505
- "base64",
625
+ "sh", "bash", "ls", "cat", "echo", "grep", "find", "sort",
626
+ "head", "tail", "cut", "tr", "sed", "awk", "wc", "tee",
627
+ "tar", "gzip", "gunzip", "touch", "mkdir", "rm", "mv", "cp",
628
+ "chmod", "ln", "pwd", "env", "date", "sleep", "id", "whoami",
629
+ "hostname", "uname", "ps", "kill", "df", "du", "curl", "wget",
630
+ "nano", "diff", "uniq", "xargs", "base64",
506
631
  ];
632
+
507
633
  for (const bin of builtins) {
508
- ensureFile(
509
- vfs,
510
- `/usr/bin/${bin}`,
511
- `#!/bin/sh\nexec builtin ${bin} "$@"\n`,
512
- 0o755,
513
- );
634
+ ensureFile(vfs, `/usr/bin/${bin}`, `#!/bin/sh\nexec builtin ${bin} "$@"\n`, 0o755);
514
635
  }
515
636
 
516
- // lsb_release script
517
- ensureFile(
518
- vfs,
519
- "/usr/bin/lsb_release",
520
- '#!/bin/sh\nexec lsb_release "$@"\n',
521
- 0o755,
522
- );
637
+ ensureFile(vfs, "/usr/bin/lsb_release", '#!/bin/sh\nexec lsb_release "$@"\n', 0o755);
523
638
  }
524
639
 
525
640
  // ─── /var ─────────────────────────────────────────────────────────────────────
@@ -527,8 +642,8 @@ function bootstrapUsr(vfs: VirtualFileSystem): void {
527
642
  function bootstrapVar(vfs: VirtualFileSystem): void {
528
643
  ensureDir(vfs, "/var");
529
644
  ensureDir(vfs, "/var/log");
645
+ ensureDir(vfs, "/var/log/apt");
530
646
  ensureDir(vfs, "/var/tmp");
531
- ensureDir(vfs, "/var/run");
532
647
  ensureDir(vfs, "/var/cache");
533
648
  ensureDir(vfs, "/var/cache/apt");
534
649
  ensureDir(vfs, "/var/cache/apt/archives");
@@ -537,39 +652,46 @@ function bootstrapVar(vfs: VirtualFileSystem): void {
537
652
  ensureDir(vfs, "/var/lib/apt/lists");
538
653
  ensureDir(vfs, "/var/lib/dpkg");
539
654
  ensureDir(vfs, "/var/lib/dpkg/info");
655
+ ensureDir(vfs, "/var/lib/misc");
656
+ ensureDir(vfs, "/var/spool");
657
+ ensureDir(vfs, "/var/spool/cron");
658
+ ensureDir(vfs, "/var/mail");
540
659
 
541
- // dpkg status — starts empty, apt install populates it
660
+ // dpkg status — starts empty, VirtualPackageManager populates it
542
661
  ensureFile(vfs, "/var/lib/dpkg/status", "");
543
662
  ensureFile(vfs, "/var/lib/dpkg/available", "");
544
663
 
545
- // syslog stub
546
- ensureFile(
547
- vfs,
548
- "/var/log/syslog",
549
- `${new Date().toUTCString()} fortune kernel: Virtual container started\n`,
550
- );
664
+ // syslog stubs
665
+ ensureFile(vfs, "/var/log/syslog", `${new Date().toUTCString()} fortune kernel: Virtual container started\n`);
551
666
  ensureFile(vfs, "/var/log/auth.log", "");
667
+ ensureFile(vfs, "/var/log/kern.log", "");
552
668
  ensureFile(vfs, "/var/log/dpkg.log", "");
553
669
  ensureFile(vfs, "/var/log/apt/history.log", "");
554
670
  ensureFile(vfs, "/var/log/apt/term.log", "");
671
+
672
+ // /run — systemd tmpfs runtime dir (canonical on modern Debian)
673
+ // /var/run is a legacy symlink to /run
674
+ ensureDir(vfs, "/run");
675
+ ensureDir(vfs, "/run/lock");
676
+ ensureDir(vfs, "/run/systemd");
677
+ ensureDir(vfs, "/run/user");
678
+ ensureFile(vfs, "/run/utmp", "");
555
679
  }
556
680
 
557
681
  // ─── /bin + /sbin symlinks ────────────────────────────────────────────────────
558
682
 
559
683
  function bootstrapBin(vfs: VirtualFileSystem): void {
560
- // On modern Debian/Ubuntu /bin is a symlink to /usr/bin
561
- if (!vfs.exists("/bin")) {
562
- vfs.symlink("/usr/bin", "/bin");
563
- }
564
- if (!vfs.exists("/sbin")) {
565
- vfs.symlink("/usr/sbin", "/sbin");
566
- }
567
- if (!vfs.exists("/lib")) {
568
- ensureDir(vfs, "/lib");
569
- }
570
- if (!vfs.exists("/lib64")) {
571
- ensureDir(vfs, "/lib64");
572
- }
684
+ // Modern Debian: /bin and /sbin are symlinks to /usr/bin and /usr/sbin
685
+ if (!vfs.exists("/bin")) vfs.symlink("/usr/bin", "/bin");
686
+ if (!vfs.exists("/sbin")) vfs.symlink("/usr/sbin", "/sbin");
687
+
688
+ // /var/run → /run (systemd compat)
689
+ if (!vfs.exists("/var/run")) vfs.symlink("/run", "/var/run");
690
+
691
+ ensureDir(vfs, "/lib");
692
+ ensureDir(vfs, "/lib64");
693
+ ensureDir(vfs, "/lib/x86_64-linux-gnu");
694
+ ensureDir(vfs, "/lib/modules");
573
695
  }
574
696
 
575
697
  // ─── /tmp ─────────────────────────────────────────────────────────────────────
@@ -578,13 +700,13 @@ function bootstrapTmp(vfs: VirtualFileSystem): void {
578
700
  ensureDir(vfs, "/tmp", 0o1777);
579
701
  }
580
702
 
581
- // ─── /root ────────────────────────────────────────────────────────────────────
703
+ // ─── /root home ───────────────────────────────────────────────────────────────
582
704
 
583
705
  function bootstrapRoot(vfs: VirtualFileSystem): void {
584
706
  ensureDir(vfs, "/root", 0o700);
585
707
  ensureFile(
586
708
  vfs,
587
- "/home/root/.bashrc",
709
+ "/root/.bashrc",
588
710
  `${[
589
711
  "# root .bashrc",
590
712
  "export PS1='\\[\\033[0;31m\\]\\u@\\h\\[\\033[0m\\]:\\[\\033[0;34m\\]\\w\\[\\033[0m\\]# '",
@@ -593,20 +715,55 @@ function bootstrapRoot(vfs: VirtualFileSystem): void {
593
715
  "alias la='ls -A'",
594
716
  ].join("\n")}\n`,
595
717
  );
596
- ensureFile(vfs, "/home/root/.profile", "[ -f ~/.bashrc ] && . ~/.bashrc\n");
597
- // Fix: /home/root should map to /root for root user
598
- // if (!vfs.exists("/home/root")) {
599
- // vfs.symlink("/root", "/home/root");
600
- // }
718
+ ensureFile(vfs, "/root/.profile", "[ -f ~/.bashrc ] && . ~/.bashrc\n");
601
719
  }
602
720
 
603
- // ─── /opt + /srv + /mnt + /media ─────────────────────────────────────────────
721
+ // ─── /opt /srv /mnt /media /home ─────────────────────────────────────────────
604
722
 
605
- function bootstrapMisc(vfs: VirtualFileSystem): void {
723
+ function bootstrapMisc(vfs: VirtualFileSystem, props: ShellProperties): void {
606
724
  ensureDir(vfs, "/opt");
607
725
  ensureDir(vfs, "/srv");
608
726
  ensureDir(vfs, "/mnt");
609
727
  ensureDir(vfs, "/media");
728
+ ensureDir(vfs, "/home");
729
+
730
+ // /boot — grub + kernel images
731
+ ensureDir(vfs, "/boot");
732
+ ensureDir(vfs, "/boot/grub");
733
+ ensureDir(vfs, "/boot/grub/grub.cfg.d");
734
+ ensureFile(
735
+ vfs,
736
+ "/boot/grub/grub.cfg",
737
+ `${[
738
+ "# GRUB configuration (virtual)",
739
+ "set default=0",
740
+ "set timeout=5",
741
+ "",
742
+ `menuentry "Fortune GNU/Linux" {`,
743
+ ` linux /vmlinuz root=/dev/sda2 ro quiet splash`,
744
+ ` initrd /initrd.img`,
745
+ `}`,
746
+ ].join("\n")}\n`,
747
+ );
748
+ // kernel + initrd stubs in /boot
749
+ const kver = props.kernel;
750
+ ensureFile(vfs, `/boot/vmlinuz-${kver}`, "", 0o644);
751
+ ensureFile(vfs, `/boot/initrd.img-${kver}`, "", 0o644);
752
+ ensureFile(vfs, `/boot/System.map-${kver}`, `${kver} virtual\n`, 0o644);
753
+ ensureFile(vfs, `/boot/config-${kver}`, `# Linux kernel config ${kver}\n`, 0o644);
754
+
755
+ // root-level symlinks (Debian convention)
756
+ if (!vfs.exists("/vmlinuz")) vfs.symlink(`/boot/vmlinuz-${kver}`, "/vmlinuz");
757
+ if (!vfs.exists("/vmlinuz.old")) vfs.symlink(`/boot/vmlinuz-${kver}`, "/vmlinuz.old");
758
+ if (!vfs.exists("/initrd.img")) vfs.symlink(`/boot/initrd.img-${kver}`, "/initrd.img");
759
+ if (!vfs.exists("/initrd.img.old")) vfs.symlink(`/boot/initrd.img-${kver}`, "/initrd.img.old");
760
+
761
+ // /snap — snapd mount namespace root
762
+ ensureDir(vfs, "/snap");
763
+ ensureDir(vfs, "/snap/bin");
764
+
765
+ // /lost+found — ext4 fsck recovery dir (mode 0o700, root only)
766
+ ensureDir(vfs, "/lost+found", 0o700);
610
767
  }
611
768
 
612
769
  // ─── main entry point ─────────────────────────────────────────────────────────
@@ -615,11 +772,12 @@ function bootstrapMisc(vfs: VirtualFileSystem): void {
615
772
  * Bootstraps the full Linux rootfs hierarchy in the VFS.
616
773
  * Safe to call multiple times — idempotent.
617
774
  *
618
- * @param vfs Target virtual filesystem.
619
- * @param users User manager (for /etc/passwd sync).
620
- * @param hostname Virtual hostname.
621
- * @param props Shell properties (kernel, os, arch).
775
+ * @param vfs Target virtual filesystem.
776
+ * @param users User manager (for /etc/passwd sync).
777
+ * @param hostname Virtual hostname.
778
+ * @param props Shell properties (kernel, os, arch).
622
779
  * @param shellStartTime Unix ms of shell creation (for uptime).
780
+ * @param sessions Active sessions (for /proc/<pid> population).
623
781
  */
624
782
  export function bootstrapLinuxRootfs(
625
783
  vfs: VirtualFileSystem,
@@ -627,16 +785,47 @@ export function bootstrapLinuxRootfs(
627
785
  hostname: string,
628
786
  props: ShellProperties,
629
787
  shellStartTime: number,
788
+ sessions: VirtualActiveSession[] = [],
630
789
  ): void {
631
790
  bootstrapEtc(vfs, hostname, props);
632
- bootstrapSys(vfs, props);
791
+ bootstrapSys(vfs, hostname, props);
633
792
  bootstrapDev(vfs);
634
793
  bootstrapUsr(vfs);
635
794
  bootstrapVar(vfs);
636
795
  bootstrapBin(vfs);
637
796
  bootstrapTmp(vfs);
638
797
  bootstrapRoot(vfs);
639
- bootstrapMisc(vfs);
640
- refreshProc(vfs, props, hostname, shellStartTime, []);
798
+ bootstrapMisc(vfs, props);
799
+ bootProcLog(vfs, props);
800
+ refreshProc(vfs, props, hostname, shellStartTime, sessions);
641
801
  syncEtcPasswd(vfs, users);
642
802
  }
803
+
804
+ // ─── optional live engine ─────────────────────────────────────────────────────
805
+
806
+ /**
807
+ * Engine for runtimes that want periodic /proc refresh (e.g. web shell
808
+ * with live `top`/`ps` output). Call `.boot()` once, then `.tick()` on
809
+ * each session change or on a timer.
810
+ *
811
+ * ```ts
812
+ * const engine = createLinuxRootfsEngine(vfs, props, hostname, Date.now());
813
+ * engine.boot(users, sessions);
814
+ * setInterval(() => engine.tick(shell.listActiveSessions()), 5000);
815
+ * ```
816
+ */
817
+ export function createLinuxRootfsEngine(
818
+ vfs: VirtualFileSystem,
819
+ props: ShellProperties,
820
+ hostname: string,
821
+ startTime: number,
822
+ ) {
823
+ return {
824
+ boot(users: VirtualUserManager, sessions: VirtualActiveSession[] = []) {
825
+ bootstrapLinuxRootfs(vfs, users, hostname, props, startTime, sessions);
826
+ },
827
+ tick(sessions: VirtualActiveSession[] = []) {
828
+ refreshProc(vfs, props, hostname, startTime, sessions);
829
+ },
830
+ };
831
+ }