Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1426,8 +1426,12 @@ install_nemoclaw() {
# floor. The source-checkout branch intentionally skips this — a developer
# running ./scripts/install.sh manages their own openshell. The script is
# idempotent on the happy path. See #2272.
spin "Installing OpenShell CLI" bash "${NEMOCLAW_SOURCE_ROOT}/scripts/install-openshell.sh"
prefer_user_local_openshell
if truthy_env "${NEMOCLAW_DEFER_OPENSHELL_INSTALL:-}"; then
info "Deferring OpenShell CLI installation until after pre-upgrade backup."
else
spin "Installing OpenShell CLI" bash "${NEMOCLAW_SOURCE_ROOT}/scripts/install-openshell.sh"
prefer_user_local_openshell
fi
fi

refresh_path
Expand Down Expand Up @@ -1576,6 +1580,54 @@ resolve_existing_cli_runner() {
return 1
}

prepare_current_cli_for_preupgrade_backup() {
local old_defer="${NEMOCLAW_DEFER_OPENSHELL_INSTALL:-__unset__}"
info "Preparing current ${_CLI_DISPLAY} CLI for legacy OpenShell backup retry…"
export NEMOCLAW_DEFER_OPENSHELL_INSTALL=1
install_nemoclaw
if [[ "$old_defer" == "__unset__" ]]; then
unset NEMOCLAW_DEFER_OPENSHELL_INSTALL
else
export NEMOCLAW_DEFER_OPENSHELL_INSTALL="$old_defer"
fi
verify_nemoclaw
}

resolve_prepared_cli_runner() {
if [[ -n "${_CLI_PATH:-}" && -x "$_CLI_PATH" ]] && is_real_nemoclaw_cli "$_CLI_PATH" "$_CLI_BIN"; then
printf "%s" "$_CLI_PATH"
return 0
fi
resolve_existing_cli_runner
}

run_preupgrade_backup() {
local old_cli_runner="$1" old_openshell_version="$2"

if "$old_cli_runner" backup-all 2>&1; then
return 0
fi

if ! legacy_openshell_gateway_upgrade_needed "$old_openshell_version"; then
return 1
fi

warn "Pre-upgrade backup with the existing ${_CLI_BIN} CLI failed."
warn "Retrying with the current ${_CLI_DISPLAY} CLI before retiring the legacy OpenShell gateway."
if ! prepare_current_cli_for_preupgrade_backup; then
warn "Could not prepare the current ${_CLI_DISPLAY} CLI for backup retry."
return 1
fi

local retry_cli_runner=""
if ! retry_cli_runner="$(resolve_prepared_cli_runner)"; then
warn "Could not locate the current ${_CLI_BIN} CLI for backup retry."
return 1
fi

"$retry_cli_runner" backup-all 2>&1
}

installed_openshell_version() {
command_exists openshell || return 1
openshell --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1
Expand Down Expand Up @@ -1734,7 +1786,7 @@ preinstall_backup_and_retire_legacy_gateway() {
fi

info "Backing up ${sandbox_count} sandbox(es) before upgrading OpenShell…"
if ! "$old_cli_runner" backup-all 2>&1; then
if ! run_preupgrade_backup "$old_cli_runner" "$old_openshell_version"; then
if legacy_openshell_gateway_upgrade_needed "$old_openshell_version"; then
error "Pre-upgrade backup failed. Aborting before retiring the legacy OpenShell gateway."
fi
Expand Down
4 changes: 3 additions & 1 deletion test/e2e/validation_suites/lib/sandbox_lifecycle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ sandbox_lifecycle_assert_status_fields_present() {
return 1
}
if [[ "${E2E_DRY_RUN:-0}" != "1" ]]; then
local status_output_lower
status_output_lower="$(printf '%s' "${SANDBOX_LIFECYCLE_LAST_OUTPUT}" | tr '[:upper:]' '[:lower:]')"
for field in status gateway sandbox; do
[[ "${SANDBOX_LIFECYCLE_LAST_OUTPUT,,}" == *"${field}"* ]] || {
[[ "${status_output_lower}" == *"${field}"* ]] || {
sandbox_lifecycle_fail "${id}" "missing status field: ${field}"
return 1
}
Expand Down
60 changes: 55 additions & 5 deletions test/install-openshell-upgrade-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ function runPreinstallUpgradeGuard(
env: Record<string, string> = {},
options: {
backupSucceeds?: boolean;
fallbackBackupSucceeds?: boolean;
fallbackAvailable?: boolean;
hasCli?: boolean;
openshellVersion?: string;
supportsBackupAll?: boolean;
Expand All @@ -29,17 +31,21 @@ function runPreinstallUpgradeGuard(
const cliLog = path.join(tmp, "cli.log");
const openshellLog = path.join(tmp, "openshell.log");
const fakeCli = path.join(bin, "nemoclaw");
const currentCli = path.join(bin, "nemoclaw-current");
const preparedFlag = path.join(tmp, "prepared-current-cli");

fs.mkdirSync(path.join(home, ".nemoclaw"), { recursive: true });
fs.mkdirSync(bin, { recursive: true });
fs.writeFileSync(path.join(home, ".nemoclaw", "sandboxes.json"), '{"sandboxes":{"alpha":{}}}');
const supportsBackupAll = options.supportsBackupAll === false ? "0" : "1";
const backupSucceeds = options.backupSucceeds === false ? "0" : "1";
const fallbackAvailable = options.fallbackAvailable === true ? "1" : "0";
const fallbackBackupSucceeds = options.fallbackBackupSucceeds === false ? "0" : "1";
const openshellVersion = options.openshellVersion ?? "0.0.36";
writeExecutable(
fakeCli,
`#!/usr/bin/env bash
printf '%s\\n' "$*" >> "${cliLog}"
printf 'old:%s\\n' "$*" >> "${cliLog}"
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
if [ "${supportsBackupAll}" = "1" ]; then
printf 'nemoclaw backup-all\\n'
Expand All @@ -52,10 +58,27 @@ if [ "$1" = "backup-all" ] && [ "\${2:-}" != "--help" ] && [ "${backupSucceeds}"
exit 3
fi
exit 0
`,
);
writeExecutable(
currentCli,
`#!/usr/bin/env bash
printf 'current:%s\\n' "$*" >> "${cliLog}"
if [ "$1" = "--version" ]; then
printf 'nemoclaw v0.1.0\\n'
exit 0
fi
if [ "$1" = "backup-all" ] && [ "${fallbackBackupSucceeds}" != "1" ]; then
exit 4
fi
exit 0
`,
);

const resolveCli = options.hasCli === false ? "return 1" : `printf '%s' "${fakeCli}"`;
const resolveCli =
options.hasCli === false
? "return 1"
: `[ -f "${preparedFlag}" ] && printf '%s' "${currentCli}" || printf '%s' "${fakeCli}"`;
const snippet = `
source "${INSTALLER_PAYLOAD}" >/dev/null 2>&1
info() { printf '[INFO] %s\\n' "$*"; }
Expand All @@ -66,6 +89,13 @@ exit 0
command_exists() { [ "$1" = "openshell" ]; }
installed_openshell_version() { printf '${openshellVersion}'; }
resolve_existing_cli_runner() { ${resolveCli}; }
prepare_current_cli_for_preupgrade_backup() {
printf 'prepare-current\\n' >> "${cliLog}"
[ "${fallbackAvailable}" = "1" ] || return 1
touch "${preparedFlag}"
_CLI_PATH="${currentCli}"
return 0
}
openshell() { printf '%s\\n' "$*" >> "${openshellLog}"; return 0; }
preinstall_backup_and_retire_legacy_gateway
printf 'RESTORE=%s\\n' "\${NEMOCLAW_RESTORE_LATEST_BACKUP_ON_RECREATE:-}"
Expand Down Expand Up @@ -127,7 +157,25 @@ describe("install.sh OpenShell 0.0.37 gateway upgrade prompt", () => {
expect(result.stdout).toContain("Accepted experimental OpenShell gateway upgrade");
expect(result.stdout).toContain("RESTORE=1");
expect(cliLog).toContain("--help");
expect(cliLog).toContain("backup-all");
expect(cliLog).toContain("old:backup-all");
expect(openshellLog).toContain("gateway destroy -g nemoclaw");
});

it("retries legacy backup with the current CLI before retiring the gateway", () => {
const { result, cliLog, openshellLog } = runPreinstallUpgradeGuard(
{
NON_INTERACTIVE: "1",
NEMOCLAW_ACCEPT_EXPERIMENTAL_OPENSHELL_UPGRADE: "1",
},
{ backupSucceeds: false, fallbackAvailable: true },
);

expect(result.status).toBe(0);
expect(result.stdout + result.stderr).toContain("Retrying with the current NemoClaw CLI");
expect(result.stdout).toContain("RESTORE=1");
expect(cliLog.split(/\r?\n/)).toContain("old:backup-all");
expect(cliLog.split(/\r?\n/)).toContain("prepare-current");
expect(cliLog.split(/\r?\n/)).toContain("current:backup-all");
expect(openshellLog).toContain("gateway destroy -g nemoclaw");
});

Expand All @@ -143,7 +191,8 @@ describe("install.sh OpenShell 0.0.37 gateway upgrade prompt", () => {
expect(result.status).not.toBe(0);
expect(result.stdout + result.stderr).toContain("Pre-upgrade backup failed");
expect(cliLog).toContain("--help");
expect(cliLog.split(/\r?\n/)).toContain("backup-all");
expect(cliLog.split(/\r?\n/)).toContain("old:backup-all");
expect(cliLog.split(/\r?\n/)).toContain("prepare-current");
expect(openshellLog).toBe("");
});

Expand All @@ -159,8 +208,9 @@ describe("install.sh OpenShell 0.0.37 gateway upgrade prompt", () => {
expect(result.stdout + result.stderr).toContain(
"Fix the OpenShell gateway state, rerun 'nemoclaw backup-all', then rerun the installer.",
);
expect(cliLog.split(/\r?\n/)).toContain("backup-all");
expect(cliLog.split(/\r?\n/)).toContain("old:backup-all");
expect(cliLog).not.toContain("--help");
expect(cliLog).not.toContain("prepare-current");
expect(openshellLog).toBe("");
});

Expand Down
Loading