Skip to content

Commit 96ed56c

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 af0530f commit 96ed56c

File tree

2 files changed

+126
-28
lines changed

2 files changed

+126
-28
lines changed

packages/orchestrator/Makefile

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ build:
2828
@docker build --platform $(BUILD_PLATFORM) --output=bin --build-arg COMMIT_SHA="$(COMMIT_SHA)" -f ./Dockerfile ..
2929

3030
.PHONY: build-local
31-
build-local:
31+
build-local: fetch-busybox
3232
# Allow for passing commit sha directly for docker builds
3333
$(eval COMMIT_SHA ?= $(shell git rev-parse --short HEAD))
3434
CGO_ENABLED=1 GOOS=linux GOARCH=$(BUILD_ARCH) go build -o bin/orchestrator -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" .
3535
CGO_ENABLED=1 GOOS=linux GOARCH=$(BUILD_ARCH) go build -o bin/clean-nfs-cache -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" ./cmd/clean-nfs-cache
3636

3737
.PHONY: build-debug
38-
build-debug:
38+
build-debug: fetch-busybox
3939
CGO_ENABLED=1 GOOS=linux GOARCH=$(BUILD_ARCH) go build -race -gcflags=all="-N -l" -o bin/orchestrator .
4040

4141
.PHONY: run-debug
@@ -132,6 +132,34 @@ build-template:
132132
-kernel $(KERNEL_VERSION) \
133133
-firecracker $(FIRECRACKER_VERSION)
134134

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

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

Lines changed: 96 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -414,71 +414,141 @@ func printLocalFileSizes(basePath, buildID string) {
414414
}
415415

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

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

425427
return nil
426428
}
427429

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

431-
return download(ctx, kernelURL, dstPath, 0o644)
456+
return download(ctx, legacyURL, dstPath, 0o644)
432457
}
433458

434459
func setupFC(ctx context.Context, dir, version string) error {
435-
dstPath := filepath.Join(dir, version, "firecracker")
460+
arch := utils.TargetArch()
461+
dstPath := filepath.Join(dir, version, arch, "firecracker")
462+
436463
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
437464
return fmt.Errorf("mkdir firecracker dir: %w", err)
438465
}
439466

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

443470
return nil
444471
}
445472

446-
// Old releases in https://github.com/e2b-dev/fc-versions/releases don't build
447-
// x86_64 and aarch64 binaries. They just build the former and the asset's name
448-
// is just 'firecracker'
449-
// TODO: Drop this work-around once we remove support for Firecracker v1.10
450-
assetName := "firecracker-amd64"
451-
if strings.HasPrefix(version, "v1.10") {
452-
assetName = "firecracker"
473+
// Download from GCS bucket with {version}/{arch}/firecracker path
474+
fcURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/fc-versions/", version, arch, "firecracker")
475+
if err != nil {
476+
return fmt.Errorf("invalid Firecracker URL: %w", err)
453477
}
454-
fcURL := fmt.Sprintf("https://github.com/e2b-dev/fc-versions/releases/download/%s/%s", version, assetName)
455-
fmt.Printf("⬇ Downloading Firecracker %s...\n", version)
456478

457-
return download(ctx, fcURL, dstPath, 0o755)
479+
fmt.Printf("⬇ Downloading Firecracker %s (%s)...\n", version, arch)
480+
481+
if err := download(ctx, fcURL, dstPath, 0o755); err == nil {
482+
return nil
483+
} else if !errors.Is(err, errNotFound) {
484+
return fmt.Errorf("failed to download Firecracker: %w", err)
485+
}
486+
487+
// Legacy URLs are x86_64-only; only fall back for amd64.
488+
if arch != "amd64" {
489+
return fmt.Errorf("firecracker %s not found for %s (no legacy fallback for non-amd64)", version, arch)
490+
}
491+
492+
legacyURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/fc-versions/", version, "firecracker")
493+
if err != nil {
494+
return fmt.Errorf("invalid Firecracker legacy URL: %w", err)
495+
}
496+
497+
fmt.Printf(" %s path not found, trying legacy URL...\n", arch)
498+
499+
return download(ctx, legacyURL, dstPath, 0o755)
458500
}
459501

460-
func download(ctx context.Context, url, path string, perm os.FileMode) error {
461-
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
502+
var errNotFound = errors.New("not found")
503+
504+
func download(ctx context.Context, rawURL, path string, perm os.FileMode) error {
505+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
506+
if err != nil {
507+
return fmt.Errorf("invalid download URL %s: %w", rawURL, err)
508+
}
509+
462510
resp, err := (&http.Client{Timeout: 5 * time.Minute}).Do(req)
463511
if err != nil {
464512
return err
465513
}
466514
defer resp.Body.Close()
467515

516+
if resp.StatusCode == http.StatusNotFound {
517+
return fmt.Errorf("%w: %s", errNotFound, rawURL)
518+
}
468519
if resp.StatusCode != http.StatusOK {
469-
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, url)
520+
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, rawURL)
470521
}
471522

472-
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
523+
// Write to a temporary file and rename atomically to avoid partial files
524+
// on network errors or disk-full conditions.
525+
tmpPath := path + ".tmp"
526+
527+
f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
473528
if err != nil {
474529
return err
475530
}
476-
defer f.Close()
477531

478-
_, err = io.Copy(f, resp.Body)
479-
if err == nil {
480-
fmt.Printf("✓ Downloaded %s\n", filepath.Base(path))
532+
if _, err := io.Copy(f, resp.Body); err != nil {
533+
f.Close()
534+
os.Remove(tmpPath)
535+
536+
return err
481537
}
482538

483-
return err
539+
if err := f.Close(); err != nil {
540+
os.Remove(tmpPath)
541+
542+
return err
543+
}
544+
545+
if err := os.Rename(tmpPath, path); err != nil {
546+
os.Remove(tmpPath)
547+
548+
return err
549+
}
550+
551+
fmt.Printf("✓ Downloaded %s\n", filepath.Base(path))
552+
553+
return nil
484554
}

0 commit comments

Comments
 (0)