package runner import ( "embed" "fmt" "io" "log" "os" "os/exec" "path" "strings" ) //go:embed alpine/keys var alpineKeys embed.FS func Run(config Config, rootfs string, cache string, src string, w io.Writer, dontDeleteTemp bool, env map[string]string) (err error) { if err = os.RemoveAll(rootfs); err != nil { return } if err = os.MkdirAll(path.Join(rootfs, "etc/apk/keys"), 0700); err != nil { return } if err = os.WriteFile(path.Join(rootfs, "etc/apk/repositories"), []byte( `https://dl-cdn.alpinelinux.org/alpine/v3.17/main https://dl-cdn.alpinelinux.org/alpine/v3.17/community`), 0600); err != nil { return } if err = os.WriteFile(path.Join(rootfs, "etc/resolv.conf"), []byte(`nameserver 8.8.8.8`), 0644); err != nil { return } keys, err := alpineKeys.ReadDir("alpine/keys") if err != nil { return } for _, entry := range keys { o, err := os.Create(path.Join(rootfs, "etc/apk/keys/", entry.Name())) if err != nil { return err } s, err := alpineKeys.Open(path.Join("alpine/keys", entry.Name())) if err != nil { return err } if _, err = io.Copy(o, s); err != nil { return err } } if err = os.MkdirAll(path.Join(cache, "_var_cache_apk"), 0700); err != nil { return } if err = os.Symlink("/var/cache/apk", path.Join(rootfs, "etc/apk/cache")); err != nil { return } cmd := exec.Command("apk", "add", "--root", rootfs, "--initdb", "--cache-dir", path.Join("..", cache, "_var_cache_apk"), "apk-tools") if err = cmd.Run(); err != nil { return fmt.Errorf("apk add apk-tools: %w", err) } cached := append(config.Cache, "/var/cache/apk") var cachedParams []string for _, c := range cached { cacheDir := path.Join(cache, strings.ReplaceAll(c, "/", "_")) if err = os.MkdirAll(cacheDir, 0700); err != nil { return } cachedParams = append(cachedParams, "--bind", cacheDir, c) } tmp, err := os.MkdirTemp("", "repro-run-") if err != nil { return } defer removeTemp(tmp, dontDeleteTemp) log.Println("/work = " + tmp) params := append( []string{"--bind", rootfs, "/", "--ro-bind", src, "/src", "--dev-bind", "/dev", "/dev", "--dev-bind", "/proc", "/proc", "--bind", tmp, "/work", "--unshare-user", "--uid", "1000", "--gid", "1000", "--setenv", "HOME", "/home/repro", "--chdir", "/work", "--setenv", "PATH", "/usr/bin:/usr/sbin:/bin:/sbin", }, cachedParams...) for k, v := range env { params = append(params, "--setenv", k, v) } cmd = exec.Command("bwrap", append(params, "apk", "add", "--quiet", "busybox", "busybox-suid", "libc-utils", "alpine-baselayout", "alpine-conf", "alpine-release")...) cmd.Stdout = os.Stderr cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { return fmt.Errorf("bwrap apk add: %w", err) } cmd = exec.Command("bwrap", append(params, "--uid", "0", "adduser", "-u", "1000", "-D", "repro")...) cmd.Stdout = os.Stderr cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { return fmt.Errorf("bwrap adduser repro: %w", err) } cmd = exec.Command("bwrap", append(params, strings.Split(config.Command, " ")...)...) // cmd.Stdin = os.Stdin if w == nil { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } else { cmd.Stdout = w cmd.Stderr = w } if err = cmd.Run(); err != nil { return fmt.Errorf("running Command: %w", err) } return } type Config struct { Command string Cache []string } func removeTemp(name string, dontRemoveTemp bool) { if !dontRemoveTemp { err := os.RemoveAll(name) if err != nil { log.Printf("No pude borrar %s: %v", name, err) } } }