Reapply "fix: harden WSL2 work root and sync"
This reverts commit bbb8183eca.
This commit is contained in:
parent
0ca412a8d5
commit
15c10ee2e2
@ -16,6 +16,9 @@
|
||||
- Fixed Windows WebVNC credential handling so generated portal links preserve special characters and managed TightVNC sessions copy service passwords into the logged-in user's registry profile.
|
||||
- Fixed managed Linux browser setup so Chrome/Chromium launches skip first-run and default-browser prompts.
|
||||
- Fixed WebVNC portal passwords with escaped special characters and kept the bridge alive across viewer resets and transient coordinator EOFs.
|
||||
- Fixed managed AWS Windows WSL2 bootstrap by using the current Ubuntu WSL rootfs URL, downloading large rootfs files through `curl.exe`, and retrying empty or partial rootfs downloads instead of reusing a poisoned tarball. Thanks @vincentkoc.
|
||||
- Fixed AWS Windows WSL2 mode overrides so they refresh the default instance type to a nested-virtualization-capable family. Thanks @steipete.
|
||||
- Fixed AWS Windows WSL2 runs so mode overrides also refresh the default work root to `/work/crabbox` and sync via a WSL archive stream instead of rsync's remote protocol through Windows OpenSSH.
|
||||
|
||||
## 0.5.0 - 2026-05-04
|
||||
|
||||
@ -49,8 +52,6 @@
|
||||
- Fixed failed Blacksmith Testbox warmups so printed, newly listed, or delayed `tbx_...` boxes are stopped instead of being left queued after an upstream workflow error.
|
||||
- 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 native Windows `--shell` runs so multi-statement PowerShell scripts keep their quotes instead of being re-parsed by a nested PowerShell process.
|
||||
- Fixed managed AWS Windows WSL2 bootstrap by using the current Ubuntu WSL rootfs URL, downloading large rootfs files through `curl.exe`, and retrying empty or partial rootfs downloads instead of reusing a poisoned tarball. Thanks @vincentkoc.
|
||||
- Fixed AWS Windows WSL2 mode overrides so they refresh the default instance type to a nested-virtualization-capable family. Thanks @steipete.
|
||||
- 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.
|
||||
- Quoted `crabbox vnc` tunnel key paths so macOS `Application Support` lease keys can be pasted directly into a shell.
|
||||
|
||||
@ -182,7 +182,7 @@ func baseConfig() Config {
|
||||
SSHPort: "2222",
|
||||
SSHFallbackPorts: []string{"22"},
|
||||
ProviderKey: "crabbox-steipete",
|
||||
WorkRoot: "/work/crabbox",
|
||||
WorkRoot: defaultPOSIXWorkRoot,
|
||||
TTL: 90 * time.Minute,
|
||||
IdleTimeout: 30 * time.Minute,
|
||||
Sync: SyncConfig{
|
||||
|
||||
@ -374,6 +374,17 @@ func (a App) runCommand(ctx context.Context, args []string) (err error) {
|
||||
recorder.Event("sync.finished", "synced", fmt.Sprintf("duration=%s mode=archive", timings.sync.Round(time.Millisecond)))
|
||||
goto afterSync
|
||||
}
|
||||
if isWindowsWSL2Target(target) {
|
||||
stepStart = time.Now()
|
||||
if err := syncWindowsWSL2(ctx, target, repo, cfg, workdir, manifest, a.Stdout, a.Stderr, rsyncOptions{Debug: *debugSync, Delete: cfg.Sync.Delete, Checksum: cfg.Sync.Checksum, Timeout: cfg.Sync.Timeout, HeartbeatInterval: 15 * time.Second}); err != nil {
|
||||
return recordFailure(err)
|
||||
}
|
||||
timings.syncSteps.rsync = time.Since(stepStart)
|
||||
timings.sync = time.Since(syncStart)
|
||||
fmt.Fprintf(a.Stderr, "sync complete in %s\n", timings.sync.Round(time.Millisecond))
|
||||
recorder.Event("sync.finished", "synced", fmt.Sprintf("duration=%s mode=archive-wsl2", timings.sync.Round(time.Millisecond)))
|
||||
goto afterSync
|
||||
}
|
||||
fingerprint := ""
|
||||
if cfg.Sync.Fingerprint {
|
||||
stepStart = time.Now()
|
||||
|
||||
@ -168,6 +168,7 @@ func TestApplyServerTypeFlagOverridesUsesTargetAwareAWSDefaults(t *testing.T) {
|
||||
WindowsMode: windowsModeNormal,
|
||||
Class: "beast",
|
||||
ServerType: "c7a.48xlarge",
|
||||
WorkRoot: defaultWindowsWorkRoot,
|
||||
}
|
||||
fs := newFlagSet("test", io.Discard)
|
||||
provider := fs.String("provider", cfg.Provider, "")
|
||||
@ -186,6 +187,9 @@ func TestApplyServerTypeFlagOverridesUsesTargetAwareAWSDefaults(t *testing.T) {
|
||||
if cfg.ServerType != tt.want {
|
||||
t.Fatalf("serverType=%q want %q", cfg.ServerType, tt.want)
|
||||
}
|
||||
if cfg.WindowsMode == windowsModeWSL2 && cfg.WorkRoot != defaultPOSIXWorkRoot {
|
||||
t.Fatalf("workRoot=%q want %q", cfg.WorkRoot, defaultPOSIXWorkRoot)
|
||||
}
|
||||
if cfg.ServerTypeExplicit {
|
||||
t.Fatal("ServerTypeExplicit=true, want false")
|
||||
}
|
||||
@ -193,6 +197,62 @@ func TestApplyServerTypeFlagOverridesUsesTargetAwareAWSDefaults(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyTargetFlagOverridesRefreshesDefaultWorkRoot(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg Config
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "native windows to wsl2",
|
||||
cfg: Config{
|
||||
TargetOS: targetWindows,
|
||||
WindowsMode: windowsModeNormal,
|
||||
WorkRoot: defaultWindowsWorkRoot,
|
||||
},
|
||||
args: []string{"--windows-mode", "wsl2"},
|
||||
want: defaultPOSIXWorkRoot,
|
||||
},
|
||||
{
|
||||
name: "wsl2 to native windows",
|
||||
cfg: Config{
|
||||
TargetOS: targetWindows,
|
||||
WindowsMode: windowsModeWSL2,
|
||||
WorkRoot: defaultPOSIXWorkRoot,
|
||||
},
|
||||
args: []string{"--windows-mode", "normal"},
|
||||
want: defaultWindowsWorkRoot,
|
||||
},
|
||||
{
|
||||
name: "custom root is preserved",
|
||||
cfg: Config{
|
||||
TargetOS: targetWindows,
|
||||
WindowsMode: windowsModeNormal,
|
||||
WorkRoot: `/custom/root`,
|
||||
},
|
||||
args: []string{"--windows-mode", "wsl2"},
|
||||
want: `/custom/root`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := newFlagSet("test", io.Discard)
|
||||
targetFlags := registerTargetFlags(fs, tt.cfg)
|
||||
if err := parseFlags(fs, tt.args); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg := tt.cfg
|
||||
if err := applyTargetFlagOverrides(&cfg, fs, targetFlags); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.WorkRoot != tt.want {
|
||||
t.Fatalf("workRoot=%q want %q", cfg.WorkRoot, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyServerTypeFlagOverridesPreservesExplicitType(t *testing.T) {
|
||||
cfg := Config{
|
||||
Provider: "aws",
|
||||
|
||||
@ -382,6 +382,26 @@ func TestRemoteApplySyncManifestOnlyCommitsManifest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPOSIXPrepareWorkdirDeletesButPreservesGit(t *testing.T) {
|
||||
workdir := t.TempDir()
|
||||
mustWriteTestFile(t, filepath.Join(workdir, ".git", "config"), "git")
|
||||
mustWriteTestFile(t, filepath.Join(workdir, "stale.txt"), "stale")
|
||||
mustWriteTestFile(t, filepath.Join(workdir, "old", "nested.txt"), "old")
|
||||
|
||||
cmd := exec.Command("bash", "-lc", posixPrepareWorkdir(workdir, true))
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("posix prepare failed: %v\n%s", err, out)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(workdir, ".git", "config")); err != nil {
|
||||
t.Fatalf(".git should survive prepare: %v", err)
|
||||
}
|
||||
for _, rel := range []string{"stale.txt", "old"} {
|
||||
if _, err := os.Stat(filepath.Join(workdir, rel)); !os.IsNotExist(err) {
|
||||
t.Fatalf("%s should be removed, stat err=%v", rel, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteGitHydrateStatusUsesMarkerAndRemoteBase(t *testing.T) {
|
||||
got := remoteGitHydrateStatus("/work/repo", "main", "abc123")
|
||||
for _, want := range []string{
|
||||
|
||||
@ -45,10 +45,78 @@ func syncWindowsNative(ctx context.Context, target SSHTarget, repo Repo, cfg Con
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncWindowsWSL2(ctx context.Context, target SSHTarget, repo Repo, cfg Config, workdir string, manifest SyncManifest, stdout, stderr anyWriter, opts rsyncOptions) error {
|
||||
if err := runSSHQuiet(ctx, target, posixPrepareWorkdir(workdir, cfg.Sync.Delete)); err != nil {
|
||||
return exit(7, "prepare remote workdir: %v", err)
|
||||
}
|
||||
if cfg.Sync.GitSeed {
|
||||
if err := runSSHQuiet(ctx, target, remoteGitSeed(workdir, repo.RemoteURL, repo.Head)); err != nil {
|
||||
fmt.Fprintf(stderr, "warning: remote git seed failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
if err := syncArchive(ctx, target, repo, workdir, manifest, stdout, stderr, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if out, err := runSSHCombinedOutput(ctx, target, remoteSyncSanity(workdir, false)); err != nil {
|
||||
if out != "" {
|
||||
return exit(6, "remote sync sanity failed: %s: %v", out, err)
|
||||
}
|
||||
return exit(6, "remote sync sanity failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncArchive(ctx context.Context, target SSHTarget, repo Repo, workdir string, manifest SyncManifest, stdout, stderr anyWriter, opts rsyncOptions) error {
|
||||
var input bytes.Buffer
|
||||
input.Write(manifest.NUL())
|
||||
cmd := exec.CommandContext(ctx, "tar", "-czf", "-", "-C", repo.Root, "--null", "-T", "-")
|
||||
cmd.Stdin = &input
|
||||
var archive bytes.Buffer
|
||||
cmd.Stdout = &archive
|
||||
cmd.Stderr = stderr
|
||||
start := time.Now()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return exit(6, "create sync archive: %v", err)
|
||||
}
|
||||
if opts.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
stopHeartbeat := startSyncHeartbeat(stderr, start, opts.HeartbeatInterval)
|
||||
err := runSSHInput(ctx, target, posixExtractArchive(workdir), &archive, stdout, stderr)
|
||||
stopHeartbeat()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return exit(6, "archive sync timed out after %s", opts.Timeout)
|
||||
}
|
||||
if err != nil {
|
||||
return exit(6, "archive sync failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type anyWriter interface {
|
||||
Write([]byte) (int, error)
|
||||
}
|
||||
|
||||
func posixPrepareWorkdir(workdir string, delete bool) string {
|
||||
deleteScript := ""
|
||||
if delete {
|
||||
deleteScript = `
|
||||
find . -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf -- {} +
|
||||
`
|
||||
}
|
||||
return "bash -lc " + shellQuote(`set -e
|
||||
mkdir -p `+shellQuote(workdir)+`
|
||||
cd `+shellQuote(workdir)+deleteScript)
|
||||
}
|
||||
|
||||
func posixExtractArchive(workdir string) string {
|
||||
return "bash -lc " + shellQuote(`set -e
|
||||
mkdir -p `+shellQuote(workdir)+`
|
||||
tar -xzf - -C `+shellQuote(workdir))
|
||||
}
|
||||
|
||||
func windowsPrepareWorkdir(workdir string, delete bool) string {
|
||||
deleteScript := ""
|
||||
if delete {
|
||||
|
||||
@ -12,17 +12,16 @@ const (
|
||||
|
||||
windowsModeNormal = "normal"
|
||||
windowsModeWSL2 = "wsl2"
|
||||
|
||||
defaultPOSIXWorkRoot = "/work/crabbox"
|
||||
defaultWindowsWorkRoot = `C:\crabbox`
|
||||
)
|
||||
|
||||
func normalizeTargetConfig(cfg *Config) {
|
||||
cfg.TargetOS = normalizeTargetOS(cfg.TargetOS)
|
||||
cfg.WindowsMode = normalizeWindowsMode(cfg.WindowsMode)
|
||||
if cfg.TargetOS == targetWindows && cfg.WorkRoot == "/work/crabbox" {
|
||||
if cfg.WindowsMode == windowsModeWSL2 {
|
||||
cfg.WorkRoot = "/work/crabbox"
|
||||
} else {
|
||||
cfg.WorkRoot = `C:\crabbox`
|
||||
}
|
||||
if isDefaultWorkRoot(cfg.WorkRoot) {
|
||||
cfg.WorkRoot = defaultWorkRootForTarget(cfg.TargetOS, cfg.WindowsMode)
|
||||
}
|
||||
if cfg.Provider == "aws" && cfg.TargetOS == targetMacOS && cfg.SSHUser == baseConfig().SSHUser {
|
||||
cfg.SSHUser = "ec2-user"
|
||||
@ -41,6 +40,22 @@ func normalizeTargetConfig(cfg *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func isDefaultWorkRoot(value string) bool {
|
||||
switch value {
|
||||
case "", defaultPOSIXWorkRoot, defaultWindowsWorkRoot:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func defaultWorkRootForTarget(targetOS, windowsMode string) string {
|
||||
if targetOS == targetWindows && windowsMode == windowsModeNormal {
|
||||
return defaultWindowsWorkRoot
|
||||
}
|
||||
return defaultPOSIXWorkRoot
|
||||
}
|
||||
|
||||
func normalizeTargetOS(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "", "linux", "ubuntu":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user