@supacloud/admin 0.19.0 → 0.20.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 +26 -0
- package/dist/index.js +94 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -101,6 +101,9 @@ npx @supacloud/admin ssh ping
|
|
|
101
101
|
npx @supacloud/admin ssh versions
|
|
102
102
|
npx @supacloud/admin ssh diagnose
|
|
103
103
|
npx @supacloud/admin ssh upgrade_status --transaction_id 11111111-1111-4111-8111-111111111111
|
|
104
|
+
npx @supacloud/admin ssh backup_cleanup_plan --before 2026-09-03 --keep_latest 2
|
|
105
|
+
npx @supacloud/admin ssh backup_cleanup_apply --before 2026-09-03 --keep_latest 2 \
|
|
106
|
+
--plan_sha256 <plan_sha256>
|
|
104
107
|
npx @supacloud/admin project create --name my-app --domain example.com \
|
|
105
108
|
--env_file /secure/path/.env.project-credentials.test --environment test
|
|
106
109
|
npx @supacloud/admin project list
|
|
@@ -118,6 +121,29 @@ plus an explicit `tree_sha256` digest. An `unknown` component remains a valid
|
|
|
118
121
|
inventory result, while any `error` component makes the CLI exit non-zero after
|
|
119
122
|
printing the structured report.
|
|
120
123
|
|
|
124
|
+
## Managed rollback backup cleanup
|
|
125
|
+
|
|
126
|
+
Use the two-step SSH cleanup flow when old platform rollback directories consume
|
|
127
|
+
disk space:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx @supacloud/admin ssh backup_cleanup_plan \
|
|
131
|
+
--before 2026-09-03 --keep_latest 2
|
|
132
|
+
# review plan_sha256 and the candidate list
|
|
133
|
+
npx @supacloud/admin ssh backup_cleanup_apply \
|
|
134
|
+
--before 2026-09-03 --keep_latest 2 \
|
|
135
|
+
--plan_sha256 <plan_sha256>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The plan is read-only. Apply requires the exact digest from the plan and
|
|
139
|
+
recomputes it immediately before deletion. Only completed `committed` or
|
|
140
|
+
`rolled_back` transaction directories directly under
|
|
141
|
+
`/opt/supacloud/backups` are eligible; the newest `keep_latest` directories
|
|
142
|
+
are retained, and candidates must be older than `before`. Symlinks,
|
|
143
|
+
mountpoints, malformed transaction names, missing status files, and every path
|
|
144
|
+
outside the managed backup root are refused. Production runs also require the
|
|
145
|
+
normal `--confirm-production host:<ssh-host>[:port]` confirmation.
|
|
146
|
+
|
|
121
147
|
## Verified platform upgrades
|
|
122
148
|
|
|
123
149
|
Use one explicit Admin command to upgrade Management, Web Console, and an
|
package/dist/index.js
CHANGED
|
@@ -25886,12 +25886,13 @@ var ACTION_POLICY = {
|
|
|
25886
25886
|
"exec",
|
|
25887
25887
|
"troubleshoot",
|
|
25888
25888
|
"container_logs",
|
|
25889
|
+
"backup_cleanup_plan",
|
|
25889
25890
|
"tenant_list",
|
|
25890
25891
|
"tenant_inspect",
|
|
25891
25892
|
"tenant_diagnose",
|
|
25892
25893
|
"upgrade_status"
|
|
25893
25894
|
],
|
|
25894
|
-
write: ["setup", "install", "upgrade", "tenant_migrate"]
|
|
25895
|
+
write: ["setup", "install", "upgrade", "backup_cleanup_apply", "tenant_migrate"]
|
|
25895
25896
|
}
|
|
25896
25897
|
};
|
|
25897
25898
|
var DYNAMIC_ACTION_FIELDS = {
|
|
@@ -25902,7 +25903,8 @@ var REFLESS_WRITE_ACTIONS = new Set([
|
|
|
25902
25903
|
"project.create",
|
|
25903
25904
|
"ssh.setup",
|
|
25904
25905
|
"ssh.install",
|
|
25905
|
-
"ssh.upgrade"
|
|
25906
|
+
"ssh.upgrade",
|
|
25907
|
+
"ssh.backup_cleanup_apply"
|
|
25906
25908
|
]);
|
|
25907
25909
|
var SSH_PROJECT_REF_ACTIONS = new Set(["tenant_manage", "tenant_inspect", "tenant_diagnose"]);
|
|
25908
25910
|
var PLATFORM_PROJECT_REF_ACTIONS = new Set([
|
|
@@ -28506,6 +28508,9 @@ var WEB_TREE_AFTER_LABEL = "SUPACLOUD_WEB_TREE_SHA256_AFTER=";
|
|
|
28506
28508
|
var WEB_MARKER_AFTER_LABEL = "SUPACLOUD_WEB_MARKER_BASE64_AFTER=";
|
|
28507
28509
|
var WEB_ROOT_REAL_AFTER_LABEL = "SUPACLOUD_WEB_ROOT_REAL_AFTER=";
|
|
28508
28510
|
var WEB_ROOT_ID_AFTER_LABEL = "SUPACLOUD_WEB_ROOT_ID_AFTER=";
|
|
28511
|
+
var BACKUP_CLEANUP_ROOT = "/opt/supacloud/backups";
|
|
28512
|
+
var BACKUP_CLEANUP_PLAN_SHA256 = /^[a-f0-9]{64}$/;
|
|
28513
|
+
var BACKUP_CLEANUP_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
28509
28514
|
function fixedBinaryProbeCommand(executablePath, versionArgument) {
|
|
28510
28515
|
const executable = quoteEnvValue(executablePath);
|
|
28511
28516
|
return [
|
|
@@ -28617,6 +28622,66 @@ function assertSafeReleaseTag(value) {
|
|
|
28617
28622
|
}
|
|
28618
28623
|
return value;
|
|
28619
28624
|
}
|
|
28625
|
+
function assertBackupCleanupDate(value) {
|
|
28626
|
+
if (!BACKUP_CLEANUP_DATE.test(value))
|
|
28627
|
+
throw new Error("before must be an ISO date (YYYY-MM-DD)");
|
|
28628
|
+
const parsed = Date.parse(`${value}T00:00:00Z`);
|
|
28629
|
+
if (!Number.isFinite(parsed) || new Date(parsed).toISOString().slice(0, 10) !== value) {
|
|
28630
|
+
throw new Error("before must be a valid ISO date (YYYY-MM-DD)");
|
|
28631
|
+
}
|
|
28632
|
+
return value;
|
|
28633
|
+
}
|
|
28634
|
+
function assertBackupCleanupKeepLatest(value) {
|
|
28635
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 100) {
|
|
28636
|
+
throw new Error("keep_latest must be an integer between 1 and 100");
|
|
28637
|
+
}
|
|
28638
|
+
return value;
|
|
28639
|
+
}
|
|
28640
|
+
function assertBackupCleanupPlanSha256(value) {
|
|
28641
|
+
if (!BACKUP_CLEANUP_PLAN_SHA256.test(value))
|
|
28642
|
+
throw new Error("plan_sha256 must be a lowercase SHA-256 digest");
|
|
28643
|
+
return value;
|
|
28644
|
+
}
|
|
28645
|
+
function backupCleanupCommand(args) {
|
|
28646
|
+
const before = quoteEnvValue(args.before);
|
|
28647
|
+
const keepLatest = String(args.keepLatest);
|
|
28648
|
+
const expectedPlan = args.planSha256 ? quoteEnvValue(args.planSha256) : "";
|
|
28649
|
+
const manifest = [
|
|
28650
|
+
"set -euo pipefail",
|
|
28651
|
+
"export TMPDIR=/dev/shm",
|
|
28652
|
+
...args.apply ? [buildUpgradeLockScript(SUPACLOUD_UPGRADE_LOCK_PATH)] : [],
|
|
28653
|
+
`ROOT=${quoteEnvValue(BACKUP_CLEANUP_ROOT)}`,
|
|
28654
|
+
`BEFORE=${before}`,
|
|
28655
|
+
`KEEP_LATEST=${keepLatest}`,
|
|
28656
|
+
"collect() {",
|
|
28657
|
+
` find -P "$ROOT" -mindepth 1 -maxdepth 1 -type d -name 'supacloud-*' -print0 |`,
|
|
28658
|
+
" while IFS= read -r -d '' path; do",
|
|
28659
|
+
" base=${path##*/}",
|
|
28660
|
+
' case "$base" in supacloud-[A-Za-z0-9._-]*-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]Z-[A-Za-z0-9._-]*) ;; *) continue ;; esac',
|
|
28661
|
+
' status_file="$path/transaction-status.json"',
|
|
28662
|
+
' [ -f "$status_file" ] && [ ! -L "$status_file" ] || continue',
|
|
28663
|
+
` status=$(sed -nE 's/^[[:space:]]*"status"[[:space:]]*:[[:space:]]*"([^"]+)".*$/\\1/p' "$status_file" | head -1)`,
|
|
28664
|
+
' case "$status" in committed|rolled_back) ;; *) continue ;; esac',
|
|
28665
|
+
` finished=$(sed -nE 's/^[[:space:]]*"finishedAt"[[:space:]]*:[[:space:]]*"([^"]+)".*$/\\1/p' "$status_file" | head -1)`,
|
|
28666
|
+
" finished_date=${finished:0:10}",
|
|
28667
|
+
' [[ "$finished_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || continue',
|
|
28668
|
+
` bytes=$(du -sb -- "$path" | awk '{print $1}')`,
|
|
28669
|
+
` printf '%s\\t%s\\t%s\\t%s\\n' "$finished" "$status" "$bytes" "$path"`,
|
|
28670
|
+
" done",
|
|
28671
|
+
"}",
|
|
28672
|
+
`manifest=$(collect | sort -t $'\\t' -k1,1r | awk -F '\\t' -v keep="$KEEP_LATEST" -v before="$BEFORE" 'NR > keep && substr($1,1,10) < before')`,
|
|
28673
|
+
`plan_sha256=$(printf '%s' "$manifest" | sha256sum | awk '{print $1}')`,
|
|
28674
|
+
'echo "plan_sha256=$plan_sha256"',
|
|
28675
|
+
'echo "before=$BEFORE keep_latest=$KEEP_LATEST"',
|
|
28676
|
+
`if [ -n "$manifest" ]; then printf '%s\\n' "$manifest"; else echo 'candidates=0'; fi`
|
|
28677
|
+
];
|
|
28678
|
+
if (!args.apply)
|
|
28679
|
+
return manifest.join(`
|
|
28680
|
+
`);
|
|
28681
|
+
manifest.push(`expected=${expectedPlan}`, `[ "$plan_sha256" = "$expected" ] || { echo 'plan_sha256 mismatch; refusing cleanup' >&2; exit 65; }`, "freed=0", "deleted=0", 'if [ -n "$manifest" ]; then', " while IFS=$'\\t' read -r finished status bytes path; do", ' [ -n "$path" ] || continue', ` case "$path" in "$ROOT"/supacloud-*) ;; *) echo 'candidate escaped backup root' >&2; exit 66 ;; esac`, ' [ -d "$path" ] && [ ! -L "$path" ] || { echo "candidate changed: $path" >&2; exit 67; }', ' mountpoint -q -- "$path" && { echo "mountpoint candidate: $path" >&2; exit 68; }', ' find -P -- "$path" -xdev -depth -delete', ' [ ! -e "$path" ] || { echo "candidate was not removed: $path" >&2; exit 69; }', " freed=$((freed + bytes))", " deleted=$((deleted + 1))", ' done <<< "$manifest"', "fi", 'echo "deleted=$deleted freed_bytes=$freed"', "df -B1 / | tail -1");
|
|
28682
|
+
return manifest.join(`
|
|
28683
|
+
`);
|
|
28684
|
+
}
|
|
28620
28685
|
function assertExactStableVersion2(value, fieldName) {
|
|
28621
28686
|
const normalized = value.startsWith("v") ? value.slice(1) : value;
|
|
28622
28687
|
if (!EXACT_STABLE_SEMVER.test(normalized)) {
|
|
@@ -29349,7 +29414,7 @@ function platformVersionsToolResult(report) {
|
|
|
29349
29414
|
}
|
|
29350
29415
|
function registerSshTools(server, ssh) {
|
|
29351
29416
|
server.tool("ssh", `Server management via SSH. Available before & after SupaCloud installation.
|
|
29352
|
-
Actions: ping, setup, install, upgrade, upgrade_status, versions, diagnose, exec, troubleshoot, container_logs, tenant_manage, tenant_list, tenant_inspect, tenant_diagnose, tenant_migrate`, {
|
|
29417
|
+
Actions: ping, setup, install, upgrade, upgrade_status, versions, diagnose, exec, troubleshoot, container_logs, backup_cleanup_plan, backup_cleanup_apply, tenant_manage, tenant_list, tenant_inspect, tenant_diagnose, tenant_migrate`, {
|
|
29353
29418
|
action: withDescription(stringEnum([
|
|
29354
29419
|
"ping",
|
|
29355
29420
|
"setup",
|
|
@@ -29361,6 +29426,8 @@ Actions: ping, setup, install, upgrade, upgrade_status, versions, diagnose, exec
|
|
|
29361
29426
|
"exec",
|
|
29362
29427
|
"troubleshoot",
|
|
29363
29428
|
"container_logs",
|
|
29429
|
+
"backup_cleanup_plan",
|
|
29430
|
+
"backup_cleanup_apply",
|
|
29364
29431
|
"tenant_manage",
|
|
29365
29432
|
"tenant_list",
|
|
29366
29433
|
"tenant_inspect",
|
|
@@ -29381,6 +29448,9 @@ Actions: ping, setup, install, upgrade, upgrade_status, versions, diagnose, exec
|
|
|
29381
29448
|
artifact_transport: optional(stringEnum(["local", "remote"]), "[upgrade] Download verified release assets locally or on the server (default: remote)"),
|
|
29382
29449
|
github_proxy: optional(Type.String(), "[install/upgrade] Explicit GitHub proxy prefix, or direct/none"),
|
|
29383
29450
|
focus: optional(stringEnum(["all", "containers", "database", "network", "disk", "logs"]), "[troubleshoot] Focus area"),
|
|
29451
|
+
before: optional(Type.String(), "[backup_cleanup_*] Delete only completed rollback directories finished before YYYY-MM-DD"),
|
|
29452
|
+
keep_latest: optional(Type.Number(), "[backup_cleanup_*] Keep this many newest completed rollback directories (default: 2)"),
|
|
29453
|
+
plan_sha256: optional(Type.String(), "[backup_cleanup_apply] Exact plan_sha256 returned by backup_cleanup_plan"),
|
|
29384
29454
|
container: optional(Type.String(), "[container_logs] Container name"),
|
|
29385
29455
|
lines: optional(Type.Number(), "[container_logs] Number of log lines (default: 100)"),
|
|
29386
29456
|
project_ref: optional(Type.String(), "[tenant_*] Project reference ID"),
|
|
@@ -29545,6 +29615,25 @@ ${upgradeExecution.stdout.slice(-300)}${edgeBoundary}`;
|
|
|
29545
29615
|
case "versions": {
|
|
29546
29616
|
return platformVersionsToolResult(await platformVersions(ssh));
|
|
29547
29617
|
}
|
|
29618
|
+
case "backup_cleanup_plan":
|
|
29619
|
+
case "backup_cleanup_apply": {
|
|
29620
|
+
if (typeof args.before !== "string")
|
|
29621
|
+
throw new Error("'before' required");
|
|
29622
|
+
const before = assertBackupCleanupDate(args.before);
|
|
29623
|
+
const keepLatest = args.keep_latest === undefined ? 2 : assertBackupCleanupKeepLatest(args.keep_latest);
|
|
29624
|
+
const apply = action === "backup_cleanup_apply";
|
|
29625
|
+
const planSha256 = args.plan_sha256 === undefined ? undefined : assertBackupCleanupPlanSha256(String(args.plan_sha256));
|
|
29626
|
+
if (apply && !planSha256)
|
|
29627
|
+
throw new Error("'plan_sha256' required for backup_cleanup_apply");
|
|
29628
|
+
if (!apply && planSha256)
|
|
29629
|
+
throw new Error("plan_sha256 is only valid for backup_cleanup_apply");
|
|
29630
|
+
const r = await ssh.exec(backupCleanupCommand({ before, keepLatest, planSha256, apply }), 120000);
|
|
29631
|
+
if (!r.success) {
|
|
29632
|
+
throw new Error(`Backup cleanup ${apply ? "apply" : "plan"} failed (exit ${r.code}): ${(r.stderr || r.stdout).slice(-1000)}`);
|
|
29633
|
+
}
|
|
29634
|
+
text = r.stdout;
|
|
29635
|
+
break;
|
|
29636
|
+
}
|
|
29548
29637
|
case "diagnose": {
|
|
29549
29638
|
const cmds = [
|
|
29550
29639
|
"set -o pipefail",
|
|
@@ -32571,7 +32660,7 @@ Actions: list_releases, get_release, upload_release, activate_release`, {
|
|
|
32571
32660
|
// package.json
|
|
32572
32661
|
var package_default = {
|
|
32573
32662
|
name: "@supacloud/admin",
|
|
32574
|
-
version: "0.
|
|
32663
|
+
version: "0.20.1",
|
|
32575
32664
|
description: "Platform administration CLI for SupaCloud operators",
|
|
32576
32665
|
type: "module",
|
|
32577
32666
|
main: "./dist/index.js",
|
|
@@ -32606,7 +32695,7 @@ var package_default = {
|
|
|
32606
32695
|
},
|
|
32607
32696
|
devDependencies: {
|
|
32608
32697
|
"@types/bun": "^1.4.0",
|
|
32609
|
-
"@types/ssh2": "^1.15.
|
|
32698
|
+
"@types/ssh2": "^1.15.6",
|
|
32610
32699
|
typescript: "^7.0.2"
|
|
32611
32700
|
}
|
|
32612
32701
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "Platform administration CLI for SupaCloud operators",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/bun": "^1.4.0",
|
|
38
|
-
"@types/ssh2": "^1.15.
|
|
38
|
+
"@types/ssh2": "^1.15.6",
|
|
39
39
|
"typescript": "^7.0.2"
|
|
40
40
|
}
|
|
41
41
|
}
|