fix: clarify managed Windows provider boundary
This commit is contained in:
parent
c4f11a13de
commit
9ffc78e003
@ -75,13 +75,13 @@ For the full mental model, see [How Crabbox Works](docs/how-it-works.md). For th
|
||||
- **Run observability.** Every coordinator-backed run gets an early `run_...` handle. Use `crabbox attach <run-id>` while it is active, `crabbox events <run-id> --after <seq> --limit <n>` for durable lifecycle/output events, and `crabbox logs <run-id>` for retained output after completion.
|
||||
- **Stable timing records.** `--timing-json` on `run`, `warmup`, and `actions hydrate` gives scripts one machine-readable sync/command/total timing schema across AWS, Hetzner, and Blacksmith Testboxes.
|
||||
- **Local-first sync.** No clean-checkout requirement. Tracked + nonignored files only, fingerprint skip on no-op runs, sanity checks against suspicious mass deletions, optional shallow base-ref hydration for changed-test workflows.
|
||||
- **Brokered cloud.** Maintainers and agents share infra without sharing provider tokens. Hetzner and AWS EC2 Spot are first-class; both fall back across instance families when capacity or quota rejects a request.
|
||||
- **Brokered cloud.** Maintainers and agents share infra without sharing provider tokens. Hetzner and AWS EC2 Spot are first-class Linux providers; AWS also owns managed Windows and EC2 Mac targets. Providers fall back across compatible instance families when capacity or quota rejects a request.
|
||||
- **macOS and Windows static hosts.** `provider: ssh` reuses existing machines; it does not create macOS or Windows Crabbox boxes. macOS and Windows WSL2 use the POSIX rsync path; native Windows uses PowerShell plus tar archive sync.
|
||||
- **Blacksmith Testbox wrapper.** Set `provider: blacksmith-testbox` to delegate warmup/run/list/status/stop to the Blacksmith CLI while Crabbox keeps local slugs, repo claims, timing summaries, and config conventions.
|
||||
- **Trusted AWS images.** Operators can create AMIs from active brokered AWS leases and promote a known-good image as the coordinator default.
|
||||
- **Cost guardrails.** Per-lease and monthly spend caps. Live pricing from EC2 Spot history or Hetzner server-type prices, with static fallbacks. `crabbox usage` summarizes spend by user, org, provider, and type.
|
||||
- **GitHub Actions hydration.** `crabbox actions hydrate` registers a leased box as an ephemeral Actions runner, so the repo's own workflow installs runtimes, services, and secrets. Crabbox does not parse Actions YAML.
|
||||
- **Interactive desktop and browser leases.** `--browser` provisions Chrome or Chromium for headless automation, `--desktop` provisions visible UI with tunnel-only VNC takeover on managed Linux, AWS Windows, and AWS EC2 Mac targets, and QA systems such as Mantis own scenario logic, screenshots, and PR evidence.
|
||||
- **Interactive desktop and browser leases.** `--browser` provisions Chrome or Chromium for headless automation, `--desktop` provisions visible UI with tunnel-only VNC takeover on managed Linux, AWS Windows, and AWS EC2 Mac targets, and QA systems such as Mantis own scenario logic, screenshots, and PR evidence. Hetzner Windows is not a managed target; use AWS for managed Windows or `provider: ssh` for an existing Windows host.
|
||||
- **Hardened coordinator auth.** GitHub browser login, owner-scoped leases, admin-only routes, optional GitHub team allowlists, Cloudflare Access JWT verification, and service-token support keep normal use and operator automation separate.
|
||||
- **OpenClaw plugin.** The repo root is a native OpenClaw plugin for box lifecycle operations: `crabbox_run`, `crabbox_warmup`, `crabbox_status`, `crabbox_list`, and `crabbox_stop`. Run inspection stays in the CLI and Crabbox skill.
|
||||
- **Operator surface.** `doctor`, `init`, `status`, `inspect`, `list`, `usage`, `history`, `logs`, `results`, `cache`, `admin`, `cleanup`, plus `--json` output where it matters.
|
||||
@ -100,6 +100,11 @@ AWS Spot standard c7a/c7i/m7a/m7i.8xlarge family
|
||||
fast …16xlarge family
|
||||
large …24xlarge family
|
||||
beast …48xlarge family, falling back to 32x/24x/16x
|
||||
|
||||
AWS Win standard m7i.large, m7a.large, t3.large
|
||||
fast m7i.2xlarge, m7a.2xlarge, m7i.xlarge
|
||||
large m7i.4xlarge, m7a.4xlarge, m7i.2xlarge
|
||||
beast m7i.4xlarge, m7a.4xlarge, m7i.2xlarge
|
||||
```
|
||||
|
||||
Override with `--type` or `CRABBOX_SERVER_TYPE` for a specific instance.
|
||||
|
||||
@ -127,6 +127,13 @@ crabbox vnc --id blue-lobster
|
||||
crabbox screenshot --id blue-lobster --output desktop.png
|
||||
```
|
||||
|
||||
Managed provider targets are intentionally narrow:
|
||||
|
||||
- Hetzner managed provisioning supports Linux only.
|
||||
- AWS supports Linux, native Windows (`--target windows --windows-mode normal`),
|
||||
and EC2 Mac (`--target macos`) when the Mac Dedicated Host is provided.
|
||||
- Existing macOS and Windows machines belong on `provider=ssh`.
|
||||
|
||||
Inspect pool:
|
||||
|
||||
```sh
|
||||
|
||||
@ -27,6 +27,11 @@ OpenSSH Server reachable, PowerShell, Git, `tar`, and a writable
|
||||
`static.workRoot`. Restart `sshd` after installing Git so new SSH sessions see
|
||||
the updated PATH.
|
||||
|
||||
With `--provider hetzner`, managed provisioning supports Linux only. Hetzner can
|
||||
run Windows through ISO/snapshot installation flows, but Crabbox does not manage
|
||||
that path today. Use `--provider aws --target windows` for managed Windows, or
|
||||
`--provider ssh --target windows` for an existing Hetzner Windows host.
|
||||
|
||||
With `--provider aws --target windows --desktop`, Crabbox creates a real AWS
|
||||
Windows Server lease. EC2Launch user data installs OpenSSH Server, Git for
|
||||
Windows, TightVNC Server, a per-lease local administrator named `crabbox`, and a
|
||||
|
||||
@ -436,7 +436,7 @@ func TestLeaseStatusRequiresSSHReadiness(t *testing.T) {
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"lease":{"id":"cbx_123","slug":"blue-crab","provider":"aws","state":"active","serverType":"c7a.8xlarge","host":"127.0.0.1","sshUser":"ubuntu","sshPort":"22"}}`))
|
||||
_, _ = w.Write([]byte(`{"lease":{"id":"cbx_123","slug":"blue-crab","provider":"aws","target":"windows","windowsMode":"normal","state":"active","serverType":"m7i.4xlarge","host":"127.0.0.1","sshUser":"crabbox","sshPort":"22"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
@ -451,6 +451,9 @@ func TestLeaseStatusRequiresSSHReadiness(t *testing.T) {
|
||||
if !state.HasHost {
|
||||
t.Fatalf("HasHost=false, want true")
|
||||
}
|
||||
if state.TargetOS != targetWindows || state.WindowsMode != windowsModeNormal {
|
||||
t.Fatalf("target=%s windowsMode=%s", state.TargetOS, state.WindowsMode)
|
||||
}
|
||||
if state.Ready {
|
||||
t.Fatalf("Ready=true, want false when ssh readiness probe fails")
|
||||
}
|
||||
|
||||
@ -99,8 +99,8 @@ func (a App) leaseStatus(ctx context.Context, cfg Config, id string) (statusView
|
||||
ID: lease.ID,
|
||||
Slug: lease.Slug,
|
||||
Provider: blank(lease.Provider, cfg.Provider),
|
||||
TargetOS: cfg.TargetOS,
|
||||
WindowsMode: cfg.WindowsMode,
|
||||
TargetOS: blank(target.TargetOS, cfg.TargetOS),
|
||||
WindowsMode: blank(target.WindowsMode, cfg.WindowsMode),
|
||||
State: lease.State,
|
||||
ServerID: leaseDisplayID(lease),
|
||||
ServerType: lease.ServerType,
|
||||
|
||||
@ -88,6 +88,9 @@ func validateProviderTarget(cfg Config) error {
|
||||
if cfg.Provider == "aws" && cfg.TargetOS == targetWindows && cfg.WindowsMode == windowsModeNormal {
|
||||
return nil
|
||||
}
|
||||
if cfg.Provider == "aws" && cfg.TargetOS == targetWindows {
|
||||
return exit(2, "provider=aws managed Windows supports windows.mode=normal only; use provider=ssh for target=windows windows.mode=%s static hosts", cfg.WindowsMode)
|
||||
}
|
||||
if cfg.Provider == "aws" && cfg.TargetOS == targetMacOS {
|
||||
if cfg.AWSMacHostID == "" && cfg.Coordinator == "" {
|
||||
return exit(2, "provider=aws target=macos requires CRABBOX_AWS_MAC_HOST_ID or aws.macHostId for an allocated EC2 Mac Dedicated Host")
|
||||
@ -98,11 +101,22 @@ func validateProviderTarget(cfg Config) error {
|
||||
return nil
|
||||
}
|
||||
if cfg.TargetOS != targetLinux {
|
||||
return exit(2, "provider=%s currently supports target=linux only; use provider=ssh for target=%s", cfg.Provider, cfg.TargetOS)
|
||||
return exit(2, "%s", unsupportedManagedTargetMessage(cfg.Provider, cfg.TargetOS))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unsupportedManagedTargetMessage(provider, target string) string {
|
||||
switch target {
|
||||
case targetWindows:
|
||||
return sprintf("provider=%s managed provisioning supports target=linux only; use provider=aws for managed Windows or provider=ssh for existing Windows hosts", provider)
|
||||
case targetMacOS:
|
||||
return sprintf("provider=%s managed provisioning supports target=linux only; use provider=aws with an EC2 Mac Dedicated Host or provider=ssh for existing macOS hosts", provider)
|
||||
default:
|
||||
return sprintf("provider=%s managed provisioning supports target=linux only", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func newTargetCoordinatorClient(cfg Config) (*CoordinatorClient, bool, error) {
|
||||
if isStaticProvider(cfg.Provider) {
|
||||
return nil, false, nil
|
||||
|
||||
@ -22,7 +22,27 @@ func TestValidateProviderTargetRejectsUnsupportedAWSTargets(t *testing.T) {
|
||||
cfg.TargetOS = targetWindows
|
||||
cfg.WindowsMode = windowsModeWSL2
|
||||
err := validateProviderTarget(cfg)
|
||||
if err == nil || !strings.Contains(err.Error(), "currently supports target=linux only") {
|
||||
if err == nil || !strings.Contains(err.Error(), "managed Windows supports windows.mode=normal only") {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Hetzner Windows needs an existing static host", func(t *testing.T) {
|
||||
cfg := baseConfig()
|
||||
cfg.Provider = "hetzner"
|
||||
cfg.TargetOS = targetWindows
|
||||
err := validateProviderTarget(cfg)
|
||||
if err == nil || !strings.Contains(err.Error(), "managed provisioning supports target=linux only") {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Hetzner macOS points at AWS Mac or static hosts", func(t *testing.T) {
|
||||
cfg := baseConfig()
|
||||
cfg.Provider = "hetzner"
|
||||
cfg.TargetOS = targetMacOS
|
||||
err := validateProviderTarget(cfg)
|
||||
if err == nil || !strings.Contains(err.Error(), "EC2 Mac Dedicated Host") {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
})
|
||||
|
||||
@ -52,6 +52,12 @@ export function leaseConfig(input: LeaseRequest): LeaseConfig {
|
||||
!(provider === "aws" && target === "windows" && windowsMode === "normal") &&
|
||||
!(provider === "aws" && target === "macos")
|
||||
) {
|
||||
if (provider === "aws" && target === "windows") {
|
||||
throw new Error("brokered aws target=windows requires windowsMode=normal");
|
||||
}
|
||||
if (provider === "hetzner") {
|
||||
throw new Error(unsupportedManagedTargetMessage(provider, target));
|
||||
}
|
||||
throw new Error(`unsupported target for brokered ${provider}: ${target}`);
|
||||
}
|
||||
if (target === "macos") {
|
||||
@ -109,6 +115,16 @@ export function leaseConfig(input: LeaseRequest): LeaseConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function unsupportedManagedTargetMessage(provider: Provider, target: TargetOS): string {
|
||||
if (target === "windows") {
|
||||
return `brokered ${provider} managed provisioning supports target=linux only; use brokered aws for managed Windows or provider=ssh for existing Windows hosts`;
|
||||
}
|
||||
if (target === "macos") {
|
||||
return `brokered ${provider} managed provisioning supports target=linux only; use brokered aws with an EC2 Mac Dedicated Host or provider=ssh for existing macOS hosts`;
|
||||
}
|
||||
return `brokered ${provider} managed provisioning supports target=linux only`;
|
||||
}
|
||||
|
||||
function normalizeTarget(value: string): TargetOS {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (normalized === "" || normalized === "linux" || normalized === "ubuntu") {
|
||||
|
||||
@ -93,7 +93,18 @@ describe("lease config", () => {
|
||||
expect(config.windowsMode).toBe("normal");
|
||||
expect(() =>
|
||||
leaseConfig({ provider: "hetzner", target: "windows", sshPublicKey: "ssh-ed25519 test" }),
|
||||
).toThrow("unsupported target");
|
||||
).toThrow("managed provisioning supports target=linux only");
|
||||
expect(() =>
|
||||
leaseConfig({ provider: "hetzner", target: "macos", sshPublicKey: "ssh-ed25519 test" }),
|
||||
).toThrow("EC2 Mac Dedicated Host");
|
||||
expect(() =>
|
||||
leaseConfig({
|
||||
provider: "aws",
|
||||
target: "windows",
|
||||
windowsMode: "wsl2",
|
||||
sshPublicKey: "ssh-ed25519 test",
|
||||
}),
|
||||
).toThrow("windowsMode=normal");
|
||||
});
|
||||
|
||||
it("allows AWS native Windows leases", () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user