@supacloud/admin 0.7.8 → 0.7.10

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 (2) hide show
  1. package/dist/index.js +50 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25215,7 +25215,10 @@ class SshTransport {
25215
25215
  let connectionReusable = true;
25216
25216
  let timeoutHandle;
25217
25217
  try {
25218
- const operationPromise = openSftp(conn).then(operation);
25218
+ const operationPromise = openSftp(conn).then(async (sftp) => {
25219
+ await operation(sftp);
25220
+ sftp.end();
25221
+ });
25219
25222
  await Promise.race([
25220
25223
  operationPromise,
25221
25224
  new Promise((_resolve, reject) => {
@@ -26396,6 +26399,7 @@ async function prepareLocalUpgradeBundle(request) {
26396
26399
  var REMOTE_STAGE_ROOT = "/var/lib/supacloud/upgrade-staging";
26397
26400
  var REMOTE_RUN_ROOT = "/var/lib/supacloud/upgrade-runs";
26398
26401
  var REMOTE_LOG_ROOT = "/var/log/supacloud";
26402
+ var REMOTE_UPLOAD_ROOT = "/var/tmp";
26399
26403
  var POLL_INTERVAL_MS = 2000;
26400
26404
  var STATE_READ_ATTEMPTS = 3;
26401
26405
  var REMOTE_STATE_READ_TIMEOUT_MS = 15000;
@@ -26430,9 +26434,9 @@ function trustedInstalledGithubFunction() {
26430
26434
  ].join(`
26431
26435
  `);
26432
26436
  }
26433
- function remotePaths(runId) {
26437
+ function buildRemoteUpgradePaths(runId) {
26434
26438
  return {
26435
- drop: `/tmp/.supacloud-upgrade-upload-${runId}`,
26439
+ drop: `${REMOTE_UPLOAD_ROOT}/.supacloud-upgrade-upload-${runId}`,
26436
26440
  stage: `${REMOTE_STAGE_ROOT}/${runId}`,
26437
26441
  status: `${REMOTE_RUN_ROOT}/${runId}.status`,
26438
26442
  log: `${REMOTE_LOG_ROOT}/upgrade-${runId}.log`,
@@ -26489,9 +26493,19 @@ function requiredRemoteBytes(bundle) {
26489
26493
  const transferBytes = [...bundle.files, ...bundle.verifierArchive ? [bundle.verifierArchive] : []].reduce((total, upload) => total + upload.size, 0);
26490
26494
  return transferBytes * 3 + 512 * 1024 * 1024;
26491
26495
  }
26492
- function prepareDropCommand(paths, bundle) {
26496
+ function remoteUploadRootGate() {
26497
+ return [
26498
+ `UPLOAD_ROOT=${quoteShell2(REMOTE_UPLOAD_ROOT)}`,
26499
+ `test -d "$UPLOAD_ROOT" && test ! -L "$UPLOAD_ROOT" || { echo 'Remote upload root must be a directory, not a symlink' >&2; exit 1; }`,
26500
+ `test "$(stat -c '%u' "$UPLOAD_ROOT")" = 0 || { echo 'Remote upload root must be owned by root' >&2; exit 1; }`,
26501
+ `UPLOAD_ROOT_MODE=$(stat -c '%a' "$UPLOAD_ROOT")`,
26502
+ `case "$UPLOAD_ROOT_MODE" in [0-7]|[0-7][0-7]|[0-7][0-7][0-7]|[0-7][0-7][0-7][0-7]) ;; *) echo 'Remote upload root has invalid permission bits' >&2; exit 1 ;; esac`,
26503
+ "(( (8#$UPLOAD_ROOT_MODE & 01000) != 0 )) || { echo 'Remote upload root must set the sticky bit' >&2; exit 1; }",
26504
+ `test -w "$UPLOAD_ROOT" && test -x "$UPLOAD_ROOT" || { echo 'Remote upload root is not writable and searchable by the SSH user' >&2; exit 1; }`
26505
+ ];
26506
+ }
26507
+ function buildPrepareDropCommand(paths, bundle) {
26493
26508
  const directories = [
26494
- paths.drop,
26495
26509
  `${paths.drop}/bundle`,
26496
26510
  `${paths.drop}/bundle/management-api`,
26497
26511
  `${paths.drop}/bundle/edge-runtime`
@@ -26502,16 +26516,18 @@ function prepareDropCommand(paths, bundle) {
26502
26516
  return [
26503
26517
  "set -euo pipefail",
26504
26518
  "umask 077",
26505
- `test ! -e ${quoteShell2(paths.drop)} || { echo 'Remote upload drop already exists' >&2; exit 1; }`,
26506
- "TMP_AVAILABLE_KB=$(df -Pk /tmp | awk 'NR == 2 { print $4 }')",
26519
+ ...remoteUploadRootGate(),
26520
+ `DROP=${quoteShell2(paths.drop)}`,
26521
+ `UPLOAD_AVAILABLE_KB=$(df -Pk "$UPLOAD_ROOT" | awk 'NR == 2 { print $4 }')`,
26507
26522
  "VAR_AVAILABLE_KB=$(df -Pk /var/lib/supacloud | awk 'NR == 2 { print $4 }')",
26508
- `test "${"$"}TMP_AVAILABLE_KB" -ge ${Math.ceil(requiredBytes / 1024)} && test "${"$"}VAR_AVAILABLE_KB" -ge ${Math.ceil(requiredBytes / 1024)} || { echo 'Insufficient remote disk space for verified upgrade staging' >&2; exit 1; }`,
26523
+ `test "${"$"}UPLOAD_AVAILABLE_KB" -ge ${Math.ceil(requiredBytes / 1024)} && test "${"$"}VAR_AVAILABLE_KB" -ge ${Math.ceil(requiredBytes / 1024)} || { echo 'Insufficient remote disk space for verified upgrade staging' >&2; exit 1; }`,
26524
+ `mkdir -m 700 -- "$DROP" || { echo 'Unable to create exclusive remote upload drop' >&2; exit 1; }`,
26509
26525
  `install -d -m 700 ${directories.map(quoteShell2).join(" ")}`
26510
26526
  ].join(`
26511
26527
  `);
26512
26528
  }
26513
26529
  async function prepareRemoteDrop(ssh, paths, bundle) {
26514
- const prepared = await ssh.exec(prepareDropCommand(paths, bundle), 30000);
26530
+ const prepared = await ssh.exec(buildPrepareDropCommand(paths, bundle), 30000);
26515
26531
  if (!prepared.success)
26516
26532
  throw remoteFailure("Unable to prepare remote upload drop", prepared);
26517
26533
  }
@@ -26716,7 +26732,10 @@ function buildAdoptDropScript(paths) {
26716
26732
  "trap 'exit 130' INT",
26717
26733
  "trap 'exit 143' TERM",
26718
26734
  `install -d -o root -g root -m 700 ${quoteShell2(REMOTE_STAGE_ROOT)} ${quoteShell2(REMOTE_RUN_ROOT)} ${quoteShell2(REMOTE_LOG_ROOT)}`,
26719
- 'test ! -e "$STAGE" && test ! -e "$STATUS" && test ! -e "$LOG"',
26735
+ 'if [ -e "$STAGE" ] || [ -L "$STAGE" ] || [ -e "$STATUS" ] || [ -L "$STATUS" ] || [ -e "$LOG" ] || [ -L "$LOG" ]; then',
26736
+ " echo 'Upgrade adoption target already exists' >&2",
26737
+ " exit 1",
26738
+ "fi",
26720
26739
  `systemctl status "$UNIT" >/dev/null 2>&1 && { echo 'Upgrade unit already exists' >&2; exit 1; } || true`,
26721
26740
  "ADOPTION_ACTIVE=true",
26722
26741
  'mv "$DROP" "$STAGE"',
@@ -26793,7 +26812,7 @@ async function startRemoteUpgrade(ssh, paths) {
26793
26812
  }
26794
26813
  throw startFailure;
26795
26814
  }
26796
- function readStateScript(paths) {
26815
+ function buildRemoteStateScript(paths) {
26797
26816
  return [
26798
26817
  "set -euo pipefail",
26799
26818
  `DROP=${quoteShell2(paths.drop)}`,
@@ -26803,11 +26822,11 @@ function readStateScript(paths) {
26803
26822
  `UNIT=${quoteShell2(paths.unit)}`,
26804
26823
  `printf 'STATUS=%s\\n' "$(sed -n '1p' "$STATUS" 2>/dev/null || true)"`,
26805
26824
  `printf 'UNIT=%s\\n' "$(systemctl is-active "$UNIT" 2>/dev/null || true)"`,
26806
- 'if [ -e "$STAGE" ]; then echo STAGE_EXISTS=yes; else echo STAGE_EXISTS=no; fi',
26825
+ 'if [ -e "$STAGE" ] || [ -L "$STAGE" ]; then echo STAGE_EXISTS=yes; else echo STAGE_EXISTS=no; fi',
26807
26826
  'if [ -d "$STAGE" ] && [ ! -L "$STAGE" ]; then echo STAGE_DIRECTORY=yes; else echo STAGE_DIRECTORY=no; fi',
26808
- 'if [ -e "$DROP" ]; then echo DROP_EXISTS=yes; else echo DROP_EXISTS=no; fi',
26809
- 'if [ -e "$STATUS" ]; then echo STATUS_EXISTS=yes; else echo STATUS_EXISTS=no; fi',
26810
- 'if [ -e "$LOG" ]; then echo LOG_EXISTS=yes; else echo LOG_EXISTS=no; fi',
26827
+ 'if [ -e "$DROP" ] || [ -L "$DROP" ]; then echo DROP_EXISTS=yes; else echo DROP_EXISTS=no; fi',
26828
+ 'if [ -e "$STATUS" ] || [ -L "$STATUS" ]; then echo STATUS_EXISTS=yes; else echo STATUS_EXISTS=no; fi',
26829
+ 'if [ -e "$LOG" ] || [ -L "$LOG" ]; then echo LOG_EXISTS=yes; else echo LOG_EXISTS=no; fi',
26811
26830
  'UNIT_LOAD_STATE=$(systemctl show --property=LoadState --value "$UNIT" 2>/dev/null || true)',
26812
26831
  `test -n "$UNIT_LOAD_STATE" || { echo 'Unable to read remote upgrade unit load state' >&2; exit 1; }`,
26813
26832
  `printf 'UNIT_LOAD=%s\\n' "$UNIT_LOAD_STATE"`,
@@ -26829,7 +26848,7 @@ function remoteStatePresence(output, field) {
26829
26848
  return presence === "yes";
26830
26849
  }
26831
26850
  async function readRemoteState(ssh, paths, timeoutMs = REMOTE_STATE_READ_TIMEOUT_MS) {
26832
- const state = await ssh.exec(rootCommand(readStateScript(paths)), timeoutMs);
26851
+ const state = await ssh.exec(rootCommand(buildRemoteStateScript(paths)), timeoutMs);
26833
26852
  if (!state.success)
26834
26853
  throw remoteFailure("Unable to read remote upgrade state", state);
26835
26854
  return {
@@ -26918,6 +26937,9 @@ function remoteEvidence(paths) {
26918
26937
  function remoteReconciliationFailure(message, failures, paths) {
26919
26938
  return new RemoteUpgradeReconciliationError(failures.map((failure) => operationError(failure, message)), `${message}; reconcile remote evidence at ${remoteEvidence(paths)}; do not retry blindly`);
26920
26939
  }
26940
+ function buildUploadDropCleanupFailure(transferFailure, cleanupFailure, paths) {
26941
+ return remoteReconciliationFailure("Local upgrade failed and upload-drop cleanup did not complete", [transferFailure, cleanupFailure], paths);
26942
+ }
26921
26943
  function failureRequiresRemoteReconciliation(error) {
26922
26944
  return error instanceof RemoteUpgradeReconciliationError;
26923
26945
  }
@@ -26938,6 +26960,14 @@ function assertSuccessfulUnitStoppedNormally(state, paths) {
26938
26960
  return;
26939
26961
  throw remoteReconciliationFailure(`Remote upgrade published SUCCEEDED but the unit stopped abnormally or ambiguously ` + `(state=${state.serviceState} load=${state.unitLoadState})`, [], paths);
26940
26962
  }
26963
+ function failedUnitReachedTerminalState(state) {
26964
+ return unitStoppedNormally(state) || state.serviceState === "failed" && state.unitLoadState === "loaded";
26965
+ }
26966
+ function assertFailedUnitReachedTerminalState(state, paths) {
26967
+ if (failedUnitReachedTerminalState(state))
26968
+ return;
26969
+ throw remoteReconciliationFailure(`Remote upgrade published FAILED but the unit state is ambiguous ` + `(state=${state.serviceState} load=${state.unitLoadState})`, [], paths);
26970
+ }
26941
26971
  function assertTerminalEvidence(state, paths) {
26942
26972
  if (state.status !== "SUCCEEDED" && !state.status.startsWith("FAILED:"))
26943
26973
  return;
@@ -27015,6 +27045,7 @@ async function awaitRemoteUpgrade(ssh, paths) {
27015
27045
  return await completedUpgradeOutput(ssh, paths);
27016
27046
  }
27017
27047
  if (state.status.startsWith("FAILED:") && !unitRunning) {
27048
+ assertFailedUnitReachedTerminalState(state, paths);
27018
27049
  await throwRemoteUpgradeFailure(ssh, paths, state.status);
27019
27050
  }
27020
27051
  if (Date.now() >= observationDeadline) {
@@ -27033,7 +27064,7 @@ async function awaitRemoteUpgrade(ssh, paths) {
27033
27064
  }
27034
27065
  async function executeLocalUpgradeTransfer(ssh, request) {
27035
27066
  const runId = randomUUID3();
27036
- const paths = remotePaths(runId);
27067
+ const paths = buildRemoteUpgradePaths(runId);
27037
27068
  const preflight = await remoteUpgradePreflight(ssh);
27038
27069
  const bundle = await prepareLocalUpgradeBundle({ ...preflight, ...request });
27039
27070
  let adopted = false;
@@ -27053,7 +27084,7 @@ async function executeLocalUpgradeTransfer(ssh, request) {
27053
27084
  try {
27054
27085
  await cleanupRemoteDrop(ssh, paths);
27055
27086
  } catch (cleanupError) {
27056
- transferError = new AggregateError([transferError, cleanupError], "Local upgrade failed and upload-drop cleanup did not complete");
27087
+ transferError = buildUploadDropCleanupFailure(transferError, cleanupError, paths);
27057
27088
  }
27058
27089
  }
27059
27090
  throw transferError;
@@ -28949,7 +28980,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
28949
28980
  // package.json
28950
28981
  var package_default = {
28951
28982
  name: "@supacloud/admin",
28952
- version: "0.7.8",
28983
+ version: "0.7.10",
28953
28984
  description: "Platform administration CLI for SupaCloud operators",
28954
28985
  type: "module",
28955
28986
  main: "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/admin",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "description": "Platform administration CLI for SupaCloud operators",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",