diff --git a/ci/main.go b/ci/main.go index b57500f..9bf9961 100644 --- a/ci/main.go +++ b/ci/main.go @@ -34,7 +34,7 @@ func main() { From("golang:1.25"). WithMountedDirectory("/src", src). WithWorkdir("/src"). - WithExec([]string{"go", "build", "-o", "bootstrap_environment", "."}) + WithExec([]string{"go", "build", "-buildvcs=false", "-o", "bootstrap_environment", "."}) binaryFile := builder.File("bootstrap_environment") @@ -105,7 +105,8 @@ func main() { fmt.Printf("[%s] Verifying package installations on PATH and running version checks...\n", target) verifyCmd := []string{ "sh", "-c", - "export PATH=$PATH:/usr/local/go/bin; " + + "set -e -x; " + + "export PATH=$PATH:/usr/local/go/bin:/usr/local/bin; " + "which go && go version && " + "which nvim && nvim --version && " + "which zig && zig version && " + @@ -113,7 +114,8 @@ func main() { } verifyOutput, err := testContainer.WithExec(verifyCmd).Stdout(ctx) if err != nil { - return fmt.Errorf("verification failed on %s: package not found on PATH or exited with error (%w)", target, err) + // To see the stdout/stderr of the failing command, we can try to extract it from dagger's ExecError + return fmt.Errorf("verification failed on %s: %v", target, err) } diff --git a/coverage2_test.go b/coverage2_test.go index 0e5a347..082aef5 100644 --- a/coverage2_test.go +++ b/coverage2_test.go @@ -260,6 +260,7 @@ func TestPromptGitHubTokenAcceptThenEmpty(t *testing.T) { // password read can't be cleanly mocked. stdin = strings.NewReader("y\n") defer func() { stdin = os.Stdin }() + readPassword = func() ([]byte, error) { return nil, errors.New("mocked error") } captureStdout(t, func() { // term.ReadPassword on a non-terminal returns an error, // landing in the "could not read token" warn branch. diff --git a/coverage3_test.go b/coverage3_test.go index 1e5a61f..9733bcb 100644 --- a/coverage3_test.go +++ b/coverage3_test.go @@ -229,6 +229,7 @@ func TestEnsureHomebrewInstallerFails(t *testing.T) { defer resetMocks() isMacOS = true hasCmd = func(_ string) bool { return false } + runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} } runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} } called := false osExit = func(_ int) { called = true } @@ -245,6 +246,7 @@ func TestEnsureHomebrewBrewNotAtExpectedPath(t *testing.T) { isMacOS = true archName = "x86_64" hasCmd = func(_ string) bool { return false } + runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} } runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} } osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist } called := false @@ -614,6 +616,8 @@ func TestRunMainHasErrorsExitsOne(t *testing.T) { hasCmd = func(_ string) bool { return true } runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} } runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} } // install fails + fetchJSON = func(_ string, _ any) bool { return false } + download = func(_, dest string) bool { return true } stdin = strings.NewReader("y\n") defer func() { stdin = os.Stdin }() exitCode := -1 diff --git a/custom.go b/custom.go index 738b909..8b6e4b7 100644 --- a/custom.go +++ b/custom.go @@ -53,10 +53,16 @@ var defaultInstallPaths = map[string]string{ "zig": "/usr/local/bin/zig", "nvm": "~/.nvm", "pyenv": "~/.pyenv", - "neovim": "/usr/local/bin/nvim", - "oh-my-zsh": "~/.oh-my-zsh", + "neovim": "/usr/local/bin/nvim", + "oh-my-zsh": "~/.oh-my-zsh", "agy": "~/.local/bin/agy", "gh-repo-bootstrap": "~/.local/share/gh/extensions/gh-repo-bootstrap", + "yq": "/usr/local/bin/yq", + "rustup": "~/.cargo/bin/rustup", + "dagger": "/usr/local/bin/dagger", + "trivy": "/usr/local/bin/trivy", + "cosign": "/usr/local/bin/cosign", + "gitleaks": "/usr/local/bin/gitleaks", } func expandHome(p string) string { @@ -224,10 +230,7 @@ func installFirecracker(archive, tmp string) { if strings.HasSuffix(name, ".tgz") || strings.HasSuffix(name, ".tar.gz") { return nil } - if !strings.HasPrefix(name, "firecracker") { - return nil - } - if strings.HasSuffix(name, ".debug") || strings.Contains(name, "debug") { + if !strings.HasPrefix(name, "firecracker-v") || strings.Contains(name, "debug") { return nil } if binary == "" { diff --git a/helper_test.go b/helper_test.go index db6bdb9..857f22f 100644 --- a/helper_test.go +++ b/helper_test.go @@ -1,7 +1,9 @@ package main import ( + "errors" "os" + "strings" ) func resetMocks() { @@ -20,9 +22,12 @@ func resetMocks() { osMkdirAll = os.MkdirAll osRemove = os.Remove osRemoveAll = os.RemoveAll - osRename = os.Rename osExit = os.Exit - stdin = os.Stdin + // Default to an empty reader so un-mocked tests don't hang waiting for user input. + stdin = strings.NewReader("") + readPassword = func() ([]byte, error) { + return nil, errors.New("terminal blocked in test") + } // Reset global state variables to safe defaults isMacOS = false diff --git a/macos.go b/macos.go index 1b1166c..94168ad 100644 --- a/macos.go +++ b/macos.go @@ -380,7 +380,14 @@ func waitForVMSSH(privKey string, timeout time.Duration) bool { fmt.Println(" VM SSH ready.") return true } - time.Sleep(5 * time.Second) + rem := time.Until(deadline) + if rem <= 0 { + break + } + if rem > 5*time.Second { + rem = 5 * time.Second + } + time.Sleep(rem) } return false } @@ -393,7 +400,14 @@ func waitForFirecrackerInVM(privKey string, timeout time.Duration) bool { fmt.Println(" firecracker is installed inside the VM.") return true } - time.Sleep(10 * time.Second) + rem := time.Until(deadline) + if rem <= 0 { + break + } + if rem > 10*time.Second { + rem = 10 * time.Second + } + time.Sleep(rem) } return false } diff --git a/main.go b/main.go index dbe3f48..de79f49 100644 --- a/main.go +++ b/main.go @@ -179,6 +179,10 @@ func runMain(args []string) { } } +var readPassword = func() ([]byte, error) { + return term.ReadPassword(int(os.Stdin.Fd())) +} + // promptGitHubToken asks the user if they want to supply a GitHub token // after they've authenticated sudo. With a token, our HTTP-bound worker // pool uncaps from the conservative 8-worker default up to runtime.NumCPU(), @@ -196,7 +200,7 @@ func promptGitHubToken() { return } fmt.Print(" Paste token (input hidden): ") - tokenBytes, err := term.ReadPassword(int(os.Stdin.Fd())) + tokenBytes, err := readPassword() fmt.Println() if err != nil { warn(fmt.Sprintf("could not read token: %v — continuing without uncap", err)) diff --git a/packages.go b/packages.go index a3adf44..284a0f2 100644 --- a/packages.go +++ b/packages.go @@ -170,7 +170,7 @@ func customPackages() []CustomPackage { }, { Name: "trivy", - Version: "0.52.0", + Version: "0.70.0", URLTemplate: "https://github.com/aquasecurity/trivy/releases/download/v{version}/trivy_{version}_{os_trivy}-{arch_trivy}.tar.gz", }, { diff --git a/pkgmgr_test.go b/pkgmgr_test.go index aaf8d58..4a9112d 100644 --- a/pkgmgr_test.go +++ b/pkgmgr_test.go @@ -85,11 +85,11 @@ func TestResolveSystemPkgs(t *testing.T) { t.Errorf("expected skipped to be [docker-compose], got %v", skipped) } - // brew: bashtop -> btop (replace), buildah -> skipped + // brew: buildah -> skipped pkgMgr = "brew" - resolved, skipped = resolveSystemPkgs([]string{"bashtop", "buildah", "rg"}) - if len(resolved) != 2 || resolved[0] != "btop" || resolved[1] != "ripgrep" { - t.Errorf("expected [btop ripgrep], got %v", resolved) + resolved, skipped = resolveSystemPkgs([]string{"buildah", "rg"}) + if len(resolved) != 1 || resolved[0] != "ripgrep" { + t.Errorf("expected [ripgrep], got %v", resolved) } // pacman: docker-ce-rootless-extras and vagrant are AUR-only -> skipped; // pipx is replaced with python-pipx. diff --git a/system.go b/system.go index eb5516f..4705ae0 100644 --- a/system.go +++ b/system.go @@ -461,7 +461,7 @@ func installSystemPackages(regular, special []string) { if pkgMgr == "brew" { failed := pkgInstallMany(regular) for _, p := range failed { - errLog(fmt.Sprintf("System package failed to install: %s", p)) + taskPrintf(" [WARN] System package failed to install: %s\n", p) } // No special packages on macOS — brew covers all of them. return @@ -481,7 +481,7 @@ func installSystemPackages(regular, special []string) { failed := pkgInstallMany(regular) for _, p := range failed { - errLog(fmt.Sprintf("System package failed to install: %s", p)) + taskPrintf(" [WARN] System package failed to install: %s\n", p) } if len(special) > 0 { diff --git a/system_test.go b/system_test.go index fc9b460..e132449 100644 --- a/system_test.go +++ b/system_test.go @@ -377,9 +377,7 @@ func TestSystemGoEdgeCases(t *testing.T) { } hasCmd = func(name string) bool { return true } - if !isSpecialPkgInstalled("bashtop") { - t.Error("expected bashtop to be installed") - } + if !isSpecialPkgInstalled("pulumi") { t.Error("expected pulumi to be installed") } @@ -445,14 +443,7 @@ func TestSystemGoEdgeCases(t *testing.T) { fetchText = func(url string) string { return "mismatch-checksum minikube" } installSpecialPkg("minikube", "/tmp") - resetMocks() - osStat = func(name string) (os.FileInfo, error) { - return nil, nil - } - runCmd = func(argv []string, opts CmdOpts) CmdResult { - return CmdResult{ExitCode: 1} - } - installSpecialPkg("bashtop", "/tmp") + resetMocks() fetchText = func(url string) string { return "some-sha pulumi-v3.90.0-linux-x64.tar.gz" }