@wenathlan/saddle 1.8.18 → 2.0.2

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 (802) hide show
  1. package/Dockerfile +1339 -0
  2. package/README.md +33 -12
  3. package/alternatives.ts +1218 -0
  4. package/biome.json +153 -0
  5. package/boards.json +1517 -0
  6. package/compute.ts +3144 -0
  7. package/cores.json +1801 -0
  8. package/dist/acquisition.d.ts +359 -0
  9. package/dist/acquisition.d.ts.map +1 -0
  10. package/dist/acquisition.js +629 -0
  11. package/dist/acquisition.js.map +1 -0
  12. package/dist/alternatives.d.ts +509 -0
  13. package/dist/alternatives.d.ts.map +1 -0
  14. package/dist/alternatives.js +951 -0
  15. package/dist/alternatives.js.map +1 -0
  16. package/dist/automation.d.ts +426 -0
  17. package/dist/automation.d.ts.map +1 -0
  18. package/dist/automation.js +561 -0
  19. package/dist/automation.js.map +1 -0
  20. package/dist/browser.d.ts +562 -0
  21. package/dist/browser.d.ts.map +1 -0
  22. package/dist/browser.js +1057 -0
  23. package/dist/browser.js.map +1 -0
  24. package/dist/capacitor.config.d.ts +13 -0
  25. package/dist/capacitor.config.d.ts.map +1 -0
  26. package/dist/capacitor.config.js +26 -0
  27. package/dist/capacitor.config.js.map +1 -0
  28. package/dist/cli.d.ts +17 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +111 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/communication.d.ts +192 -0
  33. package/dist/communication.d.ts.map +1 -0
  34. package/dist/communication.js +418 -0
  35. package/dist/communication.js.map +1 -0
  36. package/dist/compute.d.ts +1161 -0
  37. package/dist/compute.d.ts.map +1 -0
  38. package/dist/compute.js +2273 -0
  39. package/dist/compute.js.map +1 -0
  40. package/dist/distribution.d.ts +529 -0
  41. package/dist/distribution.d.ts.map +1 -0
  42. package/dist/distribution.js +811 -0
  43. package/dist/distribution.js.map +1 -0
  44. package/dist/execution.d.ts +530 -0
  45. package/dist/execution.d.ts.map +1 -0
  46. package/dist/execution.js +761 -0
  47. package/dist/execution.js.map +1 -0
  48. package/dist/format.d.ts +19 -0
  49. package/dist/format.d.ts.map +1 -0
  50. package/dist/format.js +60 -0
  51. package/dist/format.js.map +1 -0
  52. package/dist/foundation.d.ts +176 -0
  53. package/dist/foundation.d.ts.map +1 -0
  54. package/dist/foundation.js +309 -0
  55. package/dist/foundation.js.map +1 -0
  56. package/dist/index.d.ts +733 -123
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +1018 -123
  59. package/dist/index.js.map +1 -1
  60. package/dist/integration.d.ts +236 -0
  61. package/dist/integration.d.ts.map +1 -0
  62. package/dist/integration.js +236 -0
  63. package/dist/integration.js.map +1 -0
  64. package/dist/intelligence.d.ts +94 -0
  65. package/dist/intelligence.d.ts.map +1 -0
  66. package/dist/intelligence.js +145 -0
  67. package/dist/intelligence.js.map +1 -0
  68. package/dist/{isolation/contracts.d.ts → isolation.d.ts} +12 -1
  69. package/dist/isolation.d.ts.map +1 -0
  70. package/dist/{isolation/contracts.js → isolation.js} +13 -1
  71. package/dist/isolation.js.map +1 -0
  72. package/dist/media.d.ts +344 -0
  73. package/dist/media.d.ts.map +1 -0
  74. package/dist/media.js +986 -0
  75. package/dist/media.js.map +1 -0
  76. package/dist/modes.d.ts +359 -0
  77. package/dist/modes.d.ts.map +1 -0
  78. package/dist/modes.js +134 -0
  79. package/dist/modes.js.map +1 -0
  80. package/dist/{library/public.d.ts → operations.d.ts} +15 -1
  81. package/dist/operations.d.ts.map +1 -0
  82. package/dist/{library/public.js → operations.js} +18 -7
  83. package/dist/operations.js.map +1 -0
  84. package/dist/orchestrator.d.ts +1339 -0
  85. package/dist/orchestrator.d.ts.map +1 -0
  86. package/dist/orchestrator.js +4308 -0
  87. package/dist/orchestrator.js.map +1 -0
  88. package/dist/performance.d.ts +416 -0
  89. package/dist/performance.d.ts.map +1 -0
  90. package/dist/performance.js +827 -0
  91. package/dist/performance.js.map +1 -0
  92. package/dist/quantum.d.ts +810 -0
  93. package/dist/quantum.d.ts.map +1 -0
  94. package/dist/quantum.js +2073 -0
  95. package/dist/quantum.js.map +1 -0
  96. package/dist/render.d.ts +381 -0
  97. package/dist/render.d.ts.map +1 -0
  98. package/dist/render.js +1073 -0
  99. package/dist/render.js.map +1 -0
  100. package/dist/scheduler.d.ts +596 -0
  101. package/dist/scheduler.d.ts.map +1 -0
  102. package/dist/scheduler.js +1259 -0
  103. package/dist/scheduler.js.map +1 -0
  104. package/dist/security.d.ts +501 -0
  105. package/dist/security.d.ts.map +1 -0
  106. package/dist/security.js +1170 -0
  107. package/dist/security.js.map +1 -0
  108. package/dist/server.d.ts +21 -0
  109. package/dist/server.d.ts.map +1 -0
  110. package/dist/{server/node.js → server.js} +16 -2
  111. package/dist/server.js.map +1 -0
  112. package/dist/tiers.d.ts +1002 -0
  113. package/dist/tiers.d.ts.map +1 -0
  114. package/dist/tiers.js +2165 -0
  115. package/dist/tiers.js.map +1 -0
  116. package/dist/virtual.d.ts +641 -0
  117. package/dist/virtual.d.ts.map +1 -0
  118. package/dist/virtual.js +1285 -0
  119. package/dist/virtual.js.map +1 -0
  120. package/dist/virtualcpu.d.ts +316 -0
  121. package/dist/virtualcpu.d.ts.map +1 -0
  122. package/dist/virtualcpu.js +1180 -0
  123. package/dist/virtualcpu.js.map +1 -0
  124. package/dist/virtualgpu.d.ts +345 -0
  125. package/dist/virtualgpu.d.ts.map +1 -0
  126. package/dist/virtualgpu.js +1000 -0
  127. package/dist/virtualgpu.js.map +1 -0
  128. package/dist/virtualization.d.ts +607 -0
  129. package/dist/virtualization.d.ts.map +1 -0
  130. package/dist/virtualization.js +1187 -0
  131. package/dist/virtualization.js.map +1 -0
  132. package/dist/virtualmemory.d.ts +818 -0
  133. package/dist/virtualmemory.d.ts.map +1 -0
  134. package/dist/virtualmemory.js +1796 -0
  135. package/dist/virtualmemory.js.map +1 -0
  136. package/dist/webscrape.d.ts +720 -0
  137. package/dist/webscrape.d.ts.map +1 -0
  138. package/dist/webscrape.js +2697 -0
  139. package/dist/webscrape.js.map +1 -0
  140. package/docker.config +769 -0
  141. package/docs/CONVERSA.txt +1523 -0
  142. package/docs/alternatives.md +499 -0
  143. package/docs/architecture-1.8.19.md +76 -0
  144. package/docs/architecture-1.8.19.mmd +33 -0
  145. package/docs/architecture-1.8.19.png +0 -0
  146. package/docs/architecture.md +355 -0
  147. package/docs/brancharchive-2026-08-18.md +18 -0
  148. package/docs/browser.md +7 -0
  149. package/docs/consolidation.md +70 -0
  150. package/docs/e2ugh-engine.md +528 -0
  151. package/{dist/examples/localjob.js → docs/example-localjob.ts} +3 -3
  152. package/{dist/examples/publicapi.js → docs/example-publicapi.ts} +2 -2
  153. package/docs/hardware.md +342 -0
  154. package/docs/optimization.md +126 -0
  155. package/docs/performance.md +499 -0
  156. package/docs/planning.1.8.18.md +94 -0
  157. package/docs/planning.1.8.19.md +49 -0
  158. package/docs/releasenotes-1.8.19.md +35 -0
  159. package/docs/research-1.8.19-virtual-browser.md +27 -0
  160. package/docs/security.md +454 -0
  161. package/docs/todo-1.8.18.md +12 -0
  162. package/docs/todo-1.8.19.md +241 -0
  163. package/docs/viability.md +777 -0
  164. package/docs/virtualization.md +335 -0
  165. package/docs/web-duplicatecleanup.md +9 -0
  166. package/docs/web-ideas.md +106 -0
  167. package/docs/workflowimprovements-2026-08-19.md +70 -0
  168. package/gpumonitor.cpp +1543 -0
  169. package/gpus.json +2408 -0
  170. package/index.ts +1478 -0
  171. package/media.ts +1337 -0
  172. package/mttg.config +1549 -0
  173. package/orchestrator.ts +5462 -0
  174. package/package.json +304 -56
  175. package/passage.config +1331 -0
  176. package/performance.ts +1049 -0
  177. package/processors.json +2217 -0
  178. package/qemu.config +1062 -0
  179. package/qemubridge.py +1340 -0
  180. package/quantum.ts +2393 -0
  181. package/render.ts +1325 -0
  182. package/scheduler.ts +1600 -0
  183. package/security.ts +1449 -0
  184. package/tiers.ts +2871 -0
  185. package/tsconfig.json +29 -0
  186. package/virtualcpu.ts +1273 -0
  187. package/virtualgpu.ts +1181 -0
  188. package/virtualhardware.c +1011 -0
  189. package/virtualhardware.json +739 -0
  190. package/virtualization.ts +1628 -0
  191. package/virtualizationcore.cpp +4635 -0
  192. package/virtualmemory.ts +2303 -0
  193. package/vm.config.json +1910 -0
  194. package/{extension → web/extension}/manifest.json +1 -1
  195. package/web/readme.md +382 -0
  196. package/web/tsconfig.json +25 -0
  197. package/dist/adapters/forge.d.ts +0 -17
  198. package/dist/adapters/forge.d.ts.map +0 -1
  199. package/dist/adapters/forge.js +0 -19
  200. package/dist/adapters/forge.js.map +0 -1
  201. package/dist/adapters/forgejo.d.ts +0 -49
  202. package/dist/adapters/forgejo.d.ts.map +0 -1
  203. package/dist/adapters/forgejo.js +0 -8
  204. package/dist/adapters/forgejo.js.map +0 -1
  205. package/dist/adapters/github.d.ts +0 -12
  206. package/dist/adapters/github.d.ts.map +0 -1
  207. package/dist/adapters/github.js +0 -20
  208. package/dist/adapters/github.js.map +0 -1
  209. package/dist/adapters/gitlab.d.ts +0 -17
  210. package/dist/adapters/gitlab.d.ts.map +0 -1
  211. package/dist/adapters/gitlab.js +0 -10
  212. package/dist/adapters/gitlab.js.map +0 -1
  213. package/dist/adapters/huggingface.d.ts +0 -17
  214. package/dist/adapters/huggingface.d.ts.map +0 -1
  215. package/dist/adapters/huggingface.js +0 -6
  216. package/dist/adapters/huggingface.js.map +0 -1
  217. package/dist/adapters/socket.d.ts +0 -11
  218. package/dist/adapters/socket.d.ts.map +0 -1
  219. package/dist/adapters/socket.js +0 -17
  220. package/dist/adapters/socket.js.map +0 -1
  221. package/dist/adapters/transport.d.ts +0 -7
  222. package/dist/adapters/transport.d.ts.map +0 -1
  223. package/dist/adapters/transport.js +0 -36
  224. package/dist/adapters/transport.js.map +0 -1
  225. package/dist/ai/chunk.d.ts +0 -2
  226. package/dist/ai/chunk.d.ts.map +0 -1
  227. package/dist/ai/chunk.js +0 -36
  228. package/dist/ai/chunk.js.map +0 -1
  229. package/dist/ai/llmstxt.d.ts +0 -6
  230. package/dist/ai/llmstxt.d.ts.map +0 -1
  231. package/dist/ai/llmstxt.js +0 -12
  232. package/dist/ai/llmstxt.js.map +0 -1
  233. package/dist/ai/provenance.d.ts +0 -22
  234. package/dist/ai/provenance.d.ts.map +0 -1
  235. package/dist/ai/provenance.js +0 -25
  236. package/dist/ai/provenance.js.map +0 -1
  237. package/dist/ai/rag.d.ts +0 -26
  238. package/dist/ai/rag.d.ts.map +0 -1
  239. package/dist/ai/rag.js +0 -22
  240. package/dist/ai/rag.js.map +0 -1
  241. package/dist/ai/tokens.d.ts +0 -15
  242. package/dist/ai/tokens.d.ts.map +0 -1
  243. package/dist/ai/tokens.js +0 -9
  244. package/dist/ai/tokens.js.map +0 -1
  245. package/dist/api/auth.d.ts +0 -16
  246. package/dist/api/auth.d.ts.map +0 -1
  247. package/dist/api/auth.js +0 -19
  248. package/dist/api/auth.js.map +0 -1
  249. package/dist/api/contracts.d.ts +0 -28
  250. package/dist/api/contracts.d.ts.map +0 -1
  251. package/dist/api/contracts.js +0 -14
  252. package/dist/api/contracts.js.map +0 -1
  253. package/dist/api/control.d.ts +0 -10
  254. package/dist/api/control.d.ts.map +0 -1
  255. package/dist/api/control.js +0 -35
  256. package/dist/api/control.js.map +0 -1
  257. package/dist/api/http.d.ts +0 -7
  258. package/dist/api/http.d.ts.map +0 -1
  259. package/dist/api/http.js +0 -12
  260. package/dist/api/http.js.map +0 -1
  261. package/dist/api/rate.d.ts +0 -28
  262. package/dist/api/rate.d.ts.map +0 -1
  263. package/dist/api/rate.js +0 -36
  264. package/dist/api/rate.js.map +0 -1
  265. package/dist/api/security.d.ts +0 -12
  266. package/dist/api/security.d.ts.map +0 -1
  267. package/dist/api/security.js +0 -56
  268. package/dist/api/security.js.map +0 -1
  269. package/dist/api/service.d.ts +0 -5
  270. package/dist/api/service.d.ts.map +0 -1
  271. package/dist/api/service.js +0 -70
  272. package/dist/api/service.js.map +0 -1
  273. package/dist/apps/registry.d.ts +0 -33
  274. package/dist/apps/registry.d.ts.map +0 -1
  275. package/dist/apps/registry.js +0 -24
  276. package/dist/apps/registry.js.map +0 -1
  277. package/dist/binary/archive.d.ts +0 -27
  278. package/dist/binary/archive.d.ts.map +0 -1
  279. package/dist/binary/archive.js +0 -46
  280. package/dist/binary/archive.js.map +0 -1
  281. package/dist/binary/build.d.ts +0 -29
  282. package/dist/binary/build.d.ts.map +0 -1
  283. package/dist/binary/build.js +0 -17
  284. package/dist/binary/build.js.map +0 -1
  285. package/dist/binary/transform.d.ts +0 -70
  286. package/dist/binary/transform.d.ts.map +0 -1
  287. package/dist/binary/transform.js +0 -105
  288. package/dist/binary/transform.js.map +0 -1
  289. package/dist/bot/adapter.d.ts +0 -5
  290. package/dist/bot/adapter.d.ts.map +0 -1
  291. package/dist/bot/adapter.js +0 -11
  292. package/dist/bot/adapter.js.map +0 -1
  293. package/dist/bot/bot.d.ts +0 -28
  294. package/dist/bot/bot.d.ts.map +0 -1
  295. package/dist/bot/bot.js +0 -52
  296. package/dist/bot/bot.js.map +0 -1
  297. package/dist/bot/commands.d.ts +0 -8
  298. package/dist/bot/commands.d.ts.map +0 -1
  299. package/dist/bot/commands.js +0 -19
  300. package/dist/bot/commands.js.map +0 -1
  301. package/dist/bot/permissions.d.ts +0 -13
  302. package/dist/bot/permissions.d.ts.map +0 -1
  303. package/dist/bot/permissions.js +0 -16
  304. package/dist/bot/permissions.js.map +0 -1
  305. package/dist/browser/actions.d.ts +0 -33
  306. package/dist/browser/actions.d.ts.map +0 -1
  307. package/dist/browser/actions.js +0 -44
  308. package/dist/browser/actions.js.map +0 -1
  309. package/dist/browser/agent.d.ts +0 -21
  310. package/dist/browser/agent.d.ts.map +0 -1
  311. package/dist/browser/agent.js +0 -13
  312. package/dist/browser/agent.js.map +0 -1
  313. package/dist/browser/context.d.ts +0 -43
  314. package/dist/browser/context.d.ts.map +0 -1
  315. package/dist/browser/context.js +0 -54
  316. package/dist/browser/context.js.map +0 -1
  317. package/dist/browser/fingerprint.d.ts +0 -4
  318. package/dist/browser/fingerprint.d.ts.map +0 -1
  319. package/dist/browser/fingerprint.js +0 -12
  320. package/dist/browser/fingerprint.js.map +0 -1
  321. package/dist/browser/index.d.ts +0 -11
  322. package/dist/browser/index.d.ts.map +0 -1
  323. package/dist/browser/index.js +0 -11
  324. package/dist/browser/index.js.map +0 -1
  325. package/dist/browser/playwright.d.ts +0 -11
  326. package/dist/browser/playwright.d.ts.map +0 -1
  327. package/dist/browser/playwright.js +0 -24
  328. package/dist/browser/playwright.js.map +0 -1
  329. package/dist/browser/recorder.d.ts +0 -21
  330. package/dist/browser/recorder.d.ts.map +0 -1
  331. package/dist/browser/recorder.js +0 -58
  332. package/dist/browser/recorder.js.map +0 -1
  333. package/dist/browser/session.d.ts +0 -14
  334. package/dist/browser/session.d.ts.map +0 -1
  335. package/dist/browser/session.js +0 -21
  336. package/dist/browser/session.js.map +0 -1
  337. package/dist/browser/snapshot.d.ts +0 -69
  338. package/dist/browser/snapshot.d.ts.map +0 -1
  339. package/dist/browser/snapshot.js +0 -133
  340. package/dist/browser/snapshot.js.map +0 -1
  341. package/dist/captcha/contract.d.ts +0 -35
  342. package/dist/captcha/contract.d.ts.map +0 -1
  343. package/dist/captcha/contract.js +0 -17
  344. package/dist/captcha/contract.js.map +0 -1
  345. package/dist/captcha/evidence.d.ts +0 -10
  346. package/dist/captcha/evidence.d.ts.map +0 -1
  347. package/dist/captcha/evidence.js +0 -9
  348. package/dist/captcha/evidence.js.map +0 -1
  349. package/dist/captcha/guard.d.ts +0 -23
  350. package/dist/captcha/guard.d.ts.map +0 -1
  351. package/dist/captcha/guard.js +0 -12
  352. package/dist/captcha/guard.js.map +0 -1
  353. package/dist/cli/main.d.ts +0 -4
  354. package/dist/cli/main.d.ts.map +0 -1
  355. package/dist/cli/main.js +0 -46
  356. package/dist/cli/main.js.map +0 -1
  357. package/dist/core/errors.d.ts +0 -84
  358. package/dist/core/errors.d.ts.map +0 -1
  359. package/dist/core/errors.js +0 -54
  360. package/dist/core/errors.js.map +0 -1
  361. package/dist/core/events.d.ts +0 -9
  362. package/dist/core/events.d.ts.map +0 -1
  363. package/dist/core/events.js +0 -21
  364. package/dist/core/events.js.map +0 -1
  365. package/dist/core/hash.d.ts +0 -10
  366. package/dist/core/hash.d.ts.map +0 -1
  367. package/dist/core/hash.js +0 -82
  368. package/dist/core/hash.js.map +0 -1
  369. package/dist/core/ids.d.ts +0 -10
  370. package/dist/core/ids.d.ts.map +0 -1
  371. package/dist/core/ids.js +0 -15
  372. package/dist/core/ids.js.map +0 -1
  373. package/dist/delivery/manifest.d.ts +0 -35
  374. package/dist/delivery/manifest.d.ts.map +0 -1
  375. package/dist/delivery/manifest.js +0 -68
  376. package/dist/delivery/manifest.js.map +0 -1
  377. package/dist/deploy/index.d.ts +0 -6
  378. package/dist/deploy/index.d.ts.map +0 -1
  379. package/dist/deploy/index.js +0 -6
  380. package/dist/deploy/index.js.map +0 -1
  381. package/dist/dispatch/resumable.d.ts +0 -102
  382. package/dist/dispatch/resumable.d.ts.map +0 -1
  383. package/dist/dispatch/resumable.js +0 -64
  384. package/dist/dispatch/resumable.js.map +0 -1
  385. package/dist/dispatch/workflow.d.ts +0 -6
  386. package/dist/dispatch/workflow.d.ts.map +0 -1
  387. package/dist/dispatch/workflow.js +0 -37
  388. package/dist/dispatch/workflow.js.map +0 -1
  389. package/dist/domain/artifacts.d.ts +0 -12
  390. package/dist/domain/artifacts.d.ts.map +0 -1
  391. package/dist/domain/artifacts.js +0 -14
  392. package/dist/domain/artifacts.js.map +0 -1
  393. package/dist/domain/jobs.d.ts +0 -12
  394. package/dist/domain/jobs.d.ts.map +0 -1
  395. package/dist/domain/jobs.js +0 -20
  396. package/dist/domain/jobs.js.map +0 -1
  397. package/dist/domain/providers.d.ts +0 -10
  398. package/dist/domain/providers.d.ts.map +0 -1
  399. package/dist/domain/providers.js +0 -8
  400. package/dist/domain/providers.js.map +0 -1
  401. package/dist/domain/runtime.d.ts +0 -14
  402. package/dist/domain/runtime.d.ts.map +0 -1
  403. package/dist/domain/runtime.js +0 -10
  404. package/dist/domain/runtime.js.map +0 -1
  405. package/dist/domain/sessions.d.ts +0 -12
  406. package/dist/domain/sessions.d.ts.map +0 -1
  407. package/dist/domain/sessions.js +0 -57
  408. package/dist/domain/sessions.js.map +0 -1
  409. package/dist/examples/localjob.d.ts +0 -2
  410. package/dist/examples/localjob.d.ts.map +0 -1
  411. package/dist/examples/localjob.js.map +0 -1
  412. package/dist/examples/publicapi.d.ts +0 -2
  413. package/dist/examples/publicapi.d.ts.map +0 -1
  414. package/dist/examples/publicapi.js.map +0 -1
  415. package/dist/extension/build.d.ts +0 -9
  416. package/dist/extension/build.d.ts.map +0 -1
  417. package/dist/extension/build.js +0 -85
  418. package/dist/extension/build.js.map +0 -1
  419. package/dist/extension/content.d.ts +0 -6
  420. package/dist/extension/content.d.ts.map +0 -1
  421. package/dist/extension/content.js +0 -135
  422. package/dist/extension/content.js.map +0 -1
  423. package/dist/extension/index.d.ts +0 -7
  424. package/dist/extension/index.d.ts.map +0 -1
  425. package/dist/extension/index.js +0 -7
  426. package/dist/extension/index.js.map +0 -1
  427. package/dist/extension/pagebridge.d.ts +0 -6
  428. package/dist/extension/pagebridge.d.ts.map +0 -1
  429. package/dist/extension/pagebridge.js +0 -42
  430. package/dist/extension/pagebridge.js.map +0 -1
  431. package/dist/extension/permissions.d.ts +0 -26
  432. package/dist/extension/permissions.d.ts.map +0 -1
  433. package/dist/extension/permissions.js +0 -28
  434. package/dist/extension/permissions.js.map +0 -1
  435. package/dist/extension/popup.d.ts +0 -5
  436. package/dist/extension/popup.d.ts.map +0 -1
  437. package/dist/extension/popup.js +0 -26
  438. package/dist/extension/popup.js.map +0 -1
  439. package/dist/extension/protocol.d.ts +0 -58
  440. package/dist/extension/protocol.d.ts.map +0 -1
  441. package/dist/extension/protocol.js +0 -101
  442. package/dist/extension/protocol.js.map +0 -1
  443. package/dist/extension/serviceworker.d.ts +0 -25
  444. package/dist/extension/serviceworker.d.ts.map +0 -1
  445. package/dist/extension/serviceworker.js +0 -102
  446. package/dist/extension/serviceworker.js.map +0 -1
  447. package/dist/extension/worker.d.ts +0 -28
  448. package/dist/extension/worker.d.ts.map +0 -1
  449. package/dist/extension/worker.js +0 -22
  450. package/dist/extension/worker.js.map +0 -1
  451. package/dist/format/check.d.ts +0 -8
  452. package/dist/format/check.d.ts.map +0 -1
  453. package/dist/format/check.js +0 -38
  454. package/dist/format/check.js.map +0 -1
  455. package/dist/isolation/contracts.d.ts.map +0 -1
  456. package/dist/isolation/contracts.js.map +0 -1
  457. package/dist/library/public.d.ts.map +0 -1
  458. package/dist/library/public.js.map +0 -1
  459. package/dist/mcp/browser.d.ts +0 -9
  460. package/dist/mcp/browser.d.ts.map +0 -1
  461. package/dist/mcp/browser.js +0 -13
  462. package/dist/mcp/browser.js.map +0 -1
  463. package/dist/mcp/server.d.ts +0 -35
  464. package/dist/mcp/server.d.ts.map +0 -1
  465. package/dist/mcp/server.js +0 -40
  466. package/dist/mcp/server.js.map +0 -1
  467. package/dist/mcp/transport.d.ts +0 -7
  468. package/dist/mcp/transport.d.ts.map +0 -1
  469. package/dist/mcp/transport.js +0 -17
  470. package/dist/mcp/transport.js.map +0 -1
  471. package/dist/memory/bridge.d.ts +0 -14
  472. package/dist/memory/bridge.d.ts.map +0 -1
  473. package/dist/memory/bridge.js +0 -16
  474. package/dist/memory/bridge.js.map +0 -1
  475. package/dist/memory/engine.d.ts +0 -38
  476. package/dist/memory/engine.d.ts.map +0 -1
  477. package/dist/memory/engine.js +0 -109
  478. package/dist/memory/engine.js.map +0 -1
  479. package/dist/memory/modes.d.ts +0 -43
  480. package/dist/memory/modes.d.ts.map +0 -1
  481. package/dist/memory/modes.js +0 -66
  482. package/dist/memory/modes.js.map +0 -1
  483. package/dist/memory/objects.d.ts +0 -21
  484. package/dist/memory/objects.d.ts.map +0 -1
  485. package/dist/memory/objects.js +0 -20
  486. package/dist/memory/objects.js.map +0 -1
  487. package/dist/memory/planner.d.ts +0 -66
  488. package/dist/memory/planner.d.ts.map +0 -1
  489. package/dist/memory/planner.js +0 -108
  490. package/dist/memory/planner.js.map +0 -1
  491. package/dist/memory/targets.d.ts +0 -78
  492. package/dist/memory/targets.d.ts.map +0 -1
  493. package/dist/memory/targets.js +0 -29
  494. package/dist/memory/targets.js.map +0 -1
  495. package/dist/memory/transforms.d.ts +0 -19
  496. package/dist/memory/transforms.d.ts.map +0 -1
  497. package/dist/memory/transforms.js +0 -14
  498. package/dist/memory/transforms.js.map +0 -1
  499. package/dist/modes/matrix.d.ts +0 -23
  500. package/dist/modes/matrix.d.ts.map +0 -1
  501. package/dist/modes/matrix.js +0 -18
  502. package/dist/modes/matrix.js.map +0 -1
  503. package/dist/modes/modes.d.ts +0 -33
  504. package/dist/modes/modes.d.ts.map +0 -1
  505. package/dist/modes/modes.js +0 -15
  506. package/dist/modes/modes.js.map +0 -1
  507. package/dist/modes/resolve.d.ts +0 -96
  508. package/dist/modes/resolve.d.ts.map +0 -1
  509. package/dist/modes/resolve.js +0 -48
  510. package/dist/modes/resolve.js.map +0 -1
  511. package/dist/observability/metrics.d.ts +0 -14
  512. package/dist/observability/metrics.d.ts.map +0 -1
  513. package/dist/observability/metrics.js +0 -18
  514. package/dist/observability/metrics.js.map +0 -1
  515. package/dist/packager/manifest.d.ts +0 -66
  516. package/dist/packager/manifest.d.ts.map +0 -1
  517. package/dist/packager/manifest.js +0 -94
  518. package/dist/packager/manifest.js.map +0 -1
  519. package/dist/packager/publish.d.ts +0 -51
  520. package/dist/packager/publish.d.ts.map +0 -1
  521. package/dist/packager/publish.js +0 -18
  522. package/dist/packager/publish.js.map +0 -1
  523. package/dist/packager/targetcli.d.ts +0 -7
  524. package/dist/packager/targetcli.d.ts.map +0 -1
  525. package/dist/packager/targetcli.js +0 -22
  526. package/dist/packager/targetcli.js.map +0 -1
  527. package/dist/persistence/adapter.d.ts +0 -5
  528. package/dist/persistence/adapter.d.ts.map +0 -1
  529. package/dist/persistence/adapter.js +0 -11
  530. package/dist/persistence/adapter.js.map +0 -1
  531. package/dist/persistence/drizzle.d.ts +0 -2
  532. package/dist/persistence/drizzle.d.ts.map +0 -1
  533. package/dist/persistence/drizzle.js +0 -12
  534. package/dist/persistence/drizzle.js.map +0 -1
  535. package/dist/persistence/memory.d.ts +0 -2
  536. package/dist/persistence/memory.d.ts.map +0 -1
  537. package/dist/persistence/memory.js +0 -27
  538. package/dist/persistence/memory.js.map +0 -1
  539. package/dist/persistence/migrations.d.ts +0 -19
  540. package/dist/persistence/migrations.d.ts.map +0 -1
  541. package/dist/persistence/migrations.js +0 -13
  542. package/dist/persistence/migrations.js.map +0 -1
  543. package/dist/persistence/prisma.d.ts +0 -2
  544. package/dist/persistence/prisma.d.ts.map +0 -1
  545. package/dist/persistence/prisma.js +0 -25
  546. package/dist/persistence/prisma.js.map +0 -1
  547. package/dist/persistence/schema.d.ts +0 -63
  548. package/dist/persistence/schema.d.ts.map +0 -1
  549. package/dist/persistence/schema.js +0 -28
  550. package/dist/persistence/schema.js.map +0 -1
  551. package/dist/persistence/sql.d.ts +0 -3
  552. package/dist/persistence/sql.d.ts.map +0 -1
  553. package/dist/persistence/sql.js +0 -37
  554. package/dist/persistence/sql.js.map +0 -1
  555. package/dist/protocol/blocks.d.ts +0 -10
  556. package/dist/protocol/blocks.d.ts.map +0 -1
  557. package/dist/protocol/blocks.js +0 -26
  558. package/dist/protocol/blocks.js.map +0 -1
  559. package/dist/protocol/json.d.ts +0 -6
  560. package/dist/protocol/json.d.ts.map +0 -1
  561. package/dist/protocol/json.js +0 -6
  562. package/dist/protocol/json.js.map +0 -1
  563. package/dist/protocol/ndjson.d.ts +0 -3
  564. package/dist/protocol/ndjson.d.ts.map +0 -1
  565. package/dist/protocol/ndjson.js +0 -20
  566. package/dist/protocol/ndjson.js.map +0 -1
  567. package/dist/protocol/sse.d.ts +0 -7
  568. package/dist/protocol/sse.d.ts.map +0 -1
  569. package/dist/protocol/sse.js +0 -25
  570. package/dist/protocol/sse.js.map +0 -1
  571. package/dist/proxy/pool.d.ts +0 -9
  572. package/dist/proxy/pool.d.ts.map +0 -1
  573. package/dist/proxy/pool.js +0 -27
  574. package/dist/proxy/pool.js.map +0 -1
  575. package/dist/queue/idempotency.d.ts +0 -10
  576. package/dist/queue/idempotency.d.ts.map +0 -1
  577. package/dist/queue/idempotency.js +0 -13
  578. package/dist/queue/idempotency.js.map +0 -1
  579. package/dist/queue/persistent.d.ts +0 -14
  580. package/dist/queue/persistent.d.ts.map +0 -1
  581. package/dist/queue/persistent.js +0 -66
  582. package/dist/queue/persistent.js.map +0 -1
  583. package/dist/queue/queue.d.ts +0 -7
  584. package/dist/queue/queue.d.ts.map +0 -1
  585. package/dist/queue/queue.js +0 -55
  586. package/dist/queue/queue.js.map +0 -1
  587. package/dist/queue/saga.d.ts +0 -5
  588. package/dist/queue/saga.d.ts.map +0 -1
  589. package/dist/queue/saga.js +0 -20
  590. package/dist/queue/saga.js.map +0 -1
  591. package/dist/release/assets.d.ts +0 -144
  592. package/dist/release/assets.d.ts.map +0 -1
  593. package/dist/release/assets.js +0 -157
  594. package/dist/release/assets.js.map +0 -1
  595. package/dist/release/evidence.d.ts +0 -92
  596. package/dist/release/evidence.d.ts.map +0 -1
  597. package/dist/release/evidence.js +0 -148
  598. package/dist/release/evidence.js.map +0 -1
  599. package/dist/release/verify.d.ts +0 -12
  600. package/dist/release/verify.d.ts.map +0 -1
  601. package/dist/release/verify.js +0 -112
  602. package/dist/release/verify.js.map +0 -1
  603. package/dist/runners/chain.d.ts +0 -125
  604. package/dist/runners/chain.d.ts.map +0 -1
  605. package/dist/runners/chain.js +0 -95
  606. package/dist/runners/chain.js.map +0 -1
  607. package/dist/runners/health.d.ts +0 -36
  608. package/dist/runners/health.d.ts.map +0 -1
  609. package/dist/runners/health.js +0 -26
  610. package/dist/runners/health.js.map +0 -1
  611. package/dist/runners/heartbeat.d.ts +0 -23
  612. package/dist/runners/heartbeat.d.ts.map +0 -1
  613. package/dist/runners/heartbeat.js +0 -29
  614. package/dist/runners/heartbeat.js.map +0 -1
  615. package/dist/runners/inprocess.d.ts +0 -17
  616. package/dist/runners/inprocess.d.ts.map +0 -1
  617. package/dist/runners/inprocess.js +0 -20
  618. package/dist/runners/inprocess.js.map +0 -1
  619. package/dist/runners/scheduler.d.ts +0 -5
  620. package/dist/runners/scheduler.d.ts.map +0 -1
  621. package/dist/runners/scheduler.js +0 -19
  622. package/dist/runners/scheduler.js.map +0 -1
  623. package/dist/runtime/abort.d.ts +0 -8
  624. package/dist/runtime/abort.d.ts.map +0 -1
  625. package/dist/runtime/abort.js +0 -13
  626. package/dist/runtime/abort.js.map +0 -1
  627. package/dist/runtime/compatibility.d.ts +0 -37
  628. package/dist/runtime/compatibility.d.ts.map +0 -1
  629. package/dist/runtime/compatibility.js +0 -10
  630. package/dist/runtime/compatibility.js.map +0 -1
  631. package/dist/runtime/detect.d.ts +0 -13
  632. package/dist/runtime/detect.d.ts.map +0 -1
  633. package/dist/runtime/detect.js +0 -18
  634. package/dist/runtime/detect.js.map +0 -1
  635. package/dist/runtime/engine.d.ts +0 -20
  636. package/dist/runtime/engine.d.ts.map +0 -1
  637. package/dist/runtime/engine.js +0 -61
  638. package/dist/runtime/engine.js.map +0 -1
  639. package/dist/runtime/retry.d.ts +0 -19
  640. package/dist/runtime/retry.d.ts.map +0 -1
  641. package/dist/runtime/retry.js +0 -54
  642. package/dist/runtime/retry.js.map +0 -1
  643. package/dist/runtime/worker.d.ts +0 -10
  644. package/dist/runtime/worker.d.ts.map +0 -1
  645. package/dist/runtime/worker.js +0 -25
  646. package/dist/runtime/worker.js.map +0 -1
  647. package/dist/scrape/cache.d.ts +0 -45
  648. package/dist/scrape/cache.d.ts.map +0 -1
  649. package/dist/scrape/cache.js +0 -45
  650. package/dist/scrape/cache.js.map +0 -1
  651. package/dist/scrape/crawl.d.ts +0 -47
  652. package/dist/scrape/crawl.d.ts.map +0 -1
  653. package/dist/scrape/crawl.js +0 -114
  654. package/dist/scrape/crawl.js.map +0 -1
  655. package/dist/scrape/extract.d.ts +0 -63
  656. package/dist/scrape/extract.d.ts.map +0 -1
  657. package/dist/scrape/extract.js +0 -47
  658. package/dist/scrape/extract.js.map +0 -1
  659. package/dist/scrape/normalize.d.ts +0 -52
  660. package/dist/scrape/normalize.d.ts.map +0 -1
  661. package/dist/scrape/normalize.js +0 -102
  662. package/dist/scrape/normalize.js.map +0 -1
  663. package/dist/scrape/robots.d.ts +0 -37
  664. package/dist/scrape/robots.d.ts.map +0 -1
  665. package/dist/scrape/robots.js +0 -71
  666. package/dist/scrape/robots.js.map +0 -1
  667. package/dist/scrape/schema.d.ts +0 -23
  668. package/dist/scrape/schema.d.ts.map +0 -1
  669. package/dist/scrape/schema.js +0 -92
  670. package/dist/scrape/schema.js.map +0 -1
  671. package/dist/scrape/scraper.d.ts +0 -6
  672. package/dist/scrape/scraper.d.ts.map +0 -1
  673. package/dist/scrape/scraper.js +0 -46
  674. package/dist/scrape/scraper.js.map +0 -1
  675. package/dist/scrape/semantic.d.ts +0 -25
  676. package/dist/scrape/semantic.d.ts.map +0 -1
  677. package/dist/scrape/semantic.js +0 -27
  678. package/dist/scrape/semantic.js.map +0 -1
  679. package/dist/server/node.d.ts +0 -7
  680. package/dist/server/node.d.ts.map +0 -1
  681. package/dist/server/node.js.map +0 -1
  682. package/dist/sessions/file.d.ts +0 -25
  683. package/dist/sessions/file.d.ts.map +0 -1
  684. package/dist/sessions/file.js +0 -13
  685. package/dist/sessions/file.js.map +0 -1
  686. package/dist/sessions/replay.d.ts +0 -9
  687. package/dist/sessions/replay.d.ts.map +0 -1
  688. package/dist/sessions/replay.js +0 -70
  689. package/dist/sessions/replay.js.map +0 -1
  690. package/dist/sessions/store.d.ts +0 -25
  691. package/dist/sessions/store.d.ts.map +0 -1
  692. package/dist/sessions/store.js +0 -13
  693. package/dist/sessions/store.js.map +0 -1
  694. package/dist/storage/adapter.d.ts +0 -5
  695. package/dist/storage/adapter.d.ts.map +0 -1
  696. package/dist/storage/adapter.js +0 -11
  697. package/dist/storage/adapter.js.map +0 -1
  698. package/dist/storage/cache.d.ts +0 -23
  699. package/dist/storage/cache.d.ts.map +0 -1
  700. package/dist/storage/cache.js +0 -79
  701. package/dist/storage/cache.js.map +0 -1
  702. package/dist/storage/checksum.d.ts +0 -3
  703. package/dist/storage/checksum.d.ts.map +0 -1
  704. package/dist/storage/checksum.js +0 -19
  705. package/dist/storage/checksum.js.map +0 -1
  706. package/dist/storage/chunked.d.ts +0 -21
  707. package/dist/storage/chunked.d.ts.map +0 -1
  708. package/dist/storage/chunked.js +0 -67
  709. package/dist/storage/chunked.js.map +0 -1
  710. package/dist/storage/content.d.ts +0 -24
  711. package/dist/storage/content.d.ts.map +0 -1
  712. package/dist/storage/content.js +0 -44
  713. package/dist/storage/content.js.map +0 -1
  714. package/dist/storage/filehosting.d.ts +0 -2
  715. package/dist/storage/filehosting.d.ts.map +0 -1
  716. package/dist/storage/filehosting.js +0 -24
  717. package/dist/storage/filehosting.js.map +0 -1
  718. package/dist/storage/githubcontents.d.ts +0 -2
  719. package/dist/storage/githubcontents.d.ts.map +0 -1
  720. package/dist/storage/githubcontents.js +0 -29
  721. package/dist/storage/githubcontents.js.map +0 -1
  722. package/dist/storage/index.d.ts +0 -12
  723. package/dist/storage/index.d.ts.map +0 -1
  724. package/dist/storage/index.js +0 -12
  725. package/dist/storage/index.js.map +0 -1
  726. package/dist/storage/local.d.ts +0 -2
  727. package/dist/storage/local.d.ts.map +0 -1
  728. package/dist/storage/local.js +0 -72
  729. package/dist/storage/local.js.map +0 -1
  730. package/dist/storage/memory.d.ts +0 -6
  731. package/dist/storage/memory.d.ts.map +0 -1
  732. package/dist/storage/memory.js +0 -36
  733. package/dist/storage/memory.js.map +0 -1
  734. package/dist/storage/pool.d.ts +0 -95
  735. package/dist/storage/pool.d.ts.map +0 -1
  736. package/dist/storage/pool.js +0 -202
  737. package/dist/storage/pool.js.map +0 -1
  738. package/dist/storage/s3compatible.d.ts +0 -3
  739. package/dist/storage/s3compatible.d.ts.map +0 -1
  740. package/dist/storage/s3compatible.js +0 -70
  741. package/dist/storage/s3compatible.js.map +0 -1
  742. package/dist/storage/sync.d.ts +0 -47
  743. package/dist/storage/sync.d.ts.map +0 -1
  744. package/dist/storage/sync.js +0 -75
  745. package/dist/storage/sync.js.map +0 -1
  746. package/dist/surfaces/adapters.d.ts +0 -97
  747. package/dist/surfaces/adapters.d.ts.map +0 -1
  748. package/dist/surfaces/adapters.js +0 -50
  749. package/dist/surfaces/adapters.js.map +0 -1
  750. package/dist/surfaces/controls.d.ts +0 -18
  751. package/dist/surfaces/controls.d.ts.map +0 -1
  752. package/dist/surfaces/controls.js +0 -45
  753. package/dist/surfaces/controls.js.map +0 -1
  754. package/dist/surfaces/manifest.d.ts +0 -68
  755. package/dist/surfaces/manifest.d.ts.map +0 -1
  756. package/dist/surfaces/manifest.js +0 -42
  757. package/dist/surfaces/manifest.js.map +0 -1
  758. package/dist/surfaces/n8n.d.ts +0 -25
  759. package/dist/surfaces/n8n.d.ts.map +0 -1
  760. package/dist/surfaces/n8n.js +0 -31
  761. package/dist/surfaces/n8n.js.map +0 -1
  762. package/dist/surfaces/operations.d.ts +0 -32
  763. package/dist/surfaces/operations.d.ts.map +0 -1
  764. package/dist/surfaces/operations.js +0 -53
  765. package/dist/surfaces/operations.js.map +0 -1
  766. package/dist/surfaces/requirements.d.ts +0 -29
  767. package/dist/surfaces/requirements.d.ts.map +0 -1
  768. package/dist/surfaces/requirements.js +0 -50
  769. package/dist/surfaces/requirements.js.map +0 -1
  770. package/dist/surfaces/targets.d.ts +0 -196
  771. package/dist/surfaces/targets.d.ts.map +0 -1
  772. package/dist/surfaces/targets.js +0 -27
  773. package/dist/surfaces/targets.js.map +0 -1
  774. package/dist/webhook/delivery.d.ts +0 -18
  775. package/dist/webhook/delivery.d.ts.map +0 -1
  776. package/dist/webhook/delivery.js +0 -36
  777. package/dist/webhook/delivery.js.map +0 -1
  778. package/dist/webhook/receiver.d.ts +0 -26
  779. package/dist/webhook/receiver.d.ts.map +0 -1
  780. package/dist/webhook/receiver.js +0 -24
  781. package/dist/webhook/receiver.js.map +0 -1
  782. package/dist/webhook/signature.d.ts +0 -3
  783. package/dist/webhook/signature.d.ts.map +0 -1
  784. package/dist/webhook/signature.js +0 -7
  785. package/dist/webhook/signature.js.map +0 -1
  786. package/dist/workflow/manifest.d.ts +0 -22
  787. package/dist/workflow/manifest.d.ts.map +0 -1
  788. package/dist/workflow/manifest.js +0 -21
  789. package/dist/workflow/manifest.js.map +0 -1
  790. package/dist/workflow/registry.d.ts +0 -7
  791. package/dist/workflow/registry.d.ts.map +0 -1
  792. package/dist/workflow/registry.js +0 -18
  793. package/dist/workflow/registry.js.map +0 -1
  794. package/dist/workflow/templates.d.ts +0 -6
  795. package/dist/workflow/templates.d.ts.map +0 -1
  796. package/dist/workflow/templates.js +0 -18
  797. package/dist/workflow/templates.js.map +0 -1
  798. package/dist/workflow/triggers.d.ts +0 -64
  799. package/dist/workflow/triggers.d.ts.map +0 -1
  800. package/dist/workflow/triggers.js +0 -99
  801. package/dist/workflow/triggers.js.map +0 -1
  802. /package/{extension/README.md → docs/extension.md} +0 -0
package/Dockerfile ADDED
@@ -0,0 +1,1339 @@
1
+ # syntax=docker/dockerfile:1
2
+ # saddle v2.0.0 grand merge — THE ONE CONTAINER FILE.
3
+ #
4
+ # this Dockerfile carries BOTH container surfaces of the merged repository:
5
+ # 1. the saddle virtual-hardware engine image (the five stages below:
6
+ # builder, python-bridge, qemu, qemu-runtime, runtime) — the default
7
+ # build target, published as ghcr.io/wenathlan/saddle:vhe-<version>
8
+ # across three profiles and four architectures;
9
+ # 2. the saddle node-engine image (the saddle-build and saddle-runtime
10
+ # stages at the end of this file, folded from the former
11
+ # dockerfile.saddle) — an explicit build target
12
+ # (`docker build --target saddle-runtime .`), the node 26 storage and
13
+ # compute service published as ghcr.io/wenathlan/saddle:<version>.
14
+ #
15
+ # saddle v2 (grand merge) - virtual hardware engine, multi-stage, multi-arch image.
16
+ #
17
+ # the five stages mirror the stages section of docker.config
18
+ # (multi-stage-cache-optimized strategy, parallel-friendly):
19
+ # builder toolchain stage on ubuntu:24.04. installs LLVM 18
20
+ # (from the noble archive, not apt.llvm.org which is
21
+ # unreachable from GHA runners), builds mesa 26.2.1
22
+ # (llvmpipe + lavapipe; rusticl is off until
23
+ # LLVMSPIRVLib >= 22.1 is packaged), installs node
24
+ # 26.7.0 from nodesource, and compiles the native sources
25
+ # DIRECTLY with gcc-14/g++-14 (no cmake anywhere: the libs
26
+ # are single translation units documented in their own
27
+ # headers): libs/virtualhardware.c (LD_PRELOAD spoofing
28
+ # core), libs/gpumonitor.cpp (nvml/cuda shim suite in
29
+ # four build modes) and libs/virtualizationcore.cpp
30
+ # (kvm/vfio/qmp core with an executable selftest), all
31
+ # with -O3 -flto. it also bakes the /etc/virtual hardware
32
+ # profile per the VHE_PROFILE_* build-args (max: EPYC 9965
33
+ # 192c/384t 1TiB, balanced: Threadripper Pro 9995WX
34
+ # 96c/192t 512GiB, lite: Ryzen 9 9950X3D 16c/32t 128GiB).
35
+ # python-bridge python:3.14.7-slim carrying qemubridge.py. the bridge is
36
+ # stdlib-only (asyncio/json/argparse), so the stage runs a
37
+ # py_compile syntax gate and no pip install at all.
38
+ # qemu QEMU 11.1.0 compiled from download.qemu.org with the
39
+ # x86_64-softmmu target for TCG/MTTCG guest execution;
40
+ # build arg QEMU_FROM_SOURCE=0 falls back to the distro
41
+ # qemu-system-x86 package when a source build is not
42
+ # viable on the host.
43
+ # qemu-runtime the qemu stage plus OVMF firmware, pciutils, kmod and
44
+ # tini (the docker.config "qemuRuntime" role: the guest
45
+ # runner consumed by the vheqemu docker run recipe).
46
+ # runtime ubuntu:24.04 final image: mesa 26.2.1 stack, node
47
+ # 26.7.0, Xvfb, vulkan-tools, clinfo, mesa-utils,
48
+ # python3, the compiled spoofing artifacts, the python
49
+ # bridge and the engine sources under /engine. default
50
+ # build target (docker build / the engine docker run
51
+ # recipe below).
52
+ #
53
+ # platforms: linux/amd64, linux/arm64, linux/ppc64le and linux/s390x
54
+ # through buildx --platform (the four-arch registry surface); every
55
+ # stage compiles for the portable baseline of its target architecture
56
+ # by default (x86-64 / armv8-a / power8 / z196, resolved from
57
+ # TARGETARCH) so the layers stay valid on every host and every
58
+ # gha-cached runner cpu; pass --build-arg VHE_MARCH=native for
59
+ # host-tuned local builds, or an explicit -march value when
60
+ # cross-building for a known deployment target.
61
+ #
62
+ # profiles: the three profiles (max, balanced, lite) are all built for
63
+ # all four architectures; each profile carries a distinct virtual CPU
64
+ # identity (model name, vcpus, threads, ram) baked into /etc/virtual.
65
+ #
66
+ # the one container file (the gateway 1.1.5 standard): the compose stack
67
+ # and the entrypoint bootstrap are MERGED INTO this file -
68
+ # docker-compose.yml and entrypoint.sh are deleted from the tree; every
69
+ # orchestration setting they carried is expressed here (the ENV defaults,
70
+ # EXPOSE, VOLUME, HEALTHCHECK, USER, the OCI labels, the entrypoint script
71
+ # embedded as a heredoc COPY) plus the hardened docker run recipes below
72
+ # documenting every flag of the former services (init reaps zombies as
73
+ # pid 1, cap-drop ALL + no-new-privileges harden the container, unlimited
74
+ # swap, 2g shm, 15s stop grace, 50m x 5 json-file logs, restart
75
+ # unless-stopped, the tmpfs scratch and the named volumes). Containerfile
76
+ # is the same format under the OCI name - Dockerfile is the universally
77
+ # compatible spelling, so it is the one file the repository keeps.
78
+ #
79
+ # the docker run recipes (one per former compose service; pick any free
80
+ # host port in 30000-60000 - nothing below is a fixed address):
81
+ #
82
+ # vhe (the engine service, default target):
83
+ # docker run -d --name saddle-engine \
84
+ # --restart unless-stopped --init \
85
+ # --cap-drop ALL --security-opt no-new-privileges:true \
86
+ # --ulimit nofile=65536:65536 \
87
+ # --memory-swap -1 --shm-size 2g --stop-timeout 15 \
88
+ # --log-driver json-file --log-opt max-size=50m --log-opt max-file=5 \
89
+ # --tmpfs /tmp:size=512m,mode=1777 --tmpfs /run/vhe:size=64m,mode=0755 \
90
+ # --tmpfs /cache/mesa_shader_cache:size=20g,uid=10000,gid=10000,mode=1777 \
91
+ # -v vmdata:/data/vmdata -v webdata:/data/web \
92
+ # -v ./vm.config.json:/engine/vm.config.json:ro \
93
+ # -e PORT=8080 -e NODE_ENV=production -p 31280:8080 \
94
+ # ghcr.io/wenathlan/saddle:vhe-2.0.2
95
+ #
96
+ # vheqemu (the guest runner; build it first with
97
+ # docker build --target qemu-runtime -t saddle/qemu:11.1.0 . because the
98
+ # published registry image is the runtime target):
99
+ # docker run -d --name saddle-qemu \
100
+ # --restart unless-stopped --init \
101
+ # --security-opt no-new-privileges:true \
102
+ # --ulimit nofile=65536:65536 \
103
+ # --memory-swap -1 --shm-size 2g --stop-timeout 15 \
104
+ # --log-driver json-file --log-opt max-size=50m --log-opt max-file=5 \
105
+ # -v ./qemu:/qemu:ro -v qemu-kernel:/opt/kernels:ro \
106
+ # -v qemudata:/data/qemudata -v ovmfvars:/data/ovmfvars \
107
+ # -v ./qemu.config:/engine/qemu.config:ro \
108
+ # -p 31281:31281 -it \
109
+ # saddle/qemu:11.1.0 \
110
+ # -accel tcg,thread=multi,tb-size=1024 -cpu EPYC-v5 \
111
+ # -smp 192,sockets=1,cores=192,threads=2,maxcpus=384 \
112
+ # -m 131072 -machine q35 -nodefaults -no-reboot -nographic \
113
+ # -serial mon:stdio -monitor none \
114
+ # -kernel /qemu/vmlinuz -initrd /qemu/initrd.img \
115
+ # -append "console=ttyS0 panic=-1"
116
+ #
117
+ # vhegpu (the fake gpu sidecar, same engine image):
118
+ # docker run -d --name saddle-gpu \
119
+ # --restart unless-stopped --init \
120
+ # --cap-drop ALL --security-opt no-new-privileges:true \
121
+ # --ulimit nofile=65536:65536 \
122
+ # --memory-swap -1 --shm-size 2g --stop-timeout 15 \
123
+ # --log-driver json-file --log-opt max-size=50m --log-opt max-file=5 \
124
+ # -v gpudata:/data/gpudata -v ./gpus.json:/engine/gpus.json:ro \
125
+ # -e VHE_GPU_PROFILE=b200 -e VHE_GPUS=8 -e VHE_MIG=1 \
126
+ # -e VHE_SMI_DRIVER=575.57.08 -e VHE_SMI_CUDA=12.9 \
127
+ # -e VHE_SMI_INTERVAL=30 -e VHE_SKIP_XVFB=1 -e VHE_SKIP_VALIDATE=1 \
128
+ # ghcr.io/wenathlan/saddle:vhe-2.0.2 /bin/bash -c '
129
+ # while true; do
130
+ # /usr/local/bin/nvidia-smi "$VHE_GPU_PROFILE" "$VHE_GPUS" || true
131
+ # sleep "$VHE_SMI_INTERVAL"
132
+ # done
133
+ # '
134
+ #
135
+ # qemubridge (the python QMP bridge, same engine image):
136
+ # docker run -d --name saddle-bridge \
137
+ # --restart unless-stopped --init \
138
+ # --security-opt no-new-privileges:true \
139
+ # --ulimit nofile=65536:65536 \
140
+ # --memory-swap -1 --shm-size 2g --stop-timeout 15 \
141
+ # --log-driver json-file --log-opt max-size=50m --log-opt max-file=5 \
142
+ # -v qemudata:/data/qemudata \
143
+ # -v ./qemubridge.py:/engine/qemubridge.py:ro \
144
+ # -e QMP_SOCKET=/run/vhe/vm.qmp -e PYTHONUNBUFFERED=1 \
145
+ # ghcr.io/wenathlan/saddle:vhe-2.0.2 \
146
+ # python3 /engine/qemubridge.py --socket /run/vhe/vm.qmp status
147
+ #
148
+ # saddle-node (the node-engine service, the former compose.yml
149
+ # service folded here; build it with
150
+ # docker build --target saddle-runtime -t saddle:<version> .):
151
+ # docker run -d --name saddle-node \
152
+ # --restart unless-stopped --init --read-only \
153
+ # --cap-drop ALL --security-opt no-new-privileges:true \
154
+ # --pids-limit 512 --network none \
155
+ # --ulimit nofile=65536:65536 \
156
+ # --log-driver json-file --log-opt max-size=50m --log-opt max-file=5 \
157
+ # --tmpfs /tmp:size=2g,mode=1777 \
158
+ # -e SADDLE_MEMORY_ENGINE=ram -e SBOT_PLATFORM= -e SBOT_CDN_URL= \
159
+ # ghcr.io/wenathlan/saddle:2.0.2 \
160
+ # node dist/cli.js plan
161
+ #
162
+ # observability (the former prometheus scraper of the full profile)
163
+ # runs from its own upstream image (prom/prometheus:v3.4.1) with
164
+ # --config.file and a 15d retention - the one container file this
165
+ # repository keeps covers only what it builds.
166
+ #
167
+ # no port is published by the image itself; the recipes above publish
168
+ # host 31280 -> container 8080 for the engine and 31281 for the qemu
169
+ # guest runner, and a bare `docker run` without -e PORT leaves the
170
+ # embedded entrypoint picking a random port in 30000-60000 (the same
171
+ # range the recipes draw their host ports from). nothing in the stack
172
+ # ever binds a hardcoded address.
173
+
174
+ # Base image contract (the gateway family policy): tags only, never a
175
+ # @sha256: digest. A single digest cannot serve the four-architecture
176
+ # matrix this image ships (the registry resolves the tag per platform),
177
+ # and a digest written here would be a hardcoded hash - the checksums
178
+ # that exist are generated by the GitHub runners and published as
179
+ # release assets (SHA256SUMS), never written into the sources.
180
+ ARG UBUNTU="ubuntu:24.04"
181
+ # maximum virtual hardware profile baked at build time (workflow override
182
+ # friendly): the top catalog cpu (EPYC 9965, 192 cores / 384 threads), the
183
+ # catalog maximum memory plan (1024 GiB), the top gpu profile (B200) and
184
+ # eight virtual adapters. every value is spoofed software identity: no
185
+ # physical hardware is touched, probed or required.
186
+ ARG VHE_PROFILE_CPU="epyc9965"
187
+ ARG VHE_PROFILE_VCPUS="192"
188
+ ARG VHE_PROFILE_THREADS="384"
189
+ ARG VHE_PROFILE_RAM_GB="1024"
190
+ ARG VHE_PROFILE_GPU="b200"
191
+ ARG VHE_PROFILE_GPUS="8"
192
+ ARG PYTHON_BASE="python:3.14.7-slim"
193
+ ARG LLVM_VERSION="18"
194
+ ARG NODE_VERSION="26.7.0"
195
+ ARG MESA_VERSION="26.2.1"
196
+ ARG QEMU_VERSION="11.1.0"
197
+
198
+ # ---------------------------------------------------------------------------
199
+ # stage 1: builder
200
+ # ---------------------------------------------------------------------------
201
+ FROM ${UBUNTU} AS builder
202
+ ARG TARGETPLATFORM
203
+ ARG TARGETARCH
204
+ ARG BUILDARCH
205
+ ARG LLVM_VERSION
206
+ ARG NODE_VERSION
207
+ ARG MESA_VERSION
208
+ # empty = portable baseline resolved from TARGETARCH at the compile step
209
+ # (x86-64 / armv8-a); "native" is the opt-in for host-tuned local builds.
210
+ ARG VHE_MARCH=""
211
+ # NOTE: the VHE_PROFILE_* build-args are deliberately NOT declared in the
212
+ # builder stage. The builder compiles mesa/node/native shims which are
213
+ # profile-independent; keeping the profile args out of the builder means
214
+ # the buildx registry cache is shared across max/balanced/lite for each
215
+ # architecture (the profile identity is baked in the runtime stage below).
216
+
217
+ ENV DEBIAN_FRONTEND=noninteractive
218
+
219
+ # toolchain: gcc-14 (speaks c++26 for the shim suite) and the LLVM 18
220
+ # toolchain from the Ubuntu noble archive (which mesa 26.2.1 requires
221
+ # '>= 18.0.0'). The previous build pulled LLVM 22 from apt.llvm.org but
222
+ # that host is unreachable from GitHub Actions runners (curl exit 7,
223
+ # 8 retries all failed in 0 ms); the Ubuntu distro LLVM 18.1.x is served
224
+ # from archive.ubuntu.com which the runner can always reach. Rusticl is
225
+ # disabled (see the mesa block below) so no clang/libclc/libllvmspirvlib
226
+ # are needed; the native sources compile with g++-14.
227
+ RUN set -eux; \
228
+ # the Ubuntu arm64/ppc64el/s390x ports mirror (ports.ubuntu.com)
229
+ # intermittently serves either a Packages.gz whose size differs from
230
+ # the Release file while it is mid-sync ("File has unexpected size
231
+ # ... Mirror sync in progress?") OR a Release index that references
232
+ # .deb files already removed from the pool directory (404 Not Found
233
+ # on apt-get install). Retrying just apt-get update is not enough:
234
+ # the update can succeed on a stale index whose pool files are gone.
235
+ # The retry must cover BOTH apt-get update AND apt-get install so a
236
+ # 404 on install re-fetches a fresh index that references the new
237
+ # package versions. The stale list cache is cleared between retries.
238
+ apt_update_tries=5; \
239
+ while [ "$apt_update_tries" -gt 0 ]; do \
240
+ if apt-get update && apt-get install -y --no-install-recommends \
241
+ ca-certificates curl gnupg xz-utils bzip2 git \
242
+ python3 python3-pip python3-setuptools python3-wheel \
243
+ python3-mako python3-yaml python3-ply \
244
+ pkg-config bison flex ninja-build meson \
245
+ build-essential gcc-14 g++-14 \
246
+ "llvm-${LLVM_VERSION}-dev" "llvm-${LLVM_VERSION}-tools" \
247
+ libdrm-dev libexpat1-dev zlib1g-dev libzstd-dev \
248
+ libx11-dev libxext-dev libxfixes-dev libxxf86vm-dev libxrandr-dev \
249
+ libxcb1-dev libxcb-glx0-dev libxcb-dri2-0-dev libxcb-dri3-dev \
250
+ libxcb-present-dev libxcb-sync-dev libxcb-shm0-dev \
251
+ libxcb-randr0-dev libxcb-xfixes0-dev libx11-xcb-dev libxshmfence-dev \
252
+ libelf-dev libedit-dev libglvnd-dev \
253
+ glslang-tools spirv-tools; then break; fi; \
254
+ apt_update_tries=$((apt_update_tries - 1)); \
255
+ echo "apt-get update/install failed (mirror sync?), $apt_update_tries retries left"; \
256
+ sleep 10; \
257
+ rm -rf /var/lib/apt/lists/*; \
258
+ done; \
259
+ test "$apt_update_tries" -gt 0; \
260
+ ln -sf "/usr/bin/llvm-config-${LLVM_VERSION}" /usr/local/bin/llvm-config; \
261
+ ln -sf /usr/bin/gcc-14 /usr/local/bin/gcc; \
262
+ ln -sf /usr/bin/g++-14 /usr/local/bin/g++; \
263
+ # mesa 26.2.1 requires meson >= 1.4.0 (mesa.build:9); Ubuntu 24.04 noble
264
+ # ships meson 1.3.2 which is too old. meson installs from its exact
265
+ # version-pinned wheel URL: the artifact identity is the 1.12.0 release
266
+ # path on files.pythonhosted.org (a registry URL, the same class of
267
+ # contract npm ci uses through the lockfile). No hardcoded digest in
268
+ # this file: the maintainer supply-chain policy for this family is the
269
+ # version-tagged artifact (the Scorecard PinnedDependencies findings
270
+ # are dismissed per that policy) and the checksums that exist are
271
+ # generated on the GitHub runners at release time (see the SHA256SUMS
272
+ # asset of the Release workflow), never written into the sources.
273
+ curl -fsSL --retry 5 --retry-all-errors \
274
+ -o /tmp/meson-1.12.0-py3-none-any.whl \
275
+ "https://files.pythonhosted.org/packages/07/68/b0117422eb0a46d9d8d9e328f0c5b5c835179bfc058688bca35c90c89eba/meson-1.12.0-py3-none-any.whl"; \
276
+ pip3 install --break-system-packages --no-cache-dir \
277
+ /tmp/meson-1.12.0-py3-none-any.whl; \
278
+ rm -f /tmp/meson-1.12.0-py3-none-any.whl; \
279
+ meson --version
280
+
281
+ # NOTE: the rust toolchain step was removed because rusticl is disabled
282
+ # (LLVMSPIRVLib >= 22.1 not packaged for LLVM 22; see the mesa block below).
283
+ # Rusticl was the only mesa component that needed rustc + bindgen-cli; with
284
+ # it off, the build is rust-free.
285
+
286
+ # node 26.7.0 (current line, v8 14.6, temporal api, NODE_MODULE_VERSION 147).
287
+ # amd64 and arm64 install the pinned nodesource deb; ppc64le (any arch the
288
+ # nodesource apt repo does not carry) falls back to the official nodejs.org
289
+ # binary tarball, extracted straight into /usr so bin/node, include/node and
290
+ # lib/node_modules land exactly where the nodesource package (and the runtime
291
+ # stage COPY --from=builder directives) expect them. the node headers
292
+ # (/usr/include/node) are required by the ADDON build mode of gpumonitor.cpp.
293
+ RUN set -eux; \
294
+ nodesrc_ok="1"; \
295
+ case "${TARGETARCH}" in ppc64le|s390x|riscv64|loongarch64) nodesrc_ok="0" ;; esac; \
296
+ if [ "$nodesrc_ok" = "1" ]; then \
297
+ # the version-line setup script (setup_26.x) is the nodesource
298
+ # distribution contract: no hardcoded digest in this file. The
299
+ # script tracks the whole 26.x line (a frozen hash would
300
+ # false-negative on every upstream refresh), the apt repository
301
+ # it configures carries its own release signing, and the exact
302
+ # node build is pinned by the "nodejs=${NODE_VERSION}-1nodesource1"
303
+ # package below. Checksums for released artifacts are generated on
304
+ # the GitHub runners (SHA256SUMS release asset), never hardcoded.
305
+ curl -fsSL -o /tmp/nodesource-setup.sh https://deb.nodesource.com/setup_26.x; \
306
+ bash /tmp/nodesource-setup.sh; \
307
+ rm -f /tmp/nodesource-setup.sh; \
308
+ apt-get install -y --no-install-recommends \
309
+ "nodejs=${NODE_VERSION}-1nodesource1" \
310
+ || apt-get install -y --no-install-recommends nodejs; \
311
+ else \
312
+ tarball="node-v${NODE_VERSION}-linux-${TARGETARCH}.tar.xz"; \
313
+ curl -fsSL --retry 5 --retry-all-errors -o "/tmp/${tarball}" \
314
+ "https://nodejs.org/dist/v${NODE_VERSION}/${tarball}"; \
315
+ tar -xJf "/tmp/${tarball}" -C /usr --strip-components=1; \
316
+ rm -f "/tmp/${tarball}"; \
317
+ fi; \
318
+ node --version; npm --version; \
319
+ # upgrade the bundled npm to the line the engines field documents
320
+ # (nodesource node 26.7.0 ships npm 11.19.0) and surgically refresh
321
+ # the four nested dependencies whose bundled versions carry the CVEs
322
+ # the trivy release gate rejects (brace-expansion CVE-2026-14257 /
323
+ # CVE-2026-69152, ip-address CVE-2026-69192 / CVE-2026-54272 /
324
+ # CVE-2026-69198, tar CVE-2026-73566, undici CVE-2026-16728 /
325
+ # CVE-2026-16729 / CVE-2026-15157). published npm tarballs freeze
326
+ # their bundled node_modules at publish time, so the swap alone
327
+ # still ships brace-expansion 5.0.7; each fixed release is packed
328
+ # separately and extracted over the nested path, then asserted: the
329
+ # runtime stage copies /usr/lib/node_modules verbatim and the gate
330
+ # scans exactly that path.
331
+ newnpm=$(npm pack "npm@12.0.2" --pack-destination /tmp 2>/dev/null | tail -n1); \
332
+ case "${newnpm}" in /*) ;; *) newnpm="/tmp/${newnpm}";; esac; \
333
+ test -f "${newnpm}"; \
334
+ rm -rf /usr/lib/node_modules/npm; \
335
+ mkdir -p /usr/lib/node_modules/npm; \
336
+ tar -xzf "${newnpm}" -C /usr/lib/node_modules/npm --strip-components=1; \
337
+ rm -f "${newnpm}"; \
338
+ hash -r; \
339
+ npm --version; \
340
+ test -f /usr/include/node/node_api.h; \
341
+ fixdep() { \
342
+ fname="$1"; fwant="$2"; \
343
+ ftgz=$(npm pack "$fname@$fwant" --pack-destination /tmp 2>/dev/null | tail -n1); \
344
+ case "$ftgz" in /*) ;; *) ftgz="/tmp/$ftgz";; esac; \
345
+ test -f "$ftgz"; \
346
+ fdest="/usr/lib/node_modules/npm/node_modules/$fname"; \
347
+ rm -rf "$fdest"; \
348
+ mkdir -p "$fdest"; \
349
+ tar -xzf "$ftgz" -C "$fdest" --strip-components=1; \
350
+ rm -f "$ftgz"; \
351
+ fgot=$(node -p "require('/usr/lib/node_modules/npm/node_modules/$fname/package.json').version"); \
352
+ if [ "$fgot" != "$fwant" ]; then \
353
+ echo "npm tree $fname is $fgot, expected $fwant" >&2; \
354
+ exit 1; \
355
+ fi; \
356
+ echo "npm tree $fname $fgot"; \
357
+ }; \
358
+ fixdep brace-expansion 5.0.9; \
359
+ fixdep ip-address 10.3.1; \
360
+ fixdep tar 7.5.21; \
361
+ fixdep undici 6.28.0; \
362
+ rm -rf /root/.npm 2>/dev/null || true
363
+
364
+ # mesa 26.2.1 from source (the "build" arm of the PPA-or-build policy):
365
+ # llvmpipe (OpenGL 4.6 core, 161/161 extensions), lavapipe (Vulkan 1.4
366
+ # software ICD, lvp_icd.<arch>.json). Rusticl (OpenCL 3.1) is DISABLED
367
+ # because mesa 26.2.1 requires LLVMSPIRVLib >= 22.1 (the
368
+ # SPIRV-LLVM-Translator for LLVM 22), and apt.llvm.org does not yet ship
369
+ # libllvmspirvlib-22-dev (the latest available is libllvmspirvlib-19-dev
370
+ # at version 19.1.0.0, which is too old). Building SPIRV-LLVM-Translator
371
+ # from source would add ~10 min to the build; the OpenCL validation is
372
+ # deferred until the package lands upstream. video codecs and extra
373
+ # tools stay off to keep the build lean; platforms=x11 only because the
374
+ # engine renders headless through Xvfb GLX.
375
+ #
376
+ # Download strategy: GHA runners cannot reliably reach mesa.freedesktop.org
377
+ # or archive.mesa3d.org (curl exit 28 on connect observed across multiple
378
+ # Build workflow runs). The canonical upstream tarball has been mirrored
379
+ # to a GitHub release asset under the mesa-cache-26.2.1 tag in this repo,
380
+ # which is served from githubusercontent.com (always reachable from GHA).
381
+ # The build tries the GitHub release first, then falls back through the
382
+ # freedesktop.org canonical URL and the archive.mesa3d.org mirror (the
383
+ # final redirect target) so local builds without GitHub access still
384
+ # work.
385
+ #
386
+ # Verification contract (no hardcoded hashes, the gateway family policy):
387
+ # the sha256 of the tarball is NEVER written in this file. GitHub computes
388
+ # the digest of every release asset at upload time; the publish workflows
389
+ # resolve that digest at build time and pass it as the MESA_SHA256
390
+ # build-arg, so every GitHub build verifies the tarball before it lands
391
+ # in a layer. Local builds without the build-arg fetch the SHA256SUMS
392
+ # asset of the mesa-cache release when reachable and verify against it;
393
+ # a fully offline local build proceeds with a visible warning.
394
+ ARG MESA_SHA256=""
395
+ RUN set -eux; \
396
+ curl_opts="-fL --retry 5 --retry-all-errors --retry-delay 5 \
397
+ --connect-timeout 30 --max-time 900"; \
398
+ tarball="mesa-${MESA_VERSION}.tar.xz"; \
399
+ repo="${GITHUB_REPOSITORY:-wenathlan/saddle}"; \
400
+ github_release="https://github.com/${repo}/releases/download/mesa-cache-${MESA_VERSION}/${tarball}"; \
401
+ sums_url="https://github.com/${repo}/releases/download/mesa-cache-${MESA_VERSION}/SHA256SUMS"; \
402
+ upstream="https://mesa.freedesktop.org/archive/${tarball}"; \
403
+ mirror="https://archive.mesa3d.org/${tarball}"; \
404
+ rm -f "${tarball}" SHA256SUMS; \
405
+ curl ${curl_opts} -C - -o "${tarball}" "${github_release}" \
406
+ || curl ${curl_opts} -C - -o "${tarball}" "${upstream}" \
407
+ || curl ${curl_opts} -C - -o "${tarball}" "${mirror}"; \
408
+ if [ -n "${MESA_SHA256}" ]; then \
409
+ echo "${MESA_SHA256} ${tarball}" | sha256sum -c -; \
410
+ elif curl -fsSL --retry 3 --connect-timeout 15 -o SHA256SUMS "${sums_url}" \
411
+ && grep -q " ${tarball}\$" SHA256SUMS; then \
412
+ sha256sum -c --ignore-missing SHA256SUMS; \
413
+ rm -f SHA256SUMS; \
414
+ else \
415
+ echo "WARNING: MESA_SHA256 build-arg empty and no SHA256SUMS asset reachable; ${tarball} digest verification skipped (offline local build)" >&2; \
416
+ rm -f SHA256SUMS; \
417
+ fi; \
418
+ echo "mesa ${MESA_VERSION} tarball ready"; \
419
+ tar xf "${tarball}"; \
420
+ meson setup /tmp/mesa-build "mesa-${MESA_VERSION}" \
421
+ --buildtype release \
422
+ -Dprefix=/usr \
423
+ -Dsysconfdir=/etc \
424
+ -Dplatforms=x11 \
425
+ -Dglx=dri \
426
+ -Degl=enabled \
427
+ -Dshared-glapi=enabled \
428
+ -Dllvm=enabled \
429
+ -Dgallium-drivers=llvmpipe \
430
+ -Dvulkan-drivers=swrast \
431
+ -Dgallium-rusticl=false \
432
+ -Dvideo-codecs=[] \
433
+ -Dtools=[] \
434
+ -Dvalgrind=disabled \
435
+ -Dlibunwind=disabled \
436
+ -Dlmsensors=disabled \
437
+ -Dzstd=enabled; \
438
+ ninja -C /tmp/mesa-build; \
439
+ DESTDIR=/staging ninja -C /tmp/mesa-build install; \
440
+ # Ensure /staging/etc exists even when rusticl is off (no OpenCL ICD
441
+ # installed); the runtime stage COPY --from=builder /staging/etc /etc/
442
+ # would fail the buildx cache checksum without this.
443
+ mkdir -p /staging/etc; \
444
+ find /staging -name '*.so*' | head -20
445
+
446
+ # the native sources, compiled directly per the build headers of each file
447
+ # (no cmake, no build system beyond the compiler): the spoofing core and
448
+ # the shim suite with -O3 -march -flto as mandated for the engine. the
449
+ # c++26 build first tries `import std;` and falls back to -DVHE_NO_MODULES
450
+ # when the toolchain ships no std module (libstdc++-14 on noble ships
451
+ # without one). virtualizationcore.cpp additionally builds its selftest
452
+ # binary and runs it: the stage fails if a single assert trips.
453
+ WORKDIR /src
454
+ COPY virtualhardware.c gpumonitor.cpp virtualizationcore.cpp /src/libs/
455
+ RUN set -eux; \
456
+ # portable baseline per architecture, with the ARCH FLAG the compiler
457
+ # family actually accepts: x86 and arm speak -march; the ppc64le gcc
458
+ # port rejects '-march=power8' ('did you mean -mcpu=power8?') so that
459
+ # architecture selects the -mcpu spelling; s390x accepts -march=z196.
460
+ archflag="-march"; \
461
+ case "${TARGETARCH}" in \
462
+ arm64|aarch64) march="armv8-a" ;; \
463
+ ppc64le) march="power8"; archflag="-mcpu" ;; \
464
+ s390x) march="z196" ;; \
465
+ *) march="x86-64" ;; \
466
+ esac; \
467
+ if [ -n "${VHE_MARCH}" ]; then march="${VHE_MARCH}"; archflag="-march"; fi; \
468
+ echo "building native shims with ${archflag}=${march}"; \
469
+ mkdir -p /out/usr/local/lib /out/usr/local/bin /out/engine/native /out/etc/virtual; \
470
+ gcc-14 -std=c11 -O3 "$archflag=$march" -flto -funswitch-loops \
471
+ -shared -fPIC -pthread -ldl \
472
+ /src/libs/virtualhardware.c -o /out/usr/local/lib/libvirtualhardware.so; \
473
+ build_cpp() { \
474
+ g++-14 -std=c++2c -O3 "$archflag=$march" -flto \
475
+ -DVHE_NO_MODULES "$@"; \
476
+ }; \
477
+ build_cpp -shared -fPIC -ldl -DVHE_BUILD_PRELOAD \
478
+ /src/libs/gpumonitor.cpp \
479
+ -o /out/usr/local/lib/libvirtualhardware_gpu.so; \
480
+ build_cpp -DVHE_BUILD_CLI /src/libs/gpumonitor.cpp \
481
+ -o /out/usr/local/bin/nvidia-smi; \
482
+ build_cpp -DVHE_BUILD_FORGE /src/libs/gpumonitor.cpp \
483
+ -o /out/usr/local/bin/aetherforge; \
484
+ build_cpp -shared -fPIC -DVHE_BUILD_ADDON \
485
+ -DNODE_GYP_MODULE_NAME=vhe_gpu -I/usr/include/node \
486
+ /src/libs/gpumonitor.cpp -o /out/engine/native/vhe_gpu.node; \
487
+ build_cpp -shared -fPIC -pthread \
488
+ /src/libs/virtualizationcore.cpp \
489
+ -o /out/usr/local/lib/libvirtualizationcore.so; \
490
+ build_cpp -DVHE_VIRT_SELFTEST /src/libs/virtualizationcore.cpp \
491
+ -o /tmp/virtualizationcore_selftest; \
492
+ /tmp/virtualizationcore_selftest; \
493
+ ls -la /out/usr/local/lib /out/usr/local/bin /out/engine/native; \
494
+ /out/usr/local/bin/nvidia-smi a100 1 | head -3; \
495
+ /out/usr/local/bin/aetherforge --smp 16 --mttg 64 --mem 64 | head -3
496
+
497
+ # the nvidia-ml and libcuda soname symlinks make pynvml, gpustat and
498
+ # dlopen("libnvidia-ml.so.1") consumers resolve to the shim without any
499
+ # LD_PRELOAD cooperation (rick-hsu/nvml-unified-shim deployment pattern).
500
+ RUN set -eux; \
501
+ ln -s libvirtualhardware_gpu.so /out/usr/local/lib/libnvidia-ml.so.1; \
502
+ ln -s libvirtualhardware_gpu.so /out/usr/local/lib/libcuda.so.1; \
503
+ ln -s libvirtualhardware_gpu.so /out/usr/local/lib/libnvidia-ml.so; \
504
+ ln -s libvirtualhardware_gpu.so /out/usr/local/lib/libcuda.so
505
+
506
+ # /etc/virtual holds only the profile-independent kernel identity
507
+ # files (version/uptime/loadavg) here; the profile-specific cpuinfo
508
+ # and meminfo are baked in the runtime stage so the builder layers
509
+ # stay profile-independent (buildx cache shared across max/balanced/lite).
510
+ RUN mkdir -p /out/etc/virtual
511
+
512
+ COPY <<'EOF' /out/etc/virtual/version
513
+ Linux version 6.18.0-vhe (vhe-builder@vhe) (clang version 19.1.0) #1 SMP VHE PREEMPT_DYNAMIC 2026-08-22
514
+ EOF
515
+
516
+ COPY <<'EOF' /out/etc/virtual/uptime
517
+ 3600.00 2700.00
518
+ EOF
519
+
520
+ COPY <<'EOF' /out/etc/virtual/loadavg
521
+ 3.84 3.12 2.98 12/768 28471
522
+ EOF
523
+
524
+ # ---------------------------------------------------------------------------
525
+ # stage 2: python-bridge (qemubridge.py carrier)
526
+ # ---------------------------------------------------------------------------
527
+ FROM ${PYTHON_BASE} AS python-bridge
528
+ ENV PYTHONDONTWRITEBYTECODE=1 \
529
+ PYTHONUNBUFFERED=1
530
+
531
+ # qemubridge.py is stdlib-only (asyncio, json, argparse, pathlib): the v2
532
+ # merge dropped the pip dependencies of the older saddle bridge stages
533
+ # (qemu-bridge/cffi wheels) because the bridge talks raw QMP over a unix
534
+ # socket without any native binding. the py_compile run is the syntax gate
535
+ # (error catcher: a bridge that does not compile fails the image build).
536
+ COPY qemubridge.py /bridge/qemubridge.py
537
+ RUN set -eux; \
538
+ python3 -m py_compile /bridge/qemubridge.py; \
539
+ python3 /bridge/qemubridge.py --help >/dev/null; \
540
+ python3 -c "import ast, sys; ast.parse(open('/bridge/qemubridge.py').read()); print('qemubridge ast ok')"
541
+
542
+ # ---------------------------------------------------------------------------
543
+ # stage 3: qemu (TCG/MTTCG engine, the vheqemu docker run recipe base)
544
+ # ---------------------------------------------------------------------------
545
+ FROM ${UBUNTU} AS qemu
546
+ ARG QEMU_VERSION
547
+ ARG QEMU_FROM_SOURCE="1"
548
+ ENV DEBIAN_FRONTEND=noninteractive
549
+
550
+ # qemu 11.1.0 (released 2026-08-12) with the x86_64 system emulator only;
551
+ # KVM is auto-detected at runtime but the engine targets TCG with MTTCG
552
+ # (-accel tcg,thread=multi,tb-size=1024 -cpu EPYC-v5) because CI hosts and
553
+ # nested containers expose no /dev/kvm. when QEMU_FROM_SOURCE=0 (source
554
+ # build not viable, for example a constrained builder), the stage installs
555
+ # the distro qemu-system-x86 package instead; version drift is then the
556
+ # operator's tradeoff and is recorded in the image labels.
557
+ RUN set -eux; \
558
+ # mirror sync race: same retry loop as the builder stage, covering
559
+ # BOTH apt-get update AND apt-get install together so a 404 on install
560
+ # (stale Release index referencing .deb files already removed from the
561
+ # pool) re-fetches a fresh index and retries the install.
562
+ apt_update_tries=5; \
563
+ while [ "$apt_update_tries" -gt 0 ]; do \
564
+ if apt-get update && apt-get install -y --no-install-recommends \
565
+ ca-certificates curl xz-utils bzip2 \
566
+ build-essential ninja-build meson python3 python3-venv \
567
+ libglib2.0-dev libpixman-1-dev zlib1g-dev flex bison; then break; fi; \
568
+ apt_update_tries=$((apt_update_tries - 1)); \
569
+ echo "apt-get update/install failed (mirror sync?), $apt_update_tries retries left"; \
570
+ sleep 10; \
571
+ rm -rf /var/lib/apt/lists/*; \
572
+ done; \
573
+ test "$apt_update_tries" -gt 0; \
574
+ if [ "${QEMU_FROM_SOURCE}" = "1" ]; then \
575
+ curl -fsSLO "https://download.qemu.org/qemu-${QEMU_VERSION}.tar.xz"; \
576
+ tar xf "qemu-${QEMU_VERSION}.tar.xz"; \
577
+ cd "qemu-${QEMU_VERSION}"; \
578
+ ./configure \
579
+ --target-list=x86_64-softmmu \
580
+ --disable-docs \
581
+ --disable-werror \
582
+ --disable-sdl \
583
+ --disable-gtk \
584
+ --disable-curl \
585
+ --disable-spice \
586
+ --disable-opengl; \
587
+ ninja -C build; \
588
+ ninja -C build install; \
589
+ qemu-system-x86_64 --version; \
590
+ cd /; rm -rf "/qemu-${QEMU_VERSION}"*; \
591
+ else \
592
+ apt-get install -y --no-install-recommends \
593
+ qemu-system-x86 qemu-utils; \
594
+ qemu-system-x86_64 --version; \
595
+ fi; \
596
+ apt-get purge -y --auto-remove \
597
+ build-essential ninja-build meson python3-venv flex bison \
598
+ libglib2.0-dev libpixman-1-dev zlib1g-dev \
599
+ || true; \
600
+ apt-get clean; rm -rf /var/lib/apt/lists/*
601
+
602
+ ENTRYPOINT ["qemu-system-x86_64"]
603
+
604
+ # ---------------------------------------------------------------------------
605
+ # stage 4: qemu-runtime (guest runner: qemu + OVMF + pciutils + tini)
606
+ # ---------------------------------------------------------------------------
607
+ FROM qemu AS qemu-runtime
608
+ ENV DEBIAN_FRONTEND=noninteractive
609
+
610
+ # the docker.config qemuRuntime role: QEMU system (already provided by
611
+ # the parent stage, either the 11.1.0 source build or the distro
612
+ # fallback), OVMF firmware, PCI utilities, curl for health probes, kmod
613
+ # for module loading and tini as pid 1 so guest processes are reaped when
614
+ # the guest runner restarts. when the parent used the source build the
615
+ # distro qemu packages stay absent; when it used the apt fallback the
616
+ # ovmf/pciutils set still installs identically.
617
+ RUN set -eux; \
618
+ # mirror sync race: same retry loop as the builder and qemu stages,
619
+ # covering BOTH apt-get update AND apt-get install together.
620
+ apt_update_tries=5; \
621
+ while [ "$apt_update_tries" -gt 0 ]; do \
622
+ if apt-get update && apt-get install -y --no-install-recommends \
623
+ ovmf pciutils curl kmod tini; then break; fi; \
624
+ apt_update_tries=$((apt_update_tries - 1)); \
625
+ echo "apt-get update/install failed (mirror sync?), $apt_update_tries retries left"; \
626
+ sleep 10; \
627
+ rm -rf /var/lib/apt/lists/*; \
628
+ done; \
629
+ test "$apt_update_tries" -gt 0; \
630
+ mkdir -p /usr/share/OVMF /var/log/vhe /run/vhe; \
631
+ ls -lh /usr/share/OVMF/OVMF_CODE.fd || true; \
632
+ apt-get clean; rm -rf /var/lib/apt/lists/*
633
+
634
+ ENTRYPOINT ["/usr/bin/tini", "--", "qemu-system-x86_64"]
635
+
636
+ # ---------------------------------------------------------------------------
637
+ # stage 5: runtime (final, default target, the vhe/vhegpu docker run recipes)
638
+ # ---------------------------------------------------------------------------
639
+ FROM ${UBUNTU} AS runtime
640
+ ARG TARGETARCH
641
+ ARG NODE_VERSION
642
+ ARG VHE_UID=10000
643
+ ARG VHE_GID=10000
644
+ ARG VHE_PROFILE_CPU
645
+ ARG VHE_PROFILE_VCPUS
646
+ ARG VHE_PROFILE_THREADS
647
+ ARG VHE_PROFILE_RAM_GB
648
+ ARG VHE_PROFILE_GPU
649
+ ARG VHE_PROFILE_GPUS
650
+ ENV DEBIAN_FRONTEND=noninteractive
651
+
652
+ # runtime packages: Xvfb for the headless GLX display, x11-utils/xdpyinfo
653
+ # for the readiness probe, vulkan-tools (vulkaninfo), clinfo for the
654
+ # rusticl report, mesa-utils (glxinfo/es2_info), the ocl-icd loader for
655
+ # rusticl, python3 for the qmp bridge, procps for free/top inside the
656
+ # sandbox, and the shared libraries the mesa staging links against.
657
+ #
658
+ # The LLVM 18 runtime (libLLVM-18.so.1, which llvmpipe and lavapipe link
659
+ # against) is COPIED from the builder stage below instead of installed
660
+ # separately: the builder already has the full llvm-18-dev toolchain
661
+ # installed from the Ubuntu noble archive, so copying the lib tree is
662
+ # both faster (no apt round-trip) and keeps the runtime stage on the
663
+ # same LLVM version the mesa build linked against.
664
+ RUN set -eux; \
665
+ # mirror sync race: same retry loop as the builder and qemu stages,
666
+ # covering BOTH apt-get update AND apt-get install together so a 404
667
+ # on install (stale Release index referencing .deb files already
668
+ # removed from the pool) re-fetches a fresh index and retries.
669
+ # (this is the leg that failed on ppc64le in 1.2.12 and on s390x in
670
+ # 1.2.13 before this retry covered the install step too).
671
+ #
672
+ # base-image CVE closure: apt-get install only guarantees the listed
673
+ # packages - the ubuntu:24.04 base layer arrives with whatever system
674
+ # packages its digest carried (and a BuildKit registry cache hit can
675
+ # replay an even older layer chain), so perl-base, util-linux,
676
+ # ncurses, coreutils & friends can lag behind the published security
677
+ # fixes even right after a fresh build. The upgrade step pulls every
678
+ # installed base package to the latest security update resolved by
679
+ # the fresh index above (never installs new packages, never removes
680
+ # any - same-version upgrades only), and Always-Include-Phased-Updates
681
+ # skips Ubuntu's gradual phasing so CI builds are deterministic.
682
+ apt_update_tries=5; \
683
+ while [ "$apt_update_tries" -gt 0 ]; do \
684
+ if apt-get update && apt-get install -y --no-install-recommends \
685
+ ca-certificates curl gnupg bash procps \
686
+ xvfb x11-utils xauth x11-xserver-utils \
687
+ mesa-utils vulkan-tools clinfo ocl-icd-libopencl1 \
688
+ python3 \
689
+ libdrm2 libexpat1 zlib1g libzstd1 libedit2 libatomic1 \
690
+ libx11-6 libxext6 libxfixes3 libxxf86vm1 libxshmfence1 \
691
+ libxcb1 libwayland-client0 libwayland-egl1 libelf1 \
692
+ libglib2.0-0t64 libpixman-1-0 \
693
+ && apt-get -o APT::Get::Always-Include-Phased-Updates=true \
694
+ upgrade -y; then break; fi; \
695
+ apt_update_tries=$((apt_update_tries - 1)); \
696
+ echo "apt-get update/install/upgrade failed (mirror sync?), $apt_update_tries retries left"; \
697
+ sleep 10; \
698
+ rm -rf /var/lib/apt/lists/*; \
699
+ done; \
700
+ test "$apt_update_tries" -gt 0; \
701
+ apt-get clean; rm -rf /var/lib/apt/lists/*
702
+
703
+ # LLVM 18 runtime: copy the entire llvm-18 install tree from the builder
704
+ # (libLLVM-18.so.1 + the llvm-18 runtime libraries that llvmpipe and
705
+ # lavapipe link against). The ldconfig step later in this stage adds
706
+ # /usr/lib/llvm-18/lib to the search path so the loader picks them up.
707
+ COPY --from=builder /usr/lib/llvm-18/ /usr/lib/llvm-18/
708
+
709
+ # mesa 26.2.1 from the staging root: dri drivers, the lavapipe vulkan ICD
710
+ # json (lvp_icd.<arch>.json), the rusticl opencl ICD under /etc/OpenCL and
711
+ # the shared glapi.
712
+ COPY --from=builder /staging/usr /usr/
713
+ COPY --from=builder /staging/etc /etc/
714
+ # node 26.7.0 carried over from the builder, including headers so native
715
+ # addons (vhe_gpu.node and downstream user addons) rebuild without a second
716
+ # nodesource round-trip.
717
+ COPY --from=builder /usr/bin/node /usr/bin/node
718
+ COPY --from=builder /usr/bin/npm /usr/bin/npm
719
+ COPY --from=builder /usr/bin/npx /usr/bin/npx
720
+ COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules
721
+ COPY --from=builder /usr/include/node /usr/include/node
722
+ # the spoofing artifacts: preload core, gpu shim with the nvidia soname
723
+ # symlinks, the fake nvidia-smi binary, the aetherforge control plane, the
724
+ # virtualization core shared library, the napi addon and the baked
725
+ # /etc/virtual profile.
726
+ COPY --from=builder /out/usr/local/lib /usr/local/lib/
727
+ COPY --from=builder /out/usr/local/bin/nvidia-smi /usr/local/bin/nvidia-smi
728
+ COPY --from=builder /out/usr/local/bin/aetherforge /usr/local/bin/aetherforge
729
+ COPY --from=builder /out/engine /engine
730
+ COPY --from=builder /out/etc/virtual /etc/virtual
731
+
732
+ # /etc/virtual cpuinfo + meminfo baked per profile here in the runtime
733
+ # stage (the builder is profile-independent so its layers share the buildx
734
+ # cache across max/balanced/lite; the profile identity is baked here).
735
+ # cpuinfo model name, processor count and thread count per profile
736
+ # (max: AMD EPYC 9965 192c/384t, balanced: AMD Ryzen Threadripper
737
+ # Pro 9995WX 96c/192t, lite: AMD Ryzen 9 9950X3D 16c/32t); cpu
738
+ # family/model/stepping and address sizes match each identity
739
+ # (the desktop ryzen drops la57 and reports 46/48-bit addressing).
740
+ # meminfo static snapshot scaled to the profile memory plan (max 1 TiB,
741
+ # balanced 512 GiB, lite 128 GiB); the preload core regenerates
742
+ # a dynamic copy per process by default (fakemem pattern from
743
+ # VHE_TOTALRAM_GB), so this file is the VHE_MEMINFO_MODE=static
744
+ # source of truth and the fallback. VmallocTotal (the 64-bit
745
+ # address space) and Hugepagesize (the fixed 2 MiB huge page) stay
746
+ # constant regardless of the profile memory plan.
747
+ COPY <<'EOF' /tmp/meminfo-template
748
+ MemTotal: 1073741824 kB
749
+ MemFree: 33554432 kB
750
+ MemAvailable: 94673704 kB
751
+ Buffers: 8388608 kB
752
+ Cached: 58720256 kB
753
+ SwapCached: 0 kB
754
+ Active: 33554432 kB
755
+ Inactive: 16777216 kB
756
+ Active(anon): 20971520 kB
757
+ Inactive(anon): 4194304 kB
758
+ Active(file): 12582912 kB
759
+ Inactive(file): 12582912 kB
760
+ Unevictable: 0 kB
761
+ Mlocked: 0 kB
762
+ SwapTotal: 67108864 kB
763
+ SwapFree: 67108864 kB
764
+ Zswap: 0 kB
765
+ Zswapped: 0 kB
766
+ Dirty: 262144 kB
767
+ Writeback: 0 kB
768
+ AnonPages: 20971520 kB
769
+ Mapped: 4194304 kB
770
+ Shmem: 1048576 kB
771
+ KReclaimable: 2097152 kB
772
+ Slab: 4194304 kB
773
+ SReclaimable: 2097152 kB
774
+ SUnreclaim: 2097152 kB
775
+ KernelStack: 65536 kB
776
+ PageTables: 32768 kB
777
+ NFS_Unstable: 0 kB
778
+ Bounce: 0 kB
779
+ WritebackTmp: 0 kB
780
+ CommitLimit: 134217728 kB
781
+ Committed_AS: 33554432 kB
782
+ VmallocTotal: 34359738367 kB
783
+ VmallocUsed: 1048576 kB
784
+ VmallocChunk: 0 kB
785
+ Percpu: 65536 kB
786
+ HardwareCorrupted: 0 kB
787
+ AnonHugePages: 0 kB
788
+ ShmemHugePages: 0 kB
789
+ FileHugePages: 0 kB
790
+ HugePages_Total: 0
791
+ HugePages_Free: 0
792
+ HugePages_Rsvd: 0
793
+ HugePages_Surp: 0
794
+ Hugepagesize: 2048 kB
795
+ Hugetlb: 0 kB
796
+ DirectMap4k: 2097152 kB
797
+ DirectMap2M: 62914560 kB
798
+ DirectMap1G: 67108864 kB
799
+ EOF
800
+
801
+ RUN set -eux; \
802
+ : > /etc/virtual/cpuinfo; \
803
+ case "${VHE_PROFILE_CPU}" in \
804
+ epyc9965) \
805
+ cpuname="AMD EPYC 9965"; \
806
+ cpufamily="26"; cpumodel="96"; cpustepping="2"; \
807
+ addr="52 bits physical, 57 bits virtual"; la57="1" ;; \
808
+ threadripperpro9995wx) \
809
+ cpuname="AMD Ryzen Threadripper Pro 9995WX"; \
810
+ cpufamily="26"; cpumodel="96"; cpustepping="2"; \
811
+ addr="52 bits physical, 57 bits virtual"; la57="1" ;; \
812
+ ryzen9950x3d) \
813
+ cpuname="AMD Ryzen 9 9950X3D"; \
814
+ cpufamily="25"; cpumodel="97"; cpustepping="2"; \
815
+ addr="46 bits physical, 48 bits virtual"; la57="0" ;; \
816
+ *) \
817
+ echo "unknown VHE_PROFILE_CPU=${VHE_PROFILE_CPU}; expected epyc9965, threadripperpro9995wx or ryzen9950x3d" >&2; \
818
+ exit 1 ;; \
819
+ esac; \
820
+ vcpus="${VHE_PROFILE_VCPUS}"; \
821
+ threads="${VHE_PROFILE_THREADS}"; \
822
+ ram_gb="${VHE_PROFILE_RAM_GB}"; \
823
+ modelname="${cpuname} ${vcpus}-Core Processor"; \
824
+ FLAGS='fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good amd_lbr vnnm nonstop_tsc cpuid extd_apicid amd_dcm aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 invpcid_single hw_pstate ssbd mba perfmon_v2 ibrs ibpb stibp vmmcall fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm md_clear serialize tsxldtrk avx512_fp16'; \
825
+ if [ "$la57" = "1" ]; then FLAGS="${FLAGS} la57"; fi; \
826
+ for i in $(seq 0 $((vcpus - 1))); do \
827
+ { \
828
+ printf 'processor\t: %d\n' "$i"; \
829
+ printf 'vendor_id\t: AuthenticAMD\n'; \
830
+ printf 'cpu family\t: %s\n' "$cpufamily"; \
831
+ printf 'model\t\t: %s\n' "$cpumodel"; \
832
+ printf 'model name\t: %s\n' "$modelname"; \
833
+ printf 'stepping\t: %s\n' "$cpustepping"; \
834
+ printf 'microcode\t: 0x1100140\n'; \
835
+ printf 'cpu MHz\t\t: 2250.000\n'; \
836
+ printf 'cache size\t: 512 KB\n'; \
837
+ printf 'physical id\t: 0\n'; \
838
+ printf 'siblings\t: %s\n' "$threads"; \
839
+ printf 'core id\t\t: %d\n' "$((i % vcpus))"; \
840
+ printf 'cpu cores\t: %s\n' "$vcpus"; \
841
+ printf 'apicid\t\t: %d\n' "$i"; \
842
+ printf 'initial apicid\t: %d\n' "$i"; \
843
+ printf 'fpu\t\t: yes\n'; \
844
+ printf 'fpu_exception\t: yes\n'; \
845
+ printf 'cpuid level\t: 16\n'; \
846
+ printf 'wp\t\t: yes\n'; \
847
+ printf 'flags\t\t: %s\n' "$FLAGS"; \
848
+ printf 'bugs\t\t: sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass srso\n'; \
849
+ printf 'bogomips\t: 4500.00\n'; \
850
+ printf 'TLB size\t: 3584 4K pages\n'; \
851
+ printf 'clflush size\t: 64\n'; \
852
+ printf 'cache_alignment\t: 64\n'; \
853
+ printf 'address sizes\t: %s\n' "$addr"; \
854
+ printf 'power management: ts ttp tm hwpstate cpb eff_freq_ro [13] [16]\n'; \
855
+ } >> /etc/virtual/cpuinfo; \
856
+ done; \
857
+ grep -c '^processor' /etc/virtual/cpuinfo | grep -qx "$vcpus"; \
858
+ total_kb=$((ram_gb * 1024 * 1024)); \
859
+ awk -v total="$total_kb" 'function scale(line, a, n, i, out, key) { n = split(line, a, " "); if (n >= 2 && a[n] == "kB" && (a[n-1]+0) > 0) { key = a[1]; if (key != "VmallocTotal:" && key != "Hugepagesize:") { a[n-1] = int((a[n-1]+0) * total / 1073741824); } } out = a[1]; for (i = 2; i <= n; i++) out = out " " a[i]; return out; } { print scale($0) }' /tmp/meminfo-template > /etc/virtual/meminfo; \
860
+ rm -f /tmp/meminfo-template; \
861
+ grep -E "^MemTotal: +${total_kb} kB" /etc/virtual/meminfo; \
862
+ echo "baked /etc/virtual: ${modelname} (${vcpus}c/${threads}t), MemTotal ${total_kb} kB (${ram_gb} GiB plan)"
863
+ # the python bridge from its dedicated stage.
864
+ COPY --from=python-bridge /bridge/qemubridge.py /engine/qemubridge.py
865
+
866
+ # the engine sources and the specification files consumed at runtime
867
+ # (docker.config "final" stage copies the whole tree; the flat layout
868
+ # keeps that a single COPY layer).
869
+ COPY *.ts package.json tsconfig.json biome.json jsdom.d.ts /engine/
870
+ COPY processors.json gpus.json cores.json boards.json vm.config.json virtualhardware.json /engine/
871
+ COPY qemu.config mttg.config passage.config docker.config /engine/
872
+
873
+ # the web node ships inside the main container: the mesh edition (auth,
874
+ # sqlite database, dashboard and the browser sandbox pages) runs from the
875
+ # same image, so one container serves the engine AND the web surface.
876
+ # run it with: docker run ghcr.io/wenathlan/saddle node /engine/web/server.js
877
+ COPY web /engine/web
878
+
879
+ RUN set -eux; \
880
+ chmod 0755 /usr/local/bin/nvidia-smi /usr/local/bin/aetherforge; \
881
+ chmod 0644 /etc/virtual/*; \
882
+ printf '/usr/lib/llvm-18/lib\n/usr/local/lib\n' > /etc/ld.so.conf.d/vhe.conf; \
883
+ mkdir -p /etc/OpenCL/vendors; \
884
+ case "${TARGETARCH}" in \
885
+ arm64|aarch64) ln -sf lvp_icd.aarch64.json /usr/share/vulkan/icd.d/lvp_icd.json ;; \
886
+ ppc64le) ln -sf lvp_icd.ppc64le.json /usr/share/vulkan/icd.d/lvp_icd.json ;; \
887
+ s390x) ln -sf lvp_icd.s390x.json /usr/share/vulkan/icd.d/lvp_icd.json ;; \
888
+ *) ln -sf lvp_icd.x86_64.json /usr/share/vulkan/icd.d/lvp_icd.json ;; \
889
+ esac; \
890
+ ldconfig; \
891
+ node --version; \
892
+ python3 -m py_compile /engine/qemubridge.py; \
893
+ nvidia-smi b200 1 | head -2; \
894
+ ldconfig -p | grep -E 'libGL|lvp|rusticl|libLLVM-18' | head -5
895
+
896
+ # entrypoint: starts Xvfb on :99, validates the GL/Vulkan/OpenCL stack
897
+ # and execs the engine command. the bootstrap is EMBEDDED HERE (the one
898
+ # container file standard - the repository keeps no separate
899
+ # entrypoint.sh): the heredoc COPY below writes the script verbatim
900
+ # into the image and the quoted delimiter keeps every shell expansion
901
+ # literal, so the file is the exact former entrypoint.sh.
902
+ COPY <<'ENTRYPOINT_SCRIPT_EOF' /entrypoint.sh
903
+ #!/usr/bin/env bash
904
+ #
905
+ # entrypoint.sh - runtime bootstrap for the saddle virtual hardware
906
+ # engine image.
907
+ #
908
+ # responsibilities, in order:
909
+ # 0. print the engine banner, pin the MTTCG mode for downstream
910
+ # consumers, export the gallium/mesa environment overrides that are
911
+ # unique to the headless software stack, and report the firecracker
912
+ # warm-pool readiness note (the microvm runtime shares this image).
913
+ # 1. start Xvfb on display :99 with the validated headless-GLX recipe
914
+ # (Xvfb(1): -ac disables access control, -screen 0 1920x1080x24
915
+ # defines the framebuffer, -nolisten tcp closes the network surface,
916
+ # +extension GLX +render enable the GLX and RENDER extensions the
917
+ # mesa llvmpipe driver expects, -noreset keeps the server alive
918
+ # after the last client disconnects) and wait for readiness with
919
+ # xdpyinfo.
920
+ # 2. verify the LD_PRELOAD spoofing core is installed and loadable
921
+ # (global /etc/ld.so.preload from the dolos deployment pattern).
922
+ # 3. run the hardware validation heads: glxinfo (OpenGL renderer must
923
+ # be llvmpipe), vulkaninfo --summary (lavapipe ICD) and clinfo
924
+ # (rusticl reporting a gpu device on OpenCL 3.1); failures log
925
+ # warnings unless VHE_STRICT=1 turns them fatal.
926
+ # 4. allocate a random listening port in 30000-60000 when VHE_PORT/PORT
927
+ # is unset (shuf with a /dev/urandom fallback), so no host port is
928
+ # ever hardcoded across the engine.
929
+ # 5. exec "$@" so the engine becomes pid 1 and receives signals
930
+ # directly.
931
+ #
932
+ # environment knobs (all optional):
933
+ # VHE_DISPLAY x display number, default 99
934
+ # VHE_SCREEN screen geometry, default 1920x1080x24
935
+ # VHE_XVFB_WAIT max seconds waiting for the display, default 30
936
+ # VHE_SKIP_XVFB 1 skips display startup entirely (pure compute)
937
+ # VHE_SKIP_PRELOAD 1 skips the spoofing core check
938
+ # VHE_SKIP_VALIDATE 1 skips the glxinfo/vulkaninfo/clinfo probes
939
+ # VHE_STRICT 1 turns validation failures into entrypoint failures
940
+ # VHE_PORT / PORT fixed port; otherwise one is drawn from 30000-60000
941
+ # VHE_DEBUG 1 prints every decision to stderr
942
+ #
943
+ # the script never exits on validation warnings: the engine degrades to
944
+ # whatever hardware surface is actually functional, mirroring the
945
+ # defensive fallback chain of libvirtualhardware.so itself.
946
+
947
+ set -Eeuo pipefail
948
+
949
+ # ---------------------------------------------------------------- logging --
950
+
951
+ log() { printf '[saddle-entrypoint] %s\n' "$*" >&2; }
952
+ dbg() { [[ "${VHE_DEBUG:-0}" == "1" ]] && printf '[saddle-entrypoint:debug] %s\n' "$*" >&2 || true; }
953
+ warn() { printf '[saddle-entrypoint:warn] %s\n' "$*" >&2; }
954
+ die() { printf '[saddle-entrypoint:fatal] %s\n' "$*" >&2; exit 1; }
955
+
956
+ # ------------------------------------------------------------- variables --
957
+
958
+ vdisplaynum="${VHE_DISPLAY:-99}"
959
+ vdisplayid=":${vdisplaynum}"
960
+ screengeom="${VHE_SCREEN:-1920x1080x24}"
961
+ xvfbwait="${VHE_XVFB_WAIT:-30}"
962
+ xvfbpid=""
963
+ portvalue=""
964
+
965
+ cleanup() {
966
+ if [[ -n "${xvfbpid}" ]] && kill -0 "${xvfbpid}" 2>/dev/null; then
967
+ log "stopping Xvfb (pid ${xvfbpid})"
968
+ kill -TERM "${xvfbpid}" 2>/dev/null || true
969
+ wait "${xvfbpid}" 2>/dev/null || true
970
+ fi
971
+ }
972
+ trap cleanup EXIT
973
+ trap 'die "interrupted"' INT TERM
974
+
975
+ requirebin() {
976
+ command -v "$1" >/dev/null 2>&1 || die "required binary '$1' not found in PATH"
977
+ }
978
+
979
+ # ------------------------------------------- 0. banner and env overrides --
980
+
981
+ # report the baked profile identity (read from /etc/virtual so the banner
982
+ # matches the profile the image was built for: max/balanced/lite). Single
983
+ # awk process with an early exit per file (no sed|head pipeline: under
984
+ # set -o pipefail, head exiting after the first match would SIGPIPE sed on
985
+ # the large max/balanced cpuinfo and abort the entrypoint).
986
+ vhe_model="$(awk '/^model name/ { sub(/^[[:space:]]*model name[[:space:]]*:[[:space:]]*/, ""); print; exit }' /etc/virtual/cpuinfo 2>/dev/null)"
987
+ vhe_ram="$(awk '/^MemTotal/ { print $2; exit }' /etc/virtual/meminfo 2>/dev/null || true)"
988
+ case "${vhe_ram}" in *[!0-9]*) vhe_ram="" ;; esac
989
+ if [ -n "${vhe_model}" ] && [ -n "${vhe_ram}" ]; then
990
+ vhe_ram_gb=$(( vhe_ram / 1024 / 1024 ))
991
+ log "saddle virtual hardware engine (${vhe_model}, ${vhe_ram_gb} GiB, mesa 26.2.1)"
992
+ else
993
+ log "saddle virtual hardware engine (mesa 26.2.1)"
994
+ fi
995
+ log "docker contract: memory-swap=-1 shm-size=2g tmpfs shader cache 20G, Xvfb ${vdisplayid}"
996
+
997
+ # MTTCG is the default execution mode for the qemu layer of this stack:
998
+ # one host thread per vCPU, selected by -accel tcg,thread=multi. the flag
999
+ # is exported so child orchestrators (qemubridge.py, the aetherforge
1000
+ # control plane) observe a consistent default without re-deriving it.
1001
+ export QEMU_MTTCG="${QEMU_MTTCG:-1}"
1002
+
1003
+ # gallium/mesa overrides: keep the software rasterizer pinned even when
1004
+ # the base image environment is overridden by the operator, and expose
1005
+ # the avx-512 cpu caps list the llvmpipe JIT keys on. these lines are the
1006
+ # deployment counterpart of the ENV block baked in the Dockerfile.
1007
+ export LP_NATIVE_VECTOR_WIDTH="${LP_NATIVE_VECTOR_WIDTH:-512}"
1008
+ export LP_NUM_THREADS="${LP_NUM_THREADS:-0}"
1009
+ export GALLIUM_DRIVER="${GALLIUM_DRIVER:-llvmpipe}"
1010
+ export MESA_GL_VERSION_OVERRIDE="${MESA_GL_VERSION_OVERRIDE:-4.6}"
1011
+ export GALLIUM_OVERRIDE_CPU_CAPS="${GALLIUM_OVERRIDE_CPU_CAPS:-avx512f,avx512bw,avx512cd,avx512dq,avx512vl,avx512vnni,avx512_bf16}"
1012
+ export LD_PRELOAD="/usr/local/lib/libvirtualhardware.so${LD_PRELOAD:+:${LD_PRELOAD}}"
1013
+
1014
+ # the microvm runtime shares this image: the firecracker warm pool boots
1015
+ # in 125 ms (150 boots/s, 3-5 ms restore from a snapshot) and needs no
1016
+ # additional daemon here - the note keeps operators from expecting one.
1017
+ dbg "firecracker warm pool ready (125 ms boot, 3-5 ms restore)"
1018
+
1019
+ # ------------------------------------------------------ 1. display setup --
1020
+
1021
+ if [[ "${VHE_SKIP_XVFB:-0}" == "1" ]]; then
1022
+ log "VHE_SKIP_XVFB=1, skipping display startup"
1023
+ else
1024
+ requirebin Xvfb
1025
+ requirebin xdpyinfo
1026
+
1027
+ if xdpyinfo -display "${vdisplayid}" >/dev/null 2>&1; then
1028
+ log "display ${vdisplayid} already answering, reusing it"
1029
+ else
1030
+ log "starting Xvfb on ${vdisplayid} (${screengeom})"
1031
+ Xvfb "${vdisplayid}" \
1032
+ -ac \
1033
+ -screen 0 "${screengeom}" \
1034
+ -nolisten tcp \
1035
+ +extension GLX \
1036
+ +render \
1037
+ -noreset &
1038
+ xvfbpid=$!
1039
+
1040
+ waited=0
1041
+ until xdpyinfo -display "${vdisplayid}" >/dev/null 2>&1; do
1042
+ if ! kill -0 "${xvfbpid}" 2>/dev/null; then
1043
+ die "Xvfb died during startup"
1044
+ fi
1045
+ if [[ "${waited}" -ge "${xvfbwait}" ]]; then
1046
+ die "Xvfb not ready after ${xvfbwait}s on ${vdisplayid}"
1047
+ fi
1048
+ waited=$((waited + 1))
1049
+ sleep 1
1050
+ done
1051
+ log "Xvfb ready on ${vdisplayid} after ${waited}s (pid ${xvfbpid})"
1052
+ fi
1053
+ export DISPLAY="${vdisplayid}"
1054
+ fi
1055
+
1056
+ # ------------------------------------------------- 2. preload core check --
1057
+
1058
+ if [[ "${VHE_SKIP_PRELOAD:-0}" != "1" ]]; then
1059
+ preloadlib="/usr/local/lib/libvirtualhardware.so"
1060
+ if [[ ! -r "${preloadlib}" ]]; then
1061
+ die "spoofing core ${preloadlib} missing from the image"
1062
+ fi
1063
+ if ! LD_PRELOAD="${preloadlib}" /bin/true 2>/dev/null; then
1064
+ die "spoofing core ${preloadlib} is not loadable in this environment"
1065
+ fi
1066
+ if [[ -f /etc/ld.so.preload ]] && grep -q 'libvirtualhardware' /etc/ld.so.preload; then
1067
+ dbg "/etc/ld.so.preload carries the core (dolos global install)"
1068
+ else
1069
+ warn "/etc/ld.so.preload does not list libvirtualhardware.so; per-process spoofing only"
1070
+ fi
1071
+ if [[ -r /etc/virtual/cpuinfo ]]; then
1072
+ dbg "virtual cpuinfo present ($(grep -c '^processor' /etc/virtual/cpuinfo) processors)"
1073
+ else
1074
+ warn "/etc/virtual/cpuinfo missing; cpu reads fall through to the real host"
1075
+ fi
1076
+ if [[ -r /etc/virtual/meminfo ]]; then
1077
+ dbg "virtual meminfo present: $(head -1 /etc/virtual/meminfo)"
1078
+ else
1079
+ warn "/etc/virtual/meminfo missing; memory reads fall through to the real host"
1080
+ fi
1081
+ fi
1082
+
1083
+ # ------------------------------------------------- 3. stack validation --
1084
+
1085
+ if [[ "${VHE_SKIP_VALIDATE:-0}" != "1" ]]; then
1086
+ validationfailed=0
1087
+
1088
+ if command -v glxinfo >/dev/null 2>&1; then
1089
+ if renderer="$(glxinfo -B 2>/dev/null | sed -n 's/^OpenGL renderer string: //p' | head -1)"; then
1090
+ if [[ -n "${renderer}" ]]; then
1091
+ log "glxinfo: ${renderer}"
1092
+ if [[ "${renderer}" != *llvmpipe* ]]; then
1093
+ warn "OpenGL renderer is not llvmpipe: ${renderer}"
1094
+ fi
1095
+ fi
1096
+ else
1097
+ warn "glxinfo failed on display ${DISPLAY:-unset}"
1098
+ validationfailed=1
1099
+ fi
1100
+ else
1101
+ warn "glxinfo not available (mesa-utils missing), skipping GL probe"
1102
+ fi
1103
+
1104
+ if command -v vulkaninfo >/dev/null 2>&1; then
1105
+ if api="$(VULKANINFO_LOADER_DEBUG="" vulkaninfo --summary 2>/dev/null | sed -n 's/^.*apiVersion.*: *//p' | head -1)"; then
1106
+ log "vulkaninfo: api ${api:-reported} through ${VK_DRIVER_FILES:-default ICD search}"
1107
+ else
1108
+ warn "vulkaninfo --summary failed (check VK_DRIVER_FILES=${VK_DRIVER_FILES:-unset})"
1109
+ validationfailed=1
1110
+ fi
1111
+ else
1112
+ warn "vulkaninfo not available (vulkan-tools missing), skipping Vulkan probe"
1113
+ fi
1114
+
1115
+ if command -v clinfo >/dev/null 2>&1; then
1116
+ clhead="$(clinfo 2>/dev/null | grep -E 'Platform Name|Device Type|OpenCL C Version' | head -3 || true)"
1117
+ if [[ -n "${clhead}" ]]; then
1118
+ log "clinfo: $(echo "${clhead}" | tr '\n' ' ')"
1119
+ else
1120
+ warn "clinfo produced no platform report (rusticl ICD not reachable)"
1121
+ validationfailed=1
1122
+ fi
1123
+ else
1124
+ warn "clinfo not available, skipping OpenCL probe"
1125
+ fi
1126
+
1127
+ if command -v nvidia-smi >/dev/null 2>&1; then
1128
+ log "nvidia-smi virtual identity: $(nvidia-smi "${VHE_GPU_PROFILE:-a100}" 1 2>/dev/null | sed -n '2p' | cut -c1-60)..."
1129
+ else
1130
+ warn "virtual nvidia-smi adapter not on PATH"
1131
+ fi
1132
+
1133
+ if [[ "${validationfailed}" != "0" && "${VHE_STRICT:-0}" == "1" ]]; then
1134
+ die "VHE_STRICT=1 and the validation reported failures"
1135
+ fi
1136
+ fi
1137
+
1138
+ # --------------------------------------------------- 4. port allocation --
1139
+
1140
+ if [[ -z "${VHE_PORT:-}" && -z "${PORT:-}" ]]; then
1141
+ if command -v shuf >/dev/null 2>&1; then
1142
+ portvalue="$(shuf -i 30000-60000 -n 1)"
1143
+ elif [[ -r /dev/urandom ]]; then
1144
+ # deterministic fallback: urandom bytes scaled into the 30000-60000 range
1145
+ portvalue="$(( 30000 + ($(od -An -N2 -tu2 /dev/urandom | tr -d ' ') % 30001) ))"
1146
+ else
1147
+ portvalue="$(( 30000 + (RANDOM % 30001) ))"
1148
+ fi
1149
+ log "no port configured, allocated random port ${portvalue} (range 30000-60000)"
1150
+ elif [[ -n "${VHE_PORT:-}" ]]; then
1151
+ portvalue="${VHE_PORT}"
1152
+ log "using VHE_PORT=${portvalue}"
1153
+ else
1154
+ portvalue="${PORT}"
1155
+ log "using PORT=${portvalue}"
1156
+ fi
1157
+ export PORT="${portvalue}"
1158
+ export VHE_PORT="${portvalue}"
1159
+
1160
+ # ----------------------------------------------------------- 5. exec app --
1161
+
1162
+ log "environment ready: display=${DISPLAY:-none} port=${portvalue} profile=${VHE_GPU_PROFILE:-a100} gpus=${VHE_GPUS:-8} ram=${VHE_TOTALRAM_GB:-128}GiB cpus=${VHE_CPUS:-192} mttcg=${QEMU_MTTCG}"
1163
+
1164
+ if [[ $# -eq 0 ]]; then
1165
+ log "no command supplied, keeping the sandbox alive (exec /bin/bash)"
1166
+ set -- /bin/bash
1167
+ fi
1168
+
1169
+ # exec replaces this shell: Xvfb keeps running as an orphan supervised by
1170
+ # the container init (docker --init / compose init: true), and the engine
1171
+ # receives signals directly as the foreground process.
1172
+ exec "$@"
1173
+ ENTRYPOINT_SCRIPT_EOF
1174
+ RUN chmod 0755 /entrypoint.sh
1175
+
1176
+ # environment: software GL stack pinning (llvmpipe), GL 4.6 spoof, AVX-512
1177
+ # vector width for the gallivm JIT, single-threaded rasterization by
1178
+ # default to avoid container oversubscription (raise LP_NUM_THREADS to the
1179
+ # vCPU count for throughput), the gallium cpu caps override list, lavapipe
1180
+ # as the only vulkan ICD (VK_DRIVER_FILES; VK_ICD_FILENAMES is deprecated),
1181
+ # rusticl on llvmpipe reporting a gpu device on OpenCL 3.1, display :99 for
1182
+ # Xvfb, plus the default virtual profile knobs consumed by
1183
+ # libvirtualhardware and the former compose service settings merged
1184
+ # here (the web node database SADDLE_DB the recipes bind their webdata
1185
+ # volume at, and NODE_ENV).
1186
+ ENV LIBGL_ALWAYS_SOFTWARE="true" \
1187
+ GALLIUM_DRIVER="llvmpipe" \
1188
+ MESA_GL_VERSION_OVERRIDE="4.6" \
1189
+ MESA_GLSL_VERSION_OVERRIDE="460" \
1190
+ GALLIUM_OVERRIDE_CPU_CAPS="avx512f,avx512bw,avx512cd,avx512dq,avx512vl,avx512vnni,avx512_bf16" \
1191
+ LP_NATIVE_VECTOR_WIDTH="512" \
1192
+ LP_NUM_THREADS="0" \
1193
+ VK_DRIVER_FILES="/usr/share/vulkan/icd.d/lvp_icd.json" \
1194
+ RUSTICL_ENABLE="llvmpipe" \
1195
+ RUSTICL_DEVICE_TYPE="gpu" \
1196
+ RUSTICL_CL_VERSION="3.1" \
1197
+ MESA_SHADER_CACHE_DIR="/cache/mesa_shader_cache" \
1198
+ MESA_SHADER_CACHE_MAX_SIZE="20G" \
1199
+ DISPLAY=":99" \
1200
+ VHE_TOTALRAM_GB="${VHE_PROFILE_RAM_GB}" \
1201
+ VHE_CPUS="${VHE_PROFILE_VCPUS}" \
1202
+ VHE_KERNEL_RELEASE="6.18.0-vhe" \
1203
+ VHE_GPU_PROFILE="${VHE_PROFILE_GPU}" \
1204
+ VHE_GPUS="${VHE_PROFILE_GPUS}" \
1205
+ VHE_MEMINFO_REFRESH="5" \
1206
+ SADDLE_DB="/data/web/saddle.db" \
1207
+ NODE_ENV="production" \
1208
+ PATH="/engine/native:${PATH}"
1209
+
1210
+ # non-root runtime identity; uid/gid 10000 avoids collisions with host
1211
+ # accounts on bind mounts and matches the volume and tmpfs ownership
1212
+ # (the former compose shader-cache volume carried uid=10000,gid=10000).
1213
+ RUN set -eux; \
1214
+ groupadd --gid "${VHE_GID}" vhe; \
1215
+ useradd --uid "${VHE_UID}" --gid "${VHE_GID}" --create-home \
1216
+ --shell /bin/bash vhe; \
1217
+ mkdir -p /engine /cache/mesa_shader_cache /data/vmdata /data/web; \
1218
+ chown -R vhe:vhe /engine /cache /data; \
1219
+ chmod 0777 /cache/mesa_shader_cache
1220
+
1221
+ # global preload installation, written last so no image-build step after
1222
+ # this point runs under the interposer (dolos deployment pattern); setuid
1223
+ # binaries ignore /etc/ld.so.preload by loader design.
1224
+ RUN set -eux; \
1225
+ echo /usr/local/lib/libvirtualhardware.so > /etc/ld.so.preload; \
1226
+ cat /proc/meminfo | head -1; \
1227
+ free -m | head -2 || true
1228
+
1229
+ # healthcheck: the X display answers xdpyinfo and the virtual meminfo is in
1230
+ # place; start-period covers the Xvfb + shader-cache warmup. start-interval
1231
+ # probes every 5s during the grace window (compose 5.5.0 / docker 29.7.2
1232
+ # support the field).
1233
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --start-interval=5s --retries=3 \
1234
+ CMD xdpyinfo -display :99 >/dev/null 2>&1 \
1235
+ && grep -q 'MemTotal' /etc/virtual/meminfo \
1236
+ || exit 1
1237
+
1238
+ # OCI labels for registry introspection.
1239
+ LABEL org.opencontainers.image.title="saddle virtual-hardware engine (the grand merge)" \
1240
+ org.opencontainers.image.description="100% software virtual hardware: per-profile CPU/memory spoofing via LD_PRELOAD (max/balanced/lite), mesa 26.2.1 llvmpipe/lavapipe/rusticl, QEMU 11.1.0 TCG/MTTCG, virtual nvidia-smi adapter + NVML/CUDA shims, node 26.7.0, python bridge" \
1241
+ org.opencontainers.image.version="2.0.2" \
1242
+ org.opencontainers.image.licenses="MIT" \
1243
+ org.opencontainers.image.source="https://github.com/wenathlan/saddle" \
1244
+ org.opencontainers.image.documentation="https://github.com/wenathlan/saddle/blob/main/README.md" \
1245
+ org.opencontainers.image.created="2026-08-23T00:00:00Z"
1246
+
1247
+ WORKDIR /engine
1248
+ USER vhe
1249
+ STOPSIGNAL SIGTERM
1250
+
1251
+ # EXPOSE 8080: the engine service port of the docker run recipe above
1252
+ # (the former compose VHE_ENGINE_PORT default, published as 31280:8080).
1253
+ # a bare docker run without -e PORT leaves the embedded entrypoint
1254
+ # picking a random port in 30000-60000, so nothing here hardcodes a
1255
+ # bind - EXPOSE documents the recipe.
1256
+ EXPOSE 8080
1257
+
1258
+ # the state surface of the docker run recipes (the former compose named
1259
+ # volumes): /data carries vmdata (virtual machine images, bridge plans)
1260
+ # and webdata (the web node sqlite SADDLE_DB points at);
1261
+ # /cache/mesa_shader_cache keeps the JIT'd llvmpipe/lavapipe shaders
1262
+ # off the overlay filesystem (the recipe mounts a 20g tmpfs there).
1263
+ # without an explicit -v docker provisions anonymous volumes at these
1264
+ # paths so the state never lives in the container layer.
1265
+ VOLUME /data /cache/mesa_shader_cache
1266
+
1267
+ # the embedded bootstrap (the heredoc COPY above) starts Xvfb on :99,
1268
+ # validates the GL/Vulkan/OpenCL stack and execs the engine command as
1269
+ # pid 1.
1270
+ ENTRYPOINT ["/entrypoint.sh"]
1271
+
1272
+ # ══════════════════════════════════════════════════════════════════════
1273
+ # saddle node-engine stages — the storage/compute node service of the
1274
+ # grand merge (folded verbatim from the former dockerfile.saddle: the
1275
+ # hardened npm pinning survives; the build outputs dist/<domain>.js after
1276
+ # the flat consolidation). build explicitly:
1277
+ # docker build --target saddle-runtime -t saddle:<version> .
1278
+ # ══════════════════════════════════════════════════════════════════════
1279
+
1280
+ FROM node:26.8.1-bookworm-slim AS saddle-build
1281
+
1282
+ ARG SADDLE_VERSION
1283
+ WORKDIR /app
1284
+
1285
+ ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
1286
+
1287
+ COPY package.json package-lock.json ./
1288
+ RUN npm ci --ignore-scripts --legacy-peer-deps
1289
+ COPY . .
1290
+ RUN npm run build:engine \
1291
+ && node --check dist/cli.js
1292
+
1293
+ FROM node:26.8.1-bookworm-slim AS saddle-runtime
1294
+
1295
+ ARG SADDLE_VERSION
1296
+ LABEL org.opencontainers.image.title="Saddle" \
1297
+ org.opencontainers.image.description="Storage-backed browser and compute engine (the grand-merge node service)" \
1298
+ org.opencontainers.image.source="https://github.com/wenathlan/saddle" \
1299
+ org.opencontainers.image.version="$SADDLE_VERSION"
1300
+
1301
+ WORKDIR /app
1302
+
1303
+ # the service ENV surface of the former compose.yml (folded here so the
1304
+ # one container file carries the whole container context: memory engine
1305
+ # tier selection, the sbot platform tag and the sbot cdn url override)
1306
+ ENV NODE_ENV=production \
1307
+ NPM_CONFIG_LEGACY_PEER_DEPS=true \
1308
+ SADDLE_MEMORY_ENGINE=ram \
1309
+ SBOT_PLATFORM="" \
1310
+ SBOT_CDN_URL=""
1311
+
1312
+ COPY --from=saddle-build /app/package.json /app/package-lock.json ./
1313
+ RUN apt-get update \
1314
+ && apt-get upgrade -y \
1315
+ && rm -rf /var/lib/apt/lists/* \
1316
+ && npm install --global npm@12.0.2 \
1317
+ && tempdir="$(mktemp -d)" \
1318
+ && npm pack brace-expansion@5.0.9 ip-address@10.3.1 --pack-destination "$tempdir" \
1319
+ && mkdir -p /usr/local/lib/node_modules/npm/node_modules/brace-expansion /usr/local/lib/node_modules/npm/node_modules/ip-address \
1320
+ && tar -xzf "$tempdir/brace-expansion-5.0.9.tgz" --strip-components=1 -C /usr/local/lib/node_modules/npm/node_modules/brace-expansion \
1321
+ && tar -xzf "$tempdir/ip-address-10.3.1.tgz" --strip-components=1 -C /usr/local/lib/node_modules/npm/node_modules/ip-address \
1322
+ && rm -rf "$tempdir" \
1323
+ && npm ci --omit=dev --ignore-scripts --legacy-peer-deps \
1324
+ # prune the vendored build toolchains of optional native deps: the
1325
+ # cpu-features cmake sources ship nested Dockerfiles (build CI of
1326
+ # the dependency) that the trivy misconfig scanner flags (DS-0002
1327
+ # USER, DS-0029 no-install-recommends) inside the shipped image.
1328
+ # With --ignore-scripts the binding never builds and the package
1329
+ # degrades to a no-op at runtime - the sources are dead weight.
1330
+ && rm -rf node_modules/cpu-features/deps \
1331
+ && find node_modules -type f -name Dockerfile -delete
1332
+ COPY --from=saddle-build /app/dist ./dist
1333
+
1334
+ USER node
1335
+
1336
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["node", "dist/cli.js", "help"]
1337
+
1338
+ ENTRYPOINT ["node", "dist/cli.js"]
1339
+ CMD ["help"]