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,10 +1,17 @@
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
  import * as os from "node:os";
10
17
  // ─── helpers ────────────────────────────────────────────────────────────────
@@ -16,6 +23,18 @@ function ensureFile(vfs, path, content, mode = 0o644) {
16
23
  if (!vfs.exists(path))
17
24
  vfs.writeFile(path, content, { mode });
18
25
  }
26
+ function write(vfs, path, content) {
27
+ vfs.writeFile(path, content);
28
+ }
29
+ /** FNV-1a 32-bit — deterministic seed from any string */
30
+ function fnv1a(str) {
31
+ let h = 2166136261;
32
+ for (let i = 0; i < str.length; i++) {
33
+ h ^= str.charCodeAt(i);
34
+ h = Math.imul(h, 16777619);
35
+ }
36
+ return h >>> 0;
37
+ }
19
38
  // ─── /etc ────────────────────────────────────────────────────────────────────
20
39
  function bootstrapEtc(vfs, hostname, props) {
21
40
  ensureDir(vfs, "/etc");
@@ -36,7 +55,7 @@ function bootstrapEtc(vfs, hostname, props) {
36
55
  "export PATH=/usr/local/bin:/usr/bin:/bin",
37
56
  "export PS1='\\u@\\h:\\w\\$ '",
38
57
  ].join("\n")}\n`);
39
- ensureFile(vfs, "/etc/issue", `Fortune GNU/Linux 1.0 \\n \\l\n`);
58
+ ensureFile(vfs, "/etc/issue", "Fortune GNU/Linux 1.0 \\n \\l\n");
40
59
  ensureFile(vfs, "/etc/motd", ["", `Welcome to ${props.os}`, `Kernel: ${props.kernel}`, ""].join("\n"));
41
60
  // APT sources
42
61
  ensureDir(vfs, "/etc/apt");
@@ -46,7 +65,7 @@ function bootstrapEtc(vfs, hostname, props) {
46
65
  "deb [virtual] fortune://packages.fortune.local aurora main contrib",
47
66
  "deb [virtual] fortune://security.fortune.local aurora-security main",
48
67
  ].join("\n")}\n`);
49
- // network stubs
68
+ // network
50
69
  ensureDir(vfs, "/etc/network");
51
70
  ensureFile(vfs, "/etc/network/interfaces", `${[
52
71
  "auto lo",
@@ -65,13 +84,56 @@ function bootstrapEtc(vfs, hostname, props) {
65
84
  ensureDir(vfs, "/etc/init.d");
66
85
  ensureDir(vfs, "/etc/systemd");
67
86
  ensureDir(vfs, "/etc/systemd/system");
87
+ // fstab
88
+ ensureFile(vfs, "/etc/fstab", `${[
89
+ "# <file system> <mount point> <type> <options> <dump> <pass>",
90
+ "UUID=00000000-0000-0000-0000-000000000001 / ext4 errors=remount-ro 0 1",
91
+ "UUID=00000000-0000-0000-0000-000000000002 /boot ext4 defaults 0 2",
92
+ "UUID=00000000-0000-0000-0000-000000000003 none swap sw 0 0",
93
+ "tmpfs /tmp tmpfs defaults,noatime 0 0",
94
+ "tmpfs /run tmpfs defaults,noatime 0 0",
95
+ ].join("\n")}\n`);
96
+ // login.defs — useradd/passwd defaults
97
+ ensureFile(vfs, "/etc/login.defs", `${[
98
+ "MAIL_DIR /var/mail",
99
+ "PASS_MAX_DAYS 99999",
100
+ "PASS_MIN_DAYS 0",
101
+ "PASS_WARN_AGE 7",
102
+ "UID_MIN 1000",
103
+ "UID_MAX 60000",
104
+ "GID_MIN 1000",
105
+ "GID_MAX 60000",
106
+ "CREATE_HOME yes",
107
+ "UMASK 022",
108
+ "USERGROUPS_ENAB yes",
109
+ "ENCRYPT_METHOD SHA512",
110
+ ].join("\n")}\n`);
111
+ // security + pam
112
+ ensureDir(vfs, "/etc/security");
113
+ ensureFile(vfs, "/etc/security/limits.conf", "# /etc/security/limits.conf\n");
114
+ ensureFile(vfs, "/etc/security/access.conf", "# /etc/security/access.conf\n");
115
+ ensureDir(vfs, "/etc/pam.d");
116
+ ensureFile(vfs, "/etc/pam.d/common-auth", "auth required pam_unix.so\n");
117
+ ensureFile(vfs, "/etc/pam.d/common-account", "account required pam_unix.so\n");
118
+ ensureFile(vfs, "/etc/pam.d/common-password", "password required pam_unix.so obscure sha512\n");
119
+ ensureFile(vfs, "/etc/pam.d/common-session", "session required pam_unix.so\n");
120
+ ensureFile(vfs, "/etc/pam.d/sshd", "@include common-auth\n@include common-account\n@include common-session\n");
121
+ // sudo config
122
+ ensureDir(vfs, "/etc/sudoers.d");
123
+ ensureFile(vfs, "/etc/sudoers", "root ALL=(ALL:ALL) ALL\n%sudo ALL=(ALL:ALL) ALL\n", 0o440);
124
+ // ld
125
+ ensureFile(vfs, "/etc/ld.so.conf", "include /etc/ld.so.conf.d/*.conf\n");
126
+ ensureDir(vfs, "/etc/ld.so.conf.d");
127
+ 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");
128
+ // locale + timezone
129
+ ensureFile(vfs, "/etc/locale.conf", "LANG=en_US.UTF-8\n");
130
+ ensureFile(vfs, "/etc/timezone", "UTC\n");
131
+ ensureFile(vfs, "/etc/localtime", "UTC\n");
68
132
  }
69
133
  // ─── /etc/passwd + /etc/group + /etc/shadow ─────────────────────────────────
70
134
  /**
71
135
  * Sync `/etc/passwd`, `/etc/group`, and `/etc/shadow` from the
72
136
  * VirtualUserManager's current user list into the VFS.
73
- * @param vfs VirtualFileSystem instance to write files into
74
- * @param users VirtualUserManager to source users from
75
137
  */
76
138
  export function syncEtcPasswd(vfs, users) {
77
139
  const userList = users.listUsers();
@@ -97,7 +159,7 @@ export function syncEtcPasswd(vfs, users) {
97
159
  "nogroup:x:65534:",
98
160
  ];
99
161
  vfs.writeFile("/etc/group", `${groupLines.join("\n")}\n`);
100
- // shadow — fake hashes, never real
162
+ // shadow — fake hashes, never real credentials
101
163
  const shadowLines = [
102
164
  "root:*:19000:0:99999:7:::",
103
165
  "daemon:*:19000:0:99999:7:::",
@@ -109,8 +171,8 @@ export function syncEtcPasswd(vfs, users) {
109
171
  }
110
172
  vfs.writeFile("/etc/shadow", `${shadowLines.join("\n")}\n`, { mode: 0o640 });
111
173
  }
112
- // ─── /proc ───────────────────────────────────────────────────────────────────
113
- /** Derive a stable virtual PID from a tty string like "pts/0" → 1000, "pts/1" → 1001 */
174
+ // ─── /proc helpers ───────────────────────────────────────────────────────────
175
+ /** Derive a stable virtual PID from a tty string e.g. "pts/0" → 1000 */
114
176
  function ttyToPid(tty) {
115
177
  const match = tty.match(/(\d+)$/);
116
178
  return 1000 + (match?.[1] ? parseInt(match[1], 10) : 0);
@@ -122,10 +184,11 @@ function writeProcPid(vfs, pid, username, _tty, cmdline, startedAt, env) {
122
184
  ensureDir(vfs, `${dir}/fd`);
123
185
  ensureDir(vfs, `${dir}/fdinfo`);
124
186
  const uptimeSec = Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000);
125
- vfs.writeFile(`${dir}/cmdline`, `${cmdline.replace(/\s+/g, "\0")}\0`);
126
- vfs.writeFile(`${dir}/comm`, cmdline.split(/\s+/)[0] ?? "bash");
127
- vfs.writeFile(`${dir}/status`, `${[
128
- `Name: ${cmdline.split(/\s+/)[0] ?? "bash"}`,
187
+ const comm = cmdline.split(/\s+/)[0] ?? "bash";
188
+ write(vfs, `${dir}/cmdline`, `${cmdline.replace(/\s+/g, "\0")}\0`);
189
+ write(vfs, `${dir}/comm`, comm);
190
+ write(vfs, `${dir}/status`, `${[
191
+ `Name: ${comm}`,
129
192
  `State: S (sleeping)`,
130
193
  `Pid: ${pid}`,
131
194
  `PPid: 1`,
@@ -135,35 +198,45 @@ function writeProcPid(vfs, pid, username, _tty, cmdline, startedAt, env) {
135
198
  `VmSize: 16384 kB`,
136
199
  `Threads: 1`,
137
200
  ].join("\n")}\n`);
138
- vfs.writeFile(`${dir}/stat`, `${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`);
139
- vfs.writeFile(`${dir}/environ`, `${Object.entries(env)
140
- .map(([k, v]) => `${k}=${v}`)
141
- .join("\0")}\0`);
142
- vfs.writeFile(`${dir}/cwd`, `/home/${username}\0`);
143
- vfs.writeFile(`${dir}/exe`, "/bin/bash\0");
144
- // Standard fd entries
145
- vfs.writeFile(`${dir}/fd/0`, "");
146
- vfs.writeFile(`${dir}/fd/1`, "");
147
- vfs.writeFile(`${dir}/fd/2`, "");
201
+ write(vfs, `${dir}/stat`, `${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`);
202
+ write(vfs, `${dir}/environ`, `${Object.entries(env).map(([k, v]) => `${k}=${v}`).join("\0")}\0`);
203
+ write(vfs, `${dir}/cwd`, `/home/${username}\0`);
204
+ write(vfs, `${dir}/exe`, "/bin/bash\0");
205
+ // Standard fd stubs (stdin/stdout/stderr)
206
+ for (const fd of ["0", "1", "2"]) {
207
+ ensureFile(vfs, `${dir}/fd/${fd}`, "");
208
+ }
209
+ }
210
+ // ─── /proc boot log ──────────────────────────────────────────────────────────
211
+ function bootProcLog(vfs, props) {
212
+ ensureDir(vfs, "/proc/boot");
213
+ ensureFile(vfs, "/proc/boot/log", `${[
214
+ "[ 0.000000] Linux virtual kernel booting...",
215
+ "[ 0.000120] init memory subsystem",
216
+ "[ 0.000240] mount /proc /sys /dev",
217
+ "[ 0.000420] start init",
218
+ "[ 0.000680] system ready",
219
+ ].join("\n")}\n`);
220
+ ensureFile(vfs, "/proc/boot/version", `Linux ${props.kernel} (virtual)\n`);
148
221
  }
222
+ // ─── /proc refresh ───────────────────────────────────────────────────────────
149
223
  /**
150
224
  * Populate and refresh `/proc` virtual entries based on host stats and
151
- * provided active sessions. Rewrites `/proc/uptime`, `/proc/meminfo`,
152
- * `/proc/cpuinfo`, `/proc/<pid>` entries and `/proc/self` content.
153
- * @param vfs VirtualFileSystem instance
154
- * @param props ShellProperties used for version strings
155
- * @param hostname Hostname to write into /proc/hostname
156
- * @param shellStartTime Start time used to compute uptime
157
- * @param sessions Optional active sessions list to populate per-pid entries
225
+ * provided active sessions. Rewrites uptime, meminfo, cpuinfo, loadavg,
226
+ * per-pid entries, and /proc/self.
227
+ *
228
+ * Safe to call repeatedly acts as a live kernel state snapshot.
158
229
  */
159
- export function refreshProc(vfs, props, hostname, shellStartTime, sessions) {
230
+ export function refreshProc(vfs, props, hostname, shellStartTime, sessions = []) {
160
231
  ensureDir(vfs, "/proc");
161
232
  const uptimeSec = Math.floor((Date.now() - shellStartTime) / 1000);
162
- vfs.writeFile("/proc/uptime", `${uptimeSec}.00 ${Math.floor(uptimeSec * 0.9)}.00\n`);
233
+ const idleSec = Math.floor(uptimeSec * 0.9);
234
+ write(vfs, "/proc/uptime", `${uptimeSec}.00 ${idleSec}.00\n`);
235
+ // meminfo — real host values, Linux-compatible format
163
236
  const totalMemKb = Math.floor(os.totalmem() / 1024);
164
237
  const freeMemKb = Math.floor(os.freemem() / 1024);
165
238
  const availMemKb = Math.floor(freeMemKb * 0.95);
166
- vfs.writeFile("/proc/meminfo", `${[
239
+ write(vfs, "/proc/meminfo", `${[
167
240
  `MemTotal: ${String(totalMemKb).padStart(10)} kB`,
168
241
  `MemFree: ${String(freeMemKb).padStart(10)} kB`,
169
242
  `MemAvailable: ${String(availMemKb).padStart(10)} kB`,
@@ -172,22 +245,22 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions) {
172
245
  `SwapTotal: ${String(Math.floor(totalMemKb * 0.5)).padStart(10)} kB`,
173
246
  `SwapFree: ${String(Math.floor(totalMemKb * 0.5)).padStart(10)} kB`,
174
247
  ].join("\n")}\n`);
248
+ // cpuinfo — real host CPU passthrough
175
249
  const cpus = os.cpus();
176
250
  const cpuLines = [];
177
251
  for (let i = 0; i < cpus.length; i++) {
178
252
  const c = cpus[i];
179
253
  if (!c)
180
254
  continue;
181
- const mhz = c.speed.toFixed(3);
182
- cpuLines.push(`processor\t: ${i}`, `model name\t: ${c.model}`, `cpu MHz\t\t: ${mhz}`, `cache size\t: 8192 KB`, "");
255
+ cpuLines.push(`processor\t: ${i}`, `model name\t: ${c.model}`, `cpu MHz\t\t: ${c.speed.toFixed(3)}`, `cache size\t: 8192 KB`, "");
183
256
  }
184
- vfs.writeFile("/proc/cpuinfo", `${cpuLines.join("\n")}\n`);
185
- vfs.writeFile("/proc/version", `Linux version ${props.kernel} (fortune@build) (gcc version 12.2.0) #1 SMP\n`);
186
- vfs.writeFile("/proc/hostname", `${hostname}\n`);
187
- // /proc/loadavg
257
+ write(vfs, "/proc/cpuinfo", `${cpuLines.join("\n")}\n`);
258
+ write(vfs, "/proc/version", `Linux version ${props.kernel} (fortune@build) (gcc version 12.2.0) #1 SMP\n`);
259
+ write(vfs, "/proc/hostname", `${hostname}\n`);
260
+ // loadavg — slightly random but bounded
188
261
  const load = (Math.random() * 0.5).toFixed(2);
189
- const numProcs = 1 + (sessions?.length ?? 0);
190
- vfs.writeFile("/proc/loadavg", `${load} ${load} ${load} ${numProcs}/${numProcs} 1\n`);
262
+ const numProcs = 1 + sessions.length;
263
+ write(vfs, "/proc/loadavg", `${load} ${load} ${load} ${numProcs}/${numProcs} 1\n`);
191
264
  // /proc/net stubs
192
265
  ensureDir(vfs, "/proc/net");
193
266
  ensureFile(vfs, "/proc/net/dev", `${[
@@ -196,11 +269,60 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions) {
196
269
  " lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
197
270
  " eth0: 131072 1024 0 0 0 0 0 0 65536 512 0 0 0 0 0 0",
198
271
  ].join("\n")}\n`);
199
- // ── /proc/1 — init process ────────────────────────────────────────────────
272
+ ensureFile(vfs, "/proc/net/if_inet6", "");
273
+ ensureFile(vfs, "/proc/net/tcp", " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
274
+ ensureFile(vfs, "/proc/net/tcp6", " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
275
+ // /proc/cmdline — kernel boot args
276
+ write(vfs, "/proc/cmdline", `BOOT_IMAGE=/boot/vmlinuz-${props.kernel} root=/dev/sda2 ro quiet splash\n`);
277
+ // /proc/filesystems
278
+ ensureFile(vfs, "/proc/filesystems", `${["nodev\tsysfs", "nodev\ttmpfs", "nodev\tproc", "nodev\tdevtmpfs", "\text4", "\tvfat", "nodev\tsquashfs", "nodev\toverlay"].join("\n")}\n`);
279
+ // /proc/mounts (= /proc/self/mounts)
280
+ const mountsContent = `${[
281
+ "sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0",
282
+ "proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0",
283
+ "devtmpfs /dev devtmpfs rw,nosuid,size=8192k,nr_inodes=4096,mode=755 0 0",
284
+ "tmpfs /run tmpfs rw,nosuid,nodev,noexec,relatime,size=204800k,mode=755 0 0",
285
+ "tmpfs /tmp tmpfs rw,nosuid,nodev,noatime 0 0",
286
+ "/dev/sda2 / ext4 rw,relatime,errors=remount-ro 0 0",
287
+ "/dev/sda1 /boot ext4 rw,relatime 0 0",
288
+ "tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0",
289
+ "devpts /dev/pts devpts rw,nosuid,noexec,relatime,mode=620,ptmxmode=000 0 0",
290
+ "squashfs /snap/core squashfs ro,nodev,relatime 0 0",
291
+ ].join("\n")}\n`;
292
+ write(vfs, "/proc/mounts", mountsContent);
293
+ ensureDir(vfs, "/proc/self");
294
+ write(vfs, "/proc/self/mounts", mountsContent);
295
+ // /proc/partitions
296
+ write(vfs, "/proc/partitions", `${[
297
+ "major minor #blocks name",
298
+ "",
299
+ " 8 0 41943040 sda",
300
+ " 8 1 524288 sda1",
301
+ " 8 2 41417216 sda2",
302
+ " 7 0 10485760 loop0",
303
+ ].join("\n")}\n`);
304
+ // /proc/swaps
305
+ write(vfs, "/proc/swaps", "Filename\t\t\t\tType\t\tSize\t\tUsed\t\tPriority\n" +
306
+ `/dev/sda3\t\t\t\tpartition\t${Math.floor(os.totalmem() / 2048)}\t\t0\t\t-2\n`);
307
+ // /proc/sys — sysctl virtual tree
308
+ ensureDir(vfs, "/proc/sys");
309
+ ensureDir(vfs, "/proc/sys/kernel");
310
+ ensureDir(vfs, "/proc/sys/net");
311
+ ensureDir(vfs, "/proc/sys/vm");
312
+ ensureFile(vfs, "/proc/sys/kernel/hostname", `${hostname}\n`);
313
+ ensureFile(vfs, "/proc/sys/kernel/ostype", "Linux\n");
314
+ ensureFile(vfs, "/proc/sys/kernel/osrelease", `${props.kernel}\n`);
315
+ ensureFile(vfs, "/proc/sys/kernel/pid_max", "32768\n");
316
+ ensureFile(vfs, "/proc/sys/kernel/threads-max", "65536\n");
317
+ ensureFile(vfs, "/proc/sys/kernel/randomize_va_space", "2\n");
318
+ ensureFile(vfs, "/proc/sys/kernel/dmesg_restrict", "0\n");
319
+ ensureFile(vfs, "/proc/sys/net/ipv4/ip_forward", "0\n");
320
+ ensureFile(vfs, "/proc/sys/vm/swappiness", "60\n");
321
+ ensureFile(vfs, "/proc/sys/vm/overcommit_memory", "0\n");
322
+ // init process (PID 1)
200
323
  writeProcPid(vfs, 1, "root", "pts/0", "/sbin/init", new Date(shellStartTime).toISOString(), {});
201
- // ── /proc/<pid> per session ───────────────────────────────────────────────
202
- const activeSessions = sessions ?? [];
203
- for (const session of activeSessions) {
324
+ // per-session processes
325
+ for (const session of sessions) {
204
326
  const pid = ttyToPid(session.tty);
205
327
  writeProcPid(vfs, pid, session.username, session.tty, "bash", session.startedAt, {
206
328
  USER: session.username,
@@ -209,75 +331,106 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions) {
209
331
  SHELL: "/bin/bash",
210
332
  });
211
333
  }
212
- // ── /proc/self — symlink to current session PID or 1 ────────────────────
213
- // We can't know which session is "current" at populate time,
214
- // so /proc/self is a directory that mirrors the most recent session,
215
- // or init if no sessions. Commands that read /proc/self get consistent data.
216
- const selfPid = activeSessions.length > 0
217
- ? ttyToPid(activeSessions[activeSessions.length - 1].tty)
218
- : 1;
219
- // Remove existing /proc/self and recreate as content copy
334
+ // /proc/self — mirror of most recent session, or init
335
+ const selfPid = sessions.length > 0 ? ttyToPid(sessions[sessions.length - 1].tty) : 1;
220
336
  if (vfs.exists("/proc/self")) {
221
337
  try {
222
338
  vfs.remove("/proc/self");
223
339
  }
224
- catch { }
340
+ catch { /* ignore */ }
225
341
  }
226
- // /proc/self is a real directory (not a symlink, which VFS may not support for dirs)
227
342
  const selfSrc = `/proc/${selfPid}`;
343
+ ensureDir(vfs, "/proc/self");
344
+ ensureDir(vfs, "/proc/self/fd");
228
345
  if (vfs.exists(selfSrc)) {
229
- ensureDir(vfs, "/proc/self");
230
- ensureDir(vfs, "/proc/self/fd");
231
346
  for (const entry of vfs.list(selfSrc)) {
232
347
  const srcPath = `${selfSrc}/${entry}`;
233
348
  const dstPath = `/proc/self/${entry}`;
234
349
  try {
235
350
  const st = vfs.stat(srcPath);
236
- if (st.type === "file") {
237
- vfs.writeFile(dstPath, vfs.readFile(srcPath));
238
- }
351
+ if (st.type === "file")
352
+ write(vfs, dstPath, vfs.readFile(srcPath));
239
353
  }
240
- catch { }
354
+ catch { /* skip unreadable entries */ }
241
355
  }
242
- vfs.writeFile("/proc/self/status", vfs.exists(`${selfSrc}/status`) ? vfs.readFile(`${selfSrc}/status`) : "");
243
356
  }
244
357
  else {
245
- // Fallback minimal /proc/self
246
- ensureDir(vfs, "/proc/self");
247
- vfs.writeFile("/proc/self/cmdline", "bash\0");
248
- vfs.writeFile("/proc/self/comm", "bash");
249
- vfs.writeFile("/proc/self/status", "Name:\tbash\nState:\tS (sleeping)\nPid:\t1\nPPid:\t0\n");
250
- vfs.writeFile("/proc/self/environ", "");
251
- vfs.writeFile("/proc/self/cwd", "/root\0");
252
- vfs.writeFile("/proc/self/exe", "/bin/bash\0");
358
+ // Minimal fallback
359
+ write(vfs, "/proc/self/cmdline", "bash\0");
360
+ write(vfs, "/proc/self/comm", "bash");
361
+ write(vfs, "/proc/self/status", "Name:\tbash\nState:\tS (sleeping)\nPid:\t1\nPPid:\t0\n");
362
+ write(vfs, "/proc/self/environ", "");
363
+ write(vfs, "/proc/self/cwd", "/root\0");
364
+ write(vfs, "/proc/self/exe", "/bin/bash\0");
253
365
  }
254
366
  }
255
367
  // ─── /sys ─────────────────────────────────────────────────────────────────────
256
- function bootstrapSys(vfs, props) {
368
+ function bootstrapSys(vfs, hostname, props) {
257
369
  ensureDir(vfs, "/sys");
258
370
  ensureDir(vfs, "/sys/devices");
259
371
  ensureDir(vfs, "/sys/devices/virtual");
260
372
  ensureDir(vfs, "/sys/devices/virtual/dmi");
261
373
  ensureDir(vfs, "/sys/devices/virtual/dmi/id");
262
- ensureFile(vfs, "/sys/devices/virtual/dmi/id/sys_vendor", "Fortune Systems\n");
263
- ensureFile(vfs, "/sys/devices/virtual/dmi/id/product_name", "VirtualContainer v1\n");
264
- ensureFile(vfs, "/sys/devices/virtual/dmi/id/board_name", "fortune-board\n");
374
+ const seed = fnv1a(hostname);
375
+ const product = `VirtualNode-${(seed % 10000).toString().padStart(4, "0")}`;
376
+ // Full DMI table — deterministic, seeded from hostname
377
+ const dmi = {
378
+ bios_vendor: "Virtual BIOS",
379
+ bios_version: "1.0",
380
+ bios_date: "01/01/2025",
381
+ sys_vendor: "Fortune Systems",
382
+ product_name: product,
383
+ product_family: "VirtualContainer",
384
+ product_version: "v1",
385
+ product_uuid: `${seed.toString(16).padStart(8, "0")}-0000-0000-0000-000000000000`,
386
+ product_serial: `SN-${seed}`,
387
+ chassis_type: "3",
388
+ chassis_vendor: "Virtual",
389
+ chassis_version: "v1",
390
+ board_name: "fortune-board",
391
+ modalias: `dmi:bvnVirtual:bvr1.0:svnFortune:pn${product}`,
392
+ };
393
+ for (const [k, v] of Object.entries(dmi)) {
394
+ ensureFile(vfs, `/sys/devices/virtual/dmi/id/${k}`, `${v}\n`);
395
+ }
265
396
  ensureDir(vfs, "/sys/class");
266
397
  ensureDir(vfs, "/sys/class/net");
267
398
  ensureDir(vfs, "/sys/kernel");
268
- ensureFile(vfs, "/sys/kernel/hostname", "fortune-vm\n");
399
+ ensureFile(vfs, "/sys/kernel/hostname", `${hostname}\n`);
269
400
  ensureFile(vfs, "/sys/kernel/osrelease", `${props.kernel}\n`);
270
401
  ensureFile(vfs, "/sys/kernel/ostype", "Linux\n");
271
402
  }
272
403
  // ─── /dev ─────────────────────────────────────────────────────────────────────
273
404
  function bootstrapDev(vfs) {
274
405
  ensureDir(vfs, "/dev");
406
+ // character devices
275
407
  ensureFile(vfs, "/dev/null", "", 0o666);
276
408
  ensureFile(vfs, "/dev/zero", "", 0o666);
409
+ ensureFile(vfs, "/dev/full", "", 0o666);
277
410
  ensureFile(vfs, "/dev/random", "", 0o444);
278
411
  ensureFile(vfs, "/dev/urandom", "", 0o444);
412
+ ensureFile(vfs, "/dev/mem", "", 0o640);
413
+ // terminal devices
414
+ ensureFile(vfs, "/dev/console", "", 0o600);
415
+ ensureFile(vfs, "/dev/tty", "", 0o666);
416
+ ensureFile(vfs, "/dev/tty0", "", 0o620);
417
+ ensureFile(vfs, "/dev/tty1", "", 0o620);
418
+ ensureFile(vfs, "/dev/ttyS0", "", 0o660);
419
+ // loop devices
420
+ for (let i = 0; i < 8; i++) {
421
+ ensureFile(vfs, `/dev/loop${i}`, "", 0o660);
422
+ }
423
+ ensureDir(vfs, "/dev/loop-control");
424
+ // block device stubs (sda + partitions)
425
+ ensureFile(vfs, "/dev/sda", "", 0o660);
426
+ ensureFile(vfs, "/dev/sda1", "", 0o660);
427
+ ensureFile(vfs, "/dev/sda2", "", 0o660);
428
+ // misc
279
429
  ensureDir(vfs, "/dev/pts");
280
430
  ensureDir(vfs, "/dev/shm");
431
+ ensureFile(vfs, "/dev/stdin", "", 0o666);
432
+ ensureFile(vfs, "/dev/stdout", "", 0o666);
433
+ ensureFile(vfs, "/dev/stderr", "", 0o666);
281
434
  }
282
435
  // ─── /usr ─────────────────────────────────────────────────────────────────────
283
436
  function bootstrapUsr(vfs) {
@@ -293,66 +446,26 @@ function bootstrapUsr(vfs) {
293
446
  ensureDir(vfs, "/usr/share/man");
294
447
  ensureDir(vfs, "/usr/share/man/man1");
295
448
  ensureDir(vfs, "/usr/lib");
296
- // Stub binaries so `which` can find built-in commands
449
+ // Stubs so `which` can resolve built-in commands
297
450
  const builtins = [
298
- "sh",
299
- "bash",
300
- "ls",
301
- "cat",
302
- "echo",
303
- "grep",
304
- "find",
305
- "sort",
306
- "head",
307
- "tail",
308
- "cut",
309
- "tr",
310
- "sed",
311
- "awk",
312
- "wc",
313
- "tee",
314
- "tar",
315
- "gzip",
316
- "gunzip",
317
- "touch",
318
- "mkdir",
319
- "rm",
320
- "mv",
321
- "cp",
322
- "chmod",
323
- "ln",
324
- "pwd",
325
- "env",
326
- "date",
327
- "sleep",
328
- "id",
329
- "whoami",
330
- "hostname",
331
- "uname",
332
- "ps",
333
- "kill",
334
- "df",
335
- "du",
336
- "curl",
337
- "wget",
338
- "nano",
339
- "diff",
340
- "uniq",
341
- "xargs",
342
- "base64",
451
+ "sh", "bash", "ls", "cat", "echo", "grep", "find", "sort",
452
+ "head", "tail", "cut", "tr", "sed", "awk", "wc", "tee",
453
+ "tar", "gzip", "gunzip", "touch", "mkdir", "rm", "mv", "cp",
454
+ "chmod", "ln", "pwd", "env", "date", "sleep", "id", "whoami",
455
+ "hostname", "uname", "ps", "kill", "df", "du", "curl", "wget",
456
+ "nano", "diff", "uniq", "xargs", "base64",
343
457
  ];
344
458
  for (const bin of builtins) {
345
459
  ensureFile(vfs, `/usr/bin/${bin}`, `#!/bin/sh\nexec builtin ${bin} "$@"\n`, 0o755);
346
460
  }
347
- // lsb_release script
348
461
  ensureFile(vfs, "/usr/bin/lsb_release", '#!/bin/sh\nexec lsb_release "$@"\n', 0o755);
349
462
  }
350
463
  // ─── /var ─────────────────────────────────────────────────────────────────────
351
464
  function bootstrapVar(vfs) {
352
465
  ensureDir(vfs, "/var");
353
466
  ensureDir(vfs, "/var/log");
467
+ ensureDir(vfs, "/var/log/apt");
354
468
  ensureDir(vfs, "/var/tmp");
355
- ensureDir(vfs, "/var/run");
356
469
  ensureDir(vfs, "/var/cache");
357
470
  ensureDir(vfs, "/var/cache/apt");
358
471
  ensureDir(vfs, "/var/cache/apt/archives");
@@ -361,80 +474,147 @@ function bootstrapVar(vfs) {
361
474
  ensureDir(vfs, "/var/lib/apt/lists");
362
475
  ensureDir(vfs, "/var/lib/dpkg");
363
476
  ensureDir(vfs, "/var/lib/dpkg/info");
364
- // dpkg status — starts empty, apt install populates it
477
+ ensureDir(vfs, "/var/lib/misc");
478
+ ensureDir(vfs, "/var/spool");
479
+ ensureDir(vfs, "/var/spool/cron");
480
+ ensureDir(vfs, "/var/mail");
481
+ // dpkg status — starts empty, VirtualPackageManager populates it
365
482
  ensureFile(vfs, "/var/lib/dpkg/status", "");
366
483
  ensureFile(vfs, "/var/lib/dpkg/available", "");
367
- // syslog stub
484
+ // syslog stubs
368
485
  ensureFile(vfs, "/var/log/syslog", `${new Date().toUTCString()} fortune kernel: Virtual container started\n`);
369
486
  ensureFile(vfs, "/var/log/auth.log", "");
487
+ ensureFile(vfs, "/var/log/kern.log", "");
370
488
  ensureFile(vfs, "/var/log/dpkg.log", "");
371
489
  ensureFile(vfs, "/var/log/apt/history.log", "");
372
490
  ensureFile(vfs, "/var/log/apt/term.log", "");
491
+ // /run — systemd tmpfs runtime dir (canonical on modern Debian)
492
+ // /var/run is a legacy symlink to /run
493
+ ensureDir(vfs, "/run");
494
+ ensureDir(vfs, "/run/lock");
495
+ ensureDir(vfs, "/run/systemd");
496
+ ensureDir(vfs, "/run/user");
497
+ ensureFile(vfs, "/run/utmp", "");
373
498
  }
374
499
  // ─── /bin + /sbin symlinks ────────────────────────────────────────────────────
375
500
  function bootstrapBin(vfs) {
376
- // On modern Debian/Ubuntu /bin is a symlink to /usr/bin
377
- if (!vfs.exists("/bin")) {
501
+ // Modern Debian: /bin and /sbin are symlinks to /usr/bin and /usr/sbin
502
+ if (!vfs.exists("/bin"))
378
503
  vfs.symlink("/usr/bin", "/bin");
379
- }
380
- if (!vfs.exists("/sbin")) {
504
+ if (!vfs.exists("/sbin"))
381
505
  vfs.symlink("/usr/sbin", "/sbin");
382
- }
383
- if (!vfs.exists("/lib")) {
384
- ensureDir(vfs, "/lib");
385
- }
386
- if (!vfs.exists("/lib64")) {
387
- ensureDir(vfs, "/lib64");
388
- }
506
+ // /var/run → /run (systemd compat)
507
+ if (!vfs.exists("/var/run"))
508
+ vfs.symlink("/run", "/var/run");
509
+ ensureDir(vfs, "/lib");
510
+ ensureDir(vfs, "/lib64");
511
+ ensureDir(vfs, "/lib/x86_64-linux-gnu");
512
+ ensureDir(vfs, "/lib/modules");
389
513
  }
390
514
  // ─── /tmp ─────────────────────────────────────────────────────────────────────
391
515
  function bootstrapTmp(vfs) {
392
516
  ensureDir(vfs, "/tmp", 0o1777);
393
517
  }
394
- // ─── /root ────────────────────────────────────────────────────────────────────
518
+ // ─── /root home ───────────────────────────────────────────────────────────────
395
519
  function bootstrapRoot(vfs) {
396
520
  ensureDir(vfs, "/root", 0o700);
397
- ensureFile(vfs, "/home/root/.bashrc", `${[
521
+ ensureFile(vfs, "/root/.bashrc", `${[
398
522
  "# root .bashrc",
399
523
  "export PS1='\\[\\033[0;31m\\]\\u@\\h\\[\\033[0m\\]:\\[\\033[0;34m\\]\\w\\[\\033[0m\\]# '",
400
524
  "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
401
525
  "alias ll='ls -la'",
402
526
  "alias la='ls -A'",
403
527
  ].join("\n")}\n`);
404
- ensureFile(vfs, "/home/root/.profile", "[ -f ~/.bashrc ] && . ~/.bashrc\n");
405
- // Fix: /home/root should map to /root for root user
406
- // if (!vfs.exists("/home/root")) {
407
- // vfs.symlink("/root", "/home/root");
408
- // }
528
+ ensureFile(vfs, "/root/.profile", "[ -f ~/.bashrc ] && . ~/.bashrc\n");
409
529
  }
410
- // ─── /opt + /srv + /mnt + /media ─────────────────────────────────────────────
411
- function bootstrapMisc(vfs) {
530
+ // ─── /opt /srv /mnt /media /home ─────────────────────────────────────────────
531
+ function bootstrapMisc(vfs, props) {
412
532
  ensureDir(vfs, "/opt");
413
533
  ensureDir(vfs, "/srv");
414
534
  ensureDir(vfs, "/mnt");
415
535
  ensureDir(vfs, "/media");
536
+ ensureDir(vfs, "/home");
537
+ // /boot — grub + kernel images
538
+ ensureDir(vfs, "/boot");
539
+ ensureDir(vfs, "/boot/grub");
540
+ ensureDir(vfs, "/boot/grub/grub.cfg.d");
541
+ ensureFile(vfs, "/boot/grub/grub.cfg", `${[
542
+ "# GRUB configuration (virtual)",
543
+ "set default=0",
544
+ "set timeout=5",
545
+ "",
546
+ `menuentry "Fortune GNU/Linux" {`,
547
+ ` linux /vmlinuz root=/dev/sda2 ro quiet splash`,
548
+ ` initrd /initrd.img`,
549
+ `}`,
550
+ ].join("\n")}\n`);
551
+ // kernel + initrd stubs in /boot
552
+ const kver = props.kernel;
553
+ ensureFile(vfs, `/boot/vmlinuz-${kver}`, "", 0o644);
554
+ ensureFile(vfs, `/boot/initrd.img-${kver}`, "", 0o644);
555
+ ensureFile(vfs, `/boot/System.map-${kver}`, `${kver} virtual\n`, 0o644);
556
+ ensureFile(vfs, `/boot/config-${kver}`, `# Linux kernel config ${kver}\n`, 0o644);
557
+ // root-level symlinks (Debian convention)
558
+ if (!vfs.exists("/vmlinuz"))
559
+ vfs.symlink(`/boot/vmlinuz-${kver}`, "/vmlinuz");
560
+ if (!vfs.exists("/vmlinuz.old"))
561
+ vfs.symlink(`/boot/vmlinuz-${kver}`, "/vmlinuz.old");
562
+ if (!vfs.exists("/initrd.img"))
563
+ vfs.symlink(`/boot/initrd.img-${kver}`, "/initrd.img");
564
+ if (!vfs.exists("/initrd.img.old"))
565
+ vfs.symlink(`/boot/initrd.img-${kver}`, "/initrd.img.old");
566
+ // /snap — snapd mount namespace root
567
+ ensureDir(vfs, "/snap");
568
+ ensureDir(vfs, "/snap/bin");
569
+ // /lost+found — ext4 fsck recovery dir (mode 0o700, root only)
570
+ ensureDir(vfs, "/lost+found", 0o700);
416
571
  }
417
572
  // ─── main entry point ─────────────────────────────────────────────────────────
418
573
  /**
419
574
  * Bootstraps the full Linux rootfs hierarchy in the VFS.
420
575
  * Safe to call multiple times — idempotent.
421
576
  *
422
- * @param vfs Target virtual filesystem.
423
- * @param users User manager (for /etc/passwd sync).
424
- * @param hostname Virtual hostname.
425
- * @param props Shell properties (kernel, os, arch).
577
+ * @param vfs Target virtual filesystem.
578
+ * @param users User manager (for /etc/passwd sync).
579
+ * @param hostname Virtual hostname.
580
+ * @param props Shell properties (kernel, os, arch).
426
581
  * @param shellStartTime Unix ms of shell creation (for uptime).
582
+ * @param sessions Active sessions (for /proc/<pid> population).
427
583
  */
428
- export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime) {
584
+ export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime, sessions = []) {
429
585
  bootstrapEtc(vfs, hostname, props);
430
- bootstrapSys(vfs, props);
586
+ bootstrapSys(vfs, hostname, props);
431
587
  bootstrapDev(vfs);
432
588
  bootstrapUsr(vfs);
433
589
  bootstrapVar(vfs);
434
590
  bootstrapBin(vfs);
435
591
  bootstrapTmp(vfs);
436
592
  bootstrapRoot(vfs);
437
- bootstrapMisc(vfs);
438
- refreshProc(vfs, props, hostname, shellStartTime, []);
593
+ bootstrapMisc(vfs, props);
594
+ bootProcLog(vfs, props);
595
+ refreshProc(vfs, props, hostname, shellStartTime, sessions);
439
596
  syncEtcPasswd(vfs, users);
440
597
  }
598
+ // ─── optional live engine ─────────────────────────────────────────────────────
599
+ /**
600
+ * Engine for runtimes that want periodic /proc refresh (e.g. web shell
601
+ * with live `top`/`ps` output). Call `.boot()` once, then `.tick()` on
602
+ * each session change or on a timer.
603
+ *
604
+ * ```ts
605
+ * const engine = createLinuxRootfsEngine(vfs, props, hostname, Date.now());
606
+ * engine.boot(users, sessions);
607
+ * setInterval(() => engine.tick(shell.listActiveSessions()), 5000);
608
+ * ```
609
+ */
610
+ export function createLinuxRootfsEngine(vfs, props, hostname, startTime) {
611
+ return {
612
+ boot(users, sessions = []) {
613
+ bootstrapLinuxRootfs(vfs, users, hostname, props, startTime, sessions);
614
+ },
615
+ tick(sessions = []) {
616
+ refreshProc(vfs, props, hostname, startTime, sessions);
617
+ },
618
+ };
619
+ }
620
+ //# sourceMappingURL=linuxRootfs.js.map