Skip to content

Commit 5ba121c

Browse files
committed
snapshot
1 parent 59b7bd0 commit 5ba121c

File tree

6 files changed

+96
-33
lines changed

6 files changed

+96
-33
lines changed

.github/workflows/build-toolbox.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
run: |
5555
set -euo pipefail
5656
cd toolbox
57-
./toolbox-builder -playbook ../playbook/60-second-linux.yaml -out .
57+
./toolbox-builder --playbook ../playbook/60-second-linux.yaml -out .
5858
5959
- name: Upload artifact
6060
uses: actions/upload-artifact@v4

app/main.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,42 @@ import (
44
"log"
55

66
tea "github.com/charmbracelet/bubbletea"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var (
11+
toolboxRepo string
712
)
813

914
func main() {
10-
// Create a new toolbox instance
11-
toolbox := NewToolbox("https://gradient.engineer/toolbox_v0.tar.xz")
12-
defer toolbox.Cleanup()
13-
14-
// Create and run the Bubble Tea program which will handle toolbox download and diagnostics
15-
p := tea.NewProgram(NewModel(toolbox), tea.WithMouseCellMotion())
16-
if _, err := p.Run(); err != nil {
17-
log.Fatalf("Error running Bubble Tea program: %v", err)
15+
var rootCmd = &cobra.Command{
16+
Use: "gradient-engineer [flags] PLAYBOOK_NAME",
17+
Short: "Run diagnostic playbooks using gradient engineer toolbox",
18+
Long: `Gradient Engineer runs diagnostic playbooks by downloading and executing
19+
toolbox commands. The toolbox is automatically downloaded from the specified
20+
repository based on your platform (OS and architecture).`,
21+
Args: cobra.ExactArgs(1),
22+
Run: func(cmd *cobra.Command, args []string) {
23+
playbookName := args[0]
24+
25+
// Create a new toolbox instance
26+
tb := NewToolbox(toolboxRepo, playbookName)
27+
defer tb.Cleanup()
28+
29+
// Create and run the Bubble Tea program which will handle toolbox download and diagnostics
30+
p := tea.NewProgram(NewModel(tb), tea.WithMouseCellMotion())
31+
if _, err := p.Run(); err != nil {
32+
log.Fatalf("Error running Bubble Tea program: %v", err)
33+
}
34+
},
1835
}
19-
}
2036

37+
// Define flags
38+
rootCmd.Flags().StringVar(&toolboxRepo, "toolbox-repo", "https://gradient.engineer/toolbox/",
39+
"Toolbox repository URL or path (e.g., file:///home/user/mytoolboxes/)")
40+
41+
// Execute the command
42+
if err := rootCmd.Execute(); err != nil {
43+
log.Fatal(err)
44+
}
45+
}

app/toolbox.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os/exec"
1111
"path"
1212
"path/filepath"
13+
"runtime"
1314
"strings"
1415
"time"
1516

@@ -34,7 +35,9 @@ type Toolbox struct {
3435
}
3536

3637
// NewToolbox creates a new Toolbox instance
37-
func NewToolbox(url string) *Toolbox {
38+
func NewToolbox(toolboxRepo, playbookName string) *Toolbox {
39+
// Construct the toolbox URL using the specified format
40+
url := fmt.Sprintf("%s%s.%s.%s.tar.xz", toolboxRepo, playbookName, runtime.GOOS, runtime.GOARCH)
3841
return &Toolbox{
3942
URL: url,
4043
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ require (
2525
github.com/dlclark/regexp2 v1.11.5 // indirect
2626
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
2727
github.com/gorilla/css v1.0.1 // indirect
28+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2829
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
2930
github.com/mattn/go-isatty v0.0.20 // indirect
3031
github.com/mattn/go-localereader v0.0.1 // indirect
@@ -35,6 +36,8 @@ require (
3536
github.com/muesli/reflow v0.3.0 // indirect
3637
github.com/muesli/termenv v0.16.0 // indirect
3738
github.com/rivo/uniseg v0.4.7 // indirect
39+
github.com/spf13/cobra v1.9.1 // indirect
40+
github.com/spf13/pflag v1.0.6 // indirect
3841
github.com/tidwall/gjson v1.18.0 // indirect
3942
github.com/tidwall/match v1.1.1 // indirect
4043
github.com/tidwall/pretty v1.2.1 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ github.com/charmbracelet/x/exp/slice v0.0.0-20250829135019-44e44e21330d h1:H2oh4
3232
github.com/charmbracelet/x/exp/slice v0.0.0-20250829135019-44e44e21330d/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
3333
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
3434
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
35+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
3536
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3637
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3738
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
@@ -42,6 +43,8 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
4243
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
4344
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
4445
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
46+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
47+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
4548
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
4649
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
4750
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -69,6 +72,11 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
6972
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
7073
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
7174
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
75+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
76+
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
77+
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
78+
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
79+
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
7280
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
7381
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7482
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=

toolbox/generate.go

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,72 @@ package main
22

33
import (
44
"bytes"
5-
"flag"
65
"fmt"
76
"io"
8-
"log"
97
"net/http"
108
"os"
119
"os/exec"
1210
"path/filepath"
1311
"runtime"
1412
"time"
1513

14+
"github.com/spf13/cobra"
1615
"gopkg.in/yaml.v3"
1716
"gradient-engineer/playbook"
1817
)
1918

19+
var (
20+
playbookPath string
21+
outDir string
22+
)
23+
2024
func main() {
21-
var playbookPath string
22-
var outDir string
25+
var rootCmd = &cobra.Command{
26+
Use: "toolbox-generator [flags]",
27+
Short: "Generate toolbox archives from playbook configurations",
28+
Long: `Toolbox Generator creates portable toolbox archives containing Nix packages
29+
and diagnostic tools defined in playbook configurations. The generated archives
30+
include all necessary dependencies and can be distributed and executed on
31+
target systems.`,
32+
PreRunE: func(cmd *cobra.Command, args []string) error {
33+
if playbookPath == "" {
34+
return fmt.Errorf("playbook path is required")
35+
}
36+
if runtime.GOOS != "linux" {
37+
return fmt.Errorf("this utility must run on Linux")
38+
}
39+
return nil
40+
},
41+
RunE: func(cmd *cobra.Command, args []string) error {
42+
return generateToolbox()
43+
},
44+
}
2345

24-
flag.StringVar(&playbookPath, "playbook", "", "Path to playbook file")
25-
flag.StringVar(&outDir, "out", ".", "Output directory")
26-
flag.Parse()
46+
// Define flags
47+
rootCmd.Flags().StringVarP(&playbookPath, "playbook", "p", "", "Path to playbook file (required)")
48+
rootCmd.Flags().StringVarP(&outDir, "out", "o", ".", "Output directory for generated archive")
49+
50+
// Mark required flags
51+
rootCmd.MarkFlagRequired("playbook")
2752

28-
if playbookPath == "" {
29-
fmt.Fprintln(os.Stderr, "error: -playbook path is required")
30-
os.Exit(2)
31-
}
32-
if runtime.GOOS != "linux" {
33-
fmt.Fprintln(os.Stderr, "error: this utility must run on Linux")
34-
os.Exit(2)
53+
// Execute the command
54+
if err := rootCmd.Execute(); err != nil {
55+
os.Exit(1)
3556
}
57+
}
3658

59+
func generateToolbox() error {
3760
cfg, err := readPlaybook(playbookPath)
3861
if err != nil {
39-
log.Fatalf("failed to read playbook: %v", err)
62+
return fmt.Errorf("failed to read playbook: %w", err)
4063
}
4164
if len(cfg.Nixpkgs.Packages) == 0 {
42-
log.Fatalf("no nixpkgs.packages listed in %s", playbookPath)
65+
return fmt.Errorf("no nixpkgs.packages listed in %s", playbookPath)
4366
}
4467

4568
workDir, err := os.MkdirTemp("", "toolbox_work_*")
4669
if err != nil {
47-
log.Fatalf("failed to create temporary workdir: %v", err)
70+
return fmt.Errorf("failed to create temporary workdir: %w", err)
4871
}
4972
defer func() {
5073
_ = exec.Command("chmod", "-R", "u+w", workDir).Run()
@@ -53,29 +76,30 @@ func main() {
5376

5477
toolboxDir, _ := filepath.Abs(filepath.Join(workDir, "toolbox"))
5578
if err := nixCopy(toolboxDir, cfg.Nixpkgs.Version, cfg.Nixpkgs.Packages); err != nil {
56-
log.Fatalf("nix copy failed: %v", err)
79+
return fmt.Errorf("nix copy failed: %w", err)
5780
}
5881

5982
if err := fetchAndInstallProot(toolboxDir); err != nil {
60-
log.Fatalf("failed to install proot: %v", err)
83+
return fmt.Errorf("failed to install proot: %w", err)
6184
}
6285

6386
// Include the playbook file inside the toolbox directory
6487
if err := copyFile(playbookPath, filepath.Join(toolboxDir, "playbook.yaml"), 0o644); err != nil {
65-
log.Fatalf("failed to copy playbook file: %v", err)
88+
return fmt.Errorf("failed to copy playbook file: %w", err)
6689
}
6790

6891
outDir, _ = filepath.Abs(outDir)
6992
if err := os.MkdirAll(outDir, 0o755); err != nil {
70-
log.Fatalf("failed to ensure output directory: %v", err)
93+
return fmt.Errorf("failed to ensure output directory: %w", err)
7194
}
7295
archiveName := fmt.Sprintf("%s.%s.%s.tar.xz", cfg.ID, runtime.GOOS, runtime.GOARCH)
7396
outPath := filepath.Join(outDir, archiveName)
7497
if err := createTarXz(outPath, toolboxDir); err != nil {
75-
log.Fatalf("failed to create tar.xz: %v", err)
98+
return fmt.Errorf("failed to create tar.xz: %w", err)
7699
}
77100

78101
fmt.Printf("created %s\n", outPath)
102+
return nil
79103
}
80104

81105
func readPlaybook(path string) (*playbook.PlaybookConfig, error) {

0 commit comments

Comments
 (0)