Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions ci/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -105,15 +105,17 @@ 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 && " +
"which firecracker && firecracker --version",
}
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)
}


Expand Down
57 changes: 4 additions & 53 deletions coverage2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -313,11 +314,11 @@ func TestInstallSystemPackagesBrewFailures(t *testing.T) {
defer resetMocks()
pkgMgr = "brew"
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
captureStdout(t, func() {
out := captureStdout(t, func() {
installSystemPackages([]string{"git"}, nil)
})
if !hasIssueContaining("System package failed to install") {
t.Error("expected error logged for brew failure")
if !strings.Contains(out, "System package failed to install") {
t.Error("expected warning logged for brew failure")
}
}

Expand Down Expand Up @@ -610,57 +611,7 @@ func TestSetupVivaldiRepoExisting(t *testing.T) {

// ── special installer branches ──────────────────────────────────────────

func TestInstallBashtopExistingClone(t *testing.T) {
defer resetMocks()
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} }
installBashtop("/tmp")
}

func TestInstallBashtopPullFails(t *testing.T) {
defer resetMocks()
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
runCmd = func(argv []string, _ CmdOpts) CmdResult {
if len(argv) > 1 && argv[1] == "-C" {
return CmdResult{ExitCode: 1}
}
return CmdResult{ExitCode: 0}
}
installBashtop("/tmp")
if !hasIssueContaining("bashtop git pull failed") {
t.Error("expected pull failure error")
}
}

func TestInstallBashtopCloneFails(t *testing.T) {
defer resetMocks()
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
runCmd = func(argv []string, _ CmdOpts) CmdResult {
if argv[0] == "git" && argv[1] == "clone" {
return CmdResult{ExitCode: 1}
}
return CmdResult{ExitCode: 0}
}
installBashtop("/tmp")
if !hasIssueContaining("bashtop git clone failed") {
t.Error("expected clone failure error")
}
}

func TestInstallBashtopMakeFails(t *testing.T) {
defer resetMocks()
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
runCmd = func(argv []string, _ CmdOpts) CmdResult {
if argv[0] == "make" {
return CmdResult{ExitCode: 1}
}
return CmdResult{ExitCode: 0}
}
installBashtop("/tmp")
if !hasIssueContaining("make install") {
t.Error("expected make install failure error")
}
}

func TestInstallPulumiNoVersion(t *testing.T) {
defer resetMocks()
Expand Down
4 changes: 4 additions & 0 deletions coverage3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
82 changes: 76 additions & 6 deletions custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -321,6 +324,48 @@ func installNeovim(_ *CustomPackage, tmp string) {
taskPrintf(" Neovim installed to %s, symlinked at %s\n", installDir, symlink)
}

func installRustup(archive string) {
out := taskOut()
runCmd([]string{"chmod", "+x", archive}, CmdOpts{Out: out})
runCmd([]string{archive, "-y", "--no-modify-path"}, CmdOpts{Out: out})
taskPrintln(" rustup installed.")
}

func installYq(archive string) {
out := taskOut()
runCmd([]string{"cp", archive, "/usr/local/bin/yq"}, CmdOpts{AsSudo: true, Out: out})
runCmd([]string{"chmod", "+x", "/usr/local/bin/yq"}, CmdOpts{AsSudo: true, Out: out})
taskPrintln(" yq installed.")
}

func installDagger(archive, tmp string) {
out := taskOut()
runCmd([]string{"tar", "-C", tmp, "-xzf", archive}, CmdOpts{Out: out})
runCmd([]string{"mv", filepath.Join(tmp, "dagger"), "/usr/local/bin/dagger"}, CmdOpts{AsSudo: true, Out: out})
taskPrintln(" dagger installed.")
}

func installTrivy(archive, tmp string) {
out := taskOut()
runCmd([]string{"tar", "-C", tmp, "-xzf", archive}, CmdOpts{Out: out})
runCmd([]string{"mv", filepath.Join(tmp, "trivy"), "/usr/local/bin/trivy"}, CmdOpts{AsSudo: true, Out: out})
taskPrintln(" trivy installed.")
}

func installCosign(archive string) {
out := taskOut()
runCmd([]string{"cp", archive, "/usr/local/bin/cosign"}, CmdOpts{AsSudo: true, Out: out})
runCmd([]string{"chmod", "+x", "/usr/local/bin/cosign"}, CmdOpts{AsSudo: true, Out: out})
taskPrintln(" cosign installed.")
}

func installGitleaks(archive, tmp string) {
out := taskOut()
runCmd([]string{"tar", "-C", tmp, "-xzf", archive}, CmdOpts{Out: out})
runCmd([]string{"mv", filepath.Join(tmp, "gitleaks"), "/usr/local/bin/gitleaks"}, CmdOpts{AsSudo: true, Out: out})
taskPrintln(" gitleaks installed.")
}

// ── latest-version resolvers ────────────────────────────────────────────

func resolveLatestGo(_ *CustomPackage) (string, string, bool) {
Expand Down Expand Up @@ -434,10 +479,23 @@ func cmpSemver(a, b string) int {
return len(pa) - len(pb)
}

func resolveLatestYq(_ *CustomPackage) (string, string, bool) {
var rel ghRelease
if !fetchJSON("https://api.github.com/repos/mikefarah/yq/releases/latest", &rel) {
return "", "", false
}
version := strings.TrimPrefix(rel.TagName, "v")
if version == "" {
return "", "", false
}
return version, "", true
}

var latestResolvers = map[string]func(*CustomPackage) (string, string, bool){
"go": resolveLatestGo,
"firecracker": resolveLatestFirecracker,
"zig": resolveLatestZig,
"yq": resolveLatestYq,
}

// resolveLatest best-effort upgrades pkg.Version/SHA256 to the latest release.
Expand Down Expand Up @@ -598,6 +656,18 @@ func runOneCustomInstall(pkg *CustomPackage) {
installFirecracker(archive, tmp)
case "zig":
installZig(pkg, archive)
case "rustup":
installRustup(archive)
case "yq":
installYq(archive)
case "dagger":
installDagger(archive, tmp)
case "trivy":
installTrivy(archive, tmp)
case "cosign":
installCosign(archive)
case "gitleaks":
installGitleaks(archive, tmp)
default:
warn(fmt.Sprintf("No install handler for '%s' — skipping", pkg.Name))
}
Expand Down
11 changes: 11 additions & 0 deletions detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ var (
osZig = map[string]string{"linux": "linux", "macos": "macos"}
osNvim = map[string]string{"linux": "linux", "macos": "macos"}

osRustup = map[string]string{"linux": "unknown-linux-gnu", "macos": "apple-darwin"}
archRustup = map[string]string{"x86_64": "x86_64", "aarch64": "aarch64"}
osTrivy = map[string]string{"linux": "Linux", "macos": "macOS"}
archTrivy = map[string]string{"x86_64": "64bit", "aarch64": "ARM64"}
archGitleaks = map[string]string{"x86_64": "x64", "aarch64": "arm64"}

archTokens = map[string][]string{
"x86_64": {"x86_64", "amd64", "x64"},
"aarch64": {"aarch64", "arm64"},
Expand All @@ -81,6 +87,11 @@ func formatURL(template, version string) string {
"{os_go}", osGo[osName],
"{os_zig}", osZig[osName],
"{os_nvim}", osNvim[osName],
"{os_rustup}", osRustup[osName],
"{arch_rustup}", archRustup[archName],
"{os_trivy}", osTrivy[osName],
"{arch_trivy}", archTrivy[archName],
"{arch_gitleaks}", archGitleaks[archName],
)
return r.Replace(template)
}
Expand Down
9 changes: 7 additions & 2 deletions helper_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package main

import (
"errors"
"os"
"strings"
)

func resetMocks() {
Expand All @@ -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
Expand Down
18 changes: 16 additions & 2 deletions macos.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
6 changes: 5 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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))
Expand Down
Loading
Loading