46 Commits

Author SHA1 Message Date
fd019bc407 v0.11.0
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline is pending
ci/woodpecker/tag/publish-docs Pipeline was successful
2025-11-24 17:51:56 -06:00
febc2680f4 Update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-11-17 16:46:54 -06:00
caf2397349 Update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-11-17 16:27:33 -06:00
172ca8712e Update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-11-17 16:12:02 -06:00
bda16bcbb5 Update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-11-17 16:08:42 -06:00
b5d069112f Update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-11-17 15:56:28 -06:00
f56393c84c fix small things 2025-11-17 15:55:51 -06:00
55ef8e1733 Update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-11-17 15:43:47 -06:00
075fc16ec9 Update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-11-15 22:54:02 -06:00
0d6a13c1cf Cmd Type script now correctly appends arguments
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-11-15 17:37:17 -06:00
e57939f858 add files
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-10-21 23:19:00 -05:00
d45b1562fc started refactoring into executors using interfaces 2025-09-07 20:44:48 -05:00
7fe07f86a9 CLI: added exec hosts subcommand list 2025-07-23 22:05:44 -05:00
14c81a00a7 CLI: added exec hosts subcommand list 2025-07-23 22:04:48 -05:00
3eced64453 CLI: Exec subcommand host 2025-07-15 20:24:30 -05:00
c284d928fd CLI: Exec subcommand host 2025-07-15 20:24:06 -05:00
dd9da9452b CLI: Exec subcommand host 2025-07-15 20:23:58 -05:00
11d5121954 update deps 2025-07-14 20:48:53 -05:00
66d622b474 Improved error message for remote version package output 2025-07-09 23:21:46 -05:00
47b2aabd9f Improved error message for remote version package output 2025-07-09 23:20:11 -05:00
b91cf18b04 change file 2025-07-04 10:22:37 -05:00
305b504ca1 tests: beginning of tests using Docker 2025-07-04 09:02:27 -05:00
7be2679b91 change: Commands: host can now be localhost or 127.0.0.1 to run commands locally
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-21 09:09:56 -05:00
3c6e3ed914 v0.10.2
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
2025-03-19 22:42:49 -05:00
02bc040e2a v0.10.2
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-19 22:41:18 -05:00
9f1f36215a v0.10.2 2025-03-19 22:40:06 -05:00
ff75f4bbcd feat: add variable support
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-16 23:43:12 -05:00
5f40713e98 feat: add variable support 2025-03-16 23:42:54 -05:00
cd5f7611a9 notifications: add http service
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-13 23:35:00 -05:00
b542711078 notifications: add http service 2025-03-13 23:34:37 -05:00
52dbc353e5 change: update go toolchain
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-12 23:22:52 -05:00
6bef0c3e5b notifications: add http config
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-12 10:10:45 -05:00
4d705d78fb fix: golang-ci-lint version
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 22:22:39 -05:00
62d47ecfa7 fix: pipeline errors
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
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
2025-03-11 22:08:59 -05:00
32444ff82e fix: docs and pipeline errors
Some checks failed
ci/woodpecker/tag/gitea Pipeline failed
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-03-11 21:55:51 -05:00
a5a7c05640 v0.10.1
Some checks failed
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline failed
ci/woodpecker/tag/publish-docs Pipeline was successful
2025-03-11 21:37:58 -05:00
bfb81e11b2 version bump
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-03-11 21:36:53 -05:00
fd4c83f9c0 Vault: keys are now referenced by name, and the actual data by data
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-03-11 21:33:06 -05:00
fe27c6396a LinuxUserManager: correct parameters for AddUser()
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 16:13:29 -05:00
c89dde186a UserCommands: change field name
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-03-11 15:37:12 -05:00
18a64de0de UserCommands: change field name 2025-03-11 15:36:43 -05:00
99c622b69f UserCommands: add field CreateUserHome 2025-03-11 15:30:07 -05:00
95e85e8b45 UserCommands: add ssh public keys when running locally
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 15:21:02 -05:00
1a48c7bca5 change: create temp file when modifing password over SSH 2025-03-11 14:55:02 -05:00
5d21764ef1 fix: don't test empty env files
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 13:42:40 -05:00
c7302f0e17 update docs
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-03-10 12:34:33 -05:00
138 changed files with 2801 additions and 806 deletions

0
.changes/0.2.4.md Normal file → Executable file
View File

0
.changes/header.tpl.md Normal file → Executable file
View File

0
.changes/unreleased/.gitkeep Normal file → Executable file
View File

0
.changes/v0.10.0.md Normal file → Executable file
View File

8
.changes/v0.10.1.md Executable file
View File

@@ -0,0 +1,8 @@
## v0.10.1 - 2025-03-11
### Added
* UserCommands: add ssh public keys when running locally
* UserCommands: add field CreateUserHome
### Changed
* UserCommands: create temp file when modifing password over SSH
* UserCommands: change field name
* Vault: keys are now referenced by `name`, and the actual data by `data`

6
.changes/v0.10.2.md Executable file
View File

@@ -0,0 +1,6 @@
## v0.10.2 - 2025-03-19
### Added
* Notifications: http service added
* Variable support. Can be referenced with `%{var:nameOfVar}%` in select string fields.
### Changed
* vault: initialize vault before validating config

21
.changes/v0.11.0.md Normal file
View File

@@ -0,0 +1,21 @@
## v0.11.0 - 2025-11-24
### Added
* feat: Package operation `versionCheck` supports regular expressions (see [regexp](https://pkg.go.dev/regexp) package for docs)
* Command lists: added `cmdLists.[name].notify` object
* Testing setup with Docker
* CLI: add global flag --hostsConfig that allows hosts to be dynamic in relation to the main config
* CLI: Exec subcommand `hosts`. See documentation for more details.
* CLI: added `exec hosts` subcommand `list`
* Add support for hosts in parallel
### Changed
* Commands: `host` can now be `localhost` or `127.0.0.1` to run commands locally
* lists loaded from external files only if no list config present in current file
* `PackageManager.Parse` renamed to `ParseRemotePackageManagerVersionOutput`. This now returns arrays of PackageManagerCommon.Package and errors.
* Internal: refactoring and renaming functions
* Commands: moved output-prefixed keys to the `commands.[name].output` object
* Change internal method name for better understanding
* Improved error message for remote version package output
### Fixed
* Command Lists: hooks now run correctly when commands finish
* Log file passed using `--log-file` correctly used
* Cmd Type `script` now correctly appends arguments

0
.changes/v0.3.0.md Normal file → Executable file
View File

0
.changes/v0.3.1.md Normal file → Executable file
View File

0
.changes/v0.4.0.md Normal file → Executable file
View File

0
.changes/v0.5.0.md Normal file → Executable file
View File

0
.changes/v0.6.0.md Normal file → Executable file
View File

0
.changes/v0.6.1.md Normal file → Executable file
View File

0
.changes/v0.7.0.md Normal file → Executable file
View File

0
.changes/v0.7.1.md Normal file → Executable file
View File

0
.changes/v0.7.2.md Normal file → Executable file
View File

0
.changes/v0.7.3.md Normal file → Executable file
View File

0
.changes/v0.7.4.md Normal file → Executable file
View File

0
.changes/v0.7.5.md Normal file → Executable file
View File

0
.changes/v0.7.6.md Normal file → Executable file
View File

0
.changes/v0.7.7.md Normal file → Executable file
View File

0
.changes/v0.7.8.md Normal file → Executable file
View File

0
.changes/v0.8.0.md Normal file → Executable file
View File

0
.changes/v0.9.0.md Normal file → Executable file
View File

0
.changes/v0.9.1.md Normal file → Executable file
View File

0
.changie.yaml Normal file → Executable file
View File

0
.frontmatter/database/mediaDb.json Normal file → Executable file
View File

0
.frontmatter/database/taxonomyDb.json Normal file → Executable file
View File

0
.github/workflows/release.yml vendored Normal file → Executable file
View File

2
.gitignore vendored Normal file → Executable file
View File

@@ -5,6 +5,6 @@ dist/
.codegpt
*.log
*.sh
/*.sh
/*.yaml
/*.yml

0
.gitmodules vendored Normal file → Executable file
View File

0
.goreleaser/gitea.yml Normal file → Executable file
View File

0
.goreleaser/github.yml Normal file → Executable file
View File

0
.goreleaser/vern.yml Normal file → Executable file
View File

3
.vscode/settings.json vendored Normal file → Executable file
View File

@@ -9,5 +9,6 @@
"mautrix",
"nikoksr",
"Strs"
]
],
"CodeGPT.apiKey": "CodeGPT Plus Beta"
}

2
.woodpecker/gitea.yml Normal file → Executable file
View File

@@ -1,9 +1,7 @@
name: goreleaser release
steps:
golang:
image: golang:1.23
commands:
- go mod tidy
- go install github.com/goreleaser/goreleaser/v2@v2.7.0
- goreleaser release -f .goreleaser/gitea.yml --release-notes=".changes/$(go run backy.go version -V).md"
environment:

2
.woodpecker/go-lint.yml Normal file → Executable file
View File

@@ -5,7 +5,7 @@ steps:
- go build
- go test
release:
image: golangci/golangci-lint:v1.53.3
image: golangci/golangci-lint:v1.64.7
commands:
- golangci-lint run -v --timeout 5m

4
.woodpecker/publish-docs.yml Normal file → Executable file
View File

@@ -1,11 +1,11 @@
steps:
build:
image: hugomods/hugo:ci
image: hugomods/hugo:debian-ci-0.147.2
commands:
- git submodule foreach 'git fetch origin; git checkout $(git describe --tags `git rev-list --tags --max-count=1`);'
- cd docs
- hugo mod get -u github.com/divinerites/plausible-hugo
- hugo mod get -u github.com/McShelby/hugo-theme-relearn@7.3.1
- hugo mod get -u github.com/McShelby/hugo-theme-relearn@8.2.0
- hugo
deploy:

38
CHANGELOG.md Normal file → Executable file
View File

@@ -6,6 +6,44 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
## v0.11.0 - 2025-11-24
### Added
* feat: Package operation `versionCheck` supports regular expressions (see [regexp](https://pkg.go.dev/regexp) package for docs)
* Command lists: added `cmdLists.[name].notify` object
* Testing setup with Docker
* CLI: add global flag --hostsConfig that allows hosts to be dynamic in relation to the main config
* CLI: Exec subcommand `hosts`. See documentation for more details.
* CLI: added `exec hosts` subcommand `list`
* Add support for hosts in parallel
### Changed
* Commands: `host` can now be `localhost` or `127.0.0.1` to run commands locally
* lists loaded from external files only if no list config present in current file
* `PackageManager.Parse` renamed to `ParseRemotePackageManagerVersionOutput`. This now returns arrays of PackageManagerCommon.Package and errors.
* Internal: refactoring and renaming functions
* Commands: moved output-prefixed keys to the `commands.[name].output` object
* Change internal method name for better understanding
* Improved error message for remote version package output
### Fixed
* Command Lists: hooks now run correctly when commands finish
* Log file passed using `--log-file` correctly used
* Cmd Type `script` now correctly appends arguments
## v0.10.2 - 2025-03-19
### Added
* Notifications: http service added
* Variable support. Can be referenced with `%{var:nameOfVar}%` in select string fields.
### Changed
* vault: initialize vault before validating config
## v0.10.1 - 2025-03-11
### Added
* UserCommands: add ssh public keys when running locally
* UserCommands: add field CreateUserHome
### Changed
* UserCommands: create temp file when modifing password over SSH
* UserCommands: change field name
* Vault: keys are now referenced by `name`, and the actual data by `data`
## v0.10.0 - 2025-03-08
### Added
* Hooks: improved logging when executing

0
License Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

1
backy.code-workspace Normal file → Executable file
View File

@@ -18,6 +18,7 @@
"maunium",
"mautrix",
"nikoksr",
"packagemanagercommon",
"rawbytes",
"remotefetcher",
"Strs"

0
backy.go Normal file → Executable file
View File

0
cmd/.gitignore vendored Normal file → Executable file
View File

9
cmd/backup.go Normal file → Executable file
View File

@@ -30,9 +30,14 @@ func init() {
}
func Backup(cmd *cobra.Command, args []string) {
backyConfOpts := backy.NewOpts(cfgFile, backy.AddCommandLists(cmdLists), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
backyConfOpts := backy.NewConfigOptions(configFile,
backy.AddCommandLists(cmdLists),
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
backyConfOpts.InitConfig()
backyConfOpts.ReadConfig()
backyConfOpts.ParseConfigurationFile()
backyConfOpts.RunListConfig("")
for _, host := range backyConfOpts.Hosts {

54
cmd/backup_test.go Executable file
View File

@@ -0,0 +1,54 @@
package cmd
// import (
// "bufio"
// "encoding/json"
// "os"
// "os/exec"
// "strings"
// "testing"
// "github.com/stretchr/testify/assert"
// )
// // TestConfigOptions tests the configuration options for the backy package.
// func Test_ErrorHook(t *testing.T) {
// configFile := "-f ../../tests/ErrorHook.yml"
// logFile := "--log-file=ErrorHook.log"
// backyCommand := exec.Command("go", "run", "../../backy.go", configFile, logFile, "backup")
// backyCommand.Stderr = os.Stdout
// backyCommand.Stdout = os.Stdout
// err := backyCommand.Run()
// assert.Nil(t, err)
// os.Remove("ErrorHook.log")
// logFileData, logFileErr := os.ReadFile("ErrorHook.log")
// if logFileErr != nil {
// assert.FailNow(t, logFileErr.Error())
// }
// var JsonData []map[string]interface{}
// jsonScanner := bufio.NewScanner(strings.NewReader(string(logFileData)))
// for jsonScanner.Scan() {
// var jsonDataLine map[string]interface{}
// err = json.Unmarshal(jsonScanner.Bytes(), &jsonDataLine)
// assert.Nil(t, err)
// JsonData = append(JsonData, jsonDataLine)
// }
// for _, v := range JsonData {
// _, ok := v["error"]
// if !ok {
// assert.FailNow(t, "error does not exist\n")
// // return
// }
// }
// // t.Logf("%s", logFileData)
// // t.Logf("%v", JsonData)
// }
// func TestBackupErrorHook(t *testing.T) {
// logFile = "ErrorHook.log"
// configFile = "../tests/ErrorHook.yml"
// }

2
cmd/config.go Normal file → Executable file
View File

@@ -20,7 +20,7 @@ package cmd
// func config(cmd *cobra.Command, args []string) {
// opts := backy.NewOpts(cfgFile, backy.cronEnabled())
// opts := backy.NewConfigOptions(configFile, backy.cronEnabled())
// opts.InitConfig()
// }

7
cmd/cron.go Normal file → Executable file
View File

@@ -18,13 +18,14 @@ var (
func cron(cmd *cobra.Command, args []string) {
parseS3Config()
opts := backy.NewOpts(cfgFile,
opts := backy.NewConfigOptions(configFile,
backy.EnableCron(),
backy.SetLogFile(logFile),
backy.SetCmdStdOut(cmdStdOut))
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
opts.InitConfig()
opts.ReadConfig()
opts.ParseConfigurationFile()
opts.Cron()
}

10
cmd/exec.go Normal file → Executable file
View File

@@ -21,7 +21,7 @@ var (
)
func init() {
execCmd.AddCommand(hostExecCommand)
execCmd.AddCommand(hostExecCommand, hostsExecCommand)
}
@@ -32,8 +32,12 @@ func execute(cmd *cobra.Command, args []string) {
logging.ExitWithMSG("Please provide a command to run. Pass --help to see options.", 1, nil)
}
opts := backy.NewOpts(cfgFile, backy.AddCommands(args), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
opts := backy.NewConfigOptions(configFile,
backy.AddCommands(args),
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
opts.InitConfig()
opts.ReadConfig()
opts.ParseConfigurationFile()
opts.ExecuteCmds()
}

17
cmd/host.go Normal file → Executable file
View File

@@ -35,10 +35,13 @@ func init() {
// 2. stdin (on command line) (TODO)
func Host(cmd *cobra.Command, args []string) {
backyConfOpts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
backyConfOpts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
backyConfOpts.InitConfig()
backyConfOpts.ReadConfig()
backyConfOpts.ParseConfigurationFile()
// check CLI input
if hostsList == nil {
@@ -46,14 +49,20 @@ func Host(cmd *cobra.Command, args []string) {
}
for _, h := range hostsList {
if backy.IsHostLocal(h) {
continue
}
// check if h exists in the config file
_, hostFound := backyConfOpts.Hosts[h]
if !hostFound {
// check if h exists in the SSH config file
hostFoundInConfig, s := backy.CheckIfHostHasHostName(h)
hostFoundInConfig, s := backy.DoesHostHaveHostName(h)
if !hostFoundInConfig {
logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger)
}
if backyConfOpts.Hosts == nil {
backyConfOpts.Hosts = make(map[string]*backy.Host)
}
// create host with hostname and host
backyConfOpts.Hosts[h] = &backy.Host{Host: h, HostName: s}
}
@@ -68,5 +77,5 @@ func Host(cmd *cobra.Command, args []string) {
}
}
backyConfOpts.ExecCmdsSSH(cmdList, hostsList)
backyConfOpts.ExecCmdsOnHosts(cmdList, hostsList)
}

93
cmd/hosts.go Executable file
View File

@@ -0,0 +1,93 @@
package cmd
import (
"maps"
"slices"
"git.andrewnw.xyz/CyberShell/backy/pkg/backy"
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"github.com/spf13/cobra"
)
var (
runCommandsInParallel bool
hostsExecCommand = &cobra.Command{
Use: "hosts [--command=command1 --command=command2 ... | -c command1 -c command2 ...]",
Short: "Runs command defined in config file on the hosts in order specified.",
Long: "Hosts executes specified commands on all the hosts defined in config file.\nUse the --commands or -c flag to choose the commands.",
Run: Hosts,
}
hostsListExecCommand = &cobra.Command{
Use: "list list1 list2 ...",
Short: "Runs lists in order specified defined in config file on all hosts.",
Long: "Lists executes specified lists on all the hosts defined in hosts config.\nPass the names of lists as arguments after command.",
Run: HostsList,
}
)
func init() {
hostsExecCommand.AddCommand(hostsListExecCommand)
hostsListExecCommand.Flags().BoolVarP(&runCommandsInParallel, "parallel", "p", false, "Run commands in parallel on hosts")
parseS3Config()
}
// cli input should be hosts and commands. Hosts are defined in config files.
// commands can be passed by the following mutually exclusive options:
// 1. as a list of commands defined in the config file
// 2. stdin (on command line) (TODO)
func Hosts(cmd *cobra.Command, args []string) {
backyConfOpts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
backyConfOpts.InitConfig()
backyConfOpts.ParseConfigurationFile()
for _, h := range backyConfOpts.Hosts {
hostsList = append(hostsList, h.Host)
}
if cmdList == nil {
logging.ExitWithMSG("error: commands must be specified", 1, &backyConfOpts.Logger)
}
for _, c := range cmdList {
_, cmdFound := backyConfOpts.Cmds[c]
if !cmdFound {
logging.ExitWithMSG("cmd "+c+" not found", 1, &backyConfOpts.Logger)
}
}
backyConfOpts.ExecCmdsOnHosts(cmdList, hostsList)
}
func HostsList(cmd *cobra.Command, args []string) {
backyConfOpts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.EnableCommandStdOut(cmdStdOut),
backy.SetHostsConfigFile(hostsConfigFile))
backyConfOpts.InitConfig()
backyConfOpts.ParseConfigurationFile()
if len(args) == 0 {
logging.ExitWithMSG("error: no lists specified", 1, &backyConfOpts.Logger)
}
for _, l := range args {
_, listFound := backyConfOpts.CmdConfigLists[l]
if !listFound {
logging.ExitWithMSG("list "+l+" not found", 1, &backyConfOpts.Logger)
}
}
maps.DeleteFunc(backyConfOpts.CmdConfigLists, func(k string, v *backy.CmdList) bool {
return !slices.Contains(args, k)
})
backyConfOpts.ExecuteListOnHosts(args, runCommandsInParallel)
}

20
cmd/list.go Normal file → Executable file
View File

@@ -22,13 +22,13 @@ var (
Use: "cmds [cmd1 cmd2 cmd3...]",
Short: "List commands defined in config file.",
Long: "List commands defined in config file",
Run: ListCmds,
Run: ListCommands,
}
listCmdLists = &cobra.Command{
Use: "lists [list1 list2 ...]",
Short: "List lists defined in config file.",
Long: "List lists defined in config file",
Run: ListCmdLists,
Run: ListCommandLists,
}
)
@@ -40,7 +40,7 @@ func init() {
}
func ListCmds(cmd *cobra.Command, args []string) {
func ListCommands(cmd *cobra.Command, args []string) {
// setup based on whats passed in:
// - cmds
@@ -54,17 +54,19 @@ func ListCmds(cmd *cobra.Command, args []string) {
parseS3Config()
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile))
opts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.SetHostsConfigFile(hostsConfigFile))
opts.InitConfig()
opts.ReadConfig()
opts.ParseConfigurationFile()
for _, v := range cmdsToList {
opts.ListCommand(v)
}
}
func ListCmdLists(cmd *cobra.Command, args []string) {
func ListCommandLists(cmd *cobra.Command, args []string) {
parseS3Config()
@@ -74,10 +76,12 @@ func ListCmdLists(cmd *cobra.Command, args []string) {
logging.ExitWithMSG("Error: lists subcommand needs lists", 1, nil)
}
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile))
opts := backy.NewConfigOptions(configFile,
backy.SetLogFile(logFile),
backy.SetHostsConfigFile(hostsConfigFile))
opts.InitConfig()
opts.ReadConfig()
opts.ParseConfigurationFile()
for _, v := range listsToList {
opts.ListCommandList(v)

10
cmd/root.go Normal file → Executable file
View File

@@ -13,7 +13,8 @@ import (
var (
// Used for flags.
cfgFile string
configFile string
hostsConfigFile string
verbose bool
cmdStdOut bool
logFile string
@@ -35,12 +36,13 @@ func Execute() {
}
func init() {
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", "", "log file to write to")
rootCmd.PersistentFlags().StringVar(&logFile, "logFile", "", "log file to write to")
rootCmd.PersistentFlags().BoolVar(&cmdStdOut, "cmdStdOut", false, "Pass to print command output to stdout")
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "f", "", "config file to read from")
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "f", "", "config file to read from")
rootCmd.PersistentFlags().StringVar(&hostsConfigFile, "hostsConfig", "", "yaml hosts file to read from")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Sets verbose level")
rootCmd.PersistentFlags().StringVar(&s3Endpoint, "s3-endpoint", "", "Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.")
rootCmd.PersistentFlags().StringVar(&s3Endpoint, "s3Endpoint", "", "Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.")
rootCmd.AddCommand(backupCmd, execCmd, cronCmd, versionCmd, listCmd)
}

2
cmd/version.go Normal file → Executable file
View File

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

0
docs/.gitignore vendored Normal file → Executable file
View File

0
docs/.hugo_build.lock Normal file → Executable file
View File

0
docs/archetypes/default.md Normal file → Executable file
View File

1
docs/config.yaml Normal file → Executable file
View File

@@ -13,6 +13,7 @@ module:
imports:
- path: github.com/divinerites/plausible-hugo
- path: github.com/McShelby/hugo-theme-relearn
version: "v8.2.0"
params:
themeVariant:
- auto: []

0
docs/content/_index.md Normal file → Executable file
View File

View File

@@ -26,8 +26,9 @@ Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
-h, --help help for backy
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
Use "backy [command] --help" for more information about a command.
@@ -51,8 +52,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
@@ -70,8 +72,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
@@ -86,6 +89,7 @@ Usage:
Available Commands:
host 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:
-h, --help help for exec
@@ -93,8 +97,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
Use "backy exec [command] --help" for more information about a command.
@@ -117,8 +122,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
@@ -138,8 +144,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
@@ -161,8 +168,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
Use "backy list [command] --help" for more information about a command.
@@ -181,8 +189,9 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
## list lists
@@ -199,7 +208,8 @@ Flags:
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
--hostsConfig string yaml hosts file to read from
--logFile string log file to write to
--s3Endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```

0
docs/content/cli/exec.md Normal file → Executable file
View File

0
docs/content/cli/list.md Normal file → Executable file
View File

0
docs/content/config/_index.md Normal file → Executable file
View File

12
docs/content/config/command-lists.md Normal file → Executable file
View File

@@ -35,12 +35,12 @@ If a remote config file is specified (on the command-line using `-f`) and the li
```
| key | description | type | required
| --- | --- | --- | --- |
| `order` | Defines the sequence of commands to execute | `[]string` | yes |
| `getOutput` | Command(s) output is in the notification(s) | `bool` | no |
| `notifications` | The notification service(s) and ID(s) to use on success and failure. Must be *`service.id`*. See the [notifications documentation page](/config/notifications/) for more | `[]string` | no |
| `name` | Optional name of the list | `string` | no |
| `cron` | Time at which to schedule the list. Only has affect when cron subcommand is run. | `string` | no |
| --- | --- | --- | ---
| `order` | Defines the sequence of commands to execute | `[]string` | yes
| `sendNotificationOnSuccess` | Whether to send notification on list success with the commands' output | `bool` | no
| `notifications` | The notification service(s) and ID(s) to use on success and failure. Must be *`service.id`*. See the [notifications documentation page](/config/notifications/) for more | `[]string` | no
| `name` | Optional name of the list | `string` | no
| `cron` | Time at which to schedule the list. Only has affect when cron subcommand is run. | `string` | no
### Order

45
docs/content/config/commands/_index.md Normal file → Executable file
View File

@@ -16,10 +16,11 @@ Values available for this section **(case-sensitive)**:
| ----------------| ------------------------------------------------------------------------------------------------------- | --------------------- | -------- |----------------------------|
| `cmd` | Defines the command to execute | `string` | yes | No |
| `Args` | Defines the arguments to the command | `[]string` | no | No |
| `environment` | Defines environment variables for the command | `[]string` | no | No |
| `environment` | Defines environment variables for the command | `[]string` | no | Partial |
| `type` | See documentation further down the page. Additional fields may be required. | `string` | no | No |
| `getOutput` | Command(s) output is in the notification(s) | `bool` | no | No |
| `host` | If not specified, the command will execute locally. | `string` | no | No |
| `host` | Depricated: use `hosts`. If not specified, the command will execute locally. | `string` | no | No |
| `hosts` | Must be specified to run commands both locallly and in parrallel. | `[]string` | no | No |
| `scriptEnvFile` | When type is `scriptFile` or `script`, this file is prepended to the input. | `string` | no | No |
| `shell` | Run the command in the shell | `string` | no | No |
| `hooks` | Hooks are used at the end of the individual command. Must have at least `error`, `success`, or `final`. | `map[string][]string` | no | No |
@@ -51,7 +52,12 @@ Get command output when a notification is sent.
Is not required. Can be `true` or `false`.
#### host
### host
{{% notice warning %}}
Depricated: use `hosts` instead.
{{% /notice %}}
{{% notice info %}}
If any `host` is not defined or left blank, the command will run on the local machine.
@@ -66,6 +72,36 @@ For example, say that I have a host defined in my SSH config with the `Host` def
If I assign a value to host as `host: web-prod` and don't specify this value in the `hosts` object, web-prod will be used as the `Host` in searching the SSH config files.
{{% /notice %}}
### hosts
{{% notice info %}}
If any `command.[name].hosts` index is `localhost` or `127.0.0.1`, the command will run on the local machine.
You can also remove the field to have the command run locally.
{{% /notice %}}
Host may or may not be defined in the `hosts` section.
{{% notice info %}}
If any `host` from the commands section does not match any object in the `hosts` section, the `Host` is assumed to be this value. This value will be used to search in the default SSH config files.
For example, say that I have a host defined in my SSH config with the `Host` defined as `web-prod`.
If I assign a value to host as `host: web-prod` and don't specify this value in the `hosts` object, web-prod will be used as the `Host` in searching the SSH config files.
{{% /notice %}}
###### Example:
```yaml
command:
start-some-process:
cmd: start-server
hosts:
- prod-1
- prod-2
```
### shell
If shell is defined, the command will run in the specified shell.
@@ -95,8 +131,9 @@ The following options are available:
The environment variables support expansion:
- using escaped values `$VAR` or `${VAR}`
- using any external directive, and if using the env directive, the variable will be read from a `.env` file
For now, the variables have to be defined in an `.env` file in the same directory that the program is run from.
<!-- For now, the variables expanded have to be defined in an `.env` file in the same directory that the program is run from. -->
If using it with host specified, the SSH server has to be configured to accept those env variables.

6
docs/content/config/commands/packages.md Normal file → Executable file
View File

@@ -8,7 +8,7 @@ This is dedicated to `package` commands. The command `type` field must be `packa
| name | notes | type | required |
| --- | --- | --- | --- |
| `packageName` | The name of a package to be modified. | `string` | yes |
| `packageName` | The name of a package to be modified. | `[]packagemanagercommon.Package` | yes |
| `packageManager` | The name of the package manger to be used. | `string` | yes |
| `packageOperation` | The type of operation to perform. | `string` | yes |
| `packageVersion` | The version of a package. | `string` | no |
@@ -22,7 +22,9 @@ The following is an example of a package command:
update-docker:
type: package
shell: zsh
packageName: docker-ce
packages:
- name: docker-ce
version: 10
packageManager: apt
packageOperation: install
host: debian-based-host

22
docs/content/config/commands/user-commands.md Normal file → Executable file
View File

@@ -6,16 +6,18 @@ description: This is dedicated to user commands.
This is dedicated to `user` commands. The command `type` field must be `user`. User is a type that allows one to perform user operations. There are several additional options available when `type` is `user`:
| name | notes | type | required |
| --- | --- | --- | --- |
| `userName` | The name of a user to be configured. | `string` | yes |
| `userOperation` | The type of operation to perform. | `string` | yes |
| `userID` | The user ID to use. | `string` | no |
| `userGroups` | The groups the user should be added to. | `[]string` | no |
| `userSshPubKeys` | The keys to add to the user's authorized keys. | `[]string` | no |
| `userShell` | The shell for the user. | `string` | no |
| `userHome` | The user's home directory. | `string` | no |
| `userPassword` | The new password value when using the `password` operation. Can be specified by using external directive. | `string` | no |
| name | notes | type | required | External directive support
| ----------------| -------------------------------------------------------------| ---------- | ---------| --------------------------|
| `userName` | The name of a user to be configured. | `string` | yes | no |
| `userOperation` | The type of operation to perform. | `string` | yes | no |
| `userID` | The user ID to use. | `string` | no | no |
| `userGroups` | The groups the user should be added to. | `[]string` | no | no |
| `systemUser` | Create a system user. | `bool` | no | no |
| `userCreateHome`| Create the home directory. | `bool` | no | no |
| `userSshPubKeys`| The keys to add to the user's authorized keys. | `[]string` | no | yes |
| `userShell` | The shell for the user. | `string` | no | no |
| `userHome` | The user's home directory. | `string` | no | no |
| `userPassword` | The new password value when using the `password` operation. | `string` | no | yes |
#### example

0
docs/content/config/directives.md Normal file → Executable file
View File

2
docs/content/config/hosts.md Normal file → Executable file
View File

@@ -21,4 +21,4 @@ description: >
## exec host subcommand
Backy has a subcommand `exec host`. This subcommand takes the flags of `-m host1 -m host2`. For now these hosts need to be defined in the config file.
Backy has a subcommand `exec host`. This subcommand takes the flags of `-m host1 -m host2`. The commands can also be specified by `-c command1 -c command2`.

0
docs/content/config/notifications.md Normal file → Executable file
View File

0
docs/content/config/remote-resources.md Normal file → Executable file
View File

14
docs/content/config/vault.md Normal file → Executable file
View File

@@ -6,7 +6,7 @@ description: Set up and configure vault.
[Vault](https://www.vaultproject.io/) is a tool for storing secrets and other data securely.
Vault config can be used by prefixing `vault:` in front of a password or ENV var.
A Vault key can be used by prefixing `%{vault:vault.keys.name}%` in a field that supports external directives.
This is the object in the config file:
@@ -18,10 +18,12 @@ vault:
keys:
- name: mongourl
mountpath: secret
key: data
path: mongo/url
type: # KVv1 or KVv2
- name:
path:
type:
mountpath:
type: KVv2 # KVv1 or KVv2
- name: someKeyName
mountpath: secret
key: keyData
type: KVv2
path: some/path
```

0
docs/content/examples/backy.yaml Normal file → Executable file
View File

0
docs/content/examples/example.yml Normal file → Executable file
View File

0
docs/content/getting-started/_index.md Normal file → Executable file
View File

8
docs/content/getting-started/config.md Normal file → Executable file
View File

@@ -124,13 +124,13 @@ notifications:
### Logging
cmd-std-out controls whether commands output is echoed to StdOut.
`cmd-std-out` controls whether commands output is echoed to StdOut.
If logfile is not defined, the log file will be written to the config directory in the file `backy.log`.
If `logfile` is not defined, the log file will be written to the config directory in the file `backy.log`.
`console-disabled` controls whether the logging messages are echoed to StdOut. Default is false.
`verbose` basically does nothing as all necessary info is already output.
`verbose` prints out debugging messages.
```yaml
logging:
@@ -144,7 +144,7 @@ logging:
[Vault](https://www.vaultproject.io/) can be used to get some configuration values and ENV variables securely.
```
```yaml
vault:
token: hvs.tXqcASvTP8wg92f7riyvGyuf
address: http://127.0.0.1:8200

0
docs/content/getting-started/install.md Normal file → Executable file
View File

0
docs/content/repositories/_index.md Normal file → Executable file
View File

4
docs/go.mod Normal file → Executable file
View File

@@ -3,6 +3,6 @@ module git.andrewnw.xyz/CyberShell/backy/docs
go 1.19
require (
github.com/McShelby/hugo-theme-relearn v0.0.0-20250103114405-80e448e5bdaa // indirect
github.com/divinerites/plausible-hugo v1.21.1 // indirect
github.com/McShelby/hugo-theme-relearn v0.0.0-20251115105808-d9ca8e8d8f59 // indirect
github.com/divinerites/plausible-hugo v1.22.0 // indirect
)

14
docs/go.sum Normal file → Executable file
View File

@@ -1,10 +1,4 @@
github.com/McShelby/hugo-theme-relearn v0.0.0-20230209073138-890d12ea922d h1:weq1mrQ/qNAvGrNgvZVL1K8adbT3bswZf2ABLr/LCIA=
github.com/McShelby/hugo-theme-relearn v0.0.0-20230209073138-890d12ea922d/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
github.com/McShelby/hugo-theme-relearn v0.0.0-20241210183303-16d4de84becf h1:bMx4kwM7Q+dAzvSOWs3XWZ25o+n4mI0GPHqzbzeWb3M=
github.com/McShelby/hugo-theme-relearn v0.0.0-20241210183303-16d4de84becf/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
github.com/McShelby/hugo-theme-relearn v0.0.0-20250102210630-dd0597ffa4b2 h1:sWaC1/dL65v3iRvblEAaBLpKC5TIT0R9JASk1hZNET8=
github.com/McShelby/hugo-theme-relearn v0.0.0-20250102210630-dd0597ffa4b2/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
github.com/McShelby/hugo-theme-relearn v0.0.0-20250103114405-80e448e5bdaa h1:G+OnMEzK4XOzbbcf1SmaGyOYJ0h5idp/IJdguWs8ioU=
github.com/McShelby/hugo-theme-relearn v0.0.0-20250103114405-80e448e5bdaa/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
github.com/divinerites/plausible-hugo v1.21.1 h1:ZTWwjhZ0PmLMacCVGlcGiYFEZW7VaYE767tchDskOug=
github.com/divinerites/plausible-hugo v1.21.1/go.mod h1:cxr+YB3FUwbLon8KCs4pV4Ankbkq6lJxTQUpNb5KqPo=
github.com/McShelby/hugo-theme-relearn v0.0.0-20251115105808-d9ca8e8d8f59 h1:mnEjz/Wrpv6Hea26KeFJPx94w9g9ZHIurUEWvPdaEvs=
github.com/McShelby/hugo-theme-relearn v0.0.0-20251115105808-d9ca8e8d8f59/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM=
github.com/divinerites/plausible-hugo v1.22.0 h1:2pZheSaIMc+EtwcEeZv0ioU2qBOEZa1Ii7IaR/9II9k=
github.com/divinerites/plausible-hugo v1.22.0/go.mod h1:cxr+YB3FUwbLon8KCs4pV4Ankbkq6lJxTQUpNb5KqPo=

17
docs/layouts/_default/baseof.html Normal file → Executable file
View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
{{- block "storeOutputFormat" . }}{{ end }}
{{- if .IsHome }}
{{- $hugoVersion := "0.126.0" }}
{{- $hugoVersion := "0.141.0" }}
{{- if lt hugo.Version $hugoVersion }}
{{- errorf "The Relearn theme requires Hugo %s or later" $hugoVersion }}
{{- end }}
@@ -36,12 +36,12 @@
{{ (printf $link (partial "permalink.gotmpl" (dict "to" .)) .Rel .MediaType.Type ($title | htmlEscape)) | safeHTML }}
{{- end }}
{{- end }}
{{- partialCached "favicon.html" . }}
{{- partial "stylesheet.html" . }}
{{- partial "dependencies.gotmpl" (dict "page" . "location" "header") }}
{{- partial "dependencies.html" (dict "page" . "location" "header") }}
{{- partial "custom-header.html" . }}
</head>
<body class="mobile-support {{ with .Store.Get "relearnOutputFormat" }}{{ . }}{{ else }}html{{ end }}{{- if .Site.Params.disableInlineCopyToClipBoard }} disableInlineCopyToClipboard{{ end }}{{- if .Site.Params.disableHoverBlockCopyToClipBoard }} disableHoverBlockCopyToClipBoard{{ end }}" data-url="{{ partial "permalink.gotmpl" (dict "to" .) }}">
<body class="mobile-support {{ with .Store.Get "relearnOutputFormat" }}{{ . }}{{ else }}html{{ end }}{{- if .Site.Params.disableHoverBlockCopyToClipBoard }} disableHoverBlockCopyToClipBoard{{ end }}" data-url="{{ partial "permalink.gotmpl" (dict "to" .) }}">
<div id="R-body" class="default-animation">
<div id="R-body-overlay"></div>
<nav id="R-topbar">
@@ -53,7 +53,7 @@
{{- $showBreadcrumb := (and (not .Params.disableBreadcrumb) (not .Site.Params.disableBreadcrumb)) }}
{{- if $showBreadcrumb }}
<ol class="topbar-breadcrumbs breadcrumbs highlightable" itemscope itemtype="http://schema.org/BreadcrumbList">
{{- partial "breadcrumbs.html" (dict "page" .) }}
{{- partial "breadcrumbs.html" (dict "page" . "schema" true) }}
</ol>
{{- else }}
<span class="topbar-breadcrumbs highlightable">
@@ -74,11 +74,8 @@
{{- partial "custom-comments.html" . }}
</div>
{{- block "menu" . }}{{ end }}
{{- $assetBusting := partialCached "assetbusting.gotmpl" . }}
<script src="{{"js/clipboard.min.js" | relURL}}{{ $assetBusting }}" defer></script>
<script src="{{"js/perfect-scrollbar.min.js" | relURL}}{{ $assetBusting }}" defer></script>
{{- partial "dependencies.gotmpl" (dict "page" . "location" "footer") }}
<script src="{{"js/theme.js" | relURL}}{{ $assetBusting }}" defer></script>
{{- partial "dependencies.html" (dict "page" . "location" "footer") }}
{{- partial "custom-footer.html" . }}
<div id="toast-container" role="status" aria-live="polite" aria-atomic="false"></div>
</body>
</html>

0
docs/layouts/partials/logo.html Normal file → Executable file
View File

0
docs/layouts/shortcodes/code.html Normal file → Executable file
View File

0
docs/vangen.json Normal file → Executable file
View File

0
docs/vangen/backy/index.html Normal file → Executable file
View File

18
examples/backy.yaml Normal file → Executable file
View File

@@ -29,20 +29,22 @@ commands:
update-docker:
type: package
shell: zsh # best to run package commands in a shell
packageName: docker-ce
Args:
- docker-ce-cli
packages:
- name: docker-ce
version: latest
- name: docker-ce-cli
version: latest
packageManager: apt
packageOperation: install
update-dockerApt:
# type: package
shell: zsh
cmd: apt
Args:
- update
- "&&"
- apt install -y docker-ce
- docker-ce-cli
packages:
- name: docker-ce
version: latest
- name: docker-ce-cli
version: latest
packageManager: apt
packageOperation: install

3
examples/example.yml Normal file → Executable file
View File

@@ -7,7 +7,8 @@ commands:
- down
# if host is not defined, command will be run locally
# The host has to be defined in either the config file or the SSH Config files
host: some-host
hosts:
- prod
hooks:
error:
- some-other-command-when-failing

0
frontmatter.json Normal file → Executable file
View File

92
go.mod Normal file → Executable file
View File

@@ -1,78 +1,78 @@
module git.andrewnw.xyz/CyberShell/backy
go 1.23
go 1.23.0
toolchain go1.23.6
replace git.andrewnw.xyz/CyberShell/backy => /home/andrew/Projects/backy
toolchain go1.23.7
require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
github.com/dmarkham/enumer v1.5.11
github.com/go-co-op/gocron v1.37.0
github.com/hashicorp/vault/api v1.15.0
github.com/google/uuid v1.6.0
github.com/hashicorp/vault/api v1.20.0
github.com/joho/godotenv v1.5.1
github.com/kevinburke/ssh_config v1.2.0
github.com/knadh/koanf/parsers/yaml v0.1.0
github.com/knadh/koanf/providers/rawbytes v0.1.0
github.com/knadh/koanf/v2 v2.1.2
github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/rawbytes v1.0.0
github.com/knadh/koanf/v2 v2.2.2
github.com/mattn/go-isatty v0.0.20
github.com/minio/minio-go/v7 v7.0.84
github.com/minio/minio-go/v7 v7.0.94
github.com/mitchellh/go-homedir v1.1.0
github.com/nikoksr/notify v1.3.0
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.7
github.com/rs/zerolog v1.33.0
github.com/pkg/sftp v1.13.9
github.com/rs/zerolog v1.34.0
github.com/sethvargo/go-password v0.3.1
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.33.0
github.com/spf13/cobra v1.9.1
golang.org/x/crypto v0.40.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
maunium.net/go/mautrix v0.23.0
mvdan.cc/sh/v3 v3.10.0
maunium.net/go/mautrix v0.24.1
mvdan.cc/sh/v3 v3.12.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/minio/crc64nvme v1.0.2 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pascaldekloe/name v1.0.0 // indirect
github.com/pascaldekloe/name v1.0.1 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rs/xid v1.6.0 // indirect
@@ -84,14 +84,16 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
go.mau.fi/util v0.8.4 // indirect
github.com/tinylib/msgp v1.3.0 // indirect
go.mau.fi/util v0.8.8 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.10.0 // indirect
golang.org/x/tools v0.30.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.35.0 // indirect
)

213
go.sum Normal file → Executable file
View File

@@ -1,31 +1,31 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E=
github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 h1:OIHj/nAhVzIXGzbAE+4XmZ8FPvro3THr6NlqErJc3wY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32/go.mod h1:LiBEsDo34OJXqdDlRGsilhlIiXR7DL+6Cx2f4p1EgzI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6 h1:cCBJaT7EeEojpJ4s7wTDbhZlHVJOgNHN7iw6qVurGaw=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6/go.mod h1:WYH1ABybY7JK9TITPnk6ZlP7gQB8psI4c9qDmMsnLSA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 h1:OBsrtam3rk8NfBEq7OLOMm5HtQ9Yyw32X4UQMya/wjw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13/go.mod h1:3U4gFA5pmoCOja7aq4nSaIAGbaOHv2Yl2ug018cmC+Q=
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0 h1:ehvUZNVrGA1Usa6yYo8A8pUqrigRelWXSbcCqYpRLeI=
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0/go.mod h1:KuLNrwYJFaC2AVZ+CVVc12k9NyqwgWsoNNHjwqF6QNk=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 h1:5Y75q0RPQoAbieyOuGLhjV9P3txvYgXv2lg0UwJOfmE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -40,19 +40,20 @@ github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -65,20 +66,20 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 h1:FW0YttEnUNDJ2WL9XcrrfteS1xW8u+sh4ggM8pN5isQ=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8=
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4=
github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@@ -87,19 +88,19 @@ github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME=
github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c=
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4=
github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
github.com/knadh/koanf/providers/rawbytes v1.0.0 h1:MrKDh/HksJlKJmaZjgs4r8aVBb/zsJyc/8qaSnzcdNI=
github.com/knadh/koanf/providers/rawbytes v1.0.0/go.mod h1:KxwYJf1uezTKy6PBtfE+m725NGp4GPVA7XoNTJ/PtLo=
github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A=
github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -118,10 +119,12 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=
github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -132,34 +135,34 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs=
github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM=
github.com/pascaldekloe/name v1.0.0 h1:n7LKFgHixETzxpRv2R77YgPUFo85QHGZKrdaYm7eY5U=
github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
github.com/pascaldekloe/name v1.0.1 h1:9lnXOHeqeHHnWLbKfH6X98+4+ETVqFqxN09UXSjcMb0=
github.com/pascaldekloe/name v1.0.1/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -183,35 +186,52 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c=
go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -222,32 +242,43 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -259,7 +290,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/mautrix v0.23.0 h1:HNlR19eew5lvrNSL2muhExaGhYdaGk5FfEiA82QqUP4=
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
maunium.net/go/mautrix v0.24.1 h1:09/xi4qTeA03g1n/DPmmqAlT8Cx4QrgwiPlmLVzA9AU=
maunium.net/go/mautrix v0.24.1/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI=
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=

View File

@@ -0,0 +1,149 @@
// Code generated by "enumer -linecomment -yaml -text -json -type=AllowedExternalDirectives"; DO NOT EDIT.
package backy
import (
"encoding/json"
"fmt"
"strings"
)
const _AllowedExternalDirectivesName = "DefaultExternalDirvaultvault-envvault-filevault-file-envfile-envfileenv"
var _AllowedExternalDirectivesIndex = [...]uint8{0, 18, 23, 32, 42, 56, 64, 68, 71}
const _AllowedExternalDirectivesLowerName = "defaultexternaldirvaultvault-envvault-filevault-file-envfile-envfileenv"
func (i AllowedExternalDirectives) String() string {
if i < 0 || i >= AllowedExternalDirectives(len(_AllowedExternalDirectivesIndex)-1) {
return fmt.Sprintf("AllowedExternalDirectives(%d)", i)
}
return _AllowedExternalDirectivesName[_AllowedExternalDirectivesIndex[i]:_AllowedExternalDirectivesIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _AllowedExternalDirectivesNoOp() {
var x [1]struct{}
_ = x[DefaultExternalDir-(0)]
_ = x[AllowedExternalDirectiveVault-(1)]
_ = x[AllowedExternalDirectiveVaultEnv-(2)]
_ = x[AllowedExternalDirectiveVaultFile-(3)]
_ = x[AllowedExternalDirectiveAll-(4)]
_ = x[AllowedExternalDirectiveFileEnv-(5)]
_ = x[AllowedExternalDirectiveFile-(6)]
_ = x[AllowedExternalDirectiveEnv-(7)]
}
var _AllowedExternalDirectivesValues = []AllowedExternalDirectives{DefaultExternalDir, AllowedExternalDirectiveVault, AllowedExternalDirectiveVaultEnv, AllowedExternalDirectiveVaultFile, AllowedExternalDirectiveAll, AllowedExternalDirectiveFileEnv, AllowedExternalDirectiveFile, AllowedExternalDirectiveEnv}
var _AllowedExternalDirectivesNameToValueMap = map[string]AllowedExternalDirectives{
_AllowedExternalDirectivesName[0:18]: DefaultExternalDir,
_AllowedExternalDirectivesLowerName[0:18]: DefaultExternalDir,
_AllowedExternalDirectivesName[18:23]: AllowedExternalDirectiveVault,
_AllowedExternalDirectivesLowerName[18:23]: AllowedExternalDirectiveVault,
_AllowedExternalDirectivesName[23:32]: AllowedExternalDirectiveVaultEnv,
_AllowedExternalDirectivesLowerName[23:32]: AllowedExternalDirectiveVaultEnv,
_AllowedExternalDirectivesName[32:42]: AllowedExternalDirectiveVaultFile,
_AllowedExternalDirectivesLowerName[32:42]: AllowedExternalDirectiveVaultFile,
_AllowedExternalDirectivesName[42:56]: AllowedExternalDirectiveAll,
_AllowedExternalDirectivesLowerName[42:56]: AllowedExternalDirectiveAll,
_AllowedExternalDirectivesName[56:64]: AllowedExternalDirectiveFileEnv,
_AllowedExternalDirectivesLowerName[56:64]: AllowedExternalDirectiveFileEnv,
_AllowedExternalDirectivesName[64:68]: AllowedExternalDirectiveFile,
_AllowedExternalDirectivesLowerName[64:68]: AllowedExternalDirectiveFile,
_AllowedExternalDirectivesName[68:71]: AllowedExternalDirectiveEnv,
_AllowedExternalDirectivesLowerName[68:71]: AllowedExternalDirectiveEnv,
}
var _AllowedExternalDirectivesNames = []string{
_AllowedExternalDirectivesName[0:18],
_AllowedExternalDirectivesName[18:23],
_AllowedExternalDirectivesName[23:32],
_AllowedExternalDirectivesName[32:42],
_AllowedExternalDirectivesName[42:56],
_AllowedExternalDirectivesName[56:64],
_AllowedExternalDirectivesName[64:68],
_AllowedExternalDirectivesName[68:71],
}
// AllowedExternalDirectivesString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func AllowedExternalDirectivesString(s string) (AllowedExternalDirectives, error) {
if val, ok := _AllowedExternalDirectivesNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _AllowedExternalDirectivesNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to AllowedExternalDirectives values", s)
}
// AllowedExternalDirectivesValues returns all values of the enum
func AllowedExternalDirectivesValues() []AllowedExternalDirectives {
return _AllowedExternalDirectivesValues
}
// AllowedExternalDirectivesStrings returns a slice of all String values of the enum
func AllowedExternalDirectivesStrings() []string {
strs := make([]string, len(_AllowedExternalDirectivesNames))
copy(strs, _AllowedExternalDirectivesNames)
return strs
}
// IsAAllowedExternalDirectives returns "true" if the value is listed in the enum definition. "false" otherwise
func (i AllowedExternalDirectives) IsAAllowedExternalDirectives() bool {
for _, v := range _AllowedExternalDirectivesValues {
if i == v {
return true
}
}
return false
}
// MarshalJSON implements the json.Marshaler interface for AllowedExternalDirectives
func (i AllowedExternalDirectives) MarshalJSON() ([]byte, error) {
return json.Marshal(i.String())
}
// UnmarshalJSON implements the json.Unmarshaler interface for AllowedExternalDirectives
func (i *AllowedExternalDirectives) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return fmt.Errorf("AllowedExternalDirectives should be a string, got %s", data)
}
var err error
*i, err = AllowedExternalDirectivesString(s)
return err
}
// MarshalText implements the encoding.TextMarshaler interface for AllowedExternalDirectives
func (i AllowedExternalDirectives) MarshalText() ([]byte, error) {
return []byte(i.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface for AllowedExternalDirectives
func (i *AllowedExternalDirectives) UnmarshalText(text []byte) error {
var err error
*i, err = AllowedExternalDirectivesString(string(text))
return err
}
// MarshalYAML implements a YAML Marshaler for AllowedExternalDirectives
func (i AllowedExternalDirectives) MarshalYAML() (interface{}, error) {
return i.String(), nil
}
// UnmarshalYAML implements a YAML Unmarshaler for AllowedExternalDirectives
func (i *AllowedExternalDirectives) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
var err error
*i, err = AllowedExternalDirectivesString(s)
return err
}

669
pkg/backy/backy.go Normal file → Executable file
View File

@@ -11,6 +11,8 @@ import (
"io"
"os"
"os/exec"
"strings"
"sync"
"text/template"
"embed"
@@ -25,6 +27,191 @@ var requiredKeys = []string{"commands"}
var Sprintf = fmt.Sprintf
type CommandExecutor interface {
Run(cmd *Command, opts *ConfigOpts, logger zerolog.Logger) ([]string, error)
}
type OutputHandler interface {
CollectOutput(buf *bytes.Buffer, commandName string, logger zerolog.Logger, wantOutput bool) []string
}
type EnvInjector interface {
Inject(cmd *Command, opts *ConfigOpts)
}
type PackageCommandExecutor struct{}
func (e *PackageCommandExecutor) Run(cmd *Command, opts *ConfigOpts, logger zerolog.Logger) ([]string, error) {
var (
ArgsStr string
cmdOutBuf bytes.Buffer
outputArr []string
cmdOutWriters io.Writer
)
for _, v := range cmd.Args {
ArgsStr += fmt.Sprintf(" %s", v)
}
// Example: Check version operation
if cmd.PackageOperation == PackageOperationCheckVersion {
logger.Info().Msg("Checking package versions")
logger.Info().Msg("")
for _, p := range cmd.Packages {
logger.Info().Str("package", p.Name).Msg("Checking installed and remote package versions")
}
opts.Logger.Info().Msg("")
// Execute the package version command
execCmd := exec.Command(cmd.Cmd, cmd.Args...)
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
}
execCmd.Stdout = cmdOutWriters
execCmd.Stderr = cmdOutWriters
if err := execCmd.Run(); err != nil {
return nil, fmt.Errorf("error running command %s: %w", ArgsStr, err)
}
return parsePackageVersion(cmdOutBuf.String(), logger, cmd, cmdOutBuf)
}
// Other package operations (install, upgrade, etc.) can be handled here
// Default: run as a shell command
execCmd := exec.Command(cmd.Cmd, cmd.Args...)
execCmd.Stdout = &cmdOutBuf
execCmd.Stderr = &cmdOutBuf
err := execCmd.Run()
outputArr = logCommandOutput(cmd, cmdOutBuf, logger, outputArr)
if err != nil {
logger.Error().Err(fmt.Errorf("error running package command %s: %w", cmd.Name, err)).Send()
return outputArr, err
}
return outputArr, nil
}
type LocalCommandExecutor struct{}
func (e *LocalCommandExecutor) Run(cmd *Command, opts *ConfigOpts, logger zerolog.Logger) ([]string, error) {
var (
ArgsStr string
cmdOutBuf bytes.Buffer
outputArr []string
)
for _, v := range cmd.Args {
ArgsStr += fmt.Sprintf(" %s", v)
}
// Build the command
var localCMD *exec.Cmd
if cmd.Shell != "" {
logger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine in %s", cmd.Name, cmd.Shell)).Send()
ArgsStr = fmt.Sprintf("%s %s", cmd.Cmd, ArgsStr)
localCMD = exec.Command(cmd.Shell, "-c", ArgsStr)
} else {
localCMD = exec.Command(cmd.Cmd, cmd.Args...)
}
// Set working directory
if cmd.Dir != nil {
localCMD.Dir = *cmd.Dir
}
// Inject environment variables (extract this to an EnvInjector if desired)
// injectEnvIntoLocalCMD(...)
// Set output writers
cmdOutWriters := io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
}
localCMD.Stdout = cmdOutWriters
localCMD.Stderr = cmdOutWriters
// Run the command
err := localCMD.Run()
outputArr = logCommandOutput(cmd, cmdOutBuf, logger, outputArr)
if err != nil {
logger.Error().Err(fmt.Errorf("error when running cmd %s: %w", cmd.Name, err)).Send()
return outputArr, err
}
return outputArr, nil
}
// ensureRemoteHost ensures localCmd.RemoteHost is set for the given host.
// It prefers opts.Hosts lookup and falls back to a minimal Host entry so remote execution can proceed.
func (opts *ConfigOpts) ensureRemoteHost(localCmd *Command, host string) {
if localCmd.RemoteHost != nil {
return
}
if opts != nil && opts.Hosts != nil {
if rh, found := opts.Hosts[host]; found {
localCmd.RemoteHost = rh
return
}
}
// 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.
localCmd.RemoteHost = &Host{Host: host}
}
// ExecCommandOnHostsParallel runs a single configured command concurrently on the command.Hosts list.
// It reuses the standard RunCmd / RunCmdOnHost flow so the behavior is identical to normal execution.
func (opts *ConfigOpts) ExecCommandOnHostsParallel(cmdName string) ([]CmdResult, error) {
cmdObj, ok := opts.Cmds[cmdName]
if !ok {
return nil, fmt.Errorf("command %s not found", cmdName)
}
if len(cmdObj.Hosts) == 0 {
return nil, fmt.Errorf("no hosts configured for command %s", cmdName)
}
var wg sync.WaitGroup
resultsCh := make(chan CmdResult, len(cmdObj.Hosts))
for _, host := range cmdObj.Hosts {
wg.Add(1)
go func(h string) {
defer wg.Done()
// shallow copy to avoid races
local := *cmdObj
local.Host = h
opts.Logger.Debug().Str("host", h).Msg("executing command in parallel on host")
var err error
if IsHostLocal(h) {
_, err := local.RunCmd(local.GenerateLogger(opts), opts)
resultsCh <- CmdResult{CmdName: cmdName, ListName: "", Error: err}
return
// _, err = local.RunCmd(local.GenerateLogger(opts), opts)
}
// ensure RemoteHost is populated before calling RunCmdOnHost
opts.ensureRemoteHost(&local, h)
_, err = local.RunCmdOnHost(local.GenerateLogger(opts), opts)
resultsCh <- CmdResult{CmdName: cmdName, ListName: "", Error: err}
}(host)
}
wg.Wait()
close(resultsCh)
var results []CmdResult
for r := range resultsCh {
results = append(results, r)
}
return results, nil
}
// RunCmd runs a Command.
// The environment of local commands will be the machine's environment plus any extra
// variables specified in the Env file or Environment.
@@ -34,7 +221,7 @@ var Sprintf = fmt.Sprintf
func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
var (
ArgsStr string // concatenating the arguments
ArgsStr string
cmdOutBuf bytes.Buffer
cmdOutWriters io.Writer
errSSH error
@@ -47,6 +234,14 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
outputArr []string // holds the output strings returned by processes
)
if command.Host != "" && command.Hosts != nil {
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")
} else if command.Hosts != nil {
opts.ExecCommandOnHostsParallel(command.Name)
return nil, nil
}
// Getting the command type must be done before concatenating the arguments
command = getCommandTypeAndSetCommandInfo(command)
@@ -54,38 +249,29 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
ArgsStr += fmt.Sprintf(" %s", v)
}
if command.Type == UserCT {
if command.Type == UserCommandType {
if command.UserOperation == "password" {
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
}
}
if command.Host != nil {
outputArr, errSSH = command.RunCmdSSH(cmdCtxLogger, opts)
if !IsHostLocal(command.Host) {
outputArr, errSSH = command.RunCmdOnHost(cmdCtxLogger, opts)
if errSSH != nil {
return outputArr, errSSH
}
} else {
// Handle package operations
if command.Type == PackageCT && command.PackageOperation == PackOpCheckVersion {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions")
// Execute the package version command
cmd := exec.Command(command.Cmd, command.Args...)
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
cmd.Stdout = cmdOutWriters
cmd.Stderr = cmdOutWriters
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("error running command %s: %w", ArgsStr, err)
}
return parsePackageVersion(cmdOutBuf.String(), cmdCtxLogger, command, cmdOutBuf)
switch command.Type {
case PackageCommandType:
var executor PackageCommandExecutor
return executor.Run(command, opts, cmdCtxLogger)
}
var localCMD *exec.Cmd
if command.Type == RemoteScriptCT {
if command.Type == RemoteScriptCommandType {
script, err := command.Fetcher.Fetch(command.Cmd)
if err != nil {
return nil, err
@@ -95,15 +281,15 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
command.Shell = "sh"
}
localCMD = exec.Command(command.Shell, command.Args...)
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger)
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger, opts)
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
}
if command.OutputFile != "" {
file, err := os.Create(command.OutputFile)
if command.Output.File != "" {
file, err := os.Create(command.Output.File)
if err != nil {
return nil, fmt.Errorf("error creating output file: %w", err)
}
@@ -136,7 +322,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
if str, ok := outMap["output"].(string); ok {
outputArr = append(outputArr, str)
}
if command.OutputToLog {
if command.Output.ToLog {
cmdCtxLogger.Info().Fields(outMap).Send()
}
}
@@ -157,8 +343,10 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine", command.Name)).Send()
// execute package commands in a shell
if command.Type == PackageCT {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Executing package command")
if command.Type == PackageCommandType {
for _, p := range command.Packages {
cmdCtxLogger.Info().Str("packages", p.Name).Msg("Executing package command")
}
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
} else {
@@ -166,7 +354,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
}
}
if command.Type == UserCT {
if command.Type == UserCommandType {
if command.UserOperation == "password" {
localCMD.Stdin = command.stdin
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
@@ -176,7 +364,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
localCMD.Dir = *command.Dir
}
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger)
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger, opts)
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
@@ -194,20 +382,79 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
return outputArr, err
}
if command.Type == UserCommandType {
if command.UserOperation == "add" {
if command.UserSshPubKeys != nil {
var (
authorizedKeysFile *os.File
err error
userHome []byte
)
cmdCtxLogger.Info().Msg("adding SSH Keys")
localCMD := exec.Command(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
userHome, err = localCMD.CombinedOutput()
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
}
command.UserHome = strings.TrimSpace(string(userHome))
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
if _, err := os.Stat(userSshDir); os.IsNotExist(err) {
err := os.MkdirAll(userSshDir, 0700)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, 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) {
_, err := os.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, 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)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
}
defer authorizedKeysFile.Close()
for _, k := range command.UserSshPubKeys {
buf := bytes.NewBufferString(k)
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
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)
}
}
localCMD = exec.Command(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
_, err = localCMD.CombinedOutput()
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.Output.ToLog), err
}
}
}
}
}
return outputArr, nil
}
func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<- CmdResult, opts *ConfigOpts) {
func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<- string, opts *ConfigOpts) {
for list := range jobs {
fieldsMap := map[string]interface{}{"list": list.Name}
var cmdLogger zerolog.Logger
var commandExecuted *Command
var cmdsRan []string
var outStructArr []outStruct
var hasError bool // Tracks if any command in the list failed
for _, cmd := range list.Order {
cmdToRun := opts.Cmds[cmd]
commandExecuted = cmdToRun
currentCmd := cmdToRun.Name
fieldsMap["cmd"] = currentCmd
cmdLogger = cmdToRun.GenerateLogger(opts)
@@ -218,23 +465,21 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
if runErr != nil {
// Log the error and send a failed result
cmdLogger.Err(runErr).Send()
results <- CmdResult{CmdName: cmd, ListName: list.Name, Error: runErr}
// Execute error hooks for the failed command
cmdToRun.ExecuteHooks("error", opts)
// Notify failure
if list.NotifyConfig != nil {
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun)
}
// Execute error hooks for the failed command
hasError = true
break
}
// Collect output if required
if list.GetOutput || cmdToRun.GetOutputInList {
if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
outStructArr = append(outStructArr, outStruct{
CmdName: currentCmd,
CmdExecuted: currentCmd,
@@ -243,27 +488,239 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
}
}
if !hasError && list.NotifyConfig != nil && (list.NotifyOnSuccess || list.GetOutput) {
if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
}
if !hasError {
commandExecuted.ExecuteHooks("success", opts)
}
commandExecuted.ExecuteHooks("final", opts)
results <- "done"
}
}
func cmdListWorkerWithHosts(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts <-chan *Host, results chan<- string, opts *ConfigOpts) {
for list := range jobs {
fieldsMap := map[string]interface{}{"list": list.Name}
var cmdLogger zerolog.Logger
var commandExecuted *Command
var cmdsRan []string
var outStructArr []outStruct
var hasError bool // Tracks if any command in the list failed
for host := range hosts {
for _, cmd := range list.Order {
cmdToRun := opts.Cmds[cmd]
if cmdToRun.Host != host.Host {
cmdToRun.Host = host.Host
cmdToRun.RemoteHost = host
}
commandExecuted = cmdToRun
currentCmd := cmdToRun.Name
fieldsMap["cmd"] = currentCmd
cmdLogger = cmdToRun.GenerateLogger(opts)
cmdLogger.Info().Fields(fieldsMap).Send()
outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts)
cmdsRan = append(cmdsRan, cmd)
if runErr != nil {
cmdLogger.Err(runErr).Send()
cmdToRun.ExecuteHooks("error", opts)
// Notify failure
if list.NotifyConfig != nil {
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun)
}
// Execute error hooks for the failed command
hasError = true
break
}
if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
outStructArr = append(outStructArr, outStruct{
CmdName: currentCmd,
CmdExecuted: currentCmd,
Output: outputArr,
})
}
}
if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
}
if !hasError {
cmdToRun.ExecuteHooks("success", opts)
commandExecuted.ExecuteHooks("success", opts)
}
// Execute final hooks for every command
cmdToRun.ExecuteHooks("final", opts)
commandExecuted.ExecuteHooks("final", opts)
}
results <- "done"
}
}
// Send the final result for the list
if hasError {
results <- CmdResult{CmdName: cmdsRan[len(cmdsRan)-1], ListName: list.Name, Error: fmt.Errorf("list execution failed")}
} else {
results <- CmdResult{CmdName: cmdsRan[len(cmdsRan)-1], ListName: list.Name, Error: nil}
// func cmdListWorkerExecuteCommandsInParallel(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts <-chan *Host, results chan<- string, opts *ConfigOpts) {
// opts.Logger.Info().Msg("Running commands in parallel")
// for list := range jobs {
// fieldsMap := map[string]interface{}{"list": list.Name}
// var cmdLogger zerolog.Logger
// var commandExecuted *Command
// var cmdsRan []string
// var outStructArr []outStruct
// var hasError bool // Tracks if any command in the list failed
// for _, cmd := range list.Order {
// for host := range hosts {
// cmdToRun := opts.Cmds[cmd]
// if cmdToRun.Host != host.Host {
// cmdToRun.Host = host.Host
// cmdToRun.RemoteHost = host
// }
// commandExecuted = cmdToRun
// currentCmd := cmdToRun.Name
// fieldsMap["cmd"] = currentCmd
// cmdLogger = cmdToRun.GenerateLogger(opts)
// cmdLogger.Info().Fields(fieldsMap).Send()
// outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts)
// cmdsRan = append(cmdsRan, cmd)
// if runErr != nil {
// cmdLogger.Err(runErr).Send()
// cmdToRun.ExecuteHooks("error", opts)
// // Notify failure
// if list.NotifyConfig != nil {
// notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun)
// }
// // Execute error hooks for the failed command
// hasError = true
// break
// }
// if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
// outStructArr = append(outStructArr, outStruct{
// CmdName: currentCmd,
// CmdExecuted: currentCmd,
// Output: outputArr,
// })
// }
// }
// if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
// notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
// }
// if !hasError {
// commandExecuted.ExecuteHooks("success", opts)
// }
// commandExecuted.ExecuteHooks("final", opts)
// }
// results <- "done"
// }
// }
func cmdListWorkerExecuteCommandsInParallel(msgTemps *msgTemplates, jobs <-chan *CmdList, hosts <-chan *Host, results chan<- string, opts *ConfigOpts) {
opts.Logger.Info().Msg("Running commands in parallel")
for list := range jobs {
fieldsMap := map[string]interface{}{"list": list.Name}
var cmdLogger zerolog.Logger
var commandExecuted *Command
var cmdsRan []string
var outStructArr []outStruct
var hasError bool // Tracks if any command in the list failed
var wg sync.WaitGroup
hostList := []*Host{}
for host := range hosts {
hostList = append(hostList, host)
}
println("Total hosts to run commands on:", len(hostList))
println("Total commands to run:", len(list.Order))
for _, cmd := range list.Order {
cmdsRan = append(cmdsRan, cmd)
println("Running cmd:", cmd, "on", len(hostList), "hosts")
outputChan := make(chan outStruct, len(hostList))
errorChan := make(chan error, len(hostList))
// cmdToRun := opts.Cmds[cmd]
origCmd := opts.Cmds[cmd]
for _, host := range hostList {
wg.Add(1)
cmdToRun := *origCmd // shallow copy
commandExecuted = origCmd
if cmdToRun.Host != host.Host {
cmdToRun.Host = host.Host
cmdToRun.RemoteHost = host
}
cmdLogger = cmdToRun.GenerateLogger(opts)
cmdLogger.Info().Fields(fieldsMap).Send()
print("Running cmd on: ", host.Host, "\n")
go func(cmd string, host *Host) {
defer wg.Done()
currentCmd := cmdToRun.Name
fieldsMap["cmd"] = currentCmd
outputArr, runErr := cmdToRun.RunCmd(cmdLogger, opts)
if runErr != nil {
cmdLogger.Err(runErr).Send()
cmdToRun.ExecuteHooks("error", opts)
errorChan <- runErr
return
}
if list.GetCommandOutputInNotificationsOnSuccess || cmdToRun.Output.InList {
outputChan <- outStruct{
CmdName: currentCmd,
CmdExecuted: currentCmd,
Output: outputArr,
}
}
}(cmd, host)
}
wg.Wait()
close(outputChan)
close(errorChan)
for out := range outputChan {
outStructArr = append(outStructArr, out)
}
if len(errorChan) > 0 {
hasError = true
runErr := <-errorChan
if list.NotifyConfig != nil {
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, commandExecuted)
}
break
}
if !hasError && list.NotifyConfig != nil && list.Notify.OnFailure {
notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
}
if !hasError {
commandExecuted.ExecuteHooks("success", opts)
}
}
commandExecuted.ExecuteHooks("final", opts)
results <- "done"
}
}
@@ -312,7 +769,7 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
}
configListsLen := len(opts.CmdConfigLists)
listChan := make(chan *CmdList, configListsLen)
results := make(chan CmdResult, configListsLen)
results := make(chan string, configListsLen)
// Start workers
for w := 1; w <= configListsLen; w++ {
@@ -332,13 +789,66 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
// Process results
for a := 1; a <= configListsLen; a++ {
result := <-results
opts.Logger.Debug().Msgf("Processing result for list %s, command %s", result.ListName, result.CmdName)
<-results
}
opts.closeHostConnections()
}
func (opts *ConfigOpts) ExecuteListOnHosts(lists []string, parallel bool) {
mTemps := &msgTemplates{
err: template.Must(template.New("error.txt").ParseFS(templates, "templates/error.txt")),
success: template.Must(template.New("success.txt").ParseFS(templates, "templates/success.txt")),
}
// for _, l := range opts.CmdConfigLists {
// if !slices.Contains(lists, l.Name) {
// delete(opts.CmdConfigLists, l.Name)
// }
// }
configListsLen := len(opts.CmdConfigLists)
listChan := make(chan *CmdList, configListsLen)
hostChan := make(chan *Host, len(opts.Hosts))
results := make(chan string, configListsLen)
// Start workers
for w := 1; w <= configListsLen; w++ {
if parallel {
go cmdListWorkerExecuteCommandsInParallel(mTemps, listChan, hostChan, results, opts)
} else {
go cmdListWorkerWithHosts(mTemps, listChan, hostChan, results, opts)
}
}
// Enqueue jobs
for listName, cmdConfig := range opts.CmdConfigLists {
if cmdConfig.Name == "" {
cmdConfig.Name = listName
}
listChan <- cmdConfig
}
for _, h := range opts.Hosts {
if h.isProxyHost {
continue
}
hostChan <- h
// for _, proxyHost := range h.ProxyHost {
// if proxyHost.isProxyHost {
// continue
// }
// hostChan <- proxyHost
// }
}
close(listChan)
close(hostChan)
// Process results
for a := 1; a <= configListsLen; a++ {
<-results
}
opts.closeHostConnections()
}
func (opts *ConfigOpts) ExecuteCmds() {
for _, cmd := range opts.executeCmds {
cmdToRun := opts.Cmds[cmd]
@@ -401,30 +911,32 @@ func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
case "error":
for _, v := range cmd.Hooks.Error {
errCmd := opts.Cmds[v]
opts.Logger.Info().Msgf("Running error hook command %s", v)
cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "error").
Logger()
errCmd.RunCmd(cmdLogger, opts)
cmdLogger.Info().Msgf("Running error hook command %s", v)
// URGENT: Never returns
_, _ = errCmd.RunCmd(cmdLogger, opts)
return
}
case "success":
for _, v := range cmd.Hooks.Success {
successCmd := opts.Cmds[v]
opts.Logger.Info().Msgf("Running success hook command %s", v)
cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "success").
Logger()
successCmd.RunCmd(cmdLogger, opts)
cmdLogger.Info().Msgf("Running success hook command %s", v)
_, _ = successCmd.RunCmd(cmdLogger, opts)
}
case "final":
for _, v := range cmd.Hooks.Final {
finalCmd := opts.Cmds[v]
opts.Logger.Info().Msgf("Running final hook command %s", v)
cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "final").
Logger()
finalCmd.RunCmd(cmdLogger, opts)
cmdLogger.Info().Msgf("Running final hook command %s", v)
_, _ = finalCmd.RunCmd(cmdLogger, opts)
}
}
}
@@ -434,28 +946,63 @@ func (cmd *Command) GenerateLogger(opts *ConfigOpts) zerolog.Logger {
Str("Backy-cmd", cmd.Name).Str("Host", "local machine").
Logger()
if cmd.Host != nil {
if !IsHostLocal(cmd.Host) {
cmdLogger = opts.Logger.With().
Str("Backy-cmd", cmd.Name).Str("Host", *cmd.Host).
Str("Backy-cmd", cmd.Name).Str("Host", cmd.Host).
Logger()
}
return cmdLogger
}
func (opts *ConfigOpts) ExecCmdsSSH(cmdList []string, hostsList []string) {
func (opts *ConfigOpts) ExecCmdsOnHosts(cmdList []string, hostsList []string) {
// Iterate over hosts and exec commands
for _, h := range hostsList {
host := opts.Hosts[h]
for _, c := range cmdList {
cmd := opts.Cmds[c]
cmd.RemoteHost = host
cmd.Host = &host.Host
opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
_, err := cmd.RunCmdSSH(cmd.GenerateLogger(opts), opts)
cmd.Host = h
if IsHostLocal(h) {
_, err := cmd.RunCmd(cmd.GenerateLogger(opts), opts)
if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
}
} else {
cmd.Host = host.Host
opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
_, err := cmd.RunCmdOnHost(cmd.GenerateLogger(opts), opts)
if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
}
}
}
}
}
func (opts *ConfigOpts) ExecCmdsOnHostsInParallel(cmdList []string, hostsList []string) {
opts.Logger.Info().Msg("Executing commands in parallel on hosts")
// Iterate over hosts and exec commands
for _, c := range cmdList {
for _, h := range hostsList {
host := opts.Hosts[h]
cmd := opts.Cmds[c]
cmd.RemoteHost = host
cmd.Host = h
if IsHostLocal(h) {
_, err := cmd.RunCmd(cmd.GenerateLogger(opts), opts)
if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
}
} else {
cmd.Host = host.Host
opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
_, err := cmd.RunCmdOnHost(cmd.GenerateLogger(opts), opts)
if err != nil {
opts.Logger.Err(err).Str("host", h).Str("cmd", c).Send()
}
}
}
}
}
@@ -472,7 +1019,7 @@ func logCommandOutput(command *Command, cmdOutBuf bytes.Buffer, cmdCtxLogger zer
if str, ok := outMap["output"].(string); ok {
outputArr = append(outputArr, str)
}
if command.OutputToLog {
if command.Output.ToLog {
cmdCtxLogger.Info().Fields(outMap).Send()
}
}

83
pkg/backy/backy_test.go Executable file
View File

@@ -0,0 +1,83 @@
package backy
// import (
// "context"
// "fmt"
// "io"
// "log"
// "testing"
// "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman"
// packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
// "github.com/stretchr/testify/assert"
// "github.com/stretchr/testify/require"
// "github.com/testcontainers/testcontainers-go"
// )
// // TestConfigOptions tests the configuration options for the backy package.
// func Test_ErrorHook(t *testing.T) {
// configFile := "../../tests/ErrorHook.yml"
// logFile := "ErrorHook.log"
// backyConfigOptions := NewConfigOptions(configFile, SetLogFile(logFile))
// backyConfigOptions.InitConfig()
// backyConfigOptions.ParseConfigurationFile()
// backyConfigOptions.RunListConfig("")
// }
// func TestSettingCommandInfoPackageCommandDnf(t *testing.T) {
// packagecommand := &Command{
// Type: PackageCommandType,
// PackageManager: "dnf",
// Shell: "zsh",
// PackageOperation: PackageOperationCheckVersion,
// Packages: []packagemanagercommon.Package{{Name: "docker-ce"}},
// }
// dnfPackage, _ := pkgman.PackageManagerFactory("dnf", pkgman.WithoutAuth())
// packagecommand.pkgMan = dnfPackage
// PackageCommand := getCommandTypeAndSetCommandInfo(packagecommand)
// assert.Equal(t, "dnf", PackageCommand.Cmd)
// }
// func TestWithDockerFile(t *testing.T) {
// ctx := context.Background()
// docker, err := testcontainers.Run(ctx, "",
// testcontainers.WithDockerfile(testcontainers.FromDockerfile{
// Context: "../../tests/docker",
// Dockerfile: "Dockerfile",
// KeepImage: false,
// // BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) {
// // buildOptions.Target = "target2"
// // },
// }),
// )
// // docker.
// if err != nil {
// log.Printf("failed to start container: %v", err)
// return
// }
// r, err := docker.Logs(ctx)
// if err != nil {
// log.Printf("failed to get logs: %v", err)
// return
// }
// logs, err := io.ReadAll(r)
// if err != nil {
// log.Printf("failed to read logs: %v", err)
// return
// }
// fmt.Println(string(logs))
// require.NoError(t, err)
// }

View File

@@ -25,29 +25,29 @@ func (i CommandType) String() string {
// Re-run the stringer command to generate them again.
func _CommandTypeNoOp() {
var x [1]struct{}
_ = x[DefaultCT-(0)]
_ = x[ScriptCT-(1)]
_ = x[ScriptFileCT-(2)]
_ = x[RemoteScriptCT-(3)]
_ = x[PackageCT-(4)]
_ = x[UserCT-(5)]
_ = x[DefaultCommandType-(0)]
_ = x[ScriptCommandType-(1)]
_ = x[ScriptFileCommandType-(2)]
_ = x[RemoteScriptCommandType-(3)]
_ = x[PackageCommandType-(4)]
_ = x[UserCommandType-(5)]
}
var _CommandTypeValues = []CommandType{DefaultCT, ScriptCT, ScriptFileCT, RemoteScriptCT, PackageCT, UserCT}
var _CommandTypeValues = []CommandType{DefaultCommandType, ScriptCommandType, ScriptFileCommandType, RemoteScriptCommandType, PackageCommandType, UserCommandType}
var _CommandTypeNameToValueMap = map[string]CommandType{
_CommandTypeName[0:0]: DefaultCT,
_CommandTypeLowerName[0:0]: DefaultCT,
_CommandTypeName[0:6]: ScriptCT,
_CommandTypeLowerName[0:6]: ScriptCT,
_CommandTypeName[6:16]: ScriptFileCT,
_CommandTypeLowerName[6:16]: ScriptFileCT,
_CommandTypeName[16:28]: RemoteScriptCT,
_CommandTypeLowerName[16:28]: RemoteScriptCT,
_CommandTypeName[28:35]: PackageCT,
_CommandTypeLowerName[28:35]: PackageCT,
_CommandTypeName[35:39]: UserCT,
_CommandTypeLowerName[35:39]: UserCT,
_CommandTypeName[0:0]: DefaultCommandType,
_CommandTypeLowerName[0:0]: DefaultCommandType,
_CommandTypeName[0:6]: ScriptCommandType,
_CommandTypeLowerName[0:6]: ScriptCommandType,
_CommandTypeName[6:16]: ScriptFileCommandType,
_CommandTypeLowerName[6:16]: ScriptFileCommandType,
_CommandTypeName[16:28]: RemoteScriptCommandType,
_CommandTypeLowerName[16:28]: RemoteScriptCommandType,
_CommandTypeName[28:35]: PackageCommandType,
_CommandTypeLowerName[28:35]: PackageCommandType,
_CommandTypeName[35:39]: UserCommandType,
_CommandTypeLowerName[35:39]: UserCommandType,
}
var _CommandTypeNames = []string{

256
pkg/backy/config.go Normal file → Executable file
View File

@@ -1,7 +1,6 @@
package backy
import (
"context"
"errors"
"fmt"
"net/url"
@@ -96,14 +95,21 @@ func (opts *ConfigOpts) InitConfig() {
} else {
loadDefaultConfigFiles(fetcher, configFiles, backyKoanf, opts)
}
opts.koanf = backyKoanf
}
func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
func (opts *ConfigOpts) ParseConfigurationFile() *ConfigOpts {
setTerminalEnv()
backyKoanf := opts.koanf
if backyKoanf.Exists("variables") {
unmarshalConfigIntoStruct(backyKoanf, "variables", &opts.Vars, opts.Logger)
}
getConfigDir(opts)
opts.loadEnv()
if backyKoanf.Bool(getNestedConfig("logging", "cmd-std-out")) {
@@ -124,15 +130,36 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
log := setupLogger(opts)
opts.Logger = log
hostsFetcher, err := remotefetcher.NewRemoteFetcher(opts.HostsFilePath, opts.Cache)
opts.Logger.Info().Str("hosts file", opts.HostsFilePath).Send()
if err != nil {
logging.ExitWithMSG(fmt.Sprintf("error initializing config fetcher: %v", err), 1, nil)
}
var hostKoanf = koanf.New(".")
if opts.HostsFilePath != "" {
loadConfigFile(hostsFetcher, opts.HostsFilePath, hostKoanf, opts)
unmarshalConfigIntoStruct(hostKoanf, "hosts", &opts.Hosts, opts.Logger)
} else {
unmarshalConfigIntoStruct(backyKoanf, "hosts", &opts.Hosts, opts.Logger)
}
log.Info().Str("config file", opts.ConfigFilePath).Send()
unmarshalConfig(backyKoanf, "commands", &opts.Cmds, opts.Logger)
if err := opts.initializeVault(); err != nil {
log.Err(err).Send()
}
unmarshalConfigIntoStruct(backyKoanf, "commands", &opts.Cmds, opts.Logger)
getCommandEnvironments(opts)
unmarshalConfig(backyKoanf, "hosts", &opts.Hosts, opts.Logger)
getHostConfigs(opts)
resolveHostConfigs(opts)
for k, v := range opts.Vars {
v = getExternalConfigDirectiveValue(v, opts, AllowedExternalDirectiveAll)
opts.Vars[k] = v
}
loadCommandLists(opts, backyKoanf)
@@ -149,25 +176,21 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
filterExecuteLists(opts)
if backyKoanf.Exists("notifications") {
unmarshalConfig(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger)
unmarshalConfigIntoStruct(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger)
}
opts.SetupNotify()
if err := opts.setupVault(); err != nil {
log.Err(err).Send()
}
return opts
}
func loadConfigFile(fetcher remotefetcher.RemoteFetcher, filePath string, k *koanf.Koanf, opts *ConfigOpts) {
func loadConfigFile(fetcher remotefetcher.RemoteFetcher, filePath string, koanfConfigParser *koanf.Koanf, opts *ConfigOpts) {
data, err := fetcher.Fetch(filePath)
if err != nil {
logging.ExitWithMSG(generateFileFetchErrorString(filePath, "config", err), 1, nil)
}
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
if err := koanfConfigParser.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
}
}
@@ -212,23 +235,23 @@ func validateExecCommandsFromCLI(k *koanf.Koanf, opts *ConfigOpts) {
}
}
func setLoggingOptions(k *koanf.Koanf, opts *ConfigOpts) {
isLoggingVerbose := k.Bool(getLoggingKeyFromConfig("verbose"))
func setLoggingOptions(backyKoanf *koanf.Koanf, opts *ConfigOpts) {
isVerboseLoggingSetInConfig := backyKoanf.Bool(getLoggingKeyFromConfig("verbose"))
// if log file is set in config file and not set on command line, use "./backy.log"
logFile := "./backy.log"
if opts.LogFilePath == "" && k.Exists(getLoggingKeyFromConfig("file")) {
logFile = k.String(getLoggingKeyFromConfig("file"))
opts.LogFilePath = logFile
if opts.LogFilePath == "" && backyKoanf.Exists(getLoggingKeyFromConfig("file")) {
opts.LogFilePath = backyKoanf.String(getLoggingKeyFromConfig("file"))
} else {
opts.LogFilePath = "./backy.log"
}
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if isLoggingVerbose {
if isVerboseLoggingSetInConfig {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
os.Setenv("BACKY_LOGLEVEL", fmt.Sprintf("%v", zerolog.GlobalLevel()))
}
if k.Bool(getLoggingKeyFromConfig("console-disabled")) {
if backyKoanf.Bool(getLoggingKeyFromConfig("console-disabled")) {
os.Setenv("BACKY_CONSOLE_LOGGING", "")
} else {
os.Setenv("BACKY_CONSOLE_LOGGING", "enabled")
@@ -240,7 +263,7 @@ func setupLogger(opts *ConfigOpts) zerolog.Logger {
return zerolog.New(writers).With().Timestamp().Logger()
}
func unmarshalConfig(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) {
func unmarshalConfigIntoStruct(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) {
if err := k.UnmarshalWithConf(key, target, koanf.UnmarshalConf{Tag: "yaml"}); err != nil {
logging.ExitWithMSG(fmt.Sprintf("error unmarshaling key %s into struct: %v", key, err), 1, &log)
}
@@ -248,6 +271,9 @@ func unmarshalConfig(k *koanf.Koanf, key string, target interface{}, log zerolog
func getCommandEnvironments(opts *ConfigOpts) {
for cmdName, cmdConf := range opts.Cmds {
if cmdConf.Env == "" {
continue
}
opts.Logger.Debug().Str("env file", cmdConf.Env).Str("cmd", cmdName).Send()
if err := testFile(cmdConf.Env); err != nil {
logging.ExitWithMSG("Could not open file"+cmdConf.Env+": "+err.Error(), 1, &opts.Logger)
@@ -256,18 +282,18 @@ func getCommandEnvironments(opts *ConfigOpts) {
}
}
func resolveHostConfigs(opts *ConfigOpts) {
func getHostConfigs(opts *ConfigOpts) {
for hostConfigName, host := range opts.Hosts {
if host.Host == "" {
host.Host = hostConfigName
}
if host.ProxyJump != "" {
resolveProxyHosts(host, opts)
getProxyHosts(host, opts)
}
}
}
func resolveProxyHosts(host *Host, opts *ConfigOpts) {
func getProxyHosts(host *Host, opts *ConfigOpts) {
proxyHosts := strings.Split(host.ProxyJump, ",")
for _, h := range proxyHosts {
proxyHost, defined := opts.Hosts[h]
@@ -279,16 +305,22 @@ func resolveProxyHosts(host *Host, opts *ConfigOpts) {
}
}
func getConfigDir(opts *ConfigOpts) {
if isRemoteURL(opts.ConfigFilePath) {
p, _ := getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p
} else {
opts.ConfigDir = path.Dir(opts.ConfigFilePath)
}
}
func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
var listConfigFiles []string
var u *url.URL
var p string
// if config file is remote, use the directory of the remote file
if isRemoteURL(opts.ConfigFilePath) {
p, u = getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p
println(p)
// // Still use local list files if a remote config file is used, but use them last
listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()}
} else {
opts.ConfigDir = path.Dir(opts.ConfigFilePath)
@@ -301,17 +333,19 @@ func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
listsConfig := koanf.New(".")
for _, l := range listConfigFiles {
if loadListConfigFile(l, listsConfig, opts) {
break
}
}
if backyKoanf.Exists("cmdLists") {
if backyKoanf.Exists("cmdLists.file") {
loadCmdListsFile(backyKoanf, listsConfig, opts)
} else {
unmarshalConfig(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger)
unmarshalConfigIntoStruct(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger)
}
}
if opts.CmdConfigLists == nil {
for _, l := range listConfigFiles {
if loadListConfigFile(l, listsConfig, opts) {
break
}
}
}
}
@@ -351,7 +385,7 @@ func loadListConfigFile(filePath string, k *koanf.Koanf, opts *ConfigOpts) bool
return false
}
unmarshalConfig(k, "cmdLists", &opts.CmdConfigLists, opts.Logger)
unmarshalConfigIntoStruct(k, "cmdLists", &opts.CmdConfigLists, opts.Logger)
keyNotSupported("cmd-lists", "cmdLists", k, opts, true)
opts.CmdListFile = filePath
return true
@@ -360,6 +394,7 @@ func loadListConfigFile(filePath string, k *koanf.Koanf, opts *ConfigOpts) bool
func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *ConfigOpts) {
opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmdLists.file"))
if !path.IsAbs(opts.CmdListFile) {
// TODO: Needs testing - might cause undefined/unexpected behavior if remote config path is used
opts.CmdListFile = path.Join(path.Dir(opts.ConfigFilePath), opts.CmdListFile)
}
@@ -379,7 +414,7 @@ func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *C
}
keyNotSupported("cmd-lists", "cmdLists", listsConfig, opts, true)
unmarshalConfig(listsConfig, "cmdLists", &opts.CmdConfigLists, opts.Logger)
unmarshalConfigIntoStruct(listsConfig, "cmdLists", &opts.CmdConfigLists, opts.Logger)
opts.Logger.Info().Str("using lists config file", opts.CmdListFile).Send()
}
@@ -436,7 +471,7 @@ func getLoggingKeyFromConfig(key string) string {
// return fmt.Sprintf("cmdLists.%s", list)
// }
func (opts *ConfigOpts) setupVault() error {
func (opts *ConfigOpts) initializeVault() error {
if !opts.koanf.Bool("vault.enabled") {
return nil
}
@@ -457,7 +492,7 @@ func (opts *ConfigOpts) setupVault() error {
token = os.Getenv("VAULT_TOKEN")
}
if strings.TrimSpace(token) == "" {
return fmt.Errorf("no token found, but one was required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN")
return fmt.Errorf("no token found. One is required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN")
}
client.SetToken(token)
@@ -469,76 +504,29 @@ func (opts *ConfigOpts) setupVault() error {
opts.vaultClient = client
for _, v := range opts.VaultKeys {
v.Name = replaceVarInString(opts.Vars, v.Key, opts.Logger)
v.MountPath = replaceVarInString(opts.Vars, v.MountPath, opts.Logger)
}
return nil
}
func getVaultSecret(vaultClient *vault.Client, key *VaultKey) (string, error) {
var (
secret *vault.KVSecret
err error
)
if key.ValueType == "KVv2" {
secret, err = vaultClient.KVv2(key.MountPath).Get(context.Background(), key.Path)
} else if key.ValueType == "KVv1" {
secret, err = vaultClient.KVv1(key.MountPath).Get(context.Background(), key.Path)
} else if key.ValueType != "" {
return "", fmt.Errorf("type %s for key %s not known. Valid types are KVv1 or KVv2", key.ValueType, key.Name)
} else {
return "", fmt.Errorf("type for key %s must be specified. Valid types are KVv1 or KVv2", key.Name)
}
if err != nil {
return "", fmt.Errorf("unable to read secret: %v", err)
}
value, ok := secret.Data[key.Name].(string)
if !ok {
return "", fmt.Errorf("value type assertion failed: %T %#v", secret.Data[key.Name], secret.Data[key.Name])
}
return value, nil
}
func parseVaultKey(keyName string, keys []*VaultKey) (*VaultKey, error) {
for _, k := range keys {
if k.Name == keyName {
return k, nil
}
}
return nil, fmt.Errorf("key %s not found in vault keys", keyName)
}
func GetVaultKey(str string, opts *ConfigOpts, log zerolog.Logger) string {
key, err := parseVaultKey(str, opts.VaultKeys)
if key == nil && err == nil {
return str
}
if err != nil && key == nil {
log.Err(err).Send()
return ""
}
value, secretErr := getVaultSecret(opts.vaultClient, key)
if secretErr != nil {
log.Err(secretErr).Send()
return value
}
return value
}
func processCmds(opts *ConfigOpts) error {
// process commands
for cmdName, cmd := range opts.Cmds {
cmd.GetVariablesFromConf(opts)
cmd.Cmd = replaceVarInString(opts.Vars, cmd.Cmd, opts.Logger)
for i, v := range cmd.Args {
v = replaceVarInString(opts.Vars, v, opts.Logger)
cmd.Args[i] = v
}
if cmd.Name == "" {
cmd.Name = cmdName
}
// println("Cmd.Name = " + cmd.Name)
hooks := cmd.Hooks
// resolve hooks
if hooks != nil {
processHookSuccess := processHooks(cmd, hooks.Error, opts, "error")
@@ -555,9 +543,13 @@ func processCmds(opts *ConfigOpts) error {
}
}
// resolve hosts
if cmd.Host != nil {
host, hostFound := opts.Hosts[*cmd.Host]
if !IsHostLocal(cmd.Host) {
cmdHost := replaceVarInString(opts.Vars, cmd.Host, opts.Logger)
if cmdHost != cmd.Host {
cmd.Host = cmdHost
}
host, hostFound := opts.Hosts[cmd.Host]
if hostFound {
cmd.RemoteHost = host
cmd.RemoteHost.Host = host.Host
@@ -565,12 +557,12 @@ func processCmds(opts *ConfigOpts) error {
cmd.RemoteHost.HostName = host.HostName
}
} else {
opts.Logger.Info().Msgf("adding host %s to host list", *cmd.Host)
opts.Logger.Info().Msgf("adding host %s to host list", cmd.Host)
if opts.Hosts == nil {
opts.Hosts = make(map[string]*Host)
}
opts.Hosts[*cmd.Host] = &Host{Host: *cmd.Host}
cmd.RemoteHost = &Host{Host: *cmd.Host}
opts.Hosts[cmd.Host] = &Host{Host: cmd.Host}
cmd.RemoteHost = &Host{Host: cmd.Host}
}
} else {
@@ -586,15 +578,15 @@ func processCmds(opts *ConfigOpts) error {
}
}
if cmd.Type == PackageCT {
if cmd.Type == PackageCommandType {
if cmd.PackageManager == "" {
return fmt.Errorf("package manager is required for package command %s", cmd.PackageName)
return fmt.Errorf("package manager is required for package command %s", cmd.Name)
}
if cmd.PackageOperation.String() == "" {
return fmt.Errorf("package operation is required for package command %s", cmd.PackageName)
return fmt.Errorf("package operation is required for package command %s", cmd.Name)
}
if cmd.PackageName == "" {
return fmt.Errorf("package name is required for package command %s", cmd.PackageName)
if cmd.Packages == nil {
return fmt.Errorf("package name is required for package command %s", cmd.Name)
}
var err error
@@ -612,11 +604,11 @@ func processCmds(opts *ConfigOpts) error {
}
// Parse user commands
if cmd.Type == UserCT {
if cmd.Type == UserCommandType {
if cmd.Username == "" {
return fmt.Errorf("username is required for user command %s", cmd.Name)
}
cmd.Username = replaceVarInString(opts.Vars, cmd.Username, opts.Logger)
err := detectOSType(cmd, opts)
if err != nil {
opts.Logger.Info().Err(err).Str("command", cmdName).Send()
@@ -629,17 +621,19 @@ func processCmds(opts *ConfigOpts) error {
if cmd.UserOperation == "password" {
opts.Logger.Debug().Msg("changing password for user: " + cmd.Username)
cmd.UserPassword = getExternalConfigDirectiveValue(cmd.UserPassword, opts)
cmd.UserPassword = getExternalConfigDirectiveValue(cmd.UserPassword, opts, AllowedExternalDirectiveAll)
}
if cmd.Host != nil {
host, ok := opts.Hosts[*cmd.Host]
if !IsHostLocal(cmd.Host) {
host, ok := opts.Hosts[cmd.Host]
if ok {
cmd.userMan, err = usermanager.NewUserManager(host.OS)
}
}
for indx, key := range cmd.UserSshPubKeys {
opts.Logger.Debug().Msg("adding SSH Keys")
key = getExternalConfigDirectiveValue(key, opts)
key = getExternalConfigDirectiveValue(key, opts, AllowedExternalDirectiveAll)
cmd.UserSshPubKeys[indx] = key
}
if err != nil {
@@ -651,7 +645,7 @@ func processCmds(opts *ConfigOpts) error {
}
if cmd.Type == RemoteScriptCT {
if cmd.Type == RemoteScriptCommandType {
var fetchErr error
if !isRemoteURL(cmd.Cmd) {
return fmt.Errorf("remoteScript command %s must be a remote resource", cmdName)
@@ -662,9 +656,9 @@ func processCmds(opts *ConfigOpts) error {
}
}
if cmd.OutputFile != "" {
if cmd.Output.File != "" {
var err error
cmd.OutputFile, err = getFullPathWithHomeDir(cmd.OutputFile)
cmd.Output.File, err = getFullPathWithHomeDir(cmd.Output.File)
if err != nil {
return err
}
@@ -701,7 +695,9 @@ func processHooks(cmd *Command, hooks []string, opts *ConfigOpts, hookType strin
}
func detectOSType(cmd *Command, opts *ConfigOpts) error {
if cmd.Host == nil {
if IsHostLocal(cmd.Host) {
if runtime.GOOS == "linux" {
cmd.OS = "linux"
opts.Logger.Info().Msg("Unix/Linux type OS detected")
@@ -710,7 +706,7 @@ func detectOSType(cmd *Command, opts *ConfigOpts) error {
return fmt.Errorf("using an os that is not yet supported for user commands")
}
host, ok := opts.Hosts[*cmd.Host]
host, ok := opts.Hosts[cmd.Host]
if ok {
if host.OS != "" {
return nil
@@ -742,3 +738,25 @@ func keyNotSupported(oldKey, newKey string, koanf *koanf.Koanf, opts *ConfigOpts
}
}
}
func replaceVarInString(vars map[string]string, str string, logger zerolog.Logger) string {
if strings.Contains(str, "%{var:") && strings.Contains(str, "}%") {
logger.Debug().Msgf("replacing vars in string %s", str)
for k, v := range vars {
if strings.Contains(str, "%{var:"+k+"}%") {
str = strings.ReplaceAll(str, "%{var:"+k+"}%", v)
}
}
if strings.Contains(str, "%{var:") && strings.Contains(str, "}%") {
logger.Warn().Msg("could not replace all vars in string")
}
}
return str
}
func (c *Command) GetVariablesFromConf(opts *ConfigOpts) {
c.ScriptEnvFile = replaceVarInString(opts.Vars, c.ScriptEnvFile, opts.Logger)
c.Name = replaceVarInString(opts.Vars, c.Name, opts.Logger)
c.Output.File = replaceVarInString(opts.Vars, c.Output.File, opts.Logger)
c.Host = replaceVarInString(opts.Vars, c.Host, opts.Logger)
}

0
pkg/backy/cron.go Normal file → Executable file
View File

133
pkg/backy/lineinfile.go Executable file
View File

@@ -0,0 +1,133 @@
package backy
import (
"bufio"
"bytes"
"fmt"
"io"
"regexp"
"strings"
"golang.org/x/crypto/ssh"
)
func sshConnect(user, password, host string, port int) (*ssh.Client, error) {
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
addr := fmt.Sprintf("%s:%d", host, port)
return ssh.Dial("tcp", addr, config)
}
func sshReadFile(client *ssh.Client, remotePath string) (string, error) {
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
var b bytes.Buffer
session.Stdout = &b
if err := session.Run(fmt.Sprintf("cat %s", remotePath)); err != nil {
return "", err
}
return b.String(), nil
}
func sshWriteFile(client *ssh.Client, remotePath, content string) error {
session, err := client.NewSession()
if err != nil {
return err
}
defer session.Close()
stdin, err := session.StdinPipe()
if err != nil {
return err
}
go func() {
defer stdin.Close()
io.WriteString(stdin, content)
}()
cmd := fmt.Sprintf("cat > %s", remotePath)
return session.Run(cmd)
}
func lineInString(content, regexpPattern, line string) string {
scanner := bufio.NewScanner(strings.NewReader(content))
var lines []string
found := false
re := regexp.MustCompile(regexpPattern)
for scanner.Scan() {
l := scanner.Text()
if re.MatchString(l) {
found = true
lines = append(lines, line)
} else {
lines = append(lines, l)
}
}
if !found {
lines = append(lines, line)
}
return strings.Join(lines, "\n") + "\n"
}
func Call() {
user := "youruser"
password := "yourpassword"
host := "yourhost"
port := 22
remotePath := "/path/to/remote/file"
client, err := sshConnect(user, password, host, port)
if err != nil {
fmt.Println("SSH connection error:", err)
return
}
defer client.Close()
content, err := sshReadFile(client, remotePath)
if err != nil {
fmt.Println("Read error:", err)
return
}
newContent := lineInString(content, "^foo=", "foo=bar")
if err := sshWriteFile(client, remotePath, newContent); err != nil {
fmt.Println("Write error:", err)
return
}
fmt.Println("Line updated successfully over SSH.")
}
type LineInFile struct {
RemotePath string // Path to the remote file
Pattern string // Regex pattern to match lines
Line string // Line to insert or replace
InsertAfter bool // If true, insert after matched line; else replace
User string // SSH username
Password string // SSH password (use key for production)
Host string // SSH host
Port int // SSH port
regexCompiled *regexp.Regexp // Compiled regex (internal use)
}
// CompileRegex compiles the regex pattern for later use
func (l *LineInFile) CompileRegex() error {
re, err := regexp.Compile(l.Pattern)
if err != nil {
return err
}
l.regexCompiled = re
return nil
}

6
pkg/backy/list.go Normal file → Executable file
View File

@@ -46,9 +46,9 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
}
// is it remote or local
if cmdInfo.Host != nil {
if !IsHostLocal(cmdInfo.Host) {
println()
print("Host: ", *cmdInfo.Host)
print("Host: ", cmdInfo.Host)
println()
} else {
@@ -81,7 +81,7 @@ func (opts *ConfigOpts) ListCommandList(list string) {
// bool for commands not found
// gets set to false if a command is not found
// set to true if the command is found
var listFound bool = false
var listFound bool
var listInfo *CmdList
// check commands in file against cmd
for listInFile, l := range opts.CmdConfigLists {

88
pkg/backy/metrics.go Executable file
View File

@@ -0,0 +1,88 @@
package backy
import (
"encoding/json"
"os"
"time"
)
type MetricFile struct {
Filename string `json:"filename"`
CommandMetrics map[string]*Metrics `json:"commandMetrics"`
ListMetrics map[string]*Metrics `json:"listMetrics"`
}
type Metrics struct {
DateStartedLast string `json:"dateStartedLast"`
DateLastFinished string `json:"dateLastFinished"`
DateLastFinishedSuccessfully string `json:"dateLastFinishedSuccessfully"`
SuccessfulExecutions uint64 `json:"successfulExecutions"`
FailedExecutions uint64 `json:"failedExecutions"`
TotalExecutions uint64 `json:"totalExecutions"`
TotalExecutionTime float64 `json:"lastExecutionTime"` // in seconds
AverageExecutionTime float64 `json:"totalExecutionTime"` // in seconds
SuccessRate float64 `json:"successRate"` // percentage of successful executions
FailureRate float64 `json:"failureRate"` // percentage of failed executions
}
func NewMetrics() *Metrics {
return &Metrics{
DateStartedLast: time.Now().Format(time.RFC3339),
SuccessfulExecutions: 0,
FailedExecutions: 0,
TotalExecutions: 0,
TotalExecutionTime: 0.0,
AverageExecutionTime: 0.0,
SuccessRate: 0.0,
FailureRate: 0.0,
}
}
func NewMetricsFromFile(filename string) *MetricFile {
return &MetricFile{
Filename: filename,
CommandMetrics: make(map[string]*Metrics),
ListMetrics: make(map[string]*Metrics),
}
}
func (m *Metrics) Update(success bool, executionTime float64, dateLastFinished time.Time) {
m.TotalExecutions++
if success {
m.SuccessfulExecutions++
} else {
m.FailedExecutions++
}
m.DateLastFinished = dateLastFinished.Format(time.RFC3339)
m.TotalExecutionTime += executionTime
m.AverageExecutionTime = m.TotalExecutionTime / float64(m.TotalExecutions)
if m.TotalExecutions > 0 {
m.SuccessRate = float64(m.SuccessfulExecutions) / float64(m.TotalExecutions) * 100
m.FailureRate = float64(m.FailedExecutions) / float64(m.TotalExecutions) * 100
}
}
func (metricFile *MetricFile) SaveToFile() error {
data, err := json.MarshalIndent(metricFile, "", " ")
if err != nil {
return err
}
return os.WriteFile(metricFile.Filename, data, 0644)
}
func LoadMetricsFromFile(filename string) (*MetricFile, error) {
jsonData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var metrics MetricFile
err = json.Unmarshal(jsonData, &metrics)
if err != nil {
return nil, err
}
return &metrics, nil
}

Some files were not shown because too many files have changed in this diff Show More