Skip to content

Commit 8701dbd

Browse files
e2bclaude
authored andcommitted
feat: arch-aware downloads in create-build and fetch-busybox target
- Update setupKernel/setupFC to use arch-prefixed URLs with legacy fallback - Add errNotFound sentinel for 404 handling in download helper - Use atomic temp-file writes to avoid partial downloads - Add fetch-busybox Makefile target to swap busybox binary for ARM64 builds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 20a4968 commit 8701dbd

File tree

2 files changed

+127
-21
lines changed

2 files changed

+127
-21
lines changed

packages/orchestrator/Makefile

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ build:
2121
@docker build --platform linux/amd64 --output=bin --build-arg COMMIT_SHA="$(COMMIT_SHA)" -f ./Dockerfile ..
2222

2323
.PHONY: build-local
24-
build-local:
24+
build-local: fetch-busybox
2525
# Allow for passing commit sha directly for docker builds
2626
$(eval COMMIT_SHA ?= $(shell git rev-parse --short HEAD))
2727
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o bin/orchestrator -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" .
2828
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o bin/clean-nfs-cache -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" ./cmd/clean-nfs-cache
2929

3030
.PHONY: build-debug
31-
build-debug:
31+
build-debug: fetch-busybox
3232
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -race -gcflags=all="-N -l" -o bin/orchestrator .
3333

3434
.PHONY: run-debug
@@ -125,6 +125,34 @@ build-template:
125125
-kernel $(KERNEL_VERSION) \
126126
-firecracker $(FIRECRACKER_VERSION)
127127

128+
.PHONY: fetch-busybox
129+
fetch-busybox:
130+
@ARCH=$$(go env GOARCH); \
131+
BUSYBOX_TARGET=./pkg/template/build/core/systeminit/busybox_1.36.1-2; \
132+
if [ "$$ARCH" != "arm64" ]; then \
133+
echo "✓ Using bundled amd64 busybox"; \
134+
elif file "$$BUSYBOX_TARGET" 2>/dev/null | grep -q 'aarch64\|ARM aarch64'; then \
135+
echo "✓ Busybox is already arm64"; \
136+
elif command -v busybox >/dev/null 2>&1 && file "$$(command -v busybox)" 2>/dev/null | grep -q 'aarch64\|ARM aarch64' && file "$$(command -v busybox)" 2>/dev/null | grep -q 'statically linked'; then \
137+
cp "$$(command -v busybox)" "$$BUSYBOX_TARGET" && \
138+
echo "✓ Copied host busybox (arm64, static) to embedded path"; \
139+
elif command -v apt-get >/dev/null 2>&1 && command -v dpkg-deb >/dev/null 2>&1; then \
140+
echo "Fetching arm64 busybox via apt..."; \
141+
TMPDIR=$$(mktemp -d); \
142+
apt-get download busybox-static 2>/dev/null && \
143+
dpkg-deb -x busybox-static_*.deb "$$TMPDIR" && \
144+
cp "$$TMPDIR/bin/busybox" "$$BUSYBOX_TARGET" && \
145+
rm -rf "$$TMPDIR" busybox-static_*.deb && \
146+
echo "✓ Replaced embedded busybox with arm64 binary (from busybox-static package)" || \
147+
{ rm -rf "$$TMPDIR" busybox-static_*.deb; echo "⚠ apt-get download failed"; exit 1; }; \
148+
else \
149+
echo "⚠ ARM64 busybox required but no method available to fetch it."; \
150+
echo " Options:"; \
151+
echo " 1. Install busybox-static: apt install busybox-static, then re-run"; \
152+
echo " 2. Manually place an arm64 busybox binary at: $$BUSYBOX_TARGET"; \
153+
exit 1; \
154+
fi
155+
128156
.PHONY: migrate
129157
migrate:
130158
./scripts/upload-envs.sh /mnt/disks/fc-envs/v1 $(TEMPLATE_BUCKET_NAME)

packages/orchestrator/cmd/create-build/main.go

Lines changed: 97 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -412,63 +412,141 @@ func printLocalFileSizes(basePath, buildID string) {
412412
}
413413

414414
func setupKernel(ctx context.Context, dir, version string) error {
415-
dstPath := filepath.Join(dir, version, "vmlinux.bin")
415+
arch := utils.TargetArch()
416+
dstPath := filepath.Join(dir, version, arch, "vmlinux.bin")
417+
416418
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
417419
return fmt.Errorf("mkdir kernel dir: %w", err)
418420
}
419421

420422
if _, err := os.Stat(dstPath); err == nil {
421-
fmt.Printf("✓ Kernel %s exists\n", version)
423+
fmt.Printf("✓ Kernel %s (%s) exists\n", version, arch)
422424

423425
return nil
424426
}
425427

426-
kernelURL, _ := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/kernels/", version, "vmlinux.bin")
427-
fmt.Printf("⬇ Downloading kernel %s...\n", version)
428+
// Try arch-specific URL first: {version}/{arch}/vmlinux.bin
429+
archURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/kernels/", version, arch, "vmlinux.bin")
430+
if err != nil {
431+
return fmt.Errorf("invalid kernel URL: %w", err)
432+
}
433+
434+
fmt.Printf("⬇ Downloading kernel %s (%s)...\n", version, arch)
435+
436+
if err := download(ctx, archURL, dstPath, 0o644); err == nil {
437+
return nil
438+
} else if !errors.Is(err, errNotFound) {
439+
return fmt.Errorf("failed to download kernel: %w", err)
440+
}
441+
442+
// Legacy URLs are x86_64-only; only fall back for amd64.
443+
if arch != "amd64" {
444+
return fmt.Errorf("kernel %s not found for %s (no legacy fallback for non-amd64)", version, arch)
445+
}
446+
447+
legacyURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/kernels/", version, "vmlinux.bin")
448+
if err != nil {
449+
return fmt.Errorf("invalid kernel legacy URL: %w", err)
450+
}
451+
452+
fmt.Printf(" %s path not found, trying legacy URL...\n", arch)
428453

429-
return download(ctx, kernelURL, dstPath, 0o644)
454+
return download(ctx, legacyURL, dstPath, 0o644)
430455
}
431456

432457
func setupFC(ctx context.Context, dir, version string) error {
433-
dstPath := filepath.Join(dir, version, "firecracker")
458+
arch := utils.TargetArch()
459+
dstPath := filepath.Join(dir, version, arch, "firecracker")
460+
434461
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
435462
return fmt.Errorf("mkdir firecracker dir: %w", err)
436463
}
437464

438465
if _, err := os.Stat(dstPath); err == nil {
439-
fmt.Printf("✓ Firecracker %s exists\n", version)
466+
fmt.Printf("✓ Firecracker %s (%s) exists\n", version, arch)
440467

441468
return nil
442469
}
443470

444-
fcURL := fmt.Sprintf("https://github.com/e2b-dev/fc-versions/releases/download/%s/firecracker", version)
445-
fmt.Printf("⬇ Downloading Firecracker %s...\n", version)
471+
// Download from GCS bucket with {version}/{arch}/firecracker path
472+
fcURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/fc-versions/", version, arch, "firecracker")
473+
if err != nil {
474+
return fmt.Errorf("invalid Firecracker URL: %w", err)
475+
}
476+
477+
fmt.Printf("⬇ Downloading Firecracker %s (%s)...\n", version, arch)
478+
479+
if err := download(ctx, fcURL, dstPath, 0o755); err == nil {
480+
return nil
481+
} else if !errors.Is(err, errNotFound) {
482+
return fmt.Errorf("failed to download Firecracker: %w", err)
483+
}
484+
485+
// Legacy URLs are x86_64-only; only fall back for amd64.
486+
if arch != "amd64" {
487+
return fmt.Errorf("firecracker %s not found for %s (no legacy fallback for non-amd64)", version, arch)
488+
}
489+
490+
legacyURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/fc-versions/", version, "firecracker")
491+
if err != nil {
492+
return fmt.Errorf("invalid Firecracker legacy URL: %w", err)
493+
}
494+
495+
fmt.Printf(" %s path not found, trying legacy URL...\n", arch)
446496

447-
return download(ctx, fcURL, dstPath, 0o755)
497+
return download(ctx, legacyURL, dstPath, 0o755)
448498
}
449499

450-
func download(ctx context.Context, url, path string, perm os.FileMode) error {
451-
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
500+
var errNotFound = errors.New("not found")
501+
502+
func download(ctx context.Context, rawURL, path string, perm os.FileMode) error {
503+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
504+
if err != nil {
505+
return fmt.Errorf("invalid download URL %s: %w", rawURL, err)
506+
}
507+
452508
resp, err := (&http.Client{Timeout: 5 * time.Minute}).Do(req)
453509
if err != nil {
454510
return err
455511
}
456512
defer resp.Body.Close()
457513

514+
if resp.StatusCode == http.StatusNotFound {
515+
return fmt.Errorf("%w: %s", errNotFound, rawURL)
516+
}
458517
if resp.StatusCode != http.StatusOK {
459-
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, url)
518+
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, rawURL)
460519
}
461520

462-
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
521+
// Write to a temporary file and rename atomically to avoid partial files
522+
// on network errors or disk-full conditions.
523+
tmpPath := path + ".tmp"
524+
525+
f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
463526
if err != nil {
464527
return err
465528
}
466-
defer f.Close()
467529

468-
_, err = io.Copy(f, resp.Body)
469-
if err == nil {
470-
fmt.Printf("✓ Downloaded %s\n", filepath.Base(path))
530+
if _, err := io.Copy(f, resp.Body); err != nil {
531+
f.Close()
532+
os.Remove(tmpPath)
533+
534+
return err
535+
}
536+
537+
if err := f.Close(); err != nil {
538+
os.Remove(tmpPath)
539+
540+
return err
471541
}
472542

473-
return err
543+
if err := os.Rename(tmpPath, path); err != nil {
544+
os.Remove(tmpPath)
545+
546+
return err
547+
}
548+
549+
fmt.Printf("✓ Downloaded %s\n", filepath.Base(path))
550+
551+
return nil
474552
}

0 commit comments

Comments
 (0)