A virtual jailed shell environment for Go apps backed by an io/fs#FS.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: initial commit with cmd/kefka basic rigging

Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso cf09d9b0

+157
+19
LICENSE
··· 1 + Copyright (c) 2026 Xe Iaso <me@xeiaso.net> 2 + 3 + Permission is hereby granted, free of charge, to any person obtaining a copy 4 + of this software and associated documentation files (the "Software"), to deal 5 + in the Software without restriction, including without limitation the rights 6 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 + copies of the Software, and to permit persons to whom the Software is 8 + furnished to do so, subject to the following conditions: 9 + 10 + The above copyright notice and this permission notice shall be included in 11 + all copies or substantial portions of the Software. 12 + 13 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 + THE SOFTWARE.
+102
cmd/kefka/main.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "fmt" 7 + "io" 8 + "os" 9 + "strings" 10 + "time" 11 + 12 + "github.com/spf13/pflag" 13 + "golang.org/x/term" 14 + "mvdan.cc/sh/v3/interp" 15 + "mvdan.cc/sh/v3/syntax" 16 + ) 17 + 18 + var ( 19 + command = pflag.StringP("command", "c", "", "if set, run this command") 20 + timeout = pflag.DurationP("timeout", "T", 5*time.Minute, "the total time a command can run for") 21 + 22 + ErrInteractiveNotImplementedYet = errors.New("kefka: interactive mode not implemented yet") 23 + ) 24 + 25 + func main() { 26 + pflag.Parse() 27 + 28 + ctx, cancel := context.WithTimeout(context.Background(), *timeout) 29 + defer cancel() 30 + 31 + if err := run(ctx); err != nil { 32 + fmt.Fprintln(os.Stderr, "can't run shell:", err) 33 + } 34 + } 35 + 36 + func run(ctx context.Context) error { 37 + sh, err := interp.New(interp.Interactive(true), interp.StdIO(os.Stdin, os.Stdout, os.Stderr)) 38 + if err != nil { 39 + return fmt.Errorf("can't make shell: %w", err) 40 + } 41 + 42 + if *command != "" { 43 + return runReader(ctx, sh, strings.NewReader(*command), "<argument>") 44 + } 45 + 46 + if pflag.NArg() == 1 { 47 + return runFile(ctx, sh, pflag.Arg(0)) 48 + } 49 + 50 + if term.IsTerminal(int(os.Stdin.Fd())) { 51 + return runInteractive(ctx, sh, os.Stdin, os.Stdout, os.Stderr) 52 + } 53 + 54 + return runReader(ctx, sh, os.Stdin, "<stdin>") 55 + } 56 + 57 + func runReader(ctx context.Context, sh *interp.Runner, in io.Reader, name string) error { 58 + prog, err := syntax.NewParser().Parse(in, name) 59 + if err != nil { 60 + return err 61 + } 62 + sh.Reset() 63 + return sh.Run(ctx, prog) 64 + } 65 + 66 + func runFile(ctx context.Context, sh *interp.Runner, fname string) error { 67 + fin, err := os.Open(fname) 68 + if err != nil { 69 + return err 70 + } 71 + defer fin.Close() 72 + return runReader(ctx, sh, fin, fname) 73 + } 74 + 75 + func runInteractive(ctx context.Context, sh *interp.Runner, stdin io.Reader, stdout, stderr io.Writer) error { 76 + parser := syntax.NewParser() 77 + fmt.Fprintf(stdout, "$ ") 78 + for stmts, err := range parser.InteractiveSeq(stdin) { 79 + if err != nil { 80 + return err 81 + } 82 + 83 + if parser.Incomplete() { 84 + fmt.Fprintf(stdout, "> ") 85 + continue 86 + } 87 + 88 + ctx, cancel := context.WithTimeout(context.Background(), *timeout) 89 + defer cancel() 90 + 91 + for _, stmt := range stmts { 92 + err := sh.Run(ctx, stmt) 93 + if sh.Exited() { 94 + return err 95 + } 96 + } 97 + 98 + fmt.Fprintf(stdout, "$ ") 99 + } 100 + 101 + return nil 102 + }
+13
go.mod
··· 1 + module tangled.org/xeiaso.net/kefka 2 + 3 + go 1.26.2 4 + 5 + require ( 6 + github.com/spf13/pflag v1.0.10 7 + mvdan.cc/sh/v3 v3.13.1 8 + ) 9 + 10 + require ( 11 + golang.org/x/sys v0.42.0 // indirect 12 + golang.org/x/term v0.41.0 // indirect 13 + )
+20
go.sum
··· 1 + github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= 2 + github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 3 + github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= 4 + github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 5 + github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 6 + github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 7 + github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 8 + github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 9 + github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 10 + github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 11 + github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 12 + github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 13 + github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= 14 + github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 15 + golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= 16 + golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 17 + golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= 18 + golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= 19 + mvdan.cc/sh/v3 v3.13.1 h1:DP3TfgZhDkT7lerUdnp6PTGKyxxzz6T+cOlY/xEvfWk= 20 + mvdan.cc/sh/v3 v3.13.1/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=
+3
testdata/basic.sh
··· 1 + #!/usr/bin/env bash 2 + 3 + echo "Hello, world!"