Skip to content

Commit b72bdc5

Browse files
committed
feat: rewrite OpenBoot in Go for better curl|bash compatibility
Go rewrite with: - cobra CLI (--preset, --silent, --dry-run, --update, --rollback) - huh TUI forms (input, select, confirm) - Cross-platform binary (darwin/linux, amd64/arm64) Structure: - cmd/openboot/main.go - entry point - internal/cli/ - cobra commands - internal/ui/ - TUI components - internal/brew/ - homebrew operations - internal/config/ - presets and config - internal/system/ - system detection - internal/installer/ - installation orchestration Shell wrapper: - scripts/install.sh - downloads and runs correct binary GitHub Actions: - .github/workflows/release.yml - builds binaries on tag push Benefits over shell scripts: - No stdin/tty issues with curl|bash - Single binary, zero runtime dependencies - Type safety, better error handling - Faster execution
1 parent 5b35b07 commit b72bdc5

13 files changed

Lines changed: 963 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: 'Version to release (e.g., v0.2.0)'
11+
required: true
12+
13+
permissions:
14+
contents: write
15+
16+
jobs:
17+
build:
18+
runs-on: ubuntu-latest
19+
strategy:
20+
matrix:
21+
include:
22+
- os: darwin
23+
arch: amd64
24+
- os: darwin
25+
arch: arm64
26+
- os: linux
27+
arch: amd64
28+
- os: linux
29+
arch: arm64
30+
31+
steps:
32+
- name: Checkout
33+
uses: actions/checkout@v4
34+
35+
- name: Setup Go
36+
uses: actions/setup-go@v5
37+
with:
38+
go-version: '1.22'
39+
40+
- name: Build
41+
env:
42+
GOOS: ${{ matrix.os }}
43+
GOARCH: ${{ matrix.arch }}
44+
CGO_ENABLED: '0'
45+
run: |
46+
go build -ldflags="-s -w" -o openboot-${{ matrix.os }}-${{ matrix.arch }} ./cmd/openboot
47+
48+
- name: Upload artifact
49+
uses: actions/upload-artifact@v4
50+
with:
51+
name: openboot-${{ matrix.os }}-${{ matrix.arch }}
52+
path: openboot-${{ matrix.os }}-${{ matrix.arch }}
53+
54+
release:
55+
needs: build
56+
runs-on: ubuntu-latest
57+
steps:
58+
- name: Download all artifacts
59+
uses: actions/download-artifact@v4
60+
with:
61+
path: artifacts
62+
63+
- name: Get version
64+
id: version
65+
run: |
66+
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
67+
echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
68+
else
69+
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
70+
fi
71+
72+
- name: Create Release
73+
uses: softprops/action-gh-release@v1
74+
with:
75+
tag_name: ${{ steps.version.outputs.version }}
76+
name: OpenBoot ${{ steps.version.outputs.version }}
77+
draft: false
78+
prerelease: false
79+
files: |
80+
artifacts/openboot-darwin-amd64/openboot-darwin-amd64
81+
artifacts/openboot-darwin-arm64/openboot-darwin-arm64
82+
artifacts/openboot-linux-amd64/openboot-linux-amd64
83+
artifacts/openboot-linux-arm64/openboot-linux-arm64
84+
body: |
85+
## Installation
86+
87+
```bash
88+
curl -fsSL https://openboot.dev/install | bash
89+
```
90+
91+
## Binaries
92+
93+
| Platform | Architecture | Download |
94+
|----------|--------------|----------|
95+
| macOS | Apple Silicon (M1/M2/M3) | `openboot-darwin-arm64` |
96+
| macOS | Intel | `openboot-darwin-amd64` |
97+
| Linux | x86_64 | `openboot-linux-amd64` |
98+
| Linux | ARM64 | `openboot-linux-arm64` |

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ __pycache__/
3131
# Work tracking (internal)
3232
.sisyphus/
3333

34+
# Go binaries (root level only)
35+
/openboot
36+
/openboot-*
37+
*.exe
38+
3439
# Temporary files
3540
*.tmp
3641
*.temp

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.PHONY: build build-all clean test run
2+
3+
BINARY_NAME=openboot
4+
VERSION=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
5+
6+
build:
7+
go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY_NAME) ./cmd/openboot
8+
9+
build-all:
10+
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o $(BINARY_NAME)-darwin-arm64 ./cmd/openboot
11+
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o $(BINARY_NAME)-darwin-amd64 ./cmd/openboot
12+
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o $(BINARY_NAME)-linux-arm64 ./cmd/openboot
13+
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(BINARY_NAME)-linux-amd64 ./cmd/openboot
14+
15+
clean:
16+
rm -f $(BINARY_NAME) $(BINARY_NAME)-*
17+
18+
test:
19+
go test ./...
20+
21+
run:
22+
go run ./cmd/openboot
23+
24+
run-dry:
25+
go run ./cmd/openboot --dry-run
26+
27+
install:
28+
go install ./cmd/openboot

cmd/openboot/main.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/fullstackjam/openboot/internal/cli"
7+
)
8+
9+
func main() {
10+
if err := cli.Execute(); err != nil {
11+
os.Exit(1)
12+
}
13+
}

go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/fullstackjam/openboot
2+
3+
go 1.22
4+
5+
require (
6+
github.com/charmbracelet/huh v0.6.0
7+
github.com/charmbracelet/lipgloss v1.0.0
8+
github.com/spf13/cobra v1.8.1
9+
)

internal/brew/brew.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package brew
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
8+
"github.com/fullstackjam/openboot/internal/ui"
9+
)
10+
11+
func Install(packages []string, dryRun bool) error {
12+
if len(packages) == 0 {
13+
return nil
14+
}
15+
16+
if dryRun {
17+
ui.Info("Would install CLI packages:")
18+
for _, p := range packages {
19+
fmt.Printf(" brew install %s\n", p)
20+
}
21+
return nil
22+
}
23+
24+
ui.Info(fmt.Sprintf("Installing %d CLI packages...", len(packages)))
25+
26+
args := append([]string{"install"}, packages...)
27+
cmd := exec.Command("brew", args...)
28+
cmd.Stdout = os.Stdout
29+
cmd.Stderr = os.Stderr
30+
return cmd.Run()
31+
}
32+
33+
func InstallCask(packages []string, dryRun bool) error {
34+
if len(packages) == 0 {
35+
return nil
36+
}
37+
38+
if dryRun {
39+
ui.Info("Would install GUI applications:")
40+
for _, p := range packages {
41+
fmt.Printf(" brew install --cask %s\n", p)
42+
}
43+
return nil
44+
}
45+
46+
ui.Info(fmt.Sprintf("Installing %d GUI applications...", len(packages)))
47+
48+
args := append([]string{"install", "--cask"}, packages...)
49+
cmd := exec.Command("brew", args...)
50+
cmd.Stdout = os.Stdout
51+
cmd.Stderr = os.Stderr
52+
return cmd.Run()
53+
}
54+
55+
func Update(dryRun bool) error {
56+
if dryRun {
57+
ui.Info("Would run: brew update && brew upgrade")
58+
return nil
59+
}
60+
61+
ui.Info("Updating Homebrew...")
62+
if err := exec.Command("brew", "update").Run(); err != nil {
63+
return err
64+
}
65+
66+
ui.Info("Upgrading packages...")
67+
cmd := exec.Command("brew", "upgrade")
68+
cmd.Stdout = os.Stdout
69+
cmd.Stderr = os.Stderr
70+
return cmd.Run()
71+
}
72+
73+
func Cleanup() error {
74+
ui.Info("Cleaning up old versions...")
75+
return exec.Command("brew", "cleanup").Run()
76+
}

internal/cli/root.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/fullstackjam/openboot/internal/config"
8+
"github.com/fullstackjam/openboot/internal/installer"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var (
13+
version = "0.2.0"
14+
cfg = &config.Config{}
15+
)
16+
17+
var rootCmd = &cobra.Command{
18+
Use: "openboot",
19+
Short: "One-line macOS development environment setup",
20+
Long: `OpenBoot bootstraps your Mac development environment in minutes.
21+
Install Homebrew, CLI tools, GUI apps, dotfiles, and Oh-My-Zsh with a single command.`,
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
return installer.Run(cfg)
24+
},
25+
}
26+
27+
func init() {
28+
rootCmd.Flags().StringVarP(&cfg.Preset, "preset", "p", "", "Set preset (minimal, standard, full, devops, frontend, data, mobile, ai)")
29+
rootCmd.Flags().BoolVarP(&cfg.Silent, "silent", "s", false, "Non-interactive mode for CI/automation")
30+
rootCmd.Flags().BoolVar(&cfg.DryRun, "dry-run", false, "Preview what would be installed without installing")
31+
rootCmd.Flags().BoolVar(&cfg.Update, "update", false, "Update Homebrew and upgrade all packages")
32+
rootCmd.Flags().BoolVar(&cfg.Rollback, "rollback", false, "Restore backed up files")
33+
rootCmd.Flags().StringVar(&cfg.Shell, "shell", "", "Shell framework setup (install, skip)")
34+
rootCmd.Flags().StringVar(&cfg.Macos, "macos", "", "macOS preferences (configure, skip)")
35+
rootCmd.Flags().StringVar(&cfg.Dotfiles, "dotfiles", "", "Dotfiles mode (clone, link, skip)")
36+
rootCmd.Flags().BoolVar(&cfg.Resume, "resume", false, "Resume from last incomplete step")
37+
38+
rootCmd.AddCommand(versionCmd)
39+
}
40+
41+
var versionCmd = &cobra.Command{
42+
Use: "version",
43+
Short: "Print version information",
44+
Run: func(cmd *cobra.Command, args []string) {
45+
fmt.Printf("OpenBoot v%s\n", version)
46+
},
47+
}
48+
49+
func Execute() error {
50+
if cfg.Silent {
51+
if name := os.Getenv("OPENBOOT_GIT_NAME"); name != "" {
52+
cfg.GitName = name
53+
}
54+
if email := os.Getenv("OPENBOOT_GIT_EMAIL"); email != "" {
55+
cfg.GitEmail = email
56+
}
57+
if preset := os.Getenv("OPENBOOT_PRESET"); preset != "" && cfg.Preset == "" {
58+
cfg.Preset = preset
59+
}
60+
}
61+
62+
return rootCmd.Execute()
63+
}

0 commit comments

Comments
 (0)