diff --git a/packages/orchestrator/cmd/create-build/main.go b/packages/orchestrator/cmd/create-build/main.go index 4cff864073..65998c1416 100644 --- a/packages/orchestrator/cmd/create-build/main.go +++ b/packages/orchestrator/cmd/create-build/main.go @@ -414,71 +414,141 @@ func printLocalFileSizes(basePath, buildID string) { } func setupKernel(ctx context.Context, dir, version string) error { - dstPath := filepath.Join(dir, version, "vmlinux.bin") + arch := utils.TargetArch() + dstPath := filepath.Join(dir, version, arch, "vmlinux.bin") + if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil { return fmt.Errorf("mkdir kernel dir: %w", err) } if _, err := os.Stat(dstPath); err == nil { - fmt.Printf("✓ Kernel %s exists\n", version) + fmt.Printf("✓ Kernel %s (%s) exists\n", version, arch) return nil } - kernelURL, _ := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/kernels/", version, "vmlinux.bin") - fmt.Printf("⬇ Downloading kernel %s...\n", version) + // Try arch-specific URL first: {version}/{arch}/vmlinux.bin + archURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/kernels/", version, arch, "vmlinux.bin") + if err != nil { + return fmt.Errorf("invalid kernel URL: %w", err) + } + + fmt.Printf("⬇ Downloading kernel %s (%s)...\n", version, arch) + + if err := download(ctx, archURL, dstPath, 0o644); err == nil { + return nil + } else if !errors.Is(err, errNotFound) { + return fmt.Errorf("failed to download kernel: %w", err) + } + + // Legacy URLs are x86_64-only; only fall back for amd64. + if arch != "amd64" { + return fmt.Errorf("kernel %s not found for %s (no legacy fallback for non-amd64)", version, arch) + } + + legacyURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/kernels/", version, "vmlinux.bin") + if err != nil { + return fmt.Errorf("invalid kernel legacy URL: %w", err) + } + + fmt.Printf(" %s path not found, trying legacy URL...\n", arch) - return download(ctx, kernelURL, dstPath, 0o644) + return download(ctx, legacyURL, dstPath, 0o644) } func setupFC(ctx context.Context, dir, version string) error { - dstPath := filepath.Join(dir, version, "firecracker") + arch := utils.TargetArch() + dstPath := filepath.Join(dir, version, arch, "firecracker") + if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil { return fmt.Errorf("mkdir firecracker dir: %w", err) } if _, err := os.Stat(dstPath); err == nil { - fmt.Printf("✓ Firecracker %s exists\n", version) + fmt.Printf("✓ Firecracker %s (%s) exists\n", version, arch) return nil } - // Old releases in https://github.com/e2b-dev/fc-versions/releases don't build - // x86_64 and aarch64 binaries. They just build the former and the asset's name - // is just 'firecracker' - // TODO: Drop this work-around once we remove support for Firecracker v1.10 - assetName := "firecracker-amd64" - if strings.HasPrefix(version, "v1.10") { - assetName = "firecracker" + // Download from GCS bucket with {version}/{arch}/firecracker path + fcURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/fc-versions/", version, arch, "firecracker") + if err != nil { + return fmt.Errorf("invalid Firecracker URL: %w", err) } - fcURL := fmt.Sprintf("https://github.com/e2b-dev/fc-versions/releases/download/%s/%s", version, assetName) - fmt.Printf("⬇ Downloading Firecracker %s...\n", version) - return download(ctx, fcURL, dstPath, 0o755) + fmt.Printf("⬇ Downloading Firecracker %s (%s)...\n", version, arch) + + if err := download(ctx, fcURL, dstPath, 0o755); err == nil { + return nil + } else if !errors.Is(err, errNotFound) { + return fmt.Errorf("failed to download Firecracker: %w", err) + } + + // Legacy URLs are x86_64-only; only fall back for amd64. + if arch != "amd64" { + return fmt.Errorf("firecracker %s not found for %s (no legacy fallback for non-amd64)", version, arch) + } + + legacyURL, err := url.JoinPath("https://storage.googleapis.com/e2b-prod-public-builds/fc-versions/", version, "firecracker") + if err != nil { + return fmt.Errorf("invalid Firecracker legacy URL: %w", err) + } + + fmt.Printf(" %s path not found, trying legacy URL...\n", arch) + + return download(ctx, legacyURL, dstPath, 0o755) } -func download(ctx context.Context, url, path string, perm os.FileMode) error { - req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +var errNotFound = errors.New("not found") + +func download(ctx context.Context, rawURL, path string, perm os.FileMode) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) + if err != nil { + return fmt.Errorf("invalid download URL %s: %w", rawURL, err) + } + resp, err := (&http.Client{Timeout: 5 * time.Minute}).Do(req) if err != nil { return err } defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("%w: %s", errNotFound, rawURL) + } if resp.StatusCode != http.StatusOK { - return fmt.Errorf("HTTP %d: %s", resp.StatusCode, url) + return fmt.Errorf("HTTP %d: %s", resp.StatusCode, rawURL) } - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm) + // Write to a temporary file and rename atomically to avoid partial files + // on network errors or disk-full conditions. + tmpPath := path + ".tmp" + + f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm) if err != nil { return err } - defer f.Close() - _, err = io.Copy(f, resp.Body) - if err == nil { - fmt.Printf("✓ Downloaded %s\n", filepath.Base(path)) + if _, err := io.Copy(f, resp.Body); err != nil { + f.Close() + os.Remove(tmpPath) + + return err } - return err + if err := f.Close(); err != nil { + os.Remove(tmpPath) + + return err + } + + if err := os.Rename(tmpPath, path); err != nil { + os.Remove(tmpPath) + + return err + } + + fmt.Printf("✓ Downloaded %s\n", filepath.Base(path)) + + return nil } diff --git a/packages/orchestrator/pkg/template/build/core/systeminit/busybox.go b/packages/orchestrator/pkg/template/build/core/systeminit/busybox.go deleted file mode 100644 index 07aedb6baf..0000000000 --- a/packages/orchestrator/pkg/template/build/core/systeminit/busybox.go +++ /dev/null @@ -1,6 +0,0 @@ -package systeminit - -import _ "embed" - -//go:embed busybox_1.36.1-2 -var BusyboxBinary []byte diff --git a/packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2 b/packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2_amd64 similarity index 100% rename from packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2 rename to packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2_amd64 diff --git a/packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2_arm64 b/packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2_arm64 new file mode 100755 index 0000000000..625e048f09 Binary files /dev/null and b/packages/orchestrator/pkg/template/build/core/systeminit/busybox_1.36.1-2_arm64 differ diff --git a/packages/orchestrator/pkg/template/build/core/systeminit/busybox_amd64.go b/packages/orchestrator/pkg/template/build/core/systeminit/busybox_amd64.go new file mode 100644 index 0000000000..60c76bbb0a --- /dev/null +++ b/packages/orchestrator/pkg/template/build/core/systeminit/busybox_amd64.go @@ -0,0 +1,11 @@ +//go:build amd64 + +// Busybox v1.36.1 static binary for amd64 (musl, minimal ~16 applets). +// Custom build added in #1002 — origin unknown, no distro tag in binary. + +package systeminit + +import _ "embed" + +//go:embed busybox_1.36.1-2_amd64 +var BusyboxBinary []byte diff --git a/packages/orchestrator/pkg/template/build/core/systeminit/busybox_arm64.go b/packages/orchestrator/pkg/template/build/core/systeminit/busybox_arm64.go new file mode 100644 index 0000000000..d906967079 --- /dev/null +++ b/packages/orchestrator/pkg/template/build/core/systeminit/busybox_arm64.go @@ -0,0 +1,12 @@ +//go:build arm64 + +// Busybox v1.36.1 static binary for arm64 (glibc, full 271 applets). +// Source: Debian busybox-static 1:1.36.1-9 (https://packages.debian.org/busybox-static) +// TODO: rebuild both binaries from the same minimal config for consistency. + +package systeminit + +import _ "embed" + +//go:embed busybox_1.36.1-2_arm64 +var BusyboxBinary []byte