Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60cf195f45 | ||
|
|
a5726a7a60 | ||
|
|
ac933f7c7d | ||
|
|
206b4cb999 | ||
|
|
83c929c24b | ||
|
|
430b018aa9 | ||
|
|
c4f7f6d240 | ||
|
|
bc021018cd | ||
|
|
031bc618ec | ||
|
|
a7047d5018 |
@@ -1,5 +1,8 @@
|
|||||||
name: Integration Tests
|
name: Integration Tests
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -21,10 +24,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.x'
|
go-version: '1.25.x'
|
||||||
|
|
||||||
@@ -39,10 +42,10 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.x'
|
go-version: '1.25.x'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
name: macOS Manual Integration
|
name: macOS Manual Integration
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
@@ -28,12 +31,12 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.branch }}
|
ref: ${{ inputs.branch }}
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.x'
|
go-version: '1.25.x'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -58,13 +61,13 @@ jobs:
|
|||||||
suffix: darwin-arm64
|
suffix: darwin-arm64
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.resolve.outputs.ref }}
|
ref: ${{ needs.resolve.outputs.ref }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@@ -82,7 +85,7 @@ jobs:
|
|||||||
go build -trimpath -ldflags="-s -w" -o "$out" .
|
go build -trimpath -ldflags="-s -w" -o "$out" .
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: bootstrap_environment-${{ matrix.suffix }}
|
name: bootstrap_environment-${{ matrix.suffix }}
|
||||||
path: dist/bootstrap_environment-${{ matrix.suffix }}
|
path: dist/bootstrap_environment-${{ matrix.suffix }}
|
||||||
@@ -94,13 +97,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.resolve.outputs.ref }}
|
ref: ${{ needs.resolve.outputs.ref }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Download build artifacts
|
- name: Download build artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
pattern: bootstrap_environment-*
|
pattern: bootstrap_environment-*
|
||||||
@@ -114,7 +117,7 @@ jobs:
|
|||||||
cat SHA256SUMS
|
cat SHA256SUMS
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag_name: ${{ needs.resolve.outputs.tag }}
|
tag_name: ${{ needs.resolve.outputs.tag }}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
name: Unit Tests
|
name: Unit Tests
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
@@ -13,10 +16,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.x'
|
go-version: '1.25.x'
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -34,7 +34,7 @@ func main() {
|
|||||||
From("golang:1.25").
|
From("golang:1.25").
|
||||||
WithMountedDirectory("/src", src).
|
WithMountedDirectory("/src", src).
|
||||||
WithWorkdir("/src").
|
WithWorkdir("/src").
|
||||||
WithExec([]string{"go", "build", "-o", "bootstrap_environment", "."})
|
WithExec([]string{"go", "build", "-buildvcs=false", "-o", "bootstrap_environment", "."})
|
||||||
|
|
||||||
binaryFile := builder.File("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)
|
fmt.Printf("[%s] Verifying package installations on PATH and running version checks...\n", target)
|
||||||
verifyCmd := []string{
|
verifyCmd := []string{
|
||||||
"sh", "-c",
|
"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 go && go version && " +
|
||||||
"which nvim && nvim --version && " +
|
"which nvim && nvim --version && " +
|
||||||
"which zig && zig version && " +
|
"which zig && zig version && " +
|
||||||
@@ -113,7 +114,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
verifyOutput, err := testContainer.WithExec(verifyCmd).Stdout(ctx)
|
verifyOutput, err := testContainer.WithExec(verifyCmd).Stdout(ctx)
|
||||||
if err != nil {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+4
-53
@@ -260,6 +260,7 @@ func TestPromptGitHubTokenAcceptThenEmpty(t *testing.T) {
|
|||||||
// password read can't be cleanly mocked.
|
// password read can't be cleanly mocked.
|
||||||
stdin = strings.NewReader("y\n")
|
stdin = strings.NewReader("y\n")
|
||||||
defer func() { stdin = os.Stdin }()
|
defer func() { stdin = os.Stdin }()
|
||||||
|
readPassword = func() ([]byte, error) { return nil, errors.New("mocked error") }
|
||||||
captureStdout(t, func() {
|
captureStdout(t, func() {
|
||||||
// term.ReadPassword on a non-terminal returns an error,
|
// term.ReadPassword on a non-terminal returns an error,
|
||||||
// landing in the "could not read token" warn branch.
|
// landing in the "could not read token" warn branch.
|
||||||
@@ -313,11 +314,11 @@ func TestInstallSystemPackagesBrewFailures(t *testing.T) {
|
|||||||
defer resetMocks()
|
defer resetMocks()
|
||||||
pkgMgr = "brew"
|
pkgMgr = "brew"
|
||||||
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
||||||
captureStdout(t, func() {
|
out := captureStdout(t, func() {
|
||||||
installSystemPackages([]string{"git"}, nil)
|
installSystemPackages([]string{"git"}, nil)
|
||||||
})
|
})
|
||||||
if !hasIssueContaining("System package failed to install") {
|
if !strings.Contains(out, "System package failed to install") {
|
||||||
t.Error("expected error logged for brew failure")
|
t.Error("expected warning logged for brew failure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,57 +611,7 @@ func TestSetupVivaldiRepoExisting(t *testing.T) {
|
|||||||
|
|
||||||
// ── special installer branches ──────────────────────────────────────────
|
// ── 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) {
|
func TestInstallPulumiNoVersion(t *testing.T) {
|
||||||
defer resetMocks()
|
defer resetMocks()
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ func TestEnsureHomebrewInstallerFails(t *testing.T) {
|
|||||||
defer resetMocks()
|
defer resetMocks()
|
||||||
isMacOS = true
|
isMacOS = true
|
||||||
hasCmd = func(_ string) bool { return false }
|
hasCmd = func(_ string) bool { return false }
|
||||||
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} }
|
||||||
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
||||||
called := false
|
called := false
|
||||||
osExit = func(_ int) { called = true }
|
osExit = func(_ int) { called = true }
|
||||||
@@ -245,6 +246,7 @@ func TestEnsureHomebrewBrewNotAtExpectedPath(t *testing.T) {
|
|||||||
isMacOS = true
|
isMacOS = true
|
||||||
archName = "x86_64"
|
archName = "x86_64"
|
||||||
hasCmd = func(_ string) bool { return false }
|
hasCmd = func(_ string) bool { return false }
|
||||||
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} }
|
||||||
runShell = 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 }
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
||||||
called := false
|
called := false
|
||||||
@@ -614,6 +616,8 @@ func TestRunMainHasErrorsExitsOne(t *testing.T) {
|
|||||||
hasCmd = func(_ string) bool { return true }
|
hasCmd = func(_ string) bool { return true }
|
||||||
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} }
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} }
|
||||||
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} } // install fails
|
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")
|
stdin = strings.NewReader("y\n")
|
||||||
defer func() { stdin = os.Stdin }()
|
defer func() { stdin = os.Stdin }()
|
||||||
exitCode := -1
|
exitCode := -1
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ var defaultInstallPaths = map[string]string{
|
|||||||
"oh-my-zsh": "~/.oh-my-zsh",
|
"oh-my-zsh": "~/.oh-my-zsh",
|
||||||
"agy": "~/.local/bin/agy",
|
"agy": "~/.local/bin/agy",
|
||||||
"gh-repo-bootstrap": "~/.local/share/gh/extensions/gh-repo-bootstrap",
|
"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 {
|
func expandHome(p string) string {
|
||||||
@@ -224,10 +230,7 @@ func installFirecracker(archive, tmp string) {
|
|||||||
if strings.HasSuffix(name, ".tgz") || strings.HasSuffix(name, ".tar.gz") {
|
if strings.HasSuffix(name, ".tgz") || strings.HasSuffix(name, ".tar.gz") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(name, "firecracker") {
|
if !strings.HasPrefix(name, "firecracker-v") || strings.Contains(name, "debug") {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(name, ".debug") || strings.Contains(name, "debug") {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if binary == "" {
|
if binary == "" {
|
||||||
@@ -321,6 +324,48 @@ func installNeovim(_ *CustomPackage, tmp string) {
|
|||||||
taskPrintf(" Neovim installed to %s, symlinked at %s\n", installDir, symlink)
|
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 ────────────────────────────────────────────
|
// ── latest-version resolvers ────────────────────────────────────────────
|
||||||
|
|
||||||
func resolveLatestGo(_ *CustomPackage) (string, string, bool) {
|
func resolveLatestGo(_ *CustomPackage) (string, string, bool) {
|
||||||
@@ -434,10 +479,23 @@ func cmpSemver(a, b string) int {
|
|||||||
return len(pa) - len(pb)
|
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){
|
var latestResolvers = map[string]func(*CustomPackage) (string, string, bool){
|
||||||
"go": resolveLatestGo,
|
"go": resolveLatestGo,
|
||||||
"firecracker": resolveLatestFirecracker,
|
"firecracker": resolveLatestFirecracker,
|
||||||
"zig": resolveLatestZig,
|
"zig": resolveLatestZig,
|
||||||
|
"yq": resolveLatestYq,
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveLatest best-effort upgrades pkg.Version/SHA256 to the latest release.
|
// resolveLatest best-effort upgrades pkg.Version/SHA256 to the latest release.
|
||||||
@@ -598,6 +656,18 @@ func runOneCustomInstall(pkg *CustomPackage) {
|
|||||||
installFirecracker(archive, tmp)
|
installFirecracker(archive, tmp)
|
||||||
case "zig":
|
case "zig":
|
||||||
installZig(pkg, archive)
|
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:
|
default:
|
||||||
warn(fmt.Sprintf("No install handler for '%s' — skipping", pkg.Name))
|
warn(fmt.Sprintf("No install handler for '%s' — skipping", pkg.Name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ var (
|
|||||||
osZig = map[string]string{"linux": "linux", "macos": "macos"}
|
osZig = map[string]string{"linux": "linux", "macos": "macos"}
|
||||||
osNvim = 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{
|
archTokens = map[string][]string{
|
||||||
"x86_64": {"x86_64", "amd64", "x64"},
|
"x86_64": {"x86_64", "amd64", "x64"},
|
||||||
"aarch64": {"aarch64", "arm64"},
|
"aarch64": {"aarch64", "arm64"},
|
||||||
@@ -81,6 +87,11 @@ func formatURL(template, version string) string {
|
|||||||
"{os_go}", osGo[osName],
|
"{os_go}", osGo[osName],
|
||||||
"{os_zig}", osZig[osName],
|
"{os_zig}", osZig[osName],
|
||||||
"{os_nvim}", osNvim[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)
|
return r.Replace(template)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-2
@@ -1,7 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resetMocks() {
|
func resetMocks() {
|
||||||
@@ -20,9 +22,12 @@ func resetMocks() {
|
|||||||
osMkdirAll = os.MkdirAll
|
osMkdirAll = os.MkdirAll
|
||||||
osRemove = os.Remove
|
osRemove = os.Remove
|
||||||
osRemoveAll = os.RemoveAll
|
osRemoveAll = os.RemoveAll
|
||||||
osRename = os.Rename
|
|
||||||
osExit = os.Exit
|
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
|
// Reset global state variables to safe defaults
|
||||||
isMacOS = false
|
isMacOS = false
|
||||||
|
|||||||
@@ -380,7 +380,14 @@ func waitForVMSSH(privKey string, timeout time.Duration) bool {
|
|||||||
fmt.Println(" VM SSH ready.")
|
fmt.Println(" VM SSH ready.")
|
||||||
return true
|
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
|
return false
|
||||||
}
|
}
|
||||||
@@ -393,7 +400,14 @@ func waitForFirecrackerInVM(privKey string, timeout time.Duration) bool {
|
|||||||
fmt.Println(" firecracker is installed inside the VM.")
|
fmt.Println(" firecracker is installed inside the VM.")
|
||||||
return true
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ func runMain(args []string) {
|
|||||||
if isMacOS && !*noVM {
|
if isMacOS && !*noVM {
|
||||||
setupFirecrackerVM()
|
setupFirecrackerVM()
|
||||||
}
|
}
|
||||||
|
ensureLibreOfficeAutoSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
if pyenvWG != nil {
|
if pyenvWG != nil {
|
||||||
@@ -179,6 +180,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
|
// promptGitHubToken asks the user if they want to supply a GitHub token
|
||||||
// after they've authenticated sudo. With a token, our HTTP-bound worker
|
// after they've authenticated sudo. With a token, our HTTP-bound worker
|
||||||
// pool uncaps from the conservative 8-worker default up to runtime.NumCPU(),
|
// pool uncaps from the conservative 8-worker default up to runtime.NumCPU(),
|
||||||
@@ -196,7 +201,7 @@ func promptGitHubToken() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Print(" Paste token (input hidden): ")
|
fmt.Print(" Paste token (input hidden): ")
|
||||||
tokenBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
|
tokenBytes, err := readPassword()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warn(fmt.Sprintf("could not read token: %v — continuing without uncap", err))
|
warn(fmt.Sprintf("could not read token: %v — continuing without uncap", err))
|
||||||
|
|||||||
+42
-1
@@ -10,10 +10,11 @@ package main
|
|||||||
// URL templates use the substitutions described in formatURL.
|
// URL templates use the substitutions described in formatURL.
|
||||||
|
|
||||||
var SystemPackages = []string{
|
var SystemPackages = []string{
|
||||||
|
"age",
|
||||||
"ansible",
|
"ansible",
|
||||||
"ansible-core",
|
"ansible-core",
|
||||||
"aria2",
|
"aria2",
|
||||||
"bashtop",
|
"btm",
|
||||||
"build-essential",
|
"build-essential",
|
||||||
"buildah",
|
"buildah",
|
||||||
"containerd.io",
|
"containerd.io",
|
||||||
@@ -29,10 +30,14 @@ var SystemPackages = []string{
|
|||||||
"git",
|
"git",
|
||||||
"github-desktop",
|
"github-desktop",
|
||||||
"google-chrome-stable",
|
"google-chrome-stable",
|
||||||
|
"helm",
|
||||||
|
"jq",
|
||||||
|
"kubectl",
|
||||||
"lazygit",
|
"lazygit",
|
||||||
"lua",
|
"lua",
|
||||||
"minisign",
|
"minisign",
|
||||||
"minikube",
|
"minikube",
|
||||||
|
"nmap",
|
||||||
"obs-studio",
|
"obs-studio",
|
||||||
"obsidian",
|
"obsidian",
|
||||||
"pipx",
|
"pipx",
|
||||||
@@ -42,8 +47,11 @@ var SystemPackages = []string{
|
|||||||
"qemu",
|
"qemu",
|
||||||
"restic",
|
"restic",
|
||||||
"rg",
|
"rg",
|
||||||
|
"semgrep",
|
||||||
"shutter",
|
"shutter",
|
||||||
|
"sops",
|
||||||
"temurin-25-jdk",
|
"temurin-25-jdk",
|
||||||
|
"tmux",
|
||||||
"vagrant",
|
"vagrant",
|
||||||
"virt-manager",
|
"virt-manager",
|
||||||
"vivaldi-stable",
|
"vivaldi-stable",
|
||||||
@@ -52,6 +60,8 @@ var SystemPackages = []string{
|
|||||||
"yt-dlp",
|
"yt-dlp",
|
||||||
"zoom",
|
"zoom",
|
||||||
"zsh",
|
"zsh",
|
||||||
|
"fzf",
|
||||||
|
"fd",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"bzip2-devel",
|
"bzip2-devel",
|
||||||
"curl",
|
"curl",
|
||||||
@@ -142,5 +152,36 @@ func customPackages() []CustomPackage {
|
|||||||
{Name: "copilot"},
|
{Name: "copilot"},
|
||||||
{Name: "playwright"},
|
{Name: "playwright"},
|
||||||
{Name: "gh-repo-bootstrap"},
|
{Name: "gh-repo-bootstrap"},
|
||||||
|
{
|
||||||
|
Name: "rustup",
|
||||||
|
Version: "latest",
|
||||||
|
URLTemplate: "https://static.rust-lang.org/rustup/dist/{arch_rustup}-{os_rustup}/rustup-init",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "yq",
|
||||||
|
Version: "4.44.1",
|
||||||
|
URLTemplate: "https://github.com/mikefarah/yq/releases/download/v{version}/yq_{os_go}_{arch_go}",
|
||||||
|
FetchLatest: "yq",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "dagger",
|
||||||
|
Version: "0.11.4",
|
||||||
|
URLTemplate: "https://github.com/dagger/dagger/releases/download/v{version}/dagger_v{version}_{os_go}_{arch_go}.tar.gz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "trivy",
|
||||||
|
Version: "0.70.0",
|
||||||
|
URLTemplate: "https://github.com/aquasecurity/trivy/releases/download/v{version}/trivy_{version}_{os_trivy}-{arch_trivy}.tar.gz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "cosign",
|
||||||
|
Version: "2.2.4",
|
||||||
|
URLTemplate: "https://github.com/sigstore/cosign/releases/download/v{version}/cosign-{os_go}-{arch_go}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "gitleaks",
|
||||||
|
Version: "8.18.2",
|
||||||
|
URLTemplate: "https://github.com/gitleaks/gitleaks/releases/download/v{version}/gitleaks_{version}_{os_go}_{arch_gitleaks}.tar.gz",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ var packageOverrides = map[string]map[string]overrideEntry{
|
|||||||
"webcamoid": skipOverride(),
|
"webcamoid": skipOverride(),
|
||||||
},
|
},
|
||||||
"apt-get": {
|
"apt-get": {
|
||||||
|
"fd": replace("fd-find"),
|
||||||
"ffmpeg-free": replace("ffmpeg"),
|
"ffmpeg-free": replace("ffmpeg"),
|
||||||
"lua": replace("lua5.4"),
|
"lua": replace("lua5.4"),
|
||||||
"qemu": replace("qemu-system"),
|
"qemu": replace("qemu-system"),
|
||||||
@@ -165,7 +166,6 @@ var packageOverrides = map[string]map[string]overrideEntry{
|
|||||||
"rg": replace("ripgrep"),
|
"rg": replace("ripgrep"),
|
||||||
"temurin-25-jdk": replace("temurin"),
|
"temurin-25-jdk": replace("temurin"),
|
||||||
"vivaldi-stable": replace("vivaldi"),
|
"vivaldi-stable": replace("vivaldi"),
|
||||||
"bashtop": replace("btop"),
|
|
||||||
"buildah": skipOverride(),
|
"buildah": skipOverride(),
|
||||||
"shutter": skipOverride(),
|
"shutter": skipOverride(),
|
||||||
"virt-manager": skipOverride(),
|
"virt-manager": skipOverride(),
|
||||||
|
|||||||
+4
-4
@@ -85,11 +85,11 @@ func TestResolveSystemPkgs(t *testing.T) {
|
|||||||
t.Errorf("expected skipped to be [docker-compose], got %v", skipped)
|
t.Errorf("expected skipped to be [docker-compose], got %v", skipped)
|
||||||
}
|
}
|
||||||
|
|
||||||
// brew: bashtop -> btop (replace), buildah -> skipped
|
// brew: buildah -> skipped
|
||||||
pkgMgr = "brew"
|
pkgMgr = "brew"
|
||||||
resolved, skipped = resolveSystemPkgs([]string{"bashtop", "buildah", "rg"})
|
resolved, skipped = resolveSystemPkgs([]string{"buildah", "rg"})
|
||||||
if len(resolved) != 2 || resolved[0] != "btop" || resolved[1] != "ripgrep" {
|
if len(resolved) != 1 || resolved[0] != "ripgrep" {
|
||||||
t.Errorf("expected [btop ripgrep], got %v", resolved)
|
t.Errorf("expected [ripgrep], got %v", resolved)
|
||||||
}
|
}
|
||||||
// pacman: docker-ce-rootless-extras and vagrant are AUR-only -> skipped;
|
// pacman: docker-ce-rootless-extras and vagrant are AUR-only -> skipped;
|
||||||
// pipx is replaced with python-pipx.
|
// pipx is replaced with python-pipx.
|
||||||
|
|||||||
@@ -544,3 +544,45 @@ func installGHExtension(repo string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── LibreOffice AutoSave Extension ──────────────────────────────────────
|
||||||
|
|
||||||
|
func ensureLibreOfficeAutoSave() {
|
||||||
|
var unopkgPath string
|
||||||
|
if isMacOS {
|
||||||
|
unopkgPath = "/Applications/LibreOffice.app/Contents/MacOS/unopkg"
|
||||||
|
if _, err := osStat(unopkgPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !hasCmd("unopkg") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unopkgPath = "unopkg"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\n[LibreOffice] LibreOffice detected. Installing AutoSave extension ...")
|
||||||
|
|
||||||
|
tmp, err := os.MkdirTemp("", "libreoffice-autosave-")
|
||||||
|
if err != nil {
|
||||||
|
errLog(fmt.Sprintf("LibreOffice AutoSave temp dir failed: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer osRemoveAll(tmp)
|
||||||
|
|
||||||
|
url := "https://github.com/JMR-dev/LibreOfficeAutoSave/releases/latest/download/AutoSave.oxt"
|
||||||
|
dest := filepath.Join(tmp, "AutoSave.oxt")
|
||||||
|
|
||||||
|
if !download(url, dest) {
|
||||||
|
errLog("Failed to download LibreOffice AutoSave extension")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !runCmd([]string{unopkgPath, "add", "-f", dest}, CmdOpts{}).OK() {
|
||||||
|
errLog("Failed to install LibreOffice AutoSave extension")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(" LibreOffice AutoSave extension installed successfully.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -905,4 +905,98 @@ func TestEnsurePythonLatestBackgroundInstall(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnsureLibreOfficeAutoSave(t *testing.T) {
|
||||||
|
defer resetMocks()
|
||||||
|
|
||||||
|
// 1. isMacOS = false, LibreOffice not installed
|
||||||
|
isMacOS = false
|
||||||
|
hasCmd = func(name string) bool {
|
||||||
|
if name == "unopkg" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var calledDownload bool
|
||||||
|
download = func(url, dest string) bool {
|
||||||
|
calledDownload = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
ensureLibreOfficeAutoSave()
|
||||||
|
if calledDownload {
|
||||||
|
t.Error("expected download not to be called when unopkg is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. isMacOS = true, LibreOffice not installed
|
||||||
|
isMacOS = true
|
||||||
|
osStat = func(name string) (os.FileInfo, error) {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
calledDownload = false
|
||||||
|
ensureLibreOfficeAutoSave()
|
||||||
|
if calledDownload {
|
||||||
|
t.Error("expected download not to be called when unopkg is missing on macOS")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. isMacOS = false, LibreOffice installed, download fails
|
||||||
|
isMacOS = false
|
||||||
|
hasCmd = func(name string) bool {
|
||||||
|
return name == "unopkg"
|
||||||
|
}
|
||||||
|
download = func(url, dest string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var runCmdCalled bool
|
||||||
|
runCmd = func(argv []string, opts CmdOpts) CmdResult {
|
||||||
|
runCmdCalled = true
|
||||||
|
return CmdResult{ExitCode: 0}
|
||||||
|
}
|
||||||
|
ensureLibreOfficeAutoSave()
|
||||||
|
if runCmdCalled {
|
||||||
|
t.Error("expected runCmd not to be called when download fails")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. isMacOS = false, LibreOffice installed, download succeeds, runCmd fails
|
||||||
|
download = func(url, dest string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var unopkgArgv []string
|
||||||
|
runCmd = func(argv []string, opts CmdOpts) CmdResult {
|
||||||
|
unopkgArgv = argv
|
||||||
|
return CmdResult{ExitCode: 1}
|
||||||
|
}
|
||||||
|
ensureLibreOfficeAutoSave()
|
||||||
|
if len(unopkgArgv) == 0 || unopkgArgv[0] != "unopkg" {
|
||||||
|
t.Errorf("expected runCmd with unopkg, got: %v", unopkgArgv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. isMacOS = false, LibreOffice installed, download succeeds, runCmd succeeds
|
||||||
|
runCmd = func(argv []string, opts CmdOpts) CmdResult {
|
||||||
|
unopkgArgv = argv
|
||||||
|
return CmdResult{ExitCode: 0}
|
||||||
|
}
|
||||||
|
ensureLibreOfficeAutoSave()
|
||||||
|
if len(unopkgArgv) == 0 || unopkgArgv[0] != "unopkg" || unopkgArgv[1] != "add" || unopkgArgv[2] != "-f" {
|
||||||
|
t.Errorf("expected runCmd with unopkg add -f, got: %v", unopkgArgv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. isMacOS = true, LibreOffice installed, download succeeds, runCmd succeeds
|
||||||
|
isMacOS = true
|
||||||
|
osStat = func(name string) (os.FileInfo, error) {
|
||||||
|
if name == "/Applications/LibreOffice.app/Contents/MacOS/unopkg" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
runCmd = func(argv []string, opts CmdOpts) CmdResult {
|
||||||
|
unopkgArgv = argv
|
||||||
|
return CmdResult{ExitCode: 0}
|
||||||
|
}
|
||||||
|
ensureLibreOfficeAutoSave()
|
||||||
|
expectedMacPath := "/Applications/LibreOffice.app/Contents/MacOS/unopkg"
|
||||||
|
if len(unopkgArgv) == 0 || unopkgArgv[0] != expectedMacPath {
|
||||||
|
t.Errorf("expected runCmd with %s, got: %v", expectedMacPath, unopkgArgv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ func specialPkgs() map[string]bool {
|
|||||||
}
|
}
|
||||||
return map[string]bool{
|
return map[string]bool{
|
||||||
"github-desktop": true, "zoom": true, "obsidian": true,
|
"github-desktop": true, "zoom": true, "obsidian": true,
|
||||||
"minikube": true, "bashtop": true, "pipx": true,
|
"minikube": true, "pipx": true,
|
||||||
"poetry": true, "pulumi": true,
|
"poetry": true, "pulumi": true, "semgrep": true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,21 +37,20 @@ var guiSystemPkgs = map[string]bool{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isSpecialPkgInstalled(pkg string) bool {
|
func isSpecialPkgInstalled(pkg string) bool {
|
||||||
home, _ := os.UserHomeDir()
|
|
||||||
exists := func(p string) bool { _, err := osStat(p); return err == nil }
|
exists := func(p string) bool { _, err := osStat(p); return err == nil }
|
||||||
switch pkg {
|
switch pkg {
|
||||||
case "obsidian":
|
case "obsidian":
|
||||||
return exists("/usr/local/bin/obsidian")
|
return exists("/usr/local/bin/obsidian")
|
||||||
case "minikube":
|
case "minikube":
|
||||||
return exists("/usr/local/bin/minikube") || hasCmd("minikube")
|
return exists("/usr/local/bin/minikube") || hasCmd("minikube")
|
||||||
case "bashtop":
|
|
||||||
return exists("/usr/local/bin/bashtop") || exists(filepath.Join(home, "bashtop"))
|
|
||||||
case "pulumi":
|
case "pulumi":
|
||||||
return exists("/opt/pulumi/pulumi") || hasCmd("pulumi")
|
return exists("/opt/pulumi/pulumi") || hasCmd("pulumi")
|
||||||
case "pipx":
|
case "pipx":
|
||||||
return hasCmd("pipx")
|
return hasCmd("pipx")
|
||||||
case "poetry":
|
case "poetry":
|
||||||
return hasCmd("poetry")
|
return hasCmd("poetry")
|
||||||
|
case "semgrep":
|
||||||
|
return hasCmd("semgrep")
|
||||||
}
|
}
|
||||||
return isSystemPkgInstalled(pkg)
|
return isSystemPkgInstalled(pkg)
|
||||||
}
|
}
|
||||||
@@ -259,30 +258,6 @@ func installMinikube(tmp string) {
|
|||||||
fmt.Printf(" minikube installed to %s\n", installPath)
|
fmt.Printf(" minikube installed to %s\n", installPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func installBashtop(_ string) {
|
|
||||||
home, _ := os.UserHomeDir()
|
|
||||||
cloneDir := filepath.Join(home, "bashtop")
|
|
||||||
if _, err := osStat(cloneDir); err == nil {
|
|
||||||
fmt.Printf(" Updating existing clone at %s ...\n", cloneDir)
|
|
||||||
if !runCmd([]string{"git", "-C", cloneDir, "pull"}, CmdOpts{}).OK() {
|
|
||||||
errLog("bashtop git pull failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" Cloning bashtop to %s ...\n", cloneDir)
|
|
||||||
if !runCmd([]string{"git", "clone", "https://github.com/aristocratos/bashtop.git", cloneDir}, CmdOpts{}).OK() {
|
|
||||||
errLog("bashtop git clone failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !runCmd([]string{"make", "install"}, CmdOpts{AsSudo: true, Cwd: cloneDir}).OK() {
|
|
||||||
errLog("bashtop 'make install' failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
appendProfileLine("bashtop", "export PATH=$PATH:"+cloneDir)
|
|
||||||
fmt.Printf(" bashtop installed. Clone at %s, binary at /usr/local/bin/bashtop\n", cloneDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func installPulumi(tmp string) {
|
func installPulumi(tmp string) {
|
||||||
version := fetchText("https://www.pulumi.com/latest-version")
|
version := fetchText("https://www.pulumi.com/latest-version")
|
||||||
if version == "" {
|
if version == "" {
|
||||||
@@ -356,6 +331,14 @@ func installPoetry(_ string) {
|
|||||||
runCmd([]string{"pipx", "install", "poetry"}, CmdOpts{})
|
runCmd([]string{"pipx", "install", "poetry"}, CmdOpts{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func installSemgrep(_ string) {
|
||||||
|
if !hasCmd("pipx") {
|
||||||
|
errLog("pipx is not installed — cannot install semgrep")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runCmd([]string{"pipx", "install", "semgrep"}, CmdOpts{})
|
||||||
|
}
|
||||||
|
|
||||||
func installSpecialPkg(pkg, tmp string) {
|
func installSpecialPkg(pkg, tmp string) {
|
||||||
switch pkg {
|
switch pkg {
|
||||||
case "github-desktop":
|
case "github-desktop":
|
||||||
@@ -366,14 +349,14 @@ func installSpecialPkg(pkg, tmp string) {
|
|||||||
installObsidian(tmp)
|
installObsidian(tmp)
|
||||||
case "minikube":
|
case "minikube":
|
||||||
installMinikube(tmp)
|
installMinikube(tmp)
|
||||||
case "bashtop":
|
|
||||||
installBashtop(tmp)
|
|
||||||
case "pulumi":
|
case "pulumi":
|
||||||
installPulumi(tmp)
|
installPulumi(tmp)
|
||||||
case "pipx":
|
case "pipx":
|
||||||
installPipx(tmp)
|
installPipx(tmp)
|
||||||
case "poetry":
|
case "poetry":
|
||||||
installPoetry(tmp)
|
installPoetry(tmp)
|
||||||
|
case "semgrep":
|
||||||
|
installSemgrep(tmp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +461,7 @@ func installSystemPackages(regular, special []string) {
|
|||||||
if pkgMgr == "brew" {
|
if pkgMgr == "brew" {
|
||||||
failed := pkgInstallMany(regular)
|
failed := pkgInstallMany(regular)
|
||||||
for _, p := range failed {
|
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.
|
// No special packages on macOS — brew covers all of them.
|
||||||
return
|
return
|
||||||
@@ -498,7 +481,7 @@ func installSystemPackages(regular, special []string) {
|
|||||||
|
|
||||||
failed := pkgInstallMany(regular)
|
failed := pkgInstallMany(regular)
|
||||||
for _, p := range failed {
|
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 {
|
if len(special) > 0 {
|
||||||
@@ -513,6 +496,10 @@ func installSystemPackages(regular, special []string) {
|
|||||||
installSpecialPkg(pkg, tmp)
|
installSpecialPkg(pkg, tmp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pkgMgr == "apt-get" && hasCmd("fdfind") {
|
||||||
|
runCmd([]string{"ln", "-sf", "/usr/bin/fdfind", "/usr/local/bin/fd"}, CmdOpts{AsSudo: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendProfileLine adds a PATH/env line to a system-wide login-shell profile,
|
// appendProfileLine adds a PATH/env line to a system-wide login-shell profile,
|
||||||
|
|||||||
+2
-36
@@ -247,32 +247,7 @@ func TestInstallMinikube(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstallBashtop(t *testing.T) {
|
|
||||||
defer resetMocks()
|
|
||||||
|
|
||||||
osStat = func(name string) (os.FileInfo, error) {
|
|
||||||
return nil, os.ErrNotExist // Not cloned yet
|
|
||||||
}
|
|
||||||
|
|
||||||
var runCmdCalls [][]string
|
|
||||||
runCmd = func(argv []string, opts CmdOpts) CmdResult {
|
|
||||||
runCmdCalls = append(runCmdCalls, argv)
|
|
||||||
return CmdResult{ExitCode: 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
installBashtop("/tmp")
|
|
||||||
|
|
||||||
// Git clone and make install should be run
|
|
||||||
if len(runCmdCalls) < 2 {
|
|
||||||
t.Fatalf("expected git clone and make install, got calls: %v", runCmdCalls)
|
|
||||||
}
|
|
||||||
if runCmdCalls[0][1] != "clone" {
|
|
||||||
t.Errorf("expected git clone, got %v", runCmdCalls[0])
|
|
||||||
}
|
|
||||||
if runCmdCalls[1][0] != "make" || runCmdCalls[1][1] != "install" {
|
|
||||||
t.Errorf("expected make install, got %v", runCmdCalls[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInstallPulumi(t *testing.T) {
|
func TestInstallPulumi(t *testing.T) {
|
||||||
defer resetMocks()
|
defer resetMocks()
|
||||||
@@ -402,9 +377,7 @@ func TestSystemGoEdgeCases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
hasCmd = func(name string) bool { return true }
|
hasCmd = func(name string) bool { return true }
|
||||||
|
|
||||||
if !isSpecialPkgInstalled("bashtop") {
|
|
||||||
t.Error("expected bashtop to be installed")
|
|
||||||
}
|
|
||||||
if !isSpecialPkgInstalled("pulumi") {
|
if !isSpecialPkgInstalled("pulumi") {
|
||||||
t.Error("expected pulumi to be installed")
|
t.Error("expected pulumi to be installed")
|
||||||
}
|
}
|
||||||
@@ -470,14 +443,7 @@ func TestSystemGoEdgeCases(t *testing.T) {
|
|||||||
fetchText = func(url string) string { return "mismatch-checksum minikube" }
|
fetchText = func(url string) string { return "mismatch-checksum minikube" }
|
||||||
installSpecialPkg("minikube", "/tmp")
|
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()
|
resetMocks()
|
||||||
fetchText = func(url string) string { return "some-sha pulumi-v3.90.0-linux-x64.tar.gz" }
|
fetchText = func(url string) string { return "some-sha pulumi-v3.90.0-linux-x64.tar.gz" }
|
||||||
|
|||||||
Reference in New Issue
Block a user