Skip to content

Commit f918ddd

Browse files
committed
feat: auto-build hawk image on first run (no fallback to bare ubuntu)
1 parent 380f563 commit f918ddd

1 file changed

Lines changed: 52 additions & 9 deletions

File tree

cmd/container_boot.go

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package cmd
33
import (
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.
2461
func 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 locallypull 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

Comments
 (0)