From 8d06c0235bf01d40d1455fac44519d9cf6983806 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Mon, 19 Jan 2026 00:00:13 +0900 Subject: [PATCH] internal/rosa: busybox binary artifact This installs a statically linked busybox binary distribution for decompressing the gentoo stage3 tarball, since there is no native xz implementation. Signed-off-by: Ophestra --- internal/rosa/busybox.go | 99 ++++++++++++++++++++++++++++++++++++++++ internal/rosa/rosa.go | 39 ++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 internal/rosa/busybox.go diff --git a/internal/rosa/busybox.go b/internal/rosa/busybox.go new file mode 100644 index 0000000..3fe221b --- /dev/null +++ b/internal/rosa/busybox.go @@ -0,0 +1,99 @@ +package rosa + +import ( + "fmt" + "io" + "net/http" + "os" + "time" + + "hakurei.app/container/fhs" + "hakurei.app/internal/pkg" +) + +// busyboxBin is a busybox binary distribution installed under bin/busybox. +type busyboxBin struct { + // Underlying busybox binary. + bin pkg.File +} + +// Kind returns the hardcoded [pkg.Kind] value. +func (a busyboxBin) Kind() pkg.Kind { return kindBusyboxBin } + +// Params is a noop. +func (a busyboxBin) Params(*pkg.IContext) {} + +// Dependencies returns the underlying busybox [pkg.File]. +func (a busyboxBin) Dependencies() []pkg.Artifact { + return []pkg.Artifact{a.bin} +} + +// String returns the reporting name of the underlying file prefixed with expand. +func (a busyboxBin) String() string { + return "expand-" + a.bin.(fmt.Stringer).String() +} + +// Cure installs the underlying busybox [pkg.File] to bin/busybox. +func (a busyboxBin) Cure(t *pkg.TContext) (err error) { + var r io.ReadCloser + if r, err = t.Open(a.bin); err != nil { + return + } + defer func() { + closeErr := r.Close() + if err == nil { + err = closeErr + } + }() + + binDir := t.GetWorkDir().Append("bin") + if err = os.MkdirAll(binDir.String(), 0700); err != nil { + return + } + + var w *os.File + if w, err = os.OpenFile( + binDir.Append("busybox").String(), + os.O_WRONLY|os.O_CREATE|os.O_EXCL, + 0500, + ); err != nil { + return + } + defer func() { + closeErr := w.Close() + if err == nil { + err = closeErr + } + }() + + _, err = io.Copy(w, r) + return +} + +// newBusyboxBin returns a [pkg.Artifact] containing a busybox installation from +// the https://busybox.net/downloads/binaries/ binary release. +func newBusyboxBin() pkg.Artifact { + const ( + version = "1.35.0" + checksum = "L7OBIsPu9enNHn7FqpBT1kOg_mCLNmetSeNMA3i4Y60Z5jTgnlX3qX3zcQtLx5AB" + ) + return pkg.NewExec( + "busybox-bin-"+version, nil, pkg.ExecTimeoutMax, fhs.AbsRoot, []string{ + "PATH=/system/bin", + }, + AbsSystem.Append("bin", "busybox"), + []string{"hush", "-c", "" + + "busybox mkdir -p /work/system/bin/ && " + + "busybox cp /system/bin/busybox /work/system/bin/ && " + + "busybox --install -s /work/system/bin/"}, + pkg.Path(AbsSystem, true, busyboxBin{pkg.NewHTTPGet( + &http.Client{Transport: &http.Transport{ + // busybox website is really slow to respond + TLSHandshakeTimeout: 2 * time.Minute, + }}, + "https://busybox.net/downloads/binaries/"+ + version+"-"+linuxArch()+"-linux-musl/busybox", + mustDecode(checksum), + )}), + ) +} diff --git a/internal/rosa/rosa.go b/internal/rosa/rosa.go index 7ddf414..cec415a 100644 --- a/internal/rosa/rosa.go +++ b/internal/rosa/rosa.go @@ -2,10 +2,49 @@ package rosa import ( + "log" + "runtime" + + "hakurei.app/container/fhs" "hakurei.app/internal/pkg" ) const ( // kindEtc is the kind of [pkg.Artifact] of cureEtc. kindEtc = iota + pkg.KindCustomOffset + + // kindBusyboxBin is the kind of [pkg.Artifact] of busyboxBin. + kindBusyboxBin ) + +// mustDecode is like [pkg.MustDecode], but replaces the zero value and prints +// a warning. +func mustDecode(s string) pkg.Checksum { + var fallback = pkg.Checksum{} + if s == "" { + log.Println( + "falling back to", + pkg.Encode(fallback), + "for unpopulated checksum", + ) + return fallback + } + return pkg.MustDecode(s) +} + +var ( + // AbsSystem is the Rosa OS installation prefix. + AbsSystem = fhs.AbsRoot.Append("system") +) + +// linuxArch returns the architecture name used by linux corresponding to +// [runtime.GOARCH]. +func linuxArch() string { + switch runtime.GOARCH { + case "amd64": + return "x86_64" + + default: + panic("unsupported target " + runtime.GOARCH) + } +}