Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12677a928d |
@@ -193,19 +193,16 @@ describe('Unified wizard (runWizard with default skipGateway)', () => {
|
|||||||
'Your timezone': 'UTC',
|
'Your timezone': 'UTC',
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
await runWizard({
|
||||||
runWizard({
|
|
||||||
mosaicHome: tmpDir,
|
mosaicHome: tmpDir,
|
||||||
sourceDir: tmpDir,
|
sourceDir: tmpDir,
|
||||||
prompter,
|
prompter,
|
||||||
configService: createConfigService(tmpDir, tmpDir),
|
configService: createConfigService(tmpDir, tmpDir),
|
||||||
skipGatewayNpmInstall: true,
|
skipGatewayNpmInstall: true,
|
||||||
}),
|
});
|
||||||
).rejects.toThrow('Gateway configuration failed');
|
|
||||||
|
|
||||||
const logs = prompter.getLogs();
|
const logs = prompter.getLogs();
|
||||||
expect(logs.some((line) => line.includes('Gateway did not become healthy'))).toBe(true);
|
expect(logs.some((line) => line.includes('Gateway did not become healthy'))).toBe(true);
|
||||||
expect(logs.some((line) => line.includes('Gateway configuration failed'))).toBe(true);
|
|
||||||
expect(logs.some((line) => line.includes('Installation Summary'))).toBe(false);
|
expect(logs.some((line) => line.includes('Installation Summary'))).toBe(false);
|
||||||
expect(logs.some((line) => line.includes('Mosaic is ready.'))).toBe(false);
|
expect(logs.some((line) => line.includes('Mosaic is ready.'))).toBe(false);
|
||||||
expect(gatewayConfigMock).toHaveBeenCalledTimes(1);
|
expect(gatewayConfigMock).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ prompt_if_empty() {
|
|||||||
|
|
||||||
if [[ $NON_INTERACTIVE -eq 1 ]]; then
|
if [[ $NON_INTERACTIVE -eq 1 ]]; then
|
||||||
if [[ -n "$default_value" ]]; then
|
if [[ -n "$default_value" ]]; then
|
||||||
eval "$var_name=\"$default_value\""
|
printf -v "$var_name" %s "$default_value"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
echo "[mosaic-init] ERROR: --$var_name is required in non-interactive mode" >&2
|
echo "[mosaic-init] ERROR: --$var_name is required in non-interactive mode" >&2
|
||||||
@@ -115,7 +115,7 @@ prompt_if_empty() {
|
|||||||
if [[ -z "$value" && -n "$default_value" ]]; then
|
if [[ -z "$value" && -n "$default_value" ]]; then
|
||||||
value="$default_value"
|
value="$default_value"
|
||||||
fi
|
fi
|
||||||
eval "$var_name=\"$value\""
|
printf -v "$var_name" %s "$value"
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt_multiline() {
|
prompt_multiline() {
|
||||||
@@ -129,7 +129,7 @@ prompt_multiline() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $NON_INTERACTIVE -eq 1 ]]; then
|
if [[ $NON_INTERACTIVE -eq 1 ]]; then
|
||||||
eval "$var_name=\"$default_value\""
|
printf -v "$var_name" %s "$default_value"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ prompt_multiline() {
|
|||||||
if [[ -z "$value" ]]; then
|
if [[ -z "$value" ]]; then
|
||||||
value="$default_value"
|
value="$default_value"
|
||||||
fi
|
fi
|
||||||
eval "$var_name=\"$value\""
|
printf -v "$var_name" %s "$value"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Existing file detection ────────────────────────────────────
|
# ── Existing file detection ────────────────────────────────────
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Source only the prompt helpers; executing mosaic-init itself requires templates.
|
||||||
|
source <(head -n 144 "$(dirname "$0")/mosaic-init")
|
||||||
|
|
||||||
|
rm -f /tmp/pwned
|
||||||
|
payload='literal "$(touch /tmp/pwned)"'
|
||||||
|
AGENT_NAME=""
|
||||||
|
prompt_if_empty AGENT_NAME "Agent name" <<<"$payload"
|
||||||
|
|
||||||
|
[[ "$AGENT_NAME" == "$payload" ]] || {
|
||||||
|
echo "FAIL: prompt answer did not round-trip literally" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ ! -e /tmp/pwned ]] || {
|
||||||
|
echo "FAIL: prompt answer executed code" >&2
|
||||||
|
rm -f /tmp/pwned
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "mosaic-init RCE regression: PASS"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
||||||
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
|
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mosaicstack/brain": "workspace:*",
|
"@mosaicstack/brain": "workspace:*",
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ export async function quickStartPath(
|
|||||||
|
|
||||||
// Gateway config + bootstrap
|
// Gateway config + bootstrap
|
||||||
if (!options.skipGateway) {
|
if (!options.skipGateway) {
|
||||||
|
const headlessRun = process.env['MOSAIC_ASSUME_YES'] === '1' || !process.stdin.isTTY;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const configResult = await gatewayConfigStage(prompter, state, {
|
const configResult = await gatewayConfigStage(prompter, state, {
|
||||||
host: options.gatewayHost ?? 'localhost',
|
host: options.gatewayHost ?? 'localhost',
|
||||||
@@ -79,9 +81,11 @@ export async function quickStartPath(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!configResult.ready || !configResult.host || !configResult.port) {
|
if (!configResult.ready || !configResult.host || !configResult.port) {
|
||||||
const message = 'Gateway configuration failed — aborting wizard.';
|
if (headlessRun) {
|
||||||
prompter.warn(message);
|
prompter.warn('Gateway configuration failed in headless mode — aborting wizard.');
|
||||||
throw new Error(message);
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
||||||
@@ -89,9 +93,9 @@ export async function quickStartPath(
|
|||||||
port: configResult.port,
|
port: configResult.port,
|
||||||
});
|
});
|
||||||
if (!bootstrapResult.completed) {
|
if (!bootstrapResult.completed) {
|
||||||
const message = 'Admin bootstrap failed — aborting wizard.';
|
prompter.warn('Admin bootstrap failed — aborting wizard.');
|
||||||
prompter.warn(message);
|
process.exit(1);
|
||||||
throw new Error(message);
|
return;
|
||||||
}
|
}
|
||||||
finalizeResult.showSummary();
|
finalizeResult.showSummary();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -348,21 +348,18 @@ async function runFinishPath(
|
|||||||
providerType: state.providerType,
|
providerType: state.providerType,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!configResult.ready || !configResult.host || !configResult.port) {
|
if (configResult.ready && configResult.host && configResult.port) {
|
||||||
const message = 'Gateway configuration failed — aborting wizard.';
|
|
||||||
prompter.warn(message);
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
||||||
host: configResult.host,
|
host: configResult.host,
|
||||||
port: configResult.port,
|
port: configResult.port,
|
||||||
});
|
});
|
||||||
if (!bootstrapResult.completed) {
|
if (!bootstrapResult.completed) {
|
||||||
const message = 'Admin bootstrap failed — aborting wizard.';
|
prompter.warn('Admin bootstrap failed — aborting wizard.');
|
||||||
prompter.warn(message);
|
process.exit(1);
|
||||||
throw new Error(message);
|
return;
|
||||||
}
|
}
|
||||||
finalizeResult.showSummary();
|
finalizeResult.showSummary();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
prompter.warn(`Gateway setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
prompter.warn(`Gateway setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
throw err;
|
throw err;
|
||||||
@@ -423,9 +420,9 @@ async function runHeadlessPath(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!configResult.ready || !configResult.host || !configResult.port) {
|
if (!configResult.ready || !configResult.host || !configResult.port) {
|
||||||
const message = 'Gateway configuration failed in headless mode — aborting wizard.';
|
prompter.warn('Gateway configuration failed in headless mode — aborting wizard.');
|
||||||
prompter.warn(message);
|
process.exit(1);
|
||||||
throw new Error(message);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
||||||
@@ -433,9 +430,9 @@ async function runHeadlessPath(
|
|||||||
port: configResult.port,
|
port: configResult.port,
|
||||||
});
|
});
|
||||||
if (!bootstrapResult.completed) {
|
if (!bootstrapResult.completed) {
|
||||||
const message = 'Admin bootstrap failed — aborting wizard.';
|
prompter.warn('Admin bootstrap failed — aborting wizard.');
|
||||||
prompter.warn(message);
|
process.exit(1);
|
||||||
throw new Error(message);
|
return;
|
||||||
}
|
}
|
||||||
finalizeResult.showSummary();
|
finalizeResult.showSummary();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -480,21 +477,18 @@ async function runKeepPath(
|
|||||||
skipInstall: options.skipGatewayNpmInstall,
|
skipInstall: options.skipGatewayNpmInstall,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!configResult.ready || !configResult.host || !configResult.port) {
|
if (configResult.ready && configResult.host && configResult.port) {
|
||||||
const message = 'Gateway configuration failed — aborting wizard.';
|
|
||||||
prompter.warn(message);
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
|
||||||
host: configResult.host,
|
host: configResult.host,
|
||||||
port: configResult.port,
|
port: configResult.port,
|
||||||
});
|
});
|
||||||
if (!bootstrapResult.completed) {
|
if (!bootstrapResult.completed) {
|
||||||
const message = 'Admin bootstrap failed — aborting wizard.';
|
prompter.warn('Admin bootstrap failed — aborting wizard.');
|
||||||
prompter.warn(message);
|
process.exit(1);
|
||||||
throw new Error(message);
|
return;
|
||||||
}
|
}
|
||||||
finalizeResult.showSummary();
|
finalizeResult.showSummary();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
prompter.warn(`Gateway setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
prompter.warn(`Gateway setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
+1
-4
@@ -762,12 +762,9 @@ if [[ "$FLAG_CHECK" == "false" ]]; then
|
|||||||
if "$MOSAIC_CMD" wizard; then
|
if "$MOSAIC_CMD" wizard; then
|
||||||
ok "Wizard complete."
|
ok "Wizard complete."
|
||||||
else
|
else
|
||||||
fail "Wizard failed; installation is incomplete."
|
warn "Wizard exited non-zero."
|
||||||
echo " Completed: framework and CLI installation"
|
|
||||||
echo " Failed: gateway configuration or admin bootstrap"
|
|
||||||
echo " You can retry with: ${C}mosaic wizard${RESET}"
|
echo " You can retry with: ${C}mosaic wizard${RESET}"
|
||||||
echo " Or run gateway install alone: ${C}mosaic gateway install${RESET}"
|
echo " Or run gateway install alone: ${C}mosaic gateway install${RESET}"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user