GitRoot

craft your forge, build your project, grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package exec
  6
  7import (
  8	"fmt"
  9	"os"
 10	"os/exec"
 11	"path/filepath"
 12	"strings"
 13
 14	"github.com/samber/oops"
 15	pluginLib "gitroot.dev/libs/golang/plugin/model"
 16	"gitroot.dev/server/configuration"
 17	"gitroot.dev/server/logger"
 18)
 19
 20type Bwrap struct {
 21	logger   *logger.Logger
 22	conf     configuration.BwrapConf
 23	executor *BareMetal
 24}
 25
 26func NewBwrap(log *logger.Logger, conf configuration.BwrapConf) *Bwrap {
 27	mylog := log.NewSubLogger("bwrap")
 28	return &Bwrap{
 29		logger:   mylog,
 30		conf:     conf,
 31		executor: NewBareMetal(mylog),
 32	}
 33}
 34
 35func (e *Bwrap) name() string {
 36	return "bwrap"
 37}
 38
 39func (e *Bwrap) exec(dir string, exe pluginLib.Exec) (*pluginLib.ExecStatus, error) {
 40	if err := e.prepareFiles(dir); err != nil {
 41		return nil, oops.Wrapf(err, "can't prepare env bwrap")
 42	}
 43
 44	eachCmds := make([]string, len(exe.Cmds))
 45	for i, cmd := range exe.Cmds {
 46		eachCmds[i] = fmt.Sprintf("%s %s", cmd.Cmd, strings.Join(cmd.Args, " "))
 47	}
 48	toBeRun := fmt.Sprintf("cd %s && %s", APP_DIR, strings.Join(eachCmds, " && "))
 49	args := []string{
 50		"--ro-bind", "/usr", "/usr",
 51		"--dir", "/tmp",
 52		"--dir", "/var",
 53		"--symlink", "../tmp", "var/tmp",
 54		"--proc", "/proc",
 55		"--dev", "/dev",
 56		"--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf",
 57		"--ro-bind", "/etc/ssl", "/etc/ssl",
 58		"--ro-bind", filepath.Join(dir, "etc", "passwd"), "/etc/passwd",
 59		"--ro-bind", filepath.Join(dir, "etc", "group"), "/etc/group",
 60		"--bind", filepath.Join(dir, APP_DIR), fmt.Sprintf("/%s", APP_DIR),
 61		"--symlink", "usr/lib", "/lib",
 62		"--symlink", "usr/lib64", "/lib64",
 63		"--symlink", "usr/bin", "/bin",
 64		"--symlink", "usr/sbin", "/sbin",
 65		"--chdir", "/",
 66		"--unshare-all",
 67		"--share-net",
 68		"--die-with-parent",
 69		"--dir", fmt.Sprintf("/run/user/%d", e.conf.Uid),
 70		"--setenv", "XDG_RUNTIME_DIR", fmt.Sprintf("/run/user/%d", e.conf.Uid),
 71	}
 72
 73	extraBinds := []string{}
 74	for _, extern := range e.conf.RoBind {
 75		intern := extern
 76		if id := strings.Index(extern, ":"); id != -1 {
 77			intern = extern[id+1:]
 78			extern = extern[:id]
 79		}
 80		extraBinds = append(extraBinds, "--ro-bind", extern, intern)
 81	}
 82	for _, extern := range e.conf.Bind {
 83		intern := extern
 84		if id := strings.Index(extern, ":"); id != -1 {
 85			intern = extern[id+1:]
 86			extern = extern[:id]
 87		}
 88		extraBinds = append(extraBinds, "--bind", extern, intern)
 89	}
 90	args = append(args, extraBinds...)
 91
 92	args = append(args, "sh", "-c", toBeRun)
 93
 94	subCmd := pluginLib.Exec{Cmds: []pluginLib.Cmd{{Cmd: "bwrap", Args: args}}}
 95	return e.executor.exec(dir, subCmd)
 96}
 97
 98func (e *Bwrap) prepareFiles(dir string) error {
 99	passwdCmd := exec.Command("getent", "passwd", e.conf.User, fmt.Sprintf("%d", e.conf.Uid))
100	passwdOutput, err := passwdCmd.Output()
101	if err != nil {
102		return oops.Wrapf(err, "can't getent passwd")
103	}
104	if err := os.MkdirAll(filepath.Join(dir, "etc"), os.ModePerm); err != nil {
105		return oops.Wrapf(err, "can't mkdirall prepareFiles")
106	}
107	err = os.WriteFile(filepath.Join(dir, "etc", "passwd"), passwdOutput, os.ModePerm)
108	if err != nil {
109		return oops.Wrapf(err, "can't write passwd file")
110	}
111
112	groupCmd := exec.Command("getent", "group", fmt.Sprintf("%d", e.conf.Gid))
113	groupOutput, err := groupCmd.Output()
114	if err != nil {
115		return oops.Wrapf(err, "can't getent group")
116	}
117	err = os.WriteFile(filepath.Join(dir, "etc", "group"), groupOutput, os.ModePerm)
118	if err != nil {
119		return oops.Wrapf(err, "can't write passwd file")
120	}
121
122	return nil
123}