Welcome, zoa-file!
Jes Olson j3s@c3f.net
Tue, 27 Sep 2022 18:38:03 -0500
M
README
→
README
@@ -105,9 +105,9 @@ design guideline: all configuration data comes from a git repository.
the nodes themselves keep no state at all. to run zoa: - zoa https://git.j3s.sh/config main - ^ ^ ^ - zoa, duh. git repo git ref (branch or tag) + zoa https://git.j3s.sh/j3s/config main + ^ ^ ^ + zoa, duh. git repo git ref (branch or tag) zoa will clone the repo+branch specified in your config (or attempt to fetch it, if it's already cloned) to /var/lib/zoa/<repo>/<branch>
M
main.go
→
main.go
@@ -4,7 +4,9 @@ import (
"bufio" "bytes" "context" + "crypto/sha256" "fmt" + "io" "log" "os" "path/filepath"@@ -20,6 +22,14 @@ var ctx = context.Background()
var rootDir = "test/" func main() { + // if you run "zoa", i assume your PWD has a zoa entrypoint in it + // if you run "zoa <repo> <branch>", i assume + + if os.Geteuid() != 0 { + fmt.Println("you are running zoa as a non-root user. this is not advised") + fmt.Println("but you do you") + } + // TODO: this writer is responsible for the random stdout // maybe save the stdout for debug mode somehow r, err := interp.New(interp.StdIO(nil, os.Stdout, os.Stderr))@@ -32,7 +42,7 @@ r.Env, err = generateEnv()
if err != nil { log.Fatal(err) } - fmt.Printf("%+v", r.Env) + // for debuggin' fmt.Printf("%+v", r.Env) entrypoint := filepath.Join(rootDir, "main") runCommands(entrypoint, r)@@ -171,6 +181,7 @@ // certain strings
for _, stmt := range script.Stmts { cmdName := commandName(stmt) command, after, _ := strings.Cut(cmdName, " ") + if command == "zoa-script" { // recursion detected!! :3 subScriptPath := filepath.Join(rootDir + "scripts/" + after)@@ -178,24 +189,128 @@ runCommands(subScriptPath, r)
continue } + if command == "zoa-file" { + // after = "nginx /etc/nginx/conf.d/jesse.conf systemctl nginx reload" + zoaFileParts := strings.Split(after, " ") + if len(zoaFileParts) < 2 { + log.Fatal("zoa-file requires 2+ arguments") + } + src := zoaFileParts[0] + dst := zoaFileParts[1] + optionalCmd := "" + if len(zoaFileParts) > 2 { + optionalCmd = strings.Join(zoaFileParts[2:], " ") + } + fmt.Printf("$ zoa-file %s %s\n", src, dst) + dstChanged, err := zoaCopy(src, dst) + if err != nil { + log.Fatal(err) + } + // if there's an optional argument + if optionalCmd != "" && dstChanged { + re := strings.NewReader(optionalCmd) + f, err := syntax.NewParser().Parse(re, "") + if err != nil { + log.Fatal(err) + } + + for _, stmt := range f.Stmts { + runCommand(ctx, stmt, r) + } + } + return + } + // if the script name changed between runs, // print it if scriptPath != lastScriptPath { - bluePrintln(" " + scriptPath) + bluePrintln("-> " + scriptPath) lastScriptPath = scriptPath } - fmt.Printf(" $ %s\n", cmdName) + fmt.Printf("$ %s\n", cmdName) err = r.Run(ctx, stmt) if err != nil { + // ignore err here bc it's just the status code os.Exit(1) } } } +// zoaCopy copies a file from zoaRoot/scripts/src to dst. +// if the dst was changed, zoaCopy will return true, +// otherwise it will return false +// zoaCopy defaults to 0666 for permissions (before umask) +func zoaCopy(src string, dst string) (bool, error) { + src = filepath.Join(rootDir + "scripts/" + src) + srcChk, err := checksumFile(src) + if err != nil { + // source file should always exist, return error + return false, err + } + dstChk, err := checksumFile(dst) + if err != nil { + // dstfile may not exist for a million + // reasons, set checksum to blank + // to force a mismatch + dstChk = "" + } + + if srcChk == dstChk { + fmt.Println("file unchanged") + return false, nil + } + // TODO: pass the file through a templating engine + // - expand vars + // - loop support? + + err = Copy(src, dst) + if err != nil { + return false, err + } + return true, nil +} + +// Copy just copiez files, it's only used by zoaCopy rn +func Copy(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() +} + +func checksumFile(file string) (string, error) { + f, err := os.Open(file) + if err != nil { + return "", err + } + defer f.Close() + + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + + checksum := fmt.Sprintf("%x", h.Sum(nil)) + return checksum, nil +} + func runCommand(c context.Context, s *syntax.Stmt, r *interp.Runner) { name := commandName(s) - fmt.Printf(" -> %s\n", name) + fmt.Printf("$ %s\n", name) err := r.Run(c, s) if err != nil { os.Exit(1)