squidcloudctl 3.0.6 → 3.0.8
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/dist/bin/squidcloud.js +1 -0
- package/dist/lib/transfer.js +46 -10
- package/package.json +1 -1
package/dist/bin/squidcloud.js
CHANGED
|
@@ -107,6 +107,7 @@ const OPTIONS = {
|
|
|
107
107
|
['--max-body <size>', 'Body cap like 512mb / 64mb'],
|
|
108
108
|
['--allow-ips <ips>', 'Comma-separated allowlist'],
|
|
109
109
|
],
|
|
110
|
+
'fs webdav': [['--port <n>', 'WebDAV port (default 8099)']],
|
|
110
111
|
'fs mount': [
|
|
111
112
|
['--mount <path>', 'FUSE mount point'],
|
|
112
113
|
['--webdav', 'Force WebDAV mode'],
|
package/dist/lib/transfer.js
CHANGED
|
@@ -154,30 +154,35 @@ async function fetchVaultKey(fileId) {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
async function getBridgeKey() {
|
|
157
|
-
if (process.env.SQUIDCLOUD_API_KEY)
|
|
158
|
-
|
|
157
|
+
if (process.env.SQUIDCLOUD_API_KEY) {
|
|
158
|
+
const k = process.env.SQUIDCLOUD_API_KEY;
|
|
159
|
+
return k.startsWith('cb_') ? k : `cb_${k}`;
|
|
160
|
+
}
|
|
159
161
|
const fs2 = await import('node:fs');
|
|
160
162
|
const os2 = await import('node:os');
|
|
161
163
|
const path2 = await import('node:path');
|
|
162
164
|
const keyFile = path2.join(os2.homedir(), '.squidcloud', 'bridge-key.json');
|
|
163
165
|
try {
|
|
164
166
|
const cached = JSON.parse(fs2.readFileSync(keyFile, 'utf8'));
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
const k = cached?.key ?? '';
|
|
168
|
+
if (k.startsWith('cb_'))
|
|
169
|
+
return k;
|
|
170
|
+
if (k.length >= 32)
|
|
171
|
+
return `cb_${k}`;
|
|
167
172
|
}
|
|
168
173
|
catch { }
|
|
169
174
|
const created = await apiPost('/keys', {
|
|
170
175
|
name: 'cli-bridge-' + new Date().toISOString().slice(0, 10),
|
|
171
176
|
});
|
|
172
|
-
const
|
|
173
|
-
if (!
|
|
174
|
-
throw new Error('Could not provision bridge key');
|
|
177
|
+
const raw = created?.raw_key;
|
|
178
|
+
if (!raw)
|
|
179
|
+
throw new Error('Could not provision a bridge API key');
|
|
175
180
|
try {
|
|
176
181
|
fs2.mkdirSync(path2.dirname(keyFile), { recursive: true });
|
|
177
|
-
fs2.writeFileSync(keyFile, JSON.stringify({ key
|
|
182
|
+
fs2.writeFileSync(keyFile, JSON.stringify({ key: `cb_${raw}`, minted_at: Date.now() }), { mode: 0o600 });
|
|
178
183
|
}
|
|
179
184
|
catch { }
|
|
180
|
-
return
|
|
185
|
+
return `cb_${raw}`;
|
|
181
186
|
}
|
|
182
187
|
function parseTags(rawTags) {
|
|
183
188
|
let cur = rawTags;
|
|
@@ -358,9 +363,40 @@ export async function downloadFile(target, destPath, onProgress) {
|
|
|
358
363
|
const msg = e instanceof Error ? e.message : String(e);
|
|
359
364
|
if (msg.startsWith('VAULT_KEY_DRIFT'))
|
|
360
365
|
throw e;
|
|
366
|
+
if (msg.startsWith('WEB_SESSION_REQUIRED')) {
|
|
367
|
+
// No fresh web JWT — fall back to signed-mode + vault key (no JWT needed).
|
|
368
|
+
try {
|
|
369
|
+
const key0 = defaultKey || tagsKey || '';
|
|
370
|
+
const usable = key0 && !/^(managed_key|sha256:.*|byok_encrypted|byok_protected)$/.test(key0);
|
|
371
|
+
const vaultKey = usable ? key0 : await fetchVaultKey(target.id);
|
|
372
|
+
if (!vaultKey && !usable && encrypted)
|
|
373
|
+
throw new Error(msg);
|
|
374
|
+
const sres = await apiRaw(`/download/${target.id}?signed=true`);
|
|
375
|
+
if (!sres.ok)
|
|
376
|
+
throw new Error(`signed HTTP ${sres.status}`);
|
|
377
|
+
const smeta = (await sres.json());
|
|
378
|
+
if (!smeta?.success || !smeta.signed_urls?.length)
|
|
379
|
+
throw new Error('signed: empty');
|
|
380
|
+
return await fetchDecryptWrite(smeta.signed_urls, target, destPath, onProgress, vaultKey ?? undefined, chunks.length);
|
|
381
|
+
}
|
|
382
|
+
catch (e2) {
|
|
383
|
+
errors.push(`signed: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
361
386
|
errors.push(`cluster: ${msg}`);
|
|
362
387
|
}
|
|
363
388
|
}
|
|
389
|
+
// ── Strategy C: SquidFS bridge (files written through the mount) ──
|
|
390
|
+
if (chunks.length > 0 && chunks.some((c) => c.repo !== 'b2')) {
|
|
391
|
+
try {
|
|
392
|
+
const apiKey = await getBridgeKey();
|
|
393
|
+
const urls = await bridgeResolve(apiKey, chunks.map((c) => ({ path: c.path, index: c.index, bucket: c.bucket })));
|
|
394
|
+
return await fetchDecryptWrite(urls, target, destPath, onProgress);
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
errors.push(`squidfs-bridge: ${e instanceof Error ? e.message : String(e)}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
364
400
|
throw new Error(errors.join(' · ') || 'No downloadable strategy succeeded');
|
|
365
401
|
}
|
|
366
402
|
let byokCache;
|
|
@@ -380,7 +416,7 @@ async function resolveKey(fileId, tagsKey) {
|
|
|
380
416
|
return tagsKey;
|
|
381
417
|
return fetchVaultKey(fileId);
|
|
382
418
|
}
|
|
383
|
-
async function fetchDecryptWrite(urls, target, destPath, onProgress) {
|
|
419
|
+
async function fetchDecryptWrite(urls, target, destPath, onProgress, preKey, declaredChunks) {
|
|
384
420
|
const totalChunks = urls.length;
|
|
385
421
|
const finalPath = destPath || target.name;
|
|
386
422
|
const out = fs.createWriteStream(finalPath);
|