@threadedstack/wmd-web 0.1.17 → 0.1.19
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.
- package/README.md +44 -0
- package/RELEASE.json +39 -54
- package/guest/arm64/Image +2 -2
- package/guest/arm64/Image-4k +1 -1
- package/guest/arm64/initramfs.cpio.gz +0 -0
- package/guest/arm64/vm.wasm +0 -0
- package/package.json +4 -4
- package/relay/hosted.ts +27 -11
- package/relay/main.ts +3 -3
- package/src/boot.js +25 -0
- package/src/build/builder.js +791 -0
- package/src/build/context.js +349 -0
- package/src/build/dockerfile.js +1055 -0
- package/src/build/dockerignore.js +270 -0
- package/src/build/fast.js +1187 -0
- package/src/build/inspect.js +78 -0
- package/src/build/pin.js +6 -0
- package/src/build/tar.js +304 -0
- package/src/host/ctl.js +17 -0
- package/src/host/disk-map.js +330 -0
- package/src/host/disk-worker.js +125 -0
- package/src/host/disk.js +219 -0
- package/src/host/wmd-imports.js +3 -3
- package/src/image-files.js +2 -1
- package/src/registry/hub.js +113 -44
- package/src/registry/load-tar.js +473 -0
- package/src/registry/pull.js +5 -3
- package/src/worker.js +2 -0
- package/wmdpack.wasm +0 -0
package/README.md
CHANGED
|
@@ -155,6 +155,50 @@ The interface this host implements is in [docs/host-interface.md](docs/host-inte
|
|
|
155
155
|
too) and shows the last 128 KB. P-B1's merged branch had the same rings at 4 MiB
|
|
156
156
|
(cherry-picked as `17e9ce50`); the merge keeps P-B2's 32 MiB rings and drop markers.
|
|
157
157
|
|
|
158
|
+
## Building images (`wmd-web/build`)
|
|
159
|
+
|
|
160
|
+
`createBuilder` runs BuildKit (moby/buildkit v0.33.0) in a privileged builder machine in the page and builds a
|
|
161
|
+
Dockerfile there. The result is `{index, packs}`, the shape `pullImage` returns, so `bootWmd` can run it at once.
|
|
162
|
+
Chromium, Firefox and WebKit all pass the same 18 build steps (plan `docs/plans/web-buildkit-impl.md`, section 10).
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
import { createBuilder } from 'wmd-web/build'
|
|
166
|
+
|
|
167
|
+
const builder = await createBuilder({
|
|
168
|
+
vmUrl, files, // as bootWmd: the engine and the builder arch's guest files
|
|
169
|
+
arch: 'arm64', // or 'amd64'
|
|
170
|
+
image: { tar: builderTarUrl, sha256 }, // the builder image; must match BUILDER_PINS in src/build/pin.js
|
|
171
|
+
relay: { url, protocol: 'wisp' }, // the guest's network: base images and RUN downloads
|
|
172
|
+
disk: { namespace: 'my-app' }, // BuildKit's cache on an ext4 disk in OPFS (default 10240 MiB)
|
|
173
|
+
converter, // wmdpack.wasm
|
|
174
|
+
pull: ref => pullImage(ref, { fetch, converter, platform: 'linux/arm64' }), // base images for the fast path (off without it)
|
|
175
|
+
})
|
|
176
|
+
const r = await builder.build({ context: { Dockerfile: 'FROM alpine:3.20\nRUN echo hi > /x\n' } })
|
|
177
|
+
// r: {index, packs, log, path: 'fast'|'buildkit', ms, cached}
|
|
178
|
+
await builder.close()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- **The cache stays.** The disk outlives the builder and the visit. After a reload, or after a tab was killed
|
|
182
|
+
mid-build, the next build mounts the disk again and its steps come back `CACHED`. `close({keep: false})` deletes
|
|
183
|
+
the disk.
|
|
184
|
+
- **A second tab** that asks for a disk already open gets a temporary disk, deleted when it closes.
|
|
185
|
+
- **Features:** `RUN --mount=type=cache` and `type=secret`, `# syntax=` frontends, build args and target stages,
|
|
186
|
+
`.dockerignore`, file capabilities (`setcap`), push to a registry (TLS, or plain http in `httpRegistries`), and
|
|
187
|
+
`tar: true` for a Docker tar that `docker load` takes. `--mount=type=ssh` and another platform are refused before
|
|
188
|
+
any work.
|
|
189
|
+
- **The fast path.** With `pull` given, a Dockerfile with no `RUN` is built in the page without BuildKit, layer
|
|
190
|
+
for layer the same as BuildKit's (40 compared cases): under 0.4 s once its base image is pulled, where a warm
|
|
191
|
+
BuildKit takes 5 to 14 s.
|
|
192
|
+
- **Errors:** `BuildError {step, tail, reason}`: `reason` is `'no-space'`, `'quota'`, `'disk'`, `'timeout'`, `'pin'`
|
|
193
|
+
or `'secret'`. Cancelling with `signal` stops the step and leaves nothing behind.
|
|
194
|
+
- **The builder images** are built with `make web-builder` (wmd builds them; Docker Desktop is not used) into
|
|
195
|
+
`web/dist/builder/builder-<arch>.tar`, and their sha256 is pinned in `src/build/pin.js`. `make web-release` puts
|
|
196
|
+
them next to the release tgz.
|
|
197
|
+
- **Docker Hub's rate limit** applies to base images. `registryMirrors` gives BuildKit a mirror to try first.
|
|
198
|
+
- **Tests:** `bash ../scripts/bootlock.sh run --max 1800 -- bunx playwright test -c test/build --project=<browser>`
|
|
199
|
+
(`build.spec.js`: the 18 steps; `parity.spec.js`: the fast path against BuildKit). `test/build/seed-mirror.sh`
|
|
200
|
+
seeds a local mirror so the runs never touch Docker Hub.
|
|
201
|
+
|
|
158
202
|
## Running the tests
|
|
159
203
|
|
|
160
204
|
```sh
|
package/RELEASE.json
CHANGED
|
@@ -1,38 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wmd-web",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"commit": "
|
|
5
|
-
"previous":
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"commit": "89688f12e0caef39ab0a159b39c00f3525e31051",
|
|
5
|
+
"previous": "wmd-web-v0.1.0",
|
|
6
6
|
"decisions": [
|
|
7
|
-
"D-388",
|
|
8
|
-
"D-395",
|
|
9
|
-
"D-408",
|
|
10
|
-
"D-415",
|
|
11
|
-
"D-416",
|
|
12
|
-
"D-417",
|
|
13
|
-
"D-419",
|
|
14
|
-
"D-423",
|
|
15
|
-
"D-428",
|
|
16
|
-
"D-430",
|
|
17
|
-
"D-432",
|
|
18
|
-
"D-433",
|
|
19
|
-
"D-438",
|
|
20
|
-
"D-442",
|
|
21
|
-
"D-443",
|
|
22
|
-
"D-444",
|
|
23
|
-
"D-445",
|
|
24
|
-
"D-446",
|
|
25
|
-
"D-447",
|
|
26
|
-
"D-448",
|
|
27
|
-
"D-449",
|
|
28
|
-
"D-451",
|
|
29
|
-
"D-453",
|
|
30
|
-
"D-454",
|
|
31
|
-
"D-468",
|
|
32
|
-
"D-476",
|
|
33
|
-
"D-477",
|
|
34
|
-
"D-481",
|
|
35
|
-
"D-482",
|
|
36
7
|
"D-486",
|
|
37
8
|
"D-488",
|
|
38
9
|
"D-494",
|
|
@@ -49,43 +20,53 @@
|
|
|
49
20
|
"D-539",
|
|
50
21
|
"D-542",
|
|
51
22
|
"D-544",
|
|
52
|
-
"D-545"
|
|
23
|
+
"D-545",
|
|
24
|
+
"D-547",
|
|
25
|
+
"D-548"
|
|
53
26
|
],
|
|
54
|
-
"vmSources": "
|
|
27
|
+
"vmSources": "7a707b82c43d3f1eb999476cad68e7ce79cc9cb5",
|
|
55
28
|
"guest": {
|
|
56
29
|
"arch": "arm64",
|
|
57
30
|
"files": {
|
|
58
31
|
"vm.wasm": {
|
|
59
|
-
"sha256": "
|
|
60
|
-
"bytes":
|
|
32
|
+
"sha256": "40299e37e788189e6d3bea9d9cdaefa241a5acf27931e6aae14117c100b036ec",
|
|
33
|
+
"bytes": 1561499
|
|
61
34
|
},
|
|
62
35
|
"Image": {
|
|
63
|
-
"sha256": "
|
|
64
|
-
"bytes":
|
|
36
|
+
"sha256": "1d296c86b1fee0a10d0c2571b7e19854fe85ca0250cc9fadfe75eb268e48be7b",
|
|
37
|
+
"bytes": 39049224
|
|
65
38
|
},
|
|
66
39
|
"Image-4k": {
|
|
67
|
-
"sha256": "
|
|
40
|
+
"sha256": "6296e3d9fc7bb06a26340bb4453b5f0970d6b55452903546ba2c762820e2df5b",
|
|
68
41
|
"bytes": 38828040
|
|
69
42
|
},
|
|
70
43
|
"initramfs.cpio.gz": {
|
|
71
|
-
"sha256": "
|
|
72
|
-
"bytes":
|
|
44
|
+
"sha256": "f008aa9e0f6b4e913242f9982d3b194dba7dc39042a24bc89be64630672125af",
|
|
45
|
+
"bytes": 4685204
|
|
73
46
|
}
|
|
74
47
|
}
|
|
75
48
|
},
|
|
76
49
|
"files": {
|
|
77
|
-
"README.md": "
|
|
50
|
+
"README.md": "3c12ca29aab5160b955ba3f50035f19be1dbf0278c4fb006e253a37ac9efbe6a",
|
|
78
51
|
"export-page/coi-sw.js": "0300e670b413cb2e0aae35843fc583546bbf6687df971ab6df41cfa3b0d494d7",
|
|
79
52
|
"export-page/index.html": "f5450c89e63601bbbbffef421f03491b231d1f143070012c1c5315d4b0059f43",
|
|
80
53
|
"export-page/run.css": "b57adf1e76553549c1f89ff157b7973409dedc21889cd3932948cd05b51adc6f",
|
|
81
54
|
"export-page/run.js": "717636a632a40469106ddf1383e198eaac5d83651b5866d0699ec04b82462b7f",
|
|
82
|
-
"guest/arm64/Image": "
|
|
83
|
-
"guest/arm64/Image-4k": "
|
|
84
|
-
"guest/arm64/initramfs.cpio.gz": "
|
|
85
|
-
"guest/arm64/vm.wasm": "
|
|
86
|
-
"package.json": "
|
|
55
|
+
"guest/arm64/Image": "1d296c86b1fee0a10d0c2571b7e19854fe85ca0250cc9fadfe75eb268e48be7b",
|
|
56
|
+
"guest/arm64/Image-4k": "6296e3d9fc7bb06a26340bb4453b5f0970d6b55452903546ba2c762820e2df5b",
|
|
57
|
+
"guest/arm64/initramfs.cpio.gz": "f008aa9e0f6b4e913242f9982d3b194dba7dc39042a24bc89be64630672125af",
|
|
58
|
+
"guest/arm64/vm.wasm": "40299e37e788189e6d3bea9d9cdaefa241a5acf27931e6aae14117c100b036ec",
|
|
59
|
+
"package.json": "778ef2457ab01f302387c811d796b7c61f97feb095725da285655629965f2b5e",
|
|
87
60
|
"src/base.js": "982f8e46dbb727ec542a494ebdb884ccd19a31ff4e091425b2d1fc01b0d4754a",
|
|
88
|
-
"src/boot.js": "
|
|
61
|
+
"src/boot.js": "62f5cf901d797d53bef584b3c6c0c7fb1a4cc8e2450e9570d6289c72036ed025",
|
|
62
|
+
"src/build/builder.js": "751ea022e1f6f0c6ab79bd4efd6cfe0d705268ff7f699af87dcc64d1871ccc52",
|
|
63
|
+
"src/build/context.js": "4baf1884d366455f3f76f397575d515671585ebf5deb27bc35bc2b1dd9c6bc11",
|
|
64
|
+
"src/build/dockerfile.js": "5957fe3d4ac8ff8b312f980ee04bd1d98ad4373a61f9a33e02f1ff1108b6ea53",
|
|
65
|
+
"src/build/dockerignore.js": "8fc03d09da020f43ea483979b1183891b71d76f012379ab7813838ea0e53c546",
|
|
66
|
+
"src/build/fast.js": "bf7373a43cce995b21751c317a58d18438f9fd2f670266b314b1151ef3cdb9e8",
|
|
67
|
+
"src/build/inspect.js": "52aa26d332c08406e8eeaf8f72661d8acc36bbd4f547a7d68e72b4f0fe265a22",
|
|
68
|
+
"src/build/pin.js": "8c67f017ae3ec560d2638fdfc0bd91ddd758150af16c188bbe3bf2cb4d7e28b2",
|
|
69
|
+
"src/build/tar.js": "25cf477488d9a60da9015be9ca5830bad3ad24428da1546f0621411cb7f44949",
|
|
89
70
|
"src/calib-worker.js": "3d610591a1b3b58f59c3a2ab457f43c90727d95973cd43055b3b73b76180611d",
|
|
90
71
|
"src/fs/9p/codec.js": "69711eb4ebc84eff39256138169fe18f5a5066d6cd9e4ff2daf61c65380d7ef7",
|
|
91
72
|
"src/fs/9p/server.js": "da0ba00ebb5522161bb0ef7cd94bfc9616e342bf865633dfef0ba5bc67627d7a",
|
|
@@ -110,7 +91,10 @@
|
|
|
110
91
|
"src/host/ckpt-save.js": "e222571033ae74a4439596c5e90e03b2aa9a5516665dd290f8dd243712294523",
|
|
111
92
|
"src/host/ckpt.js": "2155b5c8e6f609478805eb8a0b5d5210d47cdf35a80f79e0b9ae42b2c3170923",
|
|
112
93
|
"src/host/clock.js": "aba6af3a8b1a985ae340892222e4b482d33afe20f5c8b3a4c7403ce87d1e247c",
|
|
113
|
-
"src/host/ctl.js": "
|
|
94
|
+
"src/host/ctl.js": "ca2ebbb940c9668cb0573c959cd2ff8982f96d58dfdb8dce39d304b89e033dd7",
|
|
95
|
+
"src/host/disk-map.js": "3d7e0d7d488ce25fc6a0e4dc0a9b1c8fc2af96bac1c03491a1d30be392af268b",
|
|
96
|
+
"src/host/disk-worker.js": "9d470cdc72d9e1fbf955e466ec2562abe780f2c6d89174ca3e4fbb1828f8dd76",
|
|
97
|
+
"src/host/disk.js": "373e23539e3916674b7e8708d2e3ae22578214129e56936765db5c8f465cb343",
|
|
114
98
|
"src/host/errno.js": "26021d85962084be2c43c98fb3accafc2a8f11fbf3c8a5b8d484bd6a088ae4db",
|
|
115
99
|
"src/host/fs.js": "d6753f74db35b7b01baa8709fc7fdcdfc8d638dfe42f454b0a833b22111e8f4a",
|
|
116
100
|
"src/host/gcnudge.js": "4a6569cb73ae24a11fc4ac8c467380bd9efba0bb5fa9867b797bc4f153ab15ab",
|
|
@@ -127,9 +111,9 @@
|
|
|
127
111
|
"src/host/stdio.js": "43973675578eb1d324c2c6d7078e51a6a1f121190ada5ef14a5551364b8c7f5e",
|
|
128
112
|
"src/host/threads.js": "952381940e7f43114867e9a9714528f2f20c8064b6b0f2018ab075128dc601be",
|
|
129
113
|
"src/host/wasi.js": "fbafa239b8fb4083f7e8c910b1adab38901e8e7cffd247284a9abb2847eaf4bd",
|
|
130
|
-
"src/host/wmd-imports.js": "
|
|
114
|
+
"src/host/wmd-imports.js": "3a00562f7c9c7787ab3841b14dde5d903a78f43ed19c43a2f5e31fd8a14c09d2",
|
|
131
115
|
"src/host/yamux.js": "f4044f14f7e836224bdb4b1e53e59c565ee478b83b2e428fffb9f7a048768f79",
|
|
132
|
-
"src/image-files.js": "
|
|
116
|
+
"src/image-files.js": "7bac16bd278d17ba291dfe4771564627b5efbdda927b429018a75e8b906dd4cd",
|
|
133
117
|
"src/net/dhcp-dns.js": "fdfa7c7d5cbbb4a8774b7dee195bf2df22a0c3fe8f20ac9694b24485fa8f25d5",
|
|
134
118
|
"src/net/net-worker.js": "0a2ce998f69807e386ded7ff1bee53687a1d59ed76d7ef46e2eb6f958b14ad38",
|
|
135
119
|
"src/net/packet.js": "d658f26fa84324a88d7dbc26af9c4a9c10e977c3060f16f7db431496eacd1127",
|
|
@@ -143,12 +127,13 @@
|
|
|
143
127
|
"src/net/wisp.js": "06c50159f416d40a912fe2cce4806388b938f2ab0220b2ee6da890630ce3d113",
|
|
144
128
|
"src/registry/client.js": "fd38550e9afd82c5cb498513d245c33357a71697b3b2e6dd78538934bcc9d879",
|
|
145
129
|
"src/registry/converter.js": "1fb8294fd5275f6772c27130cc9ba70bd46fafaf440884d42448036618229f37",
|
|
146
|
-
"src/registry/hub.js": "
|
|
130
|
+
"src/registry/hub.js": "cd672323bd1f96bd1d7e9eed80d4f36891e919e456fc0deb378fa0c7e3b6f249",
|
|
131
|
+
"src/registry/load-tar.js": "9dd2b5d8884c64247339b141ab5a57b8ab1a2e007c32aa45b1103b37405b0ed7",
|
|
147
132
|
"src/registry/oci.js": "6a51ea16b34a65c0e732730f3e07d7ad16c50f6943a486ec05ca6aa3e98676a1",
|
|
148
133
|
"src/registry/pack-worker.js": "fd6798851cb7815dacfcb67fc78c41654209c5504d8fd343aae524e99de1c741",
|
|
149
|
-
"src/registry/pull.js": "
|
|
134
|
+
"src/registry/pull.js": "24f2e01a76c9f7f27896af69b989f8c97f0ca31712b3e8110aaa5b5e3148b04b",
|
|
150
135
|
"src/registry/ref.js": "24038e0fbcb6ac143cb8e8f7b9fac4dd0803d0891961786dc88dba01a4402e7b",
|
|
151
136
|
"src/registry/wasi-mem.js": "a7f684343010892460db27ee8c1e38e47e797c93dc4ebe4c30185dca6ca56ee2",
|
|
152
|
-
"src/worker.js": "
|
|
137
|
+
"src/worker.js": "c654be359e741d5b1fde1be05f8909968d4e00d1fca61be169e60ea691daa662"
|
|
153
138
|
}
|
|
154
139
|
}
|
package/guest/arm64/Image
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/guest/arm64/Image
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 39049224 bytes
|
|
4
|
+
sha256: 1d296c86b1fee0a10d0c2571b7e19854fe85ca0250cc9fadfe75eb268e48be7b
|
package/guest/arm64/Image-4k
CHANGED
|
Binary file
|
package/guest/arm64/vm.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threadedstack/wmd-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://github.com/threadedstack/wmd-releases",
|
|
6
6
|
"repository": {
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"description": "Browser host for wmd's guest vm.wasm (Web Workers + shared memory + WASI preview1)",
|
|
11
11
|
"wmd": {
|
|
12
|
-
"tag": "v0.1.
|
|
13
|
-
"commit": "
|
|
14
|
-
"web": "0.
|
|
12
|
+
"tag": "v0.1.19",
|
|
13
|
+
"commit": "89688f12e0caef39ab0a159b39c00f3525e31051",
|
|
14
|
+
"web": "0.4.0"
|
|
15
15
|
},
|
|
16
16
|
"type": "module",
|
|
17
17
|
"main": "./src/boot.js",
|
package/relay/hosted.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { Bucket, type Session, type Stream } from './engine.ts'
|
|
|
18
18
|
import { type Addr, type Cidr, addrString, cidrContains, hostedBlockedReason, parseCidr, parseIP } from './policy.ts'
|
|
19
19
|
|
|
20
20
|
const HOUR = 3_600_000
|
|
21
|
+
const MINUTE = 60_000
|
|
21
22
|
|
|
22
23
|
export type HostedOptions = {
|
|
23
24
|
abuseContact: string
|
|
@@ -26,11 +27,12 @@ export type HostedOptions = {
|
|
|
26
27
|
now?: () => number // ms since the epoch (tests)
|
|
27
28
|
warn?: (line: string) => void // one-line operator notices (stderr)
|
|
28
29
|
sites?: string[] // A: page origin patterns served (required: none listed serves no page)
|
|
29
|
-
sitesPerAddrPerHour?: number // B: different sites per address group per hour (
|
|
30
|
+
sitesPerAddrPerHour?: number // B: different sites per address group per hour (800)
|
|
30
31
|
sitesPerNetPerHour?: number // B: per network (800)
|
|
31
32
|
monthBytes?: number // C: bytes relayed per UTC month (the operator's figure from the provider's free outbound data); 0 = no cap
|
|
32
33
|
usageFile?: string // C: the month's counter survives a restart here
|
|
33
34
|
dialLimitMs?: number // D, G: the engine's dial limit (connectTimeoutMs, 10 s)
|
|
35
|
+
failedDialsPerMin?: number // D: failed connections per address group within a minute before any is slowed (20)
|
|
34
36
|
fdLimit?: number // E: the open-file limit (read from the host when absent)
|
|
35
37
|
fdOpen?: number // E: descriptors open at start (read from the host when absent)
|
|
36
38
|
dnsServers?: number // E: resolvers the relay asks (each may hold a UDP and a TCP socket)
|
|
@@ -256,10 +258,11 @@ export function createGovernor(o: HostedOptions) {
|
|
|
256
258
|
const warn = o.warn ?? ((m: string) => void process.stderr.write(`${m}\n`))
|
|
257
259
|
const host = o.fdLimit === undefined || o.fdOpen === undefined ? hostLimits() : null
|
|
258
260
|
const c = {
|
|
259
|
-
sitesPerAddrPerHour: o.sitesPerAddrPerHour ??
|
|
261
|
+
sitesPerAddrPerHour: o.sitesPerAddrPerHour ?? 800,
|
|
260
262
|
sitesPerNetPerHour: o.sitesPerNetPerHour ?? 800,
|
|
261
263
|
monthBytes: o.monthBytes ?? 0,
|
|
262
264
|
dialLimitMs: o.dialLimitMs ?? 10_000,
|
|
265
|
+
failedDialsPerMin: o.failedDialsPerMin ?? 20,
|
|
263
266
|
fdLimit: o.fdLimit ?? host!.fdLimit,
|
|
264
267
|
fdOpen: o.fdOpen ?? host!.fdOpen,
|
|
265
268
|
dnsServers: o.dnsServers ?? getServers().length,
|
|
@@ -427,12 +430,16 @@ export function createGovernor(o: HostedOptions) {
|
|
|
427
430
|
}
|
|
428
431
|
|
|
429
432
|
// ---- D
|
|
430
|
-
const dials = new Map<string, { w: number; gate: number; told: boolean }>()
|
|
433
|
+
const dials = new Map<string, { w: number; gate: number; told: boolean; fails: number[] }>()
|
|
431
434
|
const dialOf = (grp: string) => {
|
|
432
435
|
let d = dials.get(grp)
|
|
433
|
-
if (!d) dials.set(grp, (d = { w: 0, gate: 0, told: false }))
|
|
436
|
+
if (!d) dials.set(grp, (d = { w: 0, gate: 0, told: false, fails: [] }))
|
|
434
437
|
return d
|
|
435
438
|
}
|
|
439
|
+
const recentFails = (d: { fails: number[] }, t: number) => {
|
|
440
|
+
while (d.fails.length && t - d.fails[0] >= MINUTE) d.fails.shift()
|
|
441
|
+
return d.fails.length
|
|
442
|
+
}
|
|
436
443
|
function dialGate(s: Session): Why | { waitMs: number } | null {
|
|
437
444
|
const d = dials.get(grpOf(s))
|
|
438
445
|
if (!d || !d.w) return null
|
|
@@ -504,15 +511,22 @@ export function createGovernor(o: HostedOptions) {
|
|
|
504
511
|
/** D: a dial was refused, found no route, timed out or named a host that does not exist, after `ms`. */
|
|
505
512
|
function dialFailed(s: Session, ms: number) {
|
|
506
513
|
const d = dialOf(grpOf(s))
|
|
514
|
+
const t = now()
|
|
515
|
+
d.fails.push(t)
|
|
516
|
+
if (d.fails.length > c.failedDialsPerMin) d.fails.shift()
|
|
517
|
+
if (!d.w && recentFails(d, t) < c.failedDialsPerMin) return
|
|
507
518
|
d.w = Math.min(c.dialLimitMs, Math.max(1, Math.round(ms), d.w * 2))
|
|
508
519
|
}
|
|
509
520
|
function dialOk(s: Session) {
|
|
510
521
|
const d = dials.get(grpOf(s))
|
|
511
522
|
if (!d) return
|
|
523
|
+
if (!d.w) return
|
|
512
524
|
d.w = Math.floor(d.w / 2)
|
|
513
525
|
if (!d.w) {
|
|
514
|
-
|
|
526
|
+
d.gate = 0
|
|
515
527
|
if (d.told) s.hx?.handle.tell?.(3, 'failed-dials', 0, '')
|
|
528
|
+
d.told = false
|
|
529
|
+
if (!recentFails(d, now())) dials.delete(grpOf(s))
|
|
516
530
|
}
|
|
517
531
|
}
|
|
518
532
|
const dialWait = (s: Session) => dials.get(grpOf(s))?.w ?? 0
|
|
@@ -616,12 +630,13 @@ export function createGovernor(o: HostedOptions) {
|
|
|
616
630
|
ticker.unref?.()
|
|
617
631
|
const saver = o.usageFile ? setInterval(saveUsage, 15_000) : null
|
|
618
632
|
saver?.unref?.()
|
|
619
|
-
|
|
633
|
+
function sweep() {
|
|
620
634
|
const t = now()
|
|
621
635
|
for (const m of [addrSites, netSites]) for (const [k, x] of m) if (!fresh(x, t).size) m.delete(k)
|
|
622
|
-
for (const [k, d] of dials) if (d.gate + HOUR < t) dials.delete(k)
|
|
623
|
-
}
|
|
624
|
-
sweep
|
|
636
|
+
for (const [k, d] of dials) if (d.gate + HOUR < t && !recentFails(d, t)) dials.delete(k)
|
|
637
|
+
}
|
|
638
|
+
const sweeper = setInterval(sweep, 60_000)
|
|
639
|
+
sweeper.unref?.()
|
|
625
640
|
|
|
626
641
|
const state = () => {
|
|
627
642
|
roll()
|
|
@@ -684,6 +699,7 @@ export function createGovernor(o: HostedOptions) {
|
|
|
684
699
|
bytes,
|
|
685
700
|
limitWhy,
|
|
686
701
|
tick,
|
|
702
|
+
sweep,
|
|
687
703
|
slowed,
|
|
688
704
|
take,
|
|
689
705
|
waitMs,
|
|
@@ -691,11 +707,11 @@ export function createGovernor(o: HostedOptions) {
|
|
|
691
707
|
blockedAddr,
|
|
692
708
|
liveStreams: () => used,
|
|
693
709
|
limitsLine: () =>
|
|
694
|
-
`only listed
|
|
710
|
+
`only listed pages may use this relay; ${c.sitesPerAddrPerHour} different sites an hour per address, ${c.sitesPerNetPerHour} per network; failed dials slow the sender` +
|
|
695
711
|
(c.monthBytes ? `; ${Math.round(c.monthBytes / 1e9)} GB a month, heavy users slowed first when the month runs ahead` : ''),
|
|
696
712
|
stop() {
|
|
697
713
|
clearInterval(ticker)
|
|
698
|
-
clearInterval(
|
|
714
|
+
clearInterval(sweeper)
|
|
699
715
|
if (saver) clearInterval(saver)
|
|
700
716
|
saveUsage()
|
|
701
717
|
},
|
package/relay/main.ts
CHANGED
|
@@ -53,7 +53,7 @@ Flags:
|
|
|
53
53
|
--allow-loopback-ports <list> guests may reach this computer's loopback on these ports
|
|
54
54
|
--block-ports <list> destination ports refused (none)
|
|
55
55
|
--allow-hosts <list> only these names (name[:port|port], a leading . for subdomains)
|
|
56
|
-
--hosted the public hosted profile (docs/relay-hosted.md): only listed
|
|
56
|
+
--hosted the public hosted profile (docs/relay-hosted.md): only listed pages may use this relay, no crawling,
|
|
57
57
|
failed dials slowed, the month's data cap, host limits, deny lists, a control
|
|
58
58
|
socket, /healthz, /readyz
|
|
59
59
|
--abuse-contact <text> required: an address or URL, shown in the MOTD, /healthz and every refusal
|
|
@@ -79,7 +79,7 @@ const FLAG_KEYS = ['host', 'port', 'origins', 'keys', 'tlsCert', 'tlsKey', 'loca
|
|
|
79
79
|
const CAP_KEYS = ['maxConns', 'maxSessions', 'maxSessionsPerGroup', 'opensPerMin', 'connectsPerMin', 'dnsPerMin', 'rateDown', 'rateUp', 'maxBytes', 'selfAddrs', 'dnsServers', 'connectTimeoutMs', 'dnsTimeoutMs'] as const
|
|
80
80
|
const WISP_KEYS = ['path', 'buffer', 'maxPayload', 'maxHeldBytes', 'maxSessionHeldBytes', 'maxHeldTotal', 'maxGroupHeldBytes', 'instancesPerHour', 'maxPrincipals', 'preAuthPerAddr', 'handshakeMs', 'streamIdleMs', 'prefixKeys'] as const
|
|
81
81
|
// the numbers of the hosted rules, under "hosted" in a config file (hosted.ts HostedOptions; all whole numbers)
|
|
82
|
-
const HOSTED_KEYS = ['sitesPerAddrPerHour', 'sitesPerNetPerHour'] as const
|
|
82
|
+
const HOSTED_KEYS = ['sitesPerAddrPerHour', 'sitesPerNetPerHour', 'failedDialsPerMin'] as const
|
|
83
83
|
// keys of the limits plan §16 removed: a config that still has them starts, with one line saying they do nothing
|
|
84
84
|
const LEGACY_KEYS = new Set(['maxBytesPerDay', 'maxBytesPerMonth'])
|
|
85
85
|
const LEGACY_HOSTED_KEYS = new Set(['groupStreams', 'groupConnectsPerMin', 'groupBytesPerHour', 'groupDnsPerMin', 'coarseSessions', 'coarseInstancesPerHour', 'coarseRateDown', 'coarseRateUp', 'originTable', 'originSessions', 'originShare', 'otherOriginSessions', 'noOriginSessions', 'originsPerAddrPerHour', 'originRateDown', 'originRateUp', 'maxStreams', 'maxRateDown', 'maxRateUp', 'destConnectsPerMin', 'destStreams', 'distinctDests', 'failedConnects', 'failedWindowMs', 'failedCoolMs'])
|
|
@@ -326,7 +326,7 @@ export function relayOptions(c: Conf, env: Record<string, string | undefined> =
|
|
|
326
326
|
if (c.behindTls && !isPrivateBind(host)) throw new UsageError(`--hosted --behind-tls binds only to loopback or a private address (the proxy\'s side), not ${host}: a public bind would let anyone skip the proxy and its TLS`)
|
|
327
327
|
if (!tls && !c.behindTls && !isPrivateBind(host)) throw new UsageError(`--hosted refuses plain ws:// on the public address ${host}: give --tls-cert and --tls-key, or put a TLS proxy in front with --behind-tls (bound to loopback)`)
|
|
328
328
|
if (c.localTls) throw new UsageError('--local-tls is for a person\'s own computer, not --hosted')
|
|
329
|
-
if (!c.sitesFile) throw new UsageError('--hosted needs --sites-file <file>: the page origins it serves (JSON {"sites": [...]});
|
|
329
|
+
if (!c.sitesFile) throw new UsageError('--hosted needs --sites-file <file>: the page origins it serves (JSON {"sites": [...]}); only listed pages may use this relay')
|
|
330
330
|
}
|
|
331
331
|
const w = c.wisp ?? {}
|
|
332
332
|
const secret = env.WMD_RELAY_SECRET || randomBytes(32).toString('base64url') // tokens (wmn's issuer) only when the secret is shared
|
package/src/boot.js
CHANGED
|
@@ -51,9 +51,11 @@ import {
|
|
|
51
51
|
} from './host/ckpt.js'
|
|
52
52
|
import { captureJit, jitSnapStats, modulesOf, ownersNeedingHub, restoreStore } from './host/jit-snap.js'
|
|
53
53
|
export { CheckpointError, memoryStore, opfsStore } from './host/ckpt.js'
|
|
54
|
+
export { DiskError, deleteDisk, openDisk, sweepDisks } from './host/disk.js'
|
|
54
55
|
export { NetSwitch } from './net/switch.js'
|
|
55
56
|
export { ImageFiles, openImage, parseGroup, parsePasswd, resolveUser } from './image-files.js'
|
|
56
57
|
export { P9_CACHE_DEFAULT, p9InitCmdline, p9MountOptions } from './host/p9mount.js'
|
|
58
|
+
export { loadImageTar } from './registry/load-tar.js'
|
|
57
59
|
|
|
58
60
|
const PAGE = 65536
|
|
59
61
|
/** A trap with the memory this close to its maximum is reported as out of memory. */
|
|
@@ -414,12 +416,30 @@ function toShared(bytes) {
|
|
|
414
416
|
return sab
|
|
415
417
|
}
|
|
416
418
|
|
|
419
|
+
/** vm.wasm's arguments for a machine with `disk`: --swap <MiB> and wmd.disk=data appended to --append. */
|
|
420
|
+
export function diskArgs(args, disk) {
|
|
421
|
+
if (args.includes('--swap')) throw new TypeError('--swap and a disk: give one')
|
|
422
|
+
const mib = disk.bytes / 2 ** 20
|
|
423
|
+
if (!Number.isInteger(mib) || mib < 1) throw new TypeError(`disk size ${disk.bytes} is not whole MiB`)
|
|
424
|
+
const out = [...args, '--swap', String(mib)]
|
|
425
|
+
const i = out.indexOf('--append')
|
|
426
|
+
if (i < 0 || i + 1 >= out.length) throw new TypeError('a disk needs --append (the kernel line gets wmd.disk=data)')
|
|
427
|
+
// the disk holds data: a wmd.disk= of any other value would let the guest init write a swap header over it
|
|
428
|
+
const given = out[i + 1].match(/(?:^| )wmd\.disk(?:=(\S*))?(?= |$)/g)
|
|
429
|
+
if (given && given.some(g => g.trim() !== 'wmd.disk=data')) throw new TypeError(`a disk needs wmd.disk=data on the kernel line, not ${given.map(g => g.trim()).join(' ')}`)
|
|
430
|
+
if (!given) out[i + 1] = `${out[i + 1]} wmd.disk=data`
|
|
431
|
+
return out
|
|
432
|
+
}
|
|
433
|
+
|
|
417
434
|
/**
|
|
418
435
|
* @param {object} o
|
|
419
436
|
* @param {string|Uint8Array|ArrayBuffer} o.vmUrl URL of vm.wasm, or its bytes
|
|
420
437
|
* @param {Record<string,string|Uint8Array>} o.files preopen contents: path → URL or bytes
|
|
421
438
|
* @param {string[]} o.args vm.wasm arguments (after argv[0])
|
|
422
439
|
* @param {string[]} [o.env] "K=V" (the runner passes only WMD_*)
|
|
440
|
+
* @param {object} [o.disk] a disk from openDisk (host/disk.js): the guest gets it as virtio-blk
|
|
441
|
+
* /dev/vda (vm.wasm's --swap MiB sizes the device) with wmd.disk=data on the kernel line, so guest init leaves
|
|
442
|
+
* it alone. A machine with a disk takes no checkpoints: guest memory and the disk would not match
|
|
423
443
|
* @param {boolean} [o.heapReserve] W21 (default true, false in mobile WebKit: heapReserveDefault, D-516): vm.wasm grows its memory to the maximum
|
|
424
444
|
* before any thread starts (WMD_HEAP_RESERVE=1), so memory never grows under running
|
|
425
445
|
* vCPUs. Chromium checks memory.fill/copy against a per-Worker size that lags another
|
|
@@ -520,6 +540,10 @@ export async function bootWmd(o) {
|
|
|
520
540
|
o.onStderr?.(text)
|
|
521
541
|
}
|
|
522
542
|
const preopen = o.preopen || '/g'
|
|
543
|
+
if (o.disk) {
|
|
544
|
+
if (o.checkpoint || o.restore || o.restoreLazy) throw new TypeError('a machine with a disk takes no checkpoints')
|
|
545
|
+
o = { ...o, args: diskArgs(o.args, o.disk) }
|
|
546
|
+
}
|
|
523
547
|
const argv = ['vm.wasm', ...o.args]
|
|
524
548
|
const env = vmEnv(o.env, o.heapReserve ?? heapReserveDefault())
|
|
525
549
|
// the default Worker is started from a literal new URL(..., import.meta.url) so bundlers (Vite, webpack)
|
|
@@ -1550,6 +1574,7 @@ export async function bootWmd(o) {
|
|
|
1550
1574
|
tableMin,
|
|
1551
1575
|
memory,
|
|
1552
1576
|
ram: ramMem,
|
|
1577
|
+
blk: o.disk?.mailbox || null,
|
|
1553
1578
|
ctlSab,
|
|
1554
1579
|
files,
|
|
1555
1580
|
preopen,
|