@daugasauron/dolly 0.1.5 → 0.1.6
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 +51 -78
- package/coi-serviceworker.js +23 -8
- package/dist/dolly-build-id.mjs +1 -1
- package/dist/dolly-image-build-id.mjs +1 -1
- package/dist/dolly-process-0.wasm +0 -0
- package/dist/dolly-process-abi.mjs +1 -1
- package/dist/dolly-process-dso.mjs +19 -11
- package/dist/dolly-process-worker.mjs +88 -33
- package/dist/dolly-runtime-worker.mjs +1544 -0
- package/dist/dolly.wasm +0 -0
- package/host/dso/dso.mjs +2 -2
- package/host/dso/module.json +4 -1
- package/host/http/abi.mjs +3 -1
- package/host/http/broker.mjs +17 -7
- package/host/http/http.mjs +19 -5
- package/host/http/module.json +3 -1
- package/host/http/policy.mjs +13 -6
- package/host/http/refusals.mjs +77 -0
- package/host/input/abi.mjs +1 -1
- package/host/modules.mjs +64 -23
- package/host/nena/module.json +2 -1
- package/host/nena/nena.mjs +2 -0
- package/host/packages/abi.mjs +27 -0
- package/host/packages/module.json +6 -4
- package/host/packages/packages.mjs +23 -8
- package/host/packages/service.mjs +46 -20
- package/host/packages/transport.mjs +123 -0
- package/host/packages/worker.mjs +13 -9
- package/host/requirements.mjs +1 -1
- package/host/runtime/abi.mjs +32 -0
- package/host/runtime/lease-bridge.mjs +13 -8
- package/host/runtime/module.json +3 -3
- package/host/runtime/runtime.mjs +3 -3
- package/host/snapshot/abi.mjs +2 -1
- package/host/snapshot/module.json +1 -1
- package/host/snapshot/snapshot.mjs +80 -57
- package/host/snapshot/transport.mjs +10 -3
- package/host/snapshot/ui.mjs +64 -39
- package/host/stack/module.json +1 -0
- package/host/stack/stack.mjs +1 -1
- package/host/threads/module.json +6 -1
- package/host/threads/threads.mjs +32 -13
- package/package.json +2 -2
- package/{src → page}/browser.mjs +37 -23
- package/page/generated.mjs +8 -0
- package/{src → page}/image-entry.mjs +10 -6
- package/{src → page}/image-inputs.mjs +11 -0
- package/{src → page}/image-names.mjs +2 -2
- package/{src → page}/kernel-plugin.mjs +1 -1
- package/{src → page}/pack-store.mjs +1 -1
- package/{src → page}/packaged-snapshot.mjs +56 -46
- package/{src → page}/process-constants.mjs +72 -15
- package/{src → page}/process-supervisor.mjs +155 -227
- package/{src → page}/snapshot-records.mjs +63 -44
- package/{src → page}/static-asset.mjs +36 -20
- package/{src → page}/version.mjs +1 -1
- package/{src → page}/worker-image.mjs +20 -9
- package/{src → site}/build-worker.mjs +36 -25
- package/{src → site}/custom-image.mjs +2 -10
- package/{src → site}/dollyfile-graph.mjs +1 -1
- package/{src → site}/dollyfile-view.mjs +1 -1
- package/{src → site}/image-artifact.mjs +9 -8
- package/{src → site}/image-build.mjs +4 -4
- package/{src → site}/image-builder.mjs +5 -4
- package/site/offline.mjs +53 -0
- package/{src → site}/session-store.mjs +47 -38
- package/src/runtime-worker.mjs +0 -57
- /package/{src → page}/build-log.mjs +0 -0
- /package/{src → page}/images.mjs +0 -0
- /package/{src → page}/page-chords.mjs +0 -0
- /package/{src → page}/page-indicators.mjs +0 -0
- /package/{src → page}/process-abi.mjs +0 -0
- /package/{src → page}/sha256.mjs +0 -0
- /package/{src → page}/terminal-text.mjs +0 -0
- /package/{src → page}/wasm-interface.mjs +0 -0
- /package/{src → site}/alphabetical.mjs +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { alphabetical } from "./alphabetical.mjs";
|
|
2
|
-
import { validName } from "
|
|
3
|
-
import { imageInputs } from "
|
|
4
|
-
import { MAX_SNAPSHOT_BYTES } from "
|
|
5
|
-
import { DOLLY_VERSION } from "
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { validName } from "../page/image-names.mjs";
|
|
3
|
+
import { imageInputs } from "../page/image-inputs.mjs";
|
|
4
|
+
import { MAX_SNAPSHOT_BYTES } from "../page/snapshot-records.mjs";
|
|
5
|
+
import { DOLLY_VERSION } from "../page/version.mjs";
|
|
6
|
+
|
|
7
|
+
// A save is the whole filesystem but /dev, /proc and /run (host/snapshot/kernel.c),
|
|
8
|
+
// at most 8 GiB before and after compression.
|
|
9
|
+
export const DOLLY_SESSION_FORMAT_VERSION = 4;
|
|
10
|
+
export const DOLLY_SESSION_MAX_BYTES = 8 * 1024 * 1024 * 1024;
|
|
9
11
|
export const DOLLY_SESSION_METADATA_MAX_BYTES = 1024 * 1024;
|
|
10
12
|
|
|
11
13
|
// Saves belong to a version: every version's pages open their own database.
|
|
@@ -43,16 +45,17 @@ export function customSessionIdentity(custom) {
|
|
|
43
45
|
return `custom:${artifact.recipeSha256}:${artifact.sha256}`;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
// A save holds its whole filesystem, so it opens wherever its programs run: on
|
|
49
|
+
// a runtime with the image build ID of the one that made it (buildId; a rebuilt
|
|
50
|
+
// kernel keeps it while the images stay valid), whatever became of its image's
|
|
51
|
+
// recipe. The image need only still be named (its host modules are the
|
|
52
|
+
// catalog's), a custom one by the recipe it carries.
|
|
53
|
+
export function sessionCompatible(record, definitions, buildId) {
|
|
47
54
|
if (record.formatVersion !== DOLLY_SESSION_FORMAT_VERSION || record.buildId !== buildId) return false;
|
|
48
55
|
if (record.image === "custom") {
|
|
49
|
-
try {
|
|
50
|
-
return record.imageIdentity === customSessionIdentity(record.customImage) &&
|
|
51
|
-
record.customImage.artifact.buildId === imageBuildId;
|
|
52
|
-
} catch { return false; }
|
|
56
|
+
try { return record.imageIdentity === customSessionIdentity(record.customImage); } catch { return false; }
|
|
53
57
|
}
|
|
54
|
-
return record.customImage === undefined && definitions.some(({ image }) => image === record.image)
|
|
55
|
-
record.imageIdentity === sessionImageIdentity(definitions, record.image);
|
|
58
|
+
return record.customImage === undefined && definitions.some(({ image }) => image === record.image);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export function sessionLoadUrl(name, applicationBase) {
|
|
@@ -92,16 +95,36 @@ async function collectStream(stream, maximum) {
|
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
|
|
98
|
+
// The compressed save, a Blob of its parts: no buffer holds it whole.
|
|
99
|
+
async function collectBlob(stream, maximum) {
|
|
100
|
+
const reader = stream.getReader(), parts = [];
|
|
101
|
+
let length = 0;
|
|
102
|
+
try {
|
|
103
|
+
for (;;) {
|
|
104
|
+
const { done, value } = await reader.read();
|
|
105
|
+
if (done) break;
|
|
106
|
+
length += value.byteLength;
|
|
107
|
+
if (length > maximum) throw new Error("Dolly session exceeds its size limit");
|
|
108
|
+
parts.push(value);
|
|
109
|
+
}
|
|
110
|
+
return new Blob(parts);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
await reader.cancel(error).catch(() => {});
|
|
113
|
+
throw error;
|
|
114
|
+
} finally {
|
|
115
|
+
reader.releaseLock();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
95
119
|
export async function encodeSessionStream(produce) {
|
|
96
120
|
const stream = new CompressionStream("gzip"), writer = stream.writable.getWriter();
|
|
97
|
-
const encoded =
|
|
121
|
+
const encoded = collectBlob(stream.readable, DOLLY_SESSION_MAX_BYTES);
|
|
98
122
|
// The reader can fail while the producer is awaiting its next mailbox chunk.
|
|
99
123
|
encoded.catch(() => {});
|
|
100
124
|
try {
|
|
101
125
|
await produce(bytes => writer.write(bytes));
|
|
102
126
|
await writer.close();
|
|
103
|
-
|
|
104
|
-
return { encoding: "gzip", bytes: (await encoded).transferToFixedLength() };
|
|
127
|
+
return { encoding: "gzip", bytes: await encoded };
|
|
105
128
|
} catch (error) {
|
|
106
129
|
await writer.abort(error).catch(() => {});
|
|
107
130
|
await encoded.catch(() => {});
|
|
@@ -109,22 +132,10 @@ export async function encodeSessionStream(produce) {
|
|
|
109
132
|
}
|
|
110
133
|
}
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
throw new Error("Stored Dolly session is invalid");
|
|
117
|
-
}
|
|
118
|
-
let offset = 0;
|
|
119
|
-
const stream = new ReadableStream({
|
|
120
|
-
pull(controller) {
|
|
121
|
-
if (offset === record.bytes.byteLength) { controller.close(); return; }
|
|
122
|
-
const length = Math.min(65536, record.bytes.byteLength - offset);
|
|
123
|
-
controller.enqueue(new Uint8Array(record.bytes, offset, length));
|
|
124
|
-
offset += length;
|
|
125
|
-
},
|
|
126
|
-
}).pipeThrough(new DecompressionStream("gzip"));
|
|
127
|
-
return collectStream(stream, DOLLY_SESSION_MAX_BYTES);
|
|
135
|
+
// A whole save in one buffer, for a test that reads its records: at most 2 GiB.
|
|
136
|
+
export async function decodeSessionSnapshot(bytes) {
|
|
137
|
+
if (!(bytes instanceof Blob) || bytes.size === 0) throw new Error("Stored Dolly session is invalid");
|
|
138
|
+
return collectStream(bytes.stream().pipeThrough(new DecompressionStream("gzip")), 2 * 1024 * 1024 * 1024);
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
function openDatabase() {
|
|
@@ -164,7 +175,6 @@ export async function loadStoredSession(name) {
|
|
|
164
175
|
if (!(record.bytes instanceof Blob) || record.bytes.size === 0 || record.bytes.size > DOLLY_SESSION_MAX_BYTES) {
|
|
165
176
|
throw new Error("Stored Dolly session is invalid");
|
|
166
177
|
}
|
|
167
|
-
record.bytes = await record.bytes.arrayBuffer();
|
|
168
178
|
return record;
|
|
169
179
|
}
|
|
170
180
|
|
|
@@ -176,8 +186,8 @@ export function validateSessionRecord(record) {
|
|
|
176
186
|
typeof record.image !== "string" || !validName(record.image) ||
|
|
177
187
|
typeof record.imageIdentity !== "string" || record.imageIdentity.length > 256 ||
|
|
178
188
|
!Number.isSafeInteger(record.updatedAt) ||
|
|
179
|
-
!(record.bytes instanceof
|
|
180
|
-
record.bytes.
|
|
189
|
+
!(record.bytes instanceof Blob) || record.bytes.size === 0 ||
|
|
190
|
+
record.bytes.size > DOLLY_SESSION_MAX_BYTES || record.encoding !== "gzip") {
|
|
181
191
|
throw new TypeError("invalid Dolly session record");
|
|
182
192
|
}
|
|
183
193
|
if (record.image === "custom" ? record.imageIdentity !== customSessionIdentity(record.customImage)
|
|
@@ -186,8 +196,7 @@ export function validateSessionRecord(record) {
|
|
|
186
196
|
|
|
187
197
|
export async function saveStoredSession(record, { overwrite = true } = {}) {
|
|
188
198
|
validateSessionRecord(record);
|
|
189
|
-
|
|
190
|
-
await transaction("readwrite", (store) => overwrite ? store.put(stored) : store.add(stored));
|
|
199
|
+
await transaction("readwrite", (store) => overwrite ? store.put(record) : store.add(record));
|
|
191
200
|
}
|
|
192
201
|
|
|
193
202
|
export async function deleteStoredSession(name) {
|
package/src/runtime-worker.mjs
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// The runtime Worker boot() starts: it restores an image of the image directory
|
|
2
|
-
// from its packs into a new kernel and runs its ENTRY (worker-image.mjs).
|
|
3
|
-
import { imageDefinition } from "./images.mjs";
|
|
4
|
-
import { loadPackagedSnapshotMetadata, streamPackagedSystemSnapshot, verifyVisibleRecipes } from "./packaged-snapshot.mjs";
|
|
5
|
-
import { siteReference, hex } from "./static-asset.mjs";
|
|
6
|
-
import { checkedMemoryRange, runImage } from "./worker-image.mjs";
|
|
7
|
-
|
|
8
|
-
await runImage(async ({ image, files: given = {} }, host) => {
|
|
9
|
-
const definition = imageDefinition(image);
|
|
10
|
-
if (!definition) throw new Error("invalid Dolly image selection");
|
|
11
|
-
const [metadata] = await Promise.all([loadPackagedSnapshotMetadata(image), verifyVisibleRecipes(image)]);
|
|
12
|
-
host.require(definition.hostRequirements);
|
|
13
|
-
host.admit(definition.hostRequirements);
|
|
14
|
-
return {
|
|
15
|
-
name: image,
|
|
16
|
-
prepare({ files }) {
|
|
17
|
-
files.write("/etc/dolly/recipe.locator", siteReference(definition.dollyfile));
|
|
18
|
-
// The kernel restores a snapshot against the list of its paths.
|
|
19
|
-
files.write("/etc/dolly/image.manifest", `${metadata.manifest.join("\n")}\n`);
|
|
20
|
-
},
|
|
21
|
-
async restore({ kernel, memory, files, stage }) {
|
|
22
|
-
stage("loading precompiled userspace snapshot...");
|
|
23
|
-
const capacity = 1024 * 1024;
|
|
24
|
-
const address = kernel.dolly_snapshot_restore_address(BigInt(capacity));
|
|
25
|
-
const staging = () => {
|
|
26
|
-
const range = checkedMemoryRange(memory, address, capacity);
|
|
27
|
-
return new Uint8Array(memory.buffer, range.address, range.size);
|
|
28
|
-
};
|
|
29
|
-
staging().set(Uint8Array.from(metadata.sha256.match(/../g), byte => parseInt(byte, 16)));
|
|
30
|
-
if (kernel.dolly_bootstrap_snapshot_begin(BigInt(metadata.byteLength)) !== 0)
|
|
31
|
-
throw new Error("Dolly snapshot stream initialization failed");
|
|
32
|
-
let loaded = 0, reported = 0;
|
|
33
|
-
await streamPackagedSystemSnapshot(metadata, bytes => {
|
|
34
|
-
for (let offset = 0; offset < bytes.length; offset += capacity) {
|
|
35
|
-
const chunk = bytes.subarray(offset, offset + capacity);
|
|
36
|
-
staging().set(chunk);
|
|
37
|
-
if (kernel.dolly_snapshot_stream_write(BigInt(chunk.length), 0) !== 0)
|
|
38
|
-
throw new Error("Dolly rejected a snapshot stream record");
|
|
39
|
-
}
|
|
40
|
-
loaded += bytes.length;
|
|
41
|
-
if (performance.now() - reported >= 1000) {
|
|
42
|
-
const mib = 1024 * 1024;
|
|
43
|
-
stage(`loading userspace: ${Math.min(Math.floor(loaded / mib), Math.ceil(metadata.byteLength / mib))} / ${Math.ceil(metadata.byteLength / mib)} MiB`);
|
|
44
|
-
reported = performance.now();
|
|
45
|
-
}
|
|
46
|
-
}, () => {
|
|
47
|
-
if (kernel.dolly_snapshot_stream_write(0n, 1) !== 0)
|
|
48
|
-
throw new Error("Dolly rejected an incomplete snapshot part");
|
|
49
|
-
return hex(staging().subarray(0, 32));
|
|
50
|
-
});
|
|
51
|
-
const status = kernel.dolly_bootstrap_snapshot_end();
|
|
52
|
-
// The embedding's files (boot's `files`) lie over the image's.
|
|
53
|
-
if (status === 0) for (const [path, contents] of Object.entries(given)) files.write(path, contents);
|
|
54
|
-
return { status };
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
});
|
|
File without changes
|
/package/{src → page}/images.mjs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/{src → page}/sha256.mjs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|