fix: clean up live smoke blockers

This commit is contained in:
Peter Steinberger 2026-05-04 22:17:41 +01:00
parent 189e03657b
commit 41ad14cc81
No known key found for this signature in database
6 changed files with 99 additions and 1 deletions

View File

@ -20,6 +20,9 @@
### Fixed
- Fixed `crabbox run --junit` so all-passing JUnit files record results instead of leaving the coordinator run stuck when the failure list is empty.
- Fixed failed Blacksmith Testbox warmups so a printed `tbx_...` is stopped instead of being left ready after an upstream workflow error.
- Fixed Worker deploy smoke to prefer the Crabbox-scoped Cloudflare token when it is present in the environment or local profile.
- Fixed brokered Tailscale requests on coordinators without OAuth secrets so they fail as disabled instead of entering the auth-key minting path.
- Fixed native Windows `--shell` runs so multi-statement PowerShell scripts keep their quotes instead of being re-parsed by a nested PowerShell process.
- Removed the static macOS managed-login path so static host VNC cannot be mistaken for a Crabbox-created external instance.
- Excluded macOS AppleDouble `._*` sidecar files from default sync manifests so native Windows archives do not transfer invalid TypeScript/package sidecars.

View File

@ -223,6 +223,9 @@ func (a App) blacksmithWarmupLease(ctx context.Context, cfg Config, repo Repo, r
cmd.Stdout = io.MultiWriter(a.Stdout, &output)
cmd.Stderr = io.MultiWriter(a.Stderr, &output)
if err := cmd.Run(); err != nil {
if leaseID := parseBlacksmithID(output.String()); leaseID != "" {
_ = a.blacksmithStopLease(ctx, cfg, leaseID)
}
return "", "", exit(exitCode(err), "blacksmith testbox warmup failed: %v", err)
}
leaseID := parseBlacksmithID(output.String())

View File

@ -94,6 +94,39 @@ func TestBlacksmithWarmupFailureRemovesPendingKey(t *testing.T) {
}
}
func TestBlacksmithWarmupFailureStopsPrintedTestbox(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
original := blacksmithCommandContext
var stopped string
blacksmithCommandContext = func(_ context.Context, _ string, args ...string) *exec.Cmd {
if len(args) >= 3 && args[0] == "testbox" && args[1] == "stop" {
for i, arg := range args {
if arg == "--id" && i+1 < len(args) {
stopped = args[i+1]
}
}
return exec.Command("sh", "-c", "exit 0")
}
return exec.Command("sh", "-c", "printf 'queued tbx_leaked123\\n'; exit 1")
}
t.Cleanup(func() {
blacksmithCommandContext = original
})
cfg := baseConfig()
cfg.Blacksmith.Workflow = ".github/workflows/testbox.yml"
app := App{Stdout: io.Discard, Stderr: io.Discard}
_, _, err := app.blacksmithWarmupLease(context.Background(), cfg, Repo{Root: "/repo"}, false)
if err == nil {
t.Fatal("expected warmup failure")
}
if stopped != "tbx_leaked123" {
t.Fatalf("stopped=%q, want tbx_leaked123", stopped)
}
}
func TestBlacksmithOneShotRunRemovesClaimAfterStop(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)

View File

@ -4,6 +4,36 @@ set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CRABBOX_BIN="${CRABBOX_BIN:-$ROOT/bin/crabbox}"
profile_export() {
local name="$1" file line value
for file in "$HOME/.profile" "$HOME/.zprofile"; do
[[ -r "$file" ]] || continue
line="$(grep -E "^export ${name}=" "$file" | tail -n 1 || true)"
[[ -n "$line" ]] || continue
value="${line#export ${name}=}"
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
printf '%s' "$value"
return 0
done
return 1
}
if [[ -z "${CRABBOX_CLOUDFLARE_API_TOKEN:-}" ]]; then
CRABBOX_CLOUDFLARE_API_TOKEN="$(profile_export CRABBOX_CLOUDFLARE_API_TOKEN || true)"
fi
if [[ -z "${CRABBOX_CLOUDFLARE_ACCOUNT_ID:-}" ]]; then
CRABBOX_CLOUDFLARE_ACCOUNT_ID="$(profile_export CRABBOX_CLOUDFLARE_ACCOUNT_ID || true)"
fi
if [[ -n "${CRABBOX_CLOUDFLARE_API_TOKEN:-}" ]]; then
export CLOUDFLARE_API_TOKEN="$CRABBOX_CLOUDFLARE_API_TOKEN"
fi
if [[ -n "${CRABBOX_CLOUDFLARE_ACCOUNT_ID:-}" ]]; then
export CLOUDFLARE_ACCOUNT_ID="$CRABBOX_CLOUDFLARE_ACCOUNT_ID"
fi
run() {
printf '+'
printf ' %q' "$@"

View File

@ -7,7 +7,13 @@ export interface TailscaleKeyRequest {
}
export function tailscaleAllowed(env: Env): boolean {
return env.CRABBOX_TAILSCALE_ENABLED !== "0";
if (env.CRABBOX_TAILSCALE_ENABLED === "0") {
return false;
}
if (env.CRABBOX_TAILSCALE_ENABLED === "1") {
return true;
}
return Boolean(env.CRABBOX_TAILSCALE_CLIENT_ID && env.CRABBOX_TAILSCALE_CLIENT_SECRET);
}
export function tailscaleDefaultTags(env: Env): string[] {

View File

@ -217,6 +217,29 @@ describe("fleet lease identity and idle", () => {
});
});
it("reports brokered Tailscale disabled when OAuth secrets are absent", async () => {
const fleet = testFleet(new MemoryStorage(), { hetzner: fakeProvider() });
const create = await fleet.fetch(
request("POST", "/v1/leases", {
headers: {
"x-crabbox-owner": "peter@example.com",
"x-crabbox-org": "openclaw",
},
body: {
leaseID: "cbx_abcdef123456",
provider: "hetzner",
tailscale: true,
sshPublicKey: "ssh-ed25519 test",
},
}),
);
expect(create.status).toBe(403);
await expect(create.json()).resolves.toMatchObject({
error: "tailscale_disabled",
message: "Tailscale is disabled for this coordinator",
});
});
it("passes the Cloudflare request source IP as AWS SSH ingress CIDR", async () => {
let awsCIDRs: string[] = [];
const fleet = testFleet(new MemoryStorage(), {