@supacloud/admin 0.15.5 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/index.js +181 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,6 +100,7 @@ npx @supacloud/admin status
|
|
|
100
100
|
npx @supacloud/admin ssh ping
|
|
101
101
|
npx @supacloud/admin ssh versions
|
|
102
102
|
npx @supacloud/admin ssh diagnose
|
|
103
|
+
npx @supacloud/admin ssh upgrade_status --transaction_id 11111111-1111-4111-8111-111111111111
|
|
103
104
|
npx @supacloud/admin project create --name my-app --domain example.com \
|
|
104
105
|
--env_file /secure/path/.env.project-credentials.test --environment test
|
|
105
106
|
npx @supacloud/admin project list
|
|
@@ -193,6 +194,33 @@ remote transaction as failed. The CLI reports the unit, stage, status, log, and
|
|
|
193
194
|
upload-drop paths for reconciliation. Inspect that evidence before retrying and
|
|
194
195
|
do not retry blindly while the remote transaction may still be running.
|
|
195
196
|
|
|
197
|
+
When observation ends after 30 minutes, or to safely reconcile a retained
|
|
198
|
+
local-artifact upgrade transaction, use the read-only `ssh upgrade_status`
|
|
199
|
+
command:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npx @supacloud/admin ssh upgrade_status \
|
|
203
|
+
--transaction_id 11111111-1111-4111-8111-111111111111
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`ssh upgrade_status` is classified as a read-only command and is permitted in
|
|
207
|
+
read-only mode (`SUPACLOUD_READ_ONLY=true`). It requires a strict UUID v4
|
|
208
|
+
transaction ID before performing any SSH access. It never deletes status, log,
|
|
209
|
+
stage, or upload-drop records, and never mutates service state. The command emits
|
|
210
|
+
a strict JSON projection (`supacloud.admin.upgrade-status.v1`) containing the
|
|
211
|
+
normalized transaction ID, lifecycle state (`running`, `succeeded`, or
|
|
212
|
+
`failed`), raw bounded status, systemd active/load states, boolean
|
|
213
|
+
evidence-presence flags, and validated structured receipts:
|
|
214
|
+
- Nonterminal (`running`): no receipts or failure evidence are included.
|
|
215
|
+
- Succeeded: path-free projections of the validated control-plane preflight and
|
|
216
|
+
transaction safety receipts.
|
|
217
|
+
- Failed: validated failure evidence with credentials and remote paths redacted.
|
|
218
|
+
|
|
219
|
+
The command fails closed for missing or inconsistent terminal evidence, stopped
|
|
220
|
+
nonterminal units, malformed or missing structured receipts, redacted or
|
|
221
|
+
truncated SSH output, or unknown status, without emitting raw logs, remote
|
|
222
|
+
filesystem paths, secrets, bearer material, env values, or customer data.
|
|
223
|
+
|
|
196
224
|
`--artifact_transport local` accepts only `--github_proxy direct` or `none` and
|
|
197
225
|
clears proxy environment variables on both hosts. The server-download path
|
|
198
226
|
remains available as `--artifact_transport remote`; it verifies and executes
|
package/dist/index.js
CHANGED
|
@@ -25902,7 +25902,8 @@ var ACTION_POLICY = {
|
|
|
25902
25902
|
"container_logs",
|
|
25903
25903
|
"tenant_list",
|
|
25904
25904
|
"tenant_inspect",
|
|
25905
|
-
"tenant_diagnose"
|
|
25905
|
+
"tenant_diagnose",
|
|
25906
|
+
"upgrade_status"
|
|
25906
25907
|
],
|
|
25907
25908
|
write: ["setup", "install", "upgrade", "tenant_migrate"]
|
|
25908
25909
|
}
|
|
@@ -27393,6 +27394,34 @@ var UPGRADE_OBSERVATION_TIMEOUT_MS = 30 * 60000;
|
|
|
27393
27394
|
|
|
27394
27395
|
class RemoteUpgradeReconciliationError extends AggregateError {
|
|
27395
27396
|
}
|
|
27397
|
+
var UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
27398
|
+
function assertValidTransactionId(transactionId) {
|
|
27399
|
+
if (typeof transactionId !== "string" || !UUID_V4_PATTERN.test(transactionId)) {
|
|
27400
|
+
throw new Error("Invalid 'transaction_id': must be a valid UUID v4");
|
|
27401
|
+
}
|
|
27402
|
+
return transactionId.toLowerCase();
|
|
27403
|
+
}
|
|
27404
|
+
function upgradeStatusControlPlaneEvidence(receipt) {
|
|
27405
|
+
return {
|
|
27406
|
+
backup_id: receipt.backup_id,
|
|
27407
|
+
bytes: receipt.bytes,
|
|
27408
|
+
candidate_counts: { ...receipt.candidate_counts },
|
|
27409
|
+
completed_at: receipt.completed_at,
|
|
27410
|
+
current_key_checkpoint_present: receipt.current_key_checkpoint_present,
|
|
27411
|
+
receipt_schema: receipt.schema,
|
|
27412
|
+
sha256: receipt.sha256
|
|
27413
|
+
};
|
|
27414
|
+
}
|
|
27415
|
+
function redactRemotePaths(message) {
|
|
27416
|
+
return message.replace(/\bhttps?:\/\/[^\s"'`,;)\]}]+/gi, "[REDACTED_URL]").replace(/\bfile:\/\/\/[^\s"'`,;)\]}]+/gi, "[REDACTED_PATH]").replace(/(^|[^A-Za-z0-9:/])\/(?!\/)[^\s"'`,;)\]}]+/g, "$1[REDACTED_PATH]");
|
|
27417
|
+
}
|
|
27418
|
+
function upgradeStatusFailureEvidence(receipt) {
|
|
27419
|
+
return {
|
|
27420
|
+
causes: receipt.causes.map(redactRemotePaths),
|
|
27421
|
+
receipt_schema: receipt.schema,
|
|
27422
|
+
summary: redactRemotePaths(receipt.summary)
|
|
27423
|
+
};
|
|
27424
|
+
}
|
|
27396
27425
|
function exactObjectKeys(candidate, expected) {
|
|
27397
27426
|
const actual = Object.keys(candidate).sort();
|
|
27398
27427
|
return actual.length === expected.length && actual.every((key, index) => key === [...expected].sort()[index]);
|
|
@@ -27704,6 +27733,16 @@ function shellArray(name, entries) {
|
|
|
27704
27733
|
}
|
|
27705
27734
|
function finishUpgradeFunction() {
|
|
27706
27735
|
return [
|
|
27736
|
+
"emit_wrapper_failure_receipt() {",
|
|
27737
|
+
" local code=$1 target_phase=${2:-${UPGRADE_PHASE:-}} phase",
|
|
27738
|
+
` if [ -n "\${LOG:-}" ] && ! grep -q '^SUPACLOUD_UPGRADE_FAILURE=' "$LOG" 2>/dev/null; then`,
|
|
27739
|
+
' case "$target_phase" in',
|
|
27740
|
+
' bootstrap|filesystem_checks|transfer_verification|verifier_setup|runner_preflight|runner_upgrade|cleanup|status_publish) phase="$target_phase" ;;',
|
|
27741
|
+
' *) phase="unknown" ;;',
|
|
27742
|
+
" esac",
|
|
27743
|
+
` printf 'SUPACLOUD_UPGRADE_FAILURE={"causes":["exit_code=%s","phase=%s"],"schema":"supacloud.upgrade-failure.v1","summary":"Remote upgrade execution failed during %s (exit code %s)"}\\n' "$code" "$phase" "$phase" "$code" >> "$LOG" || true`,
|
|
27744
|
+
" fi",
|
|
27745
|
+
"}",
|
|
27707
27746
|
"finish_upgrade() {",
|
|
27708
27747
|
" local code=$?",
|
|
27709
27748
|
" trap '' HUP INT TERM",
|
|
@@ -27711,15 +27750,27 @@ function finishUpgradeFunction() {
|
|
|
27711
27750
|
" set +e",
|
|
27712
27751
|
' if [ "$code" -ne 0 ]; then',
|
|
27713
27752
|
' write_status "FAILED:${code}:TRANSACTION" || true',
|
|
27753
|
+
' emit_wrapper_failure_receipt "$code"',
|
|
27714
27754
|
' if ! rm -rf -- "$STAGE"; then write_status "FAILED:${code}:TRANSACTION_AND_CLEANUP" || true; fi',
|
|
27715
27755
|
' exit "$code"',
|
|
27716
27756
|
" fi",
|
|
27717
|
-
" write_status CLEANING
|
|
27757
|
+
" if ! write_status CLEANING; then",
|
|
27758
|
+
" echo 'Unable to publish cleanup state' >&2",
|
|
27759
|
+
" write_status 'FAILED:1:TRANSACTION' || true",
|
|
27760
|
+
" emit_wrapper_failure_receipt 1 status_publish",
|
|
27761
|
+
` if ! rm -rf -- "$STAGE"; then write_status 'FAILED:1:TRANSACTION_AND_CLEANUP' || true; fi`,
|
|
27762
|
+
" exit 1",
|
|
27763
|
+
" fi",
|
|
27718
27764
|
' if ! rm -rf -- "$STAGE"; then',
|
|
27719
27765
|
" write_status 'FAILED:1:CLEANUP_AFTER_TRANSACTION' || true",
|
|
27766
|
+
" emit_wrapper_failure_receipt 1 cleanup",
|
|
27767
|
+
" exit 1",
|
|
27768
|
+
" fi",
|
|
27769
|
+
" if ! write_status SUCCEEDED; then",
|
|
27770
|
+
" write_status 'FAILED:1:TRANSACTION' || true",
|
|
27771
|
+
" emit_wrapper_failure_receipt 1 status_publish",
|
|
27720
27772
|
" exit 1",
|
|
27721
27773
|
" fi",
|
|
27722
|
-
" write_status SUCCEEDED || exit 1",
|
|
27723
27774
|
" exit 0",
|
|
27724
27775
|
"}"
|
|
27725
27776
|
].join(`
|
|
@@ -27739,6 +27790,7 @@ function upgradeScriptSetup(paths) {
|
|
|
27739
27790
|
`BUNDLE=${quoteShell2(bundleDirectory)}`,
|
|
27740
27791
|
`MANAGEMENT_DIR=${quoteShell2(`${bundleDirectory}/management-api`)}`,
|
|
27741
27792
|
`EDGE_DIR=${quoteShell2(`${bundleDirectory}/edge-runtime`)}`,
|
|
27793
|
+
"UPGRADE_PHASE=bootstrap",
|
|
27742
27794
|
`write_status() { local next="\${STATUS}.next"; printf '%s\\n' "$1" > "$next"; chmod 600 "$next"; mv -f "$next" "$STATUS"; }`,
|
|
27743
27795
|
finishUpgradeFunction(),
|
|
27744
27796
|
"trap finish_upgrade EXIT",
|
|
@@ -27756,6 +27808,7 @@ function upgradeScriptFilesystemChecks(bundle) {
|
|
|
27756
27808
|
const componentFiles = expectedComponentFiles(bundle);
|
|
27757
27809
|
const stageEntries = ["bundle", "run.sh", ...bundle.verifierArchive ? ["verifier"] : []];
|
|
27758
27810
|
const checks = [
|
|
27811
|
+
"UPGRADE_PHASE=filesystem_checks",
|
|
27759
27812
|
shellArray("MANAGEMENT_FILES", componentFiles.management),
|
|
27760
27813
|
shellArray("EDGE_FILES", componentFiles.edge),
|
|
27761
27814
|
`assert_directory() { local path=$1; test -d "$path" && test ! -L "$path"; test "$(stat -c '%u:%g' "$path")" = '0:0'; test "$(stat -c '%a' "$path")" = '700'; }`,
|
|
@@ -27783,6 +27836,7 @@ function upgradeScriptFilesystemChecks(bundle) {
|
|
|
27783
27836
|
function upgradeScriptTransferVerification(bundle) {
|
|
27784
27837
|
const files = [...bundle.files, ...bundle.verifierArchive ? [bundle.verifierArchive] : []];
|
|
27785
27838
|
return [
|
|
27839
|
+
"UPGRADE_PHASE=transfer_verification",
|
|
27786
27840
|
`verify_transfer() { local relative=$1 expected_size=$2 expected_sha=$3; local path=$STAGE/$relative; test "$(stat -c '%s' "$path")" = "$expected_size"; test "$(sha256sum "$path" | awk '{print $1}')" = "$expected_sha"; }`,
|
|
27787
27841
|
...files.map((file) => `verify_transfer ${quoteShell2(file.relativePath)} ${file.size} ${quoteShell2(file.sha256)}`)
|
|
27788
27842
|
];
|
|
@@ -27795,6 +27849,7 @@ function upgradeScriptVerifier(paths, bundle, architecture) {
|
|
|
27795
27849
|
];
|
|
27796
27850
|
const capabilityFlags = STRICT_GITHUB_CAPABILITY_FLAGS.join(" ");
|
|
27797
27851
|
return [
|
|
27852
|
+
"UPGRADE_PHASE=verifier_setup",
|
|
27798
27853
|
...setup,
|
|
27799
27854
|
`GH_HELP=$(timeout 15s "$GH" attestation verify --help 2>&1) || { echo 'GitHub verifier capability check failed' >&2; exit 1; }`,
|
|
27800
27855
|
`for flag in ${capabilityFlags}; do printf '%s\\n' "$GH_HELP" | grep -Eq -- "(^|[[:space:]])${"$"}{flag}([=[:space:]]|$)" || { echo "GitHub verifier lacks $flag" >&2; exit 1; }; done`,
|
|
@@ -27822,6 +27877,7 @@ function upgradeScriptExecution(paths, bundle, request) {
|
|
|
27822
27877
|
const runner = `${paths.stage}/runner`;
|
|
27823
27878
|
const preflight = 'CONTROL_PLANE_PREFLIGHT_RECEIPT=$(env PATH="$VERIFIER_PATH:$PATH" "$RUNNER" --control-plane-upgrade-preflight)';
|
|
27824
27879
|
return [
|
|
27880
|
+
"UPGRADE_PHASE=runner_preflight",
|
|
27825
27881
|
`MANAGEMENT_VERSION=${quoteShell2(request.managementVersion)}`,
|
|
27826
27882
|
`RUNNER_ASSET=${quoteShell2(runnerAsset)}`,
|
|
27827
27883
|
`RUNNER=${quoteShell2(runner)}`,
|
|
@@ -27832,6 +27888,7 @@ function upgradeScriptExecution(paths, bundle, request) {
|
|
|
27832
27888
|
`timeout 5s "$RUNNER" --systemd-unit-helper-sha256 | grep -Eq 'SupaCloud systemd-unit helper SHA-256: [0-9a-f]{64}'`,
|
|
27833
27889
|
`timeout 5s "$RUNNER" --postgrest-launcher-sha256 | grep -Eq 'SupaCloud PostgREST launcher SHA-256: [0-9a-f]{64}'`,
|
|
27834
27890
|
preflight,
|
|
27891
|
+
"UPGRADE_PHASE=runner_upgrade",
|
|
27835
27892
|
`env PATH="$VERIFIER_PATH:$PATH" "$RUNNER" upgrade --yes --target-version ${quoteShell2(request.managementVersion)} --edge-runtime-version ${quoteShell2(request.edgeRuntimeVersion)} --asset-bundle-dir "$BUNDLE"`,
|
|
27836
27893
|
`printf '%s\\n' "$CONTROL_PLANE_PREFLIGHT_RECEIPT"`
|
|
27837
27894
|
];
|
|
@@ -28272,6 +28329,115 @@ async function executeLocalUpgradeTransfer(ssh, request) {
|
|
|
28272
28329
|
}
|
|
28273
28330
|
}
|
|
28274
28331
|
}
|
|
28332
|
+
function upgradeStatusLifecycle(status) {
|
|
28333
|
+
if (["PREPARED", "RUNNING", "CLEANING"].includes(status))
|
|
28334
|
+
return "running";
|
|
28335
|
+
if (status === "SUCCEEDED")
|
|
28336
|
+
return "succeeded";
|
|
28337
|
+
const failedStatus = status.match(/^FAILED:([1-9]\d{0,2}):(TRANSACTION|TRANSACTION_AND_CLEANUP|CLEANUP_AFTER_TRANSACTION)$/);
|
|
28338
|
+
if (failedStatus && Number(failedStatus[1]) <= 255)
|
|
28339
|
+
return "failed";
|
|
28340
|
+
throw new Error("Remote upgrade status is unknown or invalid");
|
|
28341
|
+
}
|
|
28342
|
+
function upgradeStatusEvidence(state) {
|
|
28343
|
+
return {
|
|
28344
|
+
drop_exists: state.dropExists,
|
|
28345
|
+
log_exists: state.logExists,
|
|
28346
|
+
stage_exists: state.stageExists,
|
|
28347
|
+
stage_is_directory: state.stageIsDirectory,
|
|
28348
|
+
status_exists: state.statusExists,
|
|
28349
|
+
unit_exists: state.unitExists
|
|
28350
|
+
};
|
|
28351
|
+
}
|
|
28352
|
+
async function readUpgradeStatusState(ssh, paths) {
|
|
28353
|
+
try {
|
|
28354
|
+
return await readRemoteState(ssh, paths);
|
|
28355
|
+
} catch {
|
|
28356
|
+
throw new Error("Unable to read remote upgrade state safely");
|
|
28357
|
+
}
|
|
28358
|
+
}
|
|
28359
|
+
async function readUpgradeStatusLog(ssh, paths) {
|
|
28360
|
+
try {
|
|
28361
|
+
return await remoteLogTail(ssh, paths);
|
|
28362
|
+
} catch {
|
|
28363
|
+
throw new Error("Remote upgrade retained log could not be read safely");
|
|
28364
|
+
}
|
|
28365
|
+
}
|
|
28366
|
+
function assertUpgradeStatusTerminalEvidence(state, paths, lifecycle) {
|
|
28367
|
+
try {
|
|
28368
|
+
if (lifecycle === "succeeded")
|
|
28369
|
+
assertSuccessfulUnitStoppedNormally(state, paths);
|
|
28370
|
+
else
|
|
28371
|
+
assertFailedUnitReachedTerminalState(state, paths);
|
|
28372
|
+
assertTerminalEvidence(state, paths);
|
|
28373
|
+
} catch {
|
|
28374
|
+
throw new Error("Remote upgrade terminal evidence is incomplete or inconsistent");
|
|
28375
|
+
}
|
|
28376
|
+
}
|
|
28377
|
+
function succeededUpgradeStatus(transactionId, state, log) {
|
|
28378
|
+
let preflight;
|
|
28379
|
+
let transaction;
|
|
28380
|
+
try {
|
|
28381
|
+
preflight = parseControlPlanePreflightEvidence(log);
|
|
28382
|
+
transaction = parseControlPlaneSafetyEvidence(log);
|
|
28383
|
+
} catch {
|
|
28384
|
+
throw new Error("Remote upgrade safety receipts are missing or invalid");
|
|
28385
|
+
}
|
|
28386
|
+
return {
|
|
28387
|
+
schema: "supacloud.admin.upgrade-status.v1",
|
|
28388
|
+
transaction_id: transactionId,
|
|
28389
|
+
lifecycle: "succeeded",
|
|
28390
|
+
status: state.status,
|
|
28391
|
+
service_state: state.serviceState,
|
|
28392
|
+
unit_load_state: state.unitLoadState,
|
|
28393
|
+
evidence: upgradeStatusEvidence(state),
|
|
28394
|
+
preflight: upgradeStatusControlPlaneEvidence(preflight),
|
|
28395
|
+
transaction: upgradeStatusControlPlaneEvidence(transaction)
|
|
28396
|
+
};
|
|
28397
|
+
}
|
|
28398
|
+
function failedUpgradeStatus(transactionId, state, log) {
|
|
28399
|
+
let failure;
|
|
28400
|
+
try {
|
|
28401
|
+
failure = parseUpgradeFailureEvidence(log);
|
|
28402
|
+
} catch {
|
|
28403
|
+
throw new Error("Remote upgrade failure receipt is missing or invalid");
|
|
28404
|
+
}
|
|
28405
|
+
return {
|
|
28406
|
+
schema: "supacloud.admin.upgrade-status.v1",
|
|
28407
|
+
transaction_id: transactionId,
|
|
28408
|
+
lifecycle: "failed",
|
|
28409
|
+
status: state.status,
|
|
28410
|
+
service_state: state.serviceState,
|
|
28411
|
+
unit_load_state: state.unitLoadState,
|
|
28412
|
+
evidence: upgradeStatusEvidence(state),
|
|
28413
|
+
failure: upgradeStatusFailureEvidence(failure)
|
|
28414
|
+
};
|
|
28415
|
+
}
|
|
28416
|
+
function runningUpgradeStatus(transactionId, state) {
|
|
28417
|
+
if (!unitIsRunning(state.serviceState) || state.unitLoadState !== "loaded" || !state.statusExists) {
|
|
28418
|
+
throw new Error("Remote upgrade nonterminal state is inconsistent");
|
|
28419
|
+
}
|
|
28420
|
+
return {
|
|
28421
|
+
schema: "supacloud.admin.upgrade-status.v1",
|
|
28422
|
+
transaction_id: transactionId,
|
|
28423
|
+
lifecycle: "running",
|
|
28424
|
+
status: state.status,
|
|
28425
|
+
service_state: state.serviceState,
|
|
28426
|
+
unit_load_state: state.unitLoadState,
|
|
28427
|
+
evidence: upgradeStatusEvidence(state)
|
|
28428
|
+
};
|
|
28429
|
+
}
|
|
28430
|
+
async function inspectRemoteUpgradeStatus(ssh, transactionIdInput) {
|
|
28431
|
+
const transactionId = assertValidTransactionId(transactionIdInput);
|
|
28432
|
+
const paths = buildRemoteUpgradePaths(transactionId);
|
|
28433
|
+
const state = await readUpgradeStatusState(ssh, paths);
|
|
28434
|
+
const lifecycle = upgradeStatusLifecycle(state.status);
|
|
28435
|
+
if (lifecycle === "running")
|
|
28436
|
+
return runningUpgradeStatus(transactionId, state);
|
|
28437
|
+
assertUpgradeStatusTerminalEvidence(state, paths, lifecycle);
|
|
28438
|
+
const log = await readUpgradeStatusLog(ssh, paths);
|
|
28439
|
+
return lifecycle === "succeeded" ? succeededUpgradeStatus(transactionId, state, log) : failedUpgradeStatus(transactionId, state, log);
|
|
28440
|
+
}
|
|
28275
28441
|
|
|
28276
28442
|
// ../../scripts/lib/release_assets.sh
|
|
28277
28443
|
var release_assets_default = '#!/usr/bin/env bash\n\nSUPACLOUD_GITHUB_REPOSITORY="${SUPACLOUD_GITHUB_REPOSITORY:-vibeunion/supacloud}"\nSUPACLOUD_RELEASES_API="${SUPACLOUD_RELEASES_API:-https://api.github.com/repos/${SUPACLOUD_GITHUB_REPOSITORY}/releases}"\nSUPACLOUD_ATTESTATION_SIGNER_WORKFLOW="${SUPACLOUD_ATTESTATION_SIGNER_WORKFLOW:-${SUPACLOUD_GITHUB_REPOSITORY}/.github/workflows/release-please.yml}"\nSUPACLOUD_GH_VERSION="${SUPACLOUD_GH_VERSION:-2.96.0}"\nSUPACLOUD_GH_MIN_VERSION="${SUPACLOUD_GH_MIN_VERSION:-2.68.0}"\nSUPACLOUD_GH_AMD64_SHA256="${SUPACLOUD_GH_AMD64_SHA256:-83d5c2ccad5498f58bf6368acb1ab32588cf43ab3a4b1c301bf36328b1c8bd60}"\nSUPACLOUD_GH_ARM64_SHA256="${SUPACLOUD_GH_ARM64_SHA256:-06f86ec7103d41993b76cd78072f43595c34aaa56506d971d9860e67140bf909}"\nSUPACLOUD_RELEASE_ASSETS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"\nSUPACLOUD_ATTESTATION_TRUSTED_ROOT_DEFAULT="${SUPACLOUD_RELEASE_ASSETS_DIR}/../../packages/management-api/src/assets/sigstore-public-good-trusted-root.jsonl"\nreadonly SUPACLOUD_ATTESTATION_TRUSTED_ROOT_SHA256="3c2cc7f357dc064ec527fdcd78da6e9245c21a381e1abaa0f2b62b186bcac1a1"\nreadonly SUPACLOUD_ATTESTATION_TRUSTED_ROOT_SIZE="5748"\n\nsupacloud_curl_release_json() {\n local url="$1"\n local output="$2"\n curl -fsSL --proto \'=https\' --proto-redir \'=https\' \\\n --retry 1 --retry-delay 2 --retry-max-time 60 \\\n --connect-timeout 15 --max-time 30 --speed-limit 128 --speed-time 10 \\\n -o "$output" "$url"\n}\n\nsupacloud_curl_release_asset() {\n local url="$1"\n local output="$2"\n curl -fL --proto \'=https\' --proto-redir \'=https\' \\\n --retry 1 --retry-delay 2 --retry-max-time 180 \\\n --connect-timeout 15 --max-time 90 --speed-limit 128 --speed-time 60 \\\n -o "$output" "$url"\n}\n\nsupacloud_component_tag() {\n local component="$1"\n local version="$2"\n case "$version" in\n "${component}-v"*) printf \'%s\' "$version" ;;\n v*) printf \'%s-%s\' "$component" "$version" ;;\n *) printf \'%s-v%s\' "$component" "$version" ;;\n esac\n}\n\nsupacloud_select_release() {\n local component="$1"\n shift\n local required_assets_json\n [[ $# -gt 0 ]] || {\n echo "at least one required release asset must be specified" >&2\n return 1\n }\n required_assets_json=$(printf \'%s\\n\' "$@" | jq -Rsc \'split("\\n")[:-1]\')\n jq -ce --arg prefix "${component}-v" --argjson required "$required_assets_json" \'\n map(select(\n (.draft | not)\n and (.prerelease | not)\n and (.tag_name | startswith($prefix))\n and (. as $release | all($required[]; . as $asset | any($release.assets[]?; .name == $asset)))\n and any(.assets[]?; .name == "SHA256SUMS")\n ))\n | first\n // error("no matching component release contains all required assets and SHA256SUMS")\n \'\n}\n\nsupacloud_fetch_component_release() {\n local component="$1"\n local version="${2:-latest}"\n shift 2\n local required_assets=("$@")\n local required_assets_json\n local response\n [[ ${#required_assets[@]} -gt 0 ]] || {\n echo "at least one required release asset must be specified" >&2\n return 1\n }\n required_assets_json=$(printf \'%s\\n\' "${required_assets[@]}" | jq -Rsc \'split("\\n")[:-1]\')\n\n if [[ -n "$version" && "$version" != "latest" ]]; then\n local tag\n tag=$(supacloud_component_tag "$component" "$version")\n response=$(supacloud_fetch_release_json "${SUPACLOUD_RELEASES_API}/tags/${tag}") || return 1\n jq -ce --argjson required "$required_assets_json" \'\n select(\n (.draft | not)\n and (.prerelease | not)\n and (. as $release | all($required[]; . as $asset | any($release.assets[]?; .name == $asset)))\n and any(.assets[]?; .name == "SHA256SUMS")\n )\n // error("release does not contain all required assets and SHA256SUMS")\n \' <<< "$response"\n return\n fi\n\n response=$(supacloud_fetch_release_json "${SUPACLOUD_RELEASES_API}?per_page=100") || return 1\n supacloud_select_release "$component" "${required_assets[@]}" <<< "$response"\n}\n\nsupacloud_fetch_release_json() (\n local url="$1"\n local response_file\n response_file=$(mktemp) || return 1\n trap \'rm -f "$response_file"\' EXIT\n trap \'trap - EXIT HUP INT TERM; rm -f "$response_file"; exit 1\' HUP INT TERM\n supacloud_download_release_metadata_url "$url" "$response_file" || return 1\n cat "$response_file"\n)\n\nsupacloud_release_asset_url() {\n local release_json="$1"\n local asset_name="$2"\n jq -er --arg asset "$asset_name" \'\n first(.assets[]? | select(.name == $asset) | .browser_download_url)\n // error("release asset URL is missing")\n \' <<< "$release_json"\n}\n\nsupacloud_download_url() {\n local url="$1"\n local output="$2"\n local proxy="${SUPACLOUD_GITHUB_PROXY:-${GH_PROXY:-}}"\n\n if supacloud_curl_release_asset "$url" "$output"; then\n return 0\n fi\n if [[ -n "$proxy" ]]; then\n supacloud_curl_release_asset "${proxy%/}/${url}" "$output"\n return\n fi\n return 1\n}\n\nsupacloud_download_release_metadata_url() {\n local url="$1"\n local output="$2"\n local proxy="${SUPACLOUD_GITHUB_PROXY:-${GH_PROXY:-}}"\n\n if supacloud_curl_release_json "$url" "$output"; then\n return 0\n fi\n if [[ -n "$proxy" ]]; then\n supacloud_curl_release_json "${proxy%/}/${url}" "$output"\n return\n fi\n return 1\n}\n\nsupacloud_verify_checksum() {\n local artifact_file="$1"\n local asset_name="$2"\n local checksum_file="$3"\n local expected\n expected=$(awk -v asset="$asset_name" \'$2 == asset || $2 == "*" asset { print $1; exit }\' "$checksum_file")\n if [[ ! "$expected" =~ ^[0-9a-fA-F]{64}$ ]]; then\n echo "SHA256SUMS does not contain a valid checksum for ${asset_name}" >&2\n return 1\n fi\n\n local actual\n actual=$(sha256sum "$artifact_file" | awk \'{print $1}\')\n actual=$(printf \'%s\' "$actual" | tr \'[:upper:]\' \'[:lower:]\')\n expected=$(printf \'%s\' "$expected" | tr \'[:upper:]\' \'[:lower:]\')\n if [[ "$actual" != "$expected" ]]; then\n echo "SHA256 mismatch for ${asset_name}" >&2\n return 1\n fi\n}\n\nsupacloud_validate_binary() {\n local artifact_file="$1"\n local asset_name="$2"\n local description\n description=$(file -b "$artifact_file")\n if [[ "$description" != *ELF* ]]; then\n echo "${asset_name} is not an ELF binary: ${description}" >&2\n return 1\n fi\n\n case "$asset_name" in\n *amd64)\n [[ "$description" == *x86-64* || "$description" == *x86_64* ]] || {\n echo "${asset_name} does not contain an x86-64 ELF binary" >&2\n return 1\n }\n ;;\n *arm64)\n [[ "$description" == *aarch64* || "$description" == *ARM64* ]] || {\n echo "${asset_name} does not contain an arm64 ELF binary" >&2\n return 1\n }\n ;;\n esac\n}\n\nsupacloud_install_pinned_tar_xz_binary() (\n local archive="$1"\n local member="$2"\n local expected_sha256="$3"\n local arch="$4"\n local target="$5"\n local actual_sha256 member_count member_details extract_dir candidate staged_target\n\n actual_sha256=$(sha256sum "$archive" | awk \'{print $1}\')\n actual_sha256=$(printf \'%s\' "$actual_sha256" | tr \'[:upper:]\' \'[:lower:]\')\n expected_sha256=$(printf \'%s\' "$expected_sha256" | tr \'[:upper:]\' \'[:lower:]\')\n if [[ ! "$expected_sha256" =~ ^[0-9a-f]{64}$ || "$actual_sha256" != "$expected_sha256" ]]; then\n echo "SHA256 mismatch for pinned archive" >&2\n return 1\n fi\n\n member_count=$(tar -tJf "$archive" | grep -Fxc "$member" || true)\n if [[ "$member_count" != "1" ]]; then\n echo "Pinned archive must contain the exact member once: $member" >&2\n return 1\n fi\n member_details=$(tar -tvJf "$archive" "$member") || return 1\n if [[ "${member_details:0:1}" != "-" ]]; then\n echo "Pinned archive member is not a regular file: $member" >&2\n return 1\n fi\n\n extract_dir=$(mktemp -d)\n trap \'rm -rf "$extract_dir"; [[ -z "${staged_target:-}" ]] || rm -f "$staged_target"\' EXIT\n trap \'trap - EXIT HUP INT TERM; rm -rf "$extract_dir"; [[ -z "${staged_target:-}" ]] || rm -f "$staged_target"; exit 1\' HUP INT TERM\n if ! tar --no-same-owner --no-same-permissions -xJf "$archive" -C "$extract_dir" "$member"; then\n return 1\n fi\n candidate="${extract_dir}/${member}"\n supacloud_validate_binary "$candidate" "pinned-linux-${arch}" || return 1\n\n mkdir -p "$(dirname "$target")"\n staged_target=$(mktemp "${target}.tmp.XXXXXX")\n install -m 0755 "$candidate" "$staged_target"\n mv -f "$staged_target" "$target"\n staged_target=""\n)\n\nsupacloud_version_at_least() {\n local current="${1#v}"\n local required="${2#v}"\n local current_major=0 current_minor=0 current_patch=0\n local required_major=0 required_minor=0 required_patch=0\n [[ "$current" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+(-.*)?$ ]] || return 1\n [[ "$required" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+(-.*)?$ ]] || return 1\n IFS=. read -r current_major current_minor current_patch <<EOF\n${current%%-*}\nEOF\n IFS=. read -r required_major required_minor required_patch <<EOF\n${required%%-*}\nEOF\n current_major=${current_major:-0}; current_minor=${current_minor:-0}; current_patch=${current_patch:-0}\n required_major=${required_major:-0}; required_minor=${required_minor:-0}; required_patch=${required_patch:-0}\n if (( current_major != required_major )); then (( current_major > required_major )); return; fi\n if (( current_minor != required_minor )); then (( current_minor > required_minor )); return; fi\n (( current_patch >= required_patch ))\n}\n\nsupacloud_gh_version() {\n gh --version 2>/dev/null | awk \'NR == 1 && $1 == "gh" && $2 == "version" { print $3; exit }\'\n}\n\nsupacloud_install_gh_archive() {\n local archive="$1"\n local version="$2"\n local arch="$3"\n local expected_sha256="$4"\n local target="$5"\n local member="gh_${version}_linux_${arch}/bin/gh"\n local actual_sha256 member_count member_details extracted_dir candidate version_output\n\n actual_sha256=$(sha256sum "$archive" | awk \'{print $1}\')\n if [[ "$actual_sha256" != "$expected_sha256" ]]; then\n echo "GitHub CLI archive SHA256 mismatch" >&2\n return 1\n fi\n\n member_count=$(tar -tzf "$archive" | grep -Fxc "$member" || true)\n if [[ "$member_count" != "1" ]]; then\n echo "GitHub CLI archive does not contain the exact expected member: $member" >&2\n return 1\n fi\n member_details=$(tar -tvzf "$archive" "$member") || return 1\n if [[ "${member_details:0:1}" != "-" ]]; then\n echo "GitHub CLI archive member is not a regular file: $member" >&2\n return 1\n fi\n\n extracted_dir=$(mktemp -d)\n candidate="${extracted_dir}/${member}"\n if ! tar --no-same-owner --no-same-permissions -xzf "$archive" -C "$extracted_dir" "$member" \\\n || ! supacloud_validate_binary "$candidate" "gh-linux-${arch}"; then\n rm -rf "$extracted_dir"\n return 1\n fi\n chmod 0755 "$candidate"\n version_output=$("$candidate" --version 2>/dev/null | head -1) || {\n rm -rf "$extracted_dir"\n echo "GitHub CLI bootstrap binary failed its version check" >&2\n return 1\n }\n if [[ "$version_output" != "gh version ${version}"* ]]; then\n rm -rf "$extracted_dir"\n echo "GitHub CLI bootstrap version mismatch: ${version_output}" >&2\n return 1\n fi\n mkdir -p "$(dirname "$target")"\n install -m 0755 "$candidate" "$target"\n rm -rf "$extracted_dir"\n}\n\nsupacloud_install_pinned_gh() {\n local target="${1:-/usr/local/bin/gh}"\n local machine arch expected_sha256 asset url archive\n machine=$(uname -m)\n case "$machine" in\n x86_64|amd64)\n arch="amd64"\n expected_sha256="$SUPACLOUD_GH_AMD64_SHA256"\n ;;\n aarch64|arm64)\n arch="arm64"\n expected_sha256="$SUPACLOUD_GH_ARM64_SHA256"\n ;;\n *)\n echo "Unsupported architecture for GitHub CLI bootstrap: $machine" >&2\n return 1\n ;;\n esac\n asset="gh_${SUPACLOUD_GH_VERSION}_linux_${arch}.tar.gz"\n url="https://github.com/cli/cli/releases/download/v${SUPACLOUD_GH_VERSION}/${asset}"\n archive=$(mktemp)\n if ! supacloud_download_url "$url" "$archive" \\\n || ! supacloud_install_gh_archive "$archive" "$SUPACLOUD_GH_VERSION" "$arch" "$expected_sha256" "$target"; then\n rm -f "$archive"\n return 1\n fi\n rm -f "$archive"\n}\n\nsupacloud_validate_tar() {\n local artifact_file="$1"\n local entries\n entries=$(tar -tzf "$artifact_file") || {\n echo "Web Console archive is not a readable gzip tarball" >&2\n return 1\n }\n if ! printf \'%s\\n\' "$entries" | awk \'\n /^\\// { exit 1 }\n /(^|\\/)\\.\\.($|\\/)/ { exit 1 }\n \'; then\n echo "Web Console archive contains an unsafe path" >&2\n return 1\n fi\n if ! tar -tvzf "$artifact_file" | awk \'substr($1, 1, 1) != "-" && substr($1, 1, 1) != "d" { exit 1 }\'; then\n echo "Web Console archive contains links or special files" >&2\n return 1\n fi\n printf \'%s\\n\' "$entries" | grep -Eq \'(^|/)index\\.html$\' || {\n echo "Web Console archive is invalid or does not contain index.html" >&2\n return 1\n }\n}\n\nsupacloud_record_integrity_mode() {\n local mode="$1"\n local record_file="${SUPACLOUD_INTEGRITY_MODE_RECORD:-/var/lib/supacloud/artifact-integrity-mode}"\n mkdir -p "$(dirname "$record_file")" 2>/dev/null || return 0\n printf \'%s\\n\' "$mode" > "$record_file" 2>/dev/null || return 0\n chmod 600 "$record_file" 2>/dev/null || true\n}\n\nsupacloud_fetch_attestation_bundle() {\n local artifact_file="$1"\n local bundle_file="$2"\n local digest response\n digest=$(sha256sum "$artifact_file" | awk \'{print $1}\') || return 1\n [[ "$digest" =~ ^[0-9a-fA-F]{64}$ ]] || {\n echo "Unable to calculate the artifact digest for attestation lookup" >&2\n return 1\n }\n digest=$(printf \'%s\' "$digest" | tr \'[:upper:]\' \'[:lower:]\')\n response=$(supacloud_fetch_release_json \\\n "https://api.github.com/repos/${SUPACLOUD_GITHUB_REPOSITORY}/attestations/sha256:${digest}") || {\n echo "Unable to download the public GitHub artifact attestation bundle" >&2\n return 1\n }\n if ! jq -ce \'\n .attestations\n | if type != "array" or length == 0 or any(.[]; (.bundle | type) != "object")\n then error("no valid attestation bundles returned")\n else .[].bundle\n end\n \' <<< "$response" > "$bundle_file"; then\n echo "GitHub artifact attestation response did not contain a valid bundle" >&2\n return 1\n fi\n}\n\nsupacloud_attestation_trusted_root_available() {\n local trusted_root="${SUPACLOUD_ATTESTATION_TRUSTED_ROOT:-$SUPACLOUD_ATTESTATION_TRUSTED_ROOT_DEFAULT}"\n local actual_size actual_sha256\n [[ "$trusted_root" == /* && -f "$trusted_root" && ! -L "$trusted_root" ]] || return 1\n actual_size=$(wc -c < "$trusted_root" | tr -d \'[:space:]\') || return 1\n [[ "$actual_size" == "$SUPACLOUD_ATTESTATION_TRUSTED_ROOT_SIZE" ]] || return 1\n actual_sha256=$(sha256sum "$trusted_root" | awk \'{print $1}\') || return 1\n [[ "$actual_sha256" == "$SUPACLOUD_ATTESTATION_TRUSTED_ROOT_SHA256" ]] || return 1\n [[ "$(wc -l < "$trusted_root" | tr -d \'[:space:]\')" == "1" ]] || return 1\n jq -e \'type == "object" and .mediaType == "application/vnd.dev.sigstore.trustedroot+json;version=0.1"\' \\\n "$trusted_root" >/dev/null 2>&1\n}\n\nsupacloud_prepare_attestation_trusted_root() {\n local destination="$1"\n local trusted_root="${SUPACLOUD_ATTESTATION_TRUSTED_ROOT:-$SUPACLOUD_ATTESTATION_TRUSTED_ROOT_DEFAULT}"\n supacloud_attestation_trusted_root_available || {\n echo "Pinned Sigstore Public Good trusted root is missing or invalid" >&2\n return 1\n }\n jq -ce . "$trusted_root" > "$destination" || return 1\n chmod 600 "$destination"\n [[ "$(wc -c < "$destination" | tr -d \'[:space:]\')" == "$SUPACLOUD_ATTESTATION_TRUSTED_ROOT_SIZE" ]] || return 1\n [[ "$(sha256sum "$destination" | awk \'{print $1}\')" == "$SUPACLOUD_ATTESTATION_TRUSTED_ROOT_SHA256" ]]\n}\n\nsupacloud_verify_attestation() (\n local artifact_file="$1"\n if supacloud_attestation_verifier_available; then\n local verification_output bundle_dir bundle_file trusted_root_file\n bundle_dir=$(mktemp -d "${TMPDIR:-/tmp}/supacloud-attestation.XXXXXX") || return 1\n trap \'rm -rf -- "$bundle_dir"\' EXIT\n trap \'trap - EXIT HUP INT TERM; rm -rf -- "$bundle_dir"; exit 1\' HUP INT TERM\n bundle_file="${bundle_dir}/bundle.jsonl"\n trusted_root_file="${bundle_dir}/trusted_root.jsonl"\n if ! supacloud_fetch_attestation_bundle "$artifact_file" "$bundle_file"; then\n return 1\n fi\n supacloud_prepare_attestation_trusted_root "$trusted_root_file" || return 1\n if ! verification_output=$(gh attestation verify "$artifact_file" \\\n --bundle "$bundle_file" \\\n --custom-trusted-root "$trusted_root_file" \\\n --repo "$SUPACLOUD_GITHUB_REPOSITORY" \\\n --signer-workflow "$SUPACLOUD_ATTESTATION_SIGNER_WORKFLOW" \\\n --source-ref "refs/heads/main" \\\n --deny-self-hosted-runners 2>&1); then\n echo "GitHub artifact attestation verification failed: ${verification_output}" >&2\n return 1\n fi\n supacloud_record_integrity_mode "github-attestation+same-release-sha256"\n return\n fi\n\n if [[ "${SUPACLOUD_ALLOW_UNVERIFIED_RELEASE:-false}" == "true" ]]; then\n echo "BREAK-GLASS LIMITED INTEGRITY MODE: artifact attestation verification is unavailable; only the same-release SHA256 checksum was verified." >&2\n supacloud_record_integrity_mode "break-glass:same-release-sha256-only"\n return 0\n fi\n\n echo "Artifact attestation verification is required, but gh attestation verify is unavailable. Install GitHub CLI or explicitly set SUPACLOUD_ALLOW_UNVERIFIED_RELEASE=true for emergency break-glass use." >&2\n return 1\n)\n\nsupacloud_attestation_verifier_available() {\n local version help\n supacloud_attestation_trusted_root_available || return 1\n command -v gh >/dev/null 2>&1 || return 1\n version=$(supacloud_gh_version)\n [[ -n "$version" ]] || return 1\n supacloud_version_at_least "$version" "$SUPACLOUD_GH_MIN_VERSION" || return 1\n help=$(gh attestation verify --help 2>&1) || return 1\n grep -Eq -- \'(^|[[:space:]])--bundle([=[:space:]]|$)\' <<< "$help" || return 1\n grep -Eq -- \'(^|[[:space:]])--signer-workflow([=[:space:]]|$)\' <<< "$help" || return 1\n grep -Eq -- \'(^|[[:space:]])--source-ref([=[:space:]]|$)\' <<< "$help" || return 1\n grep -Eq -- \'(^|[[:space:]])--custom-trusted-root([=[:space:]]|$)\' <<< "$help" || return 1\n grep -Eq -- \'(^|[[:space:]])--deny-self-hosted-runners([=[:space:]]|$)\' <<< "$help"\n}\n\nsupacloud_download_release_asset() (\n local release_json="$1"\n local asset_name="$2"\n local destination="$3"\n local asset_kind="$4"\n local asset_url checksum_url temporary_artifact temporary_checksums\n\n asset_url=$(supacloud_release_asset_url "$release_json" "$asset_name") || return 1\n checksum_url=$(supacloud_release_asset_url "$release_json" SHA256SUMS) || return 1\n mkdir -p "$(dirname "$destination")"\n temporary_artifact=$(mktemp "${destination}.tmp.XXXXXX")\n temporary_checksums=$(mktemp "${destination}.SHA256SUMS.tmp.XXXXXX")\n trap \'rm -f "${temporary_artifact:-}" "${temporary_checksums:-}"\' EXIT\n trap \'trap - EXIT HUP INT TERM; rm -f "${temporary_artifact:-}" "${temporary_checksums:-}"; exit 1\' HUP INT TERM\n\n if ! supacloud_download_url "$asset_url" "$temporary_artifact" \\\n || ! supacloud_download_release_metadata_url "$checksum_url" "$temporary_checksums" \\\n || ! supacloud_verify_checksum "$temporary_artifact" "$asset_name" "$temporary_checksums"; then\n rm -f "$temporary_artifact" "$temporary_checksums"\n return 1\n fi\n\n # Authenticate the digest before parsing archives or inspecting binaries.\n if ! supacloud_verify_attestation "$temporary_artifact"; then\n rm -f "$temporary_artifact" "$temporary_checksums"\n return 1\n fi\n\n case "$asset_kind" in\n binary) supacloud_validate_binary "$temporary_artifact" "$asset_name" ;;\n tar) supacloud_validate_tar "$temporary_artifact" ;;\n *)\n echo "Unknown release asset kind: $asset_kind" >&2\n rm -f "$temporary_artifact" "$temporary_checksums"\n return 1\n ;;\n esac || {\n rm -f "$temporary_artifact" "$temporary_checksums"\n return 1\n }\n\n mv -f "$temporary_artifact" "$destination"\n temporary_artifact=""\n rm -f "$temporary_checksums"\n temporary_checksums=""\n)\n';
|
|
@@ -29164,12 +29330,13 @@ function platformVersionsToolResult(report) {
|
|
|
29164
29330
|
}
|
|
29165
29331
|
function registerSshTools(server, ssh) {
|
|
29166
29332
|
server.tool("ssh", `Server management via SSH. Available before & after SupaCloud installation.
|
|
29167
|
-
Actions: ping, setup, install, upgrade, versions, diagnose, exec, troubleshoot, container_logs, tenant_manage, tenant_list, tenant_inspect, tenant_diagnose, tenant_migrate`, {
|
|
29333
|
+
Actions: ping, setup, install, upgrade, upgrade_status, versions, diagnose, exec, troubleshoot, container_logs, tenant_manage, tenant_list, tenant_inspect, tenant_diagnose, tenant_migrate`, {
|
|
29168
29334
|
action: withDescription(stringEnum([
|
|
29169
29335
|
"ping",
|
|
29170
29336
|
"setup",
|
|
29171
29337
|
"install",
|
|
29172
29338
|
"upgrade",
|
|
29339
|
+
"upgrade_status",
|
|
29173
29340
|
"versions",
|
|
29174
29341
|
"diagnose",
|
|
29175
29342
|
"exec",
|
|
@@ -29189,6 +29356,7 @@ Actions: ping, setup, install, upgrade, versions, diagnose, exec, troubleshoot,
|
|
|
29189
29356
|
dashboard_password: optional(secretSchema("dashboard_password"), "[install] Console password"),
|
|
29190
29357
|
edge_runtime: optional(stringEnum(["bun"]), "[install] Runtime (default: bun)"),
|
|
29191
29358
|
storage_type: optional(stringEnum(["juicefs", "minio"]), "[install] Storage backend configurable through Admin"),
|
|
29359
|
+
transaction_id: optional(Type.String(), "[upgrade_status] UUID v4 transaction ID of a retained local-artifact upgrade"),
|
|
29192
29360
|
version: optional(Type.String(), "[upgrade] Specific version"),
|
|
29193
29361
|
edge_runtime_version: optional(Type.String(), "[upgrade] Exact independent Edge Runtime version"),
|
|
29194
29362
|
artifact_transport: optional(stringEnum(["local", "remote"]), "[upgrade] Download verified release assets locally or on the server (default: remote)"),
|
|
@@ -29347,6 +29515,14 @@ ${result.stderr.slice(-500)}`;
|
|
|
29347
29515
|
${upgradeExecution.stdout.slice(-300)}${edgeBoundary}`;
|
|
29348
29516
|
break;
|
|
29349
29517
|
}
|
|
29518
|
+
case "upgrade_status": {
|
|
29519
|
+
if (!args.transaction_id)
|
|
29520
|
+
throw new Error("'transaction_id' required");
|
|
29521
|
+
const transactionId = assertValidTransactionId(args.transaction_id);
|
|
29522
|
+
const projection = await inspectRemoteUpgradeStatus(ssh, transactionId);
|
|
29523
|
+
text = JSON.stringify(projection, null, 2);
|
|
29524
|
+
break;
|
|
29525
|
+
}
|
|
29350
29526
|
case "versions": {
|
|
29351
29527
|
return platformVersionsToolResult(await platformVersions(ssh));
|
|
29352
29528
|
}
|
|
@@ -32366,7 +32542,7 @@ Actions: list_releases, get_release, upload_release, activate_release`, {
|
|
|
32366
32542
|
// package.json
|
|
32367
32543
|
var package_default = {
|
|
32368
32544
|
name: "@supacloud/admin",
|
|
32369
|
-
version: "0.
|
|
32545
|
+
version: "0.16.1",
|
|
32370
32546
|
description: "Platform administration CLI for SupaCloud operators",
|
|
32371
32547
|
type: "module",
|
|
32372
32548
|
main: "./dist/index.js",
|