A virtual jailed shell environment for Go apps backed by an io/fs#FS.
1package python3
2
3import (
4 "context"
5 _ "embed"
6 "errors"
7
8 "github.com/tetratelabs/wazero"
9 "github.com/tetratelabs/wazero/experimental/sysfs"
10 "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
11 wsys "github.com/tetratelabs/wazero/sys"
12 "mvdan.cc/sh/v3/interp"
13 "tangled.org/xeiaso.net/kefka/command"
14 "tangled.org/xeiaso.net/kefka/wasm/billyfs"
15)
16
17//go:embed python.wasm
18var pyWASM []byte
19
20var (
21 runtime wazero.Runtime
22 compiled wazero.CompiledModule
23)
24
25func init() {
26 ctx := context.Background()
27 runtime = wazero.NewRuntime(ctx)
28 wasi_snapshot_preview1.MustInstantiate(ctx, runtime)
29
30 var err error
31 compiled, err = runtime.CompileModule(ctx, pyWASM)
32 if err != nil {
33 panic(err)
34 }
35}
36
37type Impl struct{}
38
39func (Impl) Exec(ctx context.Context, ec *command.ExecContext, args []string) error {
40 // Bare `python3` would otherwise slurp stdin to EOF before executing
41 // (the non-TTY default), which hangs the REPL under sophia. Force
42 // interactive mode so it eval-prints line by line. -S skips site.py,
43 // which under -i would inject cwd ('/') into sys.path and try to
44 // import readline by listdir-walking '/' — that path fails on s3fs
45 // and brings down the REPL before the first prompt.
46 if len(args) == 0 {
47 args = []string{"-i", "-S"}
48 }
49
50 fsConfig := wazero.NewFSConfig().(sysfs.FSConfig).
51 WithSysFSMount(billyfs.New(ec.FS), "/")
52
53 config := wazero.NewModuleConfig().
54 WithStdin(ec.Stdin).
55 WithStdout(ec.Stdout).
56 WithStderr(ec.Stderr).
57 WithArgs(append([]string{"python3"}, args...)...).
58 WithName("python3").
59 WithEnv("PYTHONUNBUFFERED", "1").
60 WithFSConfig(fsConfig).
61 WithSysNanosleep().
62 WithSysNanotime().
63 WithSysWalltime()
64
65 mod, err := runtime.InstantiateModule(ctx, compiled, config)
66 if err != nil {
67 if exitErr, ok := errors.AsType[*wsys.ExitError](err); ok {
68 if code := exitErr.ExitCode(); code != 0 {
69 return interp.ExitStatus(uint8(code))
70 }
71 return nil
72 }
73 return err
74 }
75 return mod.Close(ctx)
76}