From 2960a05ed3808e4f996f79aac855c6f23ec10bc4 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Sat, 9 Mar 2024 17:45:14 -0600 Subject: [PATCH] move weirder example code here Signed-off-by: Jeff Carr --- shell/example1/Makefile | 11 +++ shell/example1/main.go | 43 ++++++++ shell/example2/Makefile | 3 + shell/example2/main.go | 11 +++ shell/linux.go | 27 +++++ shell/run.go | 211 ++++++++++++++++++++++++++++++++++++++++ shell/ssh.go | 157 ++++++++++++++++++++++++++++++ 7 files changed, 463 insertions(+) create mode 100644 shell/example1/Makefile create mode 100644 shell/example1/main.go create mode 100644 shell/example2/Makefile create mode 100644 shell/example2/main.go create mode 100644 shell/linux.go create mode 100644 shell/run.go create mode 100644 shell/ssh.go diff --git a/shell/example1/Makefile b/shell/example1/Makefile new file mode 100644 index 0000000..443064f --- /dev/null +++ b/shell/example1/Makefile @@ -0,0 +1,11 @@ +all: + # go build + GO111MODULE="off" go run main.go + +goimports: + goimports -w *.go + +redomod: + rm -f go.* + GO111MODULE= go mod init + GO111MODULE= go mod tidy diff --git a/shell/example1/main.go b/shell/example1/main.go new file mode 100644 index 0000000..f9998a5 --- /dev/null +++ b/shell/example1/main.go @@ -0,0 +1,43 @@ +package main + +/* +import "log" +import "reflect" +*/ + +import "os" + +// import "github.com/davecgh/go-spew/spew" + +import "go.wit.com/lib/gui/shell" + +func main() { + shell.Run("ls /tmp") + + shell.Run("ping -c 3 localhost") + + // slow down the polling to every 2 seconds + shell.SetDelayInMsec(2000) + + shell.Run("ping -c 4 localhost") + + // capture ping output into a file + fout, _ := os.Create("/tmp/example1.ping.stdout") + ferr, _ := os.Create("/tmp/example1.ping.stderr") + shell.SetStdout(fout) + shell.SetStderr(ferr) + + shell.Run("ping -c 5 localhost") + + // turn out process exit debugging + shell.SpewOn() + + fout, _ = os.Create("/tmp/example1.fail.stdout") + ferr, _ = os.Create("/tmp/example1.fail.stderr") + shell.SetStdout(fout) + shell.SetStderr(ferr) + + // TODO: this might not be working + // check error handling + shell.Run("ls /tmpthisisnothere") +} diff --git a/shell/example2/Makefile b/shell/example2/Makefile new file mode 100644 index 0000000..7b4c2b6 --- /dev/null +++ b/shell/example2/Makefile @@ -0,0 +1,3 @@ +all: + # go build + GO111MODULE="off" go run main.go diff --git a/shell/example2/main.go b/shell/example2/main.go new file mode 100644 index 0000000..2593a2b --- /dev/null +++ b/shell/example2/main.go @@ -0,0 +1,11 @@ +package main + +import "log" +// import "fmt" +import "go.wit.com/lib/gui/shell" + +func main() { + err := shell.Run("cat /etc/issue") + log.Println("cat /etc/issue returned", err) + // fmt.Print(output) +} diff --git a/shell/linux.go b/shell/linux.go new file mode 100644 index 0000000..ee24420 --- /dev/null +++ b/shell/linux.go @@ -0,0 +1,27 @@ +//go:build linux && go1.7 +// +build linux,go1.7 + +// put stuff in here that you only want compiled under linux + +package shell + +import ( + "log" + "os" + "os/signal" + "syscall" + + "github.com/wercker/journalhook" +) + +var sigChan chan os.Signal + +func handleSignal(err interface{}, ret int) { + log.Println("handleSignal() only should be compiled on linux") + sigChan = make(chan os.Signal, 3) + signal.Notify(sigChan, syscall.SIGUSR1) +} + +func UseJournalctl() { + journalhook.Enable() +} diff --git a/shell/run.go b/shell/run.go new file mode 100644 index 0000000..ad16319 --- /dev/null +++ b/shell/run.go @@ -0,0 +1,211 @@ +package shell + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os" + "os/exec" + "strings" + "time" + + "github.com/svent/go-nbreader" + + "go.wit.com/log" +) + +func (cmd *OldShell) Run(cmdline string) string { + cmd.InitProcess(cmdline) + if cmd.Error != nil { + return "" + } + cmd.Exec(cmdline) + return Chomp(cmd.Buffer) +} + +func (cmd *OldShell) InitProcess(cmdline string) { + log.Log(RUN, "shell.InitProcess() START "+cmdline) + + cmd.Cmdline = Chomp(cmdline) // this is like 'chomp' in perl + cmdArgs := strings.Fields(cmd.Cmdline) + if len(cmdArgs) == 0 { + cmd.Error = fmt.Errorf("cmdline == ''") + cmd.Done = true + return + } + if cmdArgs[0] == "cd" { + if len(cmdArgs) > 1 { + log.Log(RUN, "os.Chdir()", cmd) + os.Chdir(cmdArgs[1]) + } + handleError(nil, 0) + cmd.Done = true + return + } + + cmd.Process = exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...) +} + +func (cmd *OldShell) FileCreate(out string) { + var newfile File + + var iof io.ReadCloser + if out == "STDOUT" { + iof, _ = cmd.Process.StdoutPipe() + } else { + iof, _ = cmd.Process.StderrPipe() + } + + newfile.Fio = iof + newfile.Fbufio = bufio.NewReader(iof) + // newfile.Fnbreader = nbreader.NewNBReader(newfile.Fbufio, 1024) + + if out == "STDOUT" { + cmd.STDOUT = &newfile + } else { + cmd.STDERR = &newfile + } +} + +// NOTE: this might cause problems: +// always remove the newlines at the end ? +func (cmd *OldShell) Exec(cmdline string) { + log.Log(RUN, "shell.Run() START "+cmdline) + + cmd.InitProcess(cmdline) + if cmd.Error != nil { + return + } + + cmd.FileCreate("STDOUT") + cmd.FileCreate("STDERR") + + cmd.Process.Start() + + // TODO; 'goroutine' both of these + // and make your own wait that will make sure + // the process is then done and run process.Wait() + go cmd.Capture(cmd.STDERR) + cmd.Capture(cmd.STDOUT) + + // wait until the process exists + // https://golang.org/pkg/os/exec/#Cmd.Wait + // What should happen here, before calling Wait() + // is checks to make sure the READERS() on STDOUT and STDERR are done + err := cmd.Process.Wait() + + // time.Sleep(2 * time.Second) // putting this here doesn't help STDOUT flush() + + if err != nil { + cmd.Fail = true + cmd.Error = err + log.Log(RUN, "process.Wait() END err =", err.Error()) + } else { + log.Log(RUN, "process.Wait() END") + } + return +} + +// nonblocking read until file errors +func (cmd *OldShell) Capture(f *File) { + log.Log(RUN, "nbrREADER() START") + + if cmd.Buffer == nil { + cmd.Buffer = new(bytes.Buffer) + } + if cmd.Buffer == nil { + f.Dead = false + cmd.Error = fmt.Errorf("could not make buffer") + log.Error(cmd.Error, "f.Buffer == nil") + log.Error(cmd.Error, "SHOULD DIE HERE") + cmd.Done = true + } + + f.Dead = false + + // loop that keeps trying to read from f + for f.Dead == false { + time.Sleep(time.Duration(msecDelay) * time.Millisecond) // only check the buffer 500 times a second + + // set to false so it keeps retrying reads + f.Empty = false + + // tight loop that reads 1024 bytes at a time until buffer is empty + // 1024 is set in f.BufferSize + for f.Empty == false { + f.Empty = cmd.ReadToBuffer(f) + } + } +} + +// returns true if filehandle buffer is empty +func (cmd *OldShell) ReadToBuffer(f *File) bool { + log.Log(RUN, "ReadToBuffer() START") + // nbr := f.Fnbreader + // newfile.Fnbreader = nbreader.NewNBReader(newfile.Fbufio, 1024) + nbr := nbreader.NewNBReader(f.Fbufio, 1024) + oneByte := make([]byte, 1024) + if nbr == nil { + // log.Debugln("ReadToBuffer() ERROR nbr is nil") + f.Dead = true + return true + } + count, err := nbr.Read(oneByte) + f.TotalCount += count + + if err != nil { + // log.Debugln("ReadToBuffer() file has closed with", err) + // log.Debugln("ReadToBuffer() count = ", count, "err = ", err) + f.Dead = true + return true + } + if count == 0 { + // log.Debugln("ReadToBuffer() START count == 0 return true") + return true + } + // log.Debugln("ReadToBuffer() count = ", count) + // tmp := Chomp(oneByte) + // log.Debugln("ReadToBuffer() tmp = ", tmp) + io.WriteString(cmd.Buffer, strings.Trim(string(oneByte), "\x00")) + return false +} + +// pass in two file handles (1 read, 1 write) +func nonBlockingReader(buffReader *bufio.Reader, writeFileHandle *os.File, stdout *bufio.Writer) { + // newreader := bufio.NewReader(readFileHandle) + + // create a nonblocking GO reader + nbr := nbreader.NewNBReader(buffReader, 1024) + + for { + // defer buffReader.Close() + // defer writeFileHandle.Flush() + defer writeFileHandle.Close() + totalCount := 0 + for { + oneByte := make([]byte, 1024) + count, err := nbr.Read(oneByte) + if err != nil { + log.Log(INFO, "count, err =", count, err) + handleError(err, -1) + return + } + totalCount += count + if count == 0 { + time.Sleep(time.Duration(msecDelay) * time.Millisecond) // without this delay this will peg the CPU + if totalCount != 0 { + log.Log(INFO, "STDERR: totalCount = ", totalCount) + totalCount = 0 + } + } else { + log.Log(INFO, "STDERR: count = ", count) + writeFileHandle.Write(oneByte[0:count]) + if quiet == false { + stdout.Write(oneByte[0:count]) + stdout.Flush() + } + } + } + } +} diff --git a/shell/ssh.go b/shell/ssh.go new file mode 100644 index 0000000..67b5c22 --- /dev/null +++ b/shell/ssh.go @@ -0,0 +1,157 @@ +package shell + +import ( + "fmt" + "io/ioutil" + "time" + + "github.com/tmc/scp" + "go.wit.com/log" + "golang.org/x/crypto/ssh" +) + +var sshHostname string +var sshPort int +var sshUsername string +var sshPassword string +var sshKeyfile string + +func SSHclientSet(hostname string, port int, username string, pass string, keyfile string) { + sshHostname = hostname + sshPort = port + sshUsername = username + sshPassword = pass + sshKeyfile = keyfile +} + +func SSHclientSCP(localfile string, remotefile string) { + log.Log(SSH, "shell.SSHclientSCP() START") + log.Log(SSH, "shell.SSHclientSCP() \tlocalfile =", localfile) + log.Log(SSH, "shell.SSHclientSCP() \tremotefile =", remotefile) + sess := mySsh(sshHostname, sshPort, sshUsername, sshPassword, sshKeyfile) + err := scp.CopyPath(localfile, remotefile, sess) + sess.Close() + log.Log(SSH, "shell.SSHclientSCP() \tscp.CopyPath() err =", err) + log.Log(SSH, "shell.SSHclientSCP() END") +} + +func SSHclientRun(cmd string) { + log.Log(SSH, "shell.SSHclientRun() START cmd =", cmd) + sess := mySsh(sshHostname, sshPort, sshUsername, sshPassword, sshKeyfile) + err := sess.Run(cmd) + sess.Close() + log.Log(SSH, "shell.SSHclientRun() END err =", err) +} + +func mySsh(hostname string, port int, username string, pass string, keyfile string) *ssh.Session { + // get host public key + // hostKey := getHostKey(host) + // log.Log(SSH, "hostkey =", hostKey) + + publicKey, err := PublicKeyFile(keyfile) + if err != nil { + log.Log(SSH, "PublicKeyFile() error =", err) + } + + // ssh client config + config := ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(pass), + publicKey, + }, + // allow any host key to be used (non-prod) + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + + // verify host public key + // HostKeyCallback: ssh.FixedHostKey(hostKey), + // optional host key algo list + HostKeyAlgorithms: []string{ + ssh.KeyAlgoRSA, + ssh.KeyAlgoDSA, + ssh.KeyAlgoECDSA256, + ssh.KeyAlgoECDSA384, + ssh.KeyAlgoECDSA521, + ssh.KeyAlgoED25519, + }, + // optional tcp connect timeout + Timeout: 5 * time.Second, + } + + sport := fmt.Sprintf("%d", port) + // connect + client, err := ssh.Dial("tcp", hostname+":"+sport, &config) + if err != nil { + log.Error(err) + } + // defer client.Close() + + // start session + sess, err := client.NewSession() + if err != nil { + log.Error(err) + } + // defer sess.Close() + + return sess +} + +func Scp(sess *ssh.Session, localfile string, remotefile string) { + err := scp.CopyPath(localfile, remotefile, sess) + log.Log(SSH, "scp.CopyPath() err =", err) +} + +func PublicKeyFile(file string) (ssh.AuthMethod, error) { + buffer, err := ioutil.ReadFile(file) + log.Log(SSH, "buffer =", string(buffer)) + if err != nil { + return nil, err + } + + key, err := ssh.ParsePrivateKey(buffer) + if err != nil { + return nil, err + } + return ssh.PublicKeys(key), nil +} + +// THIS doesn't work +/* +func getHostKey(host string) ssh.PublicKey { + // parse OpenSSH known_hosts file + // ssh or use ssh-keyscan to get initial key + file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + var hostKey ssh.PublicKey + for scanner.Scan() { + fields := strings.Split(scanner.Text(), " ") + if len(fields) != 3 { + continue + } + if strings.Contains(fields[0], host) { + var err error + hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes()) + if err != nil { + log.Fatalf("error parsing %q: %v", fields[2], err) + } + break + } + } + + // 9enFJdMhb8eHN/6qfHSU/jww2Mo=|pcsWQCvAyve9QXBhjL+w/LhkcHU= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMQx8BJXxD+vk3wyjy7Irzw4FA6xxJvqUP7Hb+Z+ygpOuidYj9G8x6gHEXFUnABn5YirePrWh5tNsk4Rqs48VwU= + hostKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte("9enFJdMhb8eHN/6qfHSU/jww2Mo=|pcsWQCvAyve9QXBhjL+w/LhkcHU= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMQx8BJXxD+vk3wyjy7Irzw4FA6xxJvqUP7Hb+Z+ygpOuidYj9G8x6gHEXFUnABn5YirePrWh5tNsk4Rqs48VwU=")) + log.Log(SSH, "hostkey err =", err) + log.Log(SSH, "hostkey =", hostKey) + if hostKey == nil { + log.Log(SSH, "no hostkey found err =", err) + log.Fatalf("no hostkey found for %s", host) + } + + return hostKey +} +*/