@le-space/ui 0.4.4 → 0.4.5
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/package.json +11 -3
- package/react/index.js +706 -493
- package/shared/index.d.ts +1 -1
- package/shared/index.js +706 -493
- package/svelte/SponsorRelayFab.svelte +1 -1
package/react/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
|
28
28
|
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
29
29
|
|
|
30
30
|
// src/shared/package-version.ts
|
|
31
|
-
var UI_PACKAGE_VERSION = "0.4.
|
|
31
|
+
var UI_PACKAGE_VERSION = "0.4.5";
|
|
32
32
|
|
|
33
33
|
// src/shared/format.ts
|
|
34
34
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -120,7 +120,7 @@ async function fetchWithTimeout(input, init = {}, timeoutMs = 15e3) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// ../browser/src/aleph-api.ts
|
|
123
|
-
var DEFAULT_ALEPH_API_HOST = "https://
|
|
123
|
+
var DEFAULT_ALEPH_API_HOST = "https://api.aleph.im";
|
|
124
124
|
var DEFAULT_CRN_LIST_URL = "https://crns-list.aleph.sh/crns.json";
|
|
125
125
|
var DEFAULT_ALEPH_SCHEDULER_API_HOST = "https://scheduler.api.aleph.cloud";
|
|
126
126
|
var DEFAULT_2N6_API_HOST = "https://api.2n6.me";
|
|
@@ -188,7 +188,7 @@ async function fetchInstances(address, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
|
188
188
|
const url = new URL("/api/v0/messages.json", apiHost);
|
|
189
189
|
url.searchParams.set("msgTypes", "INSTANCE");
|
|
190
190
|
url.searchParams.set("addresses", address);
|
|
191
|
-
url.searchParams.set("message_statuses", "processed,pending,rejected
|
|
191
|
+
url.searchParams.set("message_statuses", "processed,pending,rejected");
|
|
192
192
|
url.searchParams.set("pagination", "100");
|
|
193
193
|
url.searchParams.set("page", "1");
|
|
194
194
|
url.searchParams.set("sortOrder", "-1");
|
|
@@ -874,7 +874,7 @@ async function fetchInstancePricing(apiHost = DEFAULT_ALEPH_API_HOST, aggregateA
|
|
|
874
874
|
var DEFAULT_ALEPH_CHANNEL = "TEST";
|
|
875
875
|
|
|
876
876
|
// ../core/src/manifests.ts
|
|
877
|
-
var DEFAULT_ALEPH_API_HOST2 = "https://
|
|
877
|
+
var DEFAULT_ALEPH_API_HOST2 = "https://api.aleph.im";
|
|
878
878
|
|
|
879
879
|
// ../core/src/crns.ts
|
|
880
880
|
var DEFAULT_CRN_LIST_URL2 = "https://crns-list.aleph.sh/crns.json";
|
|
@@ -1276,6 +1276,477 @@ async function broadcastAlephMessage2(message, options) {
|
|
|
1276
1276
|
throw new Error("Broadcast failed: retry budget exhausted");
|
|
1277
1277
|
}
|
|
1278
1278
|
|
|
1279
|
+
// ../core/src/runtime.ts
|
|
1280
|
+
var DEFAULT_SCHEDULER_ALLOCATION_URL = "https://scheduler.api.aleph.cloud/api/v0/allocation";
|
|
1281
|
+
var DEFAULT_TWO_N_SIX_HASH_URL = "https://api.2n6.me/api/hash";
|
|
1282
|
+
function asString3(value) {
|
|
1283
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1284
|
+
}
|
|
1285
|
+
function asNumber3(value) {
|
|
1286
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1287
|
+
}
|
|
1288
|
+
function normalizeIpv6(value) {
|
|
1289
|
+
if (typeof value !== "string") return null;
|
|
1290
|
+
const normalized = value.trim().toLowerCase();
|
|
1291
|
+
if (!normalized) return null;
|
|
1292
|
+
const zoneIndex = normalized.indexOf("%");
|
|
1293
|
+
return zoneIndex >= 0 ? normalized.slice(0, zoneIndex) : normalized;
|
|
1294
|
+
}
|
|
1295
|
+
function isLikelyGlobalIpv6(value) {
|
|
1296
|
+
const normalized = normalizeIpv6(value);
|
|
1297
|
+
if (!normalized || normalized === "::" || normalized === "::1") return false;
|
|
1298
|
+
const firstHextetText = normalized.split(":", 1)[0];
|
|
1299
|
+
if (!firstHextetText) return false;
|
|
1300
|
+
const firstHextet = Number.parseInt(firstHextetText, 16);
|
|
1301
|
+
if (!Number.isFinite(firstHextet)) return false;
|
|
1302
|
+
if ((firstHextet & 65024) === 64512) return false;
|
|
1303
|
+
if ((firstHextet & 65472) === 65152) return false;
|
|
1304
|
+
if ((firstHextet & 65280) === 65280) return false;
|
|
1305
|
+
if ((firstHextet & 65472) === 65216) return false;
|
|
1306
|
+
return true;
|
|
1307
|
+
}
|
|
1308
|
+
function findCrnByHash(crns, crnHash) {
|
|
1309
|
+
return crns.find((crn) => crn.hash === crnHash) ?? null;
|
|
1310
|
+
}
|
|
1311
|
+
async function fetchSchedulerAllocation2(itemHash, options) {
|
|
1312
|
+
const response = await options.fetch(
|
|
1313
|
+
`${options.schedulerAllocationUrl ?? DEFAULT_SCHEDULER_ALLOCATION_URL}/${itemHash}`,
|
|
1314
|
+
{ cache: "no-cache" }
|
|
1315
|
+
);
|
|
1316
|
+
if (response.status === 404) return null;
|
|
1317
|
+
if (!response.ok) {
|
|
1318
|
+
throw new Error(`Scheduler allocation request failed: ${response.status}`);
|
|
1319
|
+
}
|
|
1320
|
+
const payload = await response.json();
|
|
1321
|
+
const node = payload?.node;
|
|
1322
|
+
return {
|
|
1323
|
+
source: "scheduler",
|
|
1324
|
+
crnHash: asString3(node?.node_id),
|
|
1325
|
+
crnUrl: asString3(node?.url),
|
|
1326
|
+
node: node ? {
|
|
1327
|
+
node_id: asString3(node.node_id) ?? void 0,
|
|
1328
|
+
url: asString3(node.url) ?? void 0,
|
|
1329
|
+
ipv6: asString3(node.ipv6),
|
|
1330
|
+
supports_ipv6: typeof node.supports_ipv6 === "boolean" ? node.supports_ipv6 : void 0
|
|
1331
|
+
} : null,
|
|
1332
|
+
vmIpv6: asString3(payload?.vm_ipv6),
|
|
1333
|
+
period: payload?.period ? {
|
|
1334
|
+
start_timestamp: asString3(payload.period.start_timestamp) ?? void 0,
|
|
1335
|
+
duration_seconds: asNumber3(payload.period.duration_seconds) ?? void 0
|
|
1336
|
+
} : null
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
async function fetch2n6WebAccessUrl2(itemHash, options) {
|
|
1340
|
+
const response = await options.fetch(
|
|
1341
|
+
`${options.twoN6HashUrl ?? DEFAULT_TWO_N_SIX_HASH_URL}/${itemHash}`,
|
|
1342
|
+
{ cache: "no-cache" }
|
|
1343
|
+
);
|
|
1344
|
+
if (!response.ok) return null;
|
|
1345
|
+
const payload = await response.json();
|
|
1346
|
+
return {
|
|
1347
|
+
url: normalizeProxyUrl2(payload?.url ?? payload?.subdomain),
|
|
1348
|
+
active: typeof payload?.active === "boolean" ? payload.active : null,
|
|
1349
|
+
subdomain: asString3(payload?.subdomain)
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
async function fetchCrnExecutionMap2(crnUrl, options) {
|
|
1353
|
+
const normalizedCrnUrl = String(crnUrl).replace(/\/+$/, "");
|
|
1354
|
+
for (const [version, suffix] of [
|
|
1355
|
+
["v2", "/v2/about/executions/list"],
|
|
1356
|
+
["v1", "/about/executions/list"]
|
|
1357
|
+
]) {
|
|
1358
|
+
const requestUrl = `${normalizedCrnUrl}${suffix}`;
|
|
1359
|
+
const response = await options.fetch(requestUrl, {
|
|
1360
|
+
cache: "no-cache"
|
|
1361
|
+
});
|
|
1362
|
+
if (response.ok) {
|
|
1363
|
+
const payload = await response.json();
|
|
1364
|
+
return {
|
|
1365
|
+
version,
|
|
1366
|
+
payload: payload && typeof payload === "object" ? payload : null,
|
|
1367
|
+
requestUrl
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
if (response.status !== 404) {
|
|
1371
|
+
return {
|
|
1372
|
+
version,
|
|
1373
|
+
payload: null,
|
|
1374
|
+
requestUrl
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
version: "v2",
|
|
1380
|
+
payload: null,
|
|
1381
|
+
requestUrl: `${normalizedCrnUrl}/v2/about/executions/list`
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function describeRuntimeAvailability(runtime) {
|
|
1385
|
+
const execution = runtime.execution ?? null;
|
|
1386
|
+
const mappedPorts2 = runtime.mappedPorts ?? {};
|
|
1387
|
+
const hostIpv4 = runtime.hostIpv4 ?? null;
|
|
1388
|
+
const ipv6 = runtime.ipv6 ?? null;
|
|
1389
|
+
const executionGuestIpv6 = execution?.networking?.ipv6_ip ?? null;
|
|
1390
|
+
const proxyUrl = runtime.proxyUrl ?? null;
|
|
1391
|
+
const schedulerSource = runtime.allocation?.source ?? null;
|
|
1392
|
+
const webAccessActive = runtime.webAccess?.active ?? null;
|
|
1393
|
+
const mappedPortCount = Object.keys(mappedPorts2).length;
|
|
1394
|
+
if (hostIpv4 && mappedPortCount > 0 && runtime.requirePublicGuestIpv6ForProxy === true && proxyUrl && executionGuestIpv6 && !isLikelyGlobalIpv6(executionGuestIpv6)) {
|
|
1395
|
+
return {
|
|
1396
|
+
state: "execution-invalid-public-ipv6",
|
|
1397
|
+
reason: `The CRN exposed runtime networking, but the guest IPv6 (${executionGuestIpv6}) is not globally routable. Proxy-backed HTTPS activation cannot succeed until the CRN reports a public guest IPv6.`,
|
|
1398
|
+
schedulerSource,
|
|
1399
|
+
executionSeen: Boolean(execution),
|
|
1400
|
+
webAccessActive,
|
|
1401
|
+
mappedPortCount,
|
|
1402
|
+
proxyUrl
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
if (hostIpv4 && mappedPortCount > 0) {
|
|
1406
|
+
return {
|
|
1407
|
+
state: "ready",
|
|
1408
|
+
reason: null,
|
|
1409
|
+
schedulerSource,
|
|
1410
|
+
executionSeen: Boolean(execution),
|
|
1411
|
+
webAccessActive,
|
|
1412
|
+
mappedPortCount,
|
|
1413
|
+
proxyUrl
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
if (!execution && proxyUrl && webAccessActive === false) {
|
|
1417
|
+
return {
|
|
1418
|
+
state: "proxy-reserved-inactive",
|
|
1419
|
+
reason: "Aleph reserved a proxy URL for the VM, but it is still inactive and the selected CRN has not exposed execution networking yet.",
|
|
1420
|
+
schedulerSource,
|
|
1421
|
+
executionSeen: false,
|
|
1422
|
+
webAccessActive,
|
|
1423
|
+
mappedPortCount,
|
|
1424
|
+
proxyUrl
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
if (!execution && schedulerSource === "manual") {
|
|
1428
|
+
return {
|
|
1429
|
+
state: "crn-execution-missing",
|
|
1430
|
+
reason: "The deployment is pinned to a specific CRN, but that CRN is not exposing this VM in its execution list yet.",
|
|
1431
|
+
schedulerSource,
|
|
1432
|
+
executionSeen: false,
|
|
1433
|
+
webAccessActive,
|
|
1434
|
+
mappedPortCount,
|
|
1435
|
+
proxyUrl
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
if (execution && !hostIpv4) {
|
|
1439
|
+
return {
|
|
1440
|
+
state: "execution-missing-host-ipv4",
|
|
1441
|
+
reason: "The CRN exposed an execution record, but it does not include a public host IPv4 yet.",
|
|
1442
|
+
schedulerSource,
|
|
1443
|
+
executionSeen: true,
|
|
1444
|
+
webAccessActive,
|
|
1445
|
+
mappedPortCount,
|
|
1446
|
+
proxyUrl
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
if (execution && mappedPortCount === 0) {
|
|
1450
|
+
return {
|
|
1451
|
+
state: "execution-missing-port-mappings",
|
|
1452
|
+
reason: "The CRN exposed an execution record, but mapped ports are still empty.",
|
|
1453
|
+
schedulerSource,
|
|
1454
|
+
executionSeen: true,
|
|
1455
|
+
webAccessActive,
|
|
1456
|
+
mappedPortCount,
|
|
1457
|
+
proxyUrl
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
return {
|
|
1461
|
+
state: "runtime-pending",
|
|
1462
|
+
reason: "Aleph has not exposed enough runtime networking details yet.",
|
|
1463
|
+
schedulerSource,
|
|
1464
|
+
executionSeen: Boolean(execution),
|
|
1465
|
+
webAccessActive,
|
|
1466
|
+
mappedPortCount,
|
|
1467
|
+
proxyUrl
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
async function fetchVmRuntime(args) {
|
|
1471
|
+
const crns = args.crns ?? await fetchCrns2({
|
|
1472
|
+
url: args.crnListUrl ?? DEFAULT_CRN_LIST_URL2,
|
|
1473
|
+
fetch: args.fetch
|
|
1474
|
+
});
|
|
1475
|
+
const schedulerAllocation = await fetchSchedulerAllocation2(args.itemHash, {
|
|
1476
|
+
fetch: args.fetch,
|
|
1477
|
+
schedulerAllocationUrl: args.schedulerAllocationUrl
|
|
1478
|
+
}).catch(() => null);
|
|
1479
|
+
const selectedCrn = args.crnHash ? findCrnByHash(crns, args.crnHash) : null;
|
|
1480
|
+
const allocation = schedulerAllocation ?? (selectedCrn ? {
|
|
1481
|
+
source: "manual",
|
|
1482
|
+
crnHash: selectedCrn.hash,
|
|
1483
|
+
crnUrl: selectedCrn.address,
|
|
1484
|
+
node: { url: selectedCrn.address },
|
|
1485
|
+
vmIpv6: null,
|
|
1486
|
+
period: null
|
|
1487
|
+
} : null);
|
|
1488
|
+
const webAccess = await fetch2n6WebAccessUrl2(args.itemHash, {
|
|
1489
|
+
fetch: args.fetch,
|
|
1490
|
+
twoN6HashUrl: args.twoN6HashUrl
|
|
1491
|
+
}).catch(() => null);
|
|
1492
|
+
const webAccessUrl = webAccess?.url ?? null;
|
|
1493
|
+
let execution = null;
|
|
1494
|
+
let executionLookupBlocked = false;
|
|
1495
|
+
if (allocation?.crnUrl) {
|
|
1496
|
+
const executionLookup = await fetchCrnExecutionMap2(allocation.crnUrl, {
|
|
1497
|
+
fetch: args.fetch
|
|
1498
|
+
});
|
|
1499
|
+
const executionPayload = executionLookup.payload?.[args.itemHash];
|
|
1500
|
+
if (executionPayload && typeof executionPayload === "object") {
|
|
1501
|
+
execution = normalizeExecution2(executionPayload, allocation.crnUrl);
|
|
1502
|
+
if (!execution.networking.proxy_url && webAccessUrl) {
|
|
1503
|
+
execution.networking.proxy_url = webAccessUrl;
|
|
1504
|
+
}
|
|
1505
|
+
} else if (executionLookup.payload == null) {
|
|
1506
|
+
executionLookupBlocked = true;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
const hostIpv4 = execution?.networking?.host_ipv4 ?? execution?.networking?.ipv4 ?? null;
|
|
1510
|
+
const ipv6 = execution?.networking?.ipv6_ip ?? execution?.networking?.ipv6 ?? allocation?.vmIpv6 ?? null;
|
|
1511
|
+
const mappedPorts2 = execution?.networking?.mapped_ports ?? {};
|
|
1512
|
+
const sshPort = mappedPorts2?.["22"]?.host ?? null;
|
|
1513
|
+
const proxyUrl = execution?.networking?.proxy_url ?? webAccessUrl ?? null;
|
|
1514
|
+
const diagnostics = describeRuntimeAvailability({
|
|
1515
|
+
allocation,
|
|
1516
|
+
execution,
|
|
1517
|
+
webAccess,
|
|
1518
|
+
hostIpv4,
|
|
1519
|
+
ipv6,
|
|
1520
|
+
proxyUrl,
|
|
1521
|
+
mappedPorts: mappedPorts2,
|
|
1522
|
+
requirePublicGuestIpv6ForProxy: args.requirePublicGuestIpv6ForProxy
|
|
1523
|
+
});
|
|
1524
|
+
return {
|
|
1525
|
+
allocation,
|
|
1526
|
+
execution,
|
|
1527
|
+
webAccess,
|
|
1528
|
+
webAccessUrl,
|
|
1529
|
+
hostIpv4,
|
|
1530
|
+
ipv6,
|
|
1531
|
+
proxyUrl,
|
|
1532
|
+
mappedPorts: mappedPorts2,
|
|
1533
|
+
diagnostics,
|
|
1534
|
+
sshCommand: hostIpv4 && sshPort ? `ssh root@${hostIpv4} -p ${sshPort}` : ipv6 ? `ssh root@${ipv6}` : null,
|
|
1535
|
+
selectedCrn,
|
|
1536
|
+
executionLookupBlocked
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
async function waitForVmRuntime(args) {
|
|
1540
|
+
const attempts = Math.max(1, Number(args.attempts ?? 20));
|
|
1541
|
+
const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
|
|
1542
|
+
const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1543
|
+
let lastRuntime = await fetchVmRuntime(args);
|
|
1544
|
+
args.onAttempt?.(lastRuntime, 1, attempts);
|
|
1545
|
+
for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
|
|
1546
|
+
if (lastRuntime.diagnostics?.state === "ready" || lastRuntime.diagnostics?.state === "execution-invalid-public-ipv6") {
|
|
1547
|
+
return lastRuntime;
|
|
1548
|
+
}
|
|
1549
|
+
await sleep2(delayMs);
|
|
1550
|
+
lastRuntime = await fetchVmRuntime(args);
|
|
1551
|
+
args.onAttempt?.(lastRuntime, attempt + 2, attempts);
|
|
1552
|
+
}
|
|
1553
|
+
if (lastRuntime.diagnostics) {
|
|
1554
|
+
lastRuntime.diagnostics.timedOut = true;
|
|
1555
|
+
}
|
|
1556
|
+
return lastRuntime;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// ../core/src/crn-control.ts
|
|
1560
|
+
function bytesToHex(bytes) {
|
|
1561
|
+
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1562
|
+
}
|
|
1563
|
+
function utf8Hex(value) {
|
|
1564
|
+
return bytesToHex(new TextEncoder().encode(value));
|
|
1565
|
+
}
|
|
1566
|
+
function normalizeHexSignature(signature) {
|
|
1567
|
+
return signature.startsWith("0x") ? signature : `0x${signature}`;
|
|
1568
|
+
}
|
|
1569
|
+
function asString4(value) {
|
|
1570
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1571
|
+
}
|
|
1572
|
+
function trimTrailingSlashes(value) {
|
|
1573
|
+
return value.replace(/\/+$/u, "");
|
|
1574
|
+
}
|
|
1575
|
+
function isoTimestampWithMicros(date = /* @__PURE__ */ new Date()) {
|
|
1576
|
+
return date.toISOString().replace(/\.(\d{3})Z$/u, ".$1000Z");
|
|
1577
|
+
}
|
|
1578
|
+
async function generateEphemeralKeyPair() {
|
|
1579
|
+
return globalThis.crypto.subtle.generateKey(
|
|
1580
|
+
{
|
|
1581
|
+
name: "ECDSA",
|
|
1582
|
+
namedCurve: "P-256"
|
|
1583
|
+
},
|
|
1584
|
+
true,
|
|
1585
|
+
["sign", "verify"]
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
async function exportP256PublicJwk(publicKey) {
|
|
1589
|
+
const jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
|
|
1590
|
+
return {
|
|
1591
|
+
kty: String(jwk.kty ?? "EC"),
|
|
1592
|
+
crv: String(jwk.crv ?? "P-256"),
|
|
1593
|
+
x: String(jwk.x ?? ""),
|
|
1594
|
+
y: String(jwk.y ?? "")
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
async function buildSignedPubKeyHeader(args) {
|
|
1598
|
+
const payload = {
|
|
1599
|
+
pubkey: await exportP256PublicJwk(args.publicKey),
|
|
1600
|
+
alg: "ECDSA",
|
|
1601
|
+
domain: args.domain,
|
|
1602
|
+
address: args.sender,
|
|
1603
|
+
expires: isoTimestampWithMicros(new Date(Date.now() + 24 * 60 * 60 * 1e3)),
|
|
1604
|
+
chain: "ETH"
|
|
1605
|
+
};
|
|
1606
|
+
const payloadJson = JSON.stringify(payload);
|
|
1607
|
+
const signature = normalizeHexSignature(await args.signer(args.sender, payloadJson));
|
|
1608
|
+
return JSON.stringify({
|
|
1609
|
+
sender: args.sender,
|
|
1610
|
+
payload: utf8Hex(payloadJson),
|
|
1611
|
+
signature,
|
|
1612
|
+
content: {
|
|
1613
|
+
domain: args.domain
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
async function buildSignedOperationHeader(args) {
|
|
1618
|
+
const payload = {
|
|
1619
|
+
time: isoTimestampWithMicros(),
|
|
1620
|
+
method: args.method,
|
|
1621
|
+
path: args.path,
|
|
1622
|
+
domain: args.domain
|
|
1623
|
+
};
|
|
1624
|
+
const payloadJson = JSON.stringify(payload);
|
|
1625
|
+
const signature = new Uint8Array(
|
|
1626
|
+
await globalThis.crypto.subtle.sign(
|
|
1627
|
+
{
|
|
1628
|
+
name: "ECDSA",
|
|
1629
|
+
hash: "SHA-256"
|
|
1630
|
+
},
|
|
1631
|
+
args.privateKey,
|
|
1632
|
+
new TextEncoder().encode(payloadJson)
|
|
1633
|
+
)
|
|
1634
|
+
);
|
|
1635
|
+
return JSON.stringify({
|
|
1636
|
+
payload: utf8Hex(payloadJson),
|
|
1637
|
+
signature: bytesToHex(signature)
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
async function fetchInstanceNodeHash(args) {
|
|
1641
|
+
const envelope = await fetchMessageEnvelope2(args.instanceHash, {
|
|
1642
|
+
fetch: args.fetch,
|
|
1643
|
+
apiHost: args.apiHost
|
|
1644
|
+
});
|
|
1645
|
+
const payload = envelope;
|
|
1646
|
+
return asString4(payload?.content?.requirements?.node?.node_hash) ?? asString4(payload?.message?.content?.requirements?.node?.node_hash) ?? null;
|
|
1647
|
+
}
|
|
1648
|
+
async function resolveInstanceCrn(args) {
|
|
1649
|
+
const providedCrnUrl = asString4(args.crnUrl);
|
|
1650
|
+
if (providedCrnUrl) {
|
|
1651
|
+
return {
|
|
1652
|
+
crnUrl: trimTrailingSlashes(providedCrnUrl),
|
|
1653
|
+
crnHash: asString4(args.crnHash),
|
|
1654
|
+
source: "provided"
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
const allocation = await fetchSchedulerAllocation2(args.instanceHash, {
|
|
1658
|
+
fetch: args.fetch,
|
|
1659
|
+
schedulerAllocationUrl: args.schedulerAllocationUrl ?? DEFAULT_SCHEDULER_ALLOCATION_URL
|
|
1660
|
+
}).catch(() => null);
|
|
1661
|
+
if (allocation?.crnUrl) {
|
|
1662
|
+
return {
|
|
1663
|
+
crnUrl: trimTrailingSlashes(allocation.crnUrl),
|
|
1664
|
+
crnHash: asString4(allocation.crnHash),
|
|
1665
|
+
source: "scheduler"
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
const manualCrnHash = asString4(args.crnHash) ?? await fetchInstanceNodeHash({
|
|
1669
|
+
instanceHash: args.instanceHash,
|
|
1670
|
+
fetch: args.fetch,
|
|
1671
|
+
apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST2
|
|
1672
|
+
}).catch(() => null);
|
|
1673
|
+
if (manualCrnHash) {
|
|
1674
|
+
const crns = await fetchCrns2({
|
|
1675
|
+
url: args.crnListUrl ?? DEFAULT_CRN_LIST_URL2,
|
|
1676
|
+
fetch: args.fetch
|
|
1677
|
+
}).catch(() => []);
|
|
1678
|
+
const matchingCrn = crns.find((candidate) => candidate.hash === manualCrnHash);
|
|
1679
|
+
if (matchingCrn?.address) {
|
|
1680
|
+
return {
|
|
1681
|
+
crnUrl: trimTrailingSlashes(matchingCrn.address),
|
|
1682
|
+
crnHash: manualCrnHash,
|
|
1683
|
+
source: "manual"
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
crnUrl: null,
|
|
1689
|
+
crnHash: manualCrnHash,
|
|
1690
|
+
source: "missing"
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
async function eraseInstanceOnCrn(args) {
|
|
1694
|
+
const resolution = await resolveInstanceCrn({
|
|
1695
|
+
instanceHash: args.instanceHash,
|
|
1696
|
+
fetch: args.fetch,
|
|
1697
|
+
apiHost: args.apiHost,
|
|
1698
|
+
crnUrl: args.crnUrl,
|
|
1699
|
+
crnHash: args.crnHash,
|
|
1700
|
+
crnListUrl: args.crnListUrl,
|
|
1701
|
+
schedulerAllocationUrl: args.schedulerAllocationUrl
|
|
1702
|
+
});
|
|
1703
|
+
if (!resolution.crnUrl) {
|
|
1704
|
+
return {
|
|
1705
|
+
...resolution,
|
|
1706
|
+
status: "skipped"
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
const crnUrl = new URL(resolution.crnUrl);
|
|
1710
|
+
const domain = crnUrl.host;
|
|
1711
|
+
const path = `/control/machine/${args.instanceHash}/erase`;
|
|
1712
|
+
const { privateKey, publicKey } = await generateEphemeralKeyPair();
|
|
1713
|
+
const signedPubKeyHeader = await buildSignedPubKeyHeader({
|
|
1714
|
+
sender: args.sender,
|
|
1715
|
+
signer: args.signer,
|
|
1716
|
+
domain,
|
|
1717
|
+
publicKey
|
|
1718
|
+
});
|
|
1719
|
+
const signedOperationHeader = await buildSignedOperationHeader({
|
|
1720
|
+
privateKey,
|
|
1721
|
+
domain,
|
|
1722
|
+
method: "POST",
|
|
1723
|
+
path
|
|
1724
|
+
});
|
|
1725
|
+
const response = await args.fetch(`${trimTrailingSlashes(resolution.crnUrl)}${path}`, {
|
|
1726
|
+
method: "POST",
|
|
1727
|
+
headers: {
|
|
1728
|
+
"X-SignedPubKey": signedPubKeyHeader,
|
|
1729
|
+
"X-SignedOperation": signedOperationHeader
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
if (response.status === 404) {
|
|
1733
|
+
return {
|
|
1734
|
+
...resolution,
|
|
1735
|
+
status: "missing"
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
if (response.ok) {
|
|
1739
|
+
return {
|
|
1740
|
+
...resolution,
|
|
1741
|
+
status: "erased"
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
const errorBody = await response.json().then((payload) => JSON.stringify(payload)).catch(() => "");
|
|
1745
|
+
throw new Error(
|
|
1746
|
+
`CRN erase failed for ${args.instanceHash} on ${resolution.crnUrl}: ${response.status}${errorBody ? ` ${errorBody}` : ""}`
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1279
1750
|
// ../core/src/forget.ts
|
|
1280
1751
|
function asOptionalReason(value) {
|
|
1281
1752
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -1388,528 +1859,248 @@ function createPortForwardAggregateContent(args) {
|
|
|
1388
1859
|
async function createUnsignedAggregateMessage(args) {
|
|
1389
1860
|
const itemContent = JSON.stringify(args.content);
|
|
1390
1861
|
const itemHash = await args.hasher(itemContent);
|
|
1391
|
-
return {
|
|
1392
|
-
sender: args.sender,
|
|
1393
|
-
chain: "ETH",
|
|
1394
|
-
type: "AGGREGATE",
|
|
1395
|
-
item_hash: itemHash,
|
|
1396
|
-
item_type: "inline",
|
|
1397
|
-
item_content: itemContent,
|
|
1398
|
-
time: args.now ?? Date.now() / 1e3,
|
|
1399
|
-
channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
|
|
1400
|
-
};
|
|
1401
|
-
}
|
|
1402
|
-
async function ensureInstancePortForwards(args) {
|
|
1403
|
-
const requestedPorts = requiredInstancePortForwards(args.manifest);
|
|
1404
|
-
const aggregate = await fetchPortForwardAggregate(args.sender, {
|
|
1405
|
-
apiHost: args.apiHost,
|
|
1406
|
-
fetch: args.fetch
|
|
1407
|
-
});
|
|
1408
|
-
const content = createPortForwardAggregateContent({
|
|
1409
|
-
sender: args.sender,
|
|
1410
|
-
instanceItemHash: args.instanceItemHash,
|
|
1411
|
-
requestedPorts,
|
|
1412
|
-
existingAggregate: aggregate
|
|
1413
|
-
});
|
|
1414
|
-
const unsignedMessage = await createUnsignedAggregateMessage({
|
|
1415
|
-
sender: args.sender,
|
|
1416
|
-
content,
|
|
1417
|
-
hasher: args.hasher,
|
|
1418
|
-
channel: args.channel
|
|
1419
|
-
});
|
|
1420
|
-
const message = await signAlephMessage(unsignedMessage, args.signer);
|
|
1421
|
-
const { response, httpStatus } = await broadcastAlephMessage2(message, {
|
|
1422
|
-
apiHost: args.apiHost,
|
|
1423
|
-
sync: args.sync,
|
|
1424
|
-
fetch: args.fetch
|
|
1425
|
-
});
|
|
1426
|
-
const aggregateStatus = normalizeBroadcastStatus(httpStatus, response.message_status);
|
|
1427
|
-
if (aggregateStatus === "rejected") {
|
|
1428
|
-
throw new Error(`Port-forward aggregate was rejected by Aleph: ${JSON.stringify(response.details ?? response)}`);
|
|
1429
|
-
}
|
|
1430
|
-
return {
|
|
1431
|
-
aggregateItemHash: message.item_hash,
|
|
1432
|
-
aggregateStatus,
|
|
1433
|
-
requestedPorts
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
// ../core/src/instance-deployment.ts
|
|
1438
|
-
var SSH_PUBLIC_KEY_PATTERN = /^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ssh-ed25519@openssh\.com|sk-ecdsa-sha2-nistp256@openssh\.com)\s+[A-Za-z0-9+/]+={0,3}(?:\s+.+)?$/;
|
|
1439
|
-
function toFiniteNumber(value) {
|
|
1440
|
-
if (typeof value === "number") return value;
|
|
1441
|
-
if (typeof value === "string" && value.trim()) return Number(value);
|
|
1442
|
-
return Number.NaN;
|
|
1443
|
-
}
|
|
1444
|
-
function normalizeSshPublicKey(value) {
|
|
1445
|
-
return value.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
|
|
1446
|
-
}
|
|
1447
|
-
function isValidSshPublicKey(value) {
|
|
1448
|
-
return SSH_PUBLIC_KEY_PATTERN.test(normalizeSshPublicKey(value));
|
|
1449
|
-
}
|
|
1450
|
-
function createDeploymentToken() {
|
|
1451
|
-
const randomUuid = globalThis.crypto?.randomUUID?.();
|
|
1452
|
-
if (typeof randomUuid === "string" && randomUuid.trim()) {
|
|
1453
|
-
return randomUuid;
|
|
1454
|
-
}
|
|
1455
|
-
return `deploy-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
1456
|
-
}
|
|
1457
|
-
function appendDeploymentTokenToSshPublicKey(sshPublicKey, ownerAddress, deploymentToken) {
|
|
1458
|
-
const normalized = normalizeSshPublicKey(sshPublicKey);
|
|
1459
|
-
if (!normalized) return normalized;
|
|
1460
|
-
const [algorithm = "", key = ""] = normalized.split(/\s+/, 3);
|
|
1461
|
-
const owner = ownerAddress.trim();
|
|
1462
|
-
const token = deploymentToken.trim();
|
|
1463
|
-
if (!algorithm || !key || !owner || !token) {
|
|
1464
|
-
return normalized;
|
|
1465
|
-
}
|
|
1466
|
-
return `${algorithm} ${key} aleph-bootstrap-config:${owner}:${token}`;
|
|
1467
|
-
}
|
|
1468
|
-
function createReleaseMetadata(name, rootfsVersion, deployer = "shared-aleph-tooling") {
|
|
1469
|
-
return {
|
|
1470
|
-
name,
|
|
1471
|
-
rootfs_version: rootfsVersion,
|
|
1472
|
-
deployer
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
function tierSpec(pricing, tier) {
|
|
1476
|
-
return {
|
|
1477
|
-
vcpus: pricing.compute_unit.vcpus * tier.compute_units,
|
|
1478
|
-
memoryMiB: pricing.compute_unit.memory_mib * tier.compute_units,
|
|
1479
|
-
diskMiB: pricing.compute_unit.disk_mib * tier.compute_units
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
function buildPaymentQuote(tier, pricing, balance) {
|
|
1483
|
-
const computeUnitPrice = pricing.price.compute_unit;
|
|
1484
|
-
if (!computeUnitPrice) return null;
|
|
1485
|
-
const unitPrice = toFiniteNumber(computeUnitPrice.credit);
|
|
1486
|
-
if (!Number.isFinite(unitPrice)) return null;
|
|
1487
|
-
return {
|
|
1488
|
-
required: unitPrice * tier.compute_units,
|
|
1489
|
-
available: Number(balance.credit_balance ?? 0),
|
|
1490
|
-
computeUnits: tier.compute_units,
|
|
1491
|
-
unitPrice,
|
|
1492
|
-
label: "credits"
|
|
1493
|
-
};
|
|
1494
|
-
}
|
|
1495
|
-
function describeRejectedInstanceBroadcast(response) {
|
|
1496
|
-
const balanceMessage = extractInsufficientBalanceMessage2(response.details);
|
|
1497
|
-
if (balanceMessage) {
|
|
1498
|
-
return `Aleph rejected this deployment due to ${balanceMessage}.`;
|
|
1499
|
-
}
|
|
1500
|
-
const errorCode = typeof response.error_code === "number" ? response.error_code : null;
|
|
1501
|
-
return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
|
|
1502
|
-
}
|
|
1503
|
-
function createInstanceContent(args) {
|
|
1504
|
-
const sshKey = normalizeSshPublicKey(args.sshPublicKey);
|
|
1505
|
-
if (!sshKey) {
|
|
1506
|
-
throw new Error("An SSH public key is required.");
|
|
1507
|
-
}
|
|
1508
|
-
if (!isValidSshPublicKey(sshKey)) {
|
|
1509
|
-
throw new Error("SSH public key must be a single valid .pub line.");
|
|
1510
|
-
}
|
|
1511
|
-
if (!args.rootfsItemHash || !/^[a-fA-F0-9]{64}$/.test(args.rootfsItemHash)) {
|
|
1512
|
-
throw new Error("rootfsItemHash must be a 64-character Aleph item hash.");
|
|
1513
|
-
}
|
|
1514
|
-
return {
|
|
1515
|
-
address: args.address,
|
|
1516
|
-
time: args.now ?? Date.now() / 1e3,
|
|
1517
|
-
allow_amend: false,
|
|
1518
|
-
metadata: createReleaseMetadata(args.name.trim(), args.rootfsVersion ?? "custom-rootfs", args.deployer),
|
|
1519
|
-
authorized_keys: [sshKey],
|
|
1520
|
-
environment: {
|
|
1521
|
-
internet: true,
|
|
1522
|
-
aleph_api: true,
|
|
1523
|
-
hypervisor: "qemu",
|
|
1524
|
-
reproducible: false,
|
|
1525
|
-
shared_cache: false
|
|
1526
|
-
},
|
|
1527
|
-
resources: {
|
|
1528
|
-
vcpus: Number(args.vcpus),
|
|
1529
|
-
memory: Number(args.memoryMiB),
|
|
1530
|
-
seconds: Number(args.seconds ?? 30)
|
|
1531
|
-
},
|
|
1532
|
-
payment: {
|
|
1533
|
-
type: "credit"
|
|
1534
|
-
},
|
|
1535
|
-
requirements: args.crnHash ? {
|
|
1536
|
-
node: {
|
|
1537
|
-
node_hash: args.crnHash
|
|
1538
|
-
}
|
|
1539
|
-
} : void 0,
|
|
1540
|
-
volumes: [],
|
|
1541
|
-
rootfs: {
|
|
1542
|
-
parent: {
|
|
1543
|
-
ref: args.rootfsItemHash,
|
|
1544
|
-
use_latest: true
|
|
1545
|
-
},
|
|
1546
|
-
persistence: "host",
|
|
1547
|
-
size_mib: Number(args.rootfsSizeMiB)
|
|
1548
|
-
}
|
|
1549
|
-
};
|
|
1550
|
-
}
|
|
1551
|
-
async function createUnsignedInstanceMessage(args) {
|
|
1552
|
-
const itemContent = JSON.stringify(args.content);
|
|
1553
|
-
const itemHash = await args.hasher(itemContent);
|
|
1554
|
-
return {
|
|
1555
|
-
sender: args.sender,
|
|
1556
|
-
chain: "ETH",
|
|
1557
|
-
type: "INSTANCE",
|
|
1558
|
-
item_hash: itemHash,
|
|
1559
|
-
item_type: "inline",
|
|
1560
|
-
item_content: itemContent,
|
|
1561
|
-
time: args.now ?? Date.now() / 1e3,
|
|
1562
|
-
channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
async function deployInstance(args) {
|
|
1566
|
-
args.onProgress?.({
|
|
1567
|
-
stage: "building-message",
|
|
1568
|
-
label: "Building deployment message",
|
|
1569
|
-
progress: 32,
|
|
1570
|
-
status: "info",
|
|
1571
|
-
detail: args.content.metadata?.name ?? null,
|
|
1572
|
-
itemHash: null,
|
|
1573
|
-
error: null,
|
|
1574
|
-
timestamp: Date.now()
|
|
1575
|
-
});
|
|
1576
|
-
const unsignedMessage = await createUnsignedInstanceMessage({
|
|
1862
|
+
return {
|
|
1577
1863
|
sender: args.sender,
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1864
|
+
chain: "ETH",
|
|
1865
|
+
type: "AGGREGATE",
|
|
1866
|
+
item_hash: itemHash,
|
|
1867
|
+
item_type: "inline",
|
|
1868
|
+
item_content: itemContent,
|
|
1869
|
+
time: args.now ?? Date.now() / 1e3,
|
|
1870
|
+
channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
async function ensureInstancePortForwards(args) {
|
|
1874
|
+
const requestedPorts = requiredInstancePortForwards(args.manifest);
|
|
1875
|
+
const aggregate = await fetchPortForwardAggregate(args.sender, {
|
|
1876
|
+
apiHost: args.apiHost,
|
|
1877
|
+
fetch: args.fetch
|
|
1582
1878
|
});
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
detail: unsignedMessage.item_hash,
|
|
1589
|
-
itemHash: unsignedMessage.item_hash,
|
|
1590
|
-
error: null,
|
|
1591
|
-
timestamp: Date.now()
|
|
1879
|
+
const content = createPortForwardAggregateContent({
|
|
1880
|
+
sender: args.sender,
|
|
1881
|
+
instanceItemHash: args.instanceItemHash,
|
|
1882
|
+
requestedPorts,
|
|
1883
|
+
existingAggregate: aggregate
|
|
1592
1884
|
});
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
args.onProgress?.({
|
|
1599
|
-
stage: "broadcasting",
|
|
1600
|
-
label: "Broadcasting to Aleph",
|
|
1601
|
-
progress: 64,
|
|
1602
|
-
status: "info",
|
|
1603
|
-
detail: message.item_hash,
|
|
1604
|
-
itemHash: message.item_hash,
|
|
1605
|
-
error: null,
|
|
1606
|
-
timestamp: Date.now()
|
|
1885
|
+
const unsignedMessage = await createUnsignedAggregateMessage({
|
|
1886
|
+
sender: args.sender,
|
|
1887
|
+
content,
|
|
1888
|
+
hasher: args.hasher,
|
|
1889
|
+
channel: args.channel
|
|
1607
1890
|
});
|
|
1891
|
+
const message = await signAlephMessage(unsignedMessage, args.signer);
|
|
1608
1892
|
const { response, httpStatus } = await broadcastAlephMessage2(message, {
|
|
1609
1893
|
apiHost: args.apiHost,
|
|
1610
1894
|
sync: args.sync,
|
|
1611
1895
|
fetch: args.fetch
|
|
1612
1896
|
});
|
|
1613
|
-
const
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
progress: status === "processed" ? 88 : status === "rejected" ? 100 : 78,
|
|
1618
|
-
status: status === "processed" ? "success" : status === "rejected" ? "error" : "warning",
|
|
1619
|
-
detail: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
|
|
1620
|
-
itemHash: message.item_hash,
|
|
1621
|
-
error: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
|
|
1622
|
-
timestamp: Date.now()
|
|
1623
|
-
});
|
|
1897
|
+
const aggregateStatus = normalizeBroadcastStatus(httpStatus, response.message_status);
|
|
1898
|
+
if (aggregateStatus === "rejected") {
|
|
1899
|
+
throw new Error(`Port-forward aggregate was rejected by Aleph: ${JSON.stringify(response.details ?? response)}`);
|
|
1900
|
+
}
|
|
1624
1901
|
return {
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
message,
|
|
1629
|
-
response,
|
|
1630
|
-
rejectionReason: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null
|
|
1902
|
+
aggregateItemHash: message.item_hash,
|
|
1903
|
+
aggregateStatus,
|
|
1904
|
+
requestedPorts
|
|
1631
1905
|
};
|
|
1632
1906
|
}
|
|
1633
1907
|
|
|
1634
|
-
// ../core/src/
|
|
1635
|
-
var
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
function asNumber3(value) {
|
|
1641
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1908
|
+
// ../core/src/instance-deployment.ts
|
|
1909
|
+
var SSH_PUBLIC_KEY_PATTERN = /^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|sk-ssh-ed25519@openssh\.com|sk-ecdsa-sha2-nistp256@openssh\.com)\s+[A-Za-z0-9+/]+={0,3}(?:\s+.+)?$/;
|
|
1910
|
+
function toFiniteNumber(value) {
|
|
1911
|
+
if (typeof value === "number") return value;
|
|
1912
|
+
if (typeof value === "string" && value.trim()) return Number(value);
|
|
1913
|
+
return Number.NaN;
|
|
1642
1914
|
}
|
|
1643
|
-
function
|
|
1644
|
-
|
|
1645
|
-
const normalized = value.trim().toLowerCase();
|
|
1646
|
-
if (!normalized) return null;
|
|
1647
|
-
const zoneIndex = normalized.indexOf("%");
|
|
1648
|
-
return zoneIndex >= 0 ? normalized.slice(0, zoneIndex) : normalized;
|
|
1915
|
+
function normalizeSshPublicKey(value) {
|
|
1916
|
+
return value.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
|
|
1649
1917
|
}
|
|
1650
|
-
function
|
|
1651
|
-
|
|
1652
|
-
if (!normalized || normalized === "::" || normalized === "::1") return false;
|
|
1653
|
-
const firstHextetText = normalized.split(":", 1)[0];
|
|
1654
|
-
if (!firstHextetText) return false;
|
|
1655
|
-
const firstHextet = Number.parseInt(firstHextetText, 16);
|
|
1656
|
-
if (!Number.isFinite(firstHextet)) return false;
|
|
1657
|
-
if ((firstHextet & 65024) === 64512) return false;
|
|
1658
|
-
if ((firstHextet & 65472) === 65152) return false;
|
|
1659
|
-
if ((firstHextet & 65280) === 65280) return false;
|
|
1660
|
-
if ((firstHextet & 65472) === 65216) return false;
|
|
1661
|
-
return true;
|
|
1918
|
+
function isValidSshPublicKey(value) {
|
|
1919
|
+
return SSH_PUBLIC_KEY_PATTERN.test(normalizeSshPublicKey(value));
|
|
1662
1920
|
}
|
|
1663
|
-
function
|
|
1664
|
-
|
|
1921
|
+
function createDeploymentToken() {
|
|
1922
|
+
const randomUuid = globalThis.crypto?.randomUUID?.();
|
|
1923
|
+
if (typeof randomUuid === "string" && randomUuid.trim()) {
|
|
1924
|
+
return randomUuid;
|
|
1925
|
+
}
|
|
1926
|
+
return `deploy-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
1665
1927
|
}
|
|
1666
|
-
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
);
|
|
1671
|
-
|
|
1672
|
-
if (!
|
|
1673
|
-
|
|
1928
|
+
function appendDeploymentTokenToSshPublicKey(sshPublicKey, ownerAddress, deploymentToken) {
|
|
1929
|
+
const normalized = normalizeSshPublicKey(sshPublicKey);
|
|
1930
|
+
if (!normalized) return normalized;
|
|
1931
|
+
const [algorithm = "", key = ""] = normalized.split(/\s+/, 3);
|
|
1932
|
+
const owner = ownerAddress.trim();
|
|
1933
|
+
const token = deploymentToken.trim();
|
|
1934
|
+
if (!algorithm || !key || !owner || !token) {
|
|
1935
|
+
return normalized;
|
|
1674
1936
|
}
|
|
1675
|
-
|
|
1676
|
-
const node = payload?.node;
|
|
1677
|
-
return {
|
|
1678
|
-
source: "scheduler",
|
|
1679
|
-
crnHash: asString3(node?.node_id),
|
|
1680
|
-
crnUrl: asString3(node?.url),
|
|
1681
|
-
node: node ? {
|
|
1682
|
-
node_id: asString3(node.node_id) ?? void 0,
|
|
1683
|
-
url: asString3(node.url) ?? void 0,
|
|
1684
|
-
ipv6: asString3(node.ipv6),
|
|
1685
|
-
supports_ipv6: typeof node.supports_ipv6 === "boolean" ? node.supports_ipv6 : void 0
|
|
1686
|
-
} : null,
|
|
1687
|
-
vmIpv6: asString3(payload?.vm_ipv6),
|
|
1688
|
-
period: payload?.period ? {
|
|
1689
|
-
start_timestamp: asString3(payload.period.start_timestamp) ?? void 0,
|
|
1690
|
-
duration_seconds: asNumber3(payload.period.duration_seconds) ?? void 0
|
|
1691
|
-
} : null
|
|
1692
|
-
};
|
|
1937
|
+
return `${algorithm} ${key} aleph-bootstrap-config:${owner}:${token}`;
|
|
1693
1938
|
}
|
|
1694
|
-
|
|
1695
|
-
const response = await options.fetch(
|
|
1696
|
-
`${options.twoN6HashUrl ?? DEFAULT_TWO_N_SIX_HASH_URL}/${itemHash}`,
|
|
1697
|
-
{ cache: "no-cache" }
|
|
1698
|
-
);
|
|
1699
|
-
if (!response.ok) return null;
|
|
1700
|
-
const payload = await response.json();
|
|
1939
|
+
function createReleaseMetadata(name, rootfsVersion, deployer = "shared-aleph-tooling") {
|
|
1701
1940
|
return {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1941
|
+
name,
|
|
1942
|
+
rootfs_version: rootfsVersion,
|
|
1943
|
+
deployer
|
|
1705
1944
|
};
|
|
1706
1945
|
}
|
|
1707
|
-
|
|
1708
|
-
const normalizedCrnUrl = String(crnUrl).replace(/\/+$/, "");
|
|
1709
|
-
for (const [version, suffix] of [
|
|
1710
|
-
["v2", "/v2/about/executions/list"],
|
|
1711
|
-
["v1", "/about/executions/list"]
|
|
1712
|
-
]) {
|
|
1713
|
-
const requestUrl = `${normalizedCrnUrl}${suffix}`;
|
|
1714
|
-
const response = await options.fetch(requestUrl, {
|
|
1715
|
-
cache: "no-cache"
|
|
1716
|
-
});
|
|
1717
|
-
if (response.ok) {
|
|
1718
|
-
const payload = await response.json();
|
|
1719
|
-
return {
|
|
1720
|
-
version,
|
|
1721
|
-
payload: payload && typeof payload === "object" ? payload : null,
|
|
1722
|
-
requestUrl
|
|
1723
|
-
};
|
|
1724
|
-
}
|
|
1725
|
-
if (response.status !== 404) {
|
|
1726
|
-
return {
|
|
1727
|
-
version,
|
|
1728
|
-
payload: null,
|
|
1729
|
-
requestUrl
|
|
1730
|
-
};
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1946
|
+
function tierSpec(pricing, tier) {
|
|
1733
1947
|
return {
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1948
|
+
vcpus: pricing.compute_unit.vcpus * tier.compute_units,
|
|
1949
|
+
memoryMiB: pricing.compute_unit.memory_mib * tier.compute_units,
|
|
1950
|
+
diskMiB: pricing.compute_unit.disk_mib * tier.compute_units
|
|
1737
1951
|
};
|
|
1738
1952
|
}
|
|
1739
|
-
function
|
|
1740
|
-
const
|
|
1741
|
-
|
|
1742
|
-
const
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
mappedPortCount,
|
|
1757
|
-
proxyUrl
|
|
1758
|
-
};
|
|
1759
|
-
}
|
|
1760
|
-
if (hostIpv4 && mappedPortCount > 0) {
|
|
1761
|
-
return {
|
|
1762
|
-
state: "ready",
|
|
1763
|
-
reason: null,
|
|
1764
|
-
schedulerSource,
|
|
1765
|
-
executionSeen: Boolean(execution),
|
|
1766
|
-
webAccessActive,
|
|
1767
|
-
mappedPortCount,
|
|
1768
|
-
proxyUrl
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
if (!execution && proxyUrl && webAccessActive === false) {
|
|
1772
|
-
return {
|
|
1773
|
-
state: "proxy-reserved-inactive",
|
|
1774
|
-
reason: "Aleph reserved a proxy URL for the VM, but it is still inactive and the selected CRN has not exposed execution networking yet.",
|
|
1775
|
-
schedulerSource,
|
|
1776
|
-
executionSeen: false,
|
|
1777
|
-
webAccessActive,
|
|
1778
|
-
mappedPortCount,
|
|
1779
|
-
proxyUrl
|
|
1780
|
-
};
|
|
1953
|
+
function buildPaymentQuote(tier, pricing, balance) {
|
|
1954
|
+
const computeUnitPrice = pricing.price.compute_unit;
|
|
1955
|
+
if (!computeUnitPrice) return null;
|
|
1956
|
+
const unitPrice = toFiniteNumber(computeUnitPrice.credit);
|
|
1957
|
+
if (!Number.isFinite(unitPrice)) return null;
|
|
1958
|
+
return {
|
|
1959
|
+
required: unitPrice * tier.compute_units,
|
|
1960
|
+
available: Number(balance.credit_balance ?? 0),
|
|
1961
|
+
computeUnits: tier.compute_units,
|
|
1962
|
+
unitPrice,
|
|
1963
|
+
label: "credits"
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
function describeRejectedInstanceBroadcast(response) {
|
|
1967
|
+
const balanceMessage = extractInsufficientBalanceMessage2(response.details);
|
|
1968
|
+
if (balanceMessage) {
|
|
1969
|
+
return `Aleph rejected this deployment due to ${balanceMessage}.`;
|
|
1781
1970
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
mappedPortCount,
|
|
1790
|
-
proxyUrl
|
|
1791
|
-
};
|
|
1971
|
+
const errorCode = typeof response.error_code === "number" ? response.error_code : null;
|
|
1972
|
+
return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
|
|
1973
|
+
}
|
|
1974
|
+
function createInstanceContent(args) {
|
|
1975
|
+
const sshKey = normalizeSshPublicKey(args.sshPublicKey);
|
|
1976
|
+
if (!sshKey) {
|
|
1977
|
+
throw new Error("An SSH public key is required.");
|
|
1792
1978
|
}
|
|
1793
|
-
if (
|
|
1794
|
-
|
|
1795
|
-
state: "execution-missing-host-ipv4",
|
|
1796
|
-
reason: "The CRN exposed an execution record, but it does not include a public host IPv4 yet.",
|
|
1797
|
-
schedulerSource,
|
|
1798
|
-
executionSeen: true,
|
|
1799
|
-
webAccessActive,
|
|
1800
|
-
mappedPortCount,
|
|
1801
|
-
proxyUrl
|
|
1802
|
-
};
|
|
1979
|
+
if (!isValidSshPublicKey(sshKey)) {
|
|
1980
|
+
throw new Error("SSH public key must be a single valid .pub line.");
|
|
1803
1981
|
}
|
|
1804
|
-
if (
|
|
1805
|
-
|
|
1806
|
-
state: "execution-missing-port-mappings",
|
|
1807
|
-
reason: "The CRN exposed an execution record, but mapped ports are still empty.",
|
|
1808
|
-
schedulerSource,
|
|
1809
|
-
executionSeen: true,
|
|
1810
|
-
webAccessActive,
|
|
1811
|
-
mappedPortCount,
|
|
1812
|
-
proxyUrl
|
|
1813
|
-
};
|
|
1982
|
+
if (!args.rootfsItemHash || !/^[a-fA-F0-9]{64}$/.test(args.rootfsItemHash)) {
|
|
1983
|
+
throw new Error("rootfsItemHash must be a 64-character Aleph item hash.");
|
|
1814
1984
|
}
|
|
1815
1985
|
return {
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1986
|
+
address: args.address,
|
|
1987
|
+
time: args.now ?? Date.now() / 1e3,
|
|
1988
|
+
allow_amend: false,
|
|
1989
|
+
metadata: createReleaseMetadata(args.name.trim(), args.rootfsVersion ?? "custom-rootfs", args.deployer),
|
|
1990
|
+
authorized_keys: [sshKey],
|
|
1991
|
+
environment: {
|
|
1992
|
+
internet: true,
|
|
1993
|
+
aleph_api: true,
|
|
1994
|
+
hypervisor: "qemu",
|
|
1995
|
+
reproducible: false,
|
|
1996
|
+
shared_cache: false
|
|
1997
|
+
},
|
|
1998
|
+
resources: {
|
|
1999
|
+
vcpus: Number(args.vcpus),
|
|
2000
|
+
memory: Number(args.memoryMiB),
|
|
2001
|
+
seconds: Number(args.seconds ?? 30)
|
|
2002
|
+
},
|
|
2003
|
+
payment: {
|
|
2004
|
+
type: "credit"
|
|
2005
|
+
},
|
|
2006
|
+
requirements: args.crnHash ? {
|
|
2007
|
+
node: {
|
|
2008
|
+
node_hash: args.crnHash
|
|
2009
|
+
}
|
|
2010
|
+
} : void 0,
|
|
2011
|
+
volumes: [],
|
|
2012
|
+
rootfs: {
|
|
2013
|
+
parent: {
|
|
2014
|
+
ref: args.rootfsItemHash,
|
|
2015
|
+
use_latest: true
|
|
2016
|
+
},
|
|
2017
|
+
persistence: "host",
|
|
2018
|
+
size_mib: Number(args.rootfsSizeMiB)
|
|
2019
|
+
}
|
|
1823
2020
|
};
|
|
1824
2021
|
}
|
|
1825
|
-
async function
|
|
1826
|
-
const
|
|
1827
|
-
|
|
2022
|
+
async function createUnsignedInstanceMessage(args) {
|
|
2023
|
+
const itemContent = JSON.stringify(args.content);
|
|
2024
|
+
const itemHash = await args.hasher(itemContent);
|
|
2025
|
+
return {
|
|
2026
|
+
sender: args.sender,
|
|
2027
|
+
chain: "ETH",
|
|
2028
|
+
type: "INSTANCE",
|
|
2029
|
+
item_hash: itemHash,
|
|
2030
|
+
item_type: "inline",
|
|
2031
|
+
item_content: itemContent,
|
|
2032
|
+
time: args.now ?? Date.now() / 1e3,
|
|
2033
|
+
channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
async function deployInstance(args) {
|
|
2037
|
+
args.onProgress?.({
|
|
2038
|
+
stage: "building-message",
|
|
2039
|
+
label: "Building deployment message",
|
|
2040
|
+
progress: 32,
|
|
2041
|
+
status: "info",
|
|
2042
|
+
detail: args.content.metadata?.name ?? null,
|
|
2043
|
+
itemHash: null,
|
|
2044
|
+
error: null,
|
|
2045
|
+
timestamp: Date.now()
|
|
2046
|
+
});
|
|
2047
|
+
const unsignedMessage = await createUnsignedInstanceMessage({
|
|
2048
|
+
sender: args.sender,
|
|
2049
|
+
content: args.content,
|
|
2050
|
+
hasher: args.hasher,
|
|
2051
|
+
channel: args.channel,
|
|
2052
|
+
now: args.now
|
|
2053
|
+
});
|
|
2054
|
+
args.onProgress?.({
|
|
2055
|
+
stage: "signing-message",
|
|
2056
|
+
label: "Waiting for MetaMask signature",
|
|
2057
|
+
progress: 48,
|
|
2058
|
+
status: "info",
|
|
2059
|
+
detail: unsignedMessage.item_hash,
|
|
2060
|
+
itemHash: unsignedMessage.item_hash,
|
|
2061
|
+
error: null,
|
|
2062
|
+
timestamp: Date.now()
|
|
2063
|
+
});
|
|
2064
|
+
const signature = await args.signer(unsignedMessage.sender, [unsignedMessage.chain, unsignedMessage.sender, unsignedMessage.type, unsignedMessage.item_hash].join("\n"));
|
|
2065
|
+
const message = {
|
|
2066
|
+
...unsignedMessage,
|
|
2067
|
+
signature: signature.startsWith("0x") ? signature : `0x${signature}`
|
|
2068
|
+
};
|
|
2069
|
+
args.onProgress?.({
|
|
2070
|
+
stage: "broadcasting",
|
|
2071
|
+
label: "Broadcasting to Aleph",
|
|
2072
|
+
progress: 64,
|
|
2073
|
+
status: "info",
|
|
2074
|
+
detail: message.item_hash,
|
|
2075
|
+
itemHash: message.item_hash,
|
|
2076
|
+
error: null,
|
|
2077
|
+
timestamp: Date.now()
|
|
2078
|
+
});
|
|
2079
|
+
const { response, httpStatus } = await broadcastAlephMessage2(message, {
|
|
2080
|
+
apiHost: args.apiHost,
|
|
2081
|
+
sync: args.sync,
|
|
1828
2082
|
fetch: args.fetch
|
|
1829
2083
|
});
|
|
1830
|
-
const
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
vmIpv6: null,
|
|
1841
|
-
period: null
|
|
1842
|
-
} : null);
|
|
1843
|
-
const webAccess = await fetch2n6WebAccessUrl2(args.itemHash, {
|
|
1844
|
-
fetch: args.fetch,
|
|
1845
|
-
twoN6HashUrl: args.twoN6HashUrl
|
|
1846
|
-
}).catch(() => null);
|
|
1847
|
-
const webAccessUrl = webAccess?.url ?? null;
|
|
1848
|
-
let execution = null;
|
|
1849
|
-
let executionLookupBlocked = false;
|
|
1850
|
-
if (allocation?.crnUrl) {
|
|
1851
|
-
const executionLookup = await fetchCrnExecutionMap2(allocation.crnUrl, {
|
|
1852
|
-
fetch: args.fetch
|
|
1853
|
-
});
|
|
1854
|
-
const executionPayload = executionLookup.payload?.[args.itemHash];
|
|
1855
|
-
if (executionPayload && typeof executionPayload === "object") {
|
|
1856
|
-
execution = normalizeExecution2(executionPayload, allocation.crnUrl);
|
|
1857
|
-
if (!execution.networking.proxy_url && webAccessUrl) {
|
|
1858
|
-
execution.networking.proxy_url = webAccessUrl;
|
|
1859
|
-
}
|
|
1860
|
-
} else if (executionLookup.payload == null) {
|
|
1861
|
-
executionLookupBlocked = true;
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
const hostIpv4 = execution?.networking?.host_ipv4 ?? execution?.networking?.ipv4 ?? null;
|
|
1865
|
-
const ipv6 = execution?.networking?.ipv6_ip ?? execution?.networking?.ipv6 ?? allocation?.vmIpv6 ?? null;
|
|
1866
|
-
const mappedPorts2 = execution?.networking?.mapped_ports ?? {};
|
|
1867
|
-
const sshPort = mappedPorts2?.["22"]?.host ?? null;
|
|
1868
|
-
const proxyUrl = execution?.networking?.proxy_url ?? webAccessUrl ?? null;
|
|
1869
|
-
const diagnostics = describeRuntimeAvailability({
|
|
1870
|
-
allocation,
|
|
1871
|
-
execution,
|
|
1872
|
-
webAccess,
|
|
1873
|
-
hostIpv4,
|
|
1874
|
-
ipv6,
|
|
1875
|
-
proxyUrl,
|
|
1876
|
-
mappedPorts: mappedPorts2,
|
|
1877
|
-
requirePublicGuestIpv6ForProxy: args.requirePublicGuestIpv6ForProxy
|
|
2084
|
+
const status = httpStatus === 202 ? "pending" : typeof response.message_status === "string" ? response.message_status : "unknown";
|
|
2085
|
+
args.onProgress?.({
|
|
2086
|
+
stage: status === "processed" ? "deployment-confirmed" : status === "rejected" ? "deployment-rejected" : "waiting-for-aleph",
|
|
2087
|
+
label: status === "processed" ? "Deployment accepted by Aleph" : status === "rejected" ? "Deployment rejected by Aleph" : "Deployment submitted to Aleph",
|
|
2088
|
+
progress: status === "processed" ? 88 : status === "rejected" ? 100 : 78,
|
|
2089
|
+
status: status === "processed" ? "success" : status === "rejected" ? "error" : "warning",
|
|
2090
|
+
detail: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
|
|
2091
|
+
itemHash: message.item_hash,
|
|
2092
|
+
error: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
|
|
2093
|
+
timestamp: Date.now()
|
|
1878
2094
|
});
|
|
1879
2095
|
return {
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
proxyUrl,
|
|
1887
|
-
mappedPorts: mappedPorts2,
|
|
1888
|
-
diagnostics,
|
|
1889
|
-
sshCommand: hostIpv4 && sshPort ? `ssh root@${hostIpv4} -p ${sshPort}` : ipv6 ? `ssh root@${ipv6}` : null,
|
|
1890
|
-
selectedCrn,
|
|
1891
|
-
executionLookupBlocked
|
|
2096
|
+
itemHash: message.item_hash,
|
|
2097
|
+
httpStatus,
|
|
2098
|
+
status,
|
|
2099
|
+
message,
|
|
2100
|
+
response,
|
|
2101
|
+
rejectionReason: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null
|
|
1892
2102
|
};
|
|
1893
2103
|
}
|
|
1894
|
-
async function waitForVmRuntime(args) {
|
|
1895
|
-
const attempts = Math.max(1, Number(args.attempts ?? 20));
|
|
1896
|
-
const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
|
|
1897
|
-
const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1898
|
-
let lastRuntime = await fetchVmRuntime(args);
|
|
1899
|
-
args.onAttempt?.(lastRuntime, 1, attempts);
|
|
1900
|
-
for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
|
|
1901
|
-
if (lastRuntime.diagnostics?.state === "ready" || lastRuntime.diagnostics?.state === "execution-invalid-public-ipv6") {
|
|
1902
|
-
return lastRuntime;
|
|
1903
|
-
}
|
|
1904
|
-
await sleep2(delayMs);
|
|
1905
|
-
lastRuntime = await fetchVmRuntime(args);
|
|
1906
|
-
args.onAttempt?.(lastRuntime, attempt + 2, attempts);
|
|
1907
|
-
}
|
|
1908
|
-
if (lastRuntime.diagnostics) {
|
|
1909
|
-
lastRuntime.diagnostics.timedOut = true;
|
|
1910
|
-
}
|
|
1911
|
-
return lastRuntime;
|
|
1912
|
-
}
|
|
1913
2104
|
|
|
1914
2105
|
// ../core/src/guest.ts
|
|
1915
2106
|
async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
|
|
@@ -2156,7 +2347,7 @@ async function fetchUcGoPeerMetadata(args) {
|
|
|
2156
2347
|
|
|
2157
2348
|
// ../aleph-bootstrap/src/index.ts
|
|
2158
2349
|
import { recoverMessageAddress } from "viem";
|
|
2159
|
-
var DEFAULT_ALEPH_API_HOST3 = "https://
|
|
2350
|
+
var DEFAULT_ALEPH_API_HOST3 = "https://api.aleph.im";
|
|
2160
2351
|
var DEFAULT_ALEPH_BOOTSTRAP_CHANNEL = "simple-todo";
|
|
2161
2352
|
var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
|
|
2162
2353
|
var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
|
|
@@ -2845,7 +3036,7 @@ async function listStaleVmBootstrapConfigAggregateMessageHashes(args) {
|
|
|
2845
3036
|
const requestUrl = new URL("/api/v0/messages.json", args.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
2846
3037
|
requestUrl.searchParams.set("msgType", "AGGREGATE");
|
|
2847
3038
|
requestUrl.searchParams.set("addresses", address);
|
|
2848
|
-
requestUrl.searchParams.set("message_statuses", "processed,pending,rejected
|
|
3039
|
+
requestUrl.searchParams.set("message_statuses", "processed,pending,rejected");
|
|
2849
3040
|
requestUrl.searchParams.set("pagination", String(pagination));
|
|
2850
3041
|
requestUrl.searchParams.set("page", String(page));
|
|
2851
3042
|
requestUrl.searchParams.set("sortOrder", "-1");
|
|
@@ -3306,7 +3497,7 @@ async function inspectInstanceRuntime(args) {
|
|
|
3306
3497
|
executionLookupRequestUrl: null,
|
|
3307
3498
|
executionLookupVersion: null
|
|
3308
3499
|
};
|
|
3309
|
-
if (details.messageStatus === "rejected"
|
|
3500
|
+
if (details.messageStatus === "rejected") {
|
|
3310
3501
|
return { details, lookup };
|
|
3311
3502
|
}
|
|
3312
3503
|
try {
|
|
@@ -5023,6 +5214,9 @@ var SponsorRelayController = class {
|
|
|
5023
5214
|
/* @__PURE__ */ new Set([instanceHash, ...linkedRegistrationHashes])
|
|
5024
5215
|
);
|
|
5025
5216
|
const linkedRegistrationCount = linkedRegistrationHashes.length;
|
|
5217
|
+
const knownInstance = this.state.instances.find(
|
|
5218
|
+
(entry) => entry.instance.item_hash === instanceHash
|
|
5219
|
+
);
|
|
5026
5220
|
this.patch({
|
|
5027
5221
|
busy: { deletingInstanceHash: instanceHash },
|
|
5028
5222
|
errorText: null,
|
|
@@ -5031,11 +5225,30 @@ var SponsorRelayController = class {
|
|
|
5031
5225
|
try {
|
|
5032
5226
|
this.emitProgress({
|
|
5033
5227
|
stage: "validating",
|
|
5034
|
-
label: "
|
|
5228
|
+
label: "Resolving CRN for delete",
|
|
5035
5229
|
progress: 6,
|
|
5036
5230
|
status: "warning",
|
|
5037
5231
|
itemHash: instanceHash,
|
|
5038
|
-
detail: linkedRegistrationCount > 0 ? `Preparing Aleph FORGET
|
|
5232
|
+
detail: linkedRegistrationCount > 0 ? `Preparing CRN erase and Aleph FORGET for the instance and ${linkedRegistrationCount} linked bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"}.` : "Preparing CRN erase and Aleph FORGET."
|
|
5233
|
+
});
|
|
5234
|
+
const eraseResult = await eraseInstanceOnCrn({
|
|
5235
|
+
sender: this.state.wallet.address,
|
|
5236
|
+
signer: personalSign,
|
|
5237
|
+
instanceHash,
|
|
5238
|
+
fetch: asJsonFetch,
|
|
5239
|
+
apiHost: this.client.apiHost,
|
|
5240
|
+
crnUrl: knownInstance?.details.crnUrl,
|
|
5241
|
+
crnHash: knownInstance?.instance.content?.requirements?.node?.node_hash ?? null,
|
|
5242
|
+
crnListUrl: this.props.crnListUrl,
|
|
5243
|
+
schedulerAllocationUrl: this.client.schedulerApiHost ? `${this.client.schedulerApiHost.replace(/\/+$/u, "")}/api/v0/allocation` : void 0
|
|
5244
|
+
});
|
|
5245
|
+
this.emitProgress({
|
|
5246
|
+
stage: "broadcasting-delete",
|
|
5247
|
+
label: eraseResult.status === "erased" ? "Erased VM on CRN" : eraseResult.status === "missing" ? "VM already absent on CRN" : "CRN erase skipped",
|
|
5248
|
+
progress: 34,
|
|
5249
|
+
status: eraseResult.status === "skipped" ? "warning" : "info",
|
|
5250
|
+
itemHash: instanceHash,
|
|
5251
|
+
detail: eraseResult.status === "erased" ? `Erased runtime on ${eraseResult.crnUrl ?? "selected CRN"} before sending FORGET.` : eraseResult.status === "missing" ? `The CRN no longer reports this VM on ${eraseResult.crnUrl ?? "the selected node"}, so the delete flow can continue with FORGET.` : "Could not resolve a CRN endpoint for this instance, so the delete flow is falling back to FORGET-only cleanup."
|
|
5039
5252
|
});
|
|
5040
5253
|
await forgetAlephMessages({
|
|
5041
5254
|
sender: this.state.wallet.address,
|