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}