small pixel drawing of a pufferfish zoa

basically rewrite zoa completely yet again
Jes Olson j3s@c3f.net
Sun, 09 Oct 2022 00:52:33 -0500
commit

a6df88cbc1d24c4b518de86e912a14614f2cdde3

parent

f59ea99f941a39e2a6aac4a1da7ebffcf03ce9b1

6 files changed, 185 insertions(+), 249 deletions(-)

jump to
M color/color.gocolor/color.go

@@ -26,3 +26,11 @@

func GreenPrintln(s string) { colorPrintln(s, green) } + +func ZoaSay(s string) { + BluePrintln("(✿◠‿◠) " + s) +} + +func ZoaYell(s string) { + RedPrintln("(✿ఠ‿ఠ) " + s) +}
D git/git.go

@@ -1,56 +0,0 @@

-package git - -import ( - "os" - "strings" - - "j3s.sh/zoa/color" - - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" -) - -const ( - clonePath = "/var/lib/zoa/repo" -) - -func GitMode(command string) bool { - // TODO: support file-based git remotes - // all current remote git url types: - // ssh://[user@]host.xz[:port]/path/to/repo.git/ - // git://host.xz[:port]/path/to/repo.git/ - // http[s]://host.xz[:port]/path/to/repo.git/ - // ftp[s]://host.xz[:port]/path/to/repo.git/ - // [user@]host.xz:path/to/repo.git/ - if strings.Contains(command, "://") { - // this assumption totally won't bite my ass later - return true - } - - // scp-mode - if strings.Contains(command, "@") { - return true - } - - // todo: rewrite this and make it less shit lmao - return false -} - -func Clone(repo string, branch string) error { - // for now, take the uber naive blow + clone - // technique - os.RemoveAll(clonePath) - // Tempdir to clone the repository - cloneopts := &git.CloneOptions{ - URL: repo, - } - msg := "-> cloning " + repo - if branch != "" { - msg += "@" + branch - cloneopts.ReferenceName = plumbing.ReferenceName(branch) - } - color.BluePrintln(msg) - _, err := git.PlainClone(clonePath, false, cloneopts) - - return err -}
M main.gomain.go

@@ -5,124 +5,128 @@ "fmt"

"log" "os" "os/exec" - "path/filepath" - "j3s.sh/zoa/git" + "j3s.sh/zoa/env" "j3s.sh/zoa/shell" - "j3s.sh/zoa/utils" + + "mvdan.cc/sh/v3/interp" ) func main() { - // git vars - var branch string - // if os.Geteuid() != 0 { - // fmt.Println("! you are running zoa as a non-root user. not ideal tbh. !") - // } - if len(os.Args) < 2 { - printUsage() - } - if len(os.Args) > 2 { - branch = os.Args[2] + if len(os.Args) <= 1 { + printUsageExit() } command := os.Args[1] - if command == "-h" || command == "--help" || command == "help" { - printUsage() + switch command { + case "deploy": + zoaDeployShitty() + case "run": + zoaRun() + case "version": + fmt.Println("0.1.0") + os.Exit(0) + default: + printUsageExit() } +} - if command == "deploy" { - if len(os.Args) < 3 { - fmt.Println("please supply one or more servers to deploy to") - os.Exit(1) - } - hosts := os.Args[2:] +func zoaRun() { + if len(os.Args) <= 2 { + fmt.Println("run requires 1 argument: the path to a posix shell script") + os.Exit(1) + } + path := os.Args[2] - zoaPath, err := exec.LookPath("zoa") - if err != nil { - log.Fatal("(TODO) please install zoa to /usr/local/bin and ensure it's in your PATH") - } + envs, err := env.GenerateEnv() + if err != nil { + log.Fatal(err) + } - // TODO: replace remote binary if newer - // err = shell.SSH("command -v zoa", hosts) - // if err != nil { - // Install Zoa - err = shell.SCP(zoaPath, hosts) - if err != nil { - log.Fatal(err) - } - err = shell.SSH("chmod +x zoa", hosts) - if err != nil { - log.Fatal(err) - } - err = shell.SSH("mv zoa /usr/local/bin/", hosts) - if err != nil { - subErr := shell.SSH("sudo mv zoa /usr/local/bin/", hosts) - if subErr != nil { - log.Fatal(err) - } - } + r, err := interp.New( + // TODO: make quiet flag silence stdout + interp.StdIO(nil, os.Stdout, os.Stdout), + interp.CallHandler(shell.CallHandler), + interp.Env(envs), + ) + if err != nil { + log.Fatal(err) + } - err = shell.SCP("main", hosts) - if err != nil { - log.Fatal(err) - } - err = shell.SCPDir("scripts", hosts) - if err != nil { - log.Fatal(err) - } - err = shell.SCPDir("files", hosts) - if err != nil { - log.Fatal(err) - } - err = shell.SSH("sudo zoa .", hosts) - if err != nil { - log.Fatal(err) - } - os.Exit(0) + session := &shell.Session{ + ScriptPath: path, + Runner: r, + SubShell: false, } - gitMode := git.GitMode(command) + // todo: err? + session.Run() +} - if gitMode { - utils.SetZoaRoot("/var/lib/zoa/repo") - err := os.MkdirAll(utils.ZoaRoot, 0755) - if err != nil { +// ignore this for now, i'll make it better i promise 🥺 +func zoaDeployShitty() { + if len(os.Args) <= 2 { + fmt.Println("deploy requires 1+ arguments: hostnames to deploy + run zoa on") + os.Exit(1) + } + hosts := os.Args[2:] + + zoaPath, err := exec.LookPath("zoa") + if err != nil { + log.Fatal("(TODO) please install zoa to /usr/local/bin and ensure it's in your PATH") + } + + // TODO: replace remote binary if newer + // err = shell.SSH("command -v zoa", hosts) + // if err != nil { + // Install Zoa + err = shell.SCP(zoaPath, hosts) + if err != nil { + log.Fatal(err) + } + err = shell.SSH("chmod +x zoa", hosts) + if err != nil { + log.Fatal(err) + } + err = shell.SSH("mv zoa /usr/local/bin/", hosts) + if err != nil { + subErr := shell.SSH("sudo mv zoa /usr/local/bin/", hosts) + if subErr != nil { log.Fatal(err) } - // check the path & branch, make sure it's correct - // then clone - git.Clone(command, branch) - } else { - utils.SetZoaRoot(command) } - // TODO: this writer is responsible for the random stdout - // maybe save the stdout for debug mode somehow - - main := filepath.Join(utils.ZoaRoot, "main") - shell.RunScript(main) + err = shell.SCP("main", hosts) + if err != nil { + log.Fatal(err) + } + err = shell.SCPDir("scripts", hosts) + if err != nil { + log.Fatal(err) + } + err = shell.SCPDir("files", hosts) + if err != nil { + log.Fatal(err) + } + err = shell.SSH("sudo zoa .", hosts) + if err != nil { + log.Fatal(err) + } + os.Exit(0) } -func printUsage() { +func printUsageExit() { // TODO: -v -h - fmt.Println(`usage: zoa <path> || zoa deploy <remote host...> + fmt.Println(`usage: zoa <command> [arguments] -sorry, this shit is a work in progress - -details: - zoa <path> will execute the code in the given zoa dir - - if the path is a git repo, zoa will pull it - - if the path is a dir, zoa will execute the code there - - zoa deploy <remote host...> will ssh to the given server and: - - install zoa to /usr/local/bin/ - - copy the local zoa repo to the host - - execute zoa on the remote host +commands: + deploy <hostname/ip> - install zoa on the given host & execute + run <script> - execute the given zoa script + version - print zoa version examples: - zoa ~/code/my-zoa-repo - zoa . - zoa https://git.sr.ht/~j3s/testy - zoa deploy j3s.sh git.j3s.sh`) + zoa run config-my-shit-pwease + zoa run /var/lib/my-zoa-repo/main + zoa deploy . web.j3s.sh git.j3s.sh`) os.Exit(1) }
M shell/shell.goshell/shell.go

@@ -10,7 +10,6 @@ "path/filepath"

"strings" "j3s.sh/zoa/color" - "j3s.sh/zoa/env" "j3s.sh/zoa/utils" "mvdan.cc/sh/v3/interp"

@@ -22,109 +21,100 @@ // name changes from run to run, which allows

// us to prettily-print // var lastScriptPath string -var r *interp.Runner +// r represents our global runner - we use a global here +// because we're very bad at coding, and globals are ez +// 😎 +// var r *interp.Runner + +// we use this to detect the working dir of +// the primary script the user called +// var zoaRoot string -func init() { - var err error - envs, err := env.GenerateEnv() +type Session struct { + Runner *interp.Runner + ScriptPath string + SubShell bool +} + +func (s *Session) Run() { + color.BluePrintln("./" + filepath.Base(s.ScriptPath)) + ctx := context.TODO() + ctx = context.WithValue(ctx, "script", s.ScriptPath) + f, err := parseFile(s.ScriptPath) if err != nil { log.Fatal(err) } - - r, err = interp.New( - // TODO: make debug flag enable stdout - interp.StdIO(nil, os.Stdout, os.Stdout), - interp.CallHandler(CallHandler), - interp.Env(envs), - ) - if err != nil { - log.Fatal(err) + r := s.Runner + if s.SubShell { + r = s.Runner.Subshell() + } + for _, stmt := range f.Stmts { + // 14:8 + pos := stmt.Pos() + ctx = context.WithValue(ctx, "position", pos) + err = r.Run(ctx, stmt) + if err != nil { + // we yell but keep plowing forward + // twirling, twirling + color.ZoaYell(fmt.Sprintf("%s:%s: %s", s.ScriptPath, pos, err.Error())) + } } } func CallHandler(ctx context.Context, args []string) ([]string, error) { - // hc := interp.HandlerCtx(ctx) + var script string + script = ctx.Value("script").(string) + command := args[0] fmt.Println() - if args[0] == "zoa-script" { - subScript := filepath.Join(utils.ZoaRoot, "scripts", args[1]) - // TODO: figure out how to get scripts to echo their names - // when we resume their execution - // fmt.Printf("$ %s\n", strings.Join(args, " ")) - color.BluePrintln("$ zoa-script " + args[1]) - runScriptInSubshell(ctx, subScript) - - // args has to have something in it - true is a pretty safe bet - args = []string{"true", "true"} + // "shell tip" commands should go here + if command == "echo" { + color.ZoaSay(`hewo! please prefer printf over echo where possible :3 +example: printf '%s\n' "hello world!"`) } + fmt.Printf("%s $ %s\n", script, strings.Join(args, " ")) - if args[0] == "zoa-file" { - // fmt.Fprintln(hc.Stdout, "") - // return nil - // args = "zoa-file nginx /etc/nginx/conf.d/jesse.conf systemctl nginx reload" + switch command { + case "zoa-script": + if len(args) <= 1 { + // log.Fatal here? + return args, fmt.Errorf("zoa-script requires 1 argument: the script to run") + } + // session.Run() + // subScript := filepath.Join("scripts", args[1]) + // err := RunScript(subScript) + // if err != nil { + // return args, err + // } + // args has to have _something_ in it or we panic at calltime + return []string{"true"}, nil + case "zoa-file": if len(args) <= 2 { - log.Fatal("zoa-file requires 2+ arguments") + // log.Fatal here? + return args, fmt.Errorf("zoa-file requires 2+ arguments: zoa-file <source> <destination> <optional command>") } - src := args[1] + src := filepath.Join(filepath.Dir(string(script)), "files", args[1]) dst := args[2] - optionalCmd := []string{} - if len(args) >= 4 { - optionalCmd = args[3:] + // todo: remove? orrrr + // fmt.Printf("$ zoa-file %s %s\n", src, dst) + var cmd []string + if len(args) >= 3 { + cmd = args[3:] } - fmt.Printf("$ zoa-file %s %s\n", src, dst) - filePath := filepath.Join(utils.ZoaRoot, "files", src) - dstChanged, err := zoaCopy(filePath, dst) + dstChanged, err := zoaCopy(src, dst) if err != nil { log.Fatal(err) } - if len(optionalCmd) >= 1 && dstChanged { - args = optionalCmd + // if the destination changes and we have + // a command specified, we def want to run it + if len(cmd) > 0 && dstChanged { + args = cmd } else { - args = []string{"true", "true"} + return []string{"true"}, nil } } - // this should really just say "if zoa-file or zoa-script ran" - if len(args) == 2 && args[0] == "true" { - // no printy - } else { - fmt.Printf("$ %s\n", strings.Join(args, " ")) - } - return args, nil - - // return interp.DefaultCallHandler(2*time.Second)(ctx, args) -} - -func runScriptInSubshell(ctx context.Context, script string) error { - r := r.Subshell() - f, err := parseFile(script) - if err != nil { - return err - } - for _, stmt := range f.Stmts { - err = r.Run(context.TODO(), stmt) - if err != nil { - color.RedPrintln(err.Error()) - } - } - return nil -} - -func RunScript(s string) { - color.BluePrintln("(✿◠‿◠) zoa") - script, err := parseFile(s) - if err != nil { - fmt.Printf("error in %s: %s\n", s, err) - os.Exit(1) - } - for _, stmt := range script.Stmts { - err = r.Run(context.TODO(), stmt) - if err != nil { - color.RedPrintln(err.Error()) - } - } - // err = r.Run(context.TODO(), script) } func parseFile(filename string) (*syntax.File, error) {

@@ -157,7 +147,7 @@ dstChk = ""

} if srcChk == dstChk { - fmt.Println(dst + " unchanged") + fmt.Println(dst + " was unchanged") return false, nil } // TODO: pass the file through a templating engine

@@ -170,6 +160,8 @@ return false, err

} return true, nil } + +// the code down under is dragons and shit, it's awful, ignore pls func SSH(command string, hostlist []string) error { _, err := exec.LookPath("ssh")
M test/maintest/main

@@ -4,30 +4,23 @@ printf "%s\n" "$1"

} # hewo -ls +uname -a PASSTHROUGH=bananas - -# fix perms -uname -a # tests waiting for output -sleep 2; echo hi +sleep 2; printf '%s\n' hi # check all env vars env -# test invalid code and print it +# ensure comments are ignored # ls /asfj -# test another script inclusion -# todo - zoa-script 2-call-me-from-anotha -# . ./test/2-call-me-from-anotha -ls -lat echo i am main +# test if statements if [ "$(uname -n)" = "nostromo" ]; then echo NOSTROMO fi

@@ -39,7 +32,7 @@ cat >/tmp/trashcan <<EOF

hello i am a trash can man :3 EOF -echo $PASSTHROUGH +printf "testing passthrough vars: %s\n" $PASSTHROUGH zoa-file demo /tmp/zoa-file-test echo $PASSTHROUGH ls /tmp/zoa-file-test

@@ -48,6 +41,7 @@ if printf "%s\n" "testing if statement followed by custom helper"; then

zoa-script another-thing fi -ls /asdfnoexist +# should pass +ls /asdfnoexist || true ls /
M utils/utils.goutils/utils.go

@@ -7,12 +7,6 @@ "io"

"os" ) -var ZoaRoot string - -func SetZoaRoot(root string) { - ZoaRoot = root -} - // Copy just copiez files, it's only used by zoaCopy rn func Copy(src, dst string) error { in, err := os.Open(src)