fix: keep static VNC host-managed (#13)

* Revert "feat: add managed static macOS VNC login (#12)"

This reverts commit 4333327f56.

* fix: keep static VNC host-managed
This commit is contained in:
Peter Steinberger 2026-05-04 01:50:10 +01:00 committed by GitHub
parent 4333327f56
commit 04f24e9135
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 71 additions and 256 deletions

View File

@ -6,7 +6,6 @@
- Added `--desktop`, `--browser`, and `crabbox vnc` for optional Linux UI/browser leases, including loopback-only VNC with per-lease passwords and headless browser support without a desktop.
- Added static macOS/Windows VNC endpoint discovery, including SSH-tunneled loopback VNC and trusted static direct VNC on `host:5900`.
- Added `crabbox vnc --managed-login` for static macOS hosts, creating a dedicated Crabbox login and printing reusable VNC credentials.
- Added `crabbox vnc --open` to start the SSH tunnel and launch the local VNC client for managed desktop leases.
- Added a minimal XFCE desktop profile with panel/window manager for managed VNC leases.
- Clarified static macOS/Windows VNC as existing-host access, not Crabbox-created boxes, so `--open` no longer launches an OS credential prompt unless `--host-managed` is passed.
@ -14,6 +13,7 @@
### Fixed
- Quoted `crabbox vnc` tunnel key paths so macOS `Application Support` lease keys can be pasted directly into a shell.
- Removed the static macOS managed-login path so static host VNC cannot be mistaken for a Crabbox-created external instance.
- Fixed native Windows `--shell` runs so multi-statement PowerShell scripts keep their quotes instead of being re-parsed by a nested PowerShell process.
- Skipped Linux-only GitHub Actions hydration stop markers on native Windows static targets.

View File

@ -232,9 +232,6 @@ Flags:
--debug print sync timing and itemized rsync output
--junit <paths> comma-separated remote JUnit XML paths to attach to run history
--open open local VNC client for `crabbox vnc`
--host-managed allow opening host-managed static VNC
--managed-login create/reuse a Crabbox-managed static macOS VNC login
--managed-user <user> static managed VNC login user
--reclaim claim an existing lease for the current repo
--timing-json print a final JSON timing record
--blacksmith-org <org> Blacksmith organization
@ -317,8 +314,6 @@ static:
user: steipete
port: "22"
workRoot: /Users/steipete/crabbox
managedLogin: true
managedUser: crabbox
```
Static Windows target:
@ -339,12 +334,6 @@ static:
archive. `windows.mode: wsl2` runs commands through `wsl.exe --exec bash -lc`
and uses rsync inside WSL2, so `static.workRoot` should be a WSL path.
`crabbox vnc --managed-login` is currently macOS-only. It uses SSH plus sudo to
create or reuse the configured `static.managedUser`, grants Apple Remote Desktop
access, and prints the generated VNC credentials. Static Windows VNC remains
host-managed until the target exposes a management channel such as SSH/WinRM and
a supported VNC server setup path.
`crabbox warmup --market spot|on-demand` and `crabbox run --market spot|on-demand`
override `capacity.market` for a single AWS lease. Use this for temporary quota
or capacity shifts without rewriting repo config.

View File

@ -8,7 +8,6 @@ crabbox warmup --desktop
crabbox vnc --id blue-lobster
crabbox vnc --id blue-lobster --open
crabbox vnc --provider ssh --target macos --static-host mac-studio.local
crabbox vnc --provider ssh --target macos --static-host mac-studio.local --managed-login
```
The command resolves the lease like `crabbox ssh`, claims and touches it like
@ -40,25 +39,14 @@ hosts, Crabbox first tries the same SSH tunnel to
`127.0.0.1:5900` on the target. If a static host exposes VNC directly on
`host:5900`, Crabbox prints that endpoint instead. Direct static VNC is
operator-managed and should be limited to a trusted network such as Tailscale or
LAN.
LAN. Opening a static macOS or Windows target means opening that existing
machine, not an external Crabbox instance.
Static host credentials are host-managed. On macOS, the built-in Screen Sharing
server uses the host's Screen Sharing or macOS account authentication. On
Windows, the prompt belongs to the installed VNC server. Crabbox does not print
or synthesize those passwords.
For static macOS hosts reachable over SSH, `--managed-login` creates or reuses a
dedicated local account, enables Apple Remote Desktop access for that account,
skips first-run Setup Assistant panes, and prints the username/password that
Crabbox generated. The password is stored on the target under the SSH user's
Crabbox state directory and is reused on later calls. Override the account name
with `--managed-user <name>`.
Static Windows managed login is not automatic yet. Crabbox needs a management
channel such as SSH/WinRM plus a known VNC server setup path before it can
create a Windows account and set the VNC server password. Without that, Windows
VNC remains host-managed and requires `--host-managed`.
`--open` refuses host-managed static VNC by default so a host OS password prompt
is not mistaken for a Crabbox-created box. Pass `--host-managed` only when you
intentionally want to open that existing host's VNC login prompt.
@ -73,18 +61,17 @@ Security boundary:
Provider behavior:
- Brokered and direct AWS/Hetzner Linux leases support `vnc` only when created
with `--desktop`.
- Brokered and direct AWS/Hetzner leases are Linux-only in this release. They
support `vnc` only when created with `--desktop`.
- Static Linux can participate if the operator already configured Xvfb and
loopback-bound x11vnc.
- Static macOS can participate when Screen Sharing or another VNC-compatible
service is already available on `127.0.0.1:5900` over SSH or directly on
`host:5900`. `--managed-login` can create a dedicated local macOS account for
Crabbox-managed VNC authentication on that existing Mac.
`host:5900`. This reuses an existing Mac; it does not create a macOS Crabbox.
Credentials are host-managed.
- Static native Windows can participate when a VNC server is already available
on `127.0.0.1:5900` over SSH or directly on `host:5900`. Crabbox does not
create a Windows Crabbox, or install or configure the Windows VNC server in
this release.
create a Windows Crabbox, or install or configure the Windows VNC server.
- Blacksmith Testbox does not support managed VNC in this release.
Flags:
@ -101,7 +88,5 @@ Flags:
--local-port <port>
--open
--host-managed
--managed-login
--managed-user <user>
--reclaim
```

View File

@ -77,29 +77,25 @@ Security rules:
Provider notes:
- Hetzner and AWS brokered Linux leases are the primary target because Crabbox
controls cloud-init and firewall shape there.
controls cloud-init and firewall shape there. Brokered macOS and Windows
desktop leases do not exist in this release.
- Static SSH Linux hosts can participate when the operator accepts responsibility
for packages and display services.
- Static macOS hosts are existing Macs, not Crabbox-created boxes. They can
participate when Screen Sharing or another
VNC-compatible service is already available on `127.0.0.1:5900` over SSH or
directly on `host:5900`. `crabbox vnc --managed-login` can create a dedicated
local macOS account, grant Apple Remote Desktop access to it, and print the
generated credentials. Without `--managed-login`, credentials are
host-managed because Apple Remote Desktop authentication still belongs to the
target host.
directly on `host:5900`. Credentials are host-managed because Apple Remote
Desktop authentication still belongs to the target host.
- Static Windows hosts are existing Windows machines, not Crabbox-created boxes.
They can participate only when the operator already provides a VNC-compatible
service on `127.0.0.1:5900` for SSH tunneling or, for trusted static networks,
directly on `host:5900`. Opening Windows requires `--host-managed` because the
password prompt belongs to the target OS, not Crabbox. Managed Windows login
still needs a management channel such as SSH/WinRM plus a supported VNC server
setup path.
password prompt belongs to the target OS, not Crabbox.
- Blacksmith Testbox can run headless browser automation today, but VNC takeover
needs a Blacksmith-supported SSH tunnel or connection-info API before Crabbox
can offer the same `vnc` command there.
- Crabbox-managed Windows VNC installers are still out of scope for this
release.
- Crabbox-managed macOS and Windows VNC installers are still out of scope for
this release.
For Mantis, the first consumer should be a Discord QA lane:

View File

@ -40,8 +40,7 @@ This page maps user-facing behavior back to implementation files. Keep docs desc
- CLI cloud-init bootstrap: `internal/cli/bootstrap.go`
- Worker cloud-init bootstrap: `worker/src/bootstrap.ts`
- Desktop/browser capability flags, env injection, and VNC checks: `internal/cli/capabilities.go`, `internal/cli/run.go`
- VNC tunnel command and static managed macOS login: `internal/cli/vnc.go`,
`internal/cli/static_vnc.go`
- VNC tunnel command: `internal/cli/vnc.go`
- Interactive desktop/VNC contract: `docs/features/interactive-desktop-vnc.md`
Bootstrap is intentionally tiny unless optional lease capabilities are requested:

View File

@ -198,8 +198,6 @@ Environment:
CRABBOX_DESKTOP Provision or require desktop/VNC capability
CRABBOX_BROWSER Provision or require browser capability
CRABBOX_STATIC_HOST Static SSH host for provider=ssh
CRABBOX_STATIC_MANAGED_LOGIN Enable static managed VNC login where supported
CRABBOX_STATIC_MANAGED_USER Static managed VNC login user
CRABBOX_OWNER Usage owner override
CRABBOX_ORG Usage org override
CRABBOX_CONFIG Optional config path

View File

@ -198,10 +198,10 @@ func availableLocalVNCPort() string {
func resolveVNCEndpoint(ctx context.Context, cfg Config, target SSHTarget) (vncEndpoint, error) {
if isStaticProvider(cfg.Provider) {
if err := probeLoopbackVNC(ctx, target, "2", "1"); err == nil {
return vncEndpoint{Host: "127.0.0.1", Port: managedVNCPort, Managed: cfg.Static.ManagedLogin}, nil
return vncEndpoint{Host: "127.0.0.1", Port: managedVNCPort}, nil
}
if tcpReachable(ctx, target.Host, managedVNCPort, 2*time.Second) {
return vncEndpoint{Direct: true, Host: target.Host, Port: managedVNCPort, Managed: cfg.Static.ManagedLogin}, nil
return vncEndpoint{Direct: true, Host: target.Host, Port: managedVNCPort}, nil
}
return vncEndpoint{}, exit(5, "target does not expose VNC through SSH loopback 127.0.0.1:5900 or direct %s:%s", target.Host, managedVNCPort)
}

View File

@ -97,14 +97,12 @@ type BlacksmithConfig struct {
}
type StaticConfig struct {
ID string
Name string
Host string
User string
Port string
WorkRoot string
ManagedLogin bool
ManagedUser string
ID string
Name string
Host string
User string
Port string
WorkRoot string
}
type ResultsConfig struct {
@ -331,14 +329,12 @@ type fileBlacksmithConfig struct {
}
type fileStaticConfig struct {
ID string `yaml:"id,omitempty"`
Name string `yaml:"name,omitempty"`
Host string `yaml:"host,omitempty"`
User string `yaml:"user,omitempty"`
Port string `yaml:"port,omitempty"`
WorkRoot string `yaml:"workRoot,omitempty"`
ManagedLogin *bool `yaml:"managedLogin,omitempty"`
ManagedUser string `yaml:"managedUser,omitempty"`
ID string `yaml:"id,omitempty"`
Name string `yaml:"name,omitempty"`
Host string `yaml:"host,omitempty"`
User string `yaml:"user,omitempty"`
Port string `yaml:"port,omitempty"`
WorkRoot string `yaml:"workRoot,omitempty"`
}
type fileResultsConfig struct {
@ -693,12 +689,6 @@ func applyFileConfig(cfg *Config, file fileConfig) {
if file.Static.WorkRoot != "" {
cfg.Static.WorkRoot = file.Static.WorkRoot
}
if file.Static.ManagedLogin != nil {
cfg.Static.ManagedLogin = *file.Static.ManagedLogin
}
if file.Static.ManagedUser != "" {
cfg.Static.ManagedUser = file.Static.ManagedUser
}
}
if file.Results != nil && len(file.Results.JUnit) > 0 {
cfg.Results.JUnit = appendUniqueStrings(nil, file.Results.JUnit...)
@ -799,10 +789,6 @@ func applyEnv(cfg *Config) {
cfg.Static.User = getenv("CRABBOX_STATIC_USER", cfg.Static.User)
cfg.Static.Port = getenv("CRABBOX_STATIC_PORT", cfg.Static.Port)
cfg.Static.WorkRoot = getenv("CRABBOX_STATIC_WORK_ROOT", cfg.Static.WorkRoot)
if value, ok := getenvBool("CRABBOX_STATIC_MANAGED_LOGIN"); ok {
cfg.Static.ManagedLogin = value
}
cfg.Static.ManagedUser = getenv("CRABBOX_STATIC_MANAGED_USER", cfg.Static.ManagedUser)
if idleTimeout := os.Getenv("CRABBOX_BLACKSMITH_IDLE_TIMEOUT"); idleTimeout != "" {
applyLeaseDuration(&cfg.Blacksmith.IdleTimeout, idleTimeout)
}

View File

@ -99,14 +99,12 @@ func (a App) configShow(args []string) error {
"debug": cfg.Blacksmith.Debug,
},
"static": map[string]any{
"id": cfg.Static.ID,
"name": cfg.Static.Name,
"host": cfg.Static.Host,
"user": cfg.Static.User,
"port": cfg.Static.Port,
"workRoot": cfg.Static.WorkRoot,
"managedLogin": cfg.Static.ManagedLogin,
"managedUser": cfg.Static.ManagedUser,
"id": cfg.Static.ID,
"name": cfg.Static.Name,
"host": cfg.Static.Host,
"user": cfg.Static.User,
"port": cfg.Static.Port,
"workRoot": cfg.Static.WorkRoot,
},
"results": map[string]any{
"junit": cfg.Results.JUnit,
@ -147,7 +145,7 @@ func (a App) configShow(args []string) error {
fmt.Fprintf(a.Stdout, "capacity market=%s strategy=%s fallback=%s regions=%s\n", cfg.Capacity.Market, cfg.Capacity.Strategy, cfg.Capacity.Fallback, blank(strings.Join(cfg.Capacity.Regions, ","), "-"))
fmt.Fprintf(a.Stdout, "actions repo=%s workflow=%s job=%s ref=%s runner_version=%s ephemeral=%t labels=%s\n", blank(cfg.Actions.Repo, "-"), blank(cfg.Actions.Workflow, "-"), blank(cfg.Actions.Job, "-"), blank(cfg.Actions.Ref, "-"), cfg.Actions.RunnerVersion, cfg.Actions.Ephemeral, blank(strings.Join(cfg.Actions.RunnerLabels, ","), "-"))
fmt.Fprintf(a.Stdout, "blacksmith org=%s workflow=%s job=%s ref=%s idle_timeout=%s debug=%t\n", blank(cfg.Blacksmith.Org, "-"), blank(cfg.Blacksmith.Workflow, "-"), blank(cfg.Blacksmith.Job, "-"), blank(cfg.Blacksmith.Ref, "-"), cfg.Blacksmith.IdleTimeout, cfg.Blacksmith.Debug)
fmt.Fprintf(a.Stdout, "static id=%s name=%s host=%s user=%s port=%s work_root=%s managed_login=%t managed_user=%s\n", blank(cfg.Static.ID, "-"), blank(cfg.Static.Name, "-"), blank(cfg.Static.Host, "-"), blank(cfg.Static.User, "-"), blank(cfg.Static.Port, "-"), blank(cfg.Static.WorkRoot, "-"), cfg.Static.ManagedLogin, blank(cfg.Static.ManagedUser, "-"))
fmt.Fprintf(a.Stdout, "static id=%s name=%s host=%s user=%s port=%s work_root=%s\n", blank(cfg.Static.ID, "-"), blank(cfg.Static.Name, "-"), blank(cfg.Static.Host, "-"), blank(cfg.Static.User, "-"), blank(cfg.Static.Port, "-"), blank(cfg.Static.WorkRoot, "-"))
fmt.Fprintf(a.Stdout, "results junit=%s\n", blank(strings.Join(cfg.Results.JUnit, ","), "-"))
fmt.Fprintf(a.Stdout, "cache pnpm=%t npm=%t docker=%t git=%t max_gb=%d purge_on_release=%t\n", cfg.Cache.Pnpm, cfg.Cache.Npm, cfg.Cache.Docker, cfg.Cache.Git, cfg.Cache.MaxGB, cfg.Cache.PurgeOnRelease)
fmt.Fprintf(a.Stdout, "aws region=%s root_gb=%d ssh_cidrs=%s\n", cfg.AWSRegion, cfg.AWSRootGB, blank(strings.Join(cfg.AWSSSHCIDRs, ","), "-"))

View File

@ -109,8 +109,6 @@ static:
user: peter
port: "22"
workRoot: /home/peter/crabbox
managedLogin: true
managedUser: cbx-ui
results:
junit:
- junit.xml
@ -191,7 +189,7 @@ ssh:
if cfg.Blacksmith.Org != "openclaw" || cfg.Blacksmith.Workflow != ".github/workflows/blacksmith-testbox.yml" || cfg.Blacksmith.Job != "hydrate" || cfg.Blacksmith.Ref != "main" || cfg.Blacksmith.IdleTimeout != 90*time.Minute || !cfg.Blacksmith.Debug {
t.Fatalf("blacksmith config not loaded: %#v", cfg.Blacksmith)
}
if cfg.Static.Host != "win-dev.local" || cfg.Static.User != "peter" || cfg.Static.Port != "22" || cfg.WorkRoot != "/home/peter/crabbox" || !cfg.Static.ManagedLogin || cfg.Static.ManagedUser != "cbx-ui" {
if cfg.Static.Host != "win-dev.local" || cfg.Static.User != "peter" || cfg.Static.Port != "22" || cfg.WorkRoot != "/home/peter/crabbox" {
t.Fatalf("static config not loaded: static=%#v workRoot=%s", cfg.Static, cfg.WorkRoot)
}
if len(cfg.Results.JUnit) != 1 || cfg.Results.JUnit[0] != "junit.xml" {
@ -220,8 +218,6 @@ func TestEnvOverridesConfig(t *testing.T) {
t.Setenv("CRABBOX_COORDINATOR_ADMIN_TOKEN", "env-admin-secret")
t.Setenv("CRABBOX_TARGET", "macos")
t.Setenv("CRABBOX_STATIC_HOST", "mac.local")
t.Setenv("CRABBOX_STATIC_MANAGED_LOGIN", "true")
t.Setenv("CRABBOX_STATIC_MANAGED_USER", "cbx-env")
path := userConfigPath()
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
t.Fatal(err)
@ -249,7 +245,7 @@ func TestEnvOverridesConfig(t *testing.T) {
if cfg.CoordAdminToken != "env-admin-secret" {
t.Fatalf("unexpected admin token state: %q", cfg.CoordAdminToken)
}
if cfg.TargetOS != targetMacOS || cfg.Static.Host != "mac.local" || !cfg.Static.ManagedLogin || cfg.Static.ManagedUser != "cbx-env" {
if cfg.TargetOS != targetMacOS || cfg.Static.Host != "mac.local" {
t.Fatalf("unexpected target env: target=%s static=%#v", cfg.TargetOS, cfg.Static)
}
}

View File

@ -1,99 +0,0 @@
package cli
import (
"context"
"fmt"
"strings"
)
const staticManagedVNCPasswordName = "macos-vnc.password"
type staticManagedVNCLogin struct {
User string
Password string
PasswordPath string
}
func ensureStaticManagedVNCLogin(ctx context.Context, cfg Config, target SSHTarget) (staticManagedVNCLogin, error) {
if !isStaticProvider(cfg.Provider) || !cfg.Static.ManagedLogin {
return staticManagedVNCLogin{}, nil
}
user := strings.TrimSpace(firstNonEmpty(cfg.Static.ManagedUser, "crabbox"))
if user == "" {
return staticManagedVNCLogin{}, exit(2, "static managed VNC login requires --managed-user")
}
switch target.TargetOS {
case targetMacOS:
return ensureStaticMacOSManagedVNCLogin(ctx, cfg, target, user)
case targetWindows:
return staticManagedVNCLogin{}, exit(2, "static managed VNC login for target=windows requires SSH/WinRM plus a supported VNC server setup path; this target only exposes an existing host-managed VNC service")
default:
return staticManagedVNCLogin{}, exit(2, "static managed VNC login is supported for target=macos only in this release")
}
}
func ensureStaticMacOSManagedVNCLogin(ctx context.Context, cfg Config, target SSHTarget, user string) (staticManagedVNCLogin, error) {
root := strings.TrimSpace(cfg.Static.WorkRoot)
out, err := runSSHOutput(ctx, target, staticMacOSManagedVNCLoginScript(user, root))
if err != nil {
return staticManagedVNCLogin{}, exit(5, "configure static macOS managed VNC login over SSH: %v", err)
}
values := parseEnvLines(out)
if values["USER"] == "" || values["PASSWORD"] == "" || values["PASSWORD_FILE"] == "" {
return staticManagedVNCLogin{}, exit(5, "static macOS managed VNC login did not return credentials")
}
return staticManagedVNCLogin{
User: values["USER"],
Password: values["PASSWORD"],
PasswordPath: values["PASSWORD_FILE"],
}, nil
}
func staticMacOSManagedVNCLoginScript(user, root string) string {
rootArg := ``
if strings.TrimSpace(root) != "" {
rootArg = shellQuote(root)
}
return fmt.Sprintf(`set -eu
user=%s
root=%s
if [ -z "$root" ]; then
root="$HOME/crabbox"
fi
secret_dir="$root/.crabbox"
secret_file="$secret_dir/%s"
sudo -n mkdir -p "$secret_dir"
sudo -n chmod 700 "$secret_dir"
if [ ! -s "$secret_file" ]; then
pw="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 18)"
printf '%%s\n' "$pw" | sudo -n tee "$secret_file" >/dev/null
sudo -n chmod 600 "$secret_file"
fi
pw="$(sudo -n cat "$secret_file")"
if ! id "$user" >/dev/null 2>&1; then
sudo -n sysadminctl -addUser "$user" -fullName "Crabbox" -password "$pw" -home "/Users/$user" -shell /bin/zsh >/dev/null
sudo -n createhomedir -c -u "$user" >/dev/null 2>&1 || true
else
sudo -n dscl . -passwd "/Users/$user" "$pw"
sudo -n pwpolicy -u "$user" -clearaccountpolicies >/dev/null 2>&1 || true
fi
home="$(dscl . -read "/Users/$user" NFSHomeDirectory | sed 's/NFSHomeDirectory: //')"
uid="$(id -u "$user")"
sudo -n mkdir -p "$home/Library/Preferences"
sudo -n chown -R "$user":staff "$home/Library" >/dev/null 2>&1 || true
sudo -n /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -configure -allowAccessFor -specifiedUsers >/dev/null
sudo -n /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -configure -access -on -privs -all -users "$user" >/dev/null
sudo -n /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -configure -clientopts -setreqperm -reqperm no >/dev/null
sudo -n launchctl asuser "$uid" sudo -u "$user" defaults write com.apple.SetupAssistant DidSeeAccessibility -bool true >/dev/null 2>&1 || true
sudo -n launchctl asuser "$uid" sudo -u "$user" defaults write com.apple.SetupAssistant DidSeeSiriSetup -bool true >/dev/null 2>&1 || true
sudo -n launchctl asuser "$uid" sudo -u "$user" defaults write com.apple.SetupAssistant DidSeePrivacy -bool true >/dev/null 2>&1 || true
sudo -n launchctl asuser "$uid" sudo -u "$user" defaults write com.apple.SetupAssistant DidSeeCloudSetup -bool true >/dev/null 2>&1 || true
sudo -n launchctl asuser "$uid" sudo -u "$user" defaults write com.apple.SetupAssistant LastSeenCloudProductVersion "$(sw_vers -productVersion)" >/dev/null 2>&1 || true
sudo -n pkill -u "$user" -x "Setup Assistant" >/dev/null 2>&1 || true
sudo -n pkill -u "$user" -x SetupAssistant >/dev/null 2>&1 || true
sudo -n /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -restart -agent >/dev/null
printf 'USER=%%s\n' "$user"
printf 'PASSWORD_FILE=%%s\n' "$secret_file"
printf 'PASSWORD=%%s\n' "$pw"
`, shellQuote(user), rootArg, staticManagedVNCPasswordName)
}

View File

@ -1,36 +0,0 @@
package cli
import (
"context"
"strings"
"testing"
)
func TestStaticMacOSManagedVNCLoginScript(t *testing.T) {
got := staticMacOSManagedVNCLoginScript("cbx user", "/Users/admin/crabbox root")
for _, want := range []string{
"user='cbx user'",
"root='/Users/admin/crabbox root'",
"sysadminctl -addUser",
"dscl . -passwd",
"kickstart -configure -access -on -privs -all -users \"$user\"",
"DidSeeAccessibility",
"PASSWORD_FILE=%s",
"PASSWORD=%s",
} {
if !strings.Contains(got, want) {
t.Fatalf("script missing %q:\n%s", want, got)
}
}
}
func TestEnsureStaticManagedVNCLoginRejectsWindows(t *testing.T) {
cfg := baseConfig()
cfg.Provider = staticProvider
cfg.Static.ManagedLogin = true
target := SSHTarget{TargetOS: targetWindows, WindowsMode: windowsModeNormal}
_, err := ensureStaticManagedVNCLogin(context.Background(), cfg, target)
if err == nil || !strings.Contains(err.Error(), "requires SSH/WinRM") {
t.Fatalf("err=%v", err)
}
}

View File

@ -0,0 +1,29 @@
package cli
import (
"strings"
"testing"
)
func TestValidateProviderTargetRejectsBrokeredNonLinux(t *testing.T) {
for _, target := range []string{targetMacOS, targetWindows} {
cfg := baseConfig()
cfg.Provider = "aws"
cfg.TargetOS = target
err := validateProviderTarget(cfg)
if err == nil || !strings.Contains(err.Error(), "currently supports target=linux only") {
t.Fatalf("target=%s err=%v", target, err)
}
}
}
func TestValidateProviderTargetAllowsStaticNonLinux(t *testing.T) {
for _, target := range []string{targetMacOS, targetWindows} {
cfg := baseConfig()
cfg.Provider = staticProvider
cfg.TargetOS = target
if err := validateProviderTarget(cfg); err != nil {
t.Fatalf("target=%s err=%v", target, err)
}
}
}

View File

@ -18,8 +18,6 @@ func (a App) vnc(ctx context.Context, args []string) error {
localPort := fs.String("local-port", "", "local VNC tunnel port")
openClient := fs.Bool("open", false, "open the VNC client locally")
hostManaged := fs.Bool("host-managed", false, "allow opening host-managed static VNC")
managedLogin := fs.Bool("managed-login", defaults.Static.ManagedLogin, "create or reuse a Crabbox-managed static VNC login when supported")
managedUser := fs.String("managed-user", firstNonEmpty(defaults.Static.ManagedUser, "crabbox"), "static managed VNC login user")
targetFlags := registerTargetFlags(fs, defaults)
if err := parseFlags(fs, args); err != nil {
return err
@ -33,8 +31,6 @@ func (a App) vnc(ctx context.Context, args []string) error {
}
cfg.Provider = *provider
cfg.Desktop = true
cfg.Static.ManagedLogin = *managedLogin
cfg.Static.ManagedUser = *managedUser
if err := applyTargetFlagOverrides(&cfg, fs, targetFlags); err != nil {
return err
}
@ -44,7 +40,7 @@ func (a App) vnc(ctx context.Context, args []string) error {
if *id == "" && !isStaticProvider(cfg.Provider) {
return exit(2, "usage: crabbox vnc --id <lease-id-or-slug>")
}
if *openClient && isStaticProvider(cfg.Provider) && !*hostManaged && !cfg.Static.ManagedLogin {
if *openClient && isStaticProvider(cfg.Provider) && !*hostManaged {
return exit(2, "static %s VNC is an existing host, not a Crabbox-created box; rerun with --host-managed only if you want to open that host's OS login prompt", cfg.TargetOS)
}
server, target, leaseID, err := a.resolveLeaseTarget(ctx, cfg, *id)
@ -62,10 +58,6 @@ func (a App) vnc(ctx context.Context, args []string) error {
return err
}
a.touchActiveLeaseBestEffort(ctx, cfg, server, leaseID)
login, err := ensureStaticManagedVNCLogin(ctx, cfg, target)
if err != nil {
return err
}
endpoint, err := resolveVNCEndpoint(ctx, cfg, target)
if err != nil {
return err
@ -75,9 +67,7 @@ func (a App) vnc(ctx context.Context, args []string) error {
}
password := ""
if endpoint.Managed {
if login.User != "" {
password = login.Password
} else if target.TargetOS == targetLinux {
if target.TargetOS == targetLinux {
password, _ = runSSHOutput(ctx, target, "cat "+shellQuote(vncPasswordPath))
}
}
@ -103,11 +93,7 @@ func (a App) vnc(ctx context.Context, args []string) error {
if endpoint.Direct {
fmt.Fprintln(a.Stdout, "direct vnc:")
fmt.Fprintf(a.Stdout, " %s:%s\n", endpoint.Host, endpoint.Port)
directURL := fmt.Sprintf("vnc://%s:%s", endpoint.Host, endpoint.Port)
if login.User != "" {
directURL = fmt.Sprintf("vnc://%s@%s:%s", login.User, endpoint.Host, endpoint.Port)
}
fmt.Fprintf(a.Stdout, " %s\n", directURL)
fmt.Fprintf(a.Stdout, " vnc://%s:%s\n", endpoint.Host, endpoint.Port)
} else {
fmt.Fprintln(a.Stdout, "ssh tunnel:")
fmt.Fprintf(a.Stdout, " %s\n", tunnel)
@ -119,12 +105,6 @@ func (a App) vnc(ctx context.Context, args []string) error {
fmt.Fprintf(a.Stdout, " localhost:%s\n", *localPort)
}
if strings.TrimSpace(password) != "" {
if login.User != "" {
fmt.Fprintf(a.Stdout, "username: %s\n", login.User)
if login.PasswordPath != "" {
fmt.Fprintf(a.Stdout, "password_file: %s\n", login.PasswordPath)
}
}
fmt.Fprintf(a.Stdout, "password: %s\n", strings.TrimSpace(password))
} else if staticHostVNC {
fmt.Fprintln(a.Stdout, "credentials: host-managed")
@ -140,9 +120,6 @@ func (a App) vnc(ctx context.Context, args []string) error {
fmt.Fprintln(a.Stdout, "opening existing host VNC; expect that host's OS credential prompt")
}
url := fmt.Sprintf("vnc://%s:%s", endpoint.Host, endpoint.Port)
if login.User != "" {
url = fmt.Sprintf("vnc://%s@%s:%s", login.User, endpoint.Host, endpoint.Port)
}
if !endpoint.Direct {
pid, err := startVNCTunnel(ctx, target, *localPort, endpoint.Host, endpoint.Port)
if err != nil {
@ -154,9 +131,6 @@ func (a App) vnc(ctx context.Context, args []string) error {
fmt.Fprintln(a.Stdout, "tunnel: started in background")
}
url = fmt.Sprintf("vnc://localhost:%s", *localPort)
if login.User != "" {
url = fmt.Sprintf("vnc://%s@localhost:%s", login.User, *localPort)
}
}
if err := openLocalURL(url); err != nil {
return err