Compare commits

..

No commits in common. "develop" and "remoteResources" have entirely different histories.

87 changed files with 815 additions and 2271 deletions

View File

@ -1 +0,0 @@
*.yaml

View File

@ -0,0 +1,3 @@
kind: Added
body: '[feat]: package `packageOperation` option `checkVersion` implemented'
time: 2025-01-11T21:15:46.207199643-06:00

View File

@ -0,0 +1,3 @@
kind: Added
body: user management added - see docs
time: 2025-01-11T21:18:13.182822019-06:00

View File

@ -0,0 +1,3 @@
kind: Added
body: Support for remote config sources. Only config file and list can be used for now.
time: 2025-01-13T23:12:48.383700682-06:00

View File

@ -0,0 +1,3 @@
kind: Added
body: Cache functionality - still a WIP
time: 2025-01-28T15:35:24.512485671-06:00

View File

@ -0,0 +1,3 @@
kind: Added
body: Flag `--s3-endpoint` for config file fetching from S3
time: 2025-02-07T21:24:29.596055611-06:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: Internal refactoring of config setup
time: 2025-01-13T23:10:07.215735108-06:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: Formatting and sending for notifications
time: 2025-01-13T23:16:22.260458782-06:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: "name of `configfetcher` to `remotefetcher`"
time: 2025-01-28T15:42:04.282668058-06:00

View File

@ -0,0 +1,3 @@
kind: Changed
body: Flags that took comma-separated lists now have to be passed multiple times for each argument.
time: 2025-02-08T15:15:08.457086102-06:00

View File

@ -1,3 +0,0 @@
kind: Changed
body: 'Commands: `host` can now be `localhost` or `127.0.0.1` to run commands locally'
time: 2025-03-21T09:08:49.871021144-05:00

View File

@ -0,0 +1,3 @@
kind: Fixed
body: Parsing of remote URLs when determining list config file path
time: 2025-01-28T15:38:06.957506929-06:00

View File

@ -0,0 +1,3 @@
kind: Fixed
body: Incorrect error notification template value
time: 2025-02-02T19:45:23.872797604-06:00

View File

@ -1,16 +0,0 @@
## v0.10.0 - 2025-03-08
### Added
* Hooks: improved logging when executing
* User commands: adding SSH keys using config key `userSshPubKeys`
* directives: added support for fetching values using directive `%{externalSource:key}%`
### Changed
* Commands: if dir is not specified, run in config dir
* FileDirective: use the config directory if path is not absolute
* Host: changes to case of some keys
* Notifications: added external directive to sensitive keys
### Fixed
* LocalFetcher: return fetch error
* Lists: load file key before attempting to load from current file
* fix: host not in config file, but in ssh config, properly added to hosts struct
* SSH: password authentication bugs
* User commands: change user password works

View File

@ -1,8 +0,0 @@
## 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`

View File

@ -1,6 +0,0 @@
## 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

View File

@ -1,16 +0,0 @@
## v0.7.0 - 2025-02-11
### Added
* [feat]: package `packageOperation` option `checkVersion` implemented
* user management added - see docs
* Support for remote config sources. Only config file and list can be used for now.
* Cache functionality - still a WIP
* Flag `--s3-endpoint` for config file fetching from S3
### Changed
* Internal refactoring of config setup
* Formatting and sending for notifications
* name of `configfetcher` to `remotefetcher`
* Flags that took comma-separated lists now have to be passed multiple times for each argument.
* Hosts passed to `exec host` now checked against default SSH config files
### Fixed
* Parsing of remote URLs when determining list config file path
* Incorrect error notification template value

View File

@ -1,3 +0,0 @@
## v0.7.1 - 2025-02-14
### Fixed
* Incorrect local config file loading logic caused files to not be detected

View File

@ -1,3 +0,0 @@
## v0.7.2 - 2025-02-14
### Fixed
* CI configs

View File

@ -1,3 +0,0 @@
## v0.7.3 - 2025-02-14
### Changed
* GoReleaser configs

View File

@ -1,5 +0,0 @@
## v0.7.4 - 2025-02-14
### Changed
* CI configs
### Fixed
* v0.7.1: Incorrect local config file loading logic caused files to not be detected

View File

@ -1,5 +0,0 @@
## v0.7.5 - 2025-02-14
### Changed
* CI configs
### Fixed
* v0.7.1: Incorrect local config file loading logic caused files to not be detected

View File

@ -1,4 +0,0 @@
## v0.7.6 - 2025-02-14
### Fixed
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
* CI configs

View File

@ -1,4 +0,0 @@
## v0.7.7 - 2025-02-14
### Fixed
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
* CI configs

View File

@ -1,4 +0,0 @@
## v0.7.8 - 2025-02-14
### Fixed
* Github CI config
* v0.7.1: Incorrect local config file loading logic caused files to not be detected

View File

@ -1,6 +0,0 @@
## v0.8.0 - 2025-02-15
### Changed
* Breaking: `cmd-lists` key changed to `cmdLists`
* Properly load list config
* Config file loading properly errors
* CI Configs

View File

@ -1,12 +0,0 @@
## v0.9.0 - 2025-02-28
### Added
* `list` command with subcommands `cmds` and `lists`
* Deprecation and unsupported warnings for old config keys
* CLI flag `--cmdStdOut` to output command's stdout/stderr to stdout
* Command type `remoteScript`. See docs for more info.
### Changed
* change to enums for Command type
* Cache now stores resources by URL hash for ease-of-lookup
* Changed PackageOperation to enums
### Fixed
* Local command's `dir` full path is now found with home directory

View File

@ -1,3 +0,0 @@
## v0.9.1 - 2025-03-01
### Changed
* Use EnvVar AWS_PROFILE to get S3 profile

View File

@ -15,26 +15,26 @@ jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v5
- uses: actions/setup-go@v4
with:
go-version: '1.23'
go-version: '1.20'
cache: true
# More assembly might be required: Docker logins, GPG, etc. It all depends
# on your needs.
- uses: olegtarasov/get-tag@v2.1.4
id: tagName
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
with:
# tagRegex: "foobar-(.*)" # Optional. Returns specified group text as tag name. Full tag string is returned if regex is not defined.
tagRegexGroup: 1 # Optional. Default is 1.
- uses: goreleaser/goreleaser-action@v6
# Optionally strip `v` prefix
strip_v: false
- uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: 2.7.0
args: release --release-notes=".changes/${{ env.GIT_TAG_NAME }}.md" -f .goreleaser/github.yml --clean
version: latest
args: release --release-notes=".changes/${{steps.tag.outputs.tag}}.md" -f .goreleaser/github.yml --clean
env:
GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
GIT_TAG_NAME: ${{ steps.tagName.outputs.tag }}

6
.gitignore vendored
View File

@ -1,10 +1,6 @@
!.changie.yaml
!.changes/**
dist/
.codegpt
*.log
*.sh
/*.yaml
/*.yml
*.sh

View File

@ -6,6 +6,7 @@ before:
builds:
- env:
- CGO_ENABLED=0
- GOPROXY=https://goproxy.io
goos:
- freebsd
- linux
@ -15,7 +16,7 @@ builds:
- arm64
archives:
- formats: tar.gz
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of uname.
name_template: >-
{{ .ProjectName }}_{{ .Version }}_
@ -27,7 +28,7 @@ archives:
# use zip for windows archives
format_overrides:
- goos: windows
formats: [zip]
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:

View File

@ -1,3 +1,4 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
version: 2
before:
@ -16,7 +17,7 @@ builds:
- arm64
archives:
- formats: tar.gz
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of uname.
name_template: >-
{{ .ProjectName }}_{{ .Version }}_
@ -28,7 +29,7 @@ archives:
# use zip for windows archives
format_overrides:
- goos: windows
formats: [zip]
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:

View File

@ -1,18 +1,11 @@
steps:
golang:
image: golang:1.23
release:
image: goreleaser/goreleaser
commands:
- 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:
GITEA_TOKEN:
from_secret: gitea_token
secrets: [ gitea_token ]
when:
event: tag
# release:
# image: goreleaser/goreleaser
# commands:
when:
- event: tag

View File

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

View File

@ -23,14 +23,7 @@ steps:
- echo "$SSH_DEPLOY_KEY" | tr -d '\r' | DISPLAY=":0.0" SSH_ASKPASS=~/.ssh/.print_ssh_password setsid ssh-add -
- rsync -atv --delete --progress public/ backy@backy.cybershell.xyz:docs
- rsync -atv --delete --progress vangen/ backy@backy.cybershell.xyz:vangen-go
environment:
SSH_HOST_KEY:
from_secret: ssh_host_key
SSH_DEPLOY_KEY:
from_secret: ssh_deploy_key
SSH_PASSPHRASE:
from_secret: ssh_passphrase
secrets: [ ssh_host_key, ssh_deploy_key, ssh_passphrase ]
when:
- branch: master
- path: 'docs/**'
- branch: master

View File

@ -6,122 +6,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
## 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
* User commands: adding SSH keys using config key `userSshPubKeys`
* directives: added support for fetching values using directive `%{externalSource:key}%`
### Changed
* Commands: if dir is not specified, run in config dir
* FileDirective: use the config directory if path is not absolute
* Host: changes to case of some keys
* Notifications: added external directive to sensitive keys
### Fixed
* LocalFetcher: return fetch error
* Lists: load file key before attempting to load from current file
* fix: host not in config file, but in ssh config, properly added to hosts struct
* SSH: password authentication bugs
* User commands: change user password works
## v0.9.1 - 2025-03-01
### Changed
* Use EnvVar AWS_PROFILE to get S3 profile
## v0.9.0 - 2025-02-28
### Added
* `list` command with subcommands `cmds` and `lists`
* Deprecation and unsupported warnings for old config keys
* CLI flag `--cmdStdOut` to output command's stdout/stderr to stdout
* Command type `remoteScript`. See docs for more info.
### Changed
* change to enums for Command type
* Cache now stores resources by URL hash for ease-of-lookup
* Changed PackageOperation to enums
### Fixed
* Local command's `dir` full path is now found with home directory
## v0.8.0 - 2025-02-15
### Changed
* Breaking: `cmd-lists` key changed to `cmdLists`
* Properly load list config
* Config file loading properly errors
* CI Configs
## v0.7.8 - 2025-02-14
### Fixed
* Github CI config
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
## v0.7.7 - 2025-02-14
### Fixed
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
* CI configs
## v0.7.6 - 2025-02-14
### Fixed
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
* CI configs
## v0.7.5 - 2025-02-14
### Changed
* CI configs
### Fixed
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
## v0.7.4 - 2025-02-14
### Changed
* CI configs
### Fixed
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
## v0.7.3 - 2025-02-14
### Changed
* GoReleaser configs
## v0.7.2 - 2025-02-14
### Fixed
* CI configs
## v0.7.1 - 2025-02-14
### Fixed
* Incorrect local config file loading logic caused files to not be detected
## v0.7.0 - 2025-02-11
### Added
* [feat]: package `packageOperation` option `checkVersion` implemented
* user management added - see docs
* Support for remote config sources. Only config file and list can be used for now.
* Cache functionality - still a WIP
* Flag `--s3-endpoint` for config file fetching from S3
### Changed
* Internal refactoring of config setup
* Formatting and sending for notifications
* name of `configfetcher` to `remotefetcher`
* Flags that took comma-separated lists now have to be passed multiple times for each argument.
* Hosts passed to `exec host` now checked against default SSH config files
### Fixed
* Parsing of remote URLs when determining list config file path
* Incorrect error notification template value
## v0.6.1 - 2025-01-04
### Fixed
* When running a list, hooks now run explicitly after the command executes. Fixed panic due to improper logic.
## v0.6.0 - 2025-01-04
### Added
* Command Type Package - allows one to perform package operations [docs](https://backy.cybershell.xyz/config/packages/)

View File

@ -7,19 +7,14 @@
],
"settings": {
"cSpell.words": [
"Autorestic",
"changie",
"Cmds",
"CMDSTDOUT",
"goreleaser",
"remotefetcher",
"knadh",
"koanf",
"mattn",
"maunium",
"mautrix",
"nikoksr",
"rawbytes",
"remotefetcher",
"Strs"
]
}

View File

@ -30,7 +30,7 @@ func init() {
}
func Backup(cmd *cobra.Command, args []string) {
backyConfOpts := backy.NewOpts(cfgFile, backy.AddCommandLists(cmdLists), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
backyConfOpts := backy.NewOpts(cfgFile, backy.AddCommandLists(cmdLists))
backyConfOpts.InitConfig()
backyConfOpts.ReadConfig()

View File

@ -18,11 +18,7 @@ var (
func cron(cmd *cobra.Command, args []string) {
parseS3Config()
opts := backy.NewOpts(cfgFile,
backy.EnableCron(),
backy.SetLogFile(logFile),
backy.SetCmdStdOut(cmdStdOut))
opts := backy.NewOpts(cfgFile, backy.EnableCron())
opts.InitConfig()
opts.ReadConfig()

View File

@ -32,7 +32,7 @@ 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.NewOpts(cfgFile, backy.AddCommands(args), backy.SetLogFile(logFile))
opts.InitConfig()
opts.ReadConfig()
opts.ExecuteCmds()

View File

@ -15,16 +15,14 @@ var (
}
)
// Holds list of hosts to run commands on
var hostsList []string
// Holds command list to run
var hostsList []string
var cmdList []string
func init() {
hostExecCommand.Flags().StringArrayVarP(&hostsList, "hosts", "m", nil, "Accepts space-separated names of hosts. Specify multiple times for multiple hosts.")
hostExecCommand.Flags().StringArrayVarP(&cmdList, "command", "c", nil, "Accepts space-separated names of commands. Specify multiple times for multiple commands.")
hostExecCommand.Flags().StringArrayVarP(&hostsList, "hosts", "m", nil, "Accepts space-separated names of hosts.")
hostExecCommand.Flags().StringArrayVarP(&cmdList, "command", "c", nil, "Accepts space-separated names of commands.")
parseS3Config()
}
@ -35,7 +33,7 @@ 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.NewOpts(cfgFile, backy.SetLogFile(logFile))
backyConfOpts.InitConfig()
backyConfOpts.ReadConfig()
@ -44,18 +42,13 @@ func Host(cmd *cobra.Command, args []string) {
if hostsList == nil {
logging.ExitWithMSG("error: hosts must be specified", 1, &backyConfOpts.Logger)
}
// host is only checked when we read the SSH File
// so a check may not be needed here
// but we can check if the host is in the config file
for _, h := range hostsList {
// 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)
if !hostFoundInConfig {
logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger)
}
// create host with hostname and host
backyConfOpts.Hosts[h] = &backy.Host{Host: h, HostName: s}
logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger)
}
}
if cmdList == nil {

View File

@ -6,29 +6,16 @@ package cmd
import (
"git.andrewnw.xyz/CyberShell/backy/pkg/backy"
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"github.com/spf13/cobra"
)
var (
listCmd = &cobra.Command{
Use: "list [command]",
Short: "List commands, lists, or hosts defined in config file.",
Long: "List commands, lists, or hosts defined in config file",
}
listCmds = &cobra.Command{
Use: "cmds [cmd1 cmd2 cmd3...]",
Short: "List commands defined in config file.",
Long: "List commands defined in config file",
Run: ListCmds,
}
listCmdLists = &cobra.Command{
Use: "lists [list1 list2 ...]",
Short: "List lists defined in config file.",
Long: "List lists defined in config file",
Run: ListCmdLists,
Use: "list [--list=list1,list2,... | -l list1, list2,...] [ -cmd cmd1 cmd2 cmd3...]",
Short: "Lists commands, lists, or hosts defined in config file.",
Long: "Backup lists commands or groups defined in config file.\nUse the --lists or -l flag to list the specified lists. If not flag is not given, all lists will be executed.",
Run: List,
}
)
@ -36,51 +23,27 @@ var listsToList []string
var cmdsToList []string
func init() {
listCmd.AddCommand(listCmds, listCmdLists)
listCmd.Flags().StringSliceVarP(&listsToList, "lists", "l", nil, "Accepts comma-separated names of command lists to list.")
listCmd.Flags().StringSliceVarP(&cmdsToList, "cmds", "c", nil, "Accepts comma-separated names of commands to list.")
}
func ListCmds(cmd *cobra.Command, args []string) {
func List(cmd *cobra.Command, args []string) {
// setup based on whats passed in:
// - cmds
// - lists
// - if none, list all commands
if len(args) > 0 {
cmdsToList = args
} else {
logging.ExitWithMSG("Error: list cmds subcommand needs commands to list", 1, nil)
}
if cmdLists != nil {
}
parseS3Config()
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile))
opts := backy.NewOpts(cfgFile)
opts.InitConfig()
opts.ReadConfig()
for _, v := range cmdsToList {
opts.ListCommand(v)
}
}
func ListCmdLists(cmd *cobra.Command, args []string) {
parseS3Config()
if len(args) > 0 {
listsToList = args
} else {
logging.ExitWithMSG("Error: lists subcommand needs lists", 1, nil)
}
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile))
opts.InitConfig()
opts.ReadConfig()
for _, v := range listsToList {
opts.ListCommandList(v)
}
opts.ListCommand("rm-sn-db")
}

View File

@ -15,7 +15,6 @@ var (
// Used for flags.
cfgFile string
verbose bool
cmdStdOut bool
logFile string
s3Endpoint string
@ -36,7 +35,6 @@ func Execute() {
func init() {
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", "", "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().BoolVarP(&verbose, "verbose", "v", false, "Sets verbose level")

View File

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

View File

@ -14,21 +14,18 @@ Usage:
backy [command]
Available Commands:
backup Runs commands defined in config file. Use -l flag multiple times to run multiple lists.
backup Runs commands defined in config file.
completion Generate the autocompletion script for the specified shell
cron Starts a scheduler that runs lists defined in config file.
exec Runs commands defined in config file in order given.
help Help about any command
list List commands, lists, or hosts defined in config file.
list Lists commands, lists, or hosts defined in config file.
version Prints the version and exits
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.
-v, --verbose Sets verbose level
-f, --config string config file to read from
-h, --help help for backy
-v, --verbose Sets verbose level
Use "backy [command] --help" for more information about a command.
```
@ -42,18 +39,15 @@ Backup executes commands defined in config file.
Use the --lists or -l flag to execute the specified lists. If not flag is not given, all lists will be executed.
Usage:
backy backup [--lists=list1 --lists list2 ... | -l list1 -l list2 ...] [flags]
backy backup [--lists=list1,list2,... | -l list1, list2,...] [flags]
Flags:
-h, --help help for backup
-l, --lists stringArray Accepts comma-separated names of command lists to execute.
-h, --help help for backup
-l, --lists strings Accepts comma-separated names of command lists to execute.
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.
-v, --verbose Sets verbose level
-f, --config string config file to read from
-v, --verbose Sets verbose level
```
## cron
@ -68,11 +62,8 @@ Flags:
-h, --help help for cron
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.
-v, --verbose Sets verbose level
-f, --config string config file to read from
-v, --verbose Sets verbose level
```
## exec
@ -91,11 +82,8 @@ Flags:
-h, --help help for exec
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.
-v, --verbose Sets verbose level
-f, --config string config file to read from
-v, --verbose Sets verbose level
Use "backy exec [command] --help" for more information about a command.
```
@ -107,19 +95,16 @@ Host executes specified commands on the hosts defined in config file.
Use the --commands or -c flag to choose the commands.
Usage:
backy exec host [--command=command1 --command=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] [flags]
backy exec host [--commands=command1,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] [flags]
Flags:
-c, --command stringArray Accepts space-separated names of commands. Specify multiple times for multiple commands.
-h, --help help for host
-m, --hosts stringArray Accepts space-separated names of hosts. Specify multiple times for multiple hosts.
-c, --commands strings Accepts comma-separated names of commands.
-h, --help help for host
-m, --hosts strings Accepts comma-separated names of hosts.
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.
-v, --verbose Sets verbose level
-f, --config string config file to read from
-v, --verbose Sets verbose level
```
## version
@ -136,70 +121,25 @@ Flags:
-V, --vpre Output the version with v prefixed.
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.
-v, --verbose Sets verbose level
-f, --config string config file to read from
-v, --verbose Sets verbose level
```
## list
```
List commands, lists, or hosts defined in config file
Backup lists commands or groups defined in config file.
Use the --lists or -l flag to list the specified lists. If not flag is not given, all lists will be executed.
Usage:
backy list [command]
Available Commands:
cmds List commands defined in config file.
lists List lists defined in config file.
backy list [--list=list1,list2,... | -l list1, list2,...] [ -cmd cmd1 cmd2 cmd3...] [flags]
Flags:
-h, --help help for list
-c, --cmds strings Accepts comma-separated names of commands to list.
-h, --help help for list
-l, --lists strings Accepts comma-separated names of command lists to list.
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.
-v, --verbose Sets verbose level
Use "backy list [command] --help" for more information about a command.
```
## list cmds
```
List commands defined in config file
Usage:
backy list cmds [cmd1 cmd2 cmd3...] [flags]
Flags:
-h, --help help for cmds
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.
-v, --verbose Sets verbose level
```
## list lists
```
List lists defined in config file
Usage:
backy list lists [list1 list2 ...] [flags]
Flags:
-h, --help help for lists
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.
-v, --verbose Sets verbose level
-f, --config string config file to read from
-v, --verbose Sets verbose level
```

View File

@ -7,13 +7,13 @@ The `exec` subcommand can do some things that the configuration file can't do ye
`exec host` takes the following arguments:
```sh
-c, --commands strings Accepts space-separated names of commands.
-c, --commands strings Accepts comma-separated names of commands.
-h, --help help for host
-m, --hosts strings Accepts space-separated names of hosts.
-m, --hosts strings Accepts comma-separated names of hosts.
```
The commands have to be defined in the config file. The hosts need to at least be in the ssh_config(5) file.
```sh
backy exec host [--commands=command1 -commands=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] [flags]
backy exec host [--commands=command1,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] [flags]
```

View File

@ -1,29 +0,0 @@
---
title: List
---
List commands, lists, or hosts defined in config file
Usage:
```
backy list [command]
```
Available Commands:
cmds List commands defined in config file.
lists List lists defined in config file.
Flags:
```
-h, --help help for list
```
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.
-v, --verbose Sets verbose level
```

View File

@ -2,7 +2,7 @@
title: "Command Lists"
weight: 2
description: >
This page tells you how to use command lists.
This page tells you how to get use command lists.
---
Command lists are for executing commands in sequence and getting notifications from them.
@ -11,8 +11,8 @@ The top-level object key can be anything you want but not the same as another.
Lists can go in a separate file. Command lists should be in a separate file if:
1. key 'cmdLists.file' is specified
2. lists.yml or lists.yaml is found in the same directory as the backy config file (this includes remote config files as of v0.7.0)
1. key 'cmd-lists.file' is specified
2. lists.yml or lists.yaml is found in the same directory as the backy config file
{{% notice info %}}
The lists file is also checked in remote resources.
@ -70,14 +70,14 @@ Name is optional. If name is not defined, name will be the object's map key.
Backy also has a cron mode, so one can run `backy cron` and start a process that schedules jobs to run at times defined in the configuration file.
Adding `cron: 0 0 1 * * *` to a `cmdLists` object will schedule the list at 1 in the morning. See [https://crontab.guru/](https://crontab.guru/) for reference.
Adding `cron: 0 0 1 * * *` to a `cmd-lists` object will schedule the list at 1 in the morning. See [https://crontab.guru/](https://crontab.guru/) for reference.
{{% notice tip %}}
Note: Backy uses the second field of cron, so add anything except `*` to the beginning of a regular cron expression.
{{% /notice %}}
```yaml {lineNos="true" wrap="true" title="yaml"}
cmdLists:
cmd-lists:
  docker-container-backup: # this can be any name you want
    # all commands have to be defined
    order:

View File

@ -8,21 +8,46 @@ weight: 1
### Example Config
{{% code file="/examples/example.yml" language="yaml" %}}
```yaml
commands:
stop-docker-container:
cmd: docker
Args:
- compose
- -f /some/path/to/docker-compose.yaml
- 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
hooks
error:
- some-other-command-when-failing
success:
- success-command
final:
- final-command
backup-docker-container-script:
cmd: /path/to/local/script
# script file is input as stdin to SSH
type: scriptFile # also can be script
environment:
- FOO=BAR
- APP=$VAR
```
Values available for this section **(case-sensitive)**:
| name | notes | type | required | External directive support |
| ----------------| ------------------------------------------------------------------------------------------------------- | --------------------- | -------- |----------------------------|
| `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 | 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 |
| `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 |
| name | notes | type | required
| --- | --- | --- | --- |
| `cmd` | Defines the command to execute | `string` | yes |
| `Args` | Defines the arguments to the command | `[]string` | no |
| `environment` | Defines environment variables for the command | `[]string` | no |
| `type` | See documentation further down the page. Additional fields may be required. | `string` | no |
| `getOutput` | Command(s) output is in the notification(s) | `bool` | no |
| `host` | If not specified, the command will execute locally. | `string` | no |
| `scriptEnvFile` | When type is `scriptFile` or `script`, this file is prepended to the input. | `string` | no |
| `shell` | Run the command in the shell | `string` | no |
| `hooks` | Hooks are used at the end of the individual command. Must have at least `error`, `success`, or `final`. | `map[string][]string` | no |
#### cmd
@ -95,9 +120,8 @@ 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 expanded have to be defined in an `.env` file in the same directory that the program is run from. -->
For now, the variables 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.

View File

@ -6,18 +6,14 @@ 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 | 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 |
| 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` | yes |
| `userGroups` | The groups the user should be added to. | `[]string` | yes |
| `userShell` | The shell for the user. | `string` | yes |
| `userHome` | The user's home directory. | `string` | no |
#### example

View File

@ -1,15 +0,0 @@
---
title: "External Directives"
weight: 2
description: How to set up external directives.
---
External directives are for including data that should not be in the config file. The following directives are supported:
- `%{file:path/to/file}%`
- `%{env:ENV_VAR}%`
- `%{vault:vault-key}%`
See the docs of each command if the field is supported.
If the file path does not begin with a `/`, the config file's directory will be used as the starting point.

View File

@ -5,19 +5,19 @@ description: >
This page tells you how to use hosts.
---
| Key | Description | Type | Required | External directive support |
|----------------------|---------------------------------------------------------------|----------|----------|----------------------------|
| `OS` | Operating system of the host (used for package commands) | `string` | no | No |
| `config` | Path to the SSH config file | `string` | no | No |
| `host` | Specifies the `Host` ssh_config(5) directive | `string` | yes | No |
| `hostname` | Hostname of the host | `string` | no | No |
| `knownHostsFile` | Path to the known hosts file | `string` | no | No |
| `port` | Port number to connect to | `uint16` | no | No |
| `proxyjump` | Proxy jump hosts, comma-separated | `string` | no | No |
| `password` | Password for SSH authentication | `string` | no | No |
| `privateKeyPath` | Path to the private key file | `string` | no | No |
| `privateKeyPassword` | Password for the private key file | `string` | no | Yes |
| `user` | Username for SSH authentication | `string` | no | No |
| Key | Description | Type | Required |
|----------------------|---------------------------------------------------------------|----------|----------|
| `OS` | Operating system of the host (used for package commands) | `string` | no |
| `config` | Path to the SSH config file | `string` | no |
| `host` | Specifies the `Host` ssh_config(5) directive | `string` | yes |
| `hostname` | Hostname of the host | `string` | no |
| `knownhostsfile` | Path to the known hosts file | `string` | no |
| `port` | Port number to connect to | `uint16` | no |
| `proxyjump` | Proxy jump hosts, comma-separated | `string` | no |
| `password` | Password for SSH authentication | `string` | no |
| `privatekeypath` | Path to the private key file | `string` | no |
| `privatekeypassword` | Password for the private key file | `string` | no |
| `user` | Username for SSH authentication | `string` | no |
## exec host subcommand

View File

@ -39,23 +39,23 @@ There must be a section with an id (eg. `mail.test-svr`) following one of these
### mail
| key | description | type | External directive support |
| --- | --- | --- | --- |
| `host` | Specifies the SMTP host to connect to | `string` | no
| `port` | Specifies the SMTP port | `uint16` | no
| `senderaddress` | Address from which to send mail | `string` | no
| `to` | Recipients to send emails to | `[]string` | no
| `username` | SMTP username | `string` | no
| `password` | SMTP password | `string` | yes
| key | description | type
| --- | --- | ---
| `host` | Specifies the SMTP host to connect to | `string`
| `port` | Specifies the SMTP port | `uint16`
| `senderaddress` | Address from which to send mail | `string`
| `to` | Recipients to send emails to | `[]string`
| `username` | SMTP username | `string`
| `password` | SMTP password | `string`
### matrix
| key | description | type | External directive support |
| --- | --- | ---| ---- |
| `home-server` | Specifies the Matrix server connect to | `string` | no
| `room-id` | Specifies the room ID of the room to send messages to | `string` | no
| `access-token` | Matrix access token | `string` | yes
| `user-id` | Matrix user ID | `string` | no
| key | description | type
| --- | --- | ---
| `home-server` | Specifies the Matrix server connect to | `string`
| `room-id` | Specifies the room ID of the room to send messages to | `string`
| `access-token` | Matrix access token | `string`
| `user-id` | Matrix user ID | `string`
To get your access token (assumes you are using [Element](https://element.io/)) :

View File

@ -12,10 +12,6 @@ For the main config file to be fetched remotely, pass the URL using `-f [url]`.
If using S3, you should use the s3 protocol URI: `s3://bucketName/key/path`. You will also need to set the env variable `S3_ENDPOINT` to the appropriate value. The flag `--s3-endpoint` can be used to override this value or to set this value, if not already set.
## Authentication
Currently, only the AWS authentication credentials file `~/.aws/credentials` is supported. For now, the environment variable `AWS_PROFILE` is used to lookup the profile.
## Scripts
Remote script support is currently limited to http/https endpoints.
Scripts will be coming later.

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.
A Vault key can be used by prefixing `%{vault:vault.keys.name}%` in a field that supports external directives.
Vault config can be used by prefixing `vault:` in front of a password or ENV var.
This is the object in the config file:
@ -18,12 +18,10 @@ vault:
keys:
- name: mongourl
mountpath: secret
key: data
path: mongo/url
type: KVv2 # KVv1 or KVv2
- name: someKeyName
mountpath: secret
key: keyData
type: KVv2
path: some/path
type: # KVv1 or KVv2
- name:
path:
type:
mountpath:
```

View File

@ -1,108 +0,0 @@
commands:
stop-docker-container:
cmd: docker
Args:
- compose
- -f /some/path/to/docker-compose.yaml
- down
# if host is not defined, cmd will be run locally
host: some-host
hooks:
final:
- hostname
error:
- hostname
backup-docker-container-script:
cmd: /path/to/script
# The host has to be defined in the config file
host: some-host
environment:
- FOO=BAR
- APP=$VAR
shell-cmd:
cmd: rsync
shell: bash
Args:
- -av some-host:/path/to/data ~/Docker/Backups/docker-data
hostname:
cmd: hostname
update-docker:
type: package
shell: zsh # best to run package commands in a shell
packageName: docker-ce
Args:
- docker-ce-cli
packageManager: apt
packageOperation: install
update-dockerApt:
# type: package
shell: zsh
cmd: apt
Args:
- update
- "&&"
- apt install -y docker-ce
- docker-ce-cli
packageManager: apt
packageOperation: install
cmd-lists:
cmds-to-run: # this can be any name you want
# all commands have to be defined
order:
- stop-docker-container
- backup-docker-container-script
- shell-cmd
- hostname
notifications:
- matrix.matrix
name: backup-some-server
cron: "0 0 1 * * *"
hostname:
name: hostname
order:
- hostname
notifications:
- mail.prod-email
hosts:
# any ssh_config(5) keys/values not listed here will be looked up in the config file or the default config file
some-host:
hostname: some-hostname
config: ~/.ssh/config
user: user
privateKeyPath: /path/to/private/key
port: 22
# can also be env:VAR
password: file:/path/to/file
# only one is supported for now
proxyjump: some-proxy-host
# optional
logging:
verbose: true
file: ./backy.log
console: false
cmd-std-out: false
notifications:
mail:
prod-email:
id: prod-email
type: mail
host: yourhost.tld
port: 587
senderAddress: email@domain.tld
to:
- admin@domain.tld
username: smtp-username@domain.tld
password: your-password-here
matrix:
matrix:
id: matrix
type: matrix
home-server: your-home-server.tld
room-id: room-id
access-token: your-access-token
user-id: your-user-id

View File

@ -1,24 +0,0 @@
commands:
stop-docker-container:
cmd: docker
Args:
- compose
- -f /some/path/to/docker-compose.yaml
- 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
hooks:
error:
- some-other-command-when-failing
success:
- success-command
final:
- final-command
backup-docker-container-script:
cmd: /path/to/local/script
# script file is input as stdin to SSH
type: scriptFile # also can be script
environment:
- FOO=BAR
- APP=$VAR

View File

@ -48,7 +48,7 @@ commands:
To execute groups of commands in sequence, use a list configuration.
```yaml
cmdLists:
cmd-lists:
cmds-to-run: # this can be any name you want
# all commands have to be defined in the commands section
order:
@ -97,7 +97,7 @@ hosts:
The notifications object can have two forms.
For more, [see the notification object documentation](/config/notifications). The top-level map key is id that has to be referenced by the `cmdLists` key `notifications`.
For more, [see the notification object documentation](/config/notifications). The top-level map key is id that has to be referenced by the `cmd-lists` key `notifications`.
```yaml
notifications:

View File

@ -1,3 +0,0 @@
{{ $file := .Get "file" | readFile }}
{{ $lang := .Get "language" }}
{{ (print "```" $lang "\n" $file "\n```") }}

View File

@ -1,24 +0,0 @@
commands:
stop-docker-container:
cmd: docker
Args:
- compose
- -f /some/path/to/docker-compose.yaml
- 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
hooks:
error:
- some-other-command-when-failing
success:
- success-command
final:
- final-command
backup-docker-container-script:
cmd: /path/to/local/script
# script file is input as stdin to SSH
type: scriptFile # also can be script
environment:
- FOO=BAR
- APP=$VAR

View File

@ -1,83 +1,67 @@
#!/bin/bash
CLI_PAGE="docs/content/cli/_index.md"
BACKYCOMMAND="go run backy.go"
{
echo "---
title: CLI
title: "CLI"
weight: 4
---
This page lists documentation for the CLI.
"
" > _index.md
echo "## Backy "
echo " "
echo "\`\`\`"
eval "${BACKYCOMMAND} -h"
echo "\`\`\`"
echo " "
BACKYCOMMAND="go run backy.go"
echo "## Backy " >> _index.md
echo " " >> _index.md
echo "\`\`\`" >> _index.md
eval "${BACKYCOMMAND} -h >> _index.md"
echo "\`\`\`" >> _index.md
echo " " >> _index.md
echo "# Subcommands" >> _index.md
echo "" >> _index.md
echo "# Subcommands"
echo ""
echo "## backup" >> _index.md
echo "" >> _index.md
echo "\`\`\`" >> _index.md
eval "${BACKYCOMMAND} backup -h >> _index.md"
echo "\`\`\`" >> _index.md
echo "" >> _index.md
echo "## backup"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} backup -h"
echo "\`\`\`"
echo ""
echo "## cron" >> _index.md
echo "" >> _index.md
echo "\`\`\`" >> _index.md
eval "${BACKYCOMMAND} cron -h >> _index.md"
echo "\`\`\`" >> _index.md
echo "" >> _index.md
echo "## cron"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} cron -h"
echo "\`\`\`"
echo ""
echo "## exec" >> _index.md
echo "" >> _index.md
echo "\`\`\`" >> _index.md
eval "${BACKYCOMMAND} exec -h >> _index.md"
echo "\`\`\`" >> _index.md
echo "" >> _index.md
echo "## exec"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} exec -h"
echo "\`\`\`"
echo ""
echo "### exec host"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} exec host -h"
echo "\`\`\`"
echo ""
echo "### exec host" >> _index.md
echo "" >> _index.md
echo "\`\`\`" >> _index.md
eval "${BACKYCOMMAND} exec host -h >> _index.md"
echo "\`\`\`" >> _index.md
echo "" >> _index.md
echo "## version"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} version -h"
echo "\`\`\`"
echo ""
echo "## version" >> _index.md
echo "" >> _index.md
echo "\`\`\`" >> _index.md
eval "${BACKYCOMMAND} version -h >> _index.md"
echo "\`\`\`" >> _index.md
echo "" >> _index.md
echo "## list"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} list -h"
echo "\`\`\`"
echo "## list cmds"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} list cmds -h"
echo "\`\`\`"
echo "## list lists"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} list lists -h"
echo "\`\`\`"
} >> _index.md
echo "## list" >> _index.md
echo "" >> _index.md
echo "\`\`\`" >> _index.md
eval "${BACKYCOMMAND} list -h >> _index.md"
echo "\`\`\`" >> _index.md
mv _index.md "$CLI_PAGE"

18
go.mod
View File

@ -2,13 +2,13 @@ module git.andrewnw.xyz/CyberShell/backy
go 1.23
toolchain go1.23.7
toolchain go1.23.1
replace git.andrewnw.xyz/CyberShell/backy => /home/andrew/Projects/backy
require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0
github.com/dmarkham/enumer v1.5.11
github.com/go-co-op/gocron v1.37.0
github.com/google/uuid v1.6.0
github.com/hashicorp/vault/api v1.15.0
github.com/joho/godotenv v1.5.1
github.com/kevinburke/ssh_config v1.2.0
@ -20,7 +20,6 @@ require (
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/sethvargo/go-password v0.3.1
github.com/spf13/cobra v1.8.1
@ -43,13 +42,16 @@ require (
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/cenkalti/backoff/v3 v3.2.2 // 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/v3 v3.0.3 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // 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
@ -64,15 +66,14 @@ require (
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/kr/fs v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // 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/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
@ -85,11 +86,10 @@ require (
go.mau.fi/util v0.8.4 // 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/net v0.34.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
maunium.net/go/maulogger/v2 v2.4.1 // indirect
)

141
go.sum
View File

@ -1,58 +1,95 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
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.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
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.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA=
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.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
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.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26/go.mod h1:zfgMpwHDXX2WGoG84xG2H+ZlPTkJUU4YUvx2svLQYWo=
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.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
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.4.7 h1:tB4tNw83KcajNAzaIMhkhVI2Nt8fAZd5A5ro113FEMY=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7/go.mod h1:lvpyBGkZ3tZ9iSsUIcC2EWp+0ywa7aK3BLT+FwZi+mQ=
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.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0=
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.7 h1:Hi0KGbrnr57bEHWM0bJ1QcBzxLrL/k2DHvGYhb8+W1w=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7/go.mod h1:wKNgWgExdjjrm4qvfbTorkvocEstaoDl4WCvGfeCy9c=
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.72.0 h1:SAfh4pNx5LuTafKKWR02Y+hL3A+3TX8cTKG1OIAJaBk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.0/go.mod h1:r+xl5yzMk9083rMR+sJ5TYj9Tihvf/l1oxzZXDgGj2Q=
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.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dmarkham/enumer v1.5.11 h1:quorLCaEfzjJ23Pf7PB9lyyaHseh91YfTM/sAD/4Mbo=
github.com/dmarkham/enumer v1.5.11/go.mod h1:yixql+kDDQRYqcuBM2n9Vlt7NoT9ixgXhaXry8vmRg8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-co-op/gocron v1.33.1 h1:wjX+Dg6Ae29a/f9BSQjY1Rl+jflTpW9aDyMqseCj78c=
github.com/go-co-op/gocron v1.33.1/go.mod h1:NLi+bkm4rRSy1F8U7iacZOz0xPseMoIOnvabGoSe/no=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
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/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
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-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/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
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.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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=
@ -61,22 +98,35 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs=
github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
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.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
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-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.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
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/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
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.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU=
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
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.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ=
github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8=
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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -98,10 +148,10 @@ github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP
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.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
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/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=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@ -111,10 +161,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
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=
@ -122,43 +178,53 @@ 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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
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=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/nikoksr/notify v0.41.0 h1:4LGE41GpWdHX5M3Xo6DlWRwS2WLDbOq1Rk7IzY4vjmQ=
github.com/nikoksr/notify v0.41.0/go.mod h1:FoE0UVPeopz1Vy5nm9vQZ+JVmYjEIjQgbFstbkw+cRE=
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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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/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.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
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.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -167,13 +233,18 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -184,37 +255,51 @@ github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso
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/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718 h1:hmm5bZqE0M8+Uvys0HJPCSbAIZIwYtTkBKYPjAWHuMM=
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718/go.mod h1:AxuJUMCxpzgJ5eV9JbPWKRH8aAJJidxetNdUj7qcb84=
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
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-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
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/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -222,33 +307,39 @@ 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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.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/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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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/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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -259,7 +350,13 @@ 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/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
maunium.net/go/mautrix v0.16.0 h1:iUqCzJE2yqBC1ddAK6eAn159My8rLb4X8g4SFtQh2Dk=
maunium.net/go/mautrix v0.16.0/go.mod h1:XAjE9pTSGcr6vXaiNgQGiip7tddJ8FQV1a29u2QdBG4=
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.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=

View File

@ -1,145 +0,0 @@
// Code generated by "enumer -linecomment -yaml -text -json -type=AllowedExternalDirectives"; DO NOT EDIT.
package backy
import (
"encoding/json"
"fmt"
"strings"
)
const _AllowedExternalDirectivesName = "DefaultExternalDirvaultvault-filevault-file-envfile-envfileenv"
var _AllowedExternalDirectivesIndex = [...]uint8{0, 18, 23, 33, 47, 55, 59, 62}
const _AllowedExternalDirectivesLowerName = "defaultexternaldirvaultvault-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[AllowedExternalDirectiveVaultFile-(2)]
_ = x[AllowedExternalDirectiveAll-(3)]
_ = x[AllowedExternalDirectiveFileEnv-(4)]
_ = x[AllowedExternalDirectiveFile-(5)]
_ = x[AllowedExternalDirectiveEnv-(6)]
}
var _AllowedExternalDirectivesValues = []AllowedExternalDirectives{DefaultExternalDir, AllowedExternalDirectiveVault, 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:33]: AllowedExternalDirectiveVaultFile,
_AllowedExternalDirectivesLowerName[23:33]: AllowedExternalDirectiveVaultFile,
_AllowedExternalDirectivesName[33:47]: AllowedExternalDirectiveAll,
_AllowedExternalDirectivesLowerName[33:47]: AllowedExternalDirectiveAll,
_AllowedExternalDirectivesName[47:55]: AllowedExternalDirectiveFileEnv,
_AllowedExternalDirectivesLowerName[47:55]: AllowedExternalDirectiveFileEnv,
_AllowedExternalDirectivesName[55:59]: AllowedExternalDirectiveFile,
_AllowedExternalDirectivesLowerName[55:59]: AllowedExternalDirectiveFile,
_AllowedExternalDirectivesName[59:62]: AllowedExternalDirectiveEnv,
_AllowedExternalDirectivesLowerName[59:62]: AllowedExternalDirectiveEnv,
}
var _AllowedExternalDirectivesNames = []string{
_AllowedExternalDirectivesName[0:18],
_AllowedExternalDirectivesName[18:23],
_AllowedExternalDirectivesName[23:33],
_AllowedExternalDirectivesName[33:47],
_AllowedExternalDirectivesName[47:55],
_AllowedExternalDirectivesName[55:59],
_AllowedExternalDirectivesName[59:62],
}
// 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
}

View File

@ -11,7 +11,6 @@ import (
"io"
"os"
"os/exec"
"strings"
"text/template"
"embed"
@ -35,34 +34,34 @@ var Sprintf = fmt.Sprintf
func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
var (
ArgsStr string // concatenating the arguments
outputArr []string
ArgsStr string
cmdOutBuf bytes.Buffer
cmdOutWriters io.Writer
errSSH error
envVars = environmentVars{
file: command.Env,
env: command.Environment,
}
outputArr []string // holds the output strings returned by processes
)
// Getting the command type must be done before concatenating the arguments
command = getCommandTypeAndSetCommandInfo(command)
// Get the command type
// This must be done before concatenating the arguments
command = getCommandType(command)
for _, v := range command.Args {
ArgsStr += fmt.Sprintf(" %s", v)
}
if command.Type == UserCT {
if command.Type == "user" {
if command.UserOperation == "password" {
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
}
}
if !IsHostLocal(command.Host) {
var errSSH error
// is host defined
if command.Host != nil {
outputArr, errSSH = command.RunCmdSSH(cmdCtxLogger, opts)
if errSSH != nil {
return outputArr, errSSH
@ -70,7 +69,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
} else {
// Handle package operations
if command.Type == PackageCT && command.PackageOperation == PackOpCheckVersion {
if command.Type == "package" && command.PackageOperation == "checkVersion" {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions")
// Execute the package version command
@ -87,66 +86,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
}
var localCMD *exec.Cmd
if command.Type == RemoteScriptCT {
script, err := command.Fetcher.Fetch(command.Cmd)
if err != nil {
return nil, err
}
if command.Shell == "" {
command.Shell = "sh"
}
localCMD = exec.Command(command.Shell, command.Args...)
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 err != nil {
return nil, fmt.Errorf("error creating output file: %w", err)
}
defer file.Close()
cmdOutWriters = io.MultiWriter(file, &cmdOutBuf)
if IsCmdStdOutEnabled() {
cmdOutWriters = io.MultiWriter(os.Stdout, file, &cmdOutBuf)
}
}
localCMD.Stdin = bytes.NewReader(script)
localCMD.Stdout = cmdOutWriters
localCMD.Stderr = cmdOutWriters
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running remoteScript %s on local machine in %s", command.Cmd, command.Shell)).Send()
err = localCMD.Run()
if err != nil {
return nil, fmt.Errorf("error running remote script: %w", err)
}
outScanner := bufio.NewScanner(&cmdOutBuf)
for outScanner.Scan() {
outMap := make(map[string]interface{})
outMap["cmd"] = command.Cmd
outMap["output"] = outScanner.Text()
if str, ok := outMap["output"].(string); ok {
outputArr = append(outputArr, str)
}
if command.OutputToLog {
cmdCtxLogger.Info().Fields(outMap).Send()
}
}
return outputArr, nil
}
var err error
if command.Shell != "" {
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine in %s", command.Name, command.Shell)).Send()
@ -159,7 +99,7 @@ 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 {
if command.Type == "package" {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Executing package command")
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
localCMD = exec.Command("/bin/sh", "-c", ArgsStr)
@ -168,17 +108,11 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
}
}
if command.Type == UserCT {
if command.UserOperation == "password" {
localCMD.Stdin = command.stdin
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
}
}
if command.Dir != nil {
localCMD.Dir = *command.Dir
}
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger, opts)
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger)
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
@ -191,68 +125,24 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
err = localCMD.Run()
outputArr = logCommandOutput(command, cmdOutBuf, cmdCtxLogger, outputArr)
outScanner := bufio.NewScanner(&cmdOutBuf)
for outScanner.Scan() {
outMap := make(map[string]interface{})
outMap["cmd"] = command.Cmd
outMap["output"] = outScanner.Text()
if str, ok := outMap["output"].(string); ok {
outputArr = append(outputArr, str)
}
// if command.GetOutput {
cmdCtxLogger.Info().Fields(outMap).Send()
// }
}
if err != nil {
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
return outputArr, err
}
if command.Type == UserCT {
if command.UserOperation == "add" {
if command.UserSshPubKeys != nil {
var (
f *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.OutputToLog), 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.OutputToLog), 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.OutputToLog), fmt.Errorf("error creating file %s/authorized_keys: %v", userSshDir, err)
}
}
f, err = os.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), 0700, os.ModeAppend)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
}
defer f.Close()
for _, k := range command.UserSshPubKeys {
buf := bytes.NewBufferString(k)
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
if _, err := f.ReadFrom(buf); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), 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.OutputToLog), err
}
}
}
}
}
return outputArr, nil
}
@ -293,7 +183,7 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
}
// Collect output if required
if list.GetOutput || cmdToRun.GetOutputInList {
if list.GetOutput || cmdToRun.GetOutput {
outStructArr = append(outStructArr, outStruct{
CmdName: currentCmd,
CmdExecuted: currentCmd,
@ -302,14 +192,17 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
}
}
// Notify success if no errors occurred
if !hasError && list.NotifyConfig != nil && (list.NotifyOnSuccess || list.GetOutput) {
notifySuccess(cmdLogger, msgTemps, list, cmdsRan, outStructArr)
}
// Execute success and final hooks for all commands
for _, cmd := range list.Order {
cmdToRun := opts.Cmds[cmd]
if !hasError {
// Execute success hooks if the command succeeded
if !hasError || cmdsRanContains(cmd, cmdsRan) {
cmdToRun.ExecuteHooks("success", opts)
}
@ -326,6 +219,17 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
}
}
// Helper to check if a command is in the list of executed commands
func cmdsRanContains(cmd string, cmdsRan []string) bool {
for _, c := range cmdsRan {
if c == cmd {
return true
}
}
return false
}
// Helper to notify errors
func notifyError(logger zerolog.Logger, templates *msgTemplates, list *CmdList, cmdsRan []string, outStructArr []outStruct, err error, cmd *Command) {
errStruct := map[string]interface{}{
"listName": list.Name,
@ -394,6 +298,7 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
result := <-results
opts.Logger.Debug().Msgf("Processing result for list %s, command %s", result.ListName, result.CmdName)
// Process final hooks for the list (already handled in worker)
}
opts.closeHostConnections()
}
@ -405,8 +310,10 @@ func (opts *ConfigOpts) ExecuteCmds() {
_, runErr := cmdToRun.RunCmd(cmdLogger, opts)
if runErr != nil {
opts.Logger.Err(runErr).Send()
cmdToRun.ExecuteHooks("error", opts)
} else {
cmdToRun.ExecuteHooks("success", opts)
}
@ -414,6 +321,7 @@ func (opts *ConfigOpts) ExecuteCmds() {
}
opts.closeHostConnections()
}
func (c *ConfigOpts) closeHostConnections() {
@ -460,30 +368,27 @@ 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").
Str("backy-cmd", v).
Logger()
_, _ = errCmd.RunCmd(cmdLogger, opts)
errCmd.RunCmd(cmdLogger, opts)
}
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").
Str("backy-cmd", v).
Logger()
_, _ = successCmd.RunCmd(cmdLogger, opts)
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").
Str("backy-cmd", v).
Logger()
_, _ = finalCmd.RunCmd(cmdLogger, opts)
finalCmd.RunCmd(cmdLogger, opts)
}
}
}
@ -493,10 +398,11 @@ func (cmd *Command) GenerateLogger(opts *ConfigOpts) zerolog.Logger {
Str("Backy-cmd", cmd.Name).Str("Host", "local machine").
Logger()
if !IsHostLocal(cmd.Host) {
if cmd.Host != nil {
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
}
@ -508,7 +414,7 @@ func (opts *ConfigOpts) ExecCmdsSSH(cmdList []string, hostsList []string) {
for _, c := range cmdList {
cmd := opts.Cmds[c]
cmd.RemoteHost = host
cmd.Host = host.Host
cmd.Host = &host.Host
opts.Logger.Info().Str("host", h).Str("cmd", c).Send()
_, err := cmd.RunCmdSSH(cmd.GenerateLogger(opts), opts)
if err != nil {
@ -518,32 +424,6 @@ func (opts *ConfigOpts) ExecCmdsSSH(cmdList []string, hostsList []string) {
}
}
func logCommandOutput(command *Command, cmdOutBuf bytes.Buffer, cmdCtxLogger zerolog.Logger, outputArr []string) []string {
outScanner := bufio.NewScanner(&cmdOutBuf)
for outScanner.Scan() {
outMap := make(map[string]interface{})
outMap["cmd"] = command.Name
outMap["output"] = outScanner.Text()
if str, ok := outMap["output"].(string); ok {
outputArr = append(outputArr, str)
}
if command.OutputToLog {
cmdCtxLogger.Info().Fields(outMap).Send()
}
}
return outputArr
}
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.OutputFile = replaceVarInString(opts.Vars, c.OutputFile, opts.Logger)
c.Host = replaceVarInString(opts.Vars, c.Host, opts.Logger)
}
// func executeUserCommands() []string {
// }

View File

@ -1,141 +0,0 @@
// Code generated by "enumer -linecomment -yaml -text -json -type=CommandType"; DO NOT EDIT.
package backy
import (
"encoding/json"
"fmt"
"strings"
)
const _CommandTypeName = "scriptscriptFileremoteScriptpackageuser"
var _CommandTypeIndex = [...]uint8{0, 0, 6, 16, 28, 35, 39}
const _CommandTypeLowerName = "scriptscriptfileremotescriptpackageuser"
func (i CommandType) String() string {
if i < 0 || i >= CommandType(len(_CommandTypeIndex)-1) {
return fmt.Sprintf("CommandType(%d)", i)
}
return _CommandTypeName[_CommandTypeIndex[i]:_CommandTypeIndex[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 _CommandTypeNoOp() {
var x [1]struct{}
_ = x[DefaultCT-(0)]
_ = x[ScriptCT-(1)]
_ = x[ScriptFileCT-(2)]
_ = x[RemoteScriptCT-(3)]
_ = x[PackageCT-(4)]
_ = x[UserCT-(5)]
}
var _CommandTypeValues = []CommandType{DefaultCT, ScriptCT, ScriptFileCT, RemoteScriptCT, PackageCT, UserCT}
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,
}
var _CommandTypeNames = []string{
_CommandTypeName[0:0],
_CommandTypeName[0:6],
_CommandTypeName[6:16],
_CommandTypeName[16:28],
_CommandTypeName[28:35],
_CommandTypeName[35:39],
}
// CommandTypeString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func CommandTypeString(s string) (CommandType, error) {
if val, ok := _CommandTypeNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _CommandTypeNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to CommandType values", s)
}
// CommandTypeValues returns all values of the enum
func CommandTypeValues() []CommandType {
return _CommandTypeValues
}
// CommandTypeStrings returns a slice of all String values of the enum
func CommandTypeStrings() []string {
strs := make([]string, len(_CommandTypeNames))
copy(strs, _CommandTypeNames)
return strs
}
// IsACommandType returns "true" if the value is listed in the enum definition. "false" otherwise
func (i CommandType) IsACommandType() bool {
for _, v := range _CommandTypeValues {
if i == v {
return true
}
}
return false
}
// MarshalJSON implements the json.Marshaler interface for CommandType
func (i CommandType) MarshalJSON() ([]byte, error) {
return json.Marshal(i.String())
}
// UnmarshalJSON implements the json.Unmarshaler interface for CommandType
func (i *CommandType) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return fmt.Errorf("CommandType should be a string, got %s", data)
}
var err error
*i, err = CommandTypeString(s)
return err
}
// MarshalText implements the encoding.TextMarshaler interface for CommandType
func (i CommandType) MarshalText() ([]byte, error) {
return []byte(i.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface for CommandType
func (i *CommandType) UnmarshalText(text []byte) error {
var err error
*i, err = CommandTypeString(string(text))
return err
}
// MarshalYAML implements a YAML Marshaler for CommandType
func (i CommandType) MarshalYAML() (interface{}, error) {
return i.String(), nil
}
// UnmarshalYAML implements a YAML Unmarshaler for CommandType
func (i *CommandType) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
var err error
*i, err = CommandTypeString(s)
return err
}

View File

@ -1,6 +1,7 @@
package backy
import (
"context"
"errors"
"fmt"
"net/url"
@ -21,13 +22,10 @@ import (
"github.com/rs/zerolog"
)
const (
externDirectiveStart string = "%{"
externDirectiveEnd string = "}%"
externFileDirectiveStart string = "%{file:"
envExternDirectiveStart string = "%{env:"
vaultExternDirectiveStart string = "%{vault:"
)
const macroStart string = "%{"
const macroEnd string = "}%"
const envMacroStart string = "%{env:"
const vaultMacroStart string = "%{vault:"
func (opts *ConfigOpts) InitConfig() {
var err error
@ -55,7 +53,7 @@ func (opts *ConfigOpts) InitConfig() {
cacheDir := homeCacheDir
// Load metadata from file
opts.CachedData, err = remotefetcher.LoadMetadataFromFile(path.Join(backyHomeConfDir, "cache", "cache.yml"))
opts.CachedData, err = remotefetcher.LoadMetadataFromFile(path.Join(backyHomeConfDir, "cache.yml"))
if err != nil {
fmt.Println("Error loading metadata:", err)
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
@ -79,13 +77,11 @@ func (opts *ConfigOpts) InitConfig() {
if err != nil {
logging.ExitWithMSG(fmt.Sprintf("error initializing cache: %v", err), 1, nil)
}
if isRemoteURL(opts.ConfigFilePath) {
p, _ := getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p
}
// Initialize the fetcher
// println("Creating new fetcher for source", opts.ConfigFilePath)
fetcher, err := remotefetcher.NewRemoteFetcher(opts.ConfigFilePath, opts.Cache)
// println("Created new fetcher for source", opts.ConfigFilePath)
if err != nil {
logging.ExitWithMSG(fmt.Sprintf("error initializing config fetcher: %v", err), 1, nil)
}
@ -95,34 +91,57 @@ func (opts *ConfigOpts) InitConfig() {
} else {
loadDefaultConfigFiles(fetcher, configFiles, backyKoanf, opts)
}
opts.koanf = backyKoanf
}
func loadConfigFile(fetcher remotefetcher.RemoteFetcher, filePath string, k *koanf.Koanf, opts *ConfigOpts) {
data, err := fetcher.Fetch(filePath)
if err != nil {
logging.ExitWithMSG(fmt.Sprintf("Could not fetch config file %s: %v", filePath, err), 1, nil)
}
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
}
}
func loadDefaultConfigFiles(fetcher remotefetcher.RemoteFetcher, configFiles []string, k *koanf.Koanf, opts *ConfigOpts) {
cFileFailures := 0
for _, c := range configFiles {
data, err := fetcher.Fetch(c)
if err != nil {
cFileFailures++
continue
}
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
cFileFailures++
continue
}
break
}
if cFileFailures == len(configFiles) {
logging.ExitWithMSG("Could not find any valid config file", 1, nil)
}
}
func (opts *ConfigOpts) ReadConfig() *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")) {
os.Setenv("BACKY_CMDSTDOUT", "enabled")
}
// override the default value of cmd-std-out if flag is set
if opts.CmdStdOut {
os.Setenv("BACKY_CMDSTDOUT", "enabled")
os.Setenv("BACKY_STDOUT", "enabled")
}
CheckConfigValues(backyKoanf, opts.ConfigFilePath)
validateExecCommandsFromCLI(backyKoanf, opts)
validateCommands(backyKoanf, opts)
setLoggingOptions(backyKoanf, opts)
@ -131,23 +150,14 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
log.Info().Str("config file", opts.ConfigFilePath).Send()
if err := opts.initVault(); err != nil {
log.Err(err).Send()
}
unmarshalConfig(backyKoanf, "commands", &opts.Cmds, opts.Logger)
unmarshalConfigIntoStruct(backyKoanf, "commands", &opts.Cmds, opts.Logger)
validateCommandEnvironments(opts)
getCommandEnvironments(opts)
unmarshalConfigIntoStruct(backyKoanf, "hosts", &opts.Hosts, opts.Logger)
unmarshalConfig(backyKoanf, "hosts", &opts.Hosts, opts.Logger)
resolveHostConfigs(opts)
for k, v := range opts.Vars {
v = getExternalConfigDirectiveValue(v, opts)
opts.Vars[k] = v
}
loadCommandLists(opts, backyKoanf)
validateCommandLists(opts)
@ -163,49 +173,18 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
filterExecuteLists(opts)
if backyKoanf.Exists("notifications") {
unmarshalConfigIntoStruct(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger)
unmarshalConfig(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) {
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 {
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
}
}
func loadDefaultConfigFiles(fetcher remotefetcher.RemoteFetcher, configFiles []string, k *koanf.Koanf, opts *ConfigOpts) {
cFileFailures := 0
for _, c := range configFiles {
opts.ConfigFilePath = c
data, err := fetcher.Fetch(c)
if err != nil {
cFileFailures++
continue
}
if data != nil {
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err == nil {
break
} else {
logging.ExitWithMSG(fmt.Sprintf("error loading config from file %s: %v", c, err), 1, &opts.Logger)
}
}
}
if cFileFailures == len(configFiles) {
logging.ExitWithMSG("Could not find any valid local config file", 1, nil)
}
}
func setTerminalEnv() {
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
os.Setenv("BACKY_TERM", "enabled")
@ -214,7 +193,7 @@ func setTerminalEnv() {
}
}
func validateExecCommandsFromCLI(k *koanf.Koanf, opts *ConfigOpts) {
func validateCommands(k *koanf.Koanf, opts *ConfigOpts) {
for _, c := range opts.executeCmds {
if !k.Exists(getCmdFromConfig(c)) {
logging.ExitWithMSG(fmt.Sprintf("command %s is not in config file %s", c, opts.ConfigFilePath), 1, nil)
@ -231,7 +210,6 @@ func setLoggingOptions(k *koanf.Koanf, opts *ConfigOpts) {
logFile = k.String(getLoggingKeyFromConfig("file"))
opts.LogFilePath = logFile
}
opts.LogFilePath = logFile
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if isLoggingVerbose {
@ -251,20 +229,17 @@ func setupLogger(opts *ConfigOpts) zerolog.Logger {
return zerolog.New(writers).With().Timestamp().Logger()
}
func unmarshalConfigIntoStruct(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) {
func unmarshalConfig(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)
logging.ExitWithMSG(fmt.Sprintf("error unmarshalling %s struct: %v", key, err), 1, &log)
}
}
func getCommandEnvironments(opts *ConfigOpts) {
func validateCommandEnvironments(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)
opts.Logger.Info().Str("cmd", cmdName).Err(err).Send()
os.Exit(1)
}
expandEnvVars(opts.backyEnv, cmdConf.Environment)
}
@ -293,29 +268,19 @@ 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 backyConfigFileDir string
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
_, u = getRemoteDir(opts.ConfigFilePath)
listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()}
} else {
opts.ConfigDir = path.Dir(opts.ConfigFilePath)
backyConfigFileDir = path.Dir(opts.ConfigFilePath)
listConfigFiles = []string{
// "./lists.yml", "./lists.yaml",
path.Join(opts.ConfigDir, "lists.yml"),
path.Join(opts.ConfigDir, "lists.yaml"),
path.Join(backyConfigFileDir, "lists.yml"),
path.Join(backyConfigFileDir, "lists.yaml"),
}
}
@ -327,11 +292,10 @@ func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
}
}
if backyKoanf.Exists("cmdLists") {
if backyKoanf.Exists("cmdLists.file") {
if backyKoanf.Exists("cmd-lists") {
unmarshalConfig(backyKoanf, "cmd-lists", &opts.CmdConfigLists, opts.Logger)
if backyKoanf.Exists("cmd-lists.file") {
loadCmdListsFile(backyKoanf, listsConfig, opts)
} else {
unmarshalConfigIntoStruct(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger)
}
}
}
@ -371,14 +335,12 @@ func loadListConfigFile(filePath string, k *koanf.Koanf, opts *ConfigOpts) bool
return false
}
unmarshalConfigIntoStruct(k, "cmdLists", &opts.CmdConfigLists, opts.Logger)
keyNotSupported("cmd-lists", "cmdLists", k, opts, true)
opts.CmdListFile = filePath
return true
}
func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *ConfigOpts) {
opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmdLists.file"))
opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmd-lists.file"))
if !path.IsAbs(opts.CmdListFile) {
opts.CmdListFile = path.Join(path.Dir(opts.ConfigFilePath), opts.CmdListFile)
}
@ -391,28 +353,21 @@ func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *C
data, err := fetcher.Fetch(opts.CmdListFile)
if err != nil {
logging.ExitWithMSG(generateFileFetchErrorString(opts.CmdListFile, "list config", err), 1, nil)
logging.ExitWithMSG(fmt.Sprintf("Could not fetch config file %s: %v", opts.CmdListFile, err), 1, nil)
}
if err := listsConfig.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
}
keyNotSupported("cmd-lists", "cmdLists", listsConfig, opts, true)
unmarshalConfigIntoStruct(listsConfig, "cmdLists", &opts.CmdConfigLists, opts.Logger)
unmarshalConfig(listsConfig, "cmd-lists", &opts.CmdConfigLists, opts.Logger)
opts.Logger.Info().Str("using lists config file", opts.CmdListFile).Send()
}
func generateFileFetchErrorString(file, fileType string, err error) string {
return fmt.Sprintf("Could not fetch %s file %s: %v", file, fileType, err)
}
func validateCommandLists(opts *ConfigOpts) {
var cmdNotFoundSliceErr []error
for cmdListName, cmdList := range opts.CmdConfigLists {
// if cron is enabled and cron is not set, delete the list
if opts.cronEnabled && strings.TrimSpace(cmdList.Cron) == "" {
opts.Logger.Debug().Str("cron", "enabled").Str("list", cmdListName).Msg("cron not set, deleting list")
delete(opts.CmdConfigLists, cmdListName)
continue
}
@ -452,11 +407,11 @@ func getLoggingKeyFromConfig(key string) string {
return fmt.Sprintf("logging.%s", key)
}
// func getCmdListFromConfig(list string) string {
// return fmt.Sprintf("cmdLists.%s", list)
// }
func getCmdListFromConfig(list string) string {
return fmt.Sprintf("cmd-lists.%s", list)
}
func (opts *ConfigOpts) initVault() error {
func (opts *ConfigOpts) setupVault() error {
if !opts.koanf.Bool("vault.enabled") {
return nil
}
@ -477,34 +432,91 @@ func (opts *ConfigOpts) initVault() error {
token = os.Getenv("VAULT_TOKEN")
}
if strings.TrimSpace(token) == "" {
return fmt.Errorf("no token found. One is required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN")
return fmt.Errorf("no token found, but one was required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN")
}
client.SetToken(token)
unmarshalErr := opts.koanf.UnmarshalWithConf("vault.keys", &opts.VaultKeys, koanf.UnmarshalConf{Tag: "yaml"})
if unmarshalErr != nil {
logging.ExitWithMSG(fmt.Sprintf("error unmarshaling vault.keys into struct: %v", unmarshalErr), 1, &opts.Logger)
logging.ExitWithMSG(fmt.Sprintf("error unmarshalling vault.keys into struct: %v", unmarshalErr), 1, &opts.Logger)
}
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)
}
return nil
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 isVaultKey(str string) (string, bool) {
str = strings.TrimSpace(str)
return strings.TrimPrefix(str, "vault:"), strings.HasPrefix(str, "vault:")
}
func parseVaultKey(str string, keys []*VaultKey) (*VaultKey, error) {
keyName, isKey := isVaultKey(str)
if !isKey {
return nil, nil
}
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 {
for i, v := range cmd.Args {
v = replaceVarInString(opts.Vars, v, opts.Logger)
cmd.Args[i] = v
}
if cmd.Name == "" {
cmd.Name = cmdName
}
@ -527,13 +539,9 @@ func processCmds(opts *ConfigOpts) error {
}
}
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]
// resolve hosts
if cmd.Host != nil {
host, hostFound := opts.Hosts[*cmd.Host]
if hostFound {
cmd.RemoteHost = host
cmd.RemoteHost.Host = host.Host
@ -541,32 +549,17 @@ func processCmds(opts *ConfigOpts) error {
cmd.RemoteHost.HostName = host.HostName
}
} else {
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}
}
} else {
if cmd.Dir != nil {
cmdDir, err := getFullPathWithHomeDir(*cmd.Dir)
if err != nil {
return err
}
cmd.Dir = &cmdDir
} else {
cmd.Dir = &opts.ConfigDir
opts.Hosts[*cmd.Host] = &Host{Host: *cmd.Host}
cmd.RemoteHost = &Host{Host: *cmd.Host}
}
}
if cmd.Type == PackageCT {
// Parse package commands
if cmd.Type == "package" {
if cmd.PackageManager == "" {
return fmt.Errorf("package manager is required for package command %s", cmd.PackageName)
}
if cmd.PackageOperation.String() == "" {
if cmd.PackageOperation == "" {
return fmt.Errorf("package operation is required for package command %s", cmd.PackageName)
}
if cmd.PackageName == "" {
@ -575,51 +568,36 @@ func processCmds(opts *ConfigOpts) error {
var err error
// Validate the operation
if cmd.PackageOperation.IsAPackageOperation() {
switch cmd.PackageOperation {
case "install", "remove", "upgrade", "checkVersion":
cmd.pkgMan, err = pkgman.PackageManagerFactory(cmd.PackageManager, pkgman.WithoutAuth())
if err != nil {
return err
}
} else {
default:
return fmt.Errorf("unsupported package operation %s for command %s", cmd.PackageOperation, cmd.Name)
}
}
// Parse user commands
if cmd.Type == UserCT {
if cmd.Type == "user" {
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()
}
detectOSType(cmd, opts)
var err error
// Validate the operation
switch cmd.UserOperation {
case "add", "remove", "modify", "checkIfExists", "delete", "password":
cmd.userMan, err = usermanager.NewUserManager(cmd.OS)
if cmd.UserOperation == "password" {
opts.Logger.Debug().Msg("changing password for user: " + cmd.Username)
cmd.UserPassword = getExternalConfigDirectiveValue(cmd.UserPassword, opts)
}
if !IsHostLocal(cmd.Host) {
host, ok := opts.Hosts[cmd.Host]
if cmd.Host != nil {
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)
cmd.UserSshPubKeys[indx] = key
}
if err != nil {
return err
}
@ -629,30 +607,24 @@ func processCmds(opts *ConfigOpts) error {
}
if cmd.Type == RemoteScriptCT {
var fetchErr error
if cmd.Type == "remoteScript" {
if !isRemoteURL(cmd.Cmd) {
return fmt.Errorf("remoteScript command %s must be a remote resource", cmdName)
}
cmd.Fetcher, fetchErr = remotefetcher.NewRemoteFetcher(cmd.Cmd, opts.Cache, remotefetcher.WithFileType("script"))
if fetchErr != nil {
return fmt.Errorf("error initializing remote fetcher for remoteScript: %v", fetchErr)
}
}
if cmd.OutputFile != "" {
var err error
cmd.OutputFile, err = getFullPathWithHomeDir(cmd.OutputFile)
if err != nil {
return err
}
}
}
return nil
}
// processHooks evaluates if hooks are valid Commands
//
// The cmd.hookRefs[hookType] is created with any hooks found.
//
// Returns an error, if any, if the hook command is not found
func processHooks(cmd *Command, hooks []string, opts *ConfigOpts, hookType string) error {
// initialize hook type
var hookCmdFound bool
cmd.hookRefs = map[string]map[string]*Command{}
cmd.hookRefs[hookType] = map[string]*Command{}
@ -679,18 +651,13 @@ func processHooks(cmd *Command, hooks []string, opts *ConfigOpts, hookType strin
}
func detectOSType(cmd *Command, opts *ConfigOpts) error {
if IsHostLocal(cmd.Host) {
if runtime.GOOS == "linux" {
if cmd.Host == nil {
if runtime.GOOS == "linux" { // also can be specified to FreeBSD
cmd.OS = "linux"
opts.Logger.Info().Msg("Unix/Linux type OS detected")
return nil
}
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
@ -711,35 +678,3 @@ func detectOSType(cmd *Command, opts *ConfigOpts) error {
}
return nil
}
func keyNotSupported(oldKey, newKey string, koanf *koanf.Koanf, opts *ConfigOpts, deprecated bool) {
if koanf.Exists(oldKey) {
if deprecated {
opts.Logger.Warn().Str("key", oldKey).Msg("key is deprecated. Use " + newKey + " instead.")
} else {
opts.Logger.Fatal().Err(fmt.Errorf("key %s found; it has changed to %s", oldKey, newKey)).Send()
}
}
}
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 VariadicFunctionParameterTest(allowedKeys ...string) {
if contains(allowedKeys, "file") {
println("file param included")
}
}

View File

@ -17,7 +17,10 @@ func (opts *ConfigOpts) Cron() {
s := gocron.NewScheduler(time.Local)
s.TagsUnique()
cmdLists := opts.CmdConfigLists
for _, config := range cmdLists {
for listName, config := range cmdLists {
if config.Name == "" {
config.Name = listName
}
cron := strings.TrimSpace(config.Cron)
if cron != "" {

View File

@ -23,7 +23,8 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
var cmdFound bool = false
var cmdInfo *Command
// check commands in file against cmd
for cmdInFile := range opts.Cmds {
for _, cmdInFile := range opts.executeCmds {
print(cmdInFile)
cmdFound = false
if cmd == cmdInFile {
@ -36,39 +37,28 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
// print the command's information
if cmdFound {
println("Command: ")
print("Command: ")
print(cmdInfo.Cmd)
if len(cmdInfo.Args) >= 0 {
for _, v := range cmdInfo.Args {
print(" ") // print space between command and args
print(v) // print command arg
for _, v := range cmdInfo.Args {
print(" ") // print space between command and args
print(v) // print command arg
}
}
// is it remote or local
if !IsHostLocal(cmdInfo.Host) {
println()
// is is remote or local
if cmdInfo.Host != nil {
print("Host: ", cmdInfo.Host)
println()
} else {
println()
print("Host: Runs on Local Machine\n\n")
}
if cmdInfo.Dir != nil {
println()
print("Directory: ", *cmdInfo.Dir)
println()
}
if cmdInfo.Type.String() != "" {
print("Type: ", cmdInfo.Type.String())
println()
}
} else {
fmt.Printf("Command %s not found. Check spelling.\n", cmd)
@ -76,38 +66,3 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
}
}
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 listInfo *CmdList
// check commands in file against cmd
for listInFile, l := range opts.CmdConfigLists {
listFound = false
if list == listInFile {
listFound = true
listInfo = l
break
}
}
// print the command's information
if listFound {
println("List: ", list)
println()
for _, v := range listInfo.Order {
println()
opts.ListCommand(v)
}
} else {
fmt.Printf("List %s not found. Check spelling.\n", list)
}
}

View File

@ -9,7 +9,6 @@ import (
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/http"
"github.com/nikoksr/notify/service/mail"
"github.com/nikoksr/notify/service/matrix"
"maunium.net/go/mautrix/id"
@ -31,12 +30,6 @@ type MailConfig struct {
Password string `yaml:"password"`
}
type HttpConfig struct {
URL string `yaml:"url"`
Method string `yaml:"method"`
Headers map[string][]string `yaml:"headers"`
}
// SetupNotify sets up notify instances for each command list.
func (opts *ConfigOpts) SetupNotify() {
@ -65,8 +58,6 @@ func (opts *ConfigOpts) SetupNotify() {
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in mail object", confId)).Str("list", confName).Send()
continue
}
conf.Password = getExternalConfigDirectiveValue(conf.Password, opts)
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding mail notification service")
mailConf := setupMail(conf)
services = append(services, mailConf)
case "matrix":
@ -75,23 +66,14 @@ func (opts *ConfigOpts) SetupNotify() {
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in matrix object", confId)).Str("list", confName).Send()
continue
}
conf.AccessToken = getExternalConfigDirectiveValue(conf.AccessToken, opts)
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding matrix notification service")
mtrxConf, mtrxErr := setupMatrix(conf)
if mtrxErr != nil {
opts.Logger.Info().Str("list", confName).Err(fmt.Errorf("error: configuring matrix id %s failed during setup: %w", id, mtrxErr))
continue
}
// append the services
services = append(services, mtrxConf)
case "http":
conf, ok := opts.NotificationConf.HttpConfig[confId]
if !ok {
opts.Logger.Info().Err(fmt.Errorf("error: ID %s not found in http object", confId)).Str("list", confName).Send()
continue
}
opts.Logger.Debug().Str("list", confName).Str("id", confId).Msg("adding http notification service")
httpConf := setupHttp(conf)
services = append(services, httpConf)
// service is not recognized
default:
opts.Logger.Info().Err(fmt.Errorf("id %s not found", id)).Str("list", confName).Send()
}
@ -117,19 +99,3 @@ func setupMail(config MailConfig) *mail.Mail {
mailClient.BodyFormat(mail.PlainText)
return mailClient
}
func setupHttp(httpConf HttpConfig) *http.Service {
httpService := http.New()
httpService.AddReceivers(&http.Webhook{
URL: httpConf.URL,
Header: httpConf.Headers,
ContentType: "text/plain",
Method: httpConf.Method,
BuildPayload: func(subject, message string) (payload any) {
return subject + "\n\n" + message
},
})
return httpService
}

View File

@ -1,145 +0,0 @@
// Code generated by "enumer -linecomment -yaml -text -json -type=PackageOperation"; DO NOT EDIT.
package backy
import (
"encoding/json"
"fmt"
"strings"
)
const _PackageOperationName = "installupgradepurgeremovecheckVersionisInstalled"
var _PackageOperationIndex = [...]uint8{0, 0, 7, 14, 19, 25, 37, 48}
const _PackageOperationLowerName = "installupgradepurgeremovecheckversionisinstalled"
func (i PackageOperation) String() string {
if i < 0 || i >= PackageOperation(len(_PackageOperationIndex)-1) {
return fmt.Sprintf("PackageOperation(%d)", i)
}
return _PackageOperationName[_PackageOperationIndex[i]:_PackageOperationIndex[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 _PackageOperationNoOp() {
var x [1]struct{}
_ = x[DefaultPO-(0)]
_ = x[PackOpInstall-(1)]
_ = x[PackOpUpgrade-(2)]
_ = x[PackOpPurge-(3)]
_ = x[PackOpRemove-(4)]
_ = x[PackOpCheckVersion-(5)]
_ = x[PackOpIsInstalled-(6)]
}
var _PackageOperationValues = []PackageOperation{DefaultPO, PackOpInstall, PackOpUpgrade, PackOpPurge, PackOpRemove, PackOpCheckVersion, PackOpIsInstalled}
var _PackageOperationNameToValueMap = map[string]PackageOperation{
_PackageOperationName[0:0]: DefaultPO,
_PackageOperationLowerName[0:0]: DefaultPO,
_PackageOperationName[0:7]: PackOpInstall,
_PackageOperationLowerName[0:7]: PackOpInstall,
_PackageOperationName[7:14]: PackOpUpgrade,
_PackageOperationLowerName[7:14]: PackOpUpgrade,
_PackageOperationName[14:19]: PackOpPurge,
_PackageOperationLowerName[14:19]: PackOpPurge,
_PackageOperationName[19:25]: PackOpRemove,
_PackageOperationLowerName[19:25]: PackOpRemove,
_PackageOperationName[25:37]: PackOpCheckVersion,
_PackageOperationLowerName[25:37]: PackOpCheckVersion,
_PackageOperationName[37:48]: PackOpIsInstalled,
_PackageOperationLowerName[37:48]: PackOpIsInstalled,
}
var _PackageOperationNames = []string{
_PackageOperationName[0:0],
_PackageOperationName[0:7],
_PackageOperationName[7:14],
_PackageOperationName[14:19],
_PackageOperationName[19:25],
_PackageOperationName[25:37],
_PackageOperationName[37:48],
}
// PackageOperationString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func PackageOperationString(s string) (PackageOperation, error) {
if val, ok := _PackageOperationNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _PackageOperationNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to PackageOperation values", s)
}
// PackageOperationValues returns all values of the enum
func PackageOperationValues() []PackageOperation {
return _PackageOperationValues
}
// PackageOperationStrings returns a slice of all String values of the enum
func PackageOperationStrings() []string {
strs := make([]string, len(_PackageOperationNames))
copy(strs, _PackageOperationNames)
return strs
}
// IsAPackageOperation returns "true" if the value is listed in the enum definition. "false" otherwise
func (i PackageOperation) IsAPackageOperation() bool {
for _, v := range _PackageOperationValues {
if i == v {
return true
}
}
return false
}
// MarshalJSON implements the json.Marshaler interface for PackageOperation
func (i PackageOperation) MarshalJSON() ([]byte, error) {
return json.Marshal(i.String())
}
// UnmarshalJSON implements the json.Unmarshaler interface for PackageOperation
func (i *PackageOperation) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return fmt.Errorf("PackageOperation should be a string, got %s", data)
}
var err error
*i, err = PackageOperationString(s)
return err
}
// MarshalText implements the encoding.TextMarshaler interface for PackageOperation
func (i PackageOperation) MarshalText() ([]byte, error) {
return []byte(i.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface for PackageOperation
func (i *PackageOperation) UnmarshalText(text []byte) error {
var err error
*i, err = PackageOperationString(string(text))
return err
}
// MarshalYAML implements a YAML Marshaler for PackageOperation
func (i PackageOperation) MarshalYAML() (interface{}, error) {
return i.String(), nil
}
// UnmarshalYAML implements a YAML Unmarshaler for PackageOperation
func (i *PackageOperation) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
var err error
*i, err = PackageOperationString(s)
return err
}

View File

@ -15,16 +15,14 @@ import (
"strings"
"time"
"github.com/google/uuid"
"github.com/kevinburke/ssh_config"
"github.com/pkg/errors"
"github.com/pkg/sftp"
"github.com/rs/zerolog"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
var PrivateKeyExtraInfoErr = errors.New("Private key may be encrypted. \nIf encrypted, make sure the password is specified correctly in the correct section. This may be done in one of two ways: \n Using external directives - see docs \n privatekeypassword: password (not recommended). \n ")
var PrivateKeyExtraInfoErr = errors.New("Private key may be encrypted. \nIf encrypted, make sure the password is specified correctly in the correct section. This may be done in one of three ways: \n privatekeypassword: env:PR_KEY_PASS \n privatekeypassword: file:/path/to/password-file \n privatekeypassword: password (not recommended). \n ")
var TS = strings.TrimSpace
// ConnectToHost connects to a host by looking up the config values in the file ~/.ssh/config
@ -56,7 +54,7 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
if !remoteConfig.useDefaultConfig {
var err error
remoteConfig.ConfigFilePath, err = getFullPathWithHomeDir(remoteConfig.ConfigFilePath)
remoteConfig.ConfigFilePath, err = resolveDir(remoteConfig.ConfigFilePath)
if err != nil {
return err
}
@ -65,7 +63,7 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
return sshConfigFileOpenErr
}
} else {
defaultConfig, _ := getFullPathWithHomeDir("~/.ssh/config")
defaultConfig, _ := resolveDir("~/.ssh/config")
configFile, sshConfigFileOpenErr = os.Open(defaultConfig)
if sshConfigFileOpenErr != nil {
return sshConfigFileOpenErr
@ -121,6 +119,7 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
return errors.Wrap(err, "could not create hostkeycallback function")
}
remoteConfig.ClientConfig.HostKeyCallback = hostKeyCallback
// opts.Logger.Info().Str("user", remoteConfig.ClientConfig.User).Send()
remoteConfig.SshClient, connectErr = remoteConfig.ConnectThroughBastion(opts.Logger)
if connectErr != nil {
@ -181,7 +180,11 @@ func (remoteHost *Host) GetAuthMethods(opts *ConfigOpts) error {
return err
}
remoteHost.PrivateKeyPassword = GetPrivateKeyPassword(remoteHost.PrivateKeyPassword, opts)
remoteHost.PrivateKeyPassword, err = GetPrivateKeyPassword(remoteHost.PrivateKeyPassword, opts, opts.Logger)
if err != nil {
return err
}
if remoteHost.PrivateKeyPassword == "" {
@ -204,13 +207,14 @@ func (remoteHost *Host) GetAuthMethods(opts *ConfigOpts) error {
}
}
if remoteHost.Password != "" {
if remoteHost.Password == "" {
opts.Logger.Debug().Str("password", remoteHost.Password).Str("Host", remoteHost.Host).Send()
remoteHost.Password, err = GetPassword(remoteHost.Password, opts, opts.Logger)
remoteHost.Password = GetPassword(remoteHost.Password, opts)
if err != nil {
// opts.Logger.Debug().Str("actual password", remoteHost.Password).Str("Host", remoteHost.Host).Send()
return err
}
remoteHost.ClientConfig.Auth = append(remoteHost.ClientConfig.Auth, ssh.Password(remoteHost.Password))
}
@ -238,20 +242,22 @@ func (remoteHost *Host) GetPrivateKeyFileFromConfig() {
identityFile = remoteHost.PrivateKeyPath
}
remoteHost.PrivateKeyPath, _ = getFullPathWithHomeDir(identityFile)
remoteHost.PrivateKeyPath, _ = resolveDir(identityFile)
}
// GetPort checks if the port from the config file is 0
// If it is the port is searched in the SSH config file(s)
func (remoteHost *Host) GetPort() {
port := fmt.Sprintf("%d", remoteHost.Port)
// port specified?
// port specifed?
// port will be 0 if missing from backy config
if port == "0" {
// get port from specified SSH config file
port, _ = remoteHost.SSHConfigFile.SshConfigFile.Get(remoteHost.Host, "Port")
if port == "" {
// get port from default SSH config file
port = remoteHost.SSHConfigFile.DefaultUserSettings.Get(remoteHost.Host, "Port")
// set port to be default
@ -266,6 +272,7 @@ func (remoteHost *Host) GetPort() {
func (remoteHost *Host) CombineHostNameWithPort() {
// if the port is already in the HostName, leave it
if strings.HasSuffix(remoteHost.HostName, fmt.Sprintf(":%d", remoteHost.Port)) {
return
}
@ -308,6 +315,7 @@ func (remoteHost *Host) ConnectThroughBastion(log zerolog.Logger) (*ssh.Client,
return nil, err
}
// sClient is an ssh client connected to the service host, through the bastion host.
sClient := ssh.NewClient(ncc, chans, reqs)
return sClient, nil
@ -315,23 +323,74 @@ func (remoteHost *Host) ConnectThroughBastion(log zerolog.Logger) (*ssh.Client,
// GetKnownHosts resolves the host's KnownHosts file if it is defined
// if not defined, the default location for this file is used
func (remoteHost *Host) GetKnownHosts() error {
func (remotehHost *Host) GetKnownHosts() error {
var knownHostsFileErr error
if TS(remoteHost.KnownHostsFile) != "" {
remoteHost.KnownHostsFile, knownHostsFileErr = getFullPathWithHomeDir(remoteHost.KnownHostsFile)
if TS(remotehHost.KnownHostsFile) != "" {
remotehHost.KnownHostsFile, knownHostsFileErr = resolveDir(remotehHost.KnownHostsFile)
return knownHostsFileErr
}
remoteHost.KnownHostsFile, knownHostsFileErr = getFullPathWithHomeDir("~/.ssh/known_hosts")
remotehHost.KnownHostsFile, knownHostsFileErr = resolveDir("~/.ssh/known_hosts")
return knownHostsFileErr
}
func GetPrivateKeyPassword(key string, opts *ConfigOpts) string {
return getExternalConfigDirectiveValue(key, opts)
func GetPrivateKeyPassword(key string, opts *ConfigOpts, log zerolog.Logger) (string, error) {
var prKeyPassword string
if strings.HasPrefix(key, "file:") {
privKeyPassFilePath := strings.TrimPrefix(key, "file:")
privKeyPassFilePath, _ = resolveDir(privKeyPassFilePath)
keyFile, keyFileErr := os.Open(privKeyPassFilePath)
if keyFileErr != nil {
return "", errors.Errorf("Private key password file %s failed to open. \n Make sure it is accessible and correct.", privKeyPassFilePath)
}
passwordScanner := bufio.NewScanner(keyFile)
for passwordScanner.Scan() {
prKeyPassword = passwordScanner.Text()
}
} else if strings.HasPrefix(key, "env:") {
privKey := strings.TrimPrefix(key, "env:")
privKey = strings.TrimPrefix(privKey, "${")
privKey = strings.TrimSuffix(privKey, "}")
privKey = strings.TrimPrefix(privKey, "$")
prKeyPassword = os.Getenv(privKey)
} else {
prKeyPassword = key
}
prKeyPassword = GetVaultKey(prKeyPassword, opts, opts.Logger)
return prKeyPassword, nil
}
// GetPassword gets any password
func GetPassword(pass string, opts *ConfigOpts) string {
return getExternalConfigDirectiveValue(pass, opts)
func GetPassword(pass string, opts *ConfigOpts, log zerolog.Logger) (string, error) {
pass = strings.TrimSpace(pass)
if pass == "" {
return "", nil
}
var password string
if strings.HasPrefix(pass, "file:") {
passFilePath := strings.TrimPrefix(pass, "file:")
passFilePath, _ = resolveDir(passFilePath)
keyFile, keyFileErr := os.Open(passFilePath)
if keyFileErr != nil {
return "", errors.New("Password file failed to open")
}
passwordScanner := bufio.NewScanner(keyFile)
for passwordScanner.Scan() {
password = passwordScanner.Text()
}
} else if strings.HasPrefix(pass, "env:") {
passEnv := strings.TrimPrefix(pass, "env:")
passEnv = strings.TrimPrefix(passEnv, "${")
passEnv = strings.TrimSuffix(passEnv, "}")
passEnv = strings.TrimPrefix(passEnv, "$")
password = os.Getenv(passEnv)
} else {
password = pass
}
password = GetVaultKey(password, opts, opts.Logger)
return password, nil
}
func (remoteConfig *Host) GetProxyJumpFromConfig(hosts map[string]*Host) error {
@ -383,7 +442,7 @@ func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *Confi
return sshConfigFileOpenErr
}
} else {
defaultConfig, _ := getFullPathWithHomeDir("~/.ssh/config")
defaultConfig, _ := resolveDir("~/.ssh/config")
configFile, sshConfigFileOpenErr = os.Open(defaultConfig)
if sshConfigFileOpenErr != nil {
return sshConfigFileOpenErr
@ -421,6 +480,7 @@ func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *Confi
return nil
}
// RunCmdSSH runs commands over SSH.
func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
var (
ArgsStr string
@ -432,7 +492,10 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
env: command.Environment,
}
)
command = getCommandTypeAndSetCommandInfo(command)
// Get the command type
// This must be done before concatenating the arguments
command.Type = strings.TrimSpace(command.Type)
command = getCommandType(command)
// Prepare command arguments
for _, v := range command.Args {
@ -441,8 +504,8 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
cmdCtxLogger.Info().
Str("Command", command.Name).
Str("Host", command.Host).
Msgf("Running %s on host %s", getCommandTypeAndSetCommandInfoLabel(command.Type), command.Host)
Str("Host", *command.Host).
Msgf("Running %s on host %s", getCommandTypeLabel(command.Type), *command.Host)
// cmdCtxLogger.Debug().Str("cmd", command.Cmd).Strs("args", command.Args).Send()
@ -473,14 +536,12 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
// Handle command execution based on type
switch command.Type {
case ScriptCT:
case "script":
return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf)
case RemoteScriptCT:
return command.runRemoteScript(commandSession, cmdCtxLogger, &cmdOutBuf)
case ScriptFileCT:
case "scriptFile":
return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf)
case PackageCT:
if command.PackageOperation == PackOpCheckVersion {
case "package":
if command.PackageOperation == "checkVersion" {
commandSession.Stderr = nil
// Execute the package version command remotely
// Parse the output of package version command
@ -497,7 +558,7 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
// Run simple command
if err := commandSession.Run(ArgsStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running command: %w", err)
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error running command: %w", err)
}
}
default:
@ -507,100 +568,13 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
}
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
if command.Type == UserCT && command.UserOperation == "password" {
// cmdCtxLogger.Debug().Msgf("adding stdin")
userNamePass := fmt.Sprintf("%s:%s", command.Username, command.UserPassword)
client, err := sftp.NewClient(command.RemoteHost.SshClient)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating sftp client: %v", err)
}
uuidFile := uuid.New()
passFilePath := fmt.Sprintf("/tmp/%s", uuidFile.String())
passFile, passFileErr := client.Create(passFilePath)
if passFileErr != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating file /tmp/%s: %v", uuidFile.String(), passFileErr)
}
_, err = passFile.Write([]byte(userNamePass))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error writing to file /tmp/%s: %v", uuidFile.String(), err)
}
ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath)
defer passFile.Close()
rmFileFunc := func() {
_ = client.Remove(passFilePath)
}
defer rmFileFunc()
// commandSession.Stdin = command.stdin
}
// Run simple command
if err := commandSession.Run(ArgsStr); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running command: %w", err)
}
if command.Type == UserCT {
if command.UserOperation == "add" {
if command.UserSshPubKeys != nil {
var (
f *sftp.File
err error
userHome []byte
client *sftp.Client
)
cmdCtxLogger.Info().Msg("adding SSH Keys")
commandSession, _ = command.RemoteHost.createSSHSession(opts)
userHome, err = commandSession.CombinedOutput(fmt.Sprintf("grep \"%s\" /etc/passwd | cut -d: -f6", command.Username))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error finding user home from /etc/passwd: %v", err)
}
command.UserHome = strings.TrimSpace(string(userHome))
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
client, err = sftp.NewClient(command.RemoteHost.SshClient)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating sftp client: %v", err)
}
err = client.MkdirAll(userSshDir)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating directory %s: %v", userSshDir, err)
}
_, err = client.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
}
f, err = client.OpenFile(fmt.Sprintf("%s/authorized_keys", userSshDir), os.O_APPEND|os.O_CREATE|os.O_WRONLY)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)
}
defer f.Close()
for _, k := range command.UserSshPubKeys {
buf := bytes.NewBufferString(k)
cmdCtxLogger.Info().Str("key", k).Msg("adding SSH key")
if _, err := f.ReadFrom(buf); err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error adding to authorized keys: %v", err)
}
}
commandSession, _ = command.RemoteHost.createSSHSession(opts)
_, err = commandSession.CombinedOutput(fmt.Sprintf("chown -R %s:%s %s", command.Username, command.Username, userHome))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), err
}
}
}
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.GetOutput), fmt.Errorf("error running command: %w", err)
}
}
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, true), nil
}
func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandSession *ssh.Session, cmdOutBuf bytes.Buffer) ([]string, error) {
@ -619,17 +593,17 @@ func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandS
_, parseErr := parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
if parseErr != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error: package %s not listed: %w", command.PackageName, err)
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.GetOutput), fmt.Errorf("error: package %s not listed: %w", command.PackageName, err)
}
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running %s: %w", ArgsStr, err)
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.GetOutput), fmt.Errorf("error running %s: %w", ArgsStr, err)
}
return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
}
// getCommandTypeAndSetCommandInfoLabel returns a human-readable label for the command type.
func getCommandTypeAndSetCommandInfoLabel(commandType CommandType) string {
if !commandType.IsACommandType() {
// getCommandTypeLabel returns a human-readable label for the command type.
func getCommandTypeLabel(commandType string) string {
if commandType == "" {
return "command"
}
return fmt.Sprintf("%s command", commandType)
@ -651,7 +625,7 @@ func (command *Command) runScript(session *ssh.Session, cmdCtxLogger zerolog.Log
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err)
}
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.GetOutput), nil
}
// runScriptFile handles the execution of script files.
@ -670,7 +644,7 @@ func (command *Command) runScriptFile(session *ssh.Session, cmdCtxLogger zerolog
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err)
}
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), nil
}
// prepareScriptBuffer prepares a buffer for inline scripts.
@ -714,28 +688,9 @@ func (command *Command) prepareScriptFileBuffer() (*bytes.Buffer, error) {
return &buffer, nil
}
// runRemoteScript handles the execution of remote scripts
func (command *Command) runRemoteScript(session *ssh.Session, cmdCtxLogger zerolog.Logger, outputBuf *bytes.Buffer) ([]string, error) {
script, err := command.Fetcher.Fetch(command.Cmd)
if err != nil {
return nil, err
}
if command.Shell == "" {
command.Shell = "sh"
}
session.Stdin = bytes.NewReader(script)
err = session.Run(command.Shell)
if err != nil {
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running remote script: %w", err)
}
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil
}
// readFileToBuffer reads a file into a buffer.
func readFileToBuffer(filePath string) (*bytes.Buffer, error) {
resolvedPath, err := getFullPathWithHomeDir(filePath)
resolvedPath, err := resolveDir(filePath)
if err != nil {
return nil, err
}
@ -802,17 +757,3 @@ func (h *Host) DetectOS(opts *ConfigOpts) (string, error) {
osName := string(output)
return osName, nil
}
func CheckIfHostHasHostName(host string) (bool, string) {
HostName, err := ssh_config.DefaultUserSettings.GetStrict(host, "HostName")
if err != nil {
return false, ""
}
println(HostName)
return HostName != "", HostName
}
func IsHostLocal(host string) bool {
host = strings.ToLower(host)
return host == "127.0.0.1" || host == "localhost" || host == ""
}

View File

@ -1,8 +0,0 @@
//go:build tools
package backy
import (
// Protect this entry in go.mod from being removed by go mod tidy.
_ "github.com/dmarkham/enumer"
)

View File

@ -26,15 +26,15 @@ type (
ConfigFilePath string `yaml:"config,omitempty"`
Host string `yaml:"host,omitempty"`
HostName string `yaml:"hostname,omitempty"`
KnownHostsFile string `yaml:"knownHostsFile,omitempty"`
KnownHostsFile string `yaml:"knownhostsfile,omitempty"`
ClientConfig *ssh.ClientConfig
SSHConfigFile *sshConfigFile
SshClient *ssh.Client
Port uint16 `yaml:"port,omitempty"`
ProxyJump string `yaml:"proxyjump,omitempty"`
Password string `yaml:"password,omitempty"`
PrivateKeyPath string `yaml:"privateKeyPath,omitempty"`
PrivateKeyPassword string `yaml:"privateKeyPassword,omitempty"`
PrivateKeyPath string `yaml:"privatekeypath,omitempty"`
PrivateKeyPassword string `yaml:"privatekeypassword,omitempty"`
useDefaultConfig bool
User string `yaml:"user,omitempty"`
isProxyHost bool
@ -51,83 +51,100 @@ type (
Command struct {
Name string `yaml:"name,omitempty"`
// command to run
Cmd string `yaml:"cmd"`
// See CommandType enum further down the page for acceptable values
Type CommandType `yaml:"type,omitempty"`
// Possible values: script, scriptFile
// If blank, it is regular command.
Type string `yaml:"type,omitempty"`
Host string `yaml:"host,omitempty"`
// host on which to run cmd
Host *string `yaml:"host,omitempty"`
// Hooks are for running commands on certain events
Hooks *Hooks `yaml:"hooks,omitempty"`
// hook refs are internal references of commands for each hook type
hookRefs map[string]map[string]*Command
// Shell specifies which shell to run the command in, if any.
Shell string `yaml:"shell,omitempty"`
RemoteHost *Host `yaml:"-"`
// Args is an array that holds the arguments to cmd
Args []string `yaml:"args,omitempty"`
/*
Dir specifies a directory in which to run the command.
Ignored if Host is set.
*/
Dir *string `yaml:"dir,omitempty"`
// Env points to a file containing env variables to be used with the command
Env string `yaml:"env,omitempty"`
// Environment holds env variables to be used with the command
Environment []string `yaml:"environment,omitempty"`
GetOutputInList bool `yaml:"getOutputInList,omitempty"`
// Output determines if output is requested.
//
// Only for when command is in a list.
GetOutput bool `yaml:"getOutput,omitempty"`
ScriptEnvFile string `yaml:"scriptEnvFile"`
OutputToLog bool `yaml:"outputToLog,omitempty"`
OutputFile string `yaml:"outputFile,omitempty"`
// BEGIN PACKAGE COMMAND FIELDS
PackageManager string `yaml:"packageManager,omitempty"`
PackageName string `yaml:"packageName,omitempty"`
// Version specifies the desired version for package execution
PackageVersion string `yaml:"packageVersion,omitempty"`
PackageOperation PackageOperation `yaml:"packageOperation,omitempty"`
// PackageOperation specifies the action for package-related commands (e.g., "install" or "remove")
PackageOperation string `yaml:"packageOperation,omitempty"`
pkgMan pkgman.PackageManager
packageCmdSet bool
// END PACKAGE COMMAND FIELDS
// RemoteSource specifies a URL to fetch the command or configuration remotely
RemoteSource string `yaml:"remoteSource,omitempty"`
// FetchBeforeExecution determines if the remoteSource should be fetched before running
FetchBeforeExecution bool `yaml:"fetchBeforeExecution,omitempty"`
Fetcher remotefetcher.RemoteFetcher
// BEGIN USER COMMAND FIELDS
// Username specifies the username for user creation or related operations
Username string `yaml:"userName,omitempty"`
UserID string `yaml:"userID,omitempty"`
// UserGroups specifies the groups to add the user to
UserGroups []string `yaml:"userGroups,omitempty"`
// UserHome specifies the home directory for the user
UserHome string `yaml:"userHome,omitempty"`
// UserShell specifies the shell for the user
UserShell string `yaml:"userShell,omitempty"`
UserCreateHome bool `yaml:"userCreateHome,omitempty"`
UserIsSystem bool `yaml:"userIsSystem,omitempty"`
// SystemUser specifies whether the user is a system account
SystemUser bool `yaml:"systemUser,omitempty"`
// UserPassword specifies the password for the user (can be file: or plain text)
UserPassword string `yaml:"userPassword,omitempty"`
UserSshPubKeys []string `yaml:"userSshPubKeys,omitempty"`
userMan usermanager.UserManager
// OS for the command, only used when type is user
OS string `yaml:"OS,omitempty"`
// UserOperation specifies the action for user-related commands (e.g., "create" or "remove")
UserOperation string `yaml:"userOperation,omitempty"`
userCmdSet bool
@ -170,7 +187,7 @@ type (
// CmdConfigLists holds the lists of commands to be run in order.
// Key is the command list name.
CmdConfigLists map[string]*CmdList `yaml:"cmdLists"`
CmdConfigLists map[string]*CmdList `yaml:"cmd-lists"`
// Hosts holds the Host config.
// key is the host.
@ -181,14 +198,13 @@ type (
// Global log level
BackyLogLvl *string
CmdStdOut bool
// Holds config file
ConfigFilePath string
ConfigDir string
// Holds log file
LogFilePath string
// for command list file
CmdListFile string
// use command lists using cron
@ -205,8 +221,6 @@ type (
List ListConfig
Vars map[string]string `yaml:"variables"`
VaultKeys []*VaultKey `yaml:"keys"`
koanf *koanf.Koanf
@ -225,7 +239,6 @@ type (
VaultKey struct {
Name string `yaml:"name"`
Key string `yaml:"key"`
Path string `yaml:"path"`
ValueType string `yaml:"type"`
MountPath string `yaml:"mountpath"`
@ -241,7 +254,6 @@ type (
Notifications struct {
MailConfig map[string]MailConfig `yaml:"mail,omitempty"`
MatrixConfig map[string]MatrixStruct `yaml:"matrix,omitempty"`
HttpConfig map[string]HttpConfig `yaml:"http,omitempty"`
}
CmdOutput struct {
@ -276,41 +288,4 @@ type (
ListName string // Name of the command list
Error error // Error encountered, if any
}
// use ints so we can use enums
CommandType int
PackageOperation int
AllowedExternalDirectives int
)
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType
const (
DefaultCT CommandType = iota //
ScriptCT // script
ScriptFileCT // scriptFile
RemoteScriptCT // remoteScript
PackageCT // package
UserCT // user
)
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=PackageOperation
const (
DefaultPO PackageOperation = iota //
PackOpInstall // install
PackOpUpgrade // upgrade
PackOpPurge // purge
PackOpRemove // remove
PackOpCheckVersion // checkVersion
PackOpIsInstalled // isInstalled
)
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=AllowedExternalDirectives
const (
DefaultExternalDir AllowedExternalDirectives = iota
AllowedExternalDirectiveVault // vault
AllowedExternalDirectiveVaultFile // vault-file
AllowedExternalDirectiveAll // vault-file-env
AllowedExternalDirectiveFileEnv // file-env
AllowedExternalDirectiveFile // file
AllowedExternalDirectiveEnv // env
)

View File

@ -6,7 +6,6 @@ package backy
import (
"bytes"
"context"
"errors"
"fmt"
"os"
@ -16,8 +15,6 @@ import (
"strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
vault "github.com/hashicorp/vault/api"
"github.com/joho/godotenv"
"github.com/knadh/koanf/v2"
"github.com/rs/zerolog"
@ -67,13 +64,6 @@ func SetLogFile(logFile string) BackyOptionFunc {
}
}
// SetCmdStdOut forces the command output to stdout
func SetCmdStdOut(setStdOut bool) BackyOptionFunc {
return func(bco *ConfigOpts) {
bco.CmdStdOut = setStdOut
}
}
// EnableCron enables the execution of command lists at specified times
func EnableCron() BackyOptionFunc {
return func(bco *ConfigOpts) {
@ -94,7 +84,7 @@ func NewOpts(configFilePath string, opts ...BackyOptionFunc) *ConfigOpts {
func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opts *ConfigOpts, log zerolog.Logger) {
if envVarsToInject.file != "" {
envPath, envPathErr := getFullPathWithHomeDir(envVarsToInject.file)
envPath, envPathErr := resolveDir(envVarsToInject.file)
if envPathErr != nil {
log.Fatal().Str("envFile", envPath).Err(envPathErr).Send()
}
@ -110,11 +100,7 @@ func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opt
goto errEnvFile
}
for key, val := range envMap {
err = process.Setenv(key, GetVaultKey(val, opts, log))
if err != nil {
log.Error().Err(err).Send()
}
process.Setenv(key, GetVaultKey(val, opts, log))
}
}
@ -125,18 +111,14 @@ errEnvFile:
if strings.Contains(envVal, "=") {
envVarArr := strings.Split(envVal, "=")
err := process.Setenv(envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts))
if err != nil {
log.Error().Err(err).Send()
}
process.Setenv(envVarArr[0], GetVaultKey(envVarArr[1], opts, log))
}
}
}
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger, opts *ConfigOpts) {
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger) {
if envVarsToInject.file != "" {
envPath, _ := getFullPathWithHomeDir(envVarsToInject.file)
envPath, _ := resolveDir(envVarsToInject.file)
file, fileErr := os.Open(envPath)
if fileErr != nil {
@ -158,8 +140,7 @@ errEnvFile:
for _, envVal := range envVarsToInject.env {
if strings.Contains(envVal, "=") {
envVarArr := strings.Split(envVal, "=")
process.Env = append(process.Env, fmt.Sprintf("%s=%s", envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts)))
process.Env = append(process.Env, envVal)
}
}
process.Env = append(process.Env, os.Environ()...)
@ -192,6 +173,7 @@ func testFile(c string) error {
return fileOpenErr
}
}
return nil
}
@ -200,12 +182,10 @@ func IsTerminalActive() bool {
}
func IsCmdStdOutEnabled() bool {
return os.Getenv("BACKY_CMDSTDOUT") == "enabled"
return os.Getenv("BACKY_STDOUT") == "enabled"
}
func getFullPathWithHomeDir(path string) (string, error) {
path = strings.TrimSpace(path)
func resolveDir(path string) (string, error) {
if path == "~" {
homeDir, err := os.UserHomeDir()
if err != nil {
@ -227,39 +207,21 @@ func getFullPathWithHomeDir(path string) (string, error) {
// loadEnv loads a .env file from the config file directory
func (opts *ConfigOpts) loadEnv() {
envFileInConfigDir := fmt.Sprintf("%s/.env", path.Dir(opts.ConfigFilePath))
var backyEnv map[string]string
var envFileInConfigDir string
var envFileErr error
if isRemoteURL(opts.ConfigFilePath) {
_, u := getRemoteDir(opts.ConfigFilePath)
envFileInConfigDir = u.JoinPath(".env").String()
envFetcher, err := remotefetcher.NewRemoteFetcher(envFileInConfigDir, opts.Cache)
if err != nil {
return
}
data, err := envFetcher.Fetch(envFileInConfigDir)
if err != nil {
return
}
backyEnv, envFileErr = godotenv.UnmarshalBytes(data)
if envFileErr != nil {
return
}
} else {
envFileInConfigDir = fmt.Sprintf("%s/.env", path.Dir(opts.ConfigFilePath))
backyEnv, envFileErr = godotenv.Read(envFileInConfigDir)
if envFileErr != nil {
return
}
backyEnv, envFileErr := godotenv.Read(envFileInConfigDir)
if envFileErr != nil {
return
}
opts.backyEnv = backyEnv
}
// expandEnvVars expands environment variables with the env used in the config
func expandEnvVars(backyEnv map[string]string, envVars []string) {
env := func(name string) string {
name = strings.ToUpper(name)
envVar, found := backyEnv[name]
if found {
return envVar
@ -267,35 +229,39 @@ func expandEnvVars(backyEnv map[string]string, envVars []string) {
return ""
}
// parse env variables using new macros
for indx, v := range envVars {
if strings.HasPrefix(v, envExternDirectiveStart) && strings.HasSuffix(v, externDirectiveEnd) {
v = strings.TrimPrefix(v, envExternDirectiveStart)
v = strings.TrimRight(v, externDirectiveEnd)
out, _ := shell.Expand(v, env)
envVars[indx] = out
if strings.HasPrefix(v, macroStart) && strings.HasSuffix(v, macroEnd) {
if strings.HasPrefix(v, envMacroStart) {
v = strings.TrimPrefix(v, envMacroStart)
v = strings.TrimRight(v, macroEnd)
out, _ := shell.Expand(v, env)
envVars[indx] = out
}
}
}
}
func getCommandTypeAndSetCommandInfo(command *Command) *Command {
// getCommandType checks for command type and if the command has already been set
// Checks for types package and user
// Returns the modified Command with the package- or userManager command as Cmd and the package- or userOperation as args, plus any additional Args
func getCommandType(command *Command) *Command {
if command.Type == PackageCT && !command.packageCmdSet {
if command.Type == "package" && !command.packageCmdSet {
command.packageCmdSet = true
switch command.PackageOperation {
case PackOpInstall:
case "install":
command.Cmd, command.Args = command.pkgMan.Install(command.PackageName, command.PackageVersion, command.Args)
case PackOpRemove:
case "remove":
command.Cmd, command.Args = command.pkgMan.Remove(command.PackageName, command.Args)
case PackOpUpgrade:
case "upgrade":
command.Cmd, command.Args = command.pkgMan.Upgrade(command.PackageName, command.PackageVersion)
case PackOpCheckVersion:
case "checkVersion":
command.Cmd, command.Args = command.pkgMan.CheckVersion(command.PackageName, command.PackageVersion)
}
}
if command.Type == UserCT && !command.userCmdSet {
if command.Type == "user" && !command.userCmdSet {
command.userCmdSet = true
switch command.UserOperation {
case "add":
@ -303,8 +269,7 @@ func getCommandTypeAndSetCommandInfo(command *Command) *Command {
command.Username,
command.UserHome,
command.UserShell,
command.UserIsSystem,
command.UserCreateHome,
command.SystemUser,
command.UserGroups,
command.Args)
case "modify":
@ -332,7 +297,7 @@ func parsePackageVersion(output string, cmdCtxLogger zerolog.Logger, command *Co
// println(output)
if err != nil {
cmdCtxLogger.Error().Err(err).Str("package", command.PackageName).Msg("Error parsing package version output")
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), err
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.GetOutput), err
}
cmdCtxLogger.Info().
@ -357,97 +322,3 @@ func parsePackageVersion(output string, cmdCtxLogger zerolog.Logger, command *Co
}
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, false), err
}
func getExternalConfigDirectiveValue(key string, opts *ConfigOpts) string {
if !(strings.HasPrefix(key, externDirectiveStart) && strings.HasSuffix(key, externDirectiveEnd)) {
return key
}
key = replaceVarInString(opts.Vars, key, opts.Logger)
opts.Logger.Debug().Str("expanding external key", key).Send()
if strings.HasPrefix(key, envExternDirectiveStart) {
key = strings.TrimPrefix(key, envExternDirectiveStart)
key = strings.TrimSuffix(key, externDirectiveEnd)
key = os.Getenv(key)
}
if strings.HasPrefix(key, externFileDirectiveStart) {
var err error
var keyValue []byte
key = strings.TrimPrefix(key, externFileDirectiveStart)
key = strings.TrimSuffix(key, externDirectiveEnd)
key, err = getFullPathWithHomeDir(key)
if err != nil {
opts.Logger.Err(err).Send()
return ""
}
if !path.IsAbs(key) {
key = path.Join(opts.ConfigDir, key)
}
keyValue, err = os.ReadFile(key)
if err != nil {
opts.Logger.Err(err).Send()
return ""
}
key = string(keyValue)
}
if strings.HasPrefix(key, vaultExternDirectiveStart) {
key = strings.TrimPrefix(key, vaultExternDirectiveStart)
key = strings.TrimSuffix(key, externDirectiveEnd)
key = GetVaultKey(key, opts, opts.Logger)
}
return key
}
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.Key].(string)
if !ok {
return "", fmt.Errorf("value type assertion failed for vault key %s: %T %#v", key.Name, secret.Data[key.Name], secret.Data[key.Name])
}
return value, nil
}
func getVaultKeyData(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 := getVaultKeyData(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
}

View File

@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"sync"
@ -17,7 +16,6 @@ type CacheData struct {
Hash string `yaml:"hash"`
Path string `yaml:"path"`
Type string `yaml:"type"`
URL string `yaml:"url"`
}
type Cache struct {
@ -73,7 +71,7 @@ func (c *Cache) saveToFile() error {
for _, data := range c.store {
cacheData = append(cacheData, data)
}
cacheData = unique(cacheData)
data, err := yaml.Marshal(cacheData)
if err != nil {
return err
@ -103,20 +101,16 @@ func (c *Cache) AddDataToStore(hash string, cacheData CacheData) error {
return c.saveToFile()
}
// Set stores data on disk and in the cache file and returns the cache data
// The filepath of the data is the file name + a SHA256 hash of the URL
func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheData, error) {
c.mu.Lock()
defer c.mu.Unlock()
sourceHash := HashURL(source)
fileName := filepath.Base(source)
path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, sourceHash))
path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, hash))
if _, err := os.Stat(path); os.IsNotExist(err) {
_ = os.MkdirAll(c.dir, 0700)
os.MkdirAll(c.dir, 0700)
}
err := os.WriteFile(path, data, 0644)
@ -128,10 +122,9 @@ func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheDat
Hash: hash,
Path: path,
Type: dataType,
URL: sourceHash,
}
c.store[sourceHash] = cacheData
c.store[hash] = cacheData
// Unlock before calling saveToFile to avoid double-locking
c.mu.Unlock()
@ -171,7 +164,6 @@ func (cf *CachedFetcher) Hash(data []byte) string {
func LoadMetadataFromFile(filePath string) ([]*CacheData, error) {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
// Create the file if it does not exist
_ = os.MkdirAll(path.Dir(filePath), 0700)
emptyData := []byte("[]")
err := os.WriteFile(filePath, emptyData, 0644)
if err != nil {
@ -186,35 +178,9 @@ func LoadMetadataFromFile(filePath string) ([]*CacheData, error) {
var cacheData []*CacheData
err = yaml.Unmarshal(data, &cacheData)
if err != nil {
return nil, err
}
return cacheData, nil
}
func HashURL(url string) string {
hash := sha256.Sum256([]byte(url))
return hex.EncodeToString(hash[:])
}
func unique(cache []CacheData) []CacheData {
var unique []CacheData
type key struct{ value1, value2, value3, value4 string }
m := make(map[key]int)
for _, v := range cache {
k := key{v.URL, v.Hash, v.Path, v.Type}
if i, ok := m[k]; ok {
// Overwrite previous value per requirement in
// question to keep last matching value.
unique[i] = v
} else {
// Unique key found. Record position and collect
// in result.
m[k] = len(unique)
unique = append(unique, v)
}
}
return unique
}

View File

@ -29,7 +29,7 @@ func NewRemoteFetcher(source string, cache *Cache, options ...FetcherOption) (Re
option(&config)
}
// WithFileType was not called. yaml is the default file type
// If FileType is empty (i.e. WithFileType was not called), yaml is the default file type
if strings.TrimSpace(config.FileType) == "" {
config.FileType = "yaml"
}
@ -57,13 +57,11 @@ func NewRemoteFetcher(source string, cache *Cache, options ...FetcherOption) (Re
return nil, err
}
URLHash := HashURL(source)
if cachedData, cacheMeta, exists := cache.Get(URLHash); exists {
println(cachedData)
hash := fetcher.Hash(data)
if cachedData, cacheMeta, exists := cache.Get(hash); exists {
return &CachedFetcher{data: cachedData, path: cacheMeta.Path, dataType: cacheMeta.Type}, nil
}
hash := fetcher.Hash(data)
cacheData, err := cache.Set(source, hash, data, config.FileType)
if err != nil {
return nil, err

View File

@ -20,7 +20,7 @@ func (l *LocalFetcher) Fetch(source string) ([]byte, error) {
if l.config.IgnoreFileNotFound {
return nil, ErrIgnoreFileNotFound
}
return nil, err
return nil, nil
}
file, err := os.Open(source)
if err != nil {

View File

@ -44,11 +44,11 @@ func NewS3Fetcher(endpoint string, options ...FetcherOption) (*S3Fetcher, error)
*/
s3Endpoint := os.Getenv("S3_ENDPOINT")
creds, err := getS3Credentials(os.Getenv("AWS_PROFILE"), s3Endpoint, cfg.HTTPClient)
creds, err := getS3Credentials("default", s3Endpoint, cfg.HTTPClient)
if err != nil {
println(err.Error())
return nil, err
}
// Initialize S3 client if not provided
if cfg.S3Client == nil {
s3Client, err = minio.New(s3Endpoint, &minio.Options{
@ -128,11 +128,12 @@ func (s *S3Fetcher) Hash(data []byte) string {
}
func getS3Credentials(profile, host string, httpClient *http.Client) (*credentials.Credentials, error) {
// println(s3utils.GetRegionFromURL(*u))
homeDir, hdirErr := homedir.Dir()
if hdirErr != nil {
return nil, hdirErr
}
s3Creds := credentials.NewFileAWSCredentials(path.Join(homeDir, ".aws", "credentials"), profile)
s3Creds := credentials.NewFileAWSCredentials(path.Join(homeDir, ".aws", "credentials"), "default")
credVals, credErr := s3Creds.GetWithContext(&credentials.CredContext{Endpoint: host, Client: httpClient})
if credErr != nil {
return nil, credErr

View File

@ -15,7 +15,7 @@ func (l LinuxUserManager) NewLinuxManager() *LinuxUserManager {
}
// AddUser adds a new user to the system.
func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem, createHome bool, groups, args []string) (string, []string) {
func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem bool, groups, args []string) (string, []string) {
baseArgs := []string{}
if isSystem {
@ -38,10 +38,6 @@ func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem, cre
baseArgs = append(baseArgs, args...)
}
if createHome {
baseArgs = append(baseArgs, "-m")
}
args = append(baseArgs, username)
cmd := "useradd"

View File

@ -10,7 +10,7 @@ import (
// UserManager defines the interface for user management operations.
// All functions but one return a string for the command and any args.
type UserManager interface {
AddUser(username, homeDir, shell string, createHome, isSystem bool, groups, args []string) (string, []string)
AddUser(username, homeDir, shell string, isSystem bool, groups, args []string) (string, []string)
RemoveUser(username string) (string, []string)
ModifyUser(username, homeDir, shell string, groups []string) (string, []string)
// Modify password uses chpasswd for Linux systems to build the command to change the password

13
release
View File

@ -1,15 +1,6 @@
#!/bin/bash
set -eou pipefail
go mod tidy
go generate ./...
CURRENT_TAG="$(go run backy.go version -V)"
goreleaser -f .goreleaser/github.yml check
goreleaser -f .goreleaser/gitea.yml check
changie batch $CURRENT_TAG
changie merge
git add .changes/
git commit -am "$CURRENT_TAG"
git tag "$CURRENT_TAG"
# export GORELEASER_CURRENT_TAG="$(go run backy.go version -V)"
git tag "$(go run backy.go version -V)"
git push all
git push all --tags
# goreleaser release -f .goreleaser/gitea.yml --clean --release-notes=".changes/$(go run backy.go version -V).md"

1
tests/.gitignore vendored
View File

@ -1 +0,0 @@
private

View File

@ -1,17 +0,0 @@
commands:
echoTestFail:
cmd: ech
shell: bash
Args: hello world
hooks:
error:
- errorCmd
errorCmd:
name: get docker version
cmd: docker
getOutput: true
outputToLog: true
Args:
- "-v"
host: email-svr

View File

@ -1,14 +0,0 @@
commands:
echoTestFail:
cmd: ech
shell: bash
Args: hello world
hooks:
error:
- errorCm #
errorCmd:
name: get docker version
cmd: docker
Args:
- "-v"

View File

@ -1,16 +0,0 @@
commands:
echoTestSuccess:
cmd: echo
shell: bash
Args: hello world
hooks:
success:
- successCmd
errorCmd:
name: get docker version
cmd: docker
getOutput: true
outputToLog: true
Args:
- "-v"

View File

@ -1,27 +0,0 @@
commands:
vaultEnvVar:
cmd: echo
shell: /bin/zsh
Args:
- ${VAULT_VAR}
environment:
"VAULT_VAR=%{vault:vaultTestSecret}%"
logging:
verbose: true
vault:
token: root
address: http://127.0.0.1:8200
enabled: true
keys:
- name: vaultTestSecret
key: data
mountpath: secret
path: test/var
type: KVv2 # KVv1 or KVv2
cmdLists:
addUsers:
order:
- vaultEnvVar