@@ -3,7 +3,10 @@ package cmd
33import (
44 "context"
55 "fmt"
6+ "os"
67 "os/exec"
8+ "path/filepath"
9+ "runtime"
710 "time"
811
912 tea "github.com/charmbracelet/bubbletea"
@@ -19,6 +22,40 @@ type containerStatusMsg struct {
1922 sandbox * sandbox.ContainerSandbox
2023}
2124
25+ // buildHawkImage builds the hawk container image from the bundled Dockerfile.
26+ // It writes the Dockerfile to a temp dir and runs docker build.
27+ func buildHawkImage (ctx context.Context , tag string ) bool {
28+ dockerfile := `FROM ubuntu:24.04
29+ RUN apt-get update && apt-get install -y --no-install-recommends \
30+ git curl wget jq tree ripgrep fd-find make gcc \
31+ python3 python3-pip nodejs npm ca-certificates openssh-client \
32+ && rm -rf /var/lib/apt/lists/* \
33+ && ln -sf /usr/bin/fdfind /usr/bin/fd
34+ ENV TERM=xterm-256color LANG=C.UTF-8
35+ `
36+ // Use platform-appropriate arch
37+ platform := runtime .GOARCH
38+ if platform == "arm64" {
39+ platform = "linux/arm64"
40+ } else {
41+ platform = "linux/amd64"
42+ }
43+
44+ tmpDir , err := os .MkdirTemp ("" , "hawk-build-" )
45+ if err != nil {
46+ return false
47+ }
48+ defer os .RemoveAll (tmpDir )
49+
50+ dfPath := filepath .Join (tmpDir , "Dockerfile" )
51+ if err := os .WriteFile (dfPath , []byte (dockerfile ), 0644 ); err != nil {
52+ return false
53+ }
54+
55+ cmd := exec .CommandContext (ctx , "docker" , "build" , "--platform" , platform , "-t" , tag , "-f" , dfPath , tmpDir )
56+ return cmd .Run () == nil
57+ }
58+
2259// shouldUseContainer determines if hawk should run in container mode.
2360// Default: YES if Docker is available (like herm). User can override with --no-container.
2461func shouldUseContainer () bool {
@@ -45,17 +82,23 @@ func bootContainerCmd(projectDir string) tea.Cmd {
4582 }
4683 }
4784
48- // Ensure image exists locally, pull if needed (like herm)
85+ // Ensure image exists locally — pull or build as needed (like herm)
4986 image := cs .Image ()
50- pullCtx , pullCancel := context .WithTimeout (context .Background (), 300 * time .Second )
51- defer pullCancel ()
52- checkCmd := exec .CommandContext (pullCtx , "docker" , "image" , "inspect" , image )
87+ imgCtx , imgCancel := context .WithTimeout (context .Background (), 300 * time .Second )
88+ defer imgCancel ()
89+ checkCmd := exec .CommandContext (imgCtx , "docker" , "image" , "inspect" , image )
5390 if checkCmd .Run () != nil {
54- // Image not available locally — pull it
55- pullCmd := exec .CommandContext (pullCtx , "docker" , "pull" , image )
56- if err := pullCmd .Run (); err != nil {
57- // Fall back to ubuntu:24.04 if custom image can't be pulled
58- cs .SetImage ("ubuntu:24.04" )
91+ // Image not available locally — try pull first
92+ pullCmd := exec .CommandContext (imgCtx , "docker" , "pull" , image )
93+ if pullCmd .Run () != nil {
94+ // Pull failed — build from bundled Dockerfile
95+ built := buildHawkImage (imgCtx , image )
96+ if ! built {
97+ return containerStatusMsg {
98+ status : "image build failed" ,
99+ err : fmt .Errorf ("could not pull or build %s" , image ),
100+ }
101+ }
59102 }
60103 }
61104
0 commit comments