small pixel drawing of a pufferfish zoa

Implement initial set of env vars
Jes Olson j3s@c3f.net
Tue, 27 Sep 2022 17:14:16 -0500
commit

51341263a06f20cb67556408208f2c22624468f6

parent

fb0a048223c1176b3d30d06d40157501ac5eb91b

5 files changed, 200 insertions(+), 13 deletions(-)

jump to
M READMEREADME

@@ -137,12 +137,44 @@

ENVIRONMENT VARIABLES before zoa runs, it sets a few standard environment variables for your usage. - here are _all of them_ on my dev machine: - OS="Linux" - DISTRO="arch" - NODENAME="nostromo.j3s.sh" - # design guideline: everything in zoa uses base units. MEM_TOTAL is in bytes. - MEM_TOTAL="16165548000" + here are _all of them_. + + displayed values are from my dev system + caveats are marked with * + + $PATH - the search path for binaries. this is hardcoded. + PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + + $ARCH - the name of the hardware type on which the system is running + ARCH=x86_64 + + $HOSTNAME - the name of this node. + HOSTNAME=nostromo.j3s.sh + * there is no shortname vs fqdn standard, so this env var may vary + by distro + + $OS - the operating system name + OS=Linux + * on BSD systems, this var is a lot more useful + * if you want your distro, take a look at OS_RELEASE_ID + + $RELEASE - the current release level of the operating system implementation + RELEASE=5.19.5-arch1-1 + + ! WARNING: ALL $OS_RELEASE_* VARIABLES ARE UNRELIABLE ! + be sure to check for their existence before + using them. ty~ :3 + + $OS_RELEASE_ID - short uncapitalized name of your distro + OS_RELEASE_ID=arch + * see above warning + + $OS_RELEASE_VERSION_ID - version of your distro, if applicable + OS_RELEASE_VERSION_ID= # note that arch has no version ID + * see above warning + + TODO: expose hardware info, cpu cores, ip address, memory availability, etc + HELPER FUNCTIONS
M main.gomain.go

@@ -1,6 +1,7 @@

package main import ( + "bufio" "bytes" "context" "fmt"

@@ -8,7 +9,9 @@ "log"

"os" "path/filepath" "strings" + "syscall" + "mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/syntax" )

@@ -23,11 +26,132 @@ r, err := interp.New(interp.StdIO(nil, os.Stdout, os.Stderr))

if err != nil { log.Fatal(err) } + + // set standard env vars for runtime + r.Env, err = generateEnv() + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", r.Env) entrypoint := filepath.Join(rootDir, "main") runCommands(entrypoint, r) } +func generateEnv() (expand.Environ, error) { + // syscall.Uname _should_ be supported on all *nix systems and is backed + // by posix + var env expand.Environ + uname := syscall.Utsname{} + err := syscall.Uname(&uname) + if err != nil { + return env, err + } + + // shell := "/bin/sh"? + + // $PATH is annoyingly non-standard, so we hardcode the var + // to the binary paths described in the fhs standard. + // in most cases, this will "just work" for people, but special + // cases should be evaluated - this may need some adjustment in the future. + // i am resistant to making it over-rideable. + // + // users can always call their special binaries with their full paths + // if they are resistant to moving them for some reason. + path := envString("PATH", "/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin") + uname_os := envString("OS", charsToString(uname.Sysname[:])) + uname_release := envString("RELEASE", charsToString(uname.Release[:])) + uname_arch := envString("ARCH", charsToString(uname.Machine[:])) + if err != nil { + return env, err + } + + // standards are extremely annoying about hostnames. + // + // "Note that there is no standard that + // says that the hostname set by sethostname(2) + // is the same string as the nodename field of + // the struct returned by uname() (indeed, some + // systems allow a 256-byte hostname and an 8-byte + // nodename), but this is true on Linux. The same + // holds for setdomainname(2) and the domainname field." + // + // in practice, there's usually not a difference between HOSTNAME + // and NODENAME, so i've chosen to only expose HOSTNAME for the + // sake of simplicity. i'm using the Golang implementation, which + // does call out to uname, annoyingly. + // + // if this becomes an issue, i'll revisit it. i doubt it though. + // tldr: i'm ignoring that golang's os.Hostname() implementation + // isn't standards-compliant by the letter of the law. + // in actual practice, the hostname and nodename + // are always identical in every case i've observed. + // + // and i'm exposing only 1 because otherwise things get annoying. + // shrug. + h, err := os.Hostname() + if err != nil { + return env, err + } + uname_hostname := fmt.Sprintf("HOSTNAME=%s", h) + + // !OS_RELEASE_* VARS ARE NOT STANDARDS-BACKED! + // OS_* vars may or may not exist depending on the distro in question, so + // they're not reliable _at all_. they're scraped from /etc/os-release + // and are useful for identifying specific Linux distros, or their versions. + // + // if you rely on these variables, I highly suggest checking for their + // existence with test -z before utilizing them. there be no standards here. + os_release, err := getOSRelease() + if err != nil { + return env, err + } + osReleaseID := envString("OS_RELEASE_ID", os_release.ID) + osReleaseVersionID := envString("OS_RELEASE_VERSION_ID", os_release.VersionID) + + env = expand.ListEnviron(path, // normie shit + uname_os, uname_hostname, uname_release, uname_arch, // uname-derivated env vars + osReleaseID, osReleaseVersionID) // /etc/os-release + return env, nil +} + +func envString(key string, value string) string { + return fmt.Sprintf("%s=%s", key, value) +} + +// I want to keep this list as small as possible, since +// this struct is unreliable. +// design principle: only 1 way to do common things +type OSRelease struct { + ID string // distro name - "arch" + VersionID string // for debian distros, this is set to "22.04" +} + +// getOsRelease parses /etc/os-release data +// into a struct +func getOSRelease() (OSRelease, error) { + var osr = OSRelease{} + f, err := os.Open("/etc/os-release") + if err != nil { + return osr, err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + key, value, _ := strings.Cut(scanner.Text(), "=") + value = strings.Trim(value, `"`) + switch key { + case "ID": + osr.ID = value + case "VERSION_ID": + osr.VersionID = value + } + } + + return osr, nil +} + // this is used to detect when the script // name changes from run to run, which allows // us to prettily-print

@@ -36,7 +160,9 @@

func runCommands(scriptPath string, r *interp.Runner) { script, err := parseFile(scriptPath) if err != nil { - log.Fatal(err) + fmt.Println("error in " + scriptPath) + fmt.Println(err) + os.Exit(1) } // execute every statement individually, decorating

@@ -55,11 +181,11 @@

// if the script name changed between runs, // print it if scriptPath != lastScriptPath { - fmt.Println("\t" + scriptPath) + bluePrintln(" " + scriptPath) lastScriptPath = scriptPath } - fmt.Printf("\t$ %s\n", cmdName) + fmt.Printf(" $ %s\n", cmdName) err = r.Run(ctx, stmt) if err != nil { os.Exit(1)

@@ -88,10 +214,16 @@ f, err := os.Open(filename)

if err != nil { return result, err } + defer f.Close() result, err = syntax.NewParser().Parse(f, "") return result, err } +func bluePrintln(s string) { + colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 34, s) + fmt.Println(colored) +} + // runStatements takes a file & runs individual // commands from that file, prepending the decorator // and returning the first error

@@ -99,3 +231,14 @@ // func runScript(file *syntax.File) error {

// fmt.Printf("%s%s\n", decorator, output) // return nil // } + +func charsToString(arr []int8) string { + b := make([]byte, 0, len(arr)) + for _, v := range arr { + if v == 0x00 { + break + } + b = append(b, byte(v)) + } + return string(b) +}
A test/files/motd

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

+hewo u ;3
M test/maintest/main

@@ -1,11 +1,21 @@

+# tests function inheretence +println() { + printf "%s.\n" "$1" +} + # hewo ls - PASSTHROUGH=bananas # fix perms uname -a +# tests waiting for output +sleep 2; echo hi + +# check all env vars +env + # test invalid code and print it # ls /asfj

@@ -24,9 +34,9 @@ echo $HOSTNAME

zoa-script 3-more -# copy a file from files/ -# zoa-file main.go /tmp/garbagecan.go - cat >/tmp/trashcan <<EOF hello i am a trash can man :3 EOF + +zoa-file test/main /tmp/zoa-file-test +ls /tmp/zoa-file-test
M test/scripts/3-moretest/scripts/3-more

@@ -1,1 +1,2 @@

uname +println "hewo i am a kitty"