@supacloud/admin 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +13 -3
  2. package/dist/index.js +111 -40
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -137,9 +137,19 @@ With `--artifact_transport remote` (the default), omitting
137
137
  `--edge_runtime_version` retains the Management and Web Console-only upgrade
138
138
  behavior and reports that Edge Runtime was not upgraded. Local transport
139
139
  requires exact Management and Edge Runtime versions. Caddy and GoTrue are
140
- outside this transaction and are not replaced. After a capable Management
141
- release is active, an exact rollback can use that active upgrader with explicit
142
- older targets, for example:
140
+ outside this transaction and are not replaced.
141
+
142
+ The remote transport allows the same 30-minute transaction window plus bounded
143
+ verifier/bootstrap downloads: 42 minutes for direct GitHub access, or 52
144
+ minutes when each download may try direct GitHub before an explicit proxy. If
145
+ the SSH command times out or its stream fails after dispatch, Admin reports
146
+ `OUTCOME_UNKNOWN` and does not issue client-side helper cleanup. The remote
147
+ command may finish later and then run its own cleanup. Reconcile the reported
148
+ helper and trusted-root paths and read back deployed versions before deciding
149
+ whether to retry.
150
+
151
+ After a capable Management release is active, an exact rollback can use that
152
+ active upgrader with explicit older targets, for example:
143
153
 
144
154
  ```bash
145
155
  npx @supacloud/admin ssh upgrade \
package/dist/index.js CHANGED
@@ -24931,6 +24931,14 @@ function schemaProperties(schema) {
24931
24931
  var import_ssh2 = __toESM(require_lib3(), 1);
24932
24932
  import { randomUUID, timingSafeEqual } from "node:crypto";
24933
24933
  import { readFileSync } from "node:fs";
24934
+
24935
+ class SshCommandOutcomeUnknownError extends Error {
24936
+ code = "OUTCOME_UNKNOWN";
24937
+ constructor(message) {
24938
+ super(message);
24939
+ this.name = "SshCommandOutcomeUnknownError";
24940
+ }
24941
+ }
24934
24942
  var DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
24935
24943
  var MAX_CONFIGURABLE_OUTPUT_BYTES = 16 * 1024 * 1024;
24936
24944
  var DEFAULT_UPLOAD_TIMEOUT_MS = 10 * 60000;
@@ -25148,34 +25156,45 @@ class SshTransport {
25148
25156
  const stderr = new BoundedOutputCollector(outputLimit);
25149
25157
  const timer = setTimeout(() => {
25150
25158
  connectionReusable = false;
25151
- reject(new Error(`SSH command timed out after ${timeoutMs}ms`));
25159
+ reject(new SshCommandOutcomeUnknownError(`SSH command timed out after ${timeoutMs}ms; remote outcome is unknown`));
25152
25160
  }, timeoutMs);
25153
- conn.exec(command, (err, stream) => {
25154
- if (err) {
25155
- clearTimeout(timer);
25156
- connectionReusable = false;
25157
- return reject(err);
25158
- }
25159
- stream.on("close", (code) => {
25160
- clearTimeout(timer);
25161
- resolve({
25162
- success: code === 0,
25163
- stdout: stdout.finalize(),
25164
- stderr: stderr.finalize(),
25165
- code,
25166
- stdoutTruncated: stdout.truncated,
25167
- stderrTruncated: stderr.truncated
25161
+ try {
25162
+ conn.exec(command, (err, stream) => {
25163
+ if (err) {
25164
+ clearTimeout(timer);
25165
+ connectionReusable = false;
25166
+ return reject(err);
25167
+ }
25168
+ stream.on("close", (code) => {
25169
+ clearTimeout(timer);
25170
+ if (code === undefined) {
25171
+ connectionReusable = false;
25172
+ reject(new SshCommandOutcomeUnknownError("SSH command stream closed without a terminal status; remote outcome is unknown"));
25173
+ return;
25174
+ }
25175
+ resolve({
25176
+ success: code === 0,
25177
+ stdout: stdout.finalize(),
25178
+ stderr: stderr.finalize(),
25179
+ code: code ?? 128,
25180
+ stdoutTruncated: stdout.truncated,
25181
+ stderrTruncated: stderr.truncated
25182
+ });
25183
+ }).on("error", () => {
25184
+ clearTimeout(timer);
25185
+ connectionReusable = false;
25186
+ reject(new SshCommandOutcomeUnknownError("SSH command stream failed after dispatch; remote outcome is unknown"));
25187
+ }).on("data", (data) => {
25188
+ stdout.append(data);
25189
+ }).stderr.on("data", (data) => {
25190
+ stderr.append(data);
25168
25191
  });
25169
- }).on("error", (streamError) => {
25170
- clearTimeout(timer);
25171
- connectionReusable = false;
25172
- reject(streamError);
25173
- }).on("data", (data) => {
25174
- stdout.append(data);
25175
- }).stderr.on("data", (data) => {
25176
- stderr.append(data);
25177
25192
  });
25178
- });
25193
+ } catch (error) {
25194
+ clearTimeout(timer);
25195
+ connectionReusable = false;
25196
+ reject(error);
25197
+ }
25179
25198
  });
25180
25199
  } finally {
25181
25200
  if (connectionReusable)
@@ -27073,7 +27092,7 @@ function buildRemotePreflightScript() {
27073
27092
  "test -d /run/lock || { echo '/run/lock is unavailable' >&2; exit 1; }",
27074
27093
  "test -d /var/lib/supacloud || { echo '/var/lib/supacloud is unavailable' >&2; exit 1; }",
27075
27094
  "test -f /etc/supabase/management-api.env || { echo 'EDGE_RUNTIME_MODE is unavailable' >&2; exit 1; }",
27076
- `EDGE_MODE=$(awk -F= '$1 == "EDGE_RUNTIME_MODE" { value=$2 } END { gsub(/^[[:space:]\\"'"']+|[[:space:]\\"'"']+$/, "", value); print value }' /etc/supabase/management-api.env)`,
27095
+ `EDGE_MODE=$(awk -F= '$1 == "EDGE_RUNTIME_MODE" { value=substr($0, index($0, "=") + 1) } END { gsub(/^[[:space:]\\042\\047]+|[[:space:]\\042\\047]+$/, "", value); print value }' /etc/supabase/management-api.env)`,
27077
27096
  `test "$EDGE_MODE" = external || { echo 'Local component upgrade requires persisted external Edge Runtime mode' >&2; exit 1; }`,
27078
27097
  `case "$(uname -m)" in x86_64|amd64) echo ARCH=amd64 ;; aarch64|arm64) echo ARCH=arm64 ;; *) echo 'Unsupported remote architecture' >&2; exit 1 ;; esac`,
27079
27098
  "VERIFIER=bundled",
@@ -27722,8 +27741,10 @@ var SAFE_HOSTNAME = /^(?=.{1,253}$)(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9
27722
27741
  var SAFE_SYSTEMD_UNIT = /^[a-zA-Z0-9][a-zA-Z0-9_.@:-]{0,127}$/;
27723
27742
  var SAFE_DB_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/;
27724
27743
  var MINIMUM_COMPONENT_UPGRADE_VERSION = "0.50.27";
27725
- var DIRECT_UPGRADE_SSH_TIMEOUT_MS = 720000;
27726
- var PROXIED_UPGRADE_SSH_TIMEOUT_MS = 1320000;
27744
+ var DIRECT_BOOTSTRAP_TRANSFER_BUDGET_MS = 12 * 60000;
27745
+ var PROXIED_BOOTSTRAP_TRANSFER_BUDGET_MS = 22 * 60000;
27746
+ var DIRECT_UPGRADE_SSH_TIMEOUT_MS = UPGRADE_OBSERVATION_TIMEOUT_MS + DIRECT_BOOTSTRAP_TRANSFER_BUDGET_MS;
27747
+ var PROXIED_UPGRADE_SSH_TIMEOUT_MS = UPGRADE_OBSERVATION_TIMEOUT_MS + PROXIED_BOOTSTRAP_TRANSFER_BUDGET_MS;
27727
27748
  var PLATFORM_PROBE_TIMEOUT_MS = 1e4;
27728
27749
  var PLATFORM_HASH_TIMEOUT_MS = 30000;
27729
27750
  var WEB_CONSOLE_PROBE_TIMEOUT_MS = 60000;
@@ -27902,7 +27923,7 @@ function componentBootstrapCommands(request) {
27902
27923
  function componentPreflightCommands(request) {
27903
27924
  return request.edgeRuntimeVersion ? [
27904
27925
  "test -f /etc/supabase/management-api.env || { echo 'EDGE_RUNTIME_MODE is unavailable; component upgrade requires external mode' >&2; exit 1; }",
27905
- `EDGE_RUNTIME_MODE_VALUE=$(awk -F= '$1 == "EDGE_RUNTIME_MODE" { value=$2 } END { gsub(/^[[:space:]\\"'"']+|[[:space:]\\"'"']+$/, "", value); print value }' /etc/supabase/management-api.env)`,
27926
+ `EDGE_RUNTIME_MODE_VALUE=$(awk -F= '$1 == "EDGE_RUNTIME_MODE" { value=substr($0, index($0, "=") + 1) } END { gsub(/^[[:space:]\\042\\047]+|[[:space:]\\042\\047]+$/, "", value); print value }' /etc/supabase/management-api.env)`,
27906
27927
  `test "$EDGE_RUNTIME_MODE_VALUE" = external || { echo 'Edge Runtime component upgrade supports persisted external mode only' >&2; exit 1; }`
27907
27928
  ] : [];
27908
27929
  }
@@ -27996,11 +28017,58 @@ async function removeRemoteUpgradeHelper(ssh, helperPath) {
27996
28017
  throw new Error(`Failed to remove remote upgrade helper (exit ${cleanup.code}): ${cleanup.stderr.slice(-300)}`);
27997
28018
  }
27998
28019
  }
28020
+
28021
+ class RemoteUpgradeOutcomeUnknownError extends AggregateError {
28022
+ code = "OUTCOME_UNKNOWN";
28023
+ }
28024
+ function boundedUpgradeError(error) {
28025
+ const message = error instanceof Error ? error.message : String(error);
28026
+ const diagnostic = redactSshOutput(message).replace(/[\r\n\t]+/g, " ").trim().slice(0, 500);
28027
+ return new Error(diagnostic || "Remote operation ended without a diagnostic");
28028
+ }
28029
+ function remoteUpgradeEvidence(helperPath) {
28030
+ const trustedRootPath = join2(dirname(helperPath), SIGSTORE_PUBLIC_GOOD_TRUSTED_ROOT_FILENAME);
28031
+ return `helper=${helperPath} trusted_root=${trustedRootPath}`;
28032
+ }
28033
+ function remoteUpgradeReconciliationFailure(message, failures, helperPath) {
28034
+ return new RemoteUpgradeOutcomeUnknownError(failures.map(boundedUpgradeError), `${message} Reconcile ${remoteUpgradeEvidence(helperPath)}; do not retry blindly`);
28035
+ }
28036
+ function remoteUpgradeOutcomeUnknown(transportError, helperPath) {
28037
+ return remoteUpgradeReconciliationFailure("OUTCOME_UNKNOWN: Remote upgrade transport ended after dispatch; client cleanup was suppressed " + "because the remote command may still be running. Verify deployed versions before retrying.", [transportError], helperPath);
28038
+ }
27999
28039
  function remoteUpgradeFailure(execution) {
28000
28040
  const diagnostic = execution.stderr.trim() || execution.stdout.trim() || "no remote diagnostic";
28001
28041
  return new Error(`Remote upgrade failed (exit ${execution.code}): ${diagnostic.slice(-500)}`);
28002
28042
  }
28043
+ function remoteHelperSetupOutcomeUnknown(outcome) {
28044
+ const cleanupStatus = outcome.cleanupFailed ? "the helper cleanup did not complete" : "the cleanup command completed, but setup may finish later";
28045
+ const failures = outcome.cleanupFailed ? [outcome.executionError, outcome.cleanupError] : [outcome.executionError];
28046
+ return remoteUpgradeReconciliationFailure(`OUTCOME_UNKNOWN: Remote helper setup ended without terminal status; ${cleanupStatus}. ` + "The upgrade command was not dispatched.", failures, outcome.helperPath);
28047
+ }
28048
+ function completedUpgradeCleanupOutcomeUnknown(outcome) {
28049
+ if (!outcome.execution) {
28050
+ return remoteUpgradeReconciliationFailure("OUTCOME_UNKNOWN: Upgrade execution returned no result, and helper cleanup outcome is unknown.", [outcome.cleanupError], outcome.helperPath);
28051
+ }
28052
+ const message = outcome.execution.success ? "OUTCOME_UNKNOWN: Remote upgrade succeeded, but helper cleanup outcome is unknown." : "OUTCOME_UNKNOWN: Remote upgrade failed with a terminal result, and helper cleanup outcome is unknown.";
28053
+ const failures = outcome.execution.success ? [outcome.cleanupError] : [remoteUpgradeFailure(outcome.execution), outcome.cleanupError];
28054
+ return remoteUpgradeReconciliationFailure(message, failures, outcome.helperPath);
28055
+ }
28056
+ function remoteHelperCleanupOutcomeUnknown(outcome) {
28057
+ if (!outcome.upgradeExecutionRequested) {
28058
+ return remoteUpgradeReconciliationFailure("OUTCOME_UNKNOWN: Remote helper setup failed, and helper cleanup outcome is unknown. " + "The upgrade command was not dispatched.", [outcome.executionError, outcome.cleanupError], outcome.helperPath);
28059
+ }
28060
+ if (outcome.executionFailed) {
28061
+ return remoteUpgradeReconciliationFailure("OUTCOME_UNKNOWN: Upgrade execution request failed, and helper cleanup outcome is unknown.", [outcome.executionError, outcome.cleanupError], outcome.helperPath);
28062
+ }
28063
+ return completedUpgradeCleanupOutcomeUnknown(outcome);
28064
+ }
28003
28065
  function officialUpgradeOutcome(outcome) {
28066
+ if (!outcome.upgradeExecutionRequested && outcome.executionError instanceof SshCommandOutcomeUnknownError) {
28067
+ throw remoteHelperSetupOutcomeUnknown(outcome);
28068
+ }
28069
+ if (outcome.cleanupFailed && outcome.cleanupError instanceof SshCommandOutcomeUnknownError) {
28070
+ throw remoteHelperCleanupOutcomeUnknown(outcome);
28071
+ }
28004
28072
  if (outcome.executionFailed && outcome.cleanupFailed) {
28005
28073
  throw new AggregateError([outcome.executionError, outcome.cleanupError], "Upgrade execution failed and helper cleanup did not complete");
28006
28074
  }
@@ -28021,34 +28089,37 @@ async function executeOfficialUpgrade(ssh, helperPath, command, timeoutMs) {
28021
28089
  let execution;
28022
28090
  let executionFailed = false;
28023
28091
  let executionError;
28024
- let helperPrepared = false;
28092
+ let upgradeExecutionRequested = false;
28025
28093
  try {
28026
28094
  await prepareRemoteUpgradeHelperDirectory(ssh, helperPath);
28027
- helperPrepared = true;
28028
28095
  await ssh.uploadText(helperPath, release_assets_default, 384);
28029
28096
  const trustedRootPath = join2(dirname(helperPath), SIGSTORE_PUBLIC_GOOD_TRUSTED_ROOT_FILENAME);
28030
28097
  await ssh.uploadText(trustedRootPath, SIGSTORE_PUBLIC_GOOD_TRUSTED_ROOT_JSONL, 384);
28098
+ upgradeExecutionRequested = true;
28031
28099
  execution = await ssh.exec(command, timeoutMs);
28032
28100
  } catch (error) {
28101
+ if (upgradeExecutionRequested && error instanceof SshCommandOutcomeUnknownError) {
28102
+ throw remoteUpgradeOutcomeUnknown(error, helperPath);
28103
+ }
28033
28104
  executionFailed = true;
28034
28105
  executionError = error;
28035
28106
  }
28036
28107
  let cleanupFailed = false;
28037
28108
  let cleanupError;
28038
- if (helperPrepared) {
28039
- try {
28040
- await removeRemoteUpgradeHelper(ssh, helperPath);
28041
- } catch (error) {
28042
- cleanupFailed = true;
28043
- cleanupError = error;
28044
- }
28109
+ try {
28110
+ await removeRemoteUpgradeHelper(ssh, helperPath);
28111
+ } catch (error) {
28112
+ cleanupFailed = true;
28113
+ cleanupError = error;
28045
28114
  }
28046
28115
  return officialUpgradeOutcome({
28047
28116
  execution,
28048
28117
  executionFailed,
28049
28118
  executionError,
28050
28119
  cleanupFailed,
28051
- cleanupError
28120
+ cleanupError,
28121
+ helperPath,
28122
+ upgradeExecutionRequested
28052
28123
  });
28053
28124
  }
28054
28125
  function assertSafeGithubProxy(value) {
@@ -30472,7 +30543,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
30472
30543
  // package.json
30473
30544
  var package_default = {
30474
30545
  name: "@supacloud/admin",
30475
- version: "0.10.0",
30546
+ version: "0.10.2",
30476
30547
  description: "Platform administration CLI for SupaCloud operators",
30477
30548
  type: "module",
30478
30549
  main: "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/admin",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Platform administration CLI for SupaCloud operators",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",