fix(onboard): preserve gateway settings on rerun (#111569)

* fix(onboard): preserve gateway config on rerun

* fix(onboard): scope remote secrets to endpoint

* fix(onboard): honor rerun override boundaries

* fix(onboard): secure inherited tailscale auth

* fix(onboard): honor explicit token auth

* fix(onboard): enforce funnel auth on rerun

* fix(onboard): preserve env password on rerun
This commit is contained in:
Peter Steinberger
2026-07-19 20:18:34 -07:00
committed by GitHub
parent cacd98304e
commit 6d39d3cf0b
11 changed files with 262 additions and 35 deletions
+11
View File
@@ -133,6 +133,7 @@ describe("registerOnboardCommand", () => {
await runCli(["onboard"]);
expect(setupWizardOptions().installDaemon).toBeUndefined();
expect(setupWizardOptions().tailscaleResetOnExit).toBeUndefined();
});
it("sets installDaemon from explicit install flags and prioritizes --skip-daemon", async () => {
@@ -172,6 +173,16 @@ describe("registerOnboardCommand", () => {
expect(setupWizardOptions().skipBootstrap).toBe(true);
});
it("forwards explicit --tailscale-reset-on-exit", async () => {
await runCli(["onboard", "--tailscale-reset-on-exit"]);
expect(setupWizardOptions().tailscaleResetOnExit).toBe(true);
});
it("forwards explicit --no-tailscale-reset-on-exit", async () => {
await runCli(["onboard", "--no-tailscale-reset-on-exit"]);
expect(setupWizardOptions().tailscaleResetOnExit).toBe(false);
});
it("forwards remote seed flags to setup wizard options", async () => {
const remoteToken = ["fixture", "value"].join("-");
await runCli([
+10 -1
View File
@@ -32,6 +32,13 @@ export function resolveInstallDaemonFlag(command: Command): boolean | undefined
return undefined;
}
export function resolveTailscaleResetOnExitFlag(command: Command): boolean | undefined {
if (command.getOptionValueSource("tailscaleResetOnExit") !== "cli") {
return undefined;
}
return Boolean(command.getOptionValue("tailscaleResetOnExit"));
}
const MODERN_ONBOARD_OPTION_KEYS = new Set([
"modern",
"workspace",
@@ -218,6 +225,7 @@ export function registerOnboardCommand(program: Command): void {
.option("--remote-token <token>", "Remote Gateway token (optional)")
.option("--tailscale <mode>", "Tailscale: off|serve|funnel")
.option("--tailscale-reset-on-exit", "Reset tailscale serve/funnel on exit")
.option("--no-tailscale-reset-on-exit", "Keep tailscale serve/funnel after exit")
.option("--install-daemon", "Install gateway service")
.option("--no-install-daemon", "Skip gateway service install")
.option("--skip-daemon", "Skip gateway service install")
@@ -324,6 +332,7 @@ export function registerOnboardCommand(program: Command): void {
return;
}
const installDaemon = resolveInstallDaemonFlag(commandRuntime);
const tailscaleResetOnExit = resolveTailscaleResetOnExitFlag(commandRuntime);
const gatewayPort = parsePort(opts.gatewayPort);
const { setupWizardCommand } = await import("../../commands/onboard.js");
await setupWizardCommand(
@@ -345,7 +354,7 @@ export function registerOnboardCommand(program: Command): void {
remoteUrl: opts.remoteUrl as string | undefined,
remoteToken: opts.remoteToken as string | undefined,
tailscale: opts.tailscale as TailscaleMode | undefined,
tailscaleResetOnExit: Boolean(opts.tailscaleResetOnExit),
tailscaleResetOnExit,
reset: Boolean(opts.reset),
resetScope: opts.resetScope as ResetScope | undefined,
installDaemon,
+9
View File
@@ -161,9 +161,16 @@ describe("registerSetupCommand", () => {
expect(setupWizardCommandMock).toHaveBeenCalledWith(lastWizardOptions(), runtime);
expect(lastWizardOptions()?.workspace).toBe("/tmp/ws");
expect(lastWizardOptions()?.tailscaleResetOnExit).toBeUndefined();
expect(setupCommandMock).not.toHaveBeenCalled();
});
it("forwards explicit --no-tailscale-reset-on-exit", async () => {
await runCli(["setup", "--no-tailscale-reset-on-exit"]);
expect(lastWizardOptions()?.tailscaleResetOnExit).toBe(false);
});
it("runs baseline setup command when --baseline is set", async () => {
await runCli(["setup", "--baseline", "--workspace", "/tmp/ws"]);
@@ -219,6 +226,7 @@ describe("registerSetupCommand", () => {
"--skip-search",
"--skip-skills",
"--skip-bootstrap",
"--tailscale-reset-on-exit",
"--node-manager",
"pnpm",
"--json",
@@ -237,6 +245,7 @@ describe("registerSetupCommand", () => {
skipSearch: true,
skipSkills: true,
skipBootstrap: true,
tailscaleResetOnExit: true,
nodeManager: "pnpm",
json: true,
});
+4 -1
View File
@@ -19,6 +19,7 @@ import {
pickOnboardAuthOptionValues,
registerOnboardAuthOptions,
resolveInstallDaemonFlag,
resolveTailscaleResetOnExitFlag,
} from "./register.onboard.js";
const SYSTEM_AGENT_OPTION_NAMES = new Set(["message", "yes", "json"]);
@@ -93,6 +94,7 @@ async function runOnboardingEntry(
return;
}
const installDaemon = resolveInstallDaemonFlag(commandRuntime);
const tailscaleResetOnExit = resolveTailscaleResetOnExitFlag(commandRuntime);
const gatewayPort = parsePort(options.gatewayPort);
const { setupWizardCommand } = await import("../../commands/onboard.js");
await setupWizardCommand(
@@ -113,7 +115,7 @@ async function runOnboardingEntry(
gatewayTokenRefEnv: optionalString(options.gatewayTokenRefEnv),
gatewayPassword: optionalString(options.gatewayPassword),
tailscale: options.tailscale as TailscaleMode | undefined,
tailscaleResetOnExit: Boolean(options.tailscaleResetOnExit),
tailscaleResetOnExit,
installDaemon,
daemonRuntime: options.daemonRuntime as GatewayDaemonRuntime | undefined,
skipChannels: Boolean(options.skipChannels),
@@ -199,6 +201,7 @@ export function registerSetupCommand(program: Command): void {
.option("--gateway-password <password>", "Gateway password (password auth)")
.option("--tailscale <mode>", "Tailscale: off|serve|funnel")
.option("--tailscale-reset-on-exit", "Reset tailscale serve/funnel on exit")
.option("--no-tailscale-reset-on-exit", "Keep tailscale serve/funnel after exit")
.option("--install-daemon", "Install gateway service")
.option("--no-install-daemon", "Skip gateway service install")
.option("--skip-daemon", "Skip gateway service install")