Compare commits

..
11 Commits
Author SHA1 Message Date
CyberShell 1d2a3a8306 v0.13.0
ci/woodpecker/push/publish-docs Pipeline failed
ci/woodpecker/tag/gitea Pipeline failed
ci/woodpecker/tag/publish-docs Pipeline failed
2026-08-15 00:35:57 -05:00
CyberShell 045dedef67 v0.13.0
ci/woodpecker/push/go-lint Pipeline failed
2026-08-15 00:34:13 -05:00
CyberShell 60989a3905 fix error hooks and add listing everything
ci/woodpecker/push/go-lint Pipeline failed
2026-08-15 00:30:55 -05:00
CyberShell e789924547 fix error hooks and add listing everything
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2026-08-15 00:16:05 -05:00
CyberShell 62ac2934dc v0.12.1
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
ci/woodpecker/push/go-lint Pipeline failed
2026-03-23 11:30:32 -05:00
CyberShell c078632691 remove shell history boolean
ci/woodpecker/push/publish-docs Pipeline was successful
2026-02-14 18:57:37 -06:00
CyberShell 52e25aad77 v0.12.0
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
2026-02-11 13:18:46 -06:00
CyberShell 9f996f60c6 internal logic handling for cron webserver 2026-02-11 13:18:06 -06:00
CyberShell 4c152f8089 internal logic handling for cron webserver 2026-02-11 13:17:15 -06:00
CyberShell 4d2e4ce533 v0.11.5
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
2026-02-10 12:01:11 -06:00
CyberShell 9e3960ce9f Command.Type: scriptFile no longer requests psudoterminal
ci/woodpecker/push/go-lint Pipeline failed
2026-02-10 12:00:08 -06:00
28 changed files with 415 additions and 281 deletions
+3
View File
@@ -0,0 +1,3 @@
## v0.11.5 - 2026-02-10
### Changed
* Command.Type: scriptFile no longer requests psudoterminal
+3
View File
@@ -0,0 +1,3 @@
## v0.12.0 - 2026-02-11
### Changed
* internal logic handling for cron webserver
+8
View File
@@ -0,0 +1,8 @@
## v0.12.1 - 2026-03-23
### Added
* Command output respects `command.[name].output.toLog` when standard output is enabled
### Changed
* SSH: revert scriptFile to before 0.11.3
* Output: tabs print as 4 spaces instead of '\t'
### Fixed
* Internal: output buffer will be copied to temporary buffer
+5
View File
@@ -0,0 +1,5 @@
## v0.13.0 - 2026-08-15
### Changed
* List subcommands print out everything with no arguments
### Fixed
* Error hooks no longer return after one is executed
+10 -1
View File
@@ -10,5 +10,14 @@
"nikoksr", "nikoksr",
"Strs" "Strs"
], ],
"CodeGPT.apiKey": "CodeGPT Plus Beta" "CodeGPT.apiKey": "CodeGPT Plus Beta",
"yaml.schemas": {
"file:///c%3A/Users/anw12/.vscode/extensions/continue.continue-1.2.14-win32-x64/config-yaml-schema.json": [
".continue/**/*.yaml"
],
"file:///c%3A/Users/anw12/.vscode/extensions/continue.continue-1.2.16-win32-x64/config-yaml-schema.json": [
".continue/**/*.yaml"
],
"file:///c:/Users/anw12/.vscode/extensions/continue.continue-1.2.16-win32-x64/config-yaml-schema.json": "vscode-local:/c%3A/Users/anw12/.continue/config.yaml"
}
} }
+23
View File
@@ -6,6 +6,29 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v0.13.0 - 2026-08-15
### Changed
* List subcommands print out everything with no arguments
### Fixed
* Error hooks no longer return after one is executed
## v0.12.1 - 2026-03-23
### Added
* Command output respects `command.[name].output.toLog` when standard output is enabled
### Changed
* SSH: revert scriptFile to before 0.11.3
* Output: tabs print as 4 spaces instead of '\t'
### Fixed
* Internal: output buffer will be copied to temporary buffer
## v0.12.0 - 2026-02-11
### Changed
* internal logic handling for cron webserver
## v0.11.5 - 2026-02-10
### Changed
* Command.Type: scriptFile no longer requests psudoterminal
## v0.11.4 - 2026-02-01 ## v0.11.4 - 2026-02-01
### Changed ### Changed
* Command.[name].output.file: now appends correctly to the beginning of file in an absolute path * Command.[name].output.file: now appends correctly to the beginning of file in an absolute path
+1 -1
View File
@@ -9,7 +9,7 @@ import (
var ( var (
hostExecCommand = &cobra.Command{ hostExecCommand = &cobra.Command{
Use: "host [--command=command1 --command=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] ", Use: "host [--command=command1 --command=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] ",
Short: "Runs command defined in config file on the hosts in order specified.", Short: "Specify command(s) defined in config file on the host in order specified.",
Long: "Host executes specified commands on the hosts defined in config file.\nUse the --commands or -c flag to choose the commands.", Long: "Host executes specified commands on the hosts defined in config file.\nUse the --commands or -c flag to choose the commands.",
Run: Host, Run: Host,
} }
+42 -28
View File
@@ -5,8 +5,10 @@
package cmd package cmd
import ( import (
"fmt"
"os"
"git.andrewnw.xyz/CyberShell/backy/pkg/backy" "git.andrewnw.xyz/CyberShell/backy/pkg/backy"
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -15,28 +17,26 @@ var (
listCmd = &cobra.Command{ listCmd = &cobra.Command{
Use: "list [command]", Use: "list [command]",
Short: "List commands, lists, or hosts defined in config file.", Short: "List commands, lists, or hosts defined in config file.",
Long: "List commands, lists, or hosts defined in config file", Long: "List commands, lists, or hosts defined in config file. The subcommands take zero or more arguments to print specific commands or lists",
} }
listCmds = &cobra.Command{ listCmds = &cobra.Command{
Use: "cmds [cmd1 cmd2 cmd3...]", Use: "cmds [cmd1 cmd2 cmd3...]",
Short: "List commands defined in config file.", Short: "Prints commands defined in config file.",
Long: "List commands defined in config file", Long: "Prints commands defined in config file. Pass no arguments to print all commands",
Run: ListCommands, Run: ListCommands,
} }
listCmdLists = &cobra.Command{ listCmdLists = &cobra.Command{
Use: "lists [list1 list2 ...]", Use: "lists [list1 list2 ...]",
Short: "List lists defined in config file.", Short: "Prints lists defined in config file.",
Long: "List lists defined in config file", Long: "Prints lists defined in config file. Pass no arguments to print all lists",
Run: ListCommandLists, Run: ListCommandLists,
} }
) )
var listsToList []string
var cmdsToList []string
func init() { func init() {
listCmd.AddCommand(listCmds, listCmdLists) listCmd.AddCommand(listCmds, listCmdLists)
parseS3Config()
} }
@@ -46,13 +46,6 @@ func ListCommands(cmd *cobra.Command, args []string) {
// - cmds // - cmds
// - lists // - lists
// - if none, list all commands // - if none, list all commands
if len(args) > 0 {
cmdsToList = args
} else {
logging.ExitWithMSG("Error: list cmds subcommand needs commands to list", 1, nil)
}
parseS3Config()
opts := backy.NewConfigOptions(configFile, opts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile), backy.SetLogFile(logFile),
@@ -61,21 +54,28 @@ func ListCommands(cmd *cobra.Command, args []string) {
opts.InitConfig() opts.InitConfig()
opts.ParseConfigurationFile() opts.ParseConfigurationFile()
for _, v := range cmdsToList { if len(args) > 0 {
opts.ListCommand(v) for _, v := range args {
opts.ListCommand(v)
}
os.Exit(0)
}
if len(opts.Cmds) == 0 {
fmt.Println("No commands defined in config file")
os.Exit(1)
}
for c := range opts.Cmds {
println()
println()
println("---------------------------------------------------------------------------------")
opts.ListCommand(c)
} }
} }
func ListCommandLists(cmd *cobra.Command, args []string) { func ListCommandLists(cmd *cobra.Command, args []string) {
parseS3Config()
if len(args) > 0 {
listsToList = args
} else {
logging.ExitWithMSG("Error: lists subcommand needs lists", 1, nil)
}
opts := backy.NewConfigOptions(configFile, opts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile), backy.SetLogFile(logFile),
backy.SetHostsConfigFile(hostsConfigFile)) backy.SetHostsConfigFile(hostsConfigFile))
@@ -83,8 +83,22 @@ func ListCommandLists(cmd *cobra.Command, args []string) {
opts.InitConfig() opts.InitConfig()
opts.ParseConfigurationFile() opts.ParseConfigurationFile()
for _, v := range listsToList { if len(args) > 0 {
opts.ListCommandList(v) for _, v := range args {
opts.ListCommandList(v)
}
os.Exit(0)
}
if len(opts.CmdConfigLists) == 0 {
fmt.Println("No command lists defined in config file")
os.Exit(1)
}
for c := range opts.CmdConfigLists {
println()
println()
println("---------------------------------------------------------------------------------")
opts.ListCommandList(c)
} }
} }
+20
View File
@@ -0,0 +1,20 @@
package cmd
import "testing"
// validate checks commands passed in from the command line
/*
*args:
* -t - type of cmd
* -c
*/
func TestUserCmd(t *testing.T) {
/*
*/
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const versionStr = "0.11.4" const versionStr = "0.13.0"
var ( var (
versionCmd = &cobra.Command{ versionCmd = &cobra.Command{
+6 -6
View File
@@ -88,7 +88,7 @@ Usage:
backy exec [command] backy exec [command]
Available Commands: Available Commands:
host Runs command defined in config file on the hosts in order specified. host Specify command(s) defined in config file on the host in order specified.
hosts Runs command defined in config file on the hosts in order specified. hosts Runs command defined in config file on the hosts in order specified.
Flags: Flags:
@@ -153,14 +153,14 @@ Global Flags:
## list ## list
``` ```
List commands, lists, or hosts defined in config file List commands, lists, or hosts defined in config file. The subcommands take zero or more arguments to print specific commands or lists
Usage: Usage:
backy list [command] backy list [command]
Available Commands: Available Commands:
cmds List commands defined in config file. cmds Prints commands defined in config file.
lists List lists defined in config file. lists Prints lists defined in config file.
Flags: Flags:
-h, --help help for list -h, --help help for list
@@ -178,7 +178,7 @@ Use "backy list [command] --help" for more information about a command.
## list cmds ## list cmds
``` ```
List commands defined in config file Prints commands defined in config file. Pass no arguments to print all commands
Usage: Usage:
backy list cmds [cmd1 cmd2 cmd3...] [flags] backy list cmds [cmd1 cmd2 cmd3...] [flags]
@@ -197,7 +197,7 @@ Global Flags:
## list lists ## list lists
``` ```
List lists defined in config file Prints lists defined in config file. Pass no arguments to print all lists
Usage: Usage:
backy list lists [list1 list2 ...] [flags] backy list lists [list1 list2 ...] [flags]
+13 -4
View File
@@ -2,14 +2,23 @@
title: Exec title: Exec
--- ---
The `exec` subcommand can do some things that the configuration file can't do yet. The command `exec host` can execute commands on many hosts. ~~The `exec` subcommand can do some things that the configuration file can't do yet. The command `exec host` can execute commands on many hosts.~~
{{% notice info %}}
For now only the `exec host` sub-command is implemented. More to come.
{{% /notice %}}
{{% notice warning %}}
The config file's `hosts` are overridden by this command. Hooks are not altered. For consistent results, create a local config file with only the hosts you want to use.
{{% /notice %}}
`exec host` takes the following arguments: `exec host` takes the following arguments:
```sh ```sh
-c, --commands strings Accepts space-separated names of commands. -c, --command stringArray Accepts space-separated names of commands. Specify multiple times for multiple commands.
-h, --help help for host -h, --help help for host
-m, --hosts strings Accepts space-separated names of hosts. -m, --hosts stringArray Accepts space-separated names of hosts. Specify multiple times for multiple hosts.
``` ```
The commands have to be defined in the config file. The hosts need to at least be in the ssh_config(5) file. The commands have to be defined in the config file. The hosts need to at least be in the ssh_config(5) file.
+3 -3
View File
@@ -3,7 +3,7 @@ title: List
--- ---
List commands, lists, or hosts defined in config file List commands, lists, or hosts defined in config file. The subcommands take zero or more arguments to print specific commands or lists
Usage: Usage:
``` ```
@@ -11,8 +11,8 @@ Usage:
``` ```
Available Commands: Available Commands:
cmds List commands defined in config file. cmds Prints commands defined in config file.
lists List lists defined in config file. lists Prints lists defined in config file.
Flags: Flags:
``` ```
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
go install .
systemctl --user stop backy
cp ~/go/bin/backy ~/prodConfigs/backups/backy/backy
systemctl --user start backy
+48 -57
View File
@@ -14,7 +14,6 @@ import (
"strings" "strings"
"sync" "sync"
"text/template" "text/template"
"time"
"embed" "embed"
@@ -152,28 +151,7 @@ func (e *LocalCommandExecutor) Run(cmd *Command, opts *ConfigOpts, logger zerolo
// caller is responsible for closing the returned *os.File when non-nil. // caller is responsible for closing the returned *os.File when non-nil.
func makeCmdOutWriters(buf *bytes.Buffer, outputFile string) (io.Writer, *os.File, error) { func makeCmdOutWriters(buf *bytes.Buffer, outputFile string) (io.Writer, *os.File, error) {
writers := io.MultiWriter(buf) writers := io.MultiWriter(buf)
if IsCmdStdOutEnabled() {
console := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC1123}
console.FormatLevel = func(i interface{}) string {
return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
}
console.FormatMessage = func(i any) string {
if i == nil {
return ""
}
return fmt.Sprintf("MSG: %s", i)
}
console.FormatFieldName = func(i interface{}) string {
return fmt.Sprintf("%s: ", i)
}
console.FormatFieldValue = func(i interface{}) string {
return fmt.Sprintf("%s", i)
// return strings.ToUpper(fmt.Sprintf("%s", i))
}
writers = io.MultiWriter(console, writers)
}
if outputFile != "" { if outputFile != "" {
fileLogger := &lumberjack.Logger{ fileLogger := &lumberjack.Logger{
@@ -202,7 +180,7 @@ func (opts *ConfigOpts) ensureRemoteHost(localCmd *Command, host string) {
} }
} }
// fallback: create a minimal Host so RunCmdOnHost sees a non-nil RemoteHost. // fallback: create a minimal Host so RunCmdOnHost sees a non-nil RemoteHost.
// This uses host as the address/alias; further fields (user/key) will use defaults. // This uses host as the address/alias; further fields (user/key) will use be looked up as needed.
localCmd.RemoteHost = &Host{Host: host} localCmd.RemoteHost = &Host{Host: host}
} }
@@ -227,11 +205,13 @@ func (opts *ConfigOpts) ExecCommandOnHostsParallel(cmdName string) ([]CmdResult,
// shallow copy to avoid races // shallow copy to avoid races
local := *cmdObj local := *cmdObj
local.Host = h local.Host = h
opts.Logger.Debug().Str("host", h).Msg("executing command in parallel on host") opts.Logger.Info().Str("host", h).Msg("executing command in parallel on host")
local.cmdLoggers.global = opts.Logger
var err error var err error
if IsHostLocal(h) { if IsHostLocal(h) {
_, err := local.RunCmd(local.GenerateLogger(opts), opts) local.GenerateLogger(opts)
_, err := local.RunCmd(local.cmdLoggers.cmdContxt, opts)
resultsCh <- CmdResult{CmdName: cmdName, ListName: "", Error: err} resultsCh <- CmdResult{CmdName: cmdName, ListName: "", Error: err}
return return
// _, err = local.RunCmd(local.GenerateLogger(opts), opts) // _, err = local.RunCmd(local.GenerateLogger(opts), opts)
@@ -240,9 +220,10 @@ func (opts *ConfigOpts) ExecCommandOnHostsParallel(cmdName string) ([]CmdResult,
// ensure RemoteHost is populated before calling RunCmdOnHost // ensure RemoteHost is populated before calling RunCmdOnHost
opts.ensureRemoteHost(&local, h) opts.ensureRemoteHost(&local, h)
_, err = local.RunCmdOnHost(local.GenerateLogger(opts), opts) local.GenerateLogger(opts)
_, err = local.RunCmdOnHost(local.cmdLoggers.cmdContxt, opts)
resultsCh <- CmdResult{CmdName: cmdName, ListName: "", Error: err} resultsCh <- CmdResult{CmdName: cmdName, ListName: "", Error: err, HostName: h}
}(host) }(host)
} }
@@ -252,6 +233,9 @@ func (opts *ConfigOpts) ExecCommandOnHostsParallel(cmdName string) ([]CmdResult,
var results []CmdResult var results []CmdResult
for r := range resultsCh { for r := range resultsCh {
results = append(results, r) results = append(results, r)
if r.Error != nil {
opts.Logger.Info().AnErr("error", r.Error).Str("cmd", r.CmdName).Send()
}
} }
return results, nil return results, nil
} }
@@ -282,7 +266,11 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
cmdCtxLogger.Warn().Msg("both 'host' and 'hosts' are set; 'hosts' will be ignored") cmdCtxLogger.Warn().Msg("both 'host' and 'hosts' are set; 'hosts' will be ignored")
return nil, fmt.Errorf("both 'host' and 'hosts' are set; please set one or the other") return nil, fmt.Errorf("both 'host' and 'hosts' are set; please set one or the other")
} else if command.Hosts != nil { } else if command.Hosts != nil {
opts.ExecCommandOnHostsParallel(command.Name) _, err := opts.ExecCommandOnHostsParallel(command.Name)
if err != nil {
opts.Logger.Err(err).Send()
return nil, err
}
return nil, nil return nil, nil
} }
@@ -296,6 +284,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
if command.Type == UserCommandType { if command.Type == UserCommandType {
if command.UserOperation == "password" { if command.UserOperation == "password" {
command.cmdLoggers.global.Info().Str("password", command.UserPassword).Msg("user password to be updated")
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated") cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
} }
} }
@@ -436,7 +425,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
localCMD := exec.Command(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username)) localCMD := exec.Command(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
userHome, err = localCMD.CombinedOutput() userHome, err = localCMD.CombinedOutput()
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
} }
command.UserHome = strings.TrimSpace(string(userHome)) command.UserHome = strings.TrimSpace(string(userHome))
@@ -445,33 +434,33 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
if _, err := os.Stat(userSshDir); os.IsNotExist(err) { if _, err := os.Stat(userSshDir); os.IsNotExist(err) {
err := os.MkdirAll(userSshDir, 0700) err := os.MkdirAll(userSshDir, 0700)
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating directory %s %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error creating directory %s %v", userSshDir, err)
} }
} }
if _, err := os.Stat(fmt.Sprintf("%s/authorized_keys", userSshDir)); os.IsNotExist(err) { if _, err := os.Stat(fmt.Sprintf("%s/authorized_keys", userSshDir)); os.IsNotExist(err) {
_, err := os.Create(fmt.Sprintf("%s/authorized_keys", userSshDir)) _, err := os.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating file %s/authorized_keys: %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error creating file %s/authorized_keys: %v", userSshDir, err)
} }
} }
authorizedKeysFile, err = os.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), 0700, os.ModeAppend) authorizedKeysFile, err = os.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), 0700, os.ModeAppend)
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
} }
defer authorizedKeysFile.Close() defer authorizedKeysFile.Close()
for _, k := range command.UserSshPubKeys { for _, k := range command.UserSshPubKeys {
buf := bytes.NewBufferString(k) buf := bytes.NewBufferString(k)
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key") cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
if _, err := authorizedKeysFile.ReadFrom(buf); err != nil { if _, err := authorizedKeysFile.ReadFrom(buf); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error adding to authorized keys: %v", err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error adding to authorized keys: %v", err)
} }
} }
localCMD = exec.Command(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome)) localCMD = exec.Command(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
_, err = localCMD.CombinedOutput() _, err = localCMD.CombinedOutput()
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), err return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), err
} }
} }
@@ -495,10 +484,10 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
commandExecuted = cmdToRun commandExecuted = cmdToRun
currentCmd := cmdToRun.Name currentCmd := cmdToRun.Name
fieldsMap["cmd"] = currentCmd fieldsMap["cmd"] = currentCmd
cmdLogger = cmdToRun.GenerateLogger(opts) cmdToRun.GenerateLogger(opts)
cmdLogger.Info().Fields(fieldsMap).Send() cmdToRun.cmdLoggers.cmdContxt.Info().Fields(fieldsMap).Send()
outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts) outputArr, runErr := cmdToRun.RunCmd(cmdToRun.cmdLoggers.cmdContxt, opts)
cmdsRan = append(cmdsRan, cmd) cmdsRan = append(cmdsRan, cmd)
if runErr != nil { if runErr != nil {
@@ -559,10 +548,10 @@ func cmdListWorkerWithHosts(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts
commandExecuted = cmdToRun commandExecuted = cmdToRun
currentCmd := cmdToRun.Name currentCmd := cmdToRun.Name
fieldsMap["cmd"] = currentCmd fieldsMap["cmd"] = currentCmd
cmdLogger = cmdToRun.GenerateLogger(opts) cmdToRun.GenerateLogger(opts)
cmdLogger.Info().Fields(fieldsMap).Send() cmdToRun.cmdLoggers.cmdContxt.Info().Fields(fieldsMap).Send()
outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts) outputArr, runErr := cmdToRun.RunCmd(cmdToRun.cmdLoggers.cmdContxt, opts)
cmdsRan = append(cmdsRan, cmd) cmdsRan = append(cmdsRan, cmd)
if runErr != nil { if runErr != nil {
@@ -705,8 +694,8 @@ func cmdListWorkerExecuteCommandsInParallel(msgTemps *msgTemplates, jobs <-chan
cmdToRun.Host = host.Host cmdToRun.Host = host.Host
cmdToRun.RemoteHost = host cmdToRun.RemoteHost = host
} }
cmdLogger = cmdToRun.GenerateLogger(opts) cmdToRun.GenerateLogger(opts)
cmdLogger.Info().Fields(fieldsMap).Send() cmdToRun.cmdLoggers.cmdContxt.Info().Fields(fieldsMap).Send()
print("Running cmd on: ", host.Host, "\n") print("Running cmd on: ", host.Host, "\n")
go func(cmd string, host *Host) { go func(cmd string, host *Host) {
@@ -714,7 +703,7 @@ func cmdListWorkerExecuteCommandsInParallel(msgTemps *msgTemplates, jobs <-chan
currentCmd := cmdToRun.Name currentCmd := cmdToRun.Name
fieldsMap["cmd"] = currentCmd fieldsMap["cmd"] = currentCmd
outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts) outputArr, runErr := cmdToRun.RunCmd(cmdToRun.cmdLoggers.cmdContxt, opts)
if runErr != nil { if runErr != nil {
cmdLogger.Err(runErr).Send() cmdLogger.Err(runErr).Send()
cmdToRun.ExecuteHooks("error", opts) cmdToRun.ExecuteHooks("error", opts)
@@ -890,8 +879,8 @@ func (opts *ConfigOpts) ExecuteListOnHosts(lists []string, parallel bool) {
func (opts *ConfigOpts) ExecuteCmds() { func (opts *ConfigOpts) ExecuteCmds() {
for _, cmd := range opts.executeCmds { for _, cmd := range opts.executeCmds {
cmdToRun := opts.Cmds[cmd] cmdToRun := opts.Cmds[cmd]
cmdLogger := cmdToRun.GenerateLogger(opts) cmdToRun.GenerateLogger(opts)
_, runErr := cmdToRun.RunCmd(cmdLogger, opts) _, runErr := cmdToRun.RunCmd(cmdToRun.cmdLoggers.cmdContxt, opts)
if runErr != nil { if runErr != nil {
opts.Logger.Err(runErr).Send() opts.Logger.Err(runErr).Send()
cmdToRun.ExecuteHooks("error", opts) cmdToRun.ExecuteHooks("error", opts)
@@ -955,7 +944,6 @@ func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
cmdLogger.Info().Msgf("Running error hook command %s", v) cmdLogger.Info().Msgf("Running error hook command %s", v)
// URGENT: Never returns // URGENT: Never returns
_, _ = errCmd.RunCmd(cmdLogger, opts) _, _ = errCmd.RunCmd(cmdLogger, opts)
return
} }
case "success": case "success":
@@ -979,30 +967,29 @@ func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
} }
} }
func (cmd *Command) GenerateLogger(opts *ConfigOpts) zerolog.Logger { func (cmd *Command) GenerateLogger(opts *ConfigOpts) {
cmdLogger := opts.Logger.With(). cmd.cmdLoggers.cmdContxt = opts.Logger.With().
Str("Backy-cmd", cmd.Name).Str("Host", "local machine"). Str("Backy-cmd", cmd.Name).Str("Host", "local machine").
Logger() Logger()
if !IsHostLocal(cmd.Host) { if !IsHostLocal(cmd.Host) {
cmdLogger = opts.Logger.With(). cmd.cmdLoggers.cmdContxt = opts.Logger.With().
Str("Backy-cmd", cmd.Name).Str("Host", cmd.Host). Str("Backy-cmd", cmd.Name).Str("Host", cmd.Host).
Logger() Logger()
} }
return cmdLogger
} }
func (cmd *Command) GenerateLoggerForCmd(logger zerolog.Logger) zerolog.Logger { func (cmd *Command) GenerateLoggerForCmd(logger zerolog.Logger) zerolog.Logger {
cmdLogger := logger.With(). cmd.cmdLoggers.cmdContxt = logger.With().
Str("Backy-cmd", cmd.Name).Str("Host", "local machine"). Str("Backy-cmd", cmd.Name).Str("Host", "local machine").
Logger() Logger()
if !IsHostLocal(cmd.Host) { if !IsHostLocal(cmd.Host) {
cmdLogger = logger.With(). cmd.cmdLoggers.cmdContxt = logger.With().
Str("Backy-cmd", cmd.Name).Str("Host", cmd.Host). Str("Backy-cmd", cmd.Name).Str("Host", cmd.Host).
Logger() Logger()
} }
return cmdLogger return cmd.cmdLoggers.cmdContxt
} }
func (opts *ConfigOpts) ExecCmdsOnHosts(cmdList []string, hostsList []string) { func (opts *ConfigOpts) ExecCmdsOnHosts(cmdList []string, hostsList []string) {
@@ -1014,7 +1001,8 @@ func (opts *ConfigOpts) ExecCmdsOnHosts(cmdList []string, hostsList []string) {
cmd.RemoteHost = host cmd.RemoteHost = host
cmd.Host = h cmd.Host = h
if IsHostLocal(h) { if IsHostLocal(h) {
_, err := cmd.RunCmd(cmd.GenerateLogger(opts), opts) cmd.GenerateLogger(opts)
_, err := cmd.RunCmd(cmd.cmdLoggers.cmdContxt, opts)
if err != nil { if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send() opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
} }
@@ -1022,7 +1010,8 @@ func (opts *ConfigOpts) ExecCmdsOnHosts(cmdList []string, hostsList []string) {
cmd.Host = host.Host cmd.Host = host.Host
opts.Logger.Info().Str("host", h).Str("cmd", c).Send() opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
_, err := cmd.RunCmdOnHost(cmd.GenerateLogger(opts), opts) cmd.GenerateLogger(opts)
_, err := cmd.RunCmdOnHost(cmd.cmdLoggers.cmdContxt, opts)
if err != nil { if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send() opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
} }
@@ -1041,7 +1030,8 @@ func (opts *ConfigOpts) ExecCmdsOnHostsInParallel(cmdList []string, hostsList []
cmd.RemoteHost = host cmd.RemoteHost = host
cmd.Host = h cmd.Host = h
if IsHostLocal(h) { if IsHostLocal(h) {
_, err := cmd.RunCmd(cmd.GenerateLogger(opts), opts) cmd.GenerateLogger(opts)
_, err := cmd.RunCmd(cmd.cmdLoggers.cmdContxt, opts)
if err != nil { if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send() opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
} }
@@ -1049,7 +1039,8 @@ func (opts *ConfigOpts) ExecCmdsOnHostsInParallel(cmdList []string, hostsList []
cmd.Host = host.Host cmd.Host = host.Host
opts.Logger.Info().Str("host", h).Str("cmd", c).Send() opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
_, err := cmd.RunCmdOnHost(cmd.GenerateLogger(opts), opts) cmd.GenerateLogger(opts)
_, err := cmd.RunCmdOnHost(cmd.cmdLoggers.cmdContxt, opts)
if err != nil { if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send() opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
} }
+3 -1
View File
@@ -73,6 +73,8 @@ func (opts *ConfigOpts) Cron() {
srv := server.NewServer(s, opts.GoCron.Port) srv := server.NewServer(s, opts.GoCron.Port)
// srv := server.NewServer(scheduler, 8080, server.WithTitle("My Custom Scheduler")) // with custom title if you want to customize the title of the UI (optional) // srv := server.NewServer(scheduler, 8080, server.WithTitle("My Custom Scheduler")) // with custom title if you want to customize the title of the UI (optional)
opts.Logger.Info().Msgf("GoCron UI available at http://%s", opts.GoCron.BindAddress) opts.Logger.Info().Msgf("GoCron UI available at http://%s", opts.GoCron.BindAddress)
opts.Logger.Fatal().Msg(http.ListenAndServe(opts.GoCron.BindAddress, srv.Router).Error()) if err := http.ListenAndServe(opts.GoCron.BindAddress, srv.Router); err != nil {
opts.Logger.Fatal().Msg(err.Error())
}
select {} // wait forever select {} // wait forever
} }
+4
View File
@@ -101,6 +101,10 @@ func (r *RemoteFileCommandExecutor) copyFile(source, destination string, Perms f
return nil return nil
} }
if sshClient == nil {
return fmt.Errorf("SSH Client is nil")
}
client, err := sftp.NewClient(sshClient) client, err := sftp.NewClient(sshClient)
if err != nil { if err != nil {
+50 -12
View File
@@ -36,6 +36,8 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
// print the command's information // print the command's information
if cmdFound { if cmdFound {
print("Backy Command: ")
println(cmd)
println("Command: ") println("Command: ")
print(cmdInfo.Cmd) print(cmdInfo.Cmd)
@@ -45,17 +47,57 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
print(v) // print command arg print(v) // print command arg
} }
// is it remote or local if cmdInfo.Type.String() != "" {
if !IsHostLocal(cmdInfo.Host) {
println() println()
print("Host: ", cmdInfo.Host) print("Type: ", cmdInfo.Type.String())
println() println()
}
if cmdInfo.Hosts != nil {
for n, h := range cmdInfo.Hosts {
println()
fmt.Printf("Host %d: %s", n, h)
println()
}
} else { } else {
println() // is it remote or local
print("Host: Runs on Local Machine\n\n") if !IsHostLocal(cmdInfo.Host) {
println()
print("Host: ", cmdInfo.Host)
println()
} else {
println()
print("Host: Runs on Local Machine\n\n")
}
}
if len(cmdInfo.Environment) > 0 {
fmt.Print("Environment: ")
for _, env := range cmdInfo.Environment {
fmt.Printf("%s ", env)
}
fmt.Println()
}
if cmdInfo.PackageManager != "" {
fmt.Printf("\nPackage Manager: %s\n", cmdInfo.PackageManager)
if len(cmdInfo.Packages) > 0 {
for _, pkg := range cmdInfo.Packages {
fmt.Printf("Package: %v\n", pkg)
}
}
}
if cmdInfo.Username != "" || cmdInfo.UserID != "" {
fmt.Println("\nUser Settings:")
fmt.Printf("Username: %s\n", cmdInfo.Username)
fmt.Printf("UserID: %s\n", cmdInfo.UserID)
} }
if cmdInfo.Dir != nil { if cmdInfo.Dir != nil {
@@ -64,11 +106,6 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
println() println()
} }
if cmdInfo.Type.String() != "" {
print("Type: ", cmdInfo.Type.String())
println()
}
} else { } else {
fmt.Printf("Command %s not found. Check spelling.\n", cmd) fmt.Printf("Command %s not found. Check spelling.\n", cmd)
@@ -100,9 +137,10 @@ func (opts *ConfigOpts) ListCommandList(list string) {
println("List: ", list) println("List: ", list)
println() println()
for _, v := range listInfo.Order { for n, cmd := range listInfo.Order {
println() println()
opts.ListCommand(v) fmt.Printf("Command %d: \n", n+1)
opts.ListCommand(cmd)
} }
} else { } else {
+12
View File
@@ -2,6 +2,7 @@ package backy
import ( import (
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"time" "time"
) )
@@ -75,6 +76,17 @@ func (metricFile *MetricFile) SaveToFile() error {
} }
func LoadMetricsFromFile(filename string) (*MetricFile, error) { func LoadMetricsFromFile(filename string) (*MetricFile, error) {
if err := testFile(filename); err != nil {
_, createErr := os.Create(filename)
if createErr != nil {
return nil, fmt.Errorf("error creating file and root cause: %w", err)
}
metricData := NewMetricsFromFile(filename)
data, _ := json.MarshalIndent(metricData, "", " ")
os.WriteFile(filename, data, 0600)
return metricData, nil
}
jsonData, err := os.ReadFile(filename) jsonData, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
+57 -62
View File
@@ -1,67 +1,62 @@
package backy package backy
// import ( import (
// "testing" "testing"
// "time" "time"
// ) )
// func TestAddingMetricsForCommand(t *testing.T) { func TestAddingMetricsForCommand(t *testing.T) {
// Create a new MetricFile
metricFile := NewMetricsFromFile("test_metrics.json")
metricFile, err := LoadMetricsFromFile(metricFile.Filename)
if err != nil {
t.Fatalf("Failed to load metrics from file: %v", err)
}
// Add metrics for a command
commandName := "test_command"
if metricFile.CommandMetrics != nil {
if _, exists := metricFile.CommandMetrics[commandName]; !exists {
metricFile.CommandMetrics[commandName] = NewMetrics()
}
}
// Update the metrics for the command
executionTime := 1.8 // Example execution time in seconds
success := true // Example success status
metricFile.CommandMetrics[commandName].Update(success, executionTime, time.Now())
// Check if the metrics were updated correctly
if metricFile.CommandMetrics[commandName].SuccessfulExecutions > 50 {
t.Errorf("Expected 1 successful execution, got %d", metricFile.CommandMetrics[commandName].SuccessfulExecutions)
}
if metricFile.CommandMetrics[commandName].TotalExecutions > 50 {
t.Errorf("Expected 1 total execution, got %d", metricFile.CommandMetrics[commandName].TotalExecutions)
}
if metricFile.CommandMetrics[commandName].TotalExecutionTime != executionTime {
t.Errorf("Expected execution time %f, got %f", executionTime, metricFile.CommandMetrics[commandName].TotalExecutionTime)
}
err = metricFile.SaveToFile()
if err != nil {
t.Errorf("Failed to save metrics to file: %v", err)
}
listName := "test_list"
if _, exists := metricFile.ListMetrics[listName]; !exists {
metricFile.ListMetrics[listName] = NewMetrics()
}
// Update the metrics for the list
metricFile.ListMetrics[listName].Update(success, executionTime, time.Now())
if metricFile.ListMetrics[listName].SuccessfulExecutions > 50 {
t.Errorf("Expected 1 successful execution for list, got %d", metricFile.ListMetrics[listName].SuccessfulExecutions)
}
if metricFile.ListMetrics[listName].TotalExecutions > 50 {
t.Errorf("Expected 1 total execution for list, got %d", metricFile.ListMetrics[listName].TotalExecutions)
}
if metricFile.ListMetrics[listName].TotalExecutionTime > executionTime {
t.Errorf("Expected execution time %f for list, got %f", executionTime, metricFile.ListMetrics[listName].TotalExecutionTime)
}
// // Create a new MetricFile // Save the metrics to a file
// metricFile := NewMetricsFromFile("test_metrics.json") err = metricFile.SaveToFile()
if err != nil {
t.Errorf("Failed to save metrics to file: %v", err)
}
// metricFile, err := LoadMetricsFromFile(metricFile.Filename) }
// if err != nil {
// t.Errorf("Failed to load metrics from file: %v", err)
// }
// // Add metrics for a command
// commandName := "test_command"
// if _, exists := metricFile.CommandMetrics[commandName]; !exists {
// metricFile.CommandMetrics[commandName] = NewMetrics()
// }
// // Update the metrics for the command
// executionTime := 1.8 // Example execution time in seconds
// success := true // Example success status
// metricFile.CommandMetrics[commandName].Update(success, executionTime, time.Now())
// // Check if the metrics were updated correctly
// if metricFile.CommandMetrics[commandName].SuccessfulExecutions > 50 {
// t.Errorf("Expected 1 successful execution, got %d", metricFile.CommandMetrics[commandName].SuccessfulExecutions)
// }
// if metricFile.CommandMetrics[commandName].TotalExecutions > 50 {
// t.Errorf("Expected 1 total execution, got %d", metricFile.CommandMetrics[commandName].TotalExecutions)
// }
// // if metricFile.CommandMetrics[commandName].TotalExecutionTime != executionTime {
// // t.Errorf("Expected execution time %f, got %f", executionTime, metricFile.CommandMetrics[commandName].TotalExecutionTime)
// // }
// err = metricFile.SaveToFile()
// if err != nil {
// t.Errorf("Failed to save metrics to file: %v", err)
// }
// listName := "test_list"
// if _, exists := metricFile.ListMetrics[listName]; !exists {
// metricFile.ListMetrics[listName] = NewMetrics()
// }
// // Update the metrics for the list
// metricFile.ListMetrics[listName].Update(success, executionTime, time.Now())
// if metricFile.ListMetrics[listName].SuccessfulExecutions > 50 {
// t.Errorf("Expected 1 successful execution for list, got %d", metricFile.ListMetrics[listName].SuccessfulExecutions)
// }
// if metricFile.ListMetrics[listName].TotalExecutions > 50 {
// t.Errorf("Expected 1 total execution for list, got %d", metricFile.ListMetrics[listName].TotalExecutions)
// }
// // if metricFile.ListMetrics[listName].TotalExecutionTime > executionTime {
// // t.Errorf("Expected execution time %f for list, got %f", executionTime, metricFile.ListMetrics[listName].TotalExecutionTime)
// // }
// // Save the metrics to a file
// err = metricFile.SaveToFile()
// if err != nil {
// t.Errorf("Failed to save metrics to file: %v", err)
// }
// }
+46 -79
View File
@@ -472,7 +472,7 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
defer commandSession.Close() defer commandSession.Close()
// Set output writers // Set output writers
var file *os.File // var file *os.File
if !IsHostLocal(command.Host) && command.Output.File != "" { if !IsHostLocal(command.Host) && command.Output.File != "" {
if filepath.IsAbs(command.Output.File) { if filepath.IsAbs(command.Output.File) {
fileName := filepath.Base(command.Output.File) fileName := filepath.Base(command.Output.File)
@@ -483,19 +483,19 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
} }
} }
cmdOutWriters, file, err = makeCmdOutWriters(&cmdOutBuf, command.Output.File) cmdOutWriters, _, err = makeCmdOutWriters(&cmdOutBuf, command.Output.File)
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating command output writers: %w", err) return nil, fmt.Errorf("error creating command output writers: %w", err)
} }
defer func() { // defer func() {
if file != nil { // if file != nil {
file.Close() // file.Close()
} // }
}() // }()
// cmdOutWriters = logging.SetLoggingWriterForCommand(&cmdOutBuf, command.Output.File, IsCmdStdOutEnabled()) // cmdOutWriters = logging.SetLoggingWriterForCommand(&cmdOutBuf, command.Output.File, IsCmdStdOutEnabled())
cmdCtxLogger = zerolog.New(cmdOutWriters).With().Timestamp().Logger() cmdCtxLogger = zerolog.New(cmdOutWriters).With().Timestamp().Logger()
cmdCtxLogger = command.GenerateLoggerForCmd(cmdCtxLogger) // cmdCtxLogger = command.GenerateLoggerForCmd(cmdCtxLogger)
command.cmdLoggers.cmdContxt = cmdCtxLogger
// cmdCtxLogger.Info().Msgf("Executing %s", command.Cmd) // cmdCtxLogger.Info().Msgf("Executing %s", command.Cmd)
commandSession.Stdout = cmdOutWriters commandSession.Stdout = cmdOutWriters
commandSession.Stderr = cmdOutWriters commandSession.Stderr = cmdOutWriters
@@ -513,13 +513,13 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
// Handle command execution based on type // Handle command execution based on type
switch command.Type { switch command.Type {
case ScriptCommandType: case ScriptCommandType:
return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf) return command.runScript(commandSession, &cmdOutBuf)
case RemoteScriptCommandType: case RemoteScriptCommandType:
return command.runRemoteScript(commandSession, cmdCtxLogger, &cmdOutBuf) return command.runRemoteScript(commandSession, &cmdOutBuf)
case ScriptFileCommandType: case ScriptFileCommandType:
commandSession.Stdout = nil // commandSession.Stdout = nil
commandSession.Stderr = nil // commandSession.Stderr = nil
return command.runScriptFile(commandSession, cmdCtxLogger, opts.Logger, &cmdOutBuf) return command.runScriptFile(commandSession, &cmdOutBuf)
case PackageCommandType: case PackageCommandType:
var remoteHostPackageExecutor RemoteHostPackageExecutor var remoteHostPackageExecutor RemoteHostPackageExecutor
return remoteHostPackageExecutor.RunCmdOnHost(command, commandSession, cmdCtxLogger, cmdOutBuf) return remoteHostPackageExecutor.RunCmdOnHost(command, commandSession, cmdCtxLogger, cmdOutBuf)
@@ -541,18 +541,18 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
userNamePass := fmt.Sprintf("%s:%s", command.Username, command.UserPassword) userNamePass := fmt.Sprintf("%s:%s", command.Username, command.UserPassword)
client, err := sftp.NewClient(command.RemoteHost.SshClient) client, err := sftp.NewClient(command.RemoteHost.SshClient)
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating sftp client: %v", err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error creating sftp client: %v", err)
} }
uuidFile := uuid.New() uuidFile := uuid.New()
passFilePath := fmt.Sprintf("/tmp/%s", uuidFile.String()) passFilePath := fmt.Sprintf("/tmp/%s", uuidFile.String())
passFile, passFileErr := client.Create(passFilePath) passFile, passFileErr := client.Create(passFilePath)
if passFileErr != nil { if passFileErr != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating file /tmp/%s: %v", uuidFile.String(), passFileErr) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error creating file /tmp/%s: %v", uuidFile.String(), passFileErr)
} }
_, err = passFile.Write([]byte(userNamePass)) _, err = passFile.Write([]byte(userNamePass))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error writing to file /tmp/%s: %v", uuidFile.String(), err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error writing to file /tmp/%s: %v", uuidFile.String(), err)
} }
ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath) ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath)
@@ -566,7 +566,7 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
defer rmFileFunc() defer rmFileFunc()
} }
if err := commandSession.Run(command.ArgStr); err != nil { if err := commandSession.Run(command.ArgStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
} }
if command.Type == UserCommandType { if command.Type == UserCommandType {
@@ -587,41 +587,41 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
commandSession, _ = command.RemoteHost.createSSHSession(opts) commandSession, _ = command.RemoteHost.createSSHSession(opts)
userHome, err = commandSession.CombinedOutput(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username)) userHome, err = commandSession.CombinedOutput(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
} }
command.UserHome = strings.TrimSpace(string(userHome)) command.UserHome = strings.TrimSpace(string(userHome))
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome) userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
client, err = sftp.NewClient(command.RemoteHost.SshClient) client, err = sftp.NewClient(command.RemoteHost.SshClient)
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating sftp client: %v", err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error creating sftp client: %v", err)
} }
err = client.MkdirAll(userSshDir) err = client.MkdirAll(userSshDir)
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error creating directory %s: %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error creating directory %s: %v", userSshDir, err)
} }
_, err = client.Create(fmt.Sprintf("%s/authorized_keys", userSshDir)) _, err = client.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
} }
f, err = client.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), os.O_APPEND|os.O_CREATE|os.O_WRONLY) f, err = client.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), os.O_APPEND|os.O_CREATE|os.O_WRONLY)
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
} }
defer f.Close() defer f.Close()
for _, k := range command.UserSshPubKeys { for _, k := range command.UserSshPubKeys {
buf := bytes.NewBufferString(k) buf := bytes.NewBufferString(k)
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key") cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
if _, err := f.ReadFrom(buf); err != nil { if _, err := f.ReadFrom(buf); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error adding to authorized keys: %v", err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error adding to authorized keys: %v", err)
} }
} }
commandSession, _ = command.RemoteHost.createSSHSession(opts) commandSession, _ = command.RemoteHost.createSSHSession(opts)
_, err = commandSession.CombinedOutput(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome)) _, err = commandSession.CombinedOutput(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), err return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), err
} }
} }
@@ -629,7 +629,7 @@ func (command *Command) RunCmdOnHost(cmdCtxLogger zerolog.Logger, opts *ConfigOp
} }
} }
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), nil
} }
func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandSession *ssh.Session, cmdOutBuf bytes.Buffer) ([]string, error) { func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandSession *ssh.Session, cmdOutBuf bytes.Buffer) ([]string, error) {
@@ -649,9 +649,9 @@ func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandS
_, parseErr := parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf) _, parseErr := parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
if parseErr != nil { if parseErr != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error: packages %v not listed: %w", command.Packages, err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error: packages %v not listed: %w", command.Packages, err)
} }
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running %s: %w", ArgsStr, err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error running %s: %w", ArgsStr, err)
} }
return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf) return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
@@ -666,7 +666,7 @@ func getCommandTypeAndSetCommandInfoLabel(commandType CommandType) string {
} }
// runScript handles the execution of inline scripts. // runScript handles the execution of inline scripts.
func (command *Command) runScript(session *ssh.Session, cmdCtxLogger zerolog.Logger, outputBuf *bytes.Buffer) ([]string, error) { func (command *Command) runScript(session *ssh.Session, outputBuf *bytes.Buffer) ([]string, error) {
script, err := command.prepareScriptBuffer() script, err := command.prepareScriptBuffer()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -678,63 +678,29 @@ func (command *Command) runScript(session *ssh.Session, cmdCtxLogger zerolog.Log
} }
if err := session.Wait(); err != nil { if err := session.Wait(); err != nil {
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err) return collectOutput(outputBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error waiting for command: %w", err)
} }
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil return collectOutput(outputBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), nil
} }
// runScriptFile handles the execution of script files. // runScriptFile handles the execution of script files.
func (command *Command) runScriptFile(session *ssh.Session, cmdCtxLogger, globalLogger zerolog.Logger, outputBuf *bytes.Buffer) ([]string, error) { func (command *Command) runScriptFile(session *ssh.Session, outputBuf *bytes.Buffer) ([]string, error) {
script, err := command.prepareScriptFileBuffer() script, err := command.prepareScriptFileBuffer()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// session.Stdin = script session.Stdin = script
modes := ssh.TerminalModes{
ssh.ECHO: 0,
ssh.ECHOCTL: 0,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
session.RequestPty("xterm", 80, 40, modes)
stdin, _ := session.StdinPipe()
stdout, stdOutErr := session.StdoutPipe()
if stdOutErr != nil {
return nil, fmt.Errorf("error getting stdout pipe: %w", stdOutErr)
}
if err := session.Shell(); err != nil { if err := session.Shell(); err != nil {
return nil, fmt.Errorf("error starting shell: %w", err) return nil, fmt.Errorf("error starting shell: %w", err)
} }
var LogOutputToFile bool
if command.Output.File != "" || command.Output.ToLog {
if command.Output.File != "" {
globalLogger.Info().Str("file", command.Output.File).Msg("Writing script output to file")
}
LogOutputToFile = true
}
stdin.Write(script.Bytes())
stdOutput, stdoOutReadErr := io.ReadAll(stdout)
if err := session.Wait(); err != nil { if err := session.Wait(); err != nil {
stdOutBuff := bytes.NewBuffer(stdOutput) return collectOutput(outputBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error waiting for shell: %w", err)
// outputBuf.Write(stdOutBuff.Bytes())
// Read output
return collectOutput(stdOutBuff, command.Name, cmdCtxLogger, LogOutputToFile), fmt.Errorf("error waiting for shell: %w", err)
} }
// Read output return collectOutput(outputBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), nil
if stdoOutReadErr != nil {
return collectOutput(outputBuf, command.Name, cmdCtxLogger, LogOutputToFile), fmt.Errorf("error reading stdout after shell error: %w", stdoOutReadErr)
}
stdOutBuff := bytes.NewBuffer(stdOutput)
return collectOutput(stdOutBuff, command.Name, cmdCtxLogger, LogOutputToFile), nil
} }
// prepareScriptBuffer prepares a buffer for inline scripts. // prepareScriptBuffer prepares a buffer for inline scripts.
@@ -757,7 +723,8 @@ func (command *Command) prepareScriptBuffer() (*bytes.Buffer, error) {
buffer.WriteString(command.Cmd) buffer.WriteString(command.Cmd)
for _, arg := range command.Args { for _, arg := range command.Args {
buffer.WriteString(" " + arg) buffer.WriteString(" ")
buffer.WriteString(arg)
} }
buffer.WriteByte('\n') buffer.WriteByte('\n')
return &buffer, nil return &buffer, nil
@@ -767,9 +734,9 @@ func (command *Command) prepareScriptBuffer() (*bytes.Buffer, error) {
func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) { func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) {
var buffer bytes.Buffer var buffer bytes.Buffer
if !command.SaveShellHistory { // if !command.SaveShellHistory {
buffer.WriteString("unset HISTFILE\nexport HISTSIZE=0\nexport SAVEHIST=0\n") // buffer.WriteString("unset HISTFILE\nexport HISTSIZE=0\nexport SAVEHIST=0\n")
} // }
for _, envVar := range command.Environment { for _, envVar := range command.Environment {
fmt.Fprintf(&buffer, "export %s", envVar) fmt.Fprintf(&buffer, "export %s", envVar)
@@ -797,7 +764,7 @@ func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) {
} }
// runRemoteScript handles the execution of remote scripts // runRemoteScript handles the execution of remote scripts
func (command *Command) runRemoteScript(session *ssh.Session, cmdCtxLogger zerolog.Logger, outputBuf *bytes.Buffer) ([]string, error) { func (command *Command) runRemoteScript(session *ssh.Session, outputBuf *bytes.Buffer) ([]string, error) {
script, err := command.Fetcher.Fetch(command.Cmd) script, err := command.Fetcher.Fetch(command.Cmd)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -809,10 +776,10 @@ func (command *Command) runRemoteScript(session *ssh.Session, cmdCtxLogger zerol
err = session.Run(command.Shell) err = session.Run(command.Shell)
if err != nil { if err != nil {
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running remote script: %w", err) return collectOutput(outputBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error running remote script: %w", err)
} }
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil return collectOutput(outputBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), nil
} }
// readFileToBuffer reads a file into a buffer. // readFileToBuffer reads a file into a buffer.
@@ -881,7 +848,7 @@ func DoesHostHaveHostName(host string) (bool, string) {
} }
func IsHostLocal(host string) bool { func IsHostLocal(host string) bool {
host = strings.ToLower(host) host = strings.ToLower(strings.TrimSpace(host))
return host == "127.0.0.1" || host == "localhost" || host == "" return host == "127.0.0.1" || host == "localhost" || host == ""
} }
@@ -911,7 +878,7 @@ func (r RemoteHostPackageExecutor) RunCmdOnHost(command *Command, commandSession
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send() cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
// Run simple command // Run simple command
if err := commandSession.Run(ArgsStr); err != nil { if err := commandSession.Run(ArgsStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error running command: %w", err) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error running command: %w", err)
} }
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), nil
} }
+5 -7
View File
@@ -1,15 +1,13 @@
Command list {{.listName }} failed. Backy Command: {{.CmdName}}.
The command run was {{.CmdName}}. The command executed: {{.Command}} {{ if .Args }} {{- range .Args}} {{.}} {{end}} {{end}}
The command executed was {{.Command}} {{ if .Args }} {{- range .Args}} {{.}} {{end}} {{end}} {{ if .Err }} Error: {{ .Err }}{{ end }}
{{ if .Err }} The error was {{ .Err }}{{ end }} {{ if .Output }} Output: {{- range .Output}} {{.}} {{end}} {{end}}
{{ if .Output }} The output was: {{- range .Output}} {{.}} {{end}} {{end}}
{{ if .CmdsRan }} {{ if .CmdsRan }}
The following commands ran: Commands executed (last one failed):
{{- range .CmdsRan}} {{- range .CmdsRan}}
- {{. -}} - {{. -}}
{{end}} {{end}}
+1 -3
View File
@@ -1,6 +1,4 @@
Command list {{ .listName }} completed successfully. Commands executed:
The following commands ran:
{{- range .CmdsRan}} {{- range .CmdsRan}}
- {{. -}} - {{. -}}
{{end}} {{end}}
+8 -2
View File
@@ -58,6 +58,8 @@ type (
// See CommandType enum further down the page for acceptable values // See CommandType enum further down the page for acceptable values
Type CommandType `yaml:"type,omitempty"` Type CommandType `yaml:"type,omitempty"`
RawOutput bytes.Buffer
Host string `yaml:"host,omitempty"` Host string `yaml:"host,omitempty"`
Hosts []string `yaml:"hosts,omitempty"` Hosts []string `yaml:"hosts,omitempty"`
@@ -80,8 +82,6 @@ type (
ScriptEnvFile string `yaml:"scriptEnvFile"` ScriptEnvFile string `yaml:"scriptEnvFile"`
SaveShellHistory bool `yaml:"saveShellHistory,omitempty"`
Output struct { Output struct {
File string `yaml:"file,omitempty"` File string `yaml:"file,omitempty"`
ToLog bool `yaml:"toLog,omitempty"` ToLog bool `yaml:"toLog,omitempty"`
@@ -143,6 +143,11 @@ type (
// END USER CommandType FIELDS // END USER CommandType FIELDS
cmdLoggers struct {
global zerolog.Logger
cmdContxt zerolog.Logger
}
// BEGIN FILE COMMAND FIELDS // BEGIN FILE COMMAND FIELDS
FileOperation string `yaml:"fileOperation,omitempty"` FileOperation string `yaml:"fileOperation,omitempty"`
@@ -305,6 +310,7 @@ type (
} }
CmdResult struct { CmdResult struct {
HostName string
CmdName string // Name of the command executed CmdName string // Name of the command executed
ListName string // Name of the command list ListName string // Name of the command list
Error error // Error encountered, if any Error error // Error encountered, if any
+22 -6
View File
@@ -16,6 +16,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"time"
"git.andrewnw.xyz/CyberShell/backy/pkg/logging" "git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher" "git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
@@ -222,17 +223,28 @@ func CheckConfigValues(config *koanf.Koanf, file string) {
} }
// collectOutput collects output from a buffer and logs it. // collectOutput collects output from a buffer and logs it.
func collectOutput(buf *bytes.Buffer, commandName string, logger zerolog.Logger, wantOutput bool) []string { func collectOutput(buf *bytes.Buffer, commandName string, logger, globalLogger zerolog.Logger, wantOutput bool) []string {
var outputArr []string var outputArr []string
scanner := bufio.NewScanner(buf) copyBuf := bytes.NewBuffer(buf.Bytes())
if buf.Len() == 0 {
logger.Info().Str("cmd", commandName).Msg("NO OUTPUT!!!!!!!")
globalLogger.Info().Str("cmd", commandName).Msg("NO OUTPUT!!!!!!!")
return outputArr
}
scanner := bufio.NewScanner(copyBuf)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
clean := sanitizeString(line) clean := sanitizeString(line)
outputArr = append(outputArr, clean) outputArr = append(outputArr, clean)
if wantOutput { if wantOutput {
time.Sleep(10 * time.Millisecond)
logger.Info().Str("cmd", commandName).Str("output", clean).Send() logger.Info().Str("cmd", commandName).Str("output", clean).Send()
if IsCmdStdOutEnabled() {
globalLogger.Info().Str("cmd", commandName).Str("output", clean).Send()
}
} }
} }
buf.Reset()
return outputArr return outputArr
} }
@@ -240,6 +252,7 @@ func collectOutput(buf *bytes.Buffer, commandName string, logger zerolog.Logger,
// while preserving tabs. This helps remove color codes and other terminal control // while preserving tabs. This helps remove color codes and other terminal control
// characters from remote command output. // characters from remote command output.
func sanitizeString(s string) string { func sanitizeString(s string) string {
// Remove common ANSI CSI sequences like "\x1b[31m" etc. // Remove common ANSI CSI sequences like "\x1b[31m" etc.
var ansiCSI = regexp.MustCompile(`\x1b\[[0-9;?]*[ -/]*[@-~]`) var ansiCSI = regexp.MustCompile(`\x1b\[[0-9;?]*[ -/]*[@-~]`)
s = ansiCSI.ReplaceAllString(s, "") s = ansiCSI.ReplaceAllString(s, "")
@@ -255,7 +268,10 @@ func sanitizeString(s string) string {
var b strings.Builder var b strings.Builder
for _, r := range s { for _, r := range s {
if r == '\t' || (r >= 0x20 && r != 0x7f) { if r == '\t' {
b.WriteString(" ")
}
if r >= 0x20 && r != 0x7f {
b.WriteRune(r) b.WriteRune(r)
} }
} }
@@ -410,7 +426,7 @@ func parsePackageVersion(output string, cmdCtxLogger zerolog.Logger, command *Co
pkgVersionOnSystem, err := command.pkgMan.ParseRemotePackageManagerVersionOutput(output) pkgVersionOnSystem, err := command.pkgMan.ParseRemotePackageManagerVersionOutput(output)
if err != nil { if err != nil {
cmdCtxLogger.Error().AnErr("Error parsing package version output", err).Send() cmdCtxLogger.Error().AnErr("Error parsing package version output", err).Send()
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error parsing package version output: %v", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error parsing package version output: %v", err)
} }
for _, p := range pkgVersionOnSystem { for _, p := range pkgVersionOnSystem {
@@ -453,9 +469,9 @@ func parsePackageVersion(output string, cmdCtxLogger zerolog.Logger, command *Co
} }
} }
if errs == nil { if errs == nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), nil return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), nil
} }
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error parsing package version output: %v", errs) return collectOutput(&cmdOutBuf, command.Name, command.cmdLoggers.cmdContxt, command.cmdLoggers.global, command.Output.ToLog), fmt.Errorf("error parsing package version output: %v", errs)
} }
func getPackageIndexFromCommand(command *Command, name string) int { func getPackageIndexFromCommand(command *Command, name string) int {
+7
View File
@@ -0,0 +1,7 @@
package backy
func Validate(c Command) {
if c.Cmd != "" {
}
}
+8 -8
View File
@@ -1,8 +1,8 @@
# localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14 # localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16
# localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14 [localhost]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDZi/dbBsgthhSj9kf4SIxTZCX+thRmvSgQ+ogC0yyHvoQrIio0Pu2MA66S779LE7Zng3CJ1lxiesffqdiTdcv8mrlnwHlzshNy4trl85NOawdNpwwlY8aiYQkxifXqAXSoHlgG+AlrbLcZ4q0YbeLnwgB3eEywN+eCHuJdetNr1Lq9NkqiGKltBIVYxHofEkUj+jBTJCyPDcp7/CKSYXhcELCaSw8vBkB7+PfsYYy0+TPXspiu/euLZfiagh6ik2fMcfc0k5P4Yn0GRt5Uk4jr85lob7ucO6Q2VPJpfavSjetcqa/oL6peEOJ4/SQzzrbxmh0I/5Tq76lTtrFT+R+tfqn0+n11FkNhS85IulmpdD2kLPGE6yH+vfjYrYD5TdH0qiOltqfXuiJCekRryoU9oWS/VSDi7aEVPryCQWf0IUqj05F/sa4RRtO0k7UOYL8NwJTS8q02sCt+/W+nX03f6XNoG7K57MZlYnREUGS6scqnvktb9bwKBunDZePMwZk=
[localhost]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDATufWA1HRnNayIQLjSpA2+P9N6h0WF+jP+abMaINlZkiHFnFVDAoqD5/onVXymskrgQaKEYmBOs+Kv0t+Acvdor2IcvYgFueSm+jkslpSK/uuf1mx0gVJO77S2BIjqyWtUzVv96Iy4Gjt2RsrnalgYNYmi3OyPkG0IUA+3Im+2gztSECCy+nW3R/vaoPLwr4kImpLlrijcSHc4mHOY6BurrcWKNuGrsvTAOKgUZqlya6uDd+yD7fUfsmL1MqBKwZqfP3JAdp/Dd+laNNGcvEM4WhzYFSPfhqblewD0rjbto9MSOSXLyQz5RPmdITj/m5M4lj2ECmcI2gzraDMoj8ZkuJAss50oX6fmVUZestN5jlz7Y7XKEvXuH8qfLHKwaOUTZlcGbfAMz6uSrh8DNT6KzRG4j5nZ9Z5pTn1huz/p6jnJUGuHt2Ez3EK+isM+sHS6TntXavIkebaq7ErcBCO8A1fZFZlhlHoI9o9W62tMY7gbtlGodW8dKxK89+1a88= # localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16
# localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14 [localhost]:2222 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEy0ReNz4EO68wUiQGHmkKOwLQhAUhY9oCyzpyROUWM0FWQId124SKPPcLD3DcYHQpofdG8h11NZ8Dyk1g/mW8M=
[localhost]:2222 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK9fEYfiGGgu0Eh7X2JT4jR4+utcfpm6Ee+Cer1x/XbMHzCPZg6YmYy6OaCSms/0VJ/QWxD+0HlsO7sqO5oeO60= # localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16
# localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14 # localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16
[localhost]:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKT5+Cbi/ynOAPzwv0IaOVBtGFYtW33LIvNUuBKYqqyJ # localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16
# localhost:2222 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14 [localhost]:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIArPKqZ3cHWzv0o6RUko8TNpr95olBJAzHRiPIvYD7bH
+1
View File
@@ -4,4 +4,5 @@ set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)" DIR="$(cd "$(dirname "$0")" && pwd)"
echo "Starting SSH test container (building if needed)..." echo "Starting SSH test container (building if needed)..."
docker compose -f "$DIR/compose.yml" up -d --build docker compose -f "$DIR/compose.yml" up -d --build
ssh-keyscan -p 2222 localhost > "$DIR/known_hosts"
echo "Container started on localhost:2222" echo "Container started on localhost:2222"