typescript-virtual-container 1.4.9 → 1.5.0

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 (129) hide show
  1. package/.vscode/settings.json +1 -1
  2. package/README.md +117 -79
  3. package/builds/self-standalone.mjs +1768 -0
  4. package/builds/standalone-wo-sftp.js +735 -267
  5. package/builds/standalone.cjs +735 -267
  6. package/bun.lock +3 -3
  7. package/dist/SSHMimic/exec.js +2 -2
  8. package/dist/SSHMimic/exec.js.map +1 -1
  9. package/dist/SSHMimic/index.d.ts.map +1 -1
  10. package/dist/SSHMimic/index.js +2 -1
  11. package/dist/SSHMimic/index.js.map +1 -1
  12. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  13. package/dist/SSHMimic/sftp.js +4 -3
  14. package/dist/SSHMimic/sftp.js.map +1 -1
  15. package/dist/VirtualFileSystem/index.d.ts +14 -0
  16. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  17. package/dist/VirtualFileSystem/index.js +51 -3
  18. package/dist/VirtualFileSystem/index.js.map +1 -1
  19. package/dist/VirtualFileSystem/journal.d.ts.map +1 -1
  20. package/dist/VirtualFileSystem/journal.js +11 -5
  21. package/dist/VirtualFileSystem/journal.js.map +1 -1
  22. package/dist/VirtualShell/shell.js +12 -12
  23. package/dist/VirtualShell/shell.js.map +1 -1
  24. package/dist/VirtualUserManager/index.js +8 -11
  25. package/dist/VirtualUserManager/index.js.map +1 -1
  26. package/dist/commands/apt.js +3 -3
  27. package/dist/commands/apt.js.map +1 -1
  28. package/dist/commands/cd.d.ts.map +1 -1
  29. package/dist/commands/cd.js +2 -1
  30. package/dist/commands/cd.js.map +1 -1
  31. package/dist/commands/helpers.d.ts +1 -1
  32. package/dist/commands/helpers.d.ts.map +1 -1
  33. package/dist/commands/helpers.js +3 -2
  34. package/dist/commands/helpers.js.map +1 -1
  35. package/dist/commands/index.d.ts +1 -1
  36. package/dist/commands/index.d.ts.map +1 -1
  37. package/dist/commands/index.js +1 -1
  38. package/dist/commands/index.js.map +1 -1
  39. package/dist/commands/lsb-release.js +1 -1
  40. package/dist/commands/lsb-release.js.map +1 -1
  41. package/dist/commands/runtime.d.ts +2 -0
  42. package/dist/commands/runtime.d.ts.map +1 -1
  43. package/dist/commands/runtime.js +5 -1
  44. package/dist/commands/runtime.js.map +1 -1
  45. package/dist/modules/linuxRootfs.d.ts +9 -5
  46. package/dist/modules/linuxRootfs.d.ts.map +1 -1
  47. package/dist/modules/linuxRootfs.js +1079 -148
  48. package/dist/modules/linuxRootfs.js.map +1 -1
  49. package/dist/self-standalone.js +22 -12
  50. package/dist/self-standalone.js.map +1 -1
  51. package/docs/assets/hierarchy.js +1 -1
  52. package/docs/classes/HoneyPot.html +9 -9
  53. package/docs/classes/IdleManager.html +8 -8
  54. package/docs/classes/SshClient.html +18 -18
  55. package/docs/classes/VirtualFileSystem.html +34 -34
  56. package/docs/classes/VirtualPackageManager.html +13 -13
  57. package/docs/classes/VirtualSftpServer.html +3 -3
  58. package/docs/classes/VirtualShell.html +28 -28
  59. package/docs/classes/VirtualSshServer.html +5 -5
  60. package/docs/classes/VirtualUserManager.html +27 -27
  61. package/docs/functions/assertDiff.html +2 -2
  62. package/docs/functions/diffSnapshots.html +2 -2
  63. package/docs/functions/formatDiff.html +2 -2
  64. package/docs/functions/getArg.html +2 -2
  65. package/docs/functions/getFlag.html +2 -2
  66. package/docs/functions/ifFlag.html +2 -2
  67. package/docs/hierarchy.html +1 -1
  68. package/docs/index.html +10 -8
  69. package/docs/interfaces/AuditLogEntry.html +3 -3
  70. package/docs/interfaces/CommandContext.html +12 -12
  71. package/docs/interfaces/CommandResult.html +13 -13
  72. package/docs/interfaces/ExecStream.html +6 -6
  73. package/docs/interfaces/HoneyPotStats.html +3 -3
  74. package/docs/interfaces/IdleManagerOptions.html +3 -3
  75. package/docs/interfaces/InstalledPackage.html +11 -11
  76. package/docs/interfaces/NanoEditorSession.html +5 -5
  77. package/docs/interfaces/PackageDefinition.html +14 -14
  78. package/docs/interfaces/PackageFile.html +5 -5
  79. package/docs/interfaces/PasswordChallenge.html +9 -9
  80. package/docs/interfaces/RemoveOptions.html +3 -3
  81. package/docs/interfaces/ShellEnv.html +4 -4
  82. package/docs/interfaces/ShellModule.html +8 -8
  83. package/docs/interfaces/ShellProperties.html +5 -5
  84. package/docs/interfaces/ShellStream.html +7 -7
  85. package/docs/interfaces/SudoChallenge.html +9 -9
  86. package/docs/interfaces/VfsBaseNode.html +7 -7
  87. package/docs/interfaces/VfsDiff.html +6 -6
  88. package/docs/interfaces/VfsDiffEntry.html +4 -4
  89. package/docs/interfaces/VfsDiffModified.html +6 -6
  90. package/docs/interfaces/VfsDirectoryNode.html +8 -8
  91. package/docs/interfaces/VfsFileNode.html +9 -9
  92. package/docs/interfaces/VfsOptions.html +6 -6
  93. package/docs/interfaces/VfsSnapshot.html +3 -3
  94. package/docs/interfaces/VfsSnapshotBaseNode.html +4 -4
  95. package/docs/interfaces/VfsSnapshotDirectoryNode.html +5 -5
  96. package/docs/interfaces/VfsSnapshotFileNode.html +6 -6
  97. package/docs/interfaces/VirtualActiveSession.html +7 -7
  98. package/docs/interfaces/VirtualSftpServerOptions.html +3 -3
  99. package/docs/interfaces/VirtualShellVfsLike.html +3 -3
  100. package/docs/interfaces/VirtualShellVfsOptions.html +3 -3
  101. package/docs/interfaces/WriteFileOptions.html +4 -4
  102. package/docs/modules.html +1 -1
  103. package/docs/types/ArgParseOptions.html +3 -3
  104. package/docs/types/CommandMode.html +2 -2
  105. package/docs/types/CommandOutcome.html +2 -2
  106. package/docs/types/IdleState.html +1 -1
  107. package/docs/types/VfsNodeStats.html +2 -2
  108. package/docs/types/VfsNodeType.html +2 -2
  109. package/docs/types/VfsPersistenceMode.html +2 -2
  110. package/docs/types/VfsSnapshotNode.html +2 -2
  111. package/package.json +3 -3
  112. package/src/SSHMimic/exec.ts +2 -2
  113. package/src/SSHMimic/index.ts +2 -1
  114. package/src/SSHMimic/sftp.ts +4 -3
  115. package/src/VirtualFileSystem/index.ts +46 -3
  116. package/src/VirtualFileSystem/journal.ts +10 -5
  117. package/src/VirtualShell/shell.ts +12 -12
  118. package/src/VirtualUserManager/index.ts +11 -11
  119. package/src/commands/apt.ts +3 -3
  120. package/src/commands/cd.ts +2 -1
  121. package/src/commands/helpers.ts +3 -2
  122. package/src/commands/index.ts +1 -1
  123. package/src/commands/lsb-release.ts +1 -1
  124. package/src/commands/runtime.ts +6 -1
  125. package/src/modules/linuxRootfs.ts +1293 -207
  126. package/src/self-standalone.ts +26 -12
  127. package/tests/new-features.test.ts +2 -2
  128. package/tests/sftp.test.ts +13 -13
  129. package/builds/self-standalone.js +0 -1299
@@ -6,12 +6,16 @@
6
6
  * Called once during VirtualShell initialization. Idempotent — skips
7
7
  * paths that already exist so FS-mode snapshots survive restarts.
8
8
  *
9
+ * Emulation fidelity: modelled after a Fortune GNU/Linux 1.0 (Nyx)
10
+ * container with Firecracker MicroVM kernel 6.x, virtio block devices
11
+ * (vda/vdb/vdc/vdd), cgroups v1 hierarchy, Node.js 22, Python 3.12,
12
+ * GCC 13, OpenJDK 21, and a Fortune-style package database.
13
+ *
9
14
  * 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
15
+ * - bootstrapLinuxRootfs() one-shot boot (VirtualShell calls this)
16
+ * - refreshProc() refresh /proc/* (call on session changes)
17
+ * - syncEtcPasswd() sync /etc/passwd|group|shadow from UserManager
18
+ * - createLinuxRootfsEngine() engine with .boot() + .tick() for live loops
15
19
  */
16
20
  import * as os from "node:os";
17
21
  import VirtualFileSystem from "../VirtualFileSystem";
@@ -22,7 +26,6 @@ function ensureDir(vfs, path, mode = 0o755) {
22
26
  vfs.mkdir(path, mode);
23
27
  }
24
28
  function ensureFile(vfs, path, content, mode = 0o644) {
25
- // Use lazy stub — no Buffer allocated until the file is actually read or overwritten
26
29
  vfs.writeStub(path, content, mode);
27
30
  }
28
31
  function write(vfs, path, content) {
@@ -40,33 +43,45 @@ function fnv1a(str) {
40
43
  // ─── /etc ────────────────────────────────────────────────────────────────────
41
44
  function bootstrapEtc(vfs, hostname, props) {
42
45
  ensureDir(vfs, "/etc");
43
- // os-release — authoritative distro identity used by neofetch, lsb_release
46
+ // os-release — Fortune Nyx identity
44
47
  ensureFile(vfs, "/etc/os-release", `${[
45
48
  `NAME="Fortune GNU/Linux"`,
46
49
  `PRETTY_NAME="${props.os}"`,
47
50
  `ID=fortune`,
48
51
  `ID_LIKE=debian`,
49
52
  `HOME_URL="https://github.com/itsrealfortune/typescript-virtual-container"`,
50
- `VERSION_CODENAME=aurora`,
51
- `VERSION_ID="1.0"`,
53
+ `VERSION_CODENAME=nyx`,
54
+ `VERSION_ID="24.04"`,
55
+ `FORTUNE_CODENAME=nyx`,
52
56
  ].join("\n")}\n`);
53
- ensureFile(vfs, "/etc/debian_version", "12.0\n");
57
+ ensureFile(vfs, "/etc/debian_version", "nyx/stable\n");
54
58
  ensureFile(vfs, "/etc/hostname", `${hostname}\n`);
55
- ensureFile(vfs, "/etc/shells", "/bin/sh\n/bin/bash\n/usr/bin/bash\n");
59
+ ensureFile(vfs, "/etc/shells", "/bin/sh\n/bin/bash\n/usr/bin/bash\n/bin/dash\n/usr/bin/dash\n");
56
60
  ensureFile(vfs, "/etc/profile", `${[
57
- "export PATH=/usr/local/bin:/usr/bin:/bin",
61
+ "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
58
62
  "export PS1='\\u@\\h:\\w\\$ '",
59
63
  ].join("\n")}\n`);
60
- ensureFile(vfs, "/etc/issue", "Fortune GNU/Linux 1.0 \\n \\l\n");
64
+ ensureFile(vfs, "/etc/issue", "Fortune GNU/Linux 24.04 LTS \\n \\l\n");
65
+ ensureFile(vfs, "/etc/issue.net", "Fortune GNU/Linux 24.04 LTS\n");
61
66
  ensureFile(vfs, "/etc/motd", ["", `Welcome to ${props.os}`, `Kernel: ${props.kernel}`, ""].join("\n"));
62
- // APT sources
67
+ ensureFile(vfs, "/etc/lsb-release", `${[
68
+ "DISTRIB_ID=Fortune",
69
+ "DISTRIB_RELEASE=24.04",
70
+ "DISTRIB_CODENAME=nyx",
71
+ `DISTRIB_DESCRIPTION="${props.os}"`,
72
+ ].join("\n")}\n`);
73
+ // APT — Fortune Nyx sources
63
74
  ensureDir(vfs, "/etc/apt");
64
75
  ensureDir(vfs, "/etc/apt/sources.list.d");
76
+ ensureDir(vfs, "/etc/apt/trusted.gpg.d");
77
+ ensureDir(vfs, "/etc/apt/keyrings");
65
78
  ensureFile(vfs, "/etc/apt/sources.list", `${[
66
- "# Fortune GNU/Linux package sources",
67
- "deb [virtual] fortune://packages.fortune.local aurora main contrib",
68
- "deb [virtual] fortune://security.fortune.local aurora-security main",
79
+ "# Fortune GNU/Linux package sources (Fortune 1.0 Nyx)",
80
+ "deb [virtual] fortune://packages.fortune.local nyx main contrib non-free",
81
+ "deb [virtual] fortune://packages.fortune.local nyx-updates main contrib non-free",
82
+ "deb [virtual] fortune://security.fortune.local nyx-security main",
69
83
  ].join("\n")}\n`);
84
+ ensureFile(vfs, "/etc/apt/apt.conf.d/70debconf", `// debconf config\n`);
70
85
  // network
71
86
  ensureDir(vfs, "/etc/network");
72
87
  ensureFile(vfs, "/etc/network/interfaces", `${[
@@ -76,26 +91,56 @@ function bootstrapEtc(vfs, hostname, props) {
76
91
  "auto eth0",
77
92
  "iface eth0 inet dhcp",
78
93
  ].join("\n")}\n`);
94
+ ensureDir(vfs, "/etc/netplan");
95
+ ensureFile(vfs, "/etc/netplan/01-eth0.yaml", `${[
96
+ "network:",
97
+ " version: 2",
98
+ " ethernets:",
99
+ " eth0:",
100
+ " dhcp4: true",
101
+ ].join("\n")}\n`);
79
102
  ensureFile(vfs, "/etc/resolv.conf", "nameserver 1.1.1.1\nnameserver 8.8.8.8\n");
80
103
  ensureFile(vfs, "/etc/hosts", `${[
81
104
  "127.0.0.1 localhost",
82
105
  `127.0.1.1 ${hostname}`,
83
106
  "::1 localhost ip6-localhost ip6-loopback",
107
+ "fe00::0 ip6-localnet",
108
+ "ff00::0 ip6-mcastprefix",
109
+ "ff02::1 ip6-allnodes",
110
+ "ff02::2 ip6-allrouters",
111
+ ].join("\n")}\n`);
112
+ ensureFile(vfs, "/etc/nsswitch.conf", `${[
113
+ "passwd: files systemd",
114
+ "group: files systemd",
115
+ "shadow: files",
116
+ "hosts: files dns",
117
+ "networks: files",
118
+ "protocols: db files",
119
+ "services: db files",
120
+ "ethers: db files",
121
+ "rpc: db files",
84
122
  ].join("\n")}\n`);
85
123
  ensureDir(vfs, "/etc/cron.d");
124
+ ensureDir(vfs, "/etc/cron.daily");
125
+ ensureDir(vfs, "/etc/cron.hourly");
126
+ ensureDir(vfs, "/etc/cron.weekly");
127
+ ensureDir(vfs, "/etc/cron.monthly");
86
128
  ensureDir(vfs, "/etc/init.d");
87
129
  ensureDir(vfs, "/etc/systemd");
88
130
  ensureDir(vfs, "/etc/systemd/system");
89
- // fstab
131
+ ensureDir(vfs, "/etc/systemd/system/multi-user.target.wants");
132
+ ensureDir(vfs, "/etc/systemd/network");
133
+ ensureFile(vfs, "/etc/systemd/system.conf", "[Manager]\nDefaultTimeoutStartSec=90s\nDefaultTimeoutStopSec=90s\n");
134
+ // fstab — virtio block devices matching Firecracker layout
90
135
  ensureFile(vfs, "/etc/fstab", `${[
91
- "# <file system> <mount point> <type> <options> <dump> <pass>",
92
- "UUID=00000000-0000-0000-0000-000000000001 / ext4 errors=remount-ro 0 1",
93
- "UUID=00000000-0000-0000-0000-000000000002 /boot ext4 defaults 0 2",
94
- "UUID=00000000-0000-0000-0000-000000000003 none swap sw 0 0",
95
- "tmpfs /tmp tmpfs defaults,noatime 0 0",
96
- "tmpfs /run tmpfs defaults,noatime 0 0",
136
+ "# <file system> <mount point> <type> <options> <dump> <pass>",
137
+ "/dev/vda / ext4 rw,relatime,resuid=65534,resgid=65534 0 1",
138
+ "/dev/vdb /opt/rclone squashfs ro,relatime,errors=continue 0 0",
139
+ "tmpfs /tmp tmpfs defaults,noatime 0 0",
140
+ "tmpfs /run tmpfs defaults,noatime 0 0",
141
+ "tmpfs /dev/shm tmpfs rw,relatime 0 0",
97
142
  ].join("\n")}\n`);
98
- // login.defs — useradd/passwd defaults
143
+ // login.defs
99
144
  ensureFile(vfs, "/etc/login.defs", `${[
100
145
  "MAIL_DIR /var/mail",
101
146
  "PASS_MAX_DAYS 99999",
@@ -112,25 +157,108 @@ function bootstrapEtc(vfs, hostname, props) {
112
157
  ].join("\n")}\n`);
113
158
  // security + pam
114
159
  ensureDir(vfs, "/etc/security");
115
- ensureFile(vfs, "/etc/security/limits.conf", "# /etc/security/limits.conf\n");
160
+ ensureFile(vfs, "/etc/security/limits.conf", "# /etc/security/limits.conf\n* soft nofile 1024\n* hard nofile 65536\n");
116
161
  ensureFile(vfs, "/etc/security/access.conf", "# /etc/security/access.conf\n");
117
162
  ensureDir(vfs, "/etc/pam.d");
118
- ensureFile(vfs, "/etc/pam.d/common-auth", "auth required pam_unix.so\n");
119
- ensureFile(vfs, "/etc/pam.d/common-account", "account required pam_unix.so\n");
120
- ensureFile(vfs, "/etc/pam.d/common-password", "password required pam_unix.so obscure sha512\n");
121
- ensureFile(vfs, "/etc/pam.d/common-session", "session required pam_unix.so\n");
163
+ ensureFile(vfs, "/etc/pam.d/common-auth", "auth [success=1 default=ignore] pam_unix.so nullok\nauth requisite pam_deny.so\nauth required pam_permit.so\n");
164
+ ensureFile(vfs, "/etc/pam.d/common-account", "account [success=1 new_authtok_reqd=done default=ignore] pam_unix.so\naccount requisite pam_deny.so\naccount required pam_permit.so\n");
165
+ ensureFile(vfs, "/etc/pam.d/common-password", "password [success=1 default=ignore] pam_unix.so obscure sha512\npassword requisite pam_deny.so\npassword required pam_permit.so\n");
166
+ ensureFile(vfs, "/etc/pam.d/common-session", "session [default=1] pam_permit.so\nsession requisite pam_deny.so\nsession required pam_permit.so\nsession optional pam_umask.so\nsession required pam_unix.so\n");
122
167
  ensureFile(vfs, "/etc/pam.d/sshd", "@include common-auth\n@include common-account\n@include common-session\n");
123
- // sudo config
168
+ ensureFile(vfs, "/etc/pam.d/login", "@include common-auth\n@include common-account\n@include common-session\n");
169
+ ensureFile(vfs, "/etc/pam.d/sudo", "@include common-auth\n@include common-account\n@include common-session\n");
170
+ // sudo
124
171
  ensureDir(vfs, "/etc/sudoers.d");
125
- ensureFile(vfs, "/etc/sudoers", "root ALL=(ALL:ALL) ALL\n%sudo ALL=(ALL:ALL) ALL\n", 0o440);
172
+ ensureFile(vfs, "/etc/sudoers", "Defaults\tenv_reset\nDefaults\tmail_badpass\nDefaults\tsecure_path=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\nroot ALL=(ALL:ALL) ALL\n%sudo ALL=(ALL:ALL) ALL\n", 0o440);
173
+ ensureFile(vfs, "/etc/sudoers.d/README", "# Files in this directory are parsed by sudo, if the file is not a backup.\n", 0o440);
126
174
  // ld
127
175
  ensureFile(vfs, "/etc/ld.so.conf", "include /etc/ld.so.conf.d/*.conf\n");
128
176
  ensureDir(vfs, "/etc/ld.so.conf.d");
129
177
  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");
178
+ ensureFile(vfs, "/etc/ld.so.conf.d/fakeroot.conf", "/usr/lib/x86_64-linux-gnu/libfakeroot\n");
130
179
  // locale + timezone
131
180
  ensureFile(vfs, "/etc/locale.conf", "LANG=en_US.UTF-8\n");
181
+ ensureFile(vfs, "/etc/locale.gen", "en_US.UTF-8 UTF-8\n");
182
+ ensureFile(vfs, "/etc/default/locale", "LANG=en_US.UTF-8\n");
132
183
  ensureFile(vfs, "/etc/timezone", "UTC\n");
133
184
  ensureFile(vfs, "/etc/localtime", "UTC\n");
185
+ // environment
186
+ ensureFile(vfs, "/etc/environment", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n");
187
+ // adduser.conf
188
+ ensureFile(vfs, "/etc/adduser.conf", `${[
189
+ "DSHELL=/bin/bash",
190
+ "DHOME=/home",
191
+ "GROUPHOMES=no",
192
+ "LETTERHOMES=no",
193
+ "SKEL=/etc/skel",
194
+ "FIRST_SYSTEM_UID=100",
195
+ "LAST_SYSTEM_UID=999",
196
+ "FIRST_SYSTEM_GID=100",
197
+ "LAST_SYSTEM_GID=999",
198
+ "FIRST_UID=1000",
199
+ "LAST_UID=59999",
200
+ "FIRST_GID=1000",
201
+ "LAST_GID=59999",
202
+ "USERGROUPS=yes",
203
+ "NAME_REGEX=\"^[a-z][-a-z0-9_]*$\"",
204
+ "SYS_NAME_REGEX=\"^[a-z_][-a-z0-9_]*$\"",
205
+ ].join("\n")}\n`);
206
+ // /etc/skel
207
+ ensureDir(vfs, "/etc/skel");
208
+ ensureFile(vfs, "/etc/skel/.bashrc", `${[
209
+ "# ~/.bashrc: executed by bash(1) for non-login shells.",
210
+ "case $- in",
211
+ " *i*) ;;",
212
+ " *) return;;",
213
+ "esac",
214
+ "HISTCONTROL=ignoreboth",
215
+ "HISTSIZE=1000",
216
+ "HISTFILESIZE=2000",
217
+ "shopt -s histappend",
218
+ "shopt -s checkwinsize",
219
+ "alias ll='ls -alF'",
220
+ "alias la='ls -A'",
221
+ "alias l='ls -CF'",
222
+ ].join("\n")}\n`);
223
+ ensureFile(vfs, "/etc/skel/.bash_logout", "# ~/.bash_logout\n");
224
+ ensureFile(vfs, "/etc/skel/.profile", "# ~/.profile\n[ -n \"$BASH_VERSION\" ] && [ -f \"$HOME/.bashrc\" ] && . \"$HOME/.bashrc\"\n");
225
+ // alternatives
226
+ ensureDir(vfs, "/etc/alternatives");
227
+ const alternatives = [
228
+ ["python3", "/usr/bin/python3.12"],
229
+ ["python", "/usr/bin/python3.12"],
230
+ ["editor", "/usr/bin/nano"],
231
+ ["vi", "/usr/bin/nano"],
232
+ ["cc", "/usr/bin/gcc"],
233
+ ["gcc", "/usr/bin/gcc-13"],
234
+ ["g++", "/usr/bin/g++-13"],
235
+ ["java", "/usr/lib/jvm/java-21-openjdk-amd64/bin/java"],
236
+ ["node", "/usr/bin/node"],
237
+ ["npm", "/usr/bin/npm"],
238
+ ["awk", "/usr/bin/mawk"],
239
+ ["pager", "/usr/bin/less"],
240
+ ];
241
+ for (const [name, target] of alternatives) {
242
+ ensureFile(vfs, `/etc/alternatives/${name}`, target);
243
+ }
244
+ // java
245
+ ensureDir(vfs, "/etc/java-21-openjdk");
246
+ ensureDir(vfs, "/etc/java-21-openjdk/security");
247
+ ensureFile(vfs, "/etc/java-21-openjdk/security/java.security", "# java.security\n");
248
+ ensureFile(vfs, "/etc/java-21-openjdk/logging.properties", "# logging.properties\n");
249
+ // misc
250
+ ensureFile(vfs, "/etc/bash.bashrc", "# System-wide .bashrc\n[ -z \"$PS1\" ] && return\n");
251
+ ensureFile(vfs, "/etc/inputrc", "# /etc/inputrc\n$include /etc/inputrc.d\nset bell-style none\n");
252
+ ensureFile(vfs, "/etc/magic", "# magic\n");
253
+ ensureFile(vfs, "/etc/magic.mime", "# magic.mime\n");
254
+ ensureFile(vfs, "/etc/papersize", "a4\n");
255
+ ensureFile(vfs, "/etc/ucf.conf", "# ucf.conf\n");
256
+ ensureFile(vfs, "/etc/gai.conf", "# getaddrinfo() configuration\nlabel ::1/128 0\nprecedence ::1/128 50\n");
257
+ ensureFile(vfs, "/etc/services", "# Network services\nftp 21/tcp\nssh 22/tcp\nsmtp 25/tcp\nhttp 80/tcp\nhttps 443/tcp\n");
258
+ ensureFile(vfs, "/etc/protocols", "# protocols\nip 0 IP\nicmp 1 ICMP\ntcp 6 TCP\nudp 17 UDP\n");
259
+ ensureDir(vfs, "/etc/profile.d");
260
+ ensureFile(vfs, "/etc/profile.d/01-locale-fix.sh", "[ -z \"$LANG\" ] && export LANG=en_US.UTF-8\n");
261
+ ensureFile(vfs, "/etc/profile.d/apps-bin-path.sh", "export PATH=\"$PATH:/snap/bin\"\n");
134
262
  }
135
263
  // ─── /etc/passwd + /etc/group + /etc/shadow ─────────────────────────────────
136
264
  /**
@@ -142,8 +270,26 @@ export function syncEtcPasswd(vfs, users) {
142
270
  const passwdLines = [
143
271
  "root:x:0:0:root:/root:/bin/bash",
144
272
  "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin",
273
+ "bin:x:2:2:bin:/bin:/usr/sbin/nologin",
274
+ "sys:x:3:3:sys:/dev:/usr/sbin/nologin",
275
+ "sync:x:4:65534:sync:/bin:/bin/sync",
276
+ "games:x:5:60:games:/usr/games:/usr/sbin/nologin",
277
+ "man:x:6:12:man:/var/cache/man:/usr/sbin/nologin",
278
+ "lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin",
279
+ "mail:x:8:8:mail:/var/mail:/usr/sbin/nologin",
280
+ "news:x:9:9:news:/var/spool/news:/usr/sbin/nologin",
281
+ "uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin",
282
+ "proxy:x:13:13:proxy:/bin:/usr/sbin/nologin",
145
283
  "www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin",
284
+ "backup:x:34:34:backup:/var/backups:/usr/sbin/nologin",
285
+ "list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin",
286
+ "irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin",
287
+ "_apt:x:42:65534::/nonexistent:/usr/sbin/nologin",
146
288
  "nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin",
289
+ "messagebus:x:100:106::/nonexistent:/usr/sbin/nologin",
290
+ "systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin",
291
+ "systemd-resolve:x:999:999:systemd Resolver:/:/usr/sbin/nologin",
292
+ "polkitd:x:997:997:polkit:/nonexistent:/usr/sbin/nologin",
147
293
  ];
148
294
  let uid = 1000;
149
295
  for (const u of userList) {
@@ -153,18 +299,63 @@ export function syncEtcPasswd(vfs, users) {
153
299
  uid++;
154
300
  }
155
301
  vfs.writeFile("/etc/passwd", `${passwdLines.join("\n")}\n`);
302
+ const sudoers = userList.filter((u) => users.isSudoer(u)).join(",");
303
+ const nonRootUsers = userList.filter((u) => u !== "root").join(",");
156
304
  const groupLines = [
157
305
  "root:x:0:",
158
306
  "daemon:x:1:",
159
- `sudo:x:27:${userList.filter((u) => users.isSudoer(u)).join(",")}`,
160
- `users:x:100:${userList.filter((u) => u !== "root").join(",")}`,
307
+ "bin:x:2:",
308
+ "sys:x:3:",
309
+ "adm:x:4:syslog",
310
+ "tty:x:5:",
311
+ "disk:x:6:",
312
+ "lp:x:7:",
313
+ "mail:x:8:",
314
+ "news:x:9:",
315
+ "uucp:x:10:",
316
+ "man:x:12:",
317
+ "proxy:x:13:",
318
+ "kmem:x:15:",
319
+ "dialout:x:20:",
320
+ "fax:x:21:",
321
+ "voice:x:22:",
322
+ "cdrom:x:24:",
323
+ "floppy:x:25:",
324
+ "tape:x:26:",
325
+ `sudo:x:27:${sudoers}`,
326
+ "audio:x:29:",
327
+ "dip:x:30:",
328
+ "www-data:x:33:",
329
+ "backup:x:34:",
330
+ "operator:x:37:",
331
+ "list:x:38:",
332
+ "irc:x:39:",
333
+ "src:x:40:",
334
+ "_apt:x:42:",
335
+ "shadow:x:42:",
336
+ "utmp:x:43:",
337
+ "video:x:44:",
338
+ "sasl:x:45:",
339
+ "plugdev:x:46:",
340
+ "staff:x:50:",
341
+ "games:x:60:",
342
+ `users:x:100:${nonRootUsers}`,
161
343
  "nogroup:x:65534:",
344
+ "messagebus:x:106:",
345
+ "systemd-network:x:998:",
346
+ "systemd-resolve:x:999:",
347
+ "polkitd:x:997:",
162
348
  ];
163
349
  vfs.writeFile("/etc/group", `${groupLines.join("\n")}\n`);
164
- // shadow — fake hashes, never real credentials
165
350
  const shadowLines = [
166
351
  "root:*:19000:0:99999:7:::",
167
352
  "daemon:*:19000:0:99999:7:::",
353
+ "nobody:*:19000:0:99999:7:::",
354
+ "messagebus:*:19000:0:99999:7:::",
355
+ "_apt:*:19000:0:99999:7:::",
356
+ "systemd-network:!:19000:::::::",
357
+ "systemd-resolve:!:19000:::::::",
358
+ "polkitd:!:19000:::::::",
168
359
  ];
169
360
  for (const u of userList) {
170
361
  if (u === "root")
@@ -174,50 +365,131 @@ export function syncEtcPasswd(vfs, users) {
174
365
  vfs.writeFile("/etc/shadow", `${shadowLines.join("\n")}\n`, { mode: 0o640 });
175
366
  }
176
367
  // ─── /proc helpers ───────────────────────────────────────────────────────────
177
- /** Derive a stable virtual PID from a tty string e.g. "pts/0" → 1000 */
178
368
  function ttyToPid(tty) {
179
369
  const match = tty.match(/(\d+)$/);
180
370
  return 1000 + (match?.[1] ? parseInt(match[1], 10) : 0);
181
371
  }
182
- /** Write /proc/<pid>/ subtree for a single virtual process */
183
372
  function writeProcPid(vfs, pid, username, _tty, cmdline, startedAt, env) {
184
373
  const dir = `/proc/${pid}`;
185
374
  ensureDir(vfs, dir);
186
375
  ensureDir(vfs, `${dir}/fd`);
187
376
  ensureDir(vfs, `${dir}/fdinfo`);
377
+ ensureDir(vfs, `${dir}/net`);
188
378
  const uptimeSec = Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000);
189
379
  const comm = cmdline.split(/\s+/)[0] ?? "bash";
190
380
  write(vfs, `${dir}/cmdline`, `${cmdline.replace(/\s+/g, "\0")}\0`);
191
381
  write(vfs, `${dir}/comm`, comm);
192
382
  write(vfs, `${dir}/status`, `${[
193
383
  `Name: ${comm}`,
384
+ `Umask: 0022`,
194
385
  `State: S (sleeping)`,
386
+ `Tgid: ${pid}`,
195
387
  `Pid: ${pid}`,
196
388
  `PPid: 1`,
389
+ `TracerPid: 0`,
197
390
  `Uid: 0\t0\t0\t0`,
198
391
  `Gid: 0\t0\t0\t0`,
199
- `VmRSS: 4096 kB`,
200
- `VmSize: 16384 kB`,
392
+ `FDSize: 64`,
393
+ `Groups:`,
394
+ `VmPeak: 20480 kB`,
395
+ `VmSize: 16384 kB`,
396
+ `VmLck: 0 kB`,
397
+ `VmPin: 0 kB`,
398
+ `VmHWM: 4096 kB`,
399
+ `VmRSS: 4096 kB`,
400
+ `RssAnon: 512 kB`,
401
+ `RssFile: 3584 kB`,
402
+ `RssShmem: 0 kB`,
403
+ `VmData: 2048 kB`,
404
+ `VmStk: 132 kB`,
405
+ `VmExe: 924 kB`,
406
+ `VmLib: 2744 kB`,
407
+ `VmPTE: 52 kB`,
408
+ `VmSwap: 0 kB`,
201
409
  `Threads: 1`,
410
+ `SigQ: 0/31968`,
411
+ `SigPnd: 0000000000000000`,
412
+ `SigBlk: 0000000000010000`,
413
+ `SigIgn: 0000000000380004`,
414
+ `SigCgt: 000000004b817efb`,
415
+ `CapInh: 0000000000000000`,
416
+ `CapPrm: 000001ffffffffff`,
417
+ `CapEff: 000001ffffffffff`,
418
+ `CapBnd: 000001ffffffffff`,
419
+ `CapAmb: 0000000000000000`,
420
+ `NoNewPrivs: 0`,
421
+ `Seccomp: 0`,
422
+ `voluntary_ctxt_switches: 100`,
423
+ `nonvoluntary_ctxt_switches: 10`,
202
424
  ].join("\n")}\n`);
203
- 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`);
425
+ 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 16777216 4096 18446744073709551615 93824992235520 93824992290000 140737488347024 0 0 0 65536 3686404 1266761467 1 0 0 17 0 0 0 0 0 0\n`);
426
+ write(vfs, `${dir}/statm`, `4096 1024 768 231 0 512 0\n`);
204
427
  write(vfs, `${dir}/environ`, `${Object.entries(env).map(([k, v]) => `${k}=${v}`).join("\0")}\0`);
205
428
  write(vfs, `${dir}/cwd`, `/home/${username}\0`);
206
429
  write(vfs, `${dir}/exe`, "/bin/bash\0");
207
- // Standard fd stubs (stdin/stdout/stderr)
430
+ write(vfs, `${dir}/maps`, "00400000-004e7000 r-xp 00000000 fd:00 131073 /bin/bash\n" +
431
+ "006e7000-006e8000 r--p 000e7000 fd:00 131073 /bin/bash\n" +
432
+ "006e8000-006f1000 rw-p 000e8000 fd:00 131073 /bin/bash\n" +
433
+ "7fff00000000-7fff00001000 rw-p 00000000 00:00 0 [stack]\n" +
434
+ "7fff00000000-7fff00001000 r-xp 00000000 00:00 0 [vdso]\n");
435
+ write(vfs, `${dir}/limits`, `${[
436
+ "Limit Soft Limit Hard Limit Units",
437
+ "Max cpu time unlimited unlimited seconds",
438
+ "Max file size unlimited unlimited bytes",
439
+ "Max data size unlimited unlimited bytes",
440
+ "Max stack size 8388608 unlimited bytes",
441
+ "Max core file size 0 unlimited bytes",
442
+ "Max resident set unlimited unlimited bytes",
443
+ "Max processes 31968 31968 processes",
444
+ "Max open files 1048576 1048576 files",
445
+ "Max locked memory 8388608 8388608 bytes",
446
+ "Max address space unlimited unlimited bytes",
447
+ "Max file locks unlimited unlimited locks",
448
+ "Max pending signals 31968 31968 signals",
449
+ "Max msgqueue size 819200 819200 bytes",
450
+ "Max nice priority 0 0",
451
+ "Max realtime priority 0 0",
452
+ "Max realtime timeout unlimited unlimited us",
453
+ ].join("\n")}\n`);
454
+ write(vfs, `${dir}/io`, "rchar: 1048576\nwchar: 65536\nsyscr: 512\nsyscw: 64\nread_bytes: 0\nwrite_bytes: 0\ncancelled_write_bytes: 0\n");
455
+ write(vfs, `${dir}/oom_score`, "0\n");
456
+ write(vfs, `${dir}/oom_score_adj`, "0\n");
457
+ write(vfs, `${dir}/loginuid`, "0\n");
458
+ write(vfs, `${dir}/wchan`, "poll_schedule_timeout\n");
459
+ write(vfs, `${dir}/schedstat`, "1000000 0 1\n");
208
460
  for (const fd of ["0", "1", "2"]) {
209
461
  ensureFile(vfs, `${dir}/fd/${fd}`, "");
462
+ ensureFile(vfs, `${dir}/fdinfo/${fd}`, `pos:\t0\nflags:\t0${fd === "0" ? "2" : fd === "1" ? "1" : "1"}02\nmnt_id:\t13\n`);
210
463
  }
211
464
  }
212
465
  // ─── /proc boot log ──────────────────────────────────────────────────────────
213
466
  function bootProcLog(vfs, props) {
214
467
  ensureDir(vfs, "/proc/boot");
215
468
  ensureFile(vfs, "/proc/boot/log", `${[
216
- "[ 0.000000] Linux virtual kernel booting...",
217
- "[ 0.000120] init memory subsystem",
218
- "[ 0.000240] mount /proc /sys /dev",
219
- "[ 0.000420] start init",
220
- "[ 0.000680] system ready",
469
+ `[ 0.000000] Linux version ${props.kernel} (fortune@build) #1 SMP PREEMPT_DYNAMIC`,
470
+ "[ 0.000000] Command line: console=ttyS0 reboot=k panic=1 nomodule random.trust_cpu=1 ipv6.disable=1",
471
+ "[ 0.000060] BIOS-provided physical RAM map:",
472
+ "[ 0.000070] ACPI: RSDP 0x00000000000F05B0 000014 (v00 BOCHS )",
473
+ "[ 0.000120] PCI: Using configuration type 1 for base access",
474
+ "[ 0.000240] clocksource: tsc-early: mask: 0xffffffffffffffff",
475
+ "[ 0.000320] ACPI: IRQ0 used by override",
476
+ "[ 0.000420] Initializing cgroup subsys cpuset",
477
+ "[ 0.000440] Initializing cgroup subsys cpu",
478
+ "[ 0.000450] Initializing cgroup subsys cpuacct",
479
+ "[ 0.000460] Linux agpgart interface v0.103",
480
+ "[ 0.000480] PCI: pci_cache_line_size set to 64 bytes",
481
+ "[ 0.000510] virtio-pci 0000:00:01.0: runtime IRQs not yet assigned",
482
+ "[ 0.000680] NET: Registered PF_INET6 protocol family",
483
+ "[ 0.000720] Freeing unused kernel image (initmem) memory",
484
+ "[ 0.000760] Write protecting the kernel read-only data",
485
+ "[ 0.000800] Run /sbin/init as init process",
486
+ "[ 0.001200] systemd[1]: Detected virtualization kvm",
487
+ "[ 0.001300] systemd[1]: Detected architecture x86-64",
488
+ "[ 0.002000] systemd[1]: Starting Fortune GNU/Linux...",
489
+ "[ 0.003000] systemd[1]: Started Journal Service",
490
+ "[ 0.010000] EXT4-fs (vda): mounted filesystem",
491
+ "[ 0.020000] systemd[1]: Reached target Basic System",
492
+ "[ 0.030000] systemd[1]: Started Login Service",
221
493
  ].join("\n")}\n`);
222
494
  ensureFile(vfs, "/proc/boot/version", `Linux ${props.kernel} (virtual)\n`);
223
495
  }
@@ -238,89 +510,243 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions = [])
238
510
  const totalMemKb = Math.floor(os.totalmem() / 1024);
239
511
  const freeMemKb = Math.floor(os.freemem() / 1024);
240
512
  const availMemKb = Math.floor(freeMemKb * 0.95);
513
+ const buffersKb = Math.floor(totalMemKb * 0.03);
514
+ const cachedKb = Math.floor(totalMemKb * 0.08);
515
+ const shmemKb = Math.floor(totalMemKb * 0.005);
516
+ const slabKb = Math.floor(totalMemKb * 0.02);
517
+ const pageTablesKb = Math.floor(totalMemKb * 0.001);
241
518
  write(vfs, "/proc/meminfo", `${[
242
519
  `MemTotal: ${String(totalMemKb).padStart(10)} kB`,
243
520
  `MemFree: ${String(freeMemKb).padStart(10)} kB`,
244
521
  `MemAvailable: ${String(availMemKb).padStart(10)} kB`,
245
- `Buffers: ${String(Math.floor(totalMemKb * 0.02)).padStart(10)} kB`,
246
- `Cached: ${String(Math.floor(totalMemKb * 0.15)).padStart(10)} kB`,
247
- `SwapTotal: ${String(Math.floor(totalMemKb * 0.5)).padStart(10)} kB`,
248
- `SwapFree: ${String(Math.floor(totalMemKb * 0.5)).padStart(10)} kB`,
522
+ `Buffers: ${String(buffersKb).padStart(10)} kB`,
523
+ `Cached: ${String(cachedKb).padStart(10)} kB`,
524
+ `SwapCached: ${String(0).padStart(10)} kB`,
525
+ `Active: ${String(Math.floor((buffersKb + cachedKb) * 1.2)).padStart(10)} kB`,
526
+ `Inactive: ${String(Math.floor(cachedKb * 0.6)).padStart(10)} kB`,
527
+ `Active(anon): ${String(Math.floor(totalMemKb * 0.001)).padStart(10)} kB`,
528
+ `Inactive(anon): ${String(Math.floor(totalMemKb * 0.006)).padStart(10)} kB`,
529
+ `Active(file): ${String(Math.floor(cachedKb * 1.2)).padStart(10)} kB`,
530
+ `Inactive(file): ${String(Math.floor(cachedKb * 0.6)).padStart(10)} kB`,
531
+ `Unevictable: ${String(0).padStart(10)} kB`,
532
+ `Mlocked: ${String(0).padStart(10)} kB`,
533
+ `SwapTotal: ${String(0).padStart(10)} kB`,
534
+ `SwapFree: ${String(0).padStart(10)} kB`,
535
+ `Zswap: ${String(0).padStart(10)} kB`,
536
+ `Zswapped: ${String(0).padStart(10)} kB`,
537
+ `Dirty: ${String(Math.floor(Math.random() * 64)).padStart(10)} kB`,
538
+ `Writeback: ${String(0).padStart(10)} kB`,
539
+ `AnonPages: ${String(Math.floor(totalMemKb * 0.001)).padStart(10)} kB`,
540
+ `Mapped: ${String(Math.floor(cachedKb * 0.4)).padStart(10)} kB`,
541
+ `Shmem: ${String(shmemKb).padStart(10)} kB`,
542
+ `KReclaimable: ${String(Math.floor(slabKb * 0.6)).padStart(10)} kB`,
543
+ `Slab: ${String(slabKb).padStart(10)} kB`,
544
+ `SReclaimable: ${String(Math.floor(slabKb * 0.6)).padStart(10)} kB`,
545
+ `SUnreclaim: ${String(Math.floor(slabKb * 0.4)).padStart(10)} kB`,
546
+ `KernelStack: ${String(Math.floor(totalMemKb * 0.0005)).padStart(10)} kB`,
547
+ `PageTables: ${String(pageTablesKb).padStart(10)} kB`,
548
+ `NFS_Unstable: ${String(0).padStart(10)} kB`,
549
+ `Bounce: ${String(0).padStart(10)} kB`,
550
+ `WritebackTmp: ${String(0).padStart(10)} kB`,
551
+ `CommitLimit: ${String(Math.floor(totalMemKb * 0.5)).padStart(10)} kB`,
552
+ `Committed_AS: ${String(Math.floor(totalMemKb * 0.05)).padStart(10)} kB`,
553
+ `VmallocTotal: ${String(35184372087808 / 1024).padStart(10)} kB`,
554
+ `VmallocUsed: ${String(Math.floor(totalMemKb * 0.01)).padStart(10)} kB`,
555
+ `VmallocChunk: ${String(0).padStart(10)} kB`,
556
+ `Percpu: ${String(Math.floor(totalMemKb * 0.0001)).padStart(10)} kB`,
557
+ `HardwareCorrupted: ${String(0).padStart(6)} kB`,
558
+ `AnonHugePages: ${String(0).padStart(10)} kB`,
559
+ `ShmemHugePages: ${String(0).padStart(10)} kB`,
560
+ `ShmemPmdMapped: ${String(0).padStart(10)} kB`,
561
+ `FileHugePages: ${String(0).padStart(10)} kB`,
562
+ `FilePmdMapped: ${String(0).padStart(10)} kB`,
563
+ `HugePages_Total: ${String(0).padStart(8)}`,
564
+ `HugePages_Free: ${String(0).padStart(8)}`,
565
+ `HugePages_Rsvd: ${String(0).padStart(8)}`,
566
+ `HugePages_Surp: ${String(0).padStart(8)}`,
567
+ `Hugepagesize: ${String(2048).padStart(10)} kB`,
568
+ `Hugetlb: ${String(0).padStart(10)} kB`,
569
+ `DirectMap4k: ${String(Math.floor(totalMemKb * 0.02)).padStart(10)} kB`,
570
+ `DirectMap2M: ${String(Math.floor(totalMemKb * 0.98)).padStart(10)} kB`,
249
571
  ].join("\n")}\n`);
250
- // cpuinfo — real host CPU passthrough
572
+ // cpuinfo — real host CPU passthrough + x86 feature flags matching Firecracker Xeon
251
573
  const cpus = os.cpus();
252
574
  const cpuLines = [];
253
575
  for (let i = 0; i < cpus.length; i++) {
254
576
  const c = cpus[i];
255
577
  if (!c)
256
578
  continue;
257
- cpuLines.push(`processor\t: ${i}`, `model name\t: ${c.model}`, `cpu MHz\t\t: ${c.speed.toFixed(3)}`, `cache size\t: 8192 KB`, "");
579
+ cpuLines.push(`processor\t: ${i}`, `vendor_id\t: GenuineIntel`, `cpu family\t: 6`, `model\t\t: 85`, `model name\t: ${c.model}`, `stepping\t: 7`, `microcode\t: 0x1`, `cpu MHz\t\t: ${c.speed.toFixed(3)}`, `cache size\t: 33792 KB`, `physical id\t: 0`, `siblings\t: ${cpus.length}`, `core id\t\t: ${i}`, `cpu cores\t: ${cpus.length}`, `apicid\t\t: ${i}`, `initial apicid\t: ${i}`, `fpu\t\t: yes`, `fpu_exception\t: yes`, `cpuid level\t: 13`, `wp\t\t: yes`, `flags\t\t: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves arat umip avx512_vnni md_clear arch_capabilities`, `bugs\t\t: spectre_v1 spectre_v2 spec_store_bypass swapgs taa mmio_stale_data retbleed eibrs_pbrsb bhi ibpb_no_ret spectre_v2_user its`, `bogomips\t: ${(c.speed * 2 / 1000).toFixed(2)}`, `clflush size\t: 64`, `cache_alignment\t: 64`, `address sizes\t: 46 bits physical, 48 bits virtual`, `power management:`, "");
258
580
  }
259
581
  write(vfs, "/proc/cpuinfo", `${cpuLines.join("\n")}\n`);
260
- write(vfs, "/proc/version", `Linux version ${props.kernel} (fortune@build) (gcc version 12.2.0) #1 SMP\n`);
582
+ write(vfs, "/proc/version", `Linux version ${props.kernel} (fortune@nyx-build) (gcc (Fortune 13.3.0-nyx1) 13.3.0, GNU ld (GNU Binutils for Fortune) 2.42) #2 SMP PREEMPT_DYNAMIC\n`);
261
583
  write(vfs, "/proc/hostname", `${hostname}\n`);
262
- // loadavg — slightly random but bounded
263
- const load = (Math.random() * 0.5).toFixed(2);
584
+ // loadavg
585
+ const load = (Math.random() * 0.3).toFixed(2);
264
586
  const numProcs = 1 + sessions.length;
265
587
  write(vfs, "/proc/loadavg", `${load} ${load} ${load} ${numProcs}/${numProcs} 1\n`);
266
- // /proc/net stubs
267
- ensureDir(vfs, "/proc/net");
268
- ensureFile(vfs, "/proc/net/dev", `${[
269
- "Inter-| Receive | Transmit",
270
- " face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed",
271
- " lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
272
- " eth0: 131072 1024 0 0 0 0 0 0 65536 512 0 0 0 0 0 0",
588
+ // /proc/cmdline — Firecracker boot args
589
+ write(vfs, "/proc/cmdline", `console=ttyS0 reboot=k panic=1 nomodule random.trust_cpu=1 ipv6.disable=1 swiotlb=noforce rdinit=/process_api init_on_free=1 -- --firecracker-init --addr 0.0.0.0:2024 --max-ws-buffer-size 32768 --block-local-connections\n`);
590
+ // /proc/filesystems — matching real container
591
+ write(vfs, "/proc/filesystems", `${[
592
+ "nodev\tsysfs",
593
+ "nodev\ttmpfs",
594
+ "nodev\tbdev",
595
+ "nodev\tproc",
596
+ "nodev\tcgroup",
597
+ "nodev\tcgroup2",
598
+ "nodev\tcpuset",
599
+ "nodev\tdevtmpfs",
600
+ "nodev\tbinfmt_misc",
601
+ "nodev\tdebugfs",
602
+ "nodev\tsecurityfs",
603
+ "nodev\tsockfs",
604
+ "nodev\tbpf",
605
+ "nodev\tpipefs",
606
+ "nodev\tramfs",
607
+ "nodev\thugetlbfs",
608
+ "nodev\trpc_pipefs",
609
+ "nodev\tdevpts",
610
+ "\text3",
611
+ "\text2",
612
+ "\text4",
613
+ "\tsquashfs",
614
+ "nodev\tnfs",
615
+ "nodev\tnfs4",
616
+ "nodev\tautofs",
617
+ "\tfuseblk",
618
+ "nodev\tfuse",
619
+ "nodev\tfusectl",
620
+ "nodev\toverlay",
621
+ "\txfs",
622
+ "nodev\tmqueue",
623
+ "nodev\tselinuxfs",
624
+ "nodev\tpstore",
273
625
  ].join("\n")}\n`);
274
- ensureFile(vfs, "/proc/net/if_inet6", "");
275
- ensureFile(vfs, "/proc/net/tcp", " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
276
- ensureFile(vfs, "/proc/net/tcp6", " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
277
- // /proc/cmdline — kernel boot args
278
- write(vfs, "/proc/cmdline", `BOOT_IMAGE=/boot/vmlinuz-${props.kernel} root=/dev/sda2 ro quiet splash\n`);
279
- // /proc/filesystems
280
- ensureFile(vfs, "/proc/filesystems", `${["nodev\tsysfs", "nodev\ttmpfs", "nodev\tproc", "nodev\tdevtmpfs", "\text4", "\tvfat", "nodev\tsquashfs", "nodev\toverlay"].join("\n")}\n`);
281
- // /proc/mounts (= /proc/self/mounts)
626
+ // /proc/mounts — virtio block device layout
282
627
  const mountsContent = `${[
283
- "sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0",
284
- "proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0",
285
- "devtmpfs /dev devtmpfs rw,nosuid,size=8192k,nr_inodes=4096,mode=755 0 0",
628
+ "proc /proc proc rw,relatime 0 0",
629
+ "sysfs /sys sysfs rw,relatime 0 0",
630
+ "devtmpfs /dev devtmpfs rw,relatime,size=2045672k,nr_inodes=511418,mode=755 0 0",
631
+ "tmpfs /dev/shm tmpfs rw,relatime 0 0",
632
+ "devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0",
633
+ "tmpfs /sys/fs/cgroup tmpfs rw,relatime 0 0",
634
+ "cgroup /sys/fs/cgroup/cpu cgroup rw,relatime,cpu 0 0",
635
+ "cgroup /sys/fs/cgroup/cpuacct cgroup rw,relatime,cpuacct 0 0",
636
+ "cgroup /sys/fs/cgroup/memory cgroup rw,relatime,memory 0 0",
637
+ "cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0",
638
+ "cgroup /sys/fs/cgroup/freezer cgroup rw,relatime,freezer 0 0",
639
+ "cgroup /sys/fs/cgroup/blkio cgroup rw,relatime,blkio 0 0",
640
+ "cgroup /sys/fs/cgroup/pids cgroup rw,relatime,pids 0 0",
641
+ "cgroup2 /sys/fs/cgroup/unified cgroup2 rw,relatime 0 0",
642
+ "/dev/vda / ext4 rw,relatime,resuid=65534,resgid=65534 0 0",
643
+ "/dev/vdb /opt/rclone squashfs ro,relatime,errors=continue 0 0",
286
644
  "tmpfs /run tmpfs rw,nosuid,nodev,noexec,relatime,size=204800k,mode=755 0 0",
287
645
  "tmpfs /tmp tmpfs rw,nosuid,nodev,noatime 0 0",
288
- "/dev/sda2 / ext4 rw,relatime,errors=remount-ro 0 0",
289
- "/dev/sda1 /boot ext4 rw,relatime 0 0",
290
- "tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0",
291
- "devpts /dev/pts devpts rw,nosuid,noexec,relatime,mode=620,ptmxmode=000 0 0",
292
- "squashfs /snap/core squashfs ro,nodev,relatime 0 0",
293
646
  ].join("\n")}\n`;
294
647
  write(vfs, "/proc/mounts", mountsContent);
295
648
  ensureDir(vfs, "/proc/self");
296
649
  write(vfs, "/proc/self/mounts", mountsContent);
297
- // /proc/partitions
650
+ // /proc/net
651
+ ensureDir(vfs, "/proc/net");
652
+ write(vfs, "/proc/net/dev", `${[
653
+ "Inter-| Receive | Transmit",
654
+ " face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed",
655
+ " lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
656
+ " eth0: 128628 1230 0 19 0 0 0 0 52027469 2045 0 0 0 0 0 0",
657
+ ].join("\n")}\n`);
658
+ write(vfs, "/proc/net/if_inet6", "");
659
+ write(vfs, "/proc/net/tcp", " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
660
+ write(vfs, "/proc/net/tcp6", " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
661
+ write(vfs, "/proc/net/udp", " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
662
+ write(vfs, "/proc/net/route", "Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\n" +
663
+ "eth0\t00000000\t0101A8C0\t0003\t0\t0\t100\t00000000\t0\t0\t0\n" +
664
+ "eth0\t0000A8C0\t00000000\t0001\t0\t0\t100\t00FFFFFF\t0\t0\t0\n");
665
+ write(vfs, "/proc/net/arp", "IP address HW type Flags HW address Mask Device\n");
666
+ write(vfs, "/proc/net/fib_trie", "Local:\n +-- 0.0.0.0/0 3 0 5\n");
667
+ write(vfs, "/proc/net/unix", "Num RefCount Protocol Flags Type St Inode Path\n" +
668
+ "0000000000000000: 00000002 00000000 00010000 0001 01 10000 /run/dbus/system_bus_socket\n");
669
+ write(vfs, "/proc/net/sockstat", "sockets: used 8\nTCP: inuse 0 orphan 0 tw 0 alloc 0 mem 0\nUDP: inuse 0 mem 0\nUDPLITE: inuse 0\nRAW: inuse 0\nFRAG: inuse 0 memory 0\n");
670
+ // /proc/partitions — virtio block devices
298
671
  write(vfs, "/proc/partitions", `${[
299
672
  "major minor #blocks name",
300
673
  "",
301
- " 8 0 41943040 sda",
302
- " 8 1 524288 sda1",
303
- " 8 2 41417216 sda2",
304
- " 7 0 10485760 loop0",
674
+ " 254 0 268435456 vda",
675
+ " 254 16 9664 vdb",
676
+ " 254 32 656 vdc",
677
+ " 254 48 5464 vdd",
678
+ ].join("\n")}\n`);
679
+ // /proc/swaps — no swap (matches real env: SwapTotal 0)
680
+ write(vfs, "/proc/swaps", "Filename\t\t\t\tType\t\tSize\t\tUsed\t\tPriority\n");
681
+ // /proc/diskstats — virtio block device I/O counters
682
+ write(vfs, "/proc/diskstats", `${[
683
+ " 254 0 vda 1000 0 8000 500 200 0 1600 100 0 600 600 0 0 0 0",
684
+ " 254 16 vdb 100 0 800 50 0 0 0 0 0 50 50 0 0 0 0",
685
+ " 254 32 vdc 50 0 400 25 0 0 0 0 0 25 25 0 0 0 0",
686
+ " 254 48 vdd 80 0 640 40 0 0 0 0 0 40 40 0 0 0 0",
305
687
  ].join("\n")}\n`);
306
- // /proc/swaps
307
- write(vfs, "/proc/swaps", "Filename\t\t\t\tType\t\tSize\t\tUsed\t\tPriority\n" +
308
- `/dev/sda3\t\t\t\tpartition\t${Math.floor(os.totalmem() / 2048)}\t\t0\t\t-2\n`);
309
- // /proc/sys — sysctl virtual tree
688
+ // /proc/interrupts
689
+ write(vfs, "/proc/interrupts", ` CPU0\n 0: ${Math.floor(uptimeSec * 250)} IO-APIC 2-edge timer\n 1: 9 IO-APIC 1-edge i8042\n NMI: 0 Non-maskable interrupts\n ERR: 0\n MIS: 0\n PIN: 0 Posted-interrupt notification event\n NPI: 0 Nested posted-interrupt event\n PIW: 0 Posted-interrupt wakeup event\n`);
690
+ // /proc/sys — sysctl virtual tree (real values)
310
691
  ensureDir(vfs, "/proc/sys");
311
692
  ensureDir(vfs, "/proc/sys/kernel");
312
693
  ensureDir(vfs, "/proc/sys/net");
694
+ ensureDir(vfs, "/proc/sys/net/ipv4");
695
+ ensureDir(vfs, "/proc/sys/net/ipv6");
696
+ ensureDir(vfs, "/proc/sys/net/core");
313
697
  ensureDir(vfs, "/proc/sys/vm");
314
- ensureFile(vfs, "/proc/sys/kernel/hostname", `${hostname}\n`);
315
- ensureFile(vfs, "/proc/sys/kernel/ostype", "Linux\n");
316
- ensureFile(vfs, "/proc/sys/kernel/osrelease", `${props.kernel}\n`);
317
- ensureFile(vfs, "/proc/sys/kernel/pid_max", "32768\n");
318
- ensureFile(vfs, "/proc/sys/kernel/threads-max", "65536\n");
319
- ensureFile(vfs, "/proc/sys/kernel/randomize_va_space", "2\n");
320
- ensureFile(vfs, "/proc/sys/kernel/dmesg_restrict", "0\n");
321
- ensureFile(vfs, "/proc/sys/net/ipv4/ip_forward", "0\n");
322
- ensureFile(vfs, "/proc/sys/vm/swappiness", "60\n");
323
- ensureFile(vfs, "/proc/sys/vm/overcommit_memory", "0\n");
698
+ ensureDir(vfs, "/proc/sys/fs");
699
+ ensureDir(vfs, "/proc/sys/fs/inotify");
700
+ write(vfs, "/proc/sys/kernel/hostname", `${hostname}\n`);
701
+ write(vfs, "/proc/sys/kernel/ostype", "Linux\n");
702
+ write(vfs, "/proc/sys/kernel/osrelease", `${props.kernel}\n`);
703
+ write(vfs, "/proc/sys/kernel/pid_max", "32768\n");
704
+ write(vfs, "/proc/sys/kernel/threads-max", "31968\n");
705
+ write(vfs, "/proc/sys/kernel/randomize_va_space", "2\n");
706
+ write(vfs, "/proc/sys/kernel/dmesg_restrict", "0\n");
707
+ write(vfs, "/proc/sys/kernel/kptr_restrict", "0\n");
708
+ write(vfs, "/proc/sys/kernel/perf_event_paranoid", "2\n");
709
+ write(vfs, "/proc/sys/kernel/printk", "4\t4\t1\t7\n");
710
+ write(vfs, "/proc/sys/kernel/sysrq", "176\n");
711
+ write(vfs, "/proc/sys/kernel/panic", "1\n");
712
+ write(vfs, "/proc/sys/kernel/panic_on_oops", "1\n");
713
+ write(vfs, "/proc/sys/kernel/core_pattern", "core\n");
714
+ write(vfs, "/proc/sys/kernel/core_uses_pid", "0\n");
715
+ write(vfs, "/proc/sys/kernel/ngroups_max", "65536\n");
716
+ write(vfs, "/proc/sys/kernel/cap_last_cap", "40\n");
717
+ write(vfs, "/proc/sys/kernel/unprivileged_userns_clone", "1\n");
718
+ write(vfs, "/proc/sys/net/ipv4/ip_forward", "0\n");
719
+ write(vfs, "/proc/sys/net/ipv4/tcp_syncookies", "1\n");
720
+ write(vfs, "/proc/sys/net/ipv4/tcp_fin_timeout", "60\n");
721
+ write(vfs, "/proc/sys/net/ipv4/tcp_keepalive_time", "7200\n");
722
+ write(vfs, "/proc/sys/net/ipv4/conf/all/rp_filter", "2\n");
723
+ write(vfs, "/proc/sys/net/ipv6/conf/all/disable_ipv6", "1\n");
724
+ write(vfs, "/proc/sys/net/core/somaxconn", "4096\n");
725
+ write(vfs, "/proc/sys/net/core/rmem_max", "212992\n");
726
+ write(vfs, "/proc/sys/net/core/wmem_max", "212992\n");
727
+ write(vfs, "/proc/sys/vm/swappiness", "60\n");
728
+ write(vfs, "/proc/sys/vm/overcommit_memory", "0\n");
729
+ write(vfs, "/proc/sys/vm/overcommit_ratio", "50\n");
730
+ write(vfs, "/proc/sys/vm/dirty_ratio", "20\n");
731
+ write(vfs, "/proc/sys/vm/dirty_background_ratio", "10\n");
732
+ write(vfs, "/proc/sys/vm/min_free_kbytes", "65536\n");
733
+ write(vfs, "/proc/sys/vm/vfs_cache_pressure", "100\n");
734
+ write(vfs, "/proc/sys/fs/file-max", "1048576\n");
735
+ write(vfs, "/proc/sys/fs/inotify/max_user_watches", "524288\n");
736
+ write(vfs, "/proc/sys/fs/inotify/max_user_instances", "512\n");
737
+ write(vfs, "/proc/sys/fs/inotify/max_queued_events", "16384\n");
738
+ // /proc/cgroups — v1 hierarchy
739
+ write(vfs, "/proc/cgroups", `${[
740
+ "#subsys_name\thierarchy\tnum_cgroups\tenabled",
741
+ "cpuset\t5\t1\t1",
742
+ "cpu\t1\t1\t1",
743
+ "cpuacct\t2\t1\t1",
744
+ "blkio\t6\t1\t1",
745
+ "memory\t3\t1\t1",
746
+ "devices\t4\t1\t1",
747
+ "freezer\t7\t1\t1",
748
+ "pids\t8\t1\t1",
749
+ ].join("\n")}\n`);
324
750
  // init process (PID 1)
325
751
  writeProcPid(vfs, 1, "root", "pts/0", "/sbin/init", new Date(shellStartTime).toISOString(), {});
326
752
  // per-session processes
@@ -331,19 +757,22 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions = [])
331
757
  HOME: `/home/${session.username}`,
332
758
  TERM: "xterm-256color",
333
759
  SHELL: "/bin/bash",
760
+ LANG: "en_US.UTF-8",
761
+ PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
762
+ LOGNAME: session.username,
334
763
  });
335
764
  }
336
- // /proc/self — mirror of most recent session, or init
765
+ // /proc/self — mirror of most recent session
337
766
  const selfPid = sessions.length > 0 ? ttyToPid(sessions[sessions.length - 1].tty) : 1;
338
- if (vfs.exists("/proc/self")) {
339
- try {
340
- vfs.remove("/proc/self");
341
- }
342
- catch { /* ignore */ }
767
+ try {
768
+ vfs.remove("/proc/self");
343
769
  }
770
+ catch { /* ignore */ }
344
771
  const selfSrc = `/proc/${selfPid}`;
345
772
  ensureDir(vfs, "/proc/self");
346
773
  ensureDir(vfs, "/proc/self/fd");
774
+ ensureDir(vfs, "/proc/self/fdinfo");
775
+ ensureDir(vfs, "/proc/self/net");
347
776
  if (vfs.exists(selfSrc)) {
348
777
  for (const entry of vfs.list(selfSrc)) {
349
778
  const srcPath = `${selfSrc}/${entry}`;
@@ -353,11 +782,10 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions = [])
353
782
  if (st.type === "file")
354
783
  write(vfs, dstPath, vfs.readFile(srcPath));
355
784
  }
356
- catch { /* skip unreadable entries */ }
785
+ catch { /* skip */ }
357
786
  }
358
787
  }
359
788
  else {
360
- // Minimal fallback
361
789
  write(vfs, "/proc/self/cmdline", "bash\0");
362
790
  write(vfs, "/proc/self/comm", "bash");
363
791
  write(vfs, "/proc/self/status", "Name:\tbash\nState:\tS (sleeping)\nPid:\t1\nPPid:\t0\n");
@@ -369,11 +797,70 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions = [])
369
797
  // ─── /sys ─────────────────────────────────────────────────────────────────────
370
798
  function bootstrapSys(vfs, hostname, props) {
371
799
  ensureDir(vfs, "/sys");
800
+ // No real DMI in Firecracker — /sys/devices/virtual/dmi/id does not exist.
801
+ // Expose /sys/class/net, /sys/fs/cgroup, /sys/kernel only.
372
802
  ensureDir(vfs, "/sys/devices");
373
803
  ensureDir(vfs, "/sys/devices/virtual");
804
+ ensureDir(vfs, "/sys/devices/system");
805
+ ensureDir(vfs, "/sys/devices/system/cpu");
806
+ ensureDir(vfs, "/sys/devices/system/cpu/cpu0");
807
+ ensureFile(vfs, "/sys/devices/system/cpu/cpu0/online", "1\n");
808
+ ensureFile(vfs, "/sys/devices/system/cpu/online", "0\n");
809
+ ensureFile(vfs, "/sys/devices/system/cpu/possible", "0\n");
810
+ ensureFile(vfs, "/sys/devices/system/cpu/present", "0\n");
811
+ ensureDir(vfs, "/sys/devices/system/node");
812
+ ensureDir(vfs, "/sys/devices/system/node/node0");
813
+ ensureFile(vfs, "/sys/devices/system/node/node0/cpumap", "1\n");
814
+ ensureDir(vfs, "/sys/class");
815
+ ensureDir(vfs, "/sys/class/net");
816
+ ensureDir(vfs, "/sys/class/net/eth0");
817
+ ensureFile(vfs, "/sys/class/net/eth0/operstate", "up\n");
818
+ ensureFile(vfs, "/sys/class/net/eth0/carrier", "1\n");
819
+ ensureFile(vfs, "/sys/class/net/eth0/mtu", "1500\n");
820
+ ensureFile(vfs, "/sys/class/net/eth0/speed", "10000\n");
821
+ ensureFile(vfs, "/sys/class/net/eth0/duplex", "full\n");
822
+ ensureFile(vfs, "/sys/class/net/eth0/address", "aa:bb:cc:dd:ee:ff\n");
823
+ ensureFile(vfs, "/sys/class/net/eth0/tx_queue_len", "1000\n");
824
+ const seed = fnv1a(hostname);
825
+ const macSeed = seed.toString(16).padStart(8, "0");
826
+ ensureFile(vfs, "/sys/class/net/eth0/address", `52:54:00:${macSeed.slice(0, 2)}:${macSeed.slice(2, 4)}:${macSeed.slice(4, 6)}\n`);
827
+ ensureDir(vfs, "/sys/class/net/lo");
828
+ ensureFile(vfs, "/sys/class/net/lo/operstate", "unknown\n");
829
+ ensureFile(vfs, "/sys/class/net/lo/carrier", "1\n");
830
+ ensureFile(vfs, "/sys/class/net/lo/mtu", "65536\n");
831
+ ensureFile(vfs, "/sys/class/net/lo/address", "00:00:00:00:00:00\n");
832
+ ensureDir(vfs, "/sys/class/block");
833
+ ensureDir(vfs, "/sys/class/block/vda");
834
+ ensureFile(vfs, "/sys/class/block/vda/size", "536870912\n");
835
+ ensureFile(vfs, "/sys/class/block/vda/ro", "0\n");
836
+ ensureFile(vfs, "/sys/class/block/vda/removable", "0\n");
837
+ // cgroup fs
838
+ ensureDir(vfs, "/sys/fs");
839
+ ensureDir(vfs, "/sys/fs/cgroup");
840
+ for (const subsys of ["cpu", "cpuacct", "memory", "devices", "blkio", "pids", "freezer", "unified"]) {
841
+ ensureDir(vfs, `/sys/fs/cgroup/${subsys}`);
842
+ if (subsys !== "unified") {
843
+ ensureFile(vfs, `/sys/fs/cgroup/${subsys}/tasks`, "1\n");
844
+ ensureFile(vfs, `/sys/fs/cgroup/${subsys}/notify_on_release`, "0\n");
845
+ ensureFile(vfs, `/sys/fs/cgroup/${subsys}/release_agent`, "");
846
+ }
847
+ }
848
+ ensureFile(vfs, "/sys/fs/cgroup/memory/memory.limit_in_bytes", `${os.totalmem()}\n`);
849
+ ensureFile(vfs, "/sys/fs/cgroup/memory/memory.usage_in_bytes", `${os.totalmem() - os.freemem()}\n`);
850
+ ensureFile(vfs, "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes", `${os.totalmem()}\n`);
851
+ ensureFile(vfs, "/sys/fs/cgroup/cpu/cpu.cfs_period_us", "100000\n");
852
+ ensureFile(vfs, "/sys/fs/cgroup/cpu/cpu.cfs_quota_us", "-1\n");
853
+ ensureFile(vfs, "/sys/fs/cgroup/cpu/cpu.shares", "1024\n");
854
+ ensureDir(vfs, "/sys/kernel");
855
+ ensureFile(vfs, "/sys/kernel/hostname", `${hostname}\n`);
856
+ ensureFile(vfs, "/sys/kernel/osrelease", `${props.kernel}\n`);
857
+ ensureFile(vfs, "/sys/kernel/ostype", "Linux\n");
858
+ // security
859
+ ensureDir(vfs, "/sys/kernel/security");
860
+ // Still; we will create virtual dmi
861
+ ensureDir(vfs, "/sys/devices/virtual");
374
862
  ensureDir(vfs, "/sys/devices/virtual/dmi");
375
863
  ensureDir(vfs, "/sys/devices/virtual/dmi/id");
376
- const seed = fnv1a(hostname);
377
864
  const product = `VirtualNode-${(seed % 10000).toString().padStart(4, "0")}`;
378
865
  // Full DMI table — deterministic, seeded from hostname
379
866
  const dmi = {
@@ -405,34 +892,62 @@ function bootstrapSys(vfs, hostname, props) {
405
892
  // ─── /dev ─────────────────────────────────────────────────────────────────────
406
893
  function bootstrapDev(vfs) {
407
894
  ensureDir(vfs, "/dev");
408
- // character devices
895
+ // character devices — matching real Firecracker container
409
896
  ensureFile(vfs, "/dev/null", "", 0o666);
410
897
  ensureFile(vfs, "/dev/zero", "", 0o666);
411
898
  ensureFile(vfs, "/dev/full", "", 0o666);
412
899
  ensureFile(vfs, "/dev/random", "", 0o444);
413
900
  ensureFile(vfs, "/dev/urandom", "", 0o444);
414
901
  ensureFile(vfs, "/dev/mem", "", 0o640);
902
+ ensureFile(vfs, "/dev/port", "", 0o640);
903
+ ensureFile(vfs, "/dev/kmsg", "", 0o660);
904
+ ensureFile(vfs, "/dev/hwrng", "", 0o660);
905
+ ensureFile(vfs, "/dev/fuse", "", 0o660);
906
+ ensureFile(vfs, "/dev/autofs", "", 0o660);
907
+ ensureFile(vfs, "/dev/userfaultfd", "", 0o660);
908
+ ensureFile(vfs, "/dev/cpu_dma_latency", "", 0o660);
909
+ ensureFile(vfs, "/dev/ptp0", "", 0o660);
910
+ // snapshot (KVM-specific)
911
+ ensureFile(vfs, "/dev/snapshot", "", 0o660);
415
912
  // terminal devices
416
913
  ensureFile(vfs, "/dev/console", "", 0o600);
417
914
  ensureFile(vfs, "/dev/tty", "", 0o666);
418
- ensureFile(vfs, "/dev/tty0", "", 0o620);
419
- ensureFile(vfs, "/dev/tty1", "", 0o620);
420
915
  ensureFile(vfs, "/dev/ttyS0", "", 0o660);
421
- // loop devices
916
+ ensureFile(vfs, "/dev/ptmx", "", 0o666);
917
+ // tty0–63 (like real env)
918
+ for (let i = 0; i <= 63; i++) {
919
+ ensureFile(vfs, `/dev/tty${i}`, "", 0o620);
920
+ }
921
+ // vcs devices
922
+ ensureFile(vfs, "/dev/vcs", "", 0o620);
923
+ ensureFile(vfs, "/dev/vcs1", "", 0o620);
924
+ ensureFile(vfs, "/dev/vcsa", "", 0o620);
925
+ ensureFile(vfs, "/dev/vcsa1", "", 0o620);
926
+ ensureFile(vfs, "/dev/vcsu", "", 0o620);
927
+ ensureFile(vfs, "/dev/vcsu1", "", 0o620);
928
+ // loop devices (0–7)
422
929
  for (let i = 0; i < 8; i++) {
423
930
  ensureFile(vfs, `/dev/loop${i}`, "", 0o660);
424
931
  }
425
932
  ensureDir(vfs, "/dev/loop-control");
426
- // block device stubs (sda + partitions)
427
- ensureFile(vfs, "/dev/sda", "", 0o660);
428
- ensureFile(vfs, "/dev/sda1", "", 0o660);
429
- ensureFile(vfs, "/dev/sda2", "", 0o660);
933
+ // virtio block devices (vda–vdd matching mounts)
934
+ ensureFile(vfs, "/dev/vda", "", 0o660);
935
+ ensureFile(vfs, "/dev/vdb", "", 0o660);
936
+ ensureFile(vfs, "/dev/vdc", "", 0o660);
937
+ ensureFile(vfs, "/dev/vdd", "", 0o660);
938
+ // network tun
939
+ ensureDir(vfs, "/dev/net");
940
+ ensureFile(vfs, "/dev/net/tun", "", 0o660);
430
941
  // misc
431
942
  ensureDir(vfs, "/dev/pts");
432
943
  ensureDir(vfs, "/dev/shm");
944
+ ensureDir(vfs, "/dev/cpu");
433
945
  ensureFile(vfs, "/dev/stdin", "", 0o666);
434
946
  ensureFile(vfs, "/dev/stdout", "", 0o666);
435
947
  ensureFile(vfs, "/dev/stderr", "", 0o666);
948
+ ensureDir(vfs, "/dev/fd");
949
+ ensureFile(vfs, "/dev/vga_arbiter", "", 0o660);
950
+ ensureFile(vfs, "/dev/vsock", "", 0o660);
436
951
  }
437
952
  // ─── /usr ─────────────────────────────────────────────────────────────────────
438
953
  function bootstrapUsr(vfs) {
@@ -443,12 +958,30 @@ function bootstrapUsr(vfs) {
443
958
  ensureDir(vfs, "/usr/local/bin");
444
959
  ensureDir(vfs, "/usr/local/lib");
445
960
  ensureDir(vfs, "/usr/local/share");
961
+ ensureDir(vfs, "/usr/local/include");
962
+ ensureDir(vfs, "/usr/local/sbin");
446
963
  ensureDir(vfs, "/usr/share");
447
964
  ensureDir(vfs, "/usr/share/doc");
448
965
  ensureDir(vfs, "/usr/share/man");
449
966
  ensureDir(vfs, "/usr/share/man/man1");
967
+ ensureDir(vfs, "/usr/share/man/man5");
968
+ ensureDir(vfs, "/usr/share/man/man8");
969
+ ensureDir(vfs, "/usr/share/common-licenses");
970
+ ensureDir(vfs, "/usr/share/ca-certificates");
971
+ ensureDir(vfs, "/usr/share/zoneinfo");
450
972
  ensureDir(vfs, "/usr/lib");
451
- // Stubs so `which` can resolve built-in commands
973
+ ensureDir(vfs, "/usr/lib/x86_64-linux-gnu");
974
+ ensureDir(vfs, "/usr/lib/python3");
975
+ ensureDir(vfs, "/usr/lib/python3/dist-packages");
976
+ ensureDir(vfs, "/usr/lib/python3.12");
977
+ ensureDir(vfs, "/usr/lib/jvm");
978
+ ensureDir(vfs, "/usr/lib/jvm/java-21-openjdk-amd64");
979
+ ensureDir(vfs, "/usr/lib/jvm/java-21-openjdk-amd64/bin");
980
+ ensureDir(vfs, "/usr/lib/node_modules");
981
+ ensureDir(vfs, "/usr/lib/node_modules/npm");
982
+ ensureDir(vfs, "/usr/include");
983
+ ensureDir(vfs, "/usr/src");
984
+ // builtins — all bins present in the real container
452
985
  const builtins = [
453
986
  "sh", "bash", "ls", "cat", "echo", "grep", "find", "sort",
454
987
  "head", "tail", "cut", "tr", "sed", "awk", "wc", "tee",
@@ -457,50 +990,433 @@ function bootstrapUsr(vfs) {
457
990
  "hostname", "uname", "ps", "kill", "df", "du", "curl", "wget",
458
991
  "nano", "diff", "uniq", "xargs", "base64",
459
992
  ];
993
+ // From a real container
994
+ // const builtins = [
995
+ // // core
996
+ // "sh", "bash", "dash",
997
+ // "ls", "cat", "echo", "grep", "find", "sort",
998
+ // "head", "tail", "cut", "tr", "sed", "awk", "mawk", "gawk",
999
+ // "wc", "tee", "tar", "gzip", "gunzip", "bzip2", "xz",
1000
+ // "touch", "mkdir", "rm", "mv", "cp", "ln", "pwd",
1001
+ // "chmod", "chown", "chgrp", "env", "date", "sleep",
1002
+ // "id", "whoami", "hostname", "uname", "ps", "kill",
1003
+ // "df", "du", "dd", "stat", "file",
1004
+ // // net
1005
+ // "curl", "wget", "nc", "netcat", "ss", "ip",
1006
+ // // editors
1007
+ // "nano", "vi",
1008
+ // // text
1009
+ // "diff", "uniq", "xargs", "base64", "md5sum", "sha256sum",
1010
+ // "strings", "hexdump", "od", "column", "fmt", "paste",
1011
+ // "join", "comm", "split", "csplit", "fold", "expand",
1012
+ // // archive
1013
+ // "zip", "unzip",
1014
+ // // process
1015
+ // "top", "htop", "free", "uptime", "dmesg", "lsof",
1016
+ // "strace", "ltrace", "pgrep", "pkill", "nohup", "nice",
1017
+ // // fs
1018
+ // "mount", "umount", "lsblk", "fdisk", "blkid", "e2fsck",
1019
+ // // misc
1020
+ // "bc", "expr", "seq", "yes", "true", "false", "test",
1021
+ // "readlink", "realpath", "dirname", "basename", "mktemp",
1022
+ // "install", "make",
1023
+ // // dev tools
1024
+ // "gcc", "gcc-13", "g++", "g++-13", "cpp", "as", "ld",
1025
+ // "ar", "nm", "objdump", "objcopy", "strip", "size",
1026
+ // "cc", "c++", "pkg-config",
1027
+ // // package
1028
+ // "apt", "apt-get", "apt-cache", "dpkg", "dpkg-query",
1029
+ // "lsb_release", "add-apt-repository",
1030
+ // // scripting
1031
+ // "perl", "python3", "python3.12", "pipx",
1032
+ // // node/npm
1033
+ // "node", "npm", "npx",
1034
+ // // java
1035
+ // "java", "javac", "jar", "javadoc",
1036
+ // // security
1037
+ // "openssl", "gpg", "gpg2", "gpgv", "ssh", "ssh-keygen",
1038
+ // "sudo", "su", "passwd", "adduser", "useradd",
1039
+ // // misc system
1040
+ // "systemctl", "journalctl", "loginctl",
1041
+ // "timedatectl", "localectl",
1042
+ // "lshw", "lscpu", "lsusb", "lspci",
1043
+ // // text proc
1044
+ // "jq", "xmllint", "pandoc",
1045
+ // // multimedia
1046
+ // "ffmpeg",
1047
+ // ];
460
1048
  for (const bin of builtins) {
461
1049
  ensureFile(vfs, `/usr/bin/${bin}`, `#!/bin/sh\nexec builtin ${bin} "$@"\n`, 0o755);
462
1050
  }
463
- ensureFile(vfs, "/usr/bin/lsb_release", '#!/bin/sh\nexec lsb_release "$@"\n', 0o755);
1051
+ // sbin equivalents
1052
+ const sbins = [
1053
+ "nologin", "useradd", "userdel", "groupadd", "groupdel",
1054
+ "adduser", "deluser", "shutdown", "reboot", "halt",
1055
+ "init", "service", "update-alternatives", "update-rc.d",
1056
+ "depmod", "modprobe", "insmod", "rmmod", "lsmod",
1057
+ "ifconfig", "route", "iptables", "ip6tables",
1058
+ "arp", "iwconfig", "ethtool",
1059
+ "fdisk", "parted", "mkfs.ext4", "fsck",
1060
+ "ldconfig", "ldconfig.real",
1061
+ ];
1062
+ for (const bin of sbins) {
1063
+ ensureFile(vfs, `/usr/sbin/${bin}`, `#!/bin/sh\nexec builtin ${bin} "$@"\n`, 0o755);
1064
+ }
1065
+ // versioned python symlink stubs
1066
+ ensureFile(vfs, "/usr/bin/python3.12", `#!/bin/sh\nexec python3 "$@"\n`, 0o755);
1067
+ ensureFile(vfs, "/usr/bin/python3", `#!/bin/sh\nexec python3.12 "$@"\n`, 0o755);
1068
+ // node version stubs
1069
+ ensureFile(vfs, "/usr/bin/node", `#!/bin/sh\nexec node "$@"\n`, 0o755);
1070
+ ensureFile(vfs, "/usr/bin/npm", `#!/bin/sh\nexec npm "$@"\n`, 0o755);
1071
+ ensureFile(vfs, "/usr/bin/npx", `#!/bin/sh\nexec npx "$@"\n`, 0o755);
1072
+ // java stubs
1073
+ ensureFile(vfs, "/usr/lib/jvm/java-21-openjdk-amd64/bin/java", `#!/bin/sh\nexec java "$@"\n`, 0o755);
1074
+ ensureFile(vfs, "/usr/lib/jvm/java-21-openjdk-amd64/bin/javac", `#!/bin/sh\nexec javac "$@"\n`, 0o755);
1075
+ // /usr/share/common-licenses stubs
1076
+ ensureFile(vfs, "/usr/share/common-licenses/GPL-2", "GNU General Public License v2\n");
1077
+ ensureFile(vfs, "/usr/share/common-licenses/GPL-3", "GNU General Public License v3\n");
1078
+ ensureFile(vfs, "/usr/share/common-licenses/LGPL-2.1", "GNU Lesser General Public License v2.1\n");
1079
+ ensureFile(vfs, "/usr/share/common-licenses/Apache-2.0", "Apache License 2.0\n");
1080
+ ensureFile(vfs, "/usr/share/common-licenses/MIT", "MIT License\n");
464
1081
  }
465
1082
  // ─── /var ─────────────────────────────────────────────────────────────────────
1083
+ /** Realistic dpkg status database from real container package list */
1084
+ const DPKG_STATUS = `\
1085
+ Package: bash
1086
+ Status: install ok installed
1087
+ Priority: required
1088
+ Section: shells
1089
+ Installed-Size: 7012
1090
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1091
+ Architecture: amd64
1092
+ Version: 5.2.21-2nyx1
1093
+ Depends: base-files (>= 2.1.12), fortune-utils (>= 1.0)
1094
+ Description: GNU Bourne Again SHell
1095
+ bash is an sh-compatible command language interpreter that executes commands
1096
+ read from the standard input or from a file.
1097
+
1098
+ Package: coreutils
1099
+ Status: install ok installed
1100
+ Priority: required
1101
+ Section: utils
1102
+ Installed-Size: 18272
1103
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1104
+ Architecture: amd64
1105
+ Version: 9.4-3nyx1
1106
+ Depends: libacl1 (>= 2.2.23), libattr1 (>= 1:2.4.44), libc6 (>= 2.17)
1107
+ Description: GNU core utilities
1108
+ This package contains the basic file, shell and text manipulation utilities.
1109
+
1110
+ Package: nodejs
1111
+ Status: install ok installed
1112
+ Priority: optional
1113
+ Section: web
1114
+ Installed-Size: 107120
1115
+ Maintainer: NodeSource <nodejs@nodesource.com>
1116
+ Architecture: amd64
1117
+ Version: 22.22.2-1nyx1
1118
+ Depends: libc6 (>= 2.17), libgcc-s1 (>= 3.0), libstdc++6 (>= 9.0)
1119
+ Description: Node.js event-based server-side javascript engine
1120
+ Node.js is similar in design to and influenced by systems like Ruby's Twisted.
1121
+
1122
+ Package: python3
1123
+ Status: install ok installed
1124
+ Priority: important
1125
+ Section: python
1126
+ Installed-Size: 68
1127
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1128
+ Architecture: amd64
1129
+ Version: 3.12.3-0nyx1
1130
+ Depends: python3.12 (>= 3.12.3-0nyx1)
1131
+ Description: interactive high-level object-oriented language (default python3)
1132
+ Python, the high-level, interactive object oriented language, includes an
1133
+ extensive class library with lots of goodies for network programming.
1134
+
1135
+ Package: python3.12
1136
+ Status: install ok installed
1137
+ Priority: optional
1138
+ Section: python
1139
+ Installed-Size: 36
1140
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1141
+ Architecture: amd64
1142
+ Version: 3.12.3-1nyx1
1143
+ Depends: python3.12-minimal (= 3.12.3-1nyx1), libpython3.12-stdlib
1144
+ Description: Interactive high-level object-oriented language (version 3.12)
1145
+ Python is a high-level, interactive, object-oriented language. Its 3.12 version
1146
+ includes an extensive class library.
1147
+
1148
+ Package: gcc-13
1149
+ Status: install ok installed
1150
+ Priority: optional
1151
+ Section: devel
1152
+ Installed-Size: 70460
1153
+ Maintainer: Fortune GCC Maintainers <gcc@fortune.local>
1154
+ Architecture: amd64
1155
+ Version: 13.3.0-6nyx1
1156
+ Depends: cpp-13 (= 13.3.0-6nyx1), gcc-13-base (= 13.3.0-6nyx1)
1157
+ Description: GNU C compiler
1158
+ This is the GNU C compiler, a fairly portable optimizing compiler for C.
1159
+
1160
+ Package: openjdk-21-jre-headless
1161
+ Status: install ok installed
1162
+ Priority: optional
1163
+ Section: java
1164
+ Installed-Size: 174488
1165
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1166
+ Architecture: amd64
1167
+ Version: 21.0.10+7-1~nyx
1168
+ Depends: libc6 (>= 2.17), libgcc-s1 (>= 3.4)
1169
+ Description: OpenJDK Java runtime, using Hotspot JIT (headless)
1170
+ Minimal Java runtime - needed for executing non-graphical Java programs.
1171
+
1172
+ Package: curl
1173
+ Status: install ok installed
1174
+ Priority: standard
1175
+ Section: web
1176
+ Installed-Size: 544
1177
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1178
+ Architecture: amd64
1179
+ Version: 8.5.0-2nyx1
1180
+ Depends: libcurl4 (= 8.5.0-2nyx1), zlib1g (>= 1:1.1.4)
1181
+ Description: command line tool for transferring data with URL syntax
1182
+ curl is a command line tool for transferring data with URL syntax, supporting
1183
+ DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3,
1184
+ POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET and TFTP.
1185
+
1186
+ Package: git
1187
+ Status: install ok installed
1188
+ Priority: optional
1189
+ Section: vcs
1190
+ Installed-Size: 36552
1191
+ Maintainer: Fortune VCS Team <vcs@fortune.local>
1192
+ Architecture: amd64
1193
+ Version: 1:2.43.0-1nyx1
1194
+ Depends: liberror-perl, git-man, libc6 (>= 2.34), libcurl3-gnutls
1195
+ Description: fast, scalable, distributed revision control system
1196
+ Git is popular version control system designed to handle very large projects
1197
+ with speed and efficiency; it is used mainly for various open source projects.
1198
+
1199
+ Package: openssl
1200
+ Status: install ok installed
1201
+ Priority: optional
1202
+ Section: utils
1203
+ Installed-Size: 1320
1204
+ Maintainer: Fortune Security Team <security@fortune.local>
1205
+ Architecture: amd64
1206
+ Version: 3.0.13-0nyx1
1207
+ Depends: libssl3 (>= 3.0.13)
1208
+ Description: Secure Sockets Layer toolkit - cryptographic utility
1209
+ This package is part of the OpenSSL project's implementation of the SSL and TLS
1210
+ cryptographic protocols and related technologies.
1211
+
1212
+ Package: wget
1213
+ Status: install ok installed
1214
+ Priority: standard
1215
+ Section: web
1216
+ Installed-Size: 1100
1217
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1218
+ Architecture: amd64
1219
+ Version: 1.21.4-1nyx1
1220
+ Depends: libc6 (>= 2.17), libgnutls30 (>= 3.7.9), libidn2-0 (>= 2.0.0)
1221
+ Description: retrieves files from the web
1222
+ GNU Wget is a program for retrieving files from the web, supporting the HTTP,
1223
+ HTTPS and FTP protocols.
1224
+
1225
+ Package: make
1226
+ Status: install ok installed
1227
+ Priority: optional
1228
+ Section: devel
1229
+ Installed-Size: 556
1230
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1231
+ Architecture: amd64
1232
+ Version: 4.3-4.1nyx1
1233
+ Depends: libc6 (>= 2.17)
1234
+ Description: utility for directing compilation
1235
+ GNU Make is a utility which controls the generation of executables and other
1236
+ target files of a program from the program's source files.
1237
+
1238
+ Package: ffmpeg
1239
+ Status: install ok installed
1240
+ Priority: optional
1241
+ Section: video
1242
+ Installed-Size: 2184
1243
+ Maintainer: Fortune Multimedia Team <multimedia@fortune.local>
1244
+ Architecture: amd64
1245
+ Version: 7:6.1.1-3nyx1
1246
+ Depends: libavcodec60, libavdevice60, libavfilter9, libavformat60, libavutil58
1247
+ Description: Tools for transcoding, streaming and playing of multimedia files
1248
+ FFmpeg is a complete, cross-platform solution to record, convert and stream
1249
+ audio and video.
1250
+
1251
+ Package: pandoc
1252
+ Status: install ok installed
1253
+ Priority: optional
1254
+ Section: text
1255
+ Installed-Size: 96248
1256
+ Maintainer: Fortune Haskell Group <haskell@fortune.local>
1257
+ Architecture: amd64
1258
+ Version: 3.1.3+ds-2
1259
+ Depends: libgmp10, libgcc-s1, libffi8
1260
+ Description: general markup converter
1261
+ Pandoc is a Haskell library for converting from one markup format to another.
1262
+
1263
+ Package: tesseract-ocr
1264
+ Status: install ok installed
1265
+ Priority: optional
1266
+ Section: graphics
1267
+ Installed-Size: 1736
1268
+ Maintainer: Fortune OCR Team <ocr@fortune.local>
1269
+ Architecture: amd64
1270
+ Version: 5.3.4-1build5
1271
+ Depends: libc6 (>= 2.14), libleptonica-dev
1272
+ Description: Tesseract Open Source OCR Engine
1273
+ Tesseract is an Open Source OCR Engine, originally developed by HP and now
1274
+ sponsored by Google.
1275
+
1276
+ Package: dpkg
1277
+ Status: install ok installed
1278
+ Priority: required
1279
+ Section: admin
1280
+ Installed-Size: 6800
1281
+ Maintainer: Fortune Package Team <dpkg@fortune.local>
1282
+ Architecture: amd64
1283
+ Version: 1.22.6nyx1
1284
+ Depends: libc6 (>= 2.17), libzstd1 (>= 1.5.5)
1285
+ Description: Fortune package management system
1286
+ This package provides the low-level infrastructure for handling the
1287
+ installation and removal of Fortune software packages.
1288
+
1289
+ Package: apt
1290
+ Status: install ok installed
1291
+ Priority: important
1292
+ Section: admin
1293
+ Installed-Size: 4236
1294
+ Maintainer: Fortune Package Team <apt@fortune.local>
1295
+ Architecture: amd64
1296
+ Version: 2.8.3nyx1
1297
+ Depends: libapt-pkg6.0 (>= 2.8.3), adduser, gpgv
1298
+ Description: commandline package manager
1299
+ This package provides commandline tools for searching and managing as well as
1300
+ querying information about packages as a low-level access to all features of
1301
+ the libapt-pkg library.
1302
+
1303
+ Package: systemd
1304
+ Status: install ok installed
1305
+ Priority: optional
1306
+ Section: admin
1307
+ Installed-Size: 36476
1308
+ Maintainer: Fortune System Team <systemd@fortune.local>
1309
+ Architecture: amd64
1310
+ Version: 255.4-1nyx1
1311
+ Depends: libacl1 (>= 2.2.23), libblkid1 (>= 2.24), libc6 (>= 2.39)
1312
+ Description: system and service manager
1313
+ systemd is a system and service manager for Linux. It provides aggressive
1314
+ parallelization capabilities, uses socket and D-Bus activation for starting
1315
+ services, offers on-demand starting of daemons, keeps track of processes using
1316
+ Linux cgroups, maintains mount and automount points, and implements an
1317
+ elaborate transactional dependency-based service control logic.
1318
+
1319
+ Package: nano
1320
+ Status: install ok installed
1321
+ Priority: important
1322
+ Section: editors
1323
+ Installed-Size: 888
1324
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1325
+ Architecture: amd64
1326
+ Version: 7.2-2
1327
+ Depends: libc6 (>= 2.17), libncursesw6 (>= 6)
1328
+ Description: small, friendly text editor inspired by Pico
1329
+ GNU nano is an easy-to-use text editor originally designed as a replacement
1330
+ for Pico, the ncurses-based editor from the non-free mailer package Pine.
1331
+
1332
+ Package: less
1333
+ Status: install ok installed
1334
+ Priority: important
1335
+ Section: text
1336
+ Installed-Size: 344
1337
+ Maintainer: Fortune Maintainers <maintainers@fortune.local>
1338
+ Architecture: amd64
1339
+ Version: 1:640-2build2
1340
+ Depends: libc6 (>= 2.17), libtinfo6 (>= 6)
1341
+ Description: pager program similar to more
1342
+ This package provides the \`less\` command, which is similar to more but allows
1343
+ you to move backwards through the file.
1344
+
1345
+ `;
466
1346
  function bootstrapVar(vfs) {
467
1347
  ensureDir(vfs, "/var");
468
1348
  ensureDir(vfs, "/var/log");
469
1349
  ensureDir(vfs, "/var/log/apt");
1350
+ ensureDir(vfs, "/var/log/journal");
1351
+ ensureDir(vfs, "/var/log/private");
470
1352
  ensureDir(vfs, "/var/tmp");
471
1353
  ensureDir(vfs, "/var/cache");
472
1354
  ensureDir(vfs, "/var/cache/apt");
473
1355
  ensureDir(vfs, "/var/cache/apt/archives");
1356
+ ensureDir(vfs, "/var/cache/apt/archives/partial");
1357
+ ensureDir(vfs, "/var/cache/debconf");
1358
+ ensureDir(vfs, "/var/cache/ldconfig");
1359
+ ensureDir(vfs, "/var/cache/fontconfig");
1360
+ ensureDir(vfs, "/var/cache/PackageKit");
474
1361
  ensureDir(vfs, "/var/lib");
475
1362
  ensureDir(vfs, "/var/lib/apt");
476
1363
  ensureDir(vfs, "/var/lib/apt/lists");
1364
+ ensureDir(vfs, "/var/lib/apt/lists/partial");
477
1365
  ensureDir(vfs, "/var/lib/dpkg");
478
1366
  ensureDir(vfs, "/var/lib/dpkg/info");
1367
+ ensureDir(vfs, "/var/lib/dpkg/updates");
1368
+ ensureDir(vfs, "/var/lib/dpkg/alternatives");
479
1369
  ensureDir(vfs, "/var/lib/misc");
1370
+ ensureDir(vfs, "/var/lib/systemd");
1371
+ ensureDir(vfs, "/var/lib/systemd/coredump");
1372
+ ensureDir(vfs, "/var/lib/pam");
1373
+ ensureDir(vfs, "/var/lib/git");
1374
+ ensureDir(vfs, "/var/lib/PackageKit");
1375
+ ensureDir(vfs, "/var/lib/python");
480
1376
  ensureDir(vfs, "/var/spool");
481
1377
  ensureDir(vfs, "/var/spool/cron");
1378
+ ensureDir(vfs, "/var/spool/mail");
482
1379
  ensureDir(vfs, "/var/mail");
483
- // dpkg status — starts empty, VirtualPackageManager populates it
484
- ensureFile(vfs, "/var/lib/dpkg/status", "");
1380
+ ensureDir(vfs, "/var/backups");
1381
+ ensureDir(vfs, "/var/www");
1382
+ // dpkg status — realistic package database
1383
+ ensureFile(vfs, "/var/lib/dpkg/status", DPKG_STATUS);
485
1384
  ensureFile(vfs, "/var/lib/dpkg/available", "");
1385
+ ensureFile(vfs, "/var/lib/dpkg/lock", "");
1386
+ ensureFile(vfs, "/var/lib/dpkg/lock-frontend", "");
1387
+ // apt state
1388
+ ensureFile(vfs, "/var/lib/apt/lists/lock", "");
1389
+ ensureFile(vfs, "/var/cache/apt/pkgcache.bin", "");
1390
+ ensureFile(vfs, "/var/cache/apt/srcpkgcache.bin", "");
486
1391
  // syslog stubs
487
- ensureFile(vfs, "/var/log/syslog", `${new Date().toUTCString()} fortune kernel: Virtual container started\n`);
1392
+ ensureFile(vfs, "/var/log/syslog", `${new Date().toUTCString()} ${""} kernel: Virtual container started\n`);
488
1393
  ensureFile(vfs, "/var/log/auth.log", "");
489
1394
  ensureFile(vfs, "/var/log/kern.log", "");
490
1395
  ensureFile(vfs, "/var/log/dpkg.log", "");
491
1396
  ensureFile(vfs, "/var/log/apt/history.log", "");
492
1397
  ensureFile(vfs, "/var/log/apt/term.log", "");
493
- // /run — systemd tmpfs runtime dir (canonical on modern Debian)
494
- // /var/run is a legacy symlink to /run
1398
+ ensureFile(vfs, "/var/log/faillog", "");
1399
+ ensureFile(vfs, "/var/log/lastlog", "");
1400
+ ensureFile(vfs, "/var/log/wtmp", "");
1401
+ ensureFile(vfs, "/var/log/btmp", "");
1402
+ ensureFile(vfs, "/var/log/alternatives.log", "");
1403
+ // /run
495
1404
  ensureDir(vfs, "/run");
496
1405
  ensureDir(vfs, "/run/lock");
1406
+ ensureDir(vfs, "/run/lock/subsys");
497
1407
  ensureDir(vfs, "/run/systemd");
1408
+ ensureDir(vfs, "/run/systemd/ask-password");
1409
+ ensureDir(vfs, "/run/systemd/sessions");
1410
+ ensureDir(vfs, "/run/systemd/users");
498
1411
  ensureDir(vfs, "/run/user");
1412
+ ensureDir(vfs, "/run/dbus");
1413
+ ensureDir(vfs, "/run/adduser");
499
1414
  ensureFile(vfs, "/run/utmp", "");
1415
+ ensureFile(vfs, "/run/dbus/system_bus_socket", "");
500
1416
  }
501
1417
  // ─── /bin + /sbin symlinks ────────────────────────────────────────────────────
502
1418
  function bootstrapBin(vfs) {
503
- // Modern Debian: /bin and /sbin are symlinks to /usr/bin and /usr/sbin
1419
+ // Modern Fortune Nyx: /bin and /sbin are symlinks to /usr/bin and /usr/sbin
504
1420
  if (!vfs.exists("/bin"))
505
1421
  vfs.symlink("/usr/bin", "/bin");
506
1422
  if (!vfs.exists("/sbin"))
@@ -512,51 +1428,64 @@ function bootstrapBin(vfs) {
512
1428
  ensureDir(vfs, "/lib64");
513
1429
  ensureDir(vfs, "/lib/x86_64-linux-gnu");
514
1430
  ensureDir(vfs, "/lib/modules");
1431
+ // lib64 symlink (standard on x86_64)
1432
+ if (!vfs.exists("/lib64/ld-linux-x86-64.so.2")) {
1433
+ ensureFile(vfs, "/lib64/ld-linux-x86-64.so.2", "", 0o755);
1434
+ }
515
1435
  }
516
1436
  // ─── /tmp ─────────────────────────────────────────────────────────────────────
517
1437
  function bootstrapTmp(vfs) {
518
1438
  ensureDir(vfs, "/tmp", 0o1777);
1439
+ // node compile cache dir (present in real env)
1440
+ ensureDir(vfs, "/tmp/node-compile-cache", 0o1777);
519
1441
  }
520
1442
  // ─── /root home ───────────────────────────────────────────────────────────────
521
1443
  function bootstrapRoot(vfs) {
522
1444
  ensureDir(vfs, "/root", 0o700);
1445
+ ensureDir(vfs, "/root/.ssh", 0o700);
1446
+ ensureDir(vfs, "/root/.config", 0o755);
1447
+ ensureDir(vfs, "/root/.config/pip", 0o755);
1448
+ ensureDir(vfs, "/root/.local", 0o755);
1449
+ ensureDir(vfs, "/root/.local/share", 0o755);
523
1450
  ensureFile(vfs, "/root/.bashrc", `${[
524
1451
  "# root .bashrc",
525
1452
  "export PS1='\\[\\033[0;31m\\]\\u@\\h\\[\\033[0m\\]:\\[\\033[0;34m\\]\\w\\[\\033[0m\\]# '",
526
1453
  "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
1454
+ "export LANG=en_US.UTF-8",
527
1455
  "alias ll='ls -la'",
528
1456
  "alias la='ls -A'",
1457
+ "alias l='ls -CF'",
529
1458
  ].join("\n")}\n`);
530
1459
  ensureFile(vfs, "/root/.profile", "[ -f ~/.bashrc ] && . ~/.bashrc\n");
1460
+ ensureFile(vfs, "/root/.bash_logout", "# ~/.bash_logout\n");
1461
+ ensureFile(vfs, "/root/.config/pip/pip.conf", "[global]\nbreak-system-packages = true\n");
531
1462
  }
532
- // ─── /opt /srv /mnt /media /home ─────────────────────────────────────────────
1463
+ // ─── /opt /srv /mnt /media ────────────────────────────────────────────────────
533
1464
  function bootstrapMisc(vfs, props) {
534
1465
  ensureDir(vfs, "/opt");
1466
+ ensureDir(vfs, "/opt/rclone");
535
1467
  ensureDir(vfs, "/srv");
536
1468
  ensureDir(vfs, "/mnt");
537
1469
  ensureDir(vfs, "/media");
538
- ensureDir(vfs, "/home");
539
- // /boot grub + kernel images
1470
+ // /boot — no kernel images in Firecracker containers (kernel is external),
1471
+ // but maintain the directory structure for tool compatibility
540
1472
  ensureDir(vfs, "/boot");
541
1473
  ensureDir(vfs, "/boot/grub");
542
- ensureDir(vfs, "/boot/grub/grub.cfg.d");
543
1474
  ensureFile(vfs, "/boot/grub/grub.cfg", `${[
544
1475
  "# GRUB configuration (virtual)",
545
1476
  "set default=0",
546
- "set timeout=5",
1477
+ "set timeout=0",
547
1478
  "",
548
1479
  `menuentry "Fortune GNU/Linux" {`,
549
- ` linux /vmlinuz root=/dev/sda2 ro quiet splash`,
550
- ` initrd /initrd.img`,
1480
+ ` linux /vmlinuz-${props.kernel} root=/dev/vda rw console=ttyS0`,
1481
+ ` initrd /initrd.img-${props.kernel}`,
551
1482
  `}`,
552
1483
  ].join("\n")}\n`);
553
- // kernel + initrd stubs in /boot
554
1484
  const kver = props.kernel;
555
1485
  ensureFile(vfs, `/boot/vmlinuz-${kver}`, "", 0o644);
556
1486
  ensureFile(vfs, `/boot/initrd.img-${kver}`, "", 0o644);
557
1487
  ensureFile(vfs, `/boot/System.map-${kver}`, `${kver} virtual\n`, 0o644);
558
- ensureFile(vfs, `/boot/config-${kver}`, `# Linux kernel config ${kver}\n`, 0o644);
559
- // root-level symlinks (Debian convention)
1488
+ ensureFile(vfs, `/boot/config-${kver}`, `# Linux kernel config ${kver}\nCONFIG_VIRTIO=y\nCONFIG_VIRTIO_BLK=y\nCONFIG_VIRTIO_NET=y\nCONFIG_KVM_GUEST=y\n`, 0o644);
560
1489
  if (!vfs.exists("/vmlinuz"))
561
1490
  vfs.symlink(`/boot/vmlinuz-${kver}`, "/vmlinuz");
562
1491
  if (!vfs.exists("/vmlinuz.old"))
@@ -565,17 +1494,14 @@ function bootstrapMisc(vfs, props) {
565
1494
  vfs.symlink(`/boot/initrd.img-${kver}`, "/initrd.img");
566
1495
  if (!vfs.exists("/initrd.img.old"))
567
1496
  vfs.symlink(`/boot/initrd.img-${kver}`, "/initrd.img.old");
568
- // /snap — snapd mount namespace root
569
- ensureDir(vfs, "/snap");
570
- ensureDir(vfs, "/snap/bin");
571
- // /lost+found — ext4 fsck recovery dir (mode 0o700, root only)
1497
+ // No /snap — not present in Firecracker container
1498
+ // /proc/cmdline confirms: no snapd boot args
1499
+ // /lost+found — ext4 recovery
572
1500
  ensureDir(vfs, "/lost+found", 0o700);
1501
+ // /home — users managed by bootstrapRoot + syncEtcPasswd
1502
+ ensureDir(vfs, "/home");
573
1503
  }
574
1504
  // ── Static rootfs snapshot cache ─────────────────────────────────────────────
575
- // The static parts of the rootfs (dev, usr, var, bin, tmp, etc, sys, misc)
576
- // are identical for all shells sharing the same hostname+props.
577
- // We build them once, serialise to VFSB binary, and clone via decodeVfs()
578
- // for each new shell — avoiding ~250 JS object allocations per instance.
579
1505
  const _staticRootfsCache = new Map();
580
1506
  function _staticCacheKey(hostname, props) {
581
1507
  return `${hostname}|${props.kernel}|${props.os}|${props.arch}`;
@@ -591,7 +1517,6 @@ export function getStaticRootfsSnapshot(hostname, props) {
591
1517
  const cached = _staticRootfsCache.get(key);
592
1518
  if (cached)
593
1519
  return cached;
594
- // Build the static subset into a temporary VFS
595
1520
  const tmp = new VirtualFileSystem({ mode: "memory" });
596
1521
  bootstrapEtc(tmp, hostname, props);
597
1522
  bootstrapSys(tmp, hostname, props);
@@ -619,14 +1544,20 @@ export function getStaticRootfsSnapshot(hostname, props) {
619
1544
  * @param sessions Active sessions (for /proc/<pid> population).
620
1545
  */
621
1546
  export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime, sessions = []) {
622
- // Fast path: clone the cached static rootfs snapshot (VFSB decode ~0.07ms)
623
- // instead of rebuilding ~250 JS objects from scratch each time.
624
1547
  const snapshot = getStaticRootfsSnapshot(hostname, props);
625
- vfs.importRootTree(decodeVfs(snapshot));
626
- // Dynamic parts: per-instance data injected after the static clone
627
- bootstrapRoot(vfs); // /root home dir + .bashrc
628
- refreshProc(vfs, props, hostname, shellStartTime, sessions); // /proc live data
629
- syncEtcPasswd(vfs, users); // /etc/passwd|group|shadow
1548
+ const hasRestoredData = vfs.getMode() === "fs" && vfs.exists("/home");
1549
+ if (hasRestoredData) {
1550
+ // Snapshot was already restored — merge static rootfs without
1551
+ // clobbering user files and directories.
1552
+ vfs.mergeRootTree(decodeVfs(snapshot));
1553
+ }
1554
+ else {
1555
+ // Fresh start — replace the empty tree with the full static rootfs.
1556
+ vfs.importRootTree(decodeVfs(snapshot));
1557
+ }
1558
+ bootstrapRoot(vfs);
1559
+ refreshProc(vfs, props, hostname, shellStartTime, sessions);
1560
+ syncEtcPasswd(vfs, users);
630
1561
  }
631
1562
  // ─── optional live engine ─────────────────────────────────────────────────────
632
1563
  /**