6 Commits

Author SHA1 Message Date
62ac2934dc v0.12.1
All checks were successful
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-03-23 11:30:32 -05:00
c078632691 remove shell history boolean
All checks were successful
ci/woodpecker/push/publish-docs Pipeline was successful
2026-02-14 18:57:37 -06:00
52e25aad77 v0.12.0
All checks were successful
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
9f996f60c6 internal logic handling for cron webserver 2026-02-11 13:18:06 -06:00
4c152f8089 internal logic handling for cron webserver 2026-02-11 13:17:15 -06:00
4d2e4ce533 v0.11.5
All checks were successful
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
15 changed files with 192 additions and 143 deletions

View File

@@ -1,3 +0,0 @@
kind: Changed
body: 'Command.Type: scriptFile no longer requests psudoterminal'
time: 2026-02-10T11:59:03.24953839-06:00

3
.changes/v0.11.5.md Normal file
View File

@@ -0,0 +1,3 @@
## v0.11.5 - 2026-02-10
### Changed
* Command.Type: scriptFile no longer requests psudoterminal

3
.changes/v0.12.0.md Normal file
View File

@@ -0,0 +1,3 @@
## v0.12.0 - 2026-02-11
### Changed
* internal logic handling for cron webserver

8
.changes/v0.12.1.md Normal file
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

11
.vscode/settings.json vendored
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"
}
} }

View File

@@ -6,6 +6,23 @@ 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.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

20
cmd/validate.go Normal file
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) {
/*
*/
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const versionStr = "0.11.5" const versionStr = "0.12.1"
var ( var (
versionCmd = &cobra.Command{ versionCmd = &cobra.Command{

5
localDeploy Executable file
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

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)
@@ -979,30 +968,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 +1002,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 +1011,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 +1031,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 +1040,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()
} }

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
} }

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,60 +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)
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
}
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.
@@ -764,9 +733,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)
@@ -794,7 +763,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
@@ -806,10 +775,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.
@@ -908,7 +877,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
} }

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

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,23 @@ 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())
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 +247,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 +263,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 +421,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 +464,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
pkg/backy/validate.go Normal file
View File

@@ -0,0 +1,7 @@
package backy
func Validate(c Command) {
if c.Cmd != "" {
}
}