@threadedstack/wmd-web 0.0.0-stage → 0.1.13

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 (92) hide show
  1. package/README.md +2601 -2
  2. package/RELEASE.json +123 -0
  3. package/export-page/coi-sw.js +20 -0
  4. package/export-page/index.html +17 -0
  5. package/export-page/run.css +10 -0
  6. package/export-page/run.js +236 -0
  7. package/guest/arm64/Image +4 -0
  8. package/guest/arm64/Image-4k +4 -0
  9. package/guest/arm64/initramfs.cpio.gz +0 -0
  10. package/guest/arm64/vm.wasm +0 -0
  11. package/package.json +36 -4
  12. package/page/sw.js +381 -0
  13. package/relay/control.ts +139 -0
  14. package/relay/engine.ts +787 -0
  15. package/relay/hosted.ts +660 -0
  16. package/relay/keys.ts +53 -0
  17. package/relay/local-tls.ts +121 -0
  18. package/relay/lock.ts +81 -0
  19. package/relay/logfile.ts +138 -0
  20. package/relay/main.ts +642 -0
  21. package/relay/policy.ts +241 -0
  22. package/relay/relay.ts +266 -0
  23. package/relay/token.ts +116 -0
  24. package/relay/v1.ts +107 -0
  25. package/relay/wisp.ts +615 -0
  26. package/src/base.js +325 -0
  27. package/src/boot.js +2037 -0
  28. package/src/calib-worker.js +40 -0
  29. package/src/fs/9p/codec.js +303 -0
  30. package/src/fs/9p/server.js +521 -0
  31. package/src/fs/errno.js +43 -0
  32. package/src/fs/graph.js +380 -0
  33. package/src/fs/overlay.js +1018 -0
  34. package/src/fs/pack-write.js +165 -0
  35. package/src/fs/pack.js +427 -0
  36. package/src/fs/seed.js +22 -0
  37. package/src/fs/svc-snapshot.js +65 -0
  38. package/src/fs/upper-pack.js +112 -0
  39. package/src/host/base-cache.js +195 -0
  40. package/src/host/base-file.js +328 -0
  41. package/src/host/base-io.js +93 -0
  42. package/src/host/boot-events.js +209 -0
  43. package/src/host/chan-service.js +300 -0
  44. package/src/host/chan-services.js +689 -0
  45. package/src/host/chan.js +655 -0
  46. package/src/host/ckpt-file.js +395 -0
  47. package/src/host/ckpt-io.js +207 -0
  48. package/src/host/ckpt-point.js +160 -0
  49. package/src/host/ckpt-save.js +64 -0
  50. package/src/host/ckpt.js +434 -0
  51. package/src/host/clock.js +111 -0
  52. package/src/host/ctl.js +778 -0
  53. package/src/host/errno.js +18 -0
  54. package/src/host/fs.js +167 -0
  55. package/src/host/gcnudge.js +83 -0
  56. package/src/host/i64shim.js +81 -0
  57. package/src/host/jit-cache.js +341 -0
  58. package/src/host/jit-hub-core.js +208 -0
  59. package/src/host/jit-hub.js +93 -0
  60. package/src/host/jit-snap.js +226 -0
  61. package/src/host/jit.js +1903 -0
  62. package/src/host/layout.js +100 -0
  63. package/src/host/monitor.js +110 -0
  64. package/src/host/p9mount.js +27 -0
  65. package/src/host/ram-mem.js +125 -0
  66. package/src/host/stdio.js +143 -0
  67. package/src/host/threads.js +109 -0
  68. package/src/host/wasi.js +335 -0
  69. package/src/host/wmd-imports.js +71 -0
  70. package/src/host/yamux.js +387 -0
  71. package/src/image-files.js +154 -0
  72. package/src/net/dhcp-dns.js +126 -0
  73. package/src/net/net-worker.js +172 -0
  74. package/src/net/packet.js +227 -0
  75. package/src/net/peer.js +230 -0
  76. package/src/net/proto.js +189 -0
  77. package/src/net/relay-client.js +226 -0
  78. package/src/net/stack.js +867 -0
  79. package/src/net/switch.js +160 -0
  80. package/src/net/wisp-client.js +570 -0
  81. package/src/net/wisp-transport.js +196 -0
  82. package/src/net/wisp.js +511 -0
  83. package/src/registry/client.js +176 -0
  84. package/src/registry/converter.js +68 -0
  85. package/src/registry/hub.js +221 -0
  86. package/src/registry/oci.js +76 -0
  87. package/src/registry/pack-worker.js +70 -0
  88. package/src/registry/pull.js +258 -0
  89. package/src/registry/ref.js +110 -0
  90. package/src/registry/wasi-mem.js +355 -0
  91. package/src/worker.js +315 -0
  92. package/wmdpack.wasm +0 -0
package/RELEASE.json ADDED
@@ -0,0 +1,123 @@
1
+ {
2
+ "name": "wmd-web",
3
+ "version": "0.3.1",
4
+ "commit": "23bf0595198cec6c2027017049b2caa39753efc2",
5
+ "previous": "wmd-web-v0.1.0",
6
+ "decisions": [
7
+ "D-486",
8
+ "D-488",
9
+ "D-494",
10
+ "D-498",
11
+ "D-505",
12
+ "D-506",
13
+ "D-507",
14
+ "D-509",
15
+ "D-511",
16
+ "D-516",
17
+ "D-518",
18
+ "D-531",
19
+ "D-533",
20
+ "D-539",
21
+ "D-542"
22
+ ],
23
+ "vmSources": "f638fbdada4ccae26c12334b851e12244c1985d8",
24
+ "guest": {
25
+ "arch": "arm64",
26
+ "files": {
27
+ "vm.wasm": {
28
+ "sha256": "f7affc474658fdfdc50e96c29a224eed1520ea69b426d6427fbc058eb141091a",
29
+ "bytes": 1548267
30
+ },
31
+ "Image": {
32
+ "sha256": "a6b3209cf69d5bc1dacf0f3ed67f92c535dbb4108d67b47091c2aef87c6d7b04",
33
+ "bytes": 39047176
34
+ },
35
+ "Image-4k": {
36
+ "sha256": "8ff6e338a0b42d9f774b35423e0e7f9d7f779c635446e8de2a3beb83d3518f8a",
37
+ "bytes": 38828040
38
+ },
39
+ "initramfs.cpio.gz": {
40
+ "sha256": "38101c6b9bd0f8ec8f1b0aea99046d80d36669052794fa8e0368b6a1743285c1",
41
+ "bytes": 4667433
42
+ }
43
+ }
44
+ },
45
+ "files": {
46
+ "README.md": "e26f6e48262e55f34f7bb2c3e5b4a91163aac356e3f07b5f82202b6ca16e10a3",
47
+ "export-page/coi-sw.js": "0300e670b413cb2e0aae35843fc583546bbf6687df971ab6df41cfa3b0d494d7",
48
+ "export-page/index.html": "f5450c89e63601bbbbffef421f03491b231d1f143070012c1c5315d4b0059f43",
49
+ "export-page/run.css": "b57adf1e76553549c1f89ff157b7973409dedc21889cd3932948cd05b51adc6f",
50
+ "export-page/run.js": "717636a632a40469106ddf1383e198eaac5d83651b5866d0699ec04b82462b7f",
51
+ "guest/arm64/Image": "a6b3209cf69d5bc1dacf0f3ed67f92c535dbb4108d67b47091c2aef87c6d7b04",
52
+ "guest/arm64/Image-4k": "8ff6e338a0b42d9f774b35423e0e7f9d7f779c635446e8de2a3beb83d3518f8a",
53
+ "guest/arm64/initramfs.cpio.gz": "38101c6b9bd0f8ec8f1b0aea99046d80d36669052794fa8e0368b6a1743285c1",
54
+ "guest/arm64/vm.wasm": "f7affc474658fdfdc50e96c29a224eed1520ea69b426d6427fbc058eb141091a",
55
+ "package.json": "5ec889bd829e3e0a021e042e59eaf4f832280ac2135ce8ef160582173aa9dce3",
56
+ "src/base.js": "982f8e46dbb727ec542a494ebdb884ccd19a31ff4e091425b2d1fc01b0d4754a",
57
+ "src/boot.js": "114f9cf0561bca17b042c8db2e72e6984f7a55fd8d19700730895935f226cc77",
58
+ "src/calib-worker.js": "3d610591a1b3b58f59c3a2ab457f43c90727d95973cd43055b3b73b76180611d",
59
+ "src/fs/9p/codec.js": "69711eb4ebc84eff39256138169fe18f5a5066d6cd9e4ff2daf61c65380d7ef7",
60
+ "src/fs/9p/server.js": "da0ba00ebb5522161bb0ef7cd94bfc9616e342bf865633dfef0ba5bc67627d7a",
61
+ "src/fs/errno.js": "b1f18bcd7b565be7c6c80084d87333e58fbaba2e71d81e1c8573ac62754b2530",
62
+ "src/fs/graph.js": "d24a247c7a39668078913e763e0a55520cf6fd74807bb5cd93e3f49f9b90b45b",
63
+ "src/fs/overlay.js": "9f24336b8686c40fcab1c8e6bfa68776b9067fb1a816c051de484603ede9981e",
64
+ "src/fs/pack-write.js": "3f1bc479bcac980ab568b24825eb813881858b14b7bb80ded618f7bd560cde90",
65
+ "src/fs/pack.js": "680ca17a4228f0dfaf1607a1f821755e9a36bd72cb28824a6d21b0906b8e7300",
66
+ "src/fs/seed.js": "1cd2765e5d28e961bc4e81b41898f768a63e24feaf44e0aa42bd23e5cb910a74",
67
+ "src/fs/svc-snapshot.js": "eae82a6e136f9430680197c617601028da35cbe8786df39d6cab28280a2f9a7c",
68
+ "src/fs/upper-pack.js": "737a7e2bb5aac0ff774645db5789d699adfff263ca6f979e6687a9183a47f9dc",
69
+ "src/host/base-cache.js": "e0a9ea65e4eb9e30e7f78b28d9e5653b395f8eaa6c5e4c75b92f123c8d6a76a9",
70
+ "src/host/base-file.js": "edb47c1eeb6b1cec627fa5894512185336b15d3f5db96a7151a8a57b34b4a2e8",
71
+ "src/host/base-io.js": "cdd049875dce9f1417f28b27efa2c6f494cbe2a49851249150d0c1ec871a8b59",
72
+ "src/host/boot-events.js": "a6567c889fa1e95ad7e917107a2593f03d14cc921827616368b3417de96faad2",
73
+ "src/host/chan-service.js": "f131746c6c03167b5968dfd430d797c4717c7c332563de475e62c6ed84acecbe",
74
+ "src/host/chan-services.js": "64f5cb8d05379618011f5e840830f196013f3134ac0523fed1d7a6969b4d322e",
75
+ "src/host/chan.js": "43104dcdd4d17d13f55a71906b398bdb52d9e96c6fca239310f2ee1ccbb4d4ad",
76
+ "src/host/ckpt-file.js": "da7938fa640a062e77bdfb59caad5b3b1fcbf6150e3220de3be23f253f4c4497",
77
+ "src/host/ckpt-io.js": "3fbe74f0254c597f10d088c85e8ba72725502d86ae4dd8c6d2bf0821a93c873f",
78
+ "src/host/ckpt-point.js": "3cc9ec778f131fbfd17a788709f01f096719f3ef81490a7be36a7fc1a422aef4",
79
+ "src/host/ckpt-save.js": "e222571033ae74a4439596c5e90e03b2aa9a5516665dd290f8dd243712294523",
80
+ "src/host/ckpt.js": "17c3e3f39c7e4ab9b51a4d121d1017d943257f95357330d8f21140011562d0db",
81
+ "src/host/clock.js": "aba6af3a8b1a985ae340892222e4b482d33afe20f5c8b3a4c7403ce87d1e247c",
82
+ "src/host/ctl.js": "a2293b8cb53463323999e0d74277eeab6ebc580625db81b324ecbe2e07ddd98d",
83
+ "src/host/errno.js": "26021d85962084be2c43c98fb3accafc2a8f11fbf3c8a5b8d484bd6a088ae4db",
84
+ "src/host/fs.js": "d6753f74db35b7b01baa8709fc7fdcdfc8d638dfe42f454b0a833b22111e8f4a",
85
+ "src/host/gcnudge.js": "4a6569cb73ae24a11fc4ac8c467380bd9efba0bb5fa9867b797bc4f153ab15ab",
86
+ "src/host/i64shim.js": "6c7cbe226c1596acc2ef084b1c7f817b220d43a17fb927d288246257619d5301",
87
+ "src/host/jit-cache.js": "c31311029de8ee20c0ba6f6b09042f8b91ab08e07134e5a6b1a4de3a1d492c58",
88
+ "src/host/jit-hub-core.js": "f8d39dd0e4cbd42568bf56a8a7105f321d33bcf2d5faf8756d45a1ed692bd344",
89
+ "src/host/jit-hub.js": "cf3fc9f9d4e3fb50c41d6de5e2523f380d3c6c1feaf61c15d90cde89aec2f4c3",
90
+ "src/host/jit-snap.js": "3932f36047c9edeb7f59282d1c37fef19c8f5a9fe4bd0192fb93aa64030fe51d",
91
+ "src/host/jit.js": "cb39f90df63752a7d4077f6972731cf4b1d8026cb4ca594e32f9203be6fe1edb",
92
+ "src/host/layout.js": "d5bc5aa9292630b204438df5d6278fda6daa2a65acc061e1c57e0272b32fecc8",
93
+ "src/host/monitor.js": "ae1741ba35668bcc6192642246494433b175668933921634c699af5a0a69acc0",
94
+ "src/host/p9mount.js": "43246601fc1e27d5294742e4482ed166a50c665cb5f526c3646a47dd27e06150",
95
+ "src/host/ram-mem.js": "bf4036d5d12be1d5d2f5223e526b0b8e225bc702ae7e60a2a59fdfe9fbbb49e3",
96
+ "src/host/stdio.js": "43973675578eb1d324c2c6d7078e51a6a1f121190ada5ef14a5551364b8c7f5e",
97
+ "src/host/threads.js": "952381940e7f43114867e9a9714528f2f20c8064b6b0f2018ab075128dc601be",
98
+ "src/host/wasi.js": "fbafa239b8fb4083f7e8c910b1adab38901e8e7cffd247284a9abb2847eaf4bd",
99
+ "src/host/wmd-imports.js": "094f994758c239b75177781c0613decbbd926090b0e8974163ce7caf8a99234d",
100
+ "src/host/yamux.js": "f4044f14f7e836224bdb4b1e53e59c565ee478b83b2e428fffb9f7a048768f79",
101
+ "src/image-files.js": "539856f3991b9f89202dd04ec3816ba8bce7787034ab810534c5170a205ffde7",
102
+ "src/net/dhcp-dns.js": "fdfa7c7d5cbbb4a8774b7dee195bf2df22a0c3fe8f20ac9694b24485fa8f25d5",
103
+ "src/net/net-worker.js": "0a2ce998f69807e386ded7ff1bee53687a1d59ed76d7ef46e2eb6f958b14ad38",
104
+ "src/net/packet.js": "d658f26fa84324a88d7dbc26af9c4a9c10e977c3060f16f7db431496eacd1127",
105
+ "src/net/peer.js": "d83cf85639f6197fe26c877eb79f8c9afc017bf2362dc2b22a8b6bd0f45f3f19",
106
+ "src/net/proto.js": "abfc8cd6f4331cbc98fcb1b0bba0795ff3042cc7db10476673ead1723249eee0",
107
+ "src/net/relay-client.js": "4101de76dc0251db46839dc786cce677e4020795adf022bdce14bcf7edf8f864",
108
+ "src/net/stack.js": "cd451cce7130f08e59cc19c961d2a5fca8416e577a40df1419ab6d5046018891",
109
+ "src/net/switch.js": "bb047fb1ceeb10a84b5475916ead916362accf0e6f42a86561653dfad0c6c50d",
110
+ "src/net/wisp-client.js": "51207a1fab6f0d2dc4cada17ecdc202db2ffa95f16c318459be79c0abb919faf",
111
+ "src/net/wisp-transport.js": "2b71201f3c686078c867e8995bf5c1cd6d47a0d415f476f3a33fbd5729a55f74",
112
+ "src/net/wisp.js": "f5740bd349cca6ca5bbb03ef392a0b2ce9f23c0ec8e07abe3286329fa209118d",
113
+ "src/registry/client.js": "fd38550e9afd82c5cb498513d245c33357a71697b3b2e6dd78538934bcc9d879",
114
+ "src/registry/converter.js": "1fb8294fd5275f6772c27130cc9ba70bd46fafaf440884d42448036618229f37",
115
+ "src/registry/hub.js": "22323e0f799a7078843514c9f87bbcdaa4173fd991ad4e059377befa773762d4",
116
+ "src/registry/oci.js": "6a51ea16b34a65c0e732730f3e07d7ad16c50f6943a486ec05ca6aa3e98676a1",
117
+ "src/registry/pack-worker.js": "fd6798851cb7815dacfcb67fc78c41654209c5504d8fd343aae524e99de1c741",
118
+ "src/registry/pull.js": "66edf4602aca8d41cc68393d01a2da01c4667de7c493af5ae400e8f249d4e3ae",
119
+ "src/registry/ref.js": "24038e0fbcb6ac143cb8e8f7b9fac4dd0803d0891961786dc88dba01a4402e7b",
120
+ "src/registry/wasi-mem.js": "a7f684343010892460db27ee8c1e38e47e797c93dc4ebe4c30185dca6ca56ee2",
121
+ "src/worker.js": "dda8e216ff774fc6d31ccb55c265f1c560243f872b57bc0affe3075b94bd5b4f"
122
+ }
123
+ }
@@ -0,0 +1,20 @@
1
+ // wmd image export: adds the cross-origin isolation headers (COOP, COEP, CORP) to this folder's
2
+ // own responses, for static hosts that cannot send headers (GitHub Pages). index.html registers
3
+ // it only when the page is not already cross-origin isolated. Other origins are not touched.
4
+ self.addEventListener('install', () => self.skipWaiting())
5
+ self.addEventListener('activate', e => e.waitUntil(self.clients.claim()))
6
+ self.addEventListener('fetch', e => {
7
+ const r = e.request
8
+ if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return
9
+ if (new URL(r.url).origin !== self.location.origin) return
10
+ e.respondWith(
11
+ fetch(r).then(res => {
12
+ if (res.status === 0) return res
13
+ const h = new Headers(res.headers)
14
+ h.set('Cross-Origin-Opener-Policy', 'same-origin')
15
+ h.set('Cross-Origin-Embedder-Policy', 'require-corp')
16
+ h.set('Cross-Origin-Resource-Policy', 'same-origin')
17
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h })
18
+ }),
19
+ )
20
+ })
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>wmd image</title>
7
+ <link rel="stylesheet" href="./run.css">
8
+ </head>
9
+ <body>
10
+ <header><h1 id="title">wmd image</h1><span id="status">loading</span></header>
11
+ <main>
12
+ <section><h2>Output</h2><pre id="main"></pre></section>
13
+ <details><summary>Boot log</summary><pre id="log"></pre><pre id="out"></pre><pre id="err"></pre></details>
14
+ </main>
15
+ <script type="module" src="./run.js"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1,10 @@
1
+ :root { color-scheme: light dark; --bg: #fff; --fg: #111; --pane: #f4f4f4; --bad: #b00020; }
2
+ @media (prefers-color-scheme: dark) { :root { --bg: #111; --fg: #ddd; --pane: #1c1c1c; --bad: #ff6b6b; } }
3
+ body { margin: 0; background: var(--bg); color: var(--fg); font: 14px system-ui, sans-serif; }
4
+ header { padding: 8px 16px; display: flex; gap: 16px; align-items: baseline; flex-wrap: wrap; }
5
+ h1 { font-size: 18px; margin: 0; overflow-wrap: anywhere; }
6
+ h2 { font-size: 13px; margin: 8px 0 4px; }
7
+ main { padding: 0 16px 16px; }
8
+ pre { background: var(--pane); padding: 8px; margin: 0 0 8px; max-height: 60vh; overflow: auto; white-space: pre-wrap; word-break: break-all; font: 12px ui-monospace, monospace; }
9
+ summary { cursor: pointer; margin: 8px 0; }
10
+ .error { color: var(--bad); }
@@ -0,0 +1,236 @@
1
+ // wmd image export --format web (2.7): the runner page. It boots one exported image from this
2
+ // folder alone, with no service of wmd's: page/b7.js's boot path without the registry proxy.
3
+ //
4
+ // wmd-web.json what was exported; the guest files' sha256 (checked here before boot)
5
+ // idx/..., blobs/... the image indexes and packs (getImage with pull: 'never', fetchPacks)
6
+ // guest/arm64/... vm.wasm, the kernel and the init, handed to bootWmd as checked bytes
7
+ //
8
+ // ?ref=REF the exported image to run (default: the first in wmd-web.json)
9
+ // ?cmd=STRING run `/bin/sh -c STRING` instead of the image's entrypoint + cmd
10
+ // ?argv=JSON run this argv instead
11
+ // ?auto=0 do not start
12
+ // cpus=N (1-8) ram=MiB (128-1536, boot.js's MAX_BROWSER_RAM_MIB)
13
+ //
14
+ // Every URL resolves against the page, so the folder works at a site root or under a sub-path.
15
+ // A host without the cross-origin isolation headers gets them from coi-sw.js (one reload).
16
+ // State for tests: window.wmdExport {status, crossOriginIsolated, ref, index, guest, argv, user,
17
+ // mainOut, mainExit, code, reason, error}.
18
+
19
+ import { MAX_BROWSER_RAM_MIB, bootWmd, memoryMaxPages, p9MountOptions } from './wmd-web/boot.js'
20
+ import { MAIN_PROC_ID, containerBundle, fillRunRequest } from './wmd-web/host/ctl.js'
21
+ import { openImage, resolveUser } from './wmd-web/image-files.js'
22
+ import { fetchPacks, getImage } from './wmd-web/registry/client.js'
23
+ import { parseRef } from './wmd-web/registry/ref.js'
24
+
25
+ const ENGINE_APPEND = [
26
+ 'console=ttyAMA0', 'earlycon=pl011,0x9000000', 'rdinit=/sbin/init', 'loglevel=8',
27
+ 'norandmaps', 'print-fatal-signals=1', 'debug.exception-trace=1',
28
+ 'wmd.init', 'wmd.p9tag=wmdroot', 'wmd.control=vport', 'wmd.exit',
29
+ 'initcall_blacklist=virtio_net_driver_init', // no eth0: no network in the browser here
30
+ ].join(' ')
31
+ const P9_PORT = 564
32
+ const CONTROL_PORT = 8
33
+ const KEEP = 4 << 20
34
+ const COI_RELOAD = 'wmd-export-coi-reload'
35
+
36
+ const q = new URLSearchParams(location.search)
37
+ const state = (window.wmdExport = {
38
+ status: 'idle', crossOriginIsolated: globalThis.crossOriginIsolated === true, ref: null, index: null, guest: null,
39
+ argv: null, user: null, mainOut: '', mainExit: null, stdout: '', stderr: '', code: null, reason: null, error: null,
40
+ })
41
+ const $ = id => document.getElementById(id)
42
+ const clip = (s, n) => (s.length > n ? s.slice(-n) : s)
43
+ const log = s => ($('log').textContent += `${s}\n`)
44
+ const status = s => ($('status').textContent = s)
45
+ const here = p => new URL(p, document.baseURI)
46
+ const hex = buf => [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('')
47
+
48
+ /** Cross-origin isolation, through coi-sw.js when the host does not send the headers. false: the page reloads. */
49
+ async function isolate() {
50
+ if (globalThis.crossOriginIsolated) {
51
+ try {
52
+ sessionStorage.removeItem(COI_RELOAD)
53
+ } catch {}
54
+ return true
55
+ }
56
+ let reloaded = false
57
+ try {
58
+ reloaded = sessionStorage.getItem(COI_RELOAD) === '1'
59
+ } catch {}
60
+ if (!('serviceWorker' in navigator)) throw new Error('this page needs cross-origin isolation: the host must send COOP/COEP headers (see README.txt), and this browser has no service workers to add them')
61
+ if (reloaded) throw new Error('this page is not cross-origin isolated even through coi-sw.js; send the COOP/COEP/CORP headers from the host (see README.txt)')
62
+ status('adding cross-origin isolation headers (coi-sw.js)...')
63
+ const reg = await navigator.serviceWorker.register(here('coi-sw.js'), { scope: here('./').pathname })
64
+ if (!navigator.serviceWorker.controller && !reg.active) {
65
+ await new Promise((res, rej) => {
66
+ const t = setTimeout(() => rej(new Error('coi-sw.js did not start within 30 s; send the COOP/COEP/CORP headers from the host (see README.txt)')), 30_000)
67
+ const w = reg.installing || reg.waiting
68
+ if (!w) return clearTimeout(t), rej(new Error('coi-sw.js registered but has no worker; send the headers from the host (see README.txt)'))
69
+ w.addEventListener('statechange', () => {
70
+ if (w.state === 'activated') clearTimeout(t), res()
71
+ else if (w.state === 'redundant') clearTimeout(t), rej(new Error('coi-sw.js failed to install; send the COOP/COEP/CORP headers from the host (see README.txt)'))
72
+ })
73
+ })
74
+ }
75
+ try {
76
+ sessionStorage.setItem(COI_RELOAD, '1')
77
+ } catch {}
78
+ location.reload()
79
+ return false
80
+ }
81
+
82
+ /** A whole-number query parameter in [min, max], or def when absent. */
83
+ function int(name, def, min, max) {
84
+ const v = q.get(name)
85
+ if (v === null || v === '') return def
86
+ const n = Number(v)
87
+ if (!Number.isInteger(n) || n < min || n > max) throw new Error(`?${name}= must be a whole number from ${min} to ${max}`)
88
+ return n
89
+ }
90
+
91
+ async function fetchBytes(url) {
92
+ let res
93
+ try {
94
+ res = await fetch(url, { cache: 'no-store' })
95
+ } catch (e) {
96
+ throw new Error(`${url.pathname}: unreachable (${e.message})`)
97
+ }
98
+ if (!res.ok) throw new Error(`${url.pathname}: HTTP ${res.status}`)
99
+ return new Uint8Array(await res.arrayBuffer())
100
+ }
101
+
102
+ /** A guest file, checked against wmd-web.json's size and sha256. */
103
+ async function guestFile(guest, name) {
104
+ const f = guest.files?.[name]
105
+ if (!f) throw new Error(`wmd-web.json lists no guest file ${name}`)
106
+ const b = await fetchBytes(here(f.path))
107
+ if (b.length !== f.size) throw new Error(`${f.path}: ${b.length} bytes, wmd-web.json says ${f.size}; this folder is incomplete or mixed from two exports`)
108
+ const got = hex(await crypto.subtle.digest('SHA-256', b))
109
+ if (got !== f.sha256) throw new Error(`${f.path}: sha256 ${got.slice(0, 12)}, wmd-web.json says ${f.sha256.slice(0, 12)}; refused (a file from another export?)`)
110
+ return b
111
+ }
112
+
113
+ async function main() {
114
+ if (!(await isolate())) return
115
+ state.crossOriginIsolated = true
116
+ const base = here('.').pathname.replace(/\/$/, '')
117
+
118
+ state.status = 'manifest'
119
+ const mres = await fetch(here('wmd-web.json'), { cache: 'no-store' })
120
+ if (!mres.ok) throw new Error(`wmd-web.json: HTTP ${mres.status}; is this an exported folder?`)
121
+ const man = await mres.json()
122
+ if (man.schema !== 'wmd-web-export/1') throw new Error(`wmd-web.json: schema ${JSON.stringify(man.schema)}, this page reads wmd-web-export/1`)
123
+ if (!man.images?.length) throw new Error('wmd-web.json lists no images')
124
+ let img = man.images[0]
125
+ if (q.get('ref')) {
126
+ const want = parseRef(q.get('ref')).ref
127
+ img = man.images.find(i => i.ref === want)
128
+ if (!img) throw new Error(`${want} is not in this folder; it has: ${man.images.map(i => i.ref).join(', ')}`)
129
+ }
130
+ state.ref = img.ref
131
+ $('title').textContent = img.ref
132
+ log(`wmd ${man.wmd?.version || '?'} (${(man.wmd?.commit || 'unknown commit').slice(0, 12)}): ${img.ref} ${img.platform}`)
133
+
134
+ state.status = 'files'
135
+ status(`getting ${img.ref}...`)
136
+ const kernelName = img.kernel || 'Image'
137
+ const [index, vmBytes, kernel, initrd] = await Promise.all([
138
+ getImage(img.ref, { platform: img.platform, pull: 'never', base }),
139
+ guestFile(man.guest, 'vm.wasm'),
140
+ guestFile(man.guest, kernelName),
141
+ guestFile(man.guest, 'initramfs.cpio.gz'),
142
+ ])
143
+ state.index = index
144
+ state.guest = { verified: true, kernel: kernelName }
145
+ const got = await fetchPacks(index, { base })
146
+ log(`packs: ${got.packs.length}, ${(got.bytes / 1e6).toFixed(1)} MB, checked`)
147
+
148
+ const cfg = index.config || {}
149
+ const env = cfg.Env || ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin']
150
+ const cwd = cfg.WorkingDir || '/'
151
+ let argv
152
+ if (q.get('argv')) {
153
+ try {
154
+ argv = JSON.parse(q.get('argv'))
155
+ } catch {
156
+ argv = null
157
+ }
158
+ if (!Array.isArray(argv) || !argv.every(a => typeof a === 'string')) throw new Error('?argv= must be a JSON array of strings, e.g. ["python3","-c","print(1)"]')
159
+ }
160
+ else if (q.get('cmd')) argv = ['/bin/sh', '-c', q.get('cmd')]
161
+ else argv = [...(cfg.Entrypoint || []), ...(cfg.Cmd || [])]
162
+ if (!argv.length) throw new Error('the image has no entrypoint or cmd; pass ?cmd=')
163
+ let user
164
+ if (cfg.User) {
165
+ const files = await openImage(got.packs)
166
+ const u = resolveUser(cfg.User, files.accounts())
167
+ user = { uid: u.uid, gid: u.gid }
168
+ }
169
+ state.argv = argv
170
+ state.user = user || null
171
+ const { files } = containerBundle({ args: argv, env, cwd, user })
172
+ const cpus = int('cpus', 2, 1, 8)
173
+ const ram = int('ram', 512, 128, MAX_BROWSER_RAM_MIB)
174
+ const args = [
175
+ '--machine', '--arch', 'a64', '--board', 'arm64', '--mode', 'jit',
176
+ '--cpus', String(cpus), '--ram', String(ram),
177
+ '--kernel', '/g/guest/Image', '--initrd', '/g/guest/initramfs.cpio.gz', '--append', ENGINE_APPEND,
178
+ '--p9', String(P9_PORT), '--p9-tag', 'wmdroot', '--control', String(CONTROL_PORT),
179
+ ]
180
+ state.status = 'booting'
181
+ status(`booting ${img.ref}...`)
182
+ const vm = await bootWmd({
183
+ vmUrl: vmBytes,
184
+ maxPages: memoryMaxPages(args, 512),
185
+ files: { 'guest/Image': kernel, 'guest/initramfs.cpio.gz': initrd },
186
+ args,
187
+ onStdout: s => {
188
+ state.stdout = clip(state.stdout + s, KEEP)
189
+ $('out').textContent = clip($('out').textContent + s, 32 << 10)
190
+ },
191
+ onStderr: s => {
192
+ state.stderr = clip(state.stderr + s, KEEP)
193
+ $('err').textContent = clip($('err').textContent + s, 32 << 10)
194
+ },
195
+ channels: {
196
+ services: [
197
+ { port: P9_PORT, kind: '9p', layers: got.packs, at: 'rootfs', files },
198
+ { port: CONTROL_PORT, kind: 'control', yamux: true },
199
+ ],
200
+ collect: [{ port: P9_PORT, path: 'bundle/exitcode' }],
201
+ },
202
+ })
203
+ state.status = 'running'
204
+ status(`running ${img.ref}`)
205
+ const ended = vm.exited.then(r => {
206
+ state.code = r.code
207
+ state.reason = r.reason
208
+ return r
209
+ })
210
+ const ctl = vm.control
211
+ await Promise.race([ctl.waitSession(300_000), ended.then(r => Promise.reject(new Error(`the machine ended before the container started: ${r.code} ${r.reason}`)))])
212
+ const dec = new TextDecoder()
213
+ const show = b => {
214
+ state.mainOut += dec.decode(b, { stream: true })
215
+ $('main').textContent = clip(state.mainOut, 256 << 10)
216
+ }
217
+ const proc = ctl.run(fillRunRequest({ id: MAIN_PROC_ID, args: argv, env, cwd }, { p9opts: p9MountOptions() }), { onStdout: show, onStderr: show })
218
+ state.mainExit = await Promise.race([proc.exited, ended.then(r => ({ code: null, error: `the machine ended first: ${r.code} ${r.reason}` }))])
219
+ const r = await Promise.race([ended, new Promise(res => setTimeout(() => res(null), 60_000))])
220
+ if (!r) {
221
+ vm.stop()
222
+ await ended
223
+ }
224
+ state.status = 'exited'
225
+ status(`exited ${state.mainExit?.code ?? state.mainExit?.error}`)
226
+ }
227
+
228
+ if (q.get('auto') !== '0') {
229
+ main().catch(err => {
230
+ state.error = `${err.name || 'Error'}: ${err.message}`
231
+ state.status = 'error'
232
+ status(`error: ${err.message}`)
233
+ $('status').className = 'error'
234
+ state.stderr += `\n[page] ${state.error}\n${err.stack || ''}`
235
+ })
236
+ }
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: package/guest/arm64/Image
3
+ size: 39047176 bytes
4
+ sha256: a6b3209cf69d5bc1dacf0f3ed67f92c535dbb4108d67b47091c2aef87c6d7b04
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: package/guest/arm64/Image-4k
3
+ size: 38828040 bytes
4
+ sha256: 8ff6e338a0b42d9f774b35423e0e7f9d7f779c635446e8de2a3beb83d3518f8a
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "@threadedstack/wmd-web",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.13",
4
+ "license": "MIT",
5
+ "homepage": "https://github.com/threadedstack/wmd-releases",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/threadedstack/wmd-releases.git"
9
+ },
10
+ "description": "Browser host for wmd's guest vm.wasm (Web Workers + shared memory + WASI preview1)",
11
+ "wmd": {
12
+ "tag": "v0.1.13",
13
+ "commit": "23bf0595198cec6c2027017049b2caa39753efc2",
14
+ "web": "0.3.1"
15
+ },
16
+ "type": "module",
17
+ "main": "./src/boot.js",
18
+ "exports": {
19
+ ".": "./src/boot.js",
20
+ "./src/*": "./src/*",
21
+ "./relay/*": "./relay/*",
22
+ "./page/sw.js": "./page/sw.js",
23
+ "./export-page/*": "./export-page/*",
24
+ "./guest/*": "./guest/*",
25
+ "./wmdpack.wasm": "./wmdpack.wasm",
26
+ "./RELEASE.json": "./RELEASE.json",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "files": [
30
+ "src",
31
+ "relay",
32
+ "page",
33
+ "export-page",
34
+ "guest",
35
+ "wmdpack.wasm",
36
+ "RELEASE.json"
37
+ ]
38
+ }