123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- package main
- import (
- "fmt"
- "strings"
- "strconv"
- "sync"
- "syscall"
- "os"
- "os/exec"
- "path/filepath"
- )
- func scpDirectoryDoesNotExist(config *Configuration, create_directory bool) bool {
- check_scp_directory := exec.Command(
- "ssh",
- // specify port
- "-p", strconv.Itoa(config.ssh_port),
- // specify identity file
- "-i", config.ssh_identity_path,
- // specify user@host
- fmt.Sprintf("%s@%s", config.ssh_user, config.url),
- // specify remote command
- fmt.Sprintf("test -e %s", config.scp_directory),
- )
- // start command
- err := check_scp_directory.Start()
- if err != nil {
- fmt.Fprintf(os.Stderr, "fatal error: ssh command could not be started\n")
- // this error is fatal (partly also because basically everything makes use of ssh)
- os.Exit(EXIT_SSH_ERROR)
- }
- // use wait to synchronize with command and get exit code (as err)
- does_not_exist := check_scp_directory.Wait() != nil
- if create_directory && does_not_exist {
- fmt.Printf("scp target directory %s does not exist, will be created\n", config.scp_directory)
- create_scp_directory := exec.Command(
- "ssh",
- // specify port
- "-p", strconv.Itoa(config.ssh_port),
- // specify identity file
- "-i", config.ssh_identity_path,
- // specify user@host
- fmt.Sprintf("%s@%s", config.ssh_user, config.url),
- // specify remote command
- fmt.Sprintf("mkdir -p %s", config.scp_directory),
- )
- err = create_scp_directory.Run()
- if err != nil {
- fmt.Fprintf(os.Stderr, "scp target directory %s could not be created\n", config.scp_directory)
- os.Exit(EXIT_SCP_ERROR)
- }
- }
- // signal if scp target directory does not exists
- return does_not_exist
- }
- func list(config *Configuration, files []string, placeholder bool) {
- if scpDirectoryDoesNotExist(config, false) {
- fmt.Fprintf(os.Stderr, "fatal error: remote directory %s does not exist\n", config.scp_directory)
- return
- }
- // get parameters
- target := fmt.Sprintf(config.url_fmt, config.url)
- // define commands
- //curl -s "${url}" | grep href | sed -e 's/<[^<]*>//g'
- cmd_curl := exec.Command("curl", "-s", target)
- cmd_grep := exec.Command("grep", "href")
- cmd_sed := exec.Command("sed", "-e", "s/<[^<]*>//g")
- // pipe curl into grep
- pipe_curl_to_grep, err := cmd_curl.StdoutPipe()
- if err != nil {
- fmt.Fprintf(os.Stderr, "fatal error: construction of pipe failed\n")
- // this error is fatal
- return
- }
- // make sure to close pipe at the end (if it was opened)
- defer pipe_curl_to_grep.Close()
- // finalize pipe
- cmd_grep.Stdin = pipe_curl_to_grep
- // pipe grep into sed
- pipe_grep_to_sed, err := cmd_grep.StdoutPipe()
- if err != nil {
- fmt.Fprintf(os.Stderr, "fatal error: construction of pipe failed\n")
- return
- }
- // make sure to close pipe at the end (if it was opened)
- defer pipe_grep_to_sed.Close()
- // finalize pipe
- cmd_sed.Stdin = pipe_grep_to_sed
- // let both initial commands do their work
- cmd_curl.Start()
- cmd_grep.Start()
- // finally query stdout/stderr of last command
- stdoutstderr, err := cmd_sed.CombinedOutput()
- if err != nil {
- fmt.Fprintf(os.Stderr, "error: getting of output by `sed` failed\n")
- }
- // give it to our user
- fmt.Printf("%s", stdoutstderr)
- }
- func pull(config *Configuration, filenames []string, save bool) {
- // require at least one file
- if len(filenames) <= 0 {
- fmt.Printf("pull command requires at least one file\n")
- return
- }
- // NOTE: this function does allow duplicate filenames, which, quite magically, still works
- // check existence of directory on remote host
- if scpDirectoryDoesNotExist(config, false) {
- fmt.Fprintf(os.Stderr, "fatal error: remote directory %s does not exist\n", config.scp_directory)
- return
- }
- // do this now so it is already done once we get to the files
- url := fmt.Sprintf(config.url_fmt, config.url)
- // set up saving of files (instead of stdout output)
- single_file := len(filenames) <= 1
- save_files := save || !single_file
- // configure wait group for synchronization
- wg := sync.WaitGroup{}
- wg.Add(len(filenames))
- // iterate all filenames for pull action
- for _, f := range filenames {
- // skip problemativ filenames
- if strings.Contains(f, "/") {
- fmt.Printf("directories are not supported at the time\n")
- // finish task pre-emptively
- wg.Done()
- continue
- }
- // use goroutines because tasks are independent
- go func(wg *sync.WaitGroup, f string) {
- // finish task at the end
- defer wg.Done()
- // because this is a naive reimplementation I'm not going to use the actual libraries here
- // OUTDATED
- //curl -s "${url}${1}" | base64 -d | openssl enc -d -aes-256-cbc -k "${secret}" > "${file}"
- // NEW COMMAND
- //curl -s "${url}${1}" | openssl enc -d -aes-256-cbc -k "${secret}" --base64 > "${file}"
- cmd_curl := exec.Command("curl", "-s", fmt.Sprintf("%s%s", url, f))
- cmd_openssl := exec.Command("openssl", "enc", "-d", "-aes-256-cbc", "-base64", "-k", config.secret)
- pipe_curl_to_openssl, err := cmd_curl.StdoutPipe()
- // pipe curl into grep
- if err != nil {
- fmt.Fprintf(os.Stderr, "fatal error: construction of pipe failed\n")
- // this error is fatal
- return
- }
- // make sure to close pipe at the end (if it was opened)
- defer pipe_curl_to_openssl.Close()
- // finalize pipe
- cmd_openssl.Stdin = pipe_curl_to_openssl
- // set up command output
- cmd_openssl.Stdout = os.Stdout
- if save_files {
- // create file in preparation to execute command
- file, err := os.Create(f)
- defer file.Close()
- // handle error if no such file exists exists on remote host
- if err != nil {
- fmt.Fprintf(os.Stderr, "cannot create file %s: %s\n", f, err.Error())
- }
- // assign stdout to newly created file
- cmd_openssl.Stdout = file
- }
- // let both initial commands do their work
- cmd_curl.Start()
- // finally let the actual command do it's work
- cmd_openssl.Run()
- // finally query stdout/stderr of last command
- if err != nil {
- fmt.Fprintf(os.Stderr, "file %s could not be pulled\n", f)
- return
- } else {
- if save_files {
- fmt.Printf("successfully pulled %s\n", f)
- }
- }
- }(&wg, f)
- }
- // synchronize (program has to wait for goroutines otherwise output will be lost)
- wg.Wait()
- }
- func push(config *Configuration, filenames []string, placeholder bool) {
- // require at least one file
- if len(filenames) <= 0 {
- fmt.Printf("pull command requires at least one file\n")
- return
- }
- // check for existence of directory on remote host and create if necessary
- scpDirectoryDoesNotExist(config, true)
- // configure wait group for synchronization
- wg := sync.WaitGroup{}
- wg.Add(len(filenames))
- // iterate all filenames for pull action
- for _, f := range filenames {
- // skip problemativ filenames
- if strings.Contains(f, "/") {
- fmt.Printf("directories are not supported at the time\n")
- // finish task pre-emptively
- wg.Done()
- continue
- }
- // use goroutines because tasks are independent
- go func(wg *sync.WaitGroup, f string) {
- // finish task at the end
- defer wg.Done()
- // because this is a naive reimplementation I'm not going to use the actual libraries here
- // this one is more complicated as it's composed of two commands
- // OUTDATED
- //openssl enc -aes-256-cbc -k "${secret}" < "${1}" | base64 > "${tmp}"
- //scp "${port}" -i "${identity}" "${tmp}" "${user}"@"${webserver}":~/dropzone/"${file}"
- // NEW COMMAND
- //openssl enc -aes-256-cbc -base64 -k "${secret}" -in "${1}" -out "${tmp}"
- //scp "${port}" -i "${identity}" "${tmp}" "${user}"@"${webserver}":~/dropzone/"${file}"
- // create temporary file to put encrypted contents in and later push
- tmp, err := os.CreateTemp("/tmp", "strlst.*")
- if err != nil {
- fmt.Fprintf(os.Stderr, "error creating temporary file while trying to push %s\n", f)
- return
- }
- // make sure clean up happens afterwards
- defer os.Remove(tmp.Name())
- // define openssl command
- cmd_openssl := exec.Command(
- // openssl command with parameters
- "openssl", "enc", "-aes-256-cbc", "-base64",
- // specify encryption secret
- "-k", config.secret,
- // specify input file
- "-in", f,
- // specify output file
- "-out", tmp.Name(),
- )
- // start openssl command
- err = cmd_openssl.Start()
- if err != nil {
- fmt.Fprintf(os.Stderr, "error starting openssl command while trying to push %s\n", f)
- return
- }
- // wait and get exit code
- err = cmd_openssl.Wait()
- if err != nil {
- fmt.Fprintf(os.Stderr, "error executing openssl command while trying to push %s: %v\n", f, err)
- return
- }
- // define scp command
- cmd_scp := exec.Command(
- "scp",
- // specify port
- "-P", strconv.Itoa(config.ssh_port),
- // specify identity file
- "-i", config.ssh_identity_path,
- // temporary file that was created earlier
- tmp.Name(),
- // specify user, url and destination location
- fmt.Sprintf("%s@%s:%s", config.ssh_user, config.url, filepath.Join(config.scp_directory, f)),
- )
- // start scp command
- err = cmd_scp.Start()
- if err != nil {
- fmt.Fprintf(os.Stderr, "error starting scp command while trying to push %s\n", f)
- return
- }
- // wait and get exit code
- err = cmd_scp.Wait()
- if err != nil {
- fmt.Fprintf(os.Stderr, "error executing scp command while trying to push %s: %v\n", f, err)
- return
- } else {
- fmt.Printf("successfully pushed %s\n", f)
- }
- }(&wg, f)
- }
- // synchronize (program has to wait for goroutines otherwise output will be lost)
- wg.Wait()
- // at last, commit changes
- commit(config, filenames, placeholder)
- }
- func commit(config *Configuration, filenames []string, placeholder bool) {
- // try to make changes stick (in a git history)
- commit_changes := exec.Command(
- "ssh",
- // specify port
- "-p", strconv.Itoa(config.ssh_port),
- // specify identity file
- "-i", config.ssh_identity_path,
- // specify user@host
- fmt.Sprintf("%s@%s", config.ssh_user, config.url),
- // specify remote command
- "/home/strlst/rpi-server-config/commit-updates",
- )
- // start command and report error (if there is one)
- err := commit_changes.Start()
- if err != nil {
- fmt.Fprintf(os.Stderr, "error starting ssh command: %v\n", err)
- return
- }
- // then call wait to extract exit code (which encodes important information in this case)
- err = commit_changes.Wait()
- if err != nil {
- // big ugly nested ifs to extract an exit code, the go way (tm)
- if exit_err, ok := err.(*exec.ExitError); ok {
- if status, ok := exit_err.Sys().(syscall.WaitStatus); ok {
- if status.ExitStatus() == 9 {
- fmt.Printf("nothing to commit\n")
- return
- }
- }
- }
- // ugly unconditional execution path due to nested ifs
- fmt.Fprintf(os.Stderr, "error finalizing changes by committing updates: %v\n", err)
- return
- } else {
- fmt.Printf("committed changes\n")
- }
- }
- // technically the map could be modified at runtime, making for exciting possibilities
- type fn func (*Configuration, []string, bool)
- var COMMANDS map[string]fn = map[string]fn {
- "list": list,
- "pull": pull,
- "push": push,
- "commit": commit,
- }
- func IsImplemented(command string) bool {
- if _, ok := COMMANDS[command]; ok {
- return true;
- } else {
- return false;
- }
- }
- func Exec(command string, config *Configuration, filenames []string, save bool) {
- COMMANDS[command](config, filenames, save)
- }
|