diff --git a/pkg/backy/backy.go b/pkg/backy/backy.go index 061a2ec..1904284 100644 --- a/pkg/backy/backy.go +++ b/pkg/backy/backy.go @@ -2,12 +2,14 @@ package backy import ( "bytes" + "errors" "fmt" "io" "os" "os/exec" - "git.andrewnw.xyz/CyberShell/backy/pkg/logging" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" ) // Host defines a host to which to connect @@ -16,7 +18,7 @@ type Host struct { ConfigFilePath string Empty bool Host string - HostName []string + HostName string Port uint16 PrivateKeyPath string PrivateKeyPassword string @@ -77,28 +79,24 @@ type BackupConfig struct { * Runs a backup configuration */ -func (command Command) RunCmd() logging.Logging { +func (command Command) RunCmd() { - var stdoutBuf, stderrBuf bytes.Buffer - var err error - var cmdArgs string + var cmdArgsStr string for _, v := range command.CmdArgs { - cmdArgs += v + cmdArgsStr += fmt.Sprintf(" %s", v) } - var remoteHost = &command.RemoteHost - fmt.Printf("\n\nRunning command: " + command.Cmd + " " + cmdArgs + " on host " + command.RemoteHost.Host + "...\n\n") - if command.Remote { + fmt.Printf("\n\nRunning command: " + command.Cmd + " " + cmdArgsStr + " on host " + command.Host + "...\n\n") + if command.Host != "" { - remoteHost.Port = 22 - remoteHost.Host = command.RemoteHost.Host - - sshClient, err := remoteHost.ConnectToSSHHost() + command.RemoteHost.Host = command.Host + command.RemoteHost.Port = 22 + sshc, err := command.RemoteHost.ConnectToSSHHost() if err != nil { panic(fmt.Errorf("ssh dial: %w", err)) } - defer sshClient.Close() - s, err := sshClient.NewSession() + defer sshc.Close() + s, err := sshc.NewSession() if err != nil { panic(fmt.Errorf("new ssh session: %w", err)) } @@ -109,33 +107,55 @@ func (command Command) RunCmd() logging.Logging { cmd += " " + a } + var stdoutBuf, stderrBuf bytes.Buffer s.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) s.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) err = s.Run(cmd) + log.Info().Bytes(fmt.Sprintf("%s stdout", command.Cmd), stdoutBuf.Bytes()).Send() + log.Info().Bytes(fmt.Sprintf("%s stderr", command.Cmd), stderrBuf.Bytes()).Send() + if err != nil { - return logging.Logging{ - Output: stdoutBuf.String(), - Err: fmt.Errorf("error running " + cmd + ": " + stderrBuf.String()), - } + panic(fmt.Errorf("error when running cmd " + cmd + "\n Error: " + err.Error())) } - // fmt.Printf("Output: %s\n", string(output)) } else { // shell := "/bin/bash" + var err error + if command.Shell != "" { + cmdArgsStr = fmt.Sprintf("%s %s", command.Cmd, cmdArgsStr) + localCMD := exec.Command(command.Shell, "-c", cmdArgsStr) + + var stdoutBuf, stderrBuf bytes.Buffer + localCMD.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) + localCMD.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) + err = localCMD.Run() + log.Info().Bytes(fmt.Sprintf("%s stdout", command.Cmd), stdoutBuf.Bytes()).Send() + log.Info().Bytes(fmt.Sprintf("%s stderr", command.Cmd), stderrBuf.Bytes()).Send() + + if err != nil { + panic(fmt.Errorf("error when running cmd: %s: %w", command.Cmd, err)) + } + return + } localCMD := exec.Command(command.Cmd, command.CmdArgs...) + + var stdoutBuf, stderrBuf bytes.Buffer localCMD.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) localCMD.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) err = localCMD.Run() - + log.Info().Bytes(fmt.Sprintf("%s stdout", command.Cmd), stdoutBuf.Bytes()).Send() + log.Info().Bytes(fmt.Sprintf("%s stderr", command.Cmd), stderrBuf.Bytes()).Send() if err != nil { - return logging.Logging{ - Output: stdoutBuf.String(), - Err: fmt.Errorf(stderrBuf.String()), - } + panic(fmt.Errorf("error when running cmd: %s: %w", command.Cmd, err)) } } - return logging.Logging{ - Output: stdoutBuf.String(), - Err: nil, +} + +func (config *BackyConfigFile) RunBackyConfig() { + for _, list := range config.CmdLists { + for _, cmd := range list { + cmdToRun := config.Cmds[cmd] + cmdToRun.RunCmd() + } } } @@ -152,3 +172,110 @@ func NewConfig() *BackyConfigFile { Hosts: make(map[string]Host), } } + +func ReadAndParseConfigFile() *BackyConfigFile { + + backyConfigFile := NewConfig() + + backyViper := viper.New() + backyViper.SetConfigName("backy") // name of config file (without extension) + backyViper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name + backyViper.AddConfigPath(".") // optionally look for config in the working directory + backyViper.AddConfigPath("$HOME/.config/backy") // call multiple times to add many search paths + err := backyViper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("fatal error config file: %w", err)) + } + + commandsMap := backyViper.GetStringMapString("commands") + var cmdNames []string + for k := range commandsMap { + cmdNames = append(cmdNames, k) + } + hostConfigsMap := make(map[string]*viper.Viper) + + for _, cmdName := range cmdNames { + var backupCmdStruct Command + println(cmdName) + subCmd := backyViper.Sub(getNestedConfig("commands", cmdName)) + + hostSet := subCmd.IsSet("host") + host := subCmd.GetString("host") + + cmdSet := subCmd.IsSet("cmd") + cmd := subCmd.GetString("cmd") + cmdArgsSet := subCmd.IsSet("cmdargs") + cmdArgs := subCmd.GetStringSlice("cmdargs") + shellSet := subCmd.IsSet("shell") + shell := subCmd.GetString("shell") + + if hostSet { + println("Host:") + println(host) + backupCmdStruct.Host = host + if backyViper.IsSet(getNestedConfig("hosts", host)) { + hostconfig := backyViper.Sub(getNestedConfig("hosts", host)) + hostConfigsMap[host] = hostconfig + } + } else { + println("Host is not set") + } + if cmdSet { + println("Cmd:") + println(cmd) + backupCmdStruct.Cmd = cmd + } else { + println("Cmd is not set") + } + if shellSet { + println("Shell:") + println(shell) + backupCmdStruct.Shell = shell + } else { + println("Shell is not set") + } + if cmdArgsSet { + println("CmdArgs:") + for _, arg := range cmdArgs { + println(arg) + } + backupCmdStruct.CmdArgs = cmdArgs + } else { + println("CmdArgs are not set") + } + backyConfigFile.Cmds[cmdName] = backupCmdStruct + + } + + cmdListCfg := backyViper.GetStringMapStringSlice("cmd-lists") + var cmdNotFoundSliceErr []error + for cmdListName, cmdList := range cmdListCfg { + println("Cmd list: ", cmdListName) + for _, cmdInList := range cmdList { + println("Command in list: " + cmdInList) + _, cmdNameFound := backyConfigFile.Cmds[cmdInList] + if !backyViper.IsSet(getNestedConfig("commands", cmdInList)) && !cmdNameFound { + cmdNotFoundStr := fmt.Sprintf("command definition %s is not in config file\n", cmdInList) + cmdNotFoundErr := errors.New(cmdNotFoundStr) + cmdNotFoundSliceErr = append(cmdNotFoundSliceErr, cmdNotFoundErr) + } else { + backyConfigFile.CmdLists[cmdListName] = append(backyConfigFile.CmdLists[cmdListName], cmdInList) + } + } + } + for _, err := range cmdNotFoundSliceErr { + if err != nil { + fmt.Println(err.Error()) + } + } + + return backyConfigFile +} + +func getNestedConfig(nestedConfig, key string) string { + return fmt.Sprintf("%s.%s", nestedConfig, key) +} + +func getNestedSSHConfig(key string) string { + return fmt.Sprintf("hosts.%s.config", key) +} diff --git a/pkg/backy/ssh.go b/pkg/backy/ssh.go index 29de4c1..137ecdd 100644 --- a/pkg/backy/ssh.go +++ b/pkg/backy/ssh.go @@ -30,14 +30,14 @@ type SshConfig struct { host string // host name to connect to - hostName []string + hostName string user string } func (config SshConfig) GetSSHConfig() (SshConfig, error) { - hostNames := ssh_config.GetAll(config.host, "HostName") - if hostNames == nil { + hostNames := ssh_config.Get(config.host, "HostName") + if hostNames == "" { return SshConfig{}, errors.New("hostname not found") } config.hostName = hostNames @@ -116,22 +116,24 @@ func (remoteConfig *Host) ConnectToSSHHost() (*ssh.Client, error) { identityFile = filepath.Join(dir, identityFile[2:]) } remoteConfig.PrivateKeyPath = filepath.Join(identityFile) + log.Debug().Str("Private key path", remoteConfig.PrivateKeyPath).Send() } - remoteConfig.HostName, _ = cfg.GetAll(remoteConfig.Host, "HostName") - if remoteConfig.HostName == nil { + remoteConfig.HostName, _ = cfg.Get(remoteConfig.Host, "HostName") + remoteConfig.User, _ = cfg.Get(remoteConfig.Host, "User") + if remoteConfig.HostName == "" { port, _ := cfg.Get(remoteConfig.Host, "Port") if port == "" { port = "22" } - remoteConfig.HostName[0] = remoteConfig.Host + ":" + port + // remoteConfig.HostName[0] = remoteConfig.Host + ":" + port } else { - for index, hostName := range remoteConfig.HostName { - port, _ := cfg.Get(remoteConfig.Host, "Port") - if port == "" { - port = "22" - } - remoteConfig.HostName[index] = hostName + ":" + port + // for index, hostName := range remoteConfig.HostName { + port, _ := cfg.Get(remoteConfig.Host, "Port") + if port == "" { + port = "22" } + remoteConfig.HostName = remoteConfig.HostName + ":" + port + // remoteConfig.HostName[index] = hostName + ":" + port } // TODO: Add value/option to config for host key and add bool to check for host key @@ -151,14 +153,16 @@ func (remoteConfig *Host) ConnectToSSHHost() (*ssh.Client, error) { User: remoteConfig.User, Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, HostKeyCallback: hostKeyCallback, + // HostKeyAlgorithms: []string{ssh.KeyAlgoECDSA256}, } - for _, host := range remoteConfig.HostName { - log.Info().Msgf("Connecting to host %s", host) - sshClient, connectErr = ssh.Dial("tcp", host, sshConfig) - if connectErr != nil { - log.Fatal().Err(connectErr).Str("host", host).Msg("error when connecting to host") - } + // for _, host := range remoteConfig.HostName { + log.Info().Msgf("Connecting to host %s", remoteConfig.HostName) + + sshClient, connectErr = ssh.Dial("tcp", remoteConfig.HostName, sshConfig) + if connectErr != nil { + log.Fatal().Str("host", remoteConfig.HostName).Err(connectErr).Send() } + // } break }