@koed-labs/server 0.0.0-bootstrap.0 → 0.10.3
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/LICENSE +201 -0
- package/README.md +65 -0
- package/bin/koed.js +7 -0
- package/package.json +22 -4
- package/third-party-notices.json +92 -0
- package/vendor/chunk-2BWTLF7A.js +13883 -0
- package/vendor/chunk-2DA7EFZV.js +924 -0
- package/vendor/chunk-2S3OGPUC.js +58 -0
- package/vendor/chunk-473OA62O.js +26827 -0
- package/vendor/chunk-6JRWGOQG.js +236 -0
- package/vendor/chunk-7NOQSX4T.js +14 -0
- package/vendor/chunk-7NXKPDF3.js +1586 -0
- package/vendor/chunk-A4NKL2PK.js +153 -0
- package/vendor/chunk-A6WMU2GG.js +48 -0
- package/vendor/chunk-AFLKCQHF.js +73 -0
- package/vendor/chunk-BTS4ZTF3.js +43 -0
- package/vendor/chunk-C3N4N5HR.js +108 -0
- package/vendor/chunk-DXTAWHY3.js +25 -0
- package/vendor/chunk-G4IIVVJO.js +56 -0
- package/vendor/chunk-GELDEJJ2.js +4337 -0
- package/vendor/chunk-HFM6ROJL.js +188 -0
- package/vendor/chunk-HK2H3MVE.js +328 -0
- package/vendor/chunk-HZJK5RWC.js +5088 -0
- package/vendor/chunk-J2L726BC.js +2497 -0
- package/vendor/chunk-JBGD4JLR.js +3779 -0
- package/vendor/chunk-JJNLH5GA.js +2470 -0
- package/vendor/chunk-JKOG52PE.js +128 -0
- package/vendor/chunk-K2EIFEQF.js +89 -0
- package/vendor/chunk-KUIWH33N.js +16 -0
- package/vendor/chunk-NEWIOOM6.js +34 -0
- package/vendor/chunk-NM5NXCVM.js +2404 -0
- package/vendor/chunk-PT3AUPGE.js +459 -0
- package/vendor/chunk-PY5LYLV7.js +143 -0
- package/vendor/chunk-QIIKMG3M.js +103 -0
- package/vendor/chunk-QT24SAW6.js +110 -0
- package/vendor/chunk-RVYJKKWA.js +22 -0
- package/vendor/chunk-SKVYP7NY.js +1647 -0
- package/vendor/chunk-TSRPHKAC.js +780 -0
- package/vendor/chunk-UI5RLBKQ.js +30 -0
- package/vendor/chunk-VPC5NLZG.js +259 -0
- package/vendor/chunk-W2HNI6AA.js +75 -0
- package/vendor/chunk-X6M525ZJ.js +20 -0
- package/vendor/chunk-XIZVLPHZ.js +18 -0
- package/vendor/chunk-XNFVSVBS.js +242 -0
- package/vendor/chunk-YCDEMYY6.js +2076 -0
- package/vendor/chunk-YJ6XUZT6.js +292 -0
- package/vendor/chunk-YJWREXN6.js +26020 -0
- package/vendor/chunk-YK4F4VY2.js +173 -0
- package/vendor/chunk-YOFI4PCH.js +36 -0
- package/vendor/chunk-ZUGNCQZE.js +247 -0
- package/vendor/chunk-ZXX4ZNJY.js +21 -0
- package/vendor/cli.js +7 -0
- package/vendor/component-manifest-catalog.js +8 -0
- package/vendor/control-plane.js +23 -0
- package/vendor/package.json +1 -0
- package/vendor/privacy-service-bootstrap.js +6 -0
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
import { createRequire as createKoedRequire } from "node:module"; const require = createKoedRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// packages/koed-server/dist/package-runtime.js
|
|
4
|
+
import { createHash, createPublicKey, verify } from "node:crypto";
|
|
5
|
+
|
|
6
|
+
// packages/koed-server/dist/artifact-canonical-json.mjs
|
|
7
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
8
|
+
function canonicalArtifactJson(value) {
|
|
9
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
10
|
+
const encode = (item) => {
|
|
11
|
+
if (item === null)
|
|
12
|
+
return "null";
|
|
13
|
+
if (typeof item === "string" || typeof item === "boolean")
|
|
14
|
+
return JSON.stringify(item);
|
|
15
|
+
if (typeof item === "number" && Number.isFinite(item))
|
|
16
|
+
return JSON.stringify(item);
|
|
17
|
+
if (typeof item !== "object")
|
|
18
|
+
throw new TypeError("artifact has unsupported JSON value");
|
|
19
|
+
const array = Array.isArray(item);
|
|
20
|
+
const prototype = Object.getPrototypeOf(item);
|
|
21
|
+
if (!array && prototype !== Object.prototype && prototype !== null)
|
|
22
|
+
throw new TypeError("artifact has unsupported JSON object");
|
|
23
|
+
if (Object.getOwnPropertySymbols(item).length)
|
|
24
|
+
throw new TypeError("artifact has unsupported JSON symbol key");
|
|
25
|
+
if (ancestors.has(item))
|
|
26
|
+
throw new TypeError("artifact has cyclic JSON value");
|
|
27
|
+
ancestors.add(item);
|
|
28
|
+
try {
|
|
29
|
+
if (array) {
|
|
30
|
+
const entries = [];
|
|
31
|
+
for (let index = 0; index < item.length; index++) {
|
|
32
|
+
if (!Object.hasOwn(item, index))
|
|
33
|
+
throw new TypeError("artifact has sparse JSON array");
|
|
34
|
+
entries.push(encode(item[index]));
|
|
35
|
+
}
|
|
36
|
+
return `[${entries.join(",")}]`;
|
|
37
|
+
}
|
|
38
|
+
return `{${Object.keys(item).sort().map((key) => `${JSON.stringify(key)}:${encode(item[key])}`).join(",")}}`;
|
|
39
|
+
} finally {
|
|
40
|
+
ancestors.delete(item);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return encode(value);
|
|
44
|
+
}
|
|
45
|
+
function canonicalArtifactBytes(value) {
|
|
46
|
+
return Buffer2.from(canonicalArtifactJson(value), "utf8");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// packages/koed-server/dist/package-runtime.js
|
|
50
|
+
import { createGunzip } from "node:zlib";
|
|
51
|
+
import { chmodSync, closeSync, cpSync, createReadStream, createWriteStream, existsSync, lstatSync, openSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, symlinkSync, writeSync } from "node:fs";
|
|
52
|
+
import { constants as fsConstants, statSync } from "node:fs";
|
|
53
|
+
import { basename, dirname, relative, resolve } from "node:path";
|
|
54
|
+
import { pipeline } from "node:stream/promises";
|
|
55
|
+
import { Readable, Transform } from "node:stream";
|
|
56
|
+
var standalonePackageSchemaVersion = 2;
|
|
57
|
+
var standalonePackageId = "koed-server";
|
|
58
|
+
var standalonePackageKind = "app-runtime";
|
|
59
|
+
var packageProvenanceSchemaVersion = 1;
|
|
60
|
+
var packageExtractionLimits = {
|
|
61
|
+
compressedBytes: 2 * 1024 * 1024 * 1024,
|
|
62
|
+
expandedBytes: 8 * 1024 * 1024 * 1024,
|
|
63
|
+
fileCount: 2e5,
|
|
64
|
+
individualFileBytes: 2 * 1024 * 1024 * 1024,
|
|
65
|
+
pathBytes: 4096,
|
|
66
|
+
manifestBytes: 8 * 1024 * 1024,
|
|
67
|
+
paxHeaderBytes: 1024 * 1024
|
|
68
|
+
};
|
|
69
|
+
var requiredPackageRuntimeFiles = [
|
|
70
|
+
"api/dist/index.js",
|
|
71
|
+
"worker/dist/index.js",
|
|
72
|
+
"embedding-service/dist/index.js",
|
|
73
|
+
"mcp-server/dist/cli.js",
|
|
74
|
+
"mcp-server/dist/capture-hook.js",
|
|
75
|
+
"mcp-server/dist/prompts/codex-global-agent-guidance.md",
|
|
76
|
+
"node_modules/@koed/db/dist/index.js",
|
|
77
|
+
"node_modules/@koed/db/dist/connection.js",
|
|
78
|
+
"node_modules/@koed/db/dist/user-api-token-repository.js",
|
|
79
|
+
"node_modules/@koed/db/drizzle/meta/_journal.json"
|
|
80
|
+
];
|
|
81
|
+
var excludedPackagePatterns = [
|
|
82
|
+
/^koed-runtime\/explorer-dist(?:\/|$)/,
|
|
83
|
+
/^koed-server\/dist\/explorer-static-(?:proxy|server)(?:\.|$)/,
|
|
84
|
+
/^koed-runtime\/postgres(?:\/|$)/,
|
|
85
|
+
/^koed-runtime\/llama\.cpp(?:\/|$)/,
|
|
86
|
+
/^koed-runtime\/runtime-asset-manifest\.json$/,
|
|
87
|
+
/^koed-runtime\/.*\.gguf$/,
|
|
88
|
+
/^koed-runtime\/.*\.safetensors$/,
|
|
89
|
+
/^koed-runtime\/.*\.onnx$/,
|
|
90
|
+
/^koed-runtime\/embedding-service\/\.venv(?:\/|$)/,
|
|
91
|
+
/^koed-runtime\/embedding-service\/.*\.py$/,
|
|
92
|
+
/^koed-runtime\/embedding-service\/requirements\.txt$/,
|
|
93
|
+
/^koed-runtime\/embedding-service\/pyproject\.toml$/
|
|
94
|
+
];
|
|
95
|
+
var pnpmWorkspaceSelfSymlinkPattern = /^(?<root>koed-server|koed-runtime\/api|koed-runtime\/worker|koed-runtime\/embedding-service|koed-runtime\/mcp-server)\/node_modules\/\.pnpm\/node_modules\/@koed\/(?<name>[^/]+)$/;
|
|
96
|
+
var packageSelfNames = {
|
|
97
|
+
"koed-server": "koed-server",
|
|
98
|
+
"koed-runtime/api": "api",
|
|
99
|
+
"koed-runtime/worker": "worker",
|
|
100
|
+
"koed-runtime/embedding-service": "embedding-service",
|
|
101
|
+
"koed-runtime/mcp-server": "mcp-server"
|
|
102
|
+
};
|
|
103
|
+
var platformKey = (platform = process.platform) => {
|
|
104
|
+
if (platform === "darwin")
|
|
105
|
+
return "macos";
|
|
106
|
+
if (platform === "win32")
|
|
107
|
+
return "windows";
|
|
108
|
+
return platform;
|
|
109
|
+
};
|
|
110
|
+
var normalizeSha256 = (value) => {
|
|
111
|
+
const normalized = value.trim().toLowerCase().split(/\s+/)[0] ?? "";
|
|
112
|
+
if (!/^[a-f0-9]{64}$/.test(normalized)) {
|
|
113
|
+
throw new Error("Package SHA-256 must be 64 hex characters.");
|
|
114
|
+
}
|
|
115
|
+
return normalized;
|
|
116
|
+
};
|
|
117
|
+
var validatePackageVersion = (version) => {
|
|
118
|
+
if (!/^[0-9A-Za-z][0-9A-Za-z._+-]*$/.test(version)) {
|
|
119
|
+
throw new Error("Package version must be a safe filename segment containing only letters, numbers, dots, underscores, plus signs, or hyphens.");
|
|
120
|
+
}
|
|
121
|
+
if (version === "." || version === ".." || version.includes("..")) {
|
|
122
|
+
throw new Error("Package version must not contain path traversal.");
|
|
123
|
+
}
|
|
124
|
+
return version;
|
|
125
|
+
};
|
|
126
|
+
var isInside = (base, child) => {
|
|
127
|
+
const rel = relative(resolve(base), resolve(child));
|
|
128
|
+
return rel === "" || !rel.startsWith("..") && rel !== "..";
|
|
129
|
+
};
|
|
130
|
+
var safeResolve = (base, path) => {
|
|
131
|
+
if (path.includes("\0")) {
|
|
132
|
+
throw new Error(`Package path contains NUL byte: ${path}`);
|
|
133
|
+
}
|
|
134
|
+
const resolved = resolve(base, path);
|
|
135
|
+
if (!isInside(base, resolved)) {
|
|
136
|
+
throw new Error(`Package path escapes base directory: ${path}`);
|
|
137
|
+
}
|
|
138
|
+
return resolved;
|
|
139
|
+
};
|
|
140
|
+
var packageRoot = (paths) => resolve(paths.koedHome, "runtime", "koed-server");
|
|
141
|
+
var packageVersionsDir = (paths) => resolve(packageRoot(paths), "versions");
|
|
142
|
+
var packageCacheDir = (paths) => resolve(paths.cacheDir, "koed-server-packages");
|
|
143
|
+
var packageCurrentPath = (paths) => resolve(packageRoot(paths), "current");
|
|
144
|
+
var isPnpmWorkspaceSelfSymlink = (path) => {
|
|
145
|
+
const match = path.match(pnpmWorkspaceSelfSymlinkPattern);
|
|
146
|
+
if (!match?.groups)
|
|
147
|
+
return false;
|
|
148
|
+
const { name, root } = match.groups;
|
|
149
|
+
return Boolean(root && name && name === packageSelfNames[root]);
|
|
150
|
+
};
|
|
151
|
+
var listFiles = (root, dir = root) => readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
152
|
+
const path = resolve(dir, entry.name);
|
|
153
|
+
const relativePath = relative(root, path).replaceAll("\\", "/");
|
|
154
|
+
if (isPnpmWorkspaceSelfSymlink(relativePath))
|
|
155
|
+
return [];
|
|
156
|
+
if (entry.isDirectory())
|
|
157
|
+
return listFiles(root, path);
|
|
158
|
+
return [relativePath];
|
|
159
|
+
});
|
|
160
|
+
var sha256File = (path) => {
|
|
161
|
+
const hash = createHash("sha256");
|
|
162
|
+
const stat = lstatSync(path);
|
|
163
|
+
if (stat.isSymbolicLink()) {
|
|
164
|
+
hash.update("symlink");
|
|
165
|
+
hash.update("\0");
|
|
166
|
+
hash.update(readlinkSync(path));
|
|
167
|
+
} else {
|
|
168
|
+
hash.update(readFileSync(path));
|
|
169
|
+
}
|
|
170
|
+
return hash.digest("hex");
|
|
171
|
+
};
|
|
172
|
+
var sha256Files = (root, files) => {
|
|
173
|
+
const hash = createHash("sha256");
|
|
174
|
+
for (const file of [...new Set(files)].sort()) {
|
|
175
|
+
const path = resolve(root, file);
|
|
176
|
+
const stat = lstatSync(path);
|
|
177
|
+
if (stat.isDirectory())
|
|
178
|
+
continue;
|
|
179
|
+
hash.update(file.replaceAll("\\", "/"));
|
|
180
|
+
hash.update("\0");
|
|
181
|
+
if (stat.isSymbolicLink()) {
|
|
182
|
+
hash.update("symlink");
|
|
183
|
+
hash.update("\0");
|
|
184
|
+
hash.update(readlinkSync(path));
|
|
185
|
+
} else {
|
|
186
|
+
hash.update(readFileSync(path));
|
|
187
|
+
}
|
|
188
|
+
hash.update("\0");
|
|
189
|
+
}
|
|
190
|
+
return hash.digest("hex");
|
|
191
|
+
};
|
|
192
|
+
var readJson = (path) => JSON.parse(readFileSync(path, "utf8"));
|
|
193
|
+
var compareIdentifiers = (left, right) => {
|
|
194
|
+
if (left === right)
|
|
195
|
+
return 0;
|
|
196
|
+
const leftNumeric = /^\d+$/.test(left);
|
|
197
|
+
const rightNumeric = /^\d+$/.test(right);
|
|
198
|
+
if (leftNumeric && rightNumeric)
|
|
199
|
+
return BigInt(left) < BigInt(right) ? -1 : BigInt(left) > BigInt(right) ? 1 : 0;
|
|
200
|
+
if (leftNumeric !== rightNumeric)
|
|
201
|
+
return leftNumeric ? -1 : 1;
|
|
202
|
+
return left < right ? -1 : 1;
|
|
203
|
+
};
|
|
204
|
+
var compareVersions = (a, b) => {
|
|
205
|
+
const parts = (version) => {
|
|
206
|
+
const withoutBuild = version.split("+")[0];
|
|
207
|
+
const separator = withoutBuild.indexOf("-");
|
|
208
|
+
return {
|
|
209
|
+
core: (separator < 0 ? withoutBuild : withoutBuild.slice(0, separator)).split("."),
|
|
210
|
+
prerelease: separator < 0 ? [] : withoutBuild.slice(separator + 1).split(".")
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
const left = parts(a), right = parts(b);
|
|
214
|
+
for (let index = 0; index < 3; index++) {
|
|
215
|
+
const order = compareIdentifiers(left.core[index] ?? "0", right.core[index] ?? "0");
|
|
216
|
+
if (order)
|
|
217
|
+
return order;
|
|
218
|
+
}
|
|
219
|
+
if (!left.prerelease.length || !right.prerelease.length)
|
|
220
|
+
return Number(right.prerelease.length > 0) - Number(left.prerelease.length > 0);
|
|
221
|
+
for (let index = 0; index < Math.max(left.prerelease.length, right.prerelease.length); index++) {
|
|
222
|
+
const l = left.prerelease[index], r = right.prerelease[index];
|
|
223
|
+
if (l === void 0)
|
|
224
|
+
return -1;
|
|
225
|
+
if (r === void 0)
|
|
226
|
+
return 1;
|
|
227
|
+
const order = compareIdentifiers(l, r);
|
|
228
|
+
if (order)
|
|
229
|
+
return order;
|
|
230
|
+
}
|
|
231
|
+
return 0;
|
|
232
|
+
};
|
|
233
|
+
var validateManifestShape = (manifest) => {
|
|
234
|
+
const errors = [];
|
|
235
|
+
if (!manifest || typeof manifest !== "object") {
|
|
236
|
+
return ["Package manifest must be an object."];
|
|
237
|
+
}
|
|
238
|
+
if (manifest.schemaVersion !== standalonePackageSchemaVersion) {
|
|
239
|
+
errors.push("Package manifest schemaVersion must be 2 for shared staging; upgrade koed-server or rebuild the package with the current release tooling.");
|
|
240
|
+
}
|
|
241
|
+
if (manifest.id !== standalonePackageId) {
|
|
242
|
+
errors.push("Package manifest id must be koed-server.");
|
|
243
|
+
}
|
|
244
|
+
if (manifest.packageKind !== standalonePackageKind) {
|
|
245
|
+
errors.push("Package manifest packageKind must be app-runtime.");
|
|
246
|
+
}
|
|
247
|
+
for (const key of [
|
|
248
|
+
"version",
|
|
249
|
+
"platform",
|
|
250
|
+
"architecture",
|
|
251
|
+
"createdAt"
|
|
252
|
+
]) {
|
|
253
|
+
const value = manifest[key];
|
|
254
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
255
|
+
errors.push(`Package manifest ${key} must be a non-empty string.`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (typeof manifest.version === "string" && manifest.version.length > 0) {
|
|
259
|
+
try {
|
|
260
|
+
validatePackageVersion(manifest.version);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (manifest.koedRuntime?.path !== "koed-runtime") {
|
|
266
|
+
errors.push("Package manifest koedRuntime.path must be koed-runtime.");
|
|
267
|
+
}
|
|
268
|
+
if (!Array.isArray(manifest.koedRuntime?.requiredFiles)) {
|
|
269
|
+
errors.push("Package manifest koedRuntime.requiredFiles must be an array.");
|
|
270
|
+
}
|
|
271
|
+
if (!Array.isArray(manifest.files)) {
|
|
272
|
+
errors.push("Package manifest files must be an array.");
|
|
273
|
+
}
|
|
274
|
+
if (!/^[a-f0-9]{64}$/.test(manifest.sha256 ?? "")) {
|
|
275
|
+
errors.push("Package manifest sha256 must be 64 hex characters.");
|
|
276
|
+
}
|
|
277
|
+
if (!manifest.database || typeof manifest.database !== "object") {
|
|
278
|
+
errors.push("Package manifest database must be an object.");
|
|
279
|
+
}
|
|
280
|
+
if (!manifest.provenance || typeof manifest.provenance !== "object") {
|
|
281
|
+
errors.push("Package manifest provenance must be an object.");
|
|
282
|
+
}
|
|
283
|
+
return errors;
|
|
284
|
+
};
|
|
285
|
+
var validateCompatibility = (manifest, platform = platformKey(), architecture = process.arch, nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10)) => {
|
|
286
|
+
if (!manifest)
|
|
287
|
+
return [];
|
|
288
|
+
const errors = [];
|
|
289
|
+
if (manifest.platform !== platform) {
|
|
290
|
+
errors.push(`Package platform ${manifest.platform} is incompatible with ${platform}.`);
|
|
291
|
+
}
|
|
292
|
+
if (manifest.architecture !== architecture) {
|
|
293
|
+
errors.push(`Package architecture ${manifest.architecture} is incompatible with ${architecture}.`);
|
|
294
|
+
}
|
|
295
|
+
const minimumNodeMajor = manifest.nodeRuntime?.minimumNodeMajor;
|
|
296
|
+
if (typeof minimumNodeMajor === "number" && Number.isFinite(minimumNodeMajor) && nodeMajor < minimumNodeMajor) {
|
|
297
|
+
errors.push(`Package requires Node ${minimumNodeMajor} or newer; current Node major is ${nodeMajor}.`);
|
|
298
|
+
}
|
|
299
|
+
return errors;
|
|
300
|
+
};
|
|
301
|
+
var validateServerPackageRoot = (root) => {
|
|
302
|
+
const resolvedRoot = resolve(root);
|
|
303
|
+
const manifestPath = resolve(resolvedRoot, "koed-server-package-manifest.json");
|
|
304
|
+
const manifest = existsSync(manifestPath) ? readJson(manifestPath) : null;
|
|
305
|
+
const manifestErrors = validateManifestShape(manifest);
|
|
306
|
+
const runtimePath = manifest?.koedRuntime?.path ?? "koed-runtime";
|
|
307
|
+
const runtimeRoot = resolve(resolvedRoot, runtimePath);
|
|
308
|
+
const requiredFiles = Array.isArray(manifest?.koedRuntime?.requiredFiles) ? manifest.koedRuntime.requiredFiles : requiredPackageRuntimeFiles;
|
|
309
|
+
const missing = [
|
|
310
|
+
!existsSync(manifestPath) ? "koed-server-package-manifest.json" : null,
|
|
311
|
+
!existsSync(resolve(resolvedRoot, "README.txt")) ? "README.txt" : null,
|
|
312
|
+
!existsSync(resolve(resolvedRoot, "bin", "koed")) && !existsSync(resolve(resolvedRoot, "bin", "koed-server")) ? "bin/koed" : null,
|
|
313
|
+
!existsSync(resolve(runtimeRoot, "koed-server", "dist", "cli.js")) ? "koed-runtime/koed-server/dist/cli.js" : null,
|
|
314
|
+
...requiredFiles.map((file) => existsSync(resolve(runtimeRoot, file)) ? null : `${runtimePath}/${file}`)
|
|
315
|
+
].filter((file) => Boolean(file));
|
|
316
|
+
const files = existsSync(resolvedRoot) ? listFiles(resolvedRoot) : [];
|
|
317
|
+
const excluded = files.filter((file) => excludedPackagePatterns.some((pattern) => pattern.test(file)));
|
|
318
|
+
const actualFiles = files.filter((file) => file !== "koed-server-package-manifest.json");
|
|
319
|
+
const actualSha256 = existsSync(resolvedRoot) && actualFiles.length > 0 ? sha256Files(resolvedRoot, actualFiles) : void 0;
|
|
320
|
+
const manifestFileErrors = Array.isArray(manifest?.files) ? manifest.files.flatMap((entry) => {
|
|
321
|
+
if (!entry || typeof entry.path !== "string" || !/^[a-f0-9]{64}$/.test(entry.sha256 ?? "")) {
|
|
322
|
+
return ["Package manifest files entries require path and sha256."];
|
|
323
|
+
}
|
|
324
|
+
const path = safeResolve(resolvedRoot, entry.path);
|
|
325
|
+
if (!existsSync(path))
|
|
326
|
+
return [`Manifest file is missing: ${entry.path}`];
|
|
327
|
+
const actual = sha256File(path);
|
|
328
|
+
return actual === entry.sha256 ? [] : [`Manifest file SHA-256 mismatch: ${entry.path}`];
|
|
329
|
+
}) : [];
|
|
330
|
+
const shaErrors = manifest?.sha256 && actualSha256 && manifest.sha256 !== actualSha256 ? ["Package manifest sha256 does not match package files."] : [];
|
|
331
|
+
const compatibilityErrors = validateCompatibility(manifest);
|
|
332
|
+
const errors = [
|
|
333
|
+
...manifestErrors,
|
|
334
|
+
...missing.map((file) => `Missing required package file: ${file}`),
|
|
335
|
+
...excluded.map((file) => `Excluded file is present: ${file}`),
|
|
336
|
+
...manifestFileErrors,
|
|
337
|
+
...shaErrors,
|
|
338
|
+
...compatibilityErrors
|
|
339
|
+
];
|
|
340
|
+
return {
|
|
341
|
+
ok: errors.length === 0,
|
|
342
|
+
packageRoot: resolvedRoot,
|
|
343
|
+
manifestPath,
|
|
344
|
+
manifest,
|
|
345
|
+
version: manifest?.version,
|
|
346
|
+
errors
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
var summarizeManifest = (manifest) => {
|
|
350
|
+
if (!manifest)
|
|
351
|
+
return null;
|
|
352
|
+
return {
|
|
353
|
+
schemaVersion: manifest.schemaVersion,
|
|
354
|
+
id: manifest.id,
|
|
355
|
+
version: manifest.version,
|
|
356
|
+
platform: manifest.platform,
|
|
357
|
+
architecture: manifest.architecture,
|
|
358
|
+
packageKind: manifest.packageKind,
|
|
359
|
+
createdAt: manifest.createdAt,
|
|
360
|
+
...manifest.minimumDesktopVersion ? { minimumDesktopVersion: manifest.minimumDesktopVersion } : {},
|
|
361
|
+
...manifest.maximumDesktopMajor !== void 0 ? { maximumDesktopMajor: manifest.maximumDesktopMajor } : {},
|
|
362
|
+
...manifest.nodeRuntime ? { nodeRuntime: manifest.nodeRuntime } : {},
|
|
363
|
+
...manifest.koedRuntime ? { koedRuntime: manifest.koedRuntime } : {},
|
|
364
|
+
...manifest.database ? { database: manifest.database } : {},
|
|
365
|
+
...manifest.provenance ? { provenance: manifest.provenance } : {},
|
|
366
|
+
...manifest.sha256 ? { sha256: manifest.sha256 } : {},
|
|
367
|
+
fileCount: manifest.files?.length ?? 0
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
var readCurrentTarget = (paths) => {
|
|
371
|
+
const current = packageCurrentPath(paths);
|
|
372
|
+
if (!existsSync(current))
|
|
373
|
+
return void 0;
|
|
374
|
+
const stat = lstatSync(current);
|
|
375
|
+
return stat.isSymbolicLink() ? resolve(dirname(current), readlinkSync(current)) : current;
|
|
376
|
+
};
|
|
377
|
+
var collectServerPackageStatus = (paths) => {
|
|
378
|
+
const root = packageRoot(paths);
|
|
379
|
+
const versionsDir = packageVersionsDir(paths);
|
|
380
|
+
const cacheDir = packageCacheDir(paths);
|
|
381
|
+
const currentPath = packageCurrentPath(paths);
|
|
382
|
+
const currentTarget = readCurrentTarget(paths);
|
|
383
|
+
const versionNames = existsSync(versionsDir) ? readdirSync(versionsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort() : [];
|
|
384
|
+
const installed = versionNames.map((version) => {
|
|
385
|
+
const path = resolve(versionsDir, version);
|
|
386
|
+
const validation = validateServerPackageRoot(path);
|
|
387
|
+
return {
|
|
388
|
+
version,
|
|
389
|
+
path,
|
|
390
|
+
ok: validation.ok,
|
|
391
|
+
active: currentTarget === path,
|
|
392
|
+
manifest: summarizeManifest(validation.manifest),
|
|
393
|
+
errors: validation.errors
|
|
394
|
+
};
|
|
395
|
+
});
|
|
396
|
+
const active = installed.find((entry) => entry.active);
|
|
397
|
+
const invalid = installed.filter((entry) => !entry.ok);
|
|
398
|
+
const currentVersion = active?.version;
|
|
399
|
+
const state = installed.length === 0 ? "missing" : active && active.ok && invalid.length === 0 ? "installed" : active && !active.ok ? "incompatible" : "partial";
|
|
400
|
+
return {
|
|
401
|
+
ok: state === "installed",
|
|
402
|
+
state,
|
|
403
|
+
koedHome: paths.koedHome,
|
|
404
|
+
packageRoot: root,
|
|
405
|
+
versionsDir,
|
|
406
|
+
cacheDir,
|
|
407
|
+
currentPath,
|
|
408
|
+
...currentVersion ? { currentVersion } : {},
|
|
409
|
+
...currentTarget ? { currentTarget } : {},
|
|
410
|
+
installed,
|
|
411
|
+
message: state === "installed" ? `koed-server package ${currentVersion} is active.` : state === "missing" ? "No koed-server package is installed." : "koed-server package installation needs attention.",
|
|
412
|
+
...state === "missing" ? {
|
|
413
|
+
action: "Run koed package install --source <artifact> --sha256 <sha256>."
|
|
414
|
+
} : {},
|
|
415
|
+
...invalid.length > 0 ? { errors: invalid.flatMap((entry) => entry.errors) } : {}
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
var readExpectedSha256 = (options) => {
|
|
419
|
+
if (options.sha256)
|
|
420
|
+
return normalizeSha256(options.sha256);
|
|
421
|
+
if (options.sha256File) {
|
|
422
|
+
return normalizeSha256(readFileSync(options.sha256File, "utf8"));
|
|
423
|
+
}
|
|
424
|
+
throw new Error("--sha256 or --sha256-file is required.");
|
|
425
|
+
};
|
|
426
|
+
var extractVerifiedPackageArchive = (archivePath, destination) => extractTarGz(archivePath, destination);
|
|
427
|
+
var adjacentProvenancePath = (archivePath) => {
|
|
428
|
+
const archiveName = basename(archivePath);
|
|
429
|
+
const releaseName = archiveName.replace(/^koed-server-/, "koed-server-app-runtime-").replace(/\.tar\.gz$/, ".provenance.json");
|
|
430
|
+
const candidates = [
|
|
431
|
+
`${archivePath}.provenance.json`,
|
|
432
|
+
archivePath.replace(/\.tar\.gz$/, ".provenance.json"),
|
|
433
|
+
resolve(dirname(archivePath), releaseName)
|
|
434
|
+
];
|
|
435
|
+
return candidates.find((candidate) => existsSync(candidate));
|
|
436
|
+
};
|
|
437
|
+
var readTrustedPublicKey = (options) => {
|
|
438
|
+
if (options.trustedPublicKey)
|
|
439
|
+
return options.trustedPublicKey;
|
|
440
|
+
if (options.trustedPublicKeyFile) {
|
|
441
|
+
return readFileSync(options.trustedPublicKeyFile, "utf8");
|
|
442
|
+
}
|
|
443
|
+
return process.env.KOED_SERVER_PACKAGE_TRUSTED_PUBLIC_KEY_PEM;
|
|
444
|
+
};
|
|
445
|
+
var verifyProvenanceSignature = ({ provenance, signature, publicKey }) => {
|
|
446
|
+
if (provenance.signature?.algorithm !== "ed25519") {
|
|
447
|
+
throw new Error("Package provenance signature algorithm must be ed25519.");
|
|
448
|
+
}
|
|
449
|
+
const key = createPublicKey(publicKey);
|
|
450
|
+
const payload = canonicalArtifactBytes(provenance.statement);
|
|
451
|
+
const ok = verify(null, payload, key, Buffer.from(signature, "base64"));
|
|
452
|
+
if (!ok) {
|
|
453
|
+
throw new Error("Package provenance signature verification failed.");
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
var validatePackageProvenance = ({ options, archivePath, archiveSha256, manifest }) => {
|
|
457
|
+
const policy = options.trustPolicy ?? "sha256-only";
|
|
458
|
+
const provenancePath = options.provenanceFile ?? adjacentProvenancePath(archivePath);
|
|
459
|
+
const publicKey = readTrustedPublicKey(options);
|
|
460
|
+
if (!provenancePath) {
|
|
461
|
+
if (policy === "sha256-only" && !publicKey) {
|
|
462
|
+
return { status: "not_checked", policy };
|
|
463
|
+
}
|
|
464
|
+
throw new Error("Package provenance metadata is required.");
|
|
465
|
+
}
|
|
466
|
+
const provenance = readJson(provenancePath);
|
|
467
|
+
const statement = provenance.statement;
|
|
468
|
+
if (statement?.schemaVersion !== packageProvenanceSchemaVersion) {
|
|
469
|
+
throw new Error("Package provenance schemaVersion must be 1.");
|
|
470
|
+
}
|
|
471
|
+
const subject = statement.subject;
|
|
472
|
+
if (subject?.id !== manifest.id || subject.packageKind !== manifest.packageKind) {
|
|
473
|
+
throw new Error("Package provenance subject does not match manifest.");
|
|
474
|
+
}
|
|
475
|
+
if (subject.version !== manifest.version || subject.platform !== manifest.platform || subject.architecture !== manifest.architecture) {
|
|
476
|
+
throw new Error("Package provenance version target does not match manifest.");
|
|
477
|
+
}
|
|
478
|
+
if (subject.archiveSha256 !== archiveSha256) {
|
|
479
|
+
throw new Error("Package provenance archive SHA-256 does not match.");
|
|
480
|
+
}
|
|
481
|
+
if (subject.packageSha256 !== manifest.sha256) {
|
|
482
|
+
throw new Error("Package provenance package SHA-256 does not match.");
|
|
483
|
+
}
|
|
484
|
+
const inlineSignature = provenance.signature?.value ?? void 0;
|
|
485
|
+
const signaturePath = options.signatureFile ?? (existsSync(`${provenancePath}.sig`) ? `${provenancePath}.sig` : void 0);
|
|
486
|
+
const signature = signaturePath ? readFileSync(signaturePath, "utf8").trim() : inlineSignature;
|
|
487
|
+
if (publicKey) {
|
|
488
|
+
if (!signature) {
|
|
489
|
+
throw new Error("Package provenance signature is required.");
|
|
490
|
+
}
|
|
491
|
+
verifyProvenanceSignature({ provenance, signature, publicKey });
|
|
492
|
+
return { status: "verified", policy, source: provenancePath };
|
|
493
|
+
}
|
|
494
|
+
if (policy === "require-signature") {
|
|
495
|
+
throw new Error("Package signature trust root is required.");
|
|
496
|
+
}
|
|
497
|
+
if (provenance.signature?.status === "unsigned-placeholder") {
|
|
498
|
+
return { status: "unsigned-placeholder", policy, source: provenancePath };
|
|
499
|
+
}
|
|
500
|
+
return { status: "not_checked", policy, source: provenancePath };
|
|
501
|
+
};
|
|
502
|
+
var activePackageManifest = (paths) => {
|
|
503
|
+
const target = readCurrentTarget(paths);
|
|
504
|
+
if (!target)
|
|
505
|
+
return null;
|
|
506
|
+
const validation = validateServerPackageRoot(target);
|
|
507
|
+
return validation.ok ? validation.manifest : null;
|
|
508
|
+
};
|
|
509
|
+
var migrationTimestamp = (manifest) => {
|
|
510
|
+
const value = manifest?.database?.migrationSet?.latestMigrationTimestamp;
|
|
511
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
512
|
+
};
|
|
513
|
+
var isVersionDowngrade = (currentVersion, candidateVersion) => compareVersions(candidateVersion, currentVersion) < 0;
|
|
514
|
+
var assertPackageMigrationCompatible = (currentManifest, nextManifest, allowDowngrade = false) => {
|
|
515
|
+
if (!isVersionDowngrade(currentManifest.version, nextManifest.version))
|
|
516
|
+
return;
|
|
517
|
+
if (!allowDowngrade) {
|
|
518
|
+
throw new Error(`Installing koed-server ${nextManifest.version} over active ${currentManifest.version} is a downgrade and requires --allow-downgrade.`);
|
|
519
|
+
}
|
|
520
|
+
const currentMigration = migrationTimestamp(currentManifest);
|
|
521
|
+
const nextMigration = migrationTimestamp(nextManifest);
|
|
522
|
+
if (currentMigration !== void 0 && nextMigration !== void 0 && nextMigration < currentMigration && nextManifest.database?.allowsRollback !== true) {
|
|
523
|
+
throw new Error("Downgrade would roll back the package migration set, and the target package does not allow rollback.");
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
var assertUpgradeCompatible = ({ paths, nextManifest, allowDowngrade = false }) => {
|
|
527
|
+
const currentManifest = activePackageManifest(paths);
|
|
528
|
+
if (!currentManifest?.version)
|
|
529
|
+
return;
|
|
530
|
+
assertPackageMigrationCompatible(currentManifest, nextManifest, allowDowngrade);
|
|
531
|
+
};
|
|
532
|
+
var copyAdjacentProvenanceSidecars = (sourcePath, targetPath) => {
|
|
533
|
+
const provenancePath = adjacentProvenancePath(sourcePath);
|
|
534
|
+
if (!provenancePath)
|
|
535
|
+
return;
|
|
536
|
+
const targetProvenancePath = resolve(dirname(targetPath), basename(provenancePath));
|
|
537
|
+
if (provenancePath !== targetProvenancePath) {
|
|
538
|
+
cpSync(provenancePath, targetProvenancePath);
|
|
539
|
+
}
|
|
540
|
+
const signaturePath = `${provenancePath}.sig`;
|
|
541
|
+
if (!existsSync(signaturePath))
|
|
542
|
+
return;
|
|
543
|
+
const targetSignaturePath = `${targetProvenancePath}.sig`;
|
|
544
|
+
if (signaturePath !== targetSignaturePath) {
|
|
545
|
+
cpSync(signaturePath, targetSignaturePath);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
var copyOrDownloadArchive = async (source, cacheDir) => {
|
|
549
|
+
mkdirSync(cacheDir, { recursive: true, mode: 448 });
|
|
550
|
+
if (/^https?:\/\//i.test(source)) {
|
|
551
|
+
const response = await fetch(source);
|
|
552
|
+
if (!response.ok) {
|
|
553
|
+
throw new Error(`Package download failed with HTTP ${response.status}.`);
|
|
554
|
+
}
|
|
555
|
+
if (!response.body)
|
|
556
|
+
throw new Error("Package download returned no body.");
|
|
557
|
+
const target2 = resolve(cacheDir, basename(new URL(source).pathname));
|
|
558
|
+
const temporaryTarget = `${target2}.download-${process.pid}-${Date.now()}`;
|
|
559
|
+
let received = 0;
|
|
560
|
+
const limiter = new Transform({
|
|
561
|
+
transform(chunk, _encoding, callback) {
|
|
562
|
+
received += chunk.length;
|
|
563
|
+
callback(received > packageExtractionLimits.compressedBytes ? new Error("Package archive exceeds the compressed byte limit.") : null, chunk);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
try {
|
|
567
|
+
await pipeline(Readable.fromWeb(response.body), limiter, createWriteStream(temporaryTarget, { flags: "wx", mode: 384 }));
|
|
568
|
+
renameSync(temporaryTarget, target2);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
rmSync(temporaryTarget, { force: true });
|
|
571
|
+
throw error;
|
|
572
|
+
}
|
|
573
|
+
return target2;
|
|
574
|
+
}
|
|
575
|
+
const sourcePath = resolve(source);
|
|
576
|
+
const target = resolve(cacheDir, basename(sourcePath));
|
|
577
|
+
if (sourcePath !== target) {
|
|
578
|
+
cpSync(sourcePath, target);
|
|
579
|
+
}
|
|
580
|
+
copyAdjacentProvenanceSidecars(sourcePath, target);
|
|
581
|
+
return target;
|
|
582
|
+
};
|
|
583
|
+
var tarString = (buffer, start, length) => buffer.subarray(start, start + length).toString("utf8").replace(/\0.*$/s, "");
|
|
584
|
+
var tarNumber = (buffer, start, length) => {
|
|
585
|
+
const value = tarString(buffer, start, length).trim();
|
|
586
|
+
if (!value)
|
|
587
|
+
return 0;
|
|
588
|
+
if (!/^[0-7]+$/.test(value))
|
|
589
|
+
throw new Error("Malformed tar numeric field.");
|
|
590
|
+
const result = Number.parseInt(value, 8);
|
|
591
|
+
if (!Number.isSafeInteger(result))
|
|
592
|
+
throw new Error("Oversized tar numeric field.");
|
|
593
|
+
return result;
|
|
594
|
+
};
|
|
595
|
+
var parsePax = (content) => {
|
|
596
|
+
const result = {};
|
|
597
|
+
let offset = 0;
|
|
598
|
+
while (offset < content.length) {
|
|
599
|
+
const space = content.indexOf(32, offset);
|
|
600
|
+
if (space < 0)
|
|
601
|
+
throw new Error("Malformed pax header.");
|
|
602
|
+
const lengthText = content.subarray(offset, space).toString("ascii");
|
|
603
|
+
if (!/^\d+$/.test(lengthText))
|
|
604
|
+
throw new Error("Malformed pax header length.");
|
|
605
|
+
const length = Number.parseInt(lengthText, 10);
|
|
606
|
+
if (length <= space - offset + 1 || offset + length > content.length) {
|
|
607
|
+
throw new Error("Truncated pax header.");
|
|
608
|
+
}
|
|
609
|
+
const record = content.subarray(space + 1, offset + length - 1).toString("utf8");
|
|
610
|
+
const equals = record.indexOf("=");
|
|
611
|
+
if (equals <= 0)
|
|
612
|
+
throw new Error("Malformed pax header record.");
|
|
613
|
+
result[record.slice(0, equals)] = record.slice(equals + 1);
|
|
614
|
+
offset += length;
|
|
615
|
+
}
|
|
616
|
+
return result;
|
|
617
|
+
};
|
|
618
|
+
var assertSafeParents = (destination, target) => {
|
|
619
|
+
let current = dirname(target);
|
|
620
|
+
while (current !== destination) {
|
|
621
|
+
if (existsSync(current) && lstatSync(current).isSymbolicLink()) {
|
|
622
|
+
throw new Error("Package archive would write through a symlinked parent.");
|
|
623
|
+
}
|
|
624
|
+
const next = dirname(current);
|
|
625
|
+
if (next === current)
|
|
626
|
+
throw new Error("Package archive path escaped destination.");
|
|
627
|
+
current = next;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
var extractTarGz = async (archivePath, destination) => {
|
|
631
|
+
if (statSync(archivePath).size > packageExtractionLimits.compressedBytes) {
|
|
632
|
+
throw new Error("Package archive exceeds the compressed byte limit.");
|
|
633
|
+
}
|
|
634
|
+
mkdirSync(destination, { recursive: true, mode: 448 });
|
|
635
|
+
const stream = createReadStream(archivePath).pipe(createGunzip());
|
|
636
|
+
let buffer = Buffer.alloc(0);
|
|
637
|
+
let pax = {};
|
|
638
|
+
const seen = /* @__PURE__ */ new Set();
|
|
639
|
+
let expandedBytes = 0;
|
|
640
|
+
let fileCount = 0;
|
|
641
|
+
let current;
|
|
642
|
+
let ended = false;
|
|
643
|
+
try {
|
|
644
|
+
for await (const chunk of stream) {
|
|
645
|
+
const bytes = Buffer.from(chunk);
|
|
646
|
+
buffer = buffer.length === 0 ? bytes : Buffer.concat([buffer, bytes]);
|
|
647
|
+
while (true) {
|
|
648
|
+
if (current) {
|
|
649
|
+
if (current.remaining > 0 && buffer.length > 0) {
|
|
650
|
+
const length = Math.min(current.remaining, buffer.length);
|
|
651
|
+
const part = buffer.subarray(0, length);
|
|
652
|
+
buffer = buffer.subarray(length);
|
|
653
|
+
current.remaining -= length;
|
|
654
|
+
if (current.fd !== void 0)
|
|
655
|
+
writeSync(current.fd, part);
|
|
656
|
+
else
|
|
657
|
+
current.chunks?.push(Buffer.from(part));
|
|
658
|
+
}
|
|
659
|
+
if (current.remaining > 0)
|
|
660
|
+
break;
|
|
661
|
+
if (current.fd !== void 0) {
|
|
662
|
+
closeSync(current.fd);
|
|
663
|
+
current.fd = void 0;
|
|
664
|
+
if (current.path && current.mode !== void 0) {
|
|
665
|
+
chmodSync(current.path, current.mode);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (buffer.length < current.padding)
|
|
669
|
+
break;
|
|
670
|
+
buffer = buffer.subarray(current.padding);
|
|
671
|
+
if (current.type === "x")
|
|
672
|
+
pax = parsePax(Buffer.concat(current.chunks ?? []));
|
|
673
|
+
current = void 0;
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (buffer.length < 512)
|
|
677
|
+
break;
|
|
678
|
+
const header = buffer.subarray(0, 512);
|
|
679
|
+
buffer = buffer.subarray(512);
|
|
680
|
+
if (header.every((byte) => byte === 0)) {
|
|
681
|
+
ended = true;
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
const storedChecksum = tarNumber(header, 148, 8);
|
|
685
|
+
const checksumHeader = Buffer.from(header);
|
|
686
|
+
checksumHeader.fill(32, 148, 156);
|
|
687
|
+
const actualChecksum = checksumHeader.reduce((sum, byte) => sum + byte, 0);
|
|
688
|
+
if (storedChecksum !== actualChecksum)
|
|
689
|
+
throw new Error("Malformed tar header checksum.");
|
|
690
|
+
const type = tarString(header, 156, 1) || "0";
|
|
691
|
+
const size = tarNumber(header, 124, 12);
|
|
692
|
+
if (size > packageExtractionLimits.individualFileBytes) {
|
|
693
|
+
throw new Error("Package archive entry exceeds the individual file limit.");
|
|
694
|
+
}
|
|
695
|
+
expandedBytes += size;
|
|
696
|
+
if (expandedBytes > packageExtractionLimits.expandedBytes) {
|
|
697
|
+
throw new Error("Package archive exceeds the expanded byte limit.");
|
|
698
|
+
}
|
|
699
|
+
const padding = (512 - size % 512) % 512;
|
|
700
|
+
if (type === "x") {
|
|
701
|
+
if (size > packageExtractionLimits.paxHeaderBytes) {
|
|
702
|
+
throw new Error("Pax header exceeds limit.");
|
|
703
|
+
}
|
|
704
|
+
current = { type, remaining: size, padding, chunks: [] };
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const name = tarString(header, 0, 100);
|
|
708
|
+
const prefix = tarString(header, 345, 155);
|
|
709
|
+
const rawPath = pax.path ?? (prefix ? `${prefix}/${name}` : name);
|
|
710
|
+
pax = {};
|
|
711
|
+
if (Buffer.byteLength(rawPath) > packageExtractionLimits.pathBytes) {
|
|
712
|
+
throw new Error("Package archive path exceeds the path length limit.");
|
|
713
|
+
}
|
|
714
|
+
const target = safeResolve(destination, rawPath);
|
|
715
|
+
const normalized = relative(destination, target).replaceAll("\\", "/");
|
|
716
|
+
if (seen.has(normalized))
|
|
717
|
+
throw new Error(`Duplicate package archive path: ${normalized}`);
|
|
718
|
+
seen.add(normalized);
|
|
719
|
+
if (type === "5") {
|
|
720
|
+
if (size !== 0)
|
|
721
|
+
throw new Error("Directory tar entry must be empty.");
|
|
722
|
+
assertSafeParents(destination, target);
|
|
723
|
+
mkdirSync(target, { recursive: true, mode: 493 });
|
|
724
|
+
} else if (type === "0" || type === "") {
|
|
725
|
+
fileCount += 1;
|
|
726
|
+
if (fileCount > packageExtractionLimits.fileCount)
|
|
727
|
+
throw new Error("Package archive exceeds the file count limit.");
|
|
728
|
+
if (basename(target) === "koed-server-package-manifest.json" && size > packageExtractionLimits.manifestBytes) {
|
|
729
|
+
throw new Error("Package manifest exceeds the manifest size limit.");
|
|
730
|
+
}
|
|
731
|
+
mkdirSync(dirname(target), { recursive: true, mode: 493 });
|
|
732
|
+
assertSafeParents(destination, target);
|
|
733
|
+
const mode = tarNumber(header, 100, 8) & 73 ? 493 : 420;
|
|
734
|
+
const flags = fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY | (fsConstants.O_NOFOLLOW ?? 0);
|
|
735
|
+
const fd = openSync(target, flags, mode);
|
|
736
|
+
current = { type, remaining: size, padding, fd, path: target, mode };
|
|
737
|
+
continue;
|
|
738
|
+
} else if (type === "1") {
|
|
739
|
+
throw new Error("Package archives must not contain hard links.");
|
|
740
|
+
} else if (type === "2") {
|
|
741
|
+
throw new Error("Package archives must not contain symbolic links.");
|
|
742
|
+
} else {
|
|
743
|
+
throw new Error(`Package archives contain unsupported tar entry type: ${type}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (ended)
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
} finally {
|
|
750
|
+
if (current?.fd !== void 0)
|
|
751
|
+
closeSync(current.fd);
|
|
752
|
+
}
|
|
753
|
+
if (current || !ended)
|
|
754
|
+
throw new Error("Package archive is truncated.");
|
|
755
|
+
};
|
|
756
|
+
var findExtractedPackageRoot = (extractDir) => {
|
|
757
|
+
const entries = readdirSync(extractDir, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
758
|
+
if (entries.length !== 1) {
|
|
759
|
+
throw new Error("Package archive must contain exactly one root directory.");
|
|
760
|
+
}
|
|
761
|
+
return resolve(extractDir, entries[0].name);
|
|
762
|
+
};
|
|
763
|
+
var activateVersion = (paths, version, options = {}) => {
|
|
764
|
+
const versionsDir = packageVersionsDir(paths);
|
|
765
|
+
const safeVersion = validatePackageVersion(version);
|
|
766
|
+
const target = safeResolve(versionsDir, safeVersion);
|
|
767
|
+
const validation = validateServerPackageRoot(target);
|
|
768
|
+
if (!validation.ok) {
|
|
769
|
+
throw new Error(validation.errors.join(" "));
|
|
770
|
+
}
|
|
771
|
+
if (!validation.manifest) {
|
|
772
|
+
throw new Error("Package manifest is required.");
|
|
773
|
+
}
|
|
774
|
+
assertUpgradeCompatible({
|
|
775
|
+
paths,
|
|
776
|
+
nextManifest: validation.manifest,
|
|
777
|
+
allowDowngrade: options.allowDowngrade
|
|
778
|
+
});
|
|
779
|
+
const current = packageCurrentPath(paths);
|
|
780
|
+
mkdirSync(dirname(current), { recursive: true, mode: 448 });
|
|
781
|
+
const tempLink = resolve(dirname(current), `.current-${process.pid}-${Date.now()}`);
|
|
782
|
+
rmSync(tempLink, { force: true, recursive: true });
|
|
783
|
+
symlinkSync(relative(dirname(current), target), tempLink, "dir");
|
|
784
|
+
renameSync(tempLink, current);
|
|
785
|
+
const status = collectServerPackageStatus(paths);
|
|
786
|
+
return {
|
|
787
|
+
...status,
|
|
788
|
+
ok: true,
|
|
789
|
+
state: "activated",
|
|
790
|
+
activatedVersion: safeVersion,
|
|
791
|
+
message: `koed-server package ${safeVersion} activated.`
|
|
792
|
+
};
|
|
793
|
+
};
|
|
794
|
+
var installServerPackage = async (paths, options) => {
|
|
795
|
+
const expectedSha256 = readExpectedSha256(options);
|
|
796
|
+
const cacheDir = packageCacheDir(paths);
|
|
797
|
+
const archivePath = await copyOrDownloadArchive(options.source, cacheDir);
|
|
798
|
+
const actualSha256 = sha256File(archivePath);
|
|
799
|
+
if (actualSha256 !== expectedSha256) {
|
|
800
|
+
throw new Error("Package archive SHA-256 mismatch.");
|
|
801
|
+
}
|
|
802
|
+
const root = packageRoot(paths);
|
|
803
|
+
const versionsDir = packageVersionsDir(paths);
|
|
804
|
+
mkdirSync(versionsDir, { recursive: true, mode: 448 });
|
|
805
|
+
const extractDir = resolve(root, `.install-${process.pid}-${Date.now()}`);
|
|
806
|
+
rmSync(extractDir, { recursive: true, force: true });
|
|
807
|
+
try {
|
|
808
|
+
await extractTarGz(archivePath, extractDir);
|
|
809
|
+
const extractedRoot = findExtractedPackageRoot(extractDir);
|
|
810
|
+
const validation = validateServerPackageRoot(extractedRoot);
|
|
811
|
+
if (!validation.ok || !validation.version) {
|
|
812
|
+
throw new Error(validation.errors.join(" "));
|
|
813
|
+
}
|
|
814
|
+
if (!validation.manifest) {
|
|
815
|
+
throw new Error("Package manifest is required.");
|
|
816
|
+
}
|
|
817
|
+
assertUpgradeCompatible({
|
|
818
|
+
paths,
|
|
819
|
+
nextManifest: validation.manifest,
|
|
820
|
+
allowDowngrade: options.allowDowngrade
|
|
821
|
+
});
|
|
822
|
+
const provenanceValidation = validatePackageProvenance({
|
|
823
|
+
options,
|
|
824
|
+
archivePath,
|
|
825
|
+
archiveSha256: actualSha256,
|
|
826
|
+
manifest: validation.manifest
|
|
827
|
+
});
|
|
828
|
+
const safeVersion = validatePackageVersion(validation.version);
|
|
829
|
+
const target = safeResolve(versionsDir, safeVersion);
|
|
830
|
+
const tempTarget = resolve(versionsDir, `.${safeVersion}-${process.pid}-${Date.now()}`);
|
|
831
|
+
rmSync(tempTarget, { recursive: true, force: true });
|
|
832
|
+
renameSync(extractedRoot, tempTarget);
|
|
833
|
+
rmSync(target, { recursive: true, force: true });
|
|
834
|
+
renameSync(tempTarget, target);
|
|
835
|
+
rmSync(extractDir, { recursive: true, force: true });
|
|
836
|
+
const status = options.activate ? activateVersion(paths, validation.version, {
|
|
837
|
+
allowDowngrade: options.allowDowngrade
|
|
838
|
+
}) : collectServerPackageStatus(paths);
|
|
839
|
+
return {
|
|
840
|
+
...status,
|
|
841
|
+
ok: true,
|
|
842
|
+
state: options.activate ? "activated" : "installed",
|
|
843
|
+
archivePath,
|
|
844
|
+
archiveSha256: actualSha256,
|
|
845
|
+
installedPath: target,
|
|
846
|
+
provenance: provenanceValidation,
|
|
847
|
+
message: options.activate ? `koed-server package ${safeVersion} installed and activated.` : `koed-server package ${safeVersion} installed.`
|
|
848
|
+
};
|
|
849
|
+
} catch (error) {
|
|
850
|
+
rmSync(extractDir, { recursive: true, force: true });
|
|
851
|
+
throw error;
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
var activateServerPackage = (paths, version, options = {}) => activateVersion(paths, version, options);
|
|
855
|
+
var compareVersionsDesc = (a, b) => {
|
|
856
|
+
const left = a.split(".").map((part) => Number.parseInt(part, 10));
|
|
857
|
+
const right = b.split(".").map((part) => Number.parseInt(part, 10));
|
|
858
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
859
|
+
const diff = (right[index] ?? 0) - (left[index] ?? 0);
|
|
860
|
+
if (diff !== 0)
|
|
861
|
+
return diff;
|
|
862
|
+
}
|
|
863
|
+
return b.localeCompare(a);
|
|
864
|
+
};
|
|
865
|
+
var cleanupServerPackages = (paths, keep = 1) => {
|
|
866
|
+
if (!Number.isInteger(keep) || keep < 0) {
|
|
867
|
+
throw new Error("--keep must be a non-negative integer.");
|
|
868
|
+
}
|
|
869
|
+
const status = collectServerPackageStatus(paths);
|
|
870
|
+
const activeVersion = status.currentVersion;
|
|
871
|
+
const inactive = status.installed.filter((entry) => entry.version !== activeVersion).map((entry) => entry.version).sort(compareVersionsDesc);
|
|
872
|
+
const retained = new Set(inactive.slice(0, keep));
|
|
873
|
+
const removedVersions = [];
|
|
874
|
+
for (const version of inactive) {
|
|
875
|
+
if (retained.has(version))
|
|
876
|
+
continue;
|
|
877
|
+
rmSync(safeResolve(packageVersionsDir(paths), validatePackageVersion(version)), {
|
|
878
|
+
recursive: true,
|
|
879
|
+
force: true
|
|
880
|
+
});
|
|
881
|
+
removedVersions.push(version);
|
|
882
|
+
}
|
|
883
|
+
const removedCacheEntries = [];
|
|
884
|
+
const cacheDir = packageCacheDir(paths);
|
|
885
|
+
if (existsSync(cacheDir)) {
|
|
886
|
+
for (const entry of readdirSync(cacheDir)) {
|
|
887
|
+
const path = resolve(cacheDir, entry);
|
|
888
|
+
const keepCache = activeVersion && entry.includes(activeVersion) ? true : [...retained].some((version) => entry.includes(version));
|
|
889
|
+
if (!keepCache) {
|
|
890
|
+
rmSync(path, { recursive: true, force: true });
|
|
891
|
+
removedCacheEntries.push(path);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const next = collectServerPackageStatus(paths);
|
|
896
|
+
return {
|
|
897
|
+
...next,
|
|
898
|
+
ok: true,
|
|
899
|
+
state: "cleaned",
|
|
900
|
+
removedVersions,
|
|
901
|
+
removedCacheEntries,
|
|
902
|
+
message: `Removed ${removedVersions.length} inactive package version(s) and ${removedCacheEntries.length} cached package artifact(s).`
|
|
903
|
+
};
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
export {
|
|
907
|
+
canonicalArtifactJson,
|
|
908
|
+
canonicalArtifactBytes,
|
|
909
|
+
standalonePackageSchemaVersion,
|
|
910
|
+
standalonePackageId,
|
|
911
|
+
standalonePackageKind,
|
|
912
|
+
packageProvenanceSchemaVersion,
|
|
913
|
+
packageExtractionLimits,
|
|
914
|
+
requiredPackageRuntimeFiles,
|
|
915
|
+
sha256File,
|
|
916
|
+
validateServerPackageRoot,
|
|
917
|
+
collectServerPackageStatus,
|
|
918
|
+
extractVerifiedPackageArchive,
|
|
919
|
+
isVersionDowngrade,
|
|
920
|
+
assertPackageMigrationCompatible,
|
|
921
|
+
installServerPackage,
|
|
922
|
+
activateServerPackage,
|
|
923
|
+
cleanupServerPackages
|
|
924
|
+
};
|