Skip to content

Commit f88976c

Browse files
tomassrnkae2bclaude
authored
feat: arch-aware downloads in create-build and fetch-busybox target (#2260)
* 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> * feat: commit both amd64 and arm64 busybox binaries with build tags Replace the single x86-64 busybox binary + fetch-busybox Makefile hack with two architecture-specific binaries selected via Go build tags. GOARCH automatically picks the right binary at compile time. - busybox_amd64: existing x86-64 static binary (renamed) - busybox_arm64: aarch64 static binary - busybox_amd64.go / busybox_arm64.go: build-tag-gated embeds - Remove fetch-busybox Makefile target (no longer needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: rename busybox binaries with version, add source comments Per review: keep version in filename (busybox_1.36.1-2_{arch}) and document the package source in the Go embed files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: e2b <e2b@Onsites-MacBook-Pro.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5d96982 commit f88976c

6 files changed

Lines changed: 119 additions & 32 deletions

File tree

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
}

packages/orchestrator/pkg/template/build/core/systeminit/busybox.go

Lines changed: 0 additions & 6 deletions
This file was deleted.

packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2 renamed to packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2_amd64

File renamed without changes.
Binary file not shown.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build amd64
2+
3+
// Busybox v1.36.1 static binary for amd64 (musl, minimal ~16 applets).
4+
// Custom build added in #1002 — origin unknown, no distro tag in binary.
5+
6+
package systeminit
7+
8+
import _ "embed"
9+
10+
//go:embed busybox_1.36.1-2_amd64
11+
var BusyboxBinary []byte
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build arm64
2+
3+
// Busybox v1.36.1 static binary for arm64 (glibc, full 271 applets).
4+
// Source: Debian busybox-static 1:1.36.1-9 (https://packages.debian.org/busybox-static)
5+
// TODO: rebuild both binaries from the same minimal config for consistency.
6+
7+
package systeminit
8+
9+
import _ "embed"
10+
11+
//go:embed busybox_1.36.1-2_arm64
12+
var BusyboxBinary []byte

0 commit comments

Comments
 (0)