@dinglebear/labby 2.5.0 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/platform.js +5 -0
- package/package.json +1 -1
- package/scripts/install.js +84 -3
package/lib/platform.js
CHANGED
|
@@ -54,6 +54,10 @@ function installRoot() {
|
|
|
54
54
|
return path.resolve(__dirname, "..", "vendor");
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function companionRoot() {
|
|
58
|
+
return path.join(installRoot(), "tailcat");
|
|
59
|
+
}
|
|
60
|
+
|
|
57
61
|
function binaryPath(platform = process.platform, arch = process.arch) {
|
|
58
62
|
const target = targetFor(platform, arch);
|
|
59
63
|
return path.join(installRoot(), target.binary);
|
|
@@ -61,6 +65,7 @@ function binaryPath(platform = process.platform, arch = process.arch) {
|
|
|
61
65
|
|
|
62
66
|
module.exports = {
|
|
63
67
|
binaryPath,
|
|
68
|
+
companionRoot,
|
|
64
69
|
downloadUrl,
|
|
65
70
|
installRoot,
|
|
66
71
|
packageVersion,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dinglebear/labby",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"description": "Rust MCP gateway with Code Mode, authentication, setup, logs, CLI, HTTP API, and operator web UI.",
|
|
6
6
|
"homepage": "https://github.com/dinglebear-ai/labby#readme",
|
package/scripts/install.js
CHANGED
|
@@ -158,7 +158,8 @@ async function main() {
|
|
|
158
158
|
const target = targetFor();
|
|
159
159
|
const destination = binaryPath();
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
recoverInstallation(installRoot());
|
|
162
|
+
if (installationValid(installRoot(), target, releaseVersion())) {
|
|
162
163
|
log(`${path.basename(destination)} already installed for ${releaseVersion()}`);
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
@@ -171,14 +172,90 @@ async function main() {
|
|
|
171
172
|
log(`downloading ${url}`);
|
|
172
173
|
await download(url, archive);
|
|
173
174
|
await verifyChecksum(url, archive);
|
|
174
|
-
|
|
175
|
-
fs.chmodSync(destination, 0o755);
|
|
175
|
+
installArchive(archive, installRoot(), target, releaseVersion());
|
|
176
176
|
log(`installed ${destination}`);
|
|
177
177
|
} finally {
|
|
178
178
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
function requiresCompanions(version) {
|
|
183
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
|
184
|
+
if (!match) throw new Error("invalid release version");
|
|
185
|
+
// Published v2.5.0 archives contain only the binary. Later releases require the bundle.
|
|
186
|
+
const [, major, minor, patch] = match.map(Number);
|
|
187
|
+
return major > 2 || (major === 2 && (minor > 5 || (minor === 5 && patch > 0)));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function validateInstallation(root, target, version) {
|
|
191
|
+
const binary = path.join(root, target.binary);
|
|
192
|
+
if (!fs.lstatSync(binary).isFile()) throw new Error("release binary is not regular");
|
|
193
|
+
const companions = path.join(root, "tailcat");
|
|
194
|
+
if (!fs.existsSync(companions)) {
|
|
195
|
+
if (requiresCompanions(version)) throw new Error("release companions missing");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (!fs.lstatSync(companions).isDirectory()) throw new Error("invalid companion directory");
|
|
199
|
+
const manifestPath = path.join(companions, "manifest.json");
|
|
200
|
+
if (!fs.lstatSync(manifestPath).isFile() || fs.statSync(manifestPath).size > 2 * 1024 * 1024) throw new Error("invalid companion manifest");
|
|
201
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
202
|
+
if (manifest.schemaVersion !== 1 || manifest.protocol !== 1 || manifest.version !== version.replace(/^v/, "") || manifest.target !== target.asset.replace(/^lab-/, "").replace(/\.tar\.gz$/, "") || !Array.isArray(manifest.components) || manifest.components.length > 8192) throw new Error("companion release mismatch");
|
|
203
|
+
const expected = new Set();
|
|
204
|
+
let total = 0;
|
|
205
|
+
for (const entry of manifest.components) {
|
|
206
|
+
if (typeof entry.path !== "string" || !entry.path || entry.path.includes("\\") || entry.path.split("/").some(part => !part || part === "." || part === "..") || path.isAbsolute(entry.path) || expected.has(entry.path) || !/^[a-f0-9]{64}$/.test(entry.sha256)) throw new Error("invalid companion entry");
|
|
207
|
+
expected.add(entry.path);
|
|
208
|
+
let file = companions;
|
|
209
|
+
for (const part of entry.path.split("/")) {
|
|
210
|
+
file = path.join(file, part);
|
|
211
|
+
if (fs.lstatSync(file).isSymbolicLink()) throw new Error("symlink companion");
|
|
212
|
+
}
|
|
213
|
+
const stat = fs.statSync(file);
|
|
214
|
+
total += stat.size;
|
|
215
|
+
if (!stat.isFile() || stat.size > 128 * 1024 * 1024 || total > 512 * 1024 * 1024 || crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex") !== entry.sha256) throw new Error("companion checksum mismatch");
|
|
216
|
+
}
|
|
217
|
+
let count = 0;
|
|
218
|
+
function inventory(directory, prefix = "", depth = 0) {
|
|
219
|
+
if (depth > 64) throw new Error("companion depth exceeded");
|
|
220
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
221
|
+
if (++count > 16384) throw new Error("companion inventory exceeded");
|
|
222
|
+
const relative = prefix + entry.name;
|
|
223
|
+
if (entry.isDirectory()) inventory(path.join(directory, entry.name), relative + "/", depth + 1);
|
|
224
|
+
else if (!entry.isFile() || (relative !== "manifest.json" && !expected.has(relative))) throw new Error("unlisted companion");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
inventory(companions);
|
|
228
|
+
for (const required of ["native/tailcat-bridge", "browser/hook.mjs", "browser/transport.mjs", "browser/http.mjs", "browser/wasm.mjs", "browser/tailcat.wasm", "browser/tailcat.wasm.gz", "browser/wasm_exec.js", "browser/THIRD_PARTY_NOTICES.md", "adapter/server.mjs", "adapter/cleanup.mjs", "adapter/package.json", "adapter/package-lock.json"]) {
|
|
229
|
+
if (!expected.has(required)) throw new Error("incomplete companion inventory");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function installationValid(root, target, version) {
|
|
234
|
+
try { validateInstallation(root, target, version); return true; } catch { return false; }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function recoverInstallation(root) {
|
|
238
|
+
const backup = root + ".previous";
|
|
239
|
+
if (!fs.existsSync(root) && fs.existsSync(backup)) fs.renameSync(backup, root);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function installArchive(archive, root, target, version) {
|
|
243
|
+
fs.mkdirSync(path.dirname(root), { recursive: true });
|
|
244
|
+
const stage = fs.mkdtempSync(path.join(path.dirname(root), ".labby-install-"));
|
|
245
|
+
const backup = root + ".previous";
|
|
246
|
+
try {
|
|
247
|
+
extract(archive, stage, target.archiveType);
|
|
248
|
+
validateInstallation(stage, target, version);
|
|
249
|
+
fs.chmodSync(path.join(stage, target.binary), 0o755);
|
|
250
|
+
// Preserve the prior complete directory until the new pair has been published.
|
|
251
|
+
if (fs.existsSync(backup)) fs.rmSync(backup, { recursive: true, force: true });
|
|
252
|
+
if (fs.existsSync(root)) fs.renameSync(root, backup);
|
|
253
|
+
try { fs.renameSync(stage, root); }
|
|
254
|
+
catch (error) { recoverInstallation(root); throw error; }
|
|
255
|
+
fs.rmSync(backup, { recursive: true, force: true });
|
|
256
|
+
} finally { fs.rmSync(stage, { recursive: true, force: true }); }
|
|
257
|
+
}
|
|
258
|
+
|
|
182
259
|
if (require.main === module) {
|
|
183
260
|
main().catch((error) => {
|
|
184
261
|
log(error.message);
|
|
@@ -187,6 +264,10 @@ if (require.main === module) {
|
|
|
187
264
|
}
|
|
188
265
|
|
|
189
266
|
module.exports = {
|
|
267
|
+
extract,
|
|
268
|
+
installArchive,
|
|
269
|
+
installationValid,
|
|
270
|
+
recoverInstallation,
|
|
190
271
|
powershellExpandArchiveCommand,
|
|
191
272
|
powershellLiteral,
|
|
192
273
|
};
|