- Content-addressable store (CAS) like Nix for fast lookups - Binary cache system with remote support - Linux chroot sandbox for isolated builds - Pacman-style UI with progress bars and colors - Temperature-aware parallel builds - fakeroot integration tests - Update .gitignore for Docker and test artifacts Implements LFS-optimized build pipeline with proper fakeroot support.
227 lines
5.4 KiB
Go
227 lines
5.4 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
// Sandbox provides isolated Linux build environment using chroot
|
|
type Sandbox struct {
|
|
rootDir string
|
|
workDir string
|
|
bindMounts []BindMount
|
|
environment map[string]string
|
|
}
|
|
|
|
// BindMount represents a bind mount
|
|
type BindMount struct {
|
|
Source string
|
|
Target string
|
|
ReadOnly bool
|
|
}
|
|
|
|
// NewSandbox creates Linux chroot sandbox
|
|
func NewSandbox(rootDir string) (*Sandbox, error) {
|
|
if err := os.MkdirAll(rootDir, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Sandbox{
|
|
rootDir: rootDir,
|
|
workDir: rootDir,
|
|
bindMounts: []BindMount{},
|
|
environment: make(map[string]string),
|
|
}, nil
|
|
}
|
|
|
|
// AddBindMount adds bind mount to sandbox
|
|
func (s *Sandbox) AddBindMount(source, target string, readOnly bool) {
|
|
s.bindMounts = append(s.bindMounts, BindMount{
|
|
Source: source,
|
|
Target: target,
|
|
ReadOnly: readOnly,
|
|
})
|
|
}
|
|
|
|
// SetEnv sets environment variable in sandbox
|
|
func (s *Sandbox) SetEnv(key, value string) {
|
|
s.environment[key] = value
|
|
}
|
|
|
|
// Setup prepares sandbox environment
|
|
func (s *Sandbox) Setup() error {
|
|
// Create essential directories
|
|
dirs := []string{"bin", "lib", "lib64", "usr", "tmp", "dev", "proc", "sys"}
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(filepath.Join(s.rootDir, dir), 0755); err != nil {
|
|
return fmt.Errorf("failed to create %s: %w", dir, err)
|
|
}
|
|
}
|
|
|
|
// Create essential device nodes
|
|
if err := s.createDevices(); err != nil {
|
|
return fmt.Errorf("failed to create devices: %w", err)
|
|
}
|
|
|
|
// Perform bind mounts
|
|
for _, mount := range s.bindMounts {
|
|
target := filepath.Join(s.rootDir, mount.Target)
|
|
if err := os.MkdirAll(target, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Bind mount
|
|
flags := syscall.MS_BIND
|
|
if mount.ReadOnly {
|
|
flags |= syscall.MS_RDONLY
|
|
}
|
|
|
|
if err := syscall.Mount(mount.Source, target, "", uintptr(flags), ""); err != nil {
|
|
return fmt.Errorf("failed to mount %s: %w", mount.Source, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createDevices creates essential device nodes
|
|
func (s *Sandbox) createDevices() error {
|
|
devDir := filepath.Join(s.rootDir, "dev")
|
|
|
|
// Create null device
|
|
if err := syscall.Mknod(filepath.Join(devDir, "null"), syscall.S_IFCHR|0666, int(1<<8)|3); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create zero device
|
|
if err := syscall.Mknod(filepath.Join(devDir, "zero"), syscall.S_IFCHR|0666, int(1<<8)|5); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create random device
|
|
if err := syscall.Mknod(filepath.Join(devDir, "random"), syscall.S_IFCHR|0666, int(1<<8)|8); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create urandom device
|
|
if err := syscall.Mknod(filepath.Join(devDir, "urandom"), syscall.S_IFCHR|0666, int(1<<8)|9); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Execute runs command in chroot sandbox
|
|
func (s *Sandbox) Execute(command string, args ...string) error {
|
|
cmd := exec.Command(command, args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Dir = "/"
|
|
cmd.Env = s.buildEnv()
|
|
|
|
// Use chroot
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Chroot: s.rootDir,
|
|
}
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
// ExecuteAsUser runs command as specific user in sandbox
|
|
func (s *Sandbox) ExecuteAsUser(uid, gid int, command string, args ...string) error {
|
|
cmd := exec.Command(command, args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Dir = "/"
|
|
cmd.Env = s.buildEnv()
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Chroot: s.rootDir,
|
|
Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)},
|
|
}
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
// buildEnv builds environment for sandbox
|
|
func (s *Sandbox) buildEnv() []string {
|
|
base := []string{
|
|
"PATH=/usr/bin:/bin:/usr/sbin:/sbin",
|
|
"HOME=/root",
|
|
"TMPDIR=/tmp",
|
|
"TERM=xterm",
|
|
}
|
|
|
|
for key, value := range s.environment {
|
|
base = append(base, fmt.Sprintf("%s=%s", key, value))
|
|
}
|
|
|
|
return base
|
|
}
|
|
|
|
// Cleanup removes sandbox resources
|
|
func (s *Sandbox) Cleanup() error {
|
|
// Unmount all bind mounts (in reverse order)
|
|
for i := len(s.bindMounts) - 1; i >= 0; i-- {
|
|
mount := s.bindMounts[i]
|
|
target := filepath.Join(s.rootDir, mount.Target)
|
|
syscall.Unmount(target, 0)
|
|
}
|
|
|
|
return os.RemoveAll(s.rootDir)
|
|
}
|
|
|
|
// BuilderSandbox wraps builder with Linux sandbox
|
|
type BuilderSandbox struct {
|
|
sandbox *Sandbox
|
|
workDir string
|
|
}
|
|
|
|
// NewBuilderSandbox creates sandboxed builder for Linux
|
|
func NewBuilderSandbox(workDir string) (*BuilderSandbox, error) {
|
|
sandboxDir := filepath.Join(workDir, ".sandbox")
|
|
|
|
sb, err := NewSandbox(sandboxDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Bind mount essential system directories
|
|
sb.AddBindMount("/bin", "bin", true)
|
|
sb.AddBindMount("/lib", "lib", true)
|
|
sb.AddBindMount("/lib64", "lib64", true)
|
|
sb.AddBindMount("/usr", "usr", true)
|
|
sb.AddBindMount("/dev", "dev", false)
|
|
|
|
// Bind mount work directory for writing
|
|
sb.AddBindMount(workDir, "work", false)
|
|
|
|
return &BuilderSandbox{
|
|
sandbox: sb,
|
|
workDir: workDir,
|
|
}, nil
|
|
}
|
|
|
|
// Build executes build in sandbox
|
|
func (bs *BuilderSandbox) Build(recipePath string) error {
|
|
// Set build environment
|
|
bs.sandbox.SetEnv("HOME", "/root")
|
|
bs.sandbox.SetEnv("TMPDIR", "/tmp")
|
|
bs.sandbox.SetEnv("SOURCE_DATE_EPOCH", "0")
|
|
|
|
// Setup sandbox
|
|
if err := bs.sandbox.Setup(); err != nil {
|
|
return fmt.Errorf("failed to setup sandbox: %w", err)
|
|
}
|
|
|
|
// Execute build in sandbox
|
|
return bs.sandbox.Execute("/usr/bin/zsvo", "build", "/work/recipe.yaml")
|
|
}
|
|
|
|
// Cleanup cleans up sandbox
|
|
func (bs *BuilderSandbox) Cleanup() error {
|
|
return bs.sandbox.Cleanup()
|
|
}
|