From 4d0fa399a5a6a69f78d1dc18758982d95028f0b2 Mon Sep 17 00:00:00 2001 From: Brett Dietsch Date: Thu, 14 Jan 2021 12:58:29 -0500 Subject: [PATCH] first commit --- .gitignore | 2 + LICENSE | 21 ++++++++ Makefile | 20 +++++++ README.md | 46 ++++++++++++++++ cmd/main.go | 55 +++++++++++++++++++ go.mod | 9 ++++ go.sum | 12 +++++ pkg/configurator/rebootprompt.go | 32 +++++++++++ pkg/configurator/set-environment.go | 72 +++++++++++++++++++++++++ pkg/configurator/ssh.go | 83 +++++++++++++++++++++++++++++ pkg/configurator/uploadcloud.go | 75 ++++++++++++++++++++++++++ 11 files changed, 427 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/configurator/rebootprompt.go create mode 100644 pkg/configurator/set-environment.go create mode 100644 pkg/configurator/ssh.go create mode 100644 pkg/configurator/uploadcloud.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..050a322 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +release +vc \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7bb191d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Digital Dream Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..97910a3 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: build release + +COMMIT := $(shell git rev-parse --short HEAD) + +build: + CGO_ENABLED=0 go build \ + -ldflags "-w -s -extldflags "-static"" \ + -trimpath \ + -o vc cmd/main.go + +release: + mkdir -p release + GOOS=windows GOARCH=amd64 make build + mv vc release/vc-win-amd64-$(COMMIT).exe + GOOS=darwin GOARCH=amd64 make build + mv vc release/vc-darwin-amd64-$(COMMIT) + GOOS=linux GOARCH=amd64 make build + mv vc release/vc-linux-amd64-$(COMMIT) + GOOS=linux GOARCH=arm64 make build + mv vc release/vc-linux-arm64-$(COMMIT) diff --git a/README.md b/README.md new file mode 100644 index 0000000..b6e154d --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Vector configurator + + + +Vector configurator is a command-line utility for doing some common operatioins on an [OSKR](https://oskr.ddl.io/) enabled bot. + + + +# Usage/Features + + + ## set-environment +This allows you to easily change the environment your Vector is pointed to. + +An example command would be... +``` +$ vc set-environment -e escapepod -h 10.0.2.42 -k ~/.ssh/vector.key +``` + +### Arguments +| flag | description| notes | +|--|--|--| +| -e | environment | `escapepod `and `production` are the supported environments| +| -h | hostname or IP of your robot | | +| -k | The location of the SSH key for your robot | | + +## upload-cloud-binaries +This allows you to easily upload the cloud binaries built from the [vector-cloud](https://github.com/digital-dream-labs/vector-cloud) repository + +An example command would be... +``` +$ vc upload-cloud-binaries -b ~/vector-cloud/build/ -h 10.0.2.42 -k ~/.ssh/vector.key +``` + +### Arguments +| flag | description| notes | +|--|--|--| +| -b | binary directory | The directory containing the vic-cloud and vic-gateway files | +| -h | hostname or IP of your robot | | +| -k | The location of the SSH key for your robot | | + +# Building + +``` +$ make build +``` diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..4ff0691 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "flag" + "fmt" + "os" + "vector-configurator/pkg/configurator" +) + +func main() { + + // set environment arguments + setenv := flag.NewFlagSet("set-environment", flag.ExitOnError) + shost := setenv.String("h", "", "the ip of the vector robot") + skey := setenv.String("k", "", "the location of the ssh key") + senv := setenv.String("e", "", "environment") + + cloudbin := flag.NewFlagSet("upload-cloud-binaries", flag.ExitOnError) + chost := cloudbin.String("h", "", "the ip of the vector robot") + ckey := cloudbin.String("k", "", "the location of the ssh key") + cbindir := cloudbin.String("b", "", "path to vic-cloud and vic-gateway binaries") + + flag.Parse() + + if len(os.Args) < 2 { + fmt.Println(`vector configurator + +This tool will allow you to do the following things: +set-environment - change the environment that + your bot is pointed at +upload-cloud-binaries - upload and set permissions + for the vector-cloud binaries +`) + os.Exit(0) + } + + switch os.Args[1] { + case "set-environment": + _ = setenv.Parse(os.Args[2:]) + if *senv == "" || *shost == "" || *skey == "" { + setenv.Usage() + os.Exit(1) + } + configurator.SetEnvironment(*shost, *skey, *senv) + case "upload-cloud-binaries": + _ = cloudbin.Parse(os.Args[2:]) + if *cbindir == "" || *chost == "" || *ckey == "" { + cloudbin.Usage() + os.Exit(1) + } + configurator.UploadCloud(*chost, *ckey, *cbindir) + + } + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fd48dd3 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module vector-configurator + +go 1.15 + +require ( + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..899260d --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef h1:7D6Nm4D6f0ci9yttWaKjM1TMAXrH5Su72dojqYGntFY= +github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef/go.mod h1:WLFStEdnJXpjK8kd4qKLwQKX/1vrDzp5BcDyiZJBHJM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/configurator/rebootprompt.go b/pkg/configurator/rebootprompt.go new file mode 100644 index 0000000..29cbeb7 --- /dev/null +++ b/pkg/configurator/rebootprompt.go @@ -0,0 +1,32 @@ +package configurator + +import ( + "bufio" + "fmt" + "os" + "strings" + + "golang.org/x/crypto/ssh" +) + +func rebootPrompt(c *ssh.Client) { + reader := bufio.NewReader(os.Stdin) + fmt.Println("configuration complete. Would you like to reboot your robot? (y/n)") + + for { + fmt.Print("-> ") + r, _ := reader.ReadString('\n') + r = strings.ReplaceAll(r, "\n", "") + fmt.Println(r) + if r == "y" { + if err := reboot(c); err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(0) + } else { + os.Exit(0) + } + } + +} diff --git a/pkg/configurator/set-environment.go b/pkg/configurator/set-environment.go new file mode 100644 index 0000000..8a190ab --- /dev/null +++ b/pkg/configurator/set-environment.go @@ -0,0 +1,72 @@ +package configurator + +import ( + "fmt" + "os" +) + +const ( + escapepod = ` +{ + "jdocs": "escapepod.local:8084", + "tms": "escapepod.local:8084", + "chipper": "escapepod.local:8084", + "check": "escapepod.local/ok", + "logfiles": "s3://anki-device-logs-prod/victor", + "appkey": "oDoa0quieSeir6goowai7f" +}` + prod = ` +{ + "jdocs": "jdocs.api.anki.com:443", + "tms": "token.api.anki.com:443", + "chipper": "chipper.api.anki.com:443", + "check": "conncheck.global.anki-services.com/ok", + "logfiles": "s3://anki-device-logs-prod/victor", + "appkey": "oDoa0quieSeir6goowai7f" +}` + + cfpath = "/anki/data/assets/cozmo_resources/config/server_config.json" +) + +// SetEnvironment updates a vectors environment +func SetEnvironment(host, key, env string) { + + var cf string + + switch env { + case "escapepod": + cf = escapepod + case "production": + cf = prod + default: + fmt.Println(`valid environments are: + escapepod + production`) + os.Exit(1) + } + + c, err := getSSHConn(key, host) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := runCmds( + c, + []string{ + "mount -o remount rw /", + fmt.Sprintf("cp %s %s.bak", cfpath, cfpath), + fmt.Sprintf(`cat > %s << EOF +%s +EOF`, + cfpath, + cf, + ), + }, + ); err != nil { + fmt.Println(err) + os.Exit(1) + } + + rebootPrompt(c) +} diff --git a/pkg/configurator/ssh.go b/pkg/configurator/ssh.go new file mode 100644 index 0000000..f1430a5 --- /dev/null +++ b/pkg/configurator/ssh.go @@ -0,0 +1,83 @@ +package configurator + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/tmc/scp" + "golang.org/x/crypto/ssh" +) + +// getSSHConn connects to the robot and returns an ssh client +func getSSHConn(key, host string) (*ssh.Client, error) { + b, err := ioutil.ReadFile(filepath.Clean(key)) + if err != nil { + return nil, err + } + + k, err := ssh.ParsePrivateKey(b) + if err != nil { + return nil, err + } + + return ssh.Dial( + "tcp", + fmt.Sprintf("%s:22", host), + &ssh.ClientConfig{ + User: "root", + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(k), + }, + // nolint: gosec -- I have no idea how this would be handled on windows, so I expect an issue + // or PR about this at some point -bd + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }, + ) +} + +func runCmds(c *ssh.Client, cmds []string) error { + for i := range cmds { + s, err := c.NewSession() + if err != nil { + return err + } + defer func() { + _ = s.Close() + }() + + if err := s.Run(cmds[i]); err != nil { + return fmt.Errorf( + "error running command %s: %v", + cmds[i], + err, + ) + } + + } + return nil +} + +func reboot(c *ssh.Client) error { + s, err := c.NewSession() + if err != nil { + return err + } + defer func() { + _ = s.Close() + }() + + return s.Run("reboot") +} + +func scpFile(c *ssh.Client, file, dest string) error { + s, err := c.NewSession() + if err != nil { + return err + } + defer func() { + _ = s.Close() + }() + + return scp.CopyPath(file, dest, s) +} diff --git a/pkg/configurator/uploadcloud.go b/pkg/configurator/uploadcloud.go new file mode 100644 index 0000000..94521dc --- /dev/null +++ b/pkg/configurator/uploadcloud.go @@ -0,0 +1,75 @@ +package configurator + +import ( + "fmt" + "log" + "os" +) + +const ( + ankiPath = "/anki/bin" + rootHome = "/home/root" + backupPath = "/home/root/backups" + + cloudBin = "vic-cloud" + cloudOwnership = "cloud:anki" + cloudPerms = "755" + + gwBin = "vic-gateway" + gwOwnership = "net:anki" + gwPerms = "755" +) + +// UploadCloud copies vic-cloud and vic-gateway to the robot + sets permissions, etc +func UploadCloud(host, key, path string) { + + c, err := getSSHConn(key, host) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := runCmds(c, []string{"mount -o remount rw /"}); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // copy vic-cloud + if err := scpFile( + c, + fmt.Sprintf("%s/%s", path, cloudBin), + fmt.Sprintf("%s/%s", rootHome, cloudBin), + ); err != nil { + log.Fatal(err) + } + + // copy vic-gateway + if err := scpFile( + c, + fmt.Sprintf("%s/%s", path, gwBin), + fmt.Sprintf("%s/%s", rootHome, gwBin), + ); err != nil { + log.Fatal(err) + } + + if err := runCmds( + c, + []string{ + fmt.Sprintf("mkdir -p %s", backupPath), + fmt.Sprintf("cp -pf %s/%s %s", ankiPath, cloudBin, backupPath), + fmt.Sprintf("cp -pf %s/%s %s", ankiPath, gwBin, backupPath), + fmt.Sprintf("mv %s/%s %s/%s", rootHome, cloudBin, ankiPath, cloudBin), + fmt.Sprintf("mv %s/%s %s/%s", rootHome, gwBin, ankiPath, gwBin), + fmt.Sprintf("chown %s %s/%s", cloudOwnership, ankiPath, cloudBin), + fmt.Sprintf("chown %s %s/%s", gwOwnership, ankiPath, gwBin), + fmt.Sprintf("chmod %s %s/%s", cloudPerms, ankiPath, cloudBin), + fmt.Sprintf("chmod %s %s/%s", gwPerms, ankiPath, gwBin), + }, + ); err != nil { + fmt.Println(err) + os.Exit(1) + } + + rebootPrompt(c) + +}