Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
7be2679b91 | |||
3c6e3ed914 | |||
02bc040e2a | |||
9f1f36215a | |||
ff75f4bbcd | |||
5f40713e98 | |||
cd5f7611a9 | |||
b542711078 | |||
52dbc353e5 | |||
6bef0c3e5b | |||
4d705d78fb | |||
62d47ecfa7 | |||
32444ff82e | |||
a5a7c05640 | |||
bfb81e11b2 | |||
fd4c83f9c0 | |||
fe27c6396a | |||
c89dde186a | |||
18a64de0de | |||
99c622b69f | |||
95e85e8b45 | |||
1a48c7bca5 | |||
5d21764ef1 | |||
c7302f0e17 | |||
fb1c8ec4fb | |||
fe9462dac0 | |||
d8453d1fb0 | |||
65c46a1e26 | |||
f859b5961f | |||
25ddd65f25 | |||
bcba6b2086 | |||
753b03861f | |||
80a45cd595 | |||
551c8ad441 | |||
3823b1bf44 | |||
f777c78aad | |||
bb693dbb97 | |||
7beda281e0 | |||
1143d2850b | |||
8900bd70a4 | |||
6db5f73bc0 | |||
a163c11129 | |||
2b4d191271 | |||
417088c32b | |||
4fa5efa5b6 | |||
a0bf51636c | |||
684edd7985 | |||
3acb20a40f | |||
0007c8696a | |||
cf2baf3601 | |||
e6b9f8e6e6 | |||
2eefc59cf7 | |||
98d8b8e8f2 | |||
1ad50ebcf8 | |||
c483a1056f | |||
3b9f569310 | |||
843be7968b | |||
d477d850ac | |||
8eb3229af7 | |||
d89a208bbd | |||
0d28d6afcf |
@ -0,0 +1 @@
|
||||
*.yaml
|
3
.changes/unreleased/Changed-20250321-090849.yaml
Normal file
3
.changes/unreleased/Changed-20250321-090849.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
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
|
16
.changes/v0.10.0.md
Normal file
16
.changes/v0.10.0.md
Normal file
@ -0,0 +1,16 @@
|
||||
## 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
|
8
.changes/v0.10.1.md
Normal file
8
.changes/v0.10.1.md
Normal file
@ -0,0 +1,8 @@
|
||||
## v0.10.1 - 2025-03-11
|
||||
### Added
|
||||
* UserCommands: add ssh public keys when running locally
|
||||
* UserCommands: add field CreateUserHome
|
||||
### Changed
|
||||
* UserCommands: create temp file when modifing password over SSH
|
||||
* UserCommands: change field name
|
||||
* Vault: keys are now referenced by `name`, and the actual data by `data`
|
6
.changes/v0.10.2.md
Normal file
6
.changes/v0.10.2.md
Normal file
@ -0,0 +1,6 @@
|
||||
## v0.10.2 - 2025-03-19
|
||||
### Added
|
||||
* Notifications: http service added
|
||||
* Variable support. Can be referenced with `%{var:nameOfVar}%` in select string fields.
|
||||
### Changed
|
||||
* vault: initialize vault before validating config
|
6
.changes/v0.8.0.md
Normal file
6
.changes/v0.8.0.md
Normal file
@ -0,0 +1,6 @@
|
||||
## 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
|
12
.changes/v0.9.0.md
Normal file
12
.changes/v0.9.0.md
Normal file
@ -0,0 +1,12 @@
|
||||
## 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
|
3
.changes/v0.9.1.md
Normal file
3
.changes/v0.9.1.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.9.1 - 2025-03-01
|
||||
### Changed
|
||||
* Use EnvVar AWS_PROFILE to get S3 profile
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +1,10 @@
|
||||
!.changie.yaml
|
||||
!.changes/**
|
||||
|
||||
dist/
|
||||
.codegpt
|
||||
|
||||
*.log
|
||||
*.sh
|
||||
*.sh
|
||||
/*.yaml
|
||||
/*.yml
|
||||
|
@ -1,12 +1,13 @@
|
||||
name: goreleaser release
|
||||
steps:
|
||||
golang:
|
||||
image: golang:1.23
|
||||
commands:
|
||||
- go mod tidy
|
||||
- go install github.com/goreleaser/goreleaser/v2@v2.7.0
|
||||
- goreleaser release -f .goreleaser/gitea.yml --release-notes=".changes/$(go run backy.go version -V).md"
|
||||
secrets: [ gitea_token ]
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
|
||||
when:
|
||||
event: tag
|
||||
# release:
|
||||
|
@ -5,7 +5,7 @@ steps:
|
||||
- go build
|
||||
- go test
|
||||
release:
|
||||
image: golangci/golangci-lint:v1.53.3
|
||||
image: golangci/golangci-lint:v1.64.7
|
||||
commands:
|
||||
- golangci-lint run -v --timeout 5m
|
||||
|
||||
|
@ -23,7 +23,14 @@ 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
|
||||
secrets: [ ssh_host_key, ssh_deploy_key, ssh_passphrase ]
|
||||
environment:
|
||||
SSH_HOST_KEY:
|
||||
from_secret: ssh_host_key
|
||||
SSH_DEPLOY_KEY:
|
||||
from_secret: ssh_deploy_key
|
||||
SSH_PASSPHRASE:
|
||||
from_secret: ssh_passphrase
|
||||
|
||||
when:
|
||||
- branch: master
|
||||
- branch: master
|
||||
- path: 'docs/**'
|
57
CHANGELOG.md
57
CHANGELOG.md
@ -6,6 +6,63 @@ 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
|
||||
|
@ -7,14 +7,19 @@
|
||||
],
|
||||
"settings": {
|
||||
"cSpell.words": [
|
||||
"Autorestic",
|
||||
"changie",
|
||||
"Cmds",
|
||||
"remotefetcher",
|
||||
"CMDSTDOUT",
|
||||
"goreleaser",
|
||||
"knadh",
|
||||
"koanf",
|
||||
"mattn",
|
||||
"maunium",
|
||||
"mautrix",
|
||||
"nikoksr",
|
||||
"rawbytes",
|
||||
"remotefetcher",
|
||||
"Strs"
|
||||
]
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ func init() {
|
||||
}
|
||||
|
||||
func Backup(cmd *cobra.Command, args []string) {
|
||||
backyConfOpts := backy.NewOpts(cfgFile, backy.AddCommandLists(cmdLists))
|
||||
backyConfOpts := backy.NewOpts(cfgFile, backy.AddCommandLists(cmdLists), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
|
||||
backyConfOpts.InitConfig()
|
||||
backyConfOpts.ReadConfig()
|
||||
|
||||
|
@ -18,7 +18,11 @@ var (
|
||||
func cron(cmd *cobra.Command, args []string) {
|
||||
parseS3Config()
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.EnableCron())
|
||||
opts := backy.NewOpts(cfgFile,
|
||||
backy.EnableCron(),
|
||||
backy.SetLogFile(logFile),
|
||||
backy.SetCmdStdOut(cmdStdOut))
|
||||
|
||||
opts.InitConfig()
|
||||
opts.ReadConfig()
|
||||
|
||||
|
@ -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))
|
||||
opts := backy.NewOpts(cfgFile, backy.AddCommands(args), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
|
||||
opts.InitConfig()
|
||||
opts.ReadConfig()
|
||||
opts.ExecuteCmds()
|
||||
|
@ -35,7 +35,7 @@ func init() {
|
||||
// 2. stdin (on command line) (TODO)
|
||||
|
||||
func Host(cmd *cobra.Command, args []string) {
|
||||
backyConfOpts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile))
|
||||
backyConfOpts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
|
||||
backyConfOpts.InitConfig()
|
||||
|
||||
backyConfOpts.ReadConfig()
|
||||
|
61
cmd/list.go
61
cmd/list.go
@ -6,16 +6,29 @@ 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 [--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,
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
@ -23,27 +36,51 @@ var listsToList []string
|
||||
var cmdsToList []string
|
||||
|
||||
func init() {
|
||||
|
||||
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.")
|
||||
listCmd.AddCommand(listCmds, listCmdLists)
|
||||
|
||||
}
|
||||
|
||||
func List(cmd *cobra.Command, args []string) {
|
||||
func ListCmds(cmd *cobra.Command, args []string) {
|
||||
|
||||
// setup based on whats passed in:
|
||||
// - cmds
|
||||
// - lists
|
||||
// - if none, list all commands
|
||||
if cmdLists != nil {
|
||||
|
||||
if len(args) > 0 {
|
||||
cmdsToList = args
|
||||
} else {
|
||||
logging.ExitWithMSG("Error: list cmds subcommand needs commands to list", 1, nil)
|
||||
}
|
||||
|
||||
parseS3Config()
|
||||
|
||||
opts := backy.NewOpts(cfgFile)
|
||||
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile))
|
||||
|
||||
opts.InitConfig()
|
||||
opts.ReadConfig()
|
||||
|
||||
opts.ListCommand("rm-sn-db")
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ var (
|
||||
// Used for flags.
|
||||
cfgFile string
|
||||
verbose bool
|
||||
cmdStdOut bool
|
||||
logFile string
|
||||
s3Endpoint string
|
||||
|
||||
@ -35,6 +36,7 @@ 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")
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const versionStr = "0.7.8"
|
||||
const versionStr = "0.10.2"
|
||||
|
||||
var (
|
||||
versionCmd = &cobra.Command{
|
||||
|
@ -19,10 +19,11 @@ Available Commands:
|
||||
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 Lists commands, lists, or hosts defined in config file.
|
||||
list List 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
|
||||
@ -48,6 +49,7 @@ Flags:
|
||||
-l, --lists stringArray 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.
|
||||
@ -66,6 +68,7 @@ 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.
|
||||
@ -88,6 +91,7 @@ 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.
|
||||
@ -111,6 +115,7 @@ Flags:
|
||||
-m, --hosts stringArray Accepts space-separated names of hosts. Specify multiple times for multiple 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.
|
||||
@ -131,6 +136,7 @@ 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.
|
||||
@ -140,18 +146,58 @@ Global Flags:
|
||||
## list
|
||||
|
||||
```
|
||||
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.
|
||||
List commands, lists, or hosts defined in config file
|
||||
|
||||
Usage:
|
||||
backy list [--list=list1,list2,... | -l list1, list2,...] [ -cmd cmd1 cmd2 cmd3...] [flags]
|
||||
backy list [command]
|
||||
|
||||
Available Commands:
|
||||
cmds List commands defined in config file.
|
||||
lists List lists defined in config file.
|
||||
|
||||
Flags:
|
||||
-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.
|
||||
-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
|
||||
|
||||
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.
|
||||
|
@ -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 comma-separated names of commands.
|
||||
-c, --commands strings Accepts space-separated names of commands.
|
||||
-h, --help help for host
|
||||
-m, --hosts strings Accepts comma-separated names of hosts.
|
||||
-m, --hosts strings Accepts space-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,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] [flags]
|
||||
backy exec host [--commands=command1 -commands=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] [flags]
|
||||
```
|
||||
|
29
docs/content/cli/list.md
Normal file
29
docs/content/cli/list.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
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
|
||||
```
|
@ -2,7 +2,7 @@
|
||||
title: "Command Lists"
|
||||
weight: 2
|
||||
description: >
|
||||
This page tells you how to get use command lists.
|
||||
This page tells you how to 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 'cmd-lists.file' is specified
|
||||
2. lists.yml or lists.yaml is found in the same directory as the backy config file
|
||||
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)
|
||||
|
||||
{{% 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 `cmd-lists` 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 `cmdLists` 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"}
|
||||
cmd-lists:
|
||||
cmdLists:
|
||||
docker-container-backup: # this can be any name you want
|
||||
# all commands have to be defined
|
||||
order:
|
||||
|
@ -8,46 +8,21 @@ weight: 1
|
||||
|
||||
### Example Config
|
||||
|
||||
```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
|
||||
```
|
||||
{{% code file="/examples/example.yml" language="yaml" %}}
|
||||
|
||||
Values available for this section **(case-sensitive)**:
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
#### cmd
|
||||
|
||||
@ -120,8 +95,9 @@ The following options are available:
|
||||
The environment variables support expansion:
|
||||
|
||||
- using escaped values `$VAR` or `${VAR}`
|
||||
- using any external directive, and if using the env directive, the variable will be read from a `.env` file
|
||||
|
||||
For now, the variables have to be defined in an `.env` file in the same directory that the program is run from.
|
||||
<!-- For now, the variables expanded have to be defined in an `.env` file in the same directory that the program is run from. -->
|
||||
|
||||
If using it with host specified, the SSH server has to be configured to accept those env variables.
|
||||
|
||||
|
@ -6,14 +6,18 @@ description: This is dedicated to user commands.
|
||||
|
||||
This is dedicated to `user` commands. The command `type` field must be `user`. User is a type that allows one to perform user operations. There are several additional options available when `type` is `user`:
|
||||
|
||||
| name | notes | type | required |
|
||||
| --- | --- | --- | --- |
|
||||
| `userName` | The name of a user to be configured. | `string` | yes |
|
||||
| `userOperation` | The type of operation to perform. | `string` | yes |
|
||||
| `userID` | The user ID to use. | `string` | 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 |
|
||||
| name | notes | type | required | External directive support
|
||||
| ----------------| -------------------------------------------------------------| ---------- | ---------| --------------------------|
|
||||
| `userName` | The name of a user to be configured. | `string` | yes | no |
|
||||
| `userOperation` | The type of operation to perform. | `string` | yes | no |
|
||||
| `userID` | The user ID to use. | `string` | no | no |
|
||||
| `userGroups` | The groups the user should be added to. | `[]string` | no | no |
|
||||
| `systemUser` | Create a system user. | `bool` | no | no |
|
||||
| `userCreateHome`| Create the home directory. | `bool` | no | no |
|
||||
| `userSshPubKeys`| The keys to add to the user's authorized keys. | `[]string` | no | yes |
|
||||
| `userShell` | The shell for the user. | `string` | no | no |
|
||||
| `userHome` | The user's home directory. | `string` | no | no |
|
||||
| `userPassword` | The new password value when using the `password` operation. | `string` | no | yes |
|
||||
|
||||
|
||||
#### example
|
||||
|
15
docs/content/config/directives.md
Normal file
15
docs/content/config/directives.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
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.
|
@ -5,19 +5,19 @@ description: >
|
||||
This page tells you how to use hosts.
|
||||
---
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
## exec host subcommand
|
||||
|
||||
|
@ -39,23 +39,23 @@ There must be a section with an id (eg. `mail.test-svr`) following one of these
|
||||
|
||||
### mail
|
||||
|
||||
| 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`
|
||||
| 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
|
||||
|
||||
### matrix
|
||||
|
||||
| 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`
|
||||
| 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
|
||||
|
||||
To get your access token (assumes you are using [Element](https://element.io/)) :
|
||||
|
||||
|
@ -12,6 +12,10 @@ 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
|
||||
|
||||
Scripts will be coming later.
|
||||
Remote script support is currently limited to http/https endpoints.
|
@ -6,7 +6,7 @@ description: Set up and configure vault.
|
||||
|
||||
[Vault](https://www.vaultproject.io/) is a tool for storing secrets and other data securely.
|
||||
|
||||
Vault config can be used by prefixing `vault:` in front of a password or ENV var.
|
||||
A Vault key can be used by prefixing `%{vault:vault.keys.name}%` in a field that supports external directives.
|
||||
|
||||
This is the object in the config file:
|
||||
|
||||
@ -18,10 +18,12 @@ vault:
|
||||
keys:
|
||||
- name: mongourl
|
||||
mountpath: secret
|
||||
key: data
|
||||
path: mongo/url
|
||||
type: # KVv1 or KVv2
|
||||
- name:
|
||||
path:
|
||||
type:
|
||||
mountpath:
|
||||
type: KVv2 # KVv1 or KVv2
|
||||
- name: someKeyName
|
||||
mountpath: secret
|
||||
key: keyData
|
||||
type: KVv2
|
||||
path: some/path
|
||||
```
|
||||
|
108
docs/content/examples/backy.yaml
Normal file
108
docs/content/examples/backy.yaml
Normal file
@ -0,0 +1,108 @@
|
||||
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
|
24
docs/content/examples/example.yml
Normal file
24
docs/content/examples/example.yml
Normal file
@ -0,0 +1,24 @@
|
||||
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
|
@ -48,7 +48,7 @@ commands:
|
||||
To execute groups of commands in sequence, use a list configuration.
|
||||
|
||||
```yaml
|
||||
cmd-lists:
|
||||
cmdLists:
|
||||
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 `cmd-lists` 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 `cmdLists` key `notifications`.
|
||||
|
||||
```yaml
|
||||
notifications:
|
||||
|
3
docs/layouts/shortcodes/code.html
Normal file
3
docs/layouts/shortcodes/code.html
Normal file
@ -0,0 +1,3 @@
|
||||
{{ $file := .Get "file" | readFile }}
|
||||
{{ $lang := .Get "language" }}
|
||||
{{ (print "```" $lang "\n" $file "\n```") }}
|
24
examples/example.yml
Normal file
24
examples/example.yml
Normal file
@ -0,0 +1,24 @@
|
||||
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
|
110
getCommandHelp
110
getCommandHelp
@ -1,67 +1,83 @@
|
||||
#!/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
|
||||
"
|
||||
|
||||
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 "## Backy "
|
||||
echo " "
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} -h"
|
||||
echo "\`\`\`"
|
||||
echo " "
|
||||
|
||||
|
||||
echo "# Subcommands" >> _index.md
|
||||
echo "" >> _index.md
|
||||
|
||||
echo "## backup" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} backup -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "# Subcommands"
|
||||
echo ""
|
||||
|
||||
echo "## cron" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} cron -h >> _index.md"
|
||||
echo "\`\`\`" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "## backup"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} backup -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 "## cron"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} cron -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 "## exec"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} exec -h"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
|
||||
echo "### exec host"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} exec host -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 "## version"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
eval "${BACKYCOMMAND} version -h"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
|
||||
echo "## list" >> _index.md
|
||||
echo "" >> _index.md
|
||||
echo "\`\`\`" >> _index.md
|
||||
eval "${BACKYCOMMAND} list -h >> _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
|
||||
|
||||
|
||||
mv _index.md "$CLI_PAGE"
|
14
go.mod
14
go.mod
@ -2,13 +2,13 @@ module git.andrewnw.xyz/CyberShell/backy
|
||||
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.6
|
||||
|
||||
replace git.andrewnw.xyz/CyberShell/backy => /home/andrew/Projects/backy
|
||||
toolchain go1.23.7
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0
|
||||
github.com/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,6 +20,7 @@ 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
|
||||
@ -49,7 +50,6 @@ require (
|
||||
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,11 +64,13 @@ 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/rs/xid v1.6.0 // indirect
|
||||
@ -83,9 +85,11 @@ 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/net v0.34.0 // indirect
|
||||
golang.org/x/mod v0.23.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
golang.org/x/tools v0.30.0 // indirect
|
||||
)
|
||||
|
54
go.sum
54
go.sum
@ -30,6 +30,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
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.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
@ -98,6 +100,8 @@ github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI
|
||||
github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c=
|
||||
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
|
||||
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
|
||||
github.com/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=
|
||||
@ -128,9 +132,13 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs=
|
||||
github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM=
|
||||
github.com/pascaldekloe/name v1.0.0 h1:n7LKFgHixETzxpRv2R77YgPUFo85QHGZKrdaYm7eY5U=
|
||||
github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
|
||||
github.com/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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
@ -175,30 +183,72 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
go.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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
|
||||
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
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-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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/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=
|
||||
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=
|
||||
|
145
pkg/backy/allowedexternaldirectives_enumer.go
Normal file
145
pkg/backy/allowedexternaldirectives_enumer.go
Normal file
@ -0,0 +1,145 @@
|
||||
// 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
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"embed"
|
||||
@ -47,23 +48,21 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
outputArr []string // holds the output strings returned by processes
|
||||
)
|
||||
|
||||
// Get the command type
|
||||
// This must be done before concatenating the arguments
|
||||
command = getCommandType(command)
|
||||
// Getting the command type must be done before concatenating the arguments
|
||||
command = getCommandTypeAndSetCommandInfo(command)
|
||||
|
||||
for _, v := range command.Args {
|
||||
ArgsStr += fmt.Sprintf(" %s", v)
|
||||
}
|
||||
|
||||
// print the user's password if it is updated
|
||||
if command.Type == "user" {
|
||||
if command.Type == UserCT {
|
||||
if command.UserOperation == "password" {
|
||||
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
|
||||
}
|
||||
}
|
||||
|
||||
// is host defined
|
||||
if command.Host != nil {
|
||||
if !IsHostLocal(command.Host) {
|
||||
|
||||
outputArr, errSSH = command.RunCmdSSH(cmdCtxLogger, opts)
|
||||
if errSSH != nil {
|
||||
return outputArr, errSSH
|
||||
@ -71,7 +70,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
} else {
|
||||
|
||||
// Handle package operations
|
||||
if command.Type == "package" && command.PackageOperation == "checkVersion" {
|
||||
if command.Type == PackageCT && command.PackageOperation == PackOpCheckVersion {
|
||||
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions")
|
||||
|
||||
// Execute the package version command
|
||||
@ -88,7 +87,66 @@ 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()
|
||||
|
||||
@ -101,7 +159,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 == "package" {
|
||||
if command.Type == PackageCT {
|
||||
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)
|
||||
@ -110,11 +168,17 @@ 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)
|
||||
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger, opts)
|
||||
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
|
||||
@ -127,24 +191,68 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
|
||||
err = localCMD.Run()
|
||||
|
||||
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()
|
||||
// }
|
||||
}
|
||||
outputArr = logCommandOutput(command, cmdOutBuf, cmdCtxLogger, outputArr)
|
||||
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
|
||||
}
|
||||
@ -185,7 +293,7 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
|
||||
}
|
||||
|
||||
// Collect output if required
|
||||
if list.GetOutput || cmdToRun.GetOutput {
|
||||
if list.GetOutput || cmdToRun.GetOutputInList {
|
||||
outStructArr = append(outStructArr, outStruct{
|
||||
CmdName: currentCmd,
|
||||
CmdExecuted: currentCmd,
|
||||
@ -194,17 +302,14 @@ 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]
|
||||
|
||||
// Execute success hooks if the command succeeded
|
||||
if !hasError || cmdsRanContains(cmd, cmdsRan) {
|
||||
if !hasError {
|
||||
cmdToRun.ExecuteHooks("success", opts)
|
||||
}
|
||||
|
||||
@ -221,17 +326,6 @@ 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,
|
||||
@ -300,7 +394,6 @@ 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()
|
||||
}
|
||||
@ -312,10 +405,8 @@ 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)
|
||||
}
|
||||
|
||||
@ -323,7 +414,6 @@ func (opts *ConfigOpts) ExecuteCmds() {
|
||||
}
|
||||
|
||||
opts.closeHostConnections()
|
||||
|
||||
}
|
||||
|
||||
func (c *ConfigOpts) closeHostConnections() {
|
||||
@ -370,27 +460,30 @@ 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("backy-cmd", v).Str("hookType", "error").
|
||||
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("backy-cmd", v).Str("hookType", "success").
|
||||
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("backy-cmd", v).Str("hookType", "final").
|
||||
Logger()
|
||||
finalCmd.RunCmd(cmdLogger, opts)
|
||||
_, _ = finalCmd.RunCmd(cmdLogger, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -400,11 +493,10 @@ func (cmd *Command) GenerateLogger(opts *ConfigOpts) zerolog.Logger {
|
||||
Str("Backy-cmd", cmd.Name).Str("Host", "local machine").
|
||||
Logger()
|
||||
|
||||
if cmd.Host != nil {
|
||||
if !IsHostLocal(cmd.Host) {
|
||||
cmdLogger = opts.Logger.With().
|
||||
Str("Backy-cmd", cmd.Name).Str("Host", *cmd.Host).
|
||||
Str("Backy-cmd", cmd.Name).Str("Host", cmd.Host).
|
||||
Logger()
|
||||
|
||||
}
|
||||
return cmdLogger
|
||||
}
|
||||
@ -416,7 +508,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 {
|
||||
@ -426,6 +518,32 @@ 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 {
|
||||
|
||||
// }
|
||||
|
141
pkg/backy/commandtype_enumer.go
Normal file
141
pkg/backy/commandtype_enumer.go
Normal file
@ -0,0 +1,141 @@
|
||||
// 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
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package backy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@ -22,10 +21,13 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const macroStart string = "%{"
|
||||
const macroEnd string = "}%"
|
||||
const envMacroStart string = "%{env:"
|
||||
const vaultMacroStart string = "%{vault:"
|
||||
const (
|
||||
externDirectiveStart string = "%{"
|
||||
externDirectiveEnd string = "}%"
|
||||
externFileDirectiveStart string = "%{file:"
|
||||
envExternDirectiveStart string = "%{env:"
|
||||
vaultExternDirectiveStart string = "%{vault:"
|
||||
)
|
||||
|
||||
func (opts *ConfigOpts) InitConfig() {
|
||||
var err error
|
||||
@ -53,7 +55,7 @@ func (opts *ConfigOpts) InitConfig() {
|
||||
cacheDir := homeCacheDir
|
||||
|
||||
// Load metadata from file
|
||||
opts.CachedData, err = remotefetcher.LoadMetadataFromFile(path.Join(backyHomeConfDir, "cache.yml"))
|
||||
opts.CachedData, err = remotefetcher.LoadMetadataFromFile(path.Join(backyHomeConfDir, "cache", "cache.yml"))
|
||||
if err != nil {
|
||||
fmt.Println("Error loading metadata:", err)
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
@ -77,27 +79,102 @@ func (opts *ConfigOpts) InitConfig() {
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error initializing cache: %v", err), 1, nil)
|
||||
}
|
||||
// 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 isRemoteURL(opts.ConfigFilePath) {
|
||||
p, _ := getRemoteDir(opts.ConfigFilePath)
|
||||
opts.ConfigDir = p
|
||||
}
|
||||
|
||||
fetcher, err := remotefetcher.NewRemoteFetcher(opts.ConfigFilePath, opts.Cache)
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error initializing config fetcher: %v", err), 1, nil)
|
||||
}
|
||||
|
||||
if opts.ConfigFilePath != "" {
|
||||
loadConfigFile(fetcher, opts.ConfigFilePath, backyKoanf, opts)
|
||||
} else {
|
||||
loadDefaultConfigFiles(fetcher, configFiles, backyKoanf, opts)
|
||||
}
|
||||
|
||||
opts.koanf = backyKoanf
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
CheckConfigValues(backyKoanf, opts.ConfigFilePath)
|
||||
|
||||
validateExecCommandsFromCLI(backyKoanf, opts)
|
||||
|
||||
setLoggingOptions(backyKoanf, opts)
|
||||
|
||||
log := setupLogger(opts)
|
||||
opts.Logger = log
|
||||
|
||||
log.Info().Str("config file", opts.ConfigFilePath).Send()
|
||||
|
||||
if err := opts.initVault(); err != nil {
|
||||
log.Err(err).Send()
|
||||
}
|
||||
|
||||
unmarshalConfigIntoStruct(backyKoanf, "commands", &opts.Cmds, opts.Logger)
|
||||
|
||||
getCommandEnvironments(opts)
|
||||
|
||||
unmarshalConfigIntoStruct(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)
|
||||
|
||||
if opts.cronEnabled && len(opts.CmdConfigLists) == 0 {
|
||||
logging.ExitWithMSG("No cron fields detected in any command lists", 1, nil)
|
||||
}
|
||||
|
||||
if err := processCmds(opts); err != nil {
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
filterExecuteLists(opts)
|
||||
|
||||
if backyKoanf.Exists("notifications") {
|
||||
unmarshalConfigIntoStruct(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger)
|
||||
}
|
||||
|
||||
opts.SetupNotify()
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
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)
|
||||
logging.ExitWithMSG(generateFileFetchErrorString(filePath, "config", err), 1, nil)
|
||||
}
|
||||
|
||||
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
|
||||
@ -117,7 +194,9 @@ func loadDefaultConfigFiles(fetcher remotefetcher.RemoteFetcher, configFiles []s
|
||||
|
||||
if data != nil {
|
||||
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err == nil {
|
||||
continue
|
||||
break
|
||||
} else {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error loading config from file %s: %v", c, err), 1, &opts.Logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,63 +206,6 @@ func loadDefaultConfigFiles(fetcher remotefetcher.RemoteFetcher, configFiles []s
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
|
||||
setTerminalEnv()
|
||||
|
||||
backyKoanf := opts.koanf
|
||||
|
||||
opts.loadEnv()
|
||||
|
||||
if backyKoanf.Bool(getNestedConfig("logging", "cmd-std-out")) {
|
||||
os.Setenv("BACKY_STDOUT", "enabled")
|
||||
}
|
||||
|
||||
CheckConfigValues(backyKoanf, opts.ConfigFilePath)
|
||||
|
||||
validateCommands(backyKoanf, opts)
|
||||
|
||||
setLoggingOptions(backyKoanf, opts)
|
||||
|
||||
log := setupLogger(opts)
|
||||
opts.Logger = log
|
||||
|
||||
log.Info().Str("config file", opts.ConfigFilePath).Send()
|
||||
|
||||
unmarshalConfig(backyKoanf, "commands", &opts.Cmds, opts.Logger)
|
||||
|
||||
validateCommandEnvironments(opts)
|
||||
|
||||
unmarshalConfig(backyKoanf, "hosts", &opts.Hosts, opts.Logger)
|
||||
|
||||
resolveHostConfigs(opts)
|
||||
|
||||
loadCommandLists(opts, backyKoanf)
|
||||
|
||||
validateCommandLists(opts)
|
||||
|
||||
if opts.cronEnabled && len(opts.CmdConfigLists) == 0 {
|
||||
logging.ExitWithMSG("No cron fields detected in any command lists", 1, nil)
|
||||
}
|
||||
|
||||
if err := processCmds(opts); err != nil {
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
filterExecuteLists(opts)
|
||||
|
||||
if backyKoanf.Exists("notifications") {
|
||||
unmarshalConfig(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger)
|
||||
}
|
||||
|
||||
opts.SetupNotify()
|
||||
|
||||
if err := opts.setupVault(); err != nil {
|
||||
log.Err(err).Send()
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func setTerminalEnv() {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
||||
os.Setenv("BACKY_TERM", "enabled")
|
||||
@ -192,7 +214,7 @@ func setTerminalEnv() {
|
||||
}
|
||||
}
|
||||
|
||||
func validateCommands(k *koanf.Koanf, opts *ConfigOpts) {
|
||||
func validateExecCommandsFromCLI(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)
|
||||
@ -209,6 +231,7 @@ func setLoggingOptions(k *koanf.Koanf, opts *ConfigOpts) {
|
||||
logFile = k.String(getLoggingKeyFromConfig("file"))
|
||||
opts.LogFilePath = logFile
|
||||
}
|
||||
opts.LogFilePath = logFile
|
||||
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
if isLoggingVerbose {
|
||||
@ -228,17 +251,20 @@ func setupLogger(opts *ConfigOpts) zerolog.Logger {
|
||||
return zerolog.New(writers).With().Timestamp().Logger()
|
||||
}
|
||||
|
||||
func unmarshalConfig(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) {
|
||||
func unmarshalConfigIntoStruct(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) {
|
||||
if err := k.UnmarshalWithConf(key, target, koanf.UnmarshalConf{Tag: "yaml"}); err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error unmarshalling %s struct: %v", key, err), 1, &log)
|
||||
logging.ExitWithMSG(fmt.Sprintf("error unmarshaling key %s into struct: %v", key, err), 1, &log)
|
||||
}
|
||||
}
|
||||
|
||||
func validateCommandEnvironments(opts *ConfigOpts) {
|
||||
func getCommandEnvironments(opts *ConfigOpts) {
|
||||
for cmdName, cmdConf := range opts.Cmds {
|
||||
if cmdConf.Env == "" {
|
||||
continue
|
||||
}
|
||||
opts.Logger.Debug().Str("env file", cmdConf.Env).Str("cmd", cmdName).Send()
|
||||
if err := testFile(cmdConf.Env); err != nil {
|
||||
opts.Logger.Info().Str("cmd", cmdName).Err(err).Send()
|
||||
os.Exit(1)
|
||||
logging.ExitWithMSG("Could not open file"+cmdConf.Env+": "+err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
expandEnvVars(opts.backyEnv, cmdConf.Environment)
|
||||
}
|
||||
@ -267,19 +293,29 @@ 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
|
||||
// if config file is remote, use the directory of the remote file
|
||||
var p string
|
||||
if isRemoteURL(opts.ConfigFilePath) {
|
||||
_, u = getRemoteDir(opts.ConfigFilePath)
|
||||
p, u = getRemoteDir(opts.ConfigFilePath)
|
||||
opts.ConfigDir = p
|
||||
listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()}
|
||||
} else {
|
||||
backyConfigFileDir = path.Dir(opts.ConfigFilePath)
|
||||
opts.ConfigDir = path.Dir(opts.ConfigFilePath)
|
||||
listConfigFiles = []string{
|
||||
path.Join(backyConfigFileDir, "lists.yml"),
|
||||
path.Join(backyConfigFileDir, "lists.yaml"),
|
||||
// "./lists.yml", "./lists.yaml",
|
||||
path.Join(opts.ConfigDir, "lists.yml"),
|
||||
path.Join(opts.ConfigDir, "lists.yaml"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,10 +327,11 @@ func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
|
||||
}
|
||||
}
|
||||
|
||||
if backyKoanf.Exists("cmd-lists") {
|
||||
unmarshalConfig(backyKoanf, "cmd-lists", &opts.CmdConfigLists, opts.Logger)
|
||||
if backyKoanf.Exists("cmd-lists.file") {
|
||||
if backyKoanf.Exists("cmdLists") {
|
||||
if backyKoanf.Exists("cmdLists.file") {
|
||||
loadCmdListsFile(backyKoanf, listsConfig, opts)
|
||||
} else {
|
||||
unmarshalConfigIntoStruct(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -334,12 +371,14 @@ 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("cmd-lists.file"))
|
||||
opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmdLists.file"))
|
||||
if !path.IsAbs(opts.CmdListFile) {
|
||||
opts.CmdListFile = path.Join(path.Dir(opts.ConfigFilePath), opts.CmdListFile)
|
||||
}
|
||||
@ -352,21 +391,28 @@ func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *C
|
||||
|
||||
data, err := fetcher.Fetch(opts.CmdListFile)
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("Could not fetch config file %s: %v", opts.CmdListFile, err), 1, nil)
|
||||
logging.ExitWithMSG(generateFileFetchErrorString(opts.CmdListFile, "list config", 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)
|
||||
}
|
||||
|
||||
unmarshalConfig(listsConfig, "cmd-lists", &opts.CmdConfigLists, opts.Logger)
|
||||
keyNotSupported("cmd-lists", "cmdLists", listsConfig, opts, true)
|
||||
unmarshalConfigIntoStruct(listsConfig, "cmdLists", &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
|
||||
}
|
||||
@ -406,11 +452,11 @@ func getLoggingKeyFromConfig(key string) string {
|
||||
return fmt.Sprintf("logging.%s", key)
|
||||
}
|
||||
|
||||
func getCmdListFromConfig(list string) string {
|
||||
return fmt.Sprintf("cmd-lists.%s", list)
|
||||
}
|
||||
// func getCmdListFromConfig(list string) string {
|
||||
// return fmt.Sprintf("cmdLists.%s", list)
|
||||
// }
|
||||
|
||||
func (opts *ConfigOpts) setupVault() error {
|
||||
func (opts *ConfigOpts) initVault() error {
|
||||
if !opts.koanf.Bool("vault.enabled") {
|
||||
return nil
|
||||
}
|
||||
@ -431,91 +477,34 @@ func (opts *ConfigOpts) setupVault() error {
|
||||
token = os.Getenv("VAULT_TOKEN")
|
||||
}
|
||||
if strings.TrimSpace(token) == "" {
|
||||
return fmt.Errorf("no token found, but one was required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN")
|
||||
return fmt.Errorf("no token found. One is required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN")
|
||||
}
|
||||
|
||||
client.SetToken(token)
|
||||
|
||||
unmarshalErr := opts.koanf.UnmarshalWithConf("vault.keys", &opts.VaultKeys, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
if unmarshalErr != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error unmarshalling vault.keys into struct: %v", unmarshalErr), 1, &opts.Logger)
|
||||
logging.ExitWithMSG(fmt.Sprintf("error unmarshaling 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -538,9 +527,13 @@ func processCmds(opts *ConfigOpts) error {
|
||||
}
|
||||
}
|
||||
|
||||
// resolve hosts
|
||||
if cmd.Host != nil {
|
||||
host, hostFound := opts.Hosts[*cmd.Host]
|
||||
if !IsHostLocal(cmd.Host) {
|
||||
|
||||
cmdHost := replaceVarInString(opts.Vars, cmd.Host, opts.Logger)
|
||||
if cmdHost != cmd.Host {
|
||||
cmd.Host = cmdHost
|
||||
}
|
||||
host, hostFound := opts.Hosts[cmd.Host]
|
||||
if hostFound {
|
||||
cmd.RemoteHost = host
|
||||
cmd.RemoteHost.Host = host.Host
|
||||
@ -548,17 +541,32 @@ func processCmds(opts *ConfigOpts) error {
|
||||
cmd.RemoteHost.HostName = host.HostName
|
||||
}
|
||||
} else {
|
||||
opts.Hosts[*cmd.Host] = &Host{Host: *cmd.Host}
|
||||
cmd.RemoteHost = &Host{Host: *cmd.Host}
|
||||
opts.Logger.Info().Msgf("adding host %s to host list", cmd.Host)
|
||||
if opts.Hosts == nil {
|
||||
opts.Hosts = make(map[string]*Host)
|
||||
}
|
||||
opts.Hosts[cmd.Host] = &Host{Host: cmd.Host}
|
||||
cmd.RemoteHost = &Host{Host: cmd.Host}
|
||||
}
|
||||
} else {
|
||||
|
||||
if cmd.Dir != nil {
|
||||
|
||||
cmdDir, err := getFullPathWithHomeDir(*cmd.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Dir = &cmdDir
|
||||
} else {
|
||||
cmd.Dir = &opts.ConfigDir
|
||||
}
|
||||
}
|
||||
|
||||
// Parse package commands
|
||||
if cmd.Type == "package" {
|
||||
if cmd.Type == PackageCT {
|
||||
if cmd.PackageManager == "" {
|
||||
return fmt.Errorf("package manager is required for package command %s", cmd.PackageName)
|
||||
}
|
||||
if cmd.PackageOperation == "" {
|
||||
if cmd.PackageOperation.String() == "" {
|
||||
return fmt.Errorf("package operation is required for package command %s", cmd.PackageName)
|
||||
}
|
||||
if cmd.PackageName == "" {
|
||||
@ -567,36 +575,51 @@ func processCmds(opts *ConfigOpts) error {
|
||||
var err error
|
||||
|
||||
// Validate the operation
|
||||
switch cmd.PackageOperation {
|
||||
case "install", "remove", "upgrade", "checkVersion":
|
||||
if cmd.PackageOperation.IsAPackageOperation() {
|
||||
|
||||
cmd.pkgMan, err = pkgman.PackageManagerFactory(cmd.PackageManager, pkgman.WithoutAuth())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
} else {
|
||||
return fmt.Errorf("unsupported package operation %s for command %s", cmd.PackageOperation, cmd.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Parse user commands
|
||||
if cmd.Type == "user" {
|
||||
if cmd.Type == UserCT {
|
||||
if cmd.Username == "" {
|
||||
return fmt.Errorf("username is required for user command %s", cmd.Name)
|
||||
}
|
||||
|
||||
detectOSType(cmd, opts)
|
||||
var err error
|
||||
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()
|
||||
}
|
||||
|
||||
// Validate the operation
|
||||
switch cmd.UserOperation {
|
||||
case "add", "remove", "modify", "checkIfExists", "delete", "password":
|
||||
cmd.userMan, err = usermanager.NewUserManager(cmd.OS)
|
||||
if cmd.Host != nil {
|
||||
host, ok := opts.Hosts[*cmd.Host]
|
||||
|
||||
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 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
|
||||
}
|
||||
@ -606,24 +629,30 @@ func processCmds(opts *ConfigOpts) error {
|
||||
|
||||
}
|
||||
|
||||
if cmd.Type == "remoteScript" {
|
||||
if cmd.Type == RemoteScriptCT {
|
||||
var fetchErr error
|
||||
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{}
|
||||
@ -650,13 +679,18 @@ func processHooks(cmd *Command, hooks []string, opts *ConfigOpts, hookType strin
|
||||
}
|
||||
|
||||
func detectOSType(cmd *Command, opts *ConfigOpts) error {
|
||||
if cmd.Host == nil {
|
||||
if runtime.GOOS == "linux" { // also can be specified to FreeBSD
|
||||
|
||||
if IsHostLocal(cmd.Host) {
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
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
|
||||
@ -677,3 +711,35 @@ 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")
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,7 @@ func (opts *ConfigOpts) Cron() {
|
||||
s := gocron.NewScheduler(time.Local)
|
||||
s.TagsUnique()
|
||||
cmdLists := opts.CmdConfigLists
|
||||
for listName, config := range cmdLists {
|
||||
if config.Name == "" {
|
||||
config.Name = listName
|
||||
}
|
||||
for _, config := range cmdLists {
|
||||
|
||||
cron := strings.TrimSpace(config.Cron)
|
||||
if cron != "" {
|
||||
|
@ -23,8 +23,7 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
|
||||
var cmdFound bool = false
|
||||
var cmdInfo *Command
|
||||
// check commands in file against cmd
|
||||
for _, cmdInFile := range opts.executeCmds {
|
||||
print(cmdInFile)
|
||||
for cmdInFile := range opts.Cmds {
|
||||
cmdFound = false
|
||||
|
||||
if cmd == cmdInFile {
|
||||
@ -37,28 +36,39 @@ func (opts *ConfigOpts) ListCommand(cmd string) {
|
||||
// print the command's information
|
||||
if cmdFound {
|
||||
|
||||
print("Command: ")
|
||||
println("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 is remote or local
|
||||
if cmdInfo.Host != nil {
|
||||
|
||||
// is it remote or local
|
||||
if !IsHostLocal(cmdInfo.Host) {
|
||||
println()
|
||||
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)
|
||||
@ -66,3 +76,38 @@ 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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ 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"
|
||||
@ -30,6 +31,12 @@ 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() {
|
||||
|
||||
@ -58,6 +65,8 @@ 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":
|
||||
@ -66,14 +75,23 @@ 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)
|
||||
// service is not recognized
|
||||
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)
|
||||
default:
|
||||
opts.Logger.Info().Err(fmt.Errorf("id %s not found", id)).Str("list", confName).Send()
|
||||
}
|
||||
@ -99,3 +117,19 @@ 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
|
||||
}
|
||||
|
145
pkg/backy/packageoperation_enumer.go
Normal file
145
pkg/backy/packageoperation_enumer.go
Normal file
@ -0,0 +1,145 @@
|
||||
// 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
|
||||
}
|
256
pkg/backy/ssh.go
256
pkg/backy/ssh.go
@ -15,14 +15,16 @@ 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 three ways: \n privatekeypassword: env:PR_KEY_PASS \n privatekeypassword: file:/path/to/password-file \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 two ways: \n Using external directives - see docs \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
|
||||
@ -54,7 +56,7 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
|
||||
|
||||
if !remoteConfig.useDefaultConfig {
|
||||
var err error
|
||||
remoteConfig.ConfigFilePath, err = resolveDir(remoteConfig.ConfigFilePath)
|
||||
remoteConfig.ConfigFilePath, err = getFullPathWithHomeDir(remoteConfig.ConfigFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -63,7 +65,7 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
|
||||
return sshConfigFileOpenErr
|
||||
}
|
||||
} else {
|
||||
defaultConfig, _ := resolveDir("~/.ssh/config")
|
||||
defaultConfig, _ := getFullPathWithHomeDir("~/.ssh/config")
|
||||
configFile, sshConfigFileOpenErr = os.Open(defaultConfig)
|
||||
if sshConfigFileOpenErr != nil {
|
||||
return sshConfigFileOpenErr
|
||||
@ -119,7 +121,6 @@ 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 {
|
||||
@ -180,11 +181,7 @@ func (remoteHost *Host) GetAuthMethods(opts *ConfigOpts) error {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteHost.PrivateKeyPassword, err = GetPrivateKeyPassword(remoteHost.PrivateKeyPassword, opts, opts.Logger)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteHost.PrivateKeyPassword = GetPrivateKeyPassword(remoteHost.PrivateKeyPassword, opts)
|
||||
|
||||
if remoteHost.PrivateKeyPassword == "" {
|
||||
|
||||
@ -207,14 +204,13 @@ func (remoteHost *Host) GetAuthMethods(opts *ConfigOpts) error {
|
||||
}
|
||||
}
|
||||
|
||||
if remoteHost.Password == "" {
|
||||
if remoteHost.Password != "" {
|
||||
|
||||
remoteHost.Password, err = GetPassword(remoteHost.Password, opts, opts.Logger)
|
||||
opts.Logger.Debug().Str("password", remoteHost.Password).Str("Host", remoteHost.Host).Send()
|
||||
|
||||
if err != nil {
|
||||
remoteHost.Password = GetPassword(remoteHost.Password, opts)
|
||||
|
||||
return err
|
||||
}
|
||||
// opts.Logger.Debug().Str("actual password", remoteHost.Password).Str("Host", remoteHost.Host).Send()
|
||||
|
||||
remoteHost.ClientConfig.Auth = append(remoteHost.ClientConfig.Auth, ssh.Password(remoteHost.Password))
|
||||
}
|
||||
@ -242,22 +238,20 @@ func (remoteHost *Host) GetPrivateKeyFileFromConfig() {
|
||||
identityFile = remoteHost.PrivateKeyPath
|
||||
}
|
||||
|
||||
remoteHost.PrivateKeyPath, _ = resolveDir(identityFile)
|
||||
remoteHost.PrivateKeyPath, _ = getFullPathWithHomeDir(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 specifed?
|
||||
// port specified?
|
||||
// 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
|
||||
@ -272,7 +266,6 @@ 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
|
||||
}
|
||||
@ -315,7 +308,6 @@ 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
|
||||
@ -323,74 +315,23 @@ 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 (remotehHost *Host) GetKnownHosts() error {
|
||||
func (remoteHost *Host) GetKnownHosts() error {
|
||||
var knownHostsFileErr error
|
||||
if TS(remotehHost.KnownHostsFile) != "" {
|
||||
remotehHost.KnownHostsFile, knownHostsFileErr = resolveDir(remotehHost.KnownHostsFile)
|
||||
if TS(remoteHost.KnownHostsFile) != "" {
|
||||
remoteHost.KnownHostsFile, knownHostsFileErr = getFullPathWithHomeDir(remoteHost.KnownHostsFile)
|
||||
return knownHostsFileErr
|
||||
}
|
||||
remotehHost.KnownHostsFile, knownHostsFileErr = resolveDir("~/.ssh/known_hosts")
|
||||
remoteHost.KnownHostsFile, knownHostsFileErr = getFullPathWithHomeDir("~/.ssh/known_hosts")
|
||||
return knownHostsFileErr
|
||||
}
|
||||
|
||||
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
|
||||
func GetPrivateKeyPassword(key string, opts *ConfigOpts) string {
|
||||
return getExternalConfigDirectiveValue(key, opts)
|
||||
}
|
||||
|
||||
// GetPassword gets any password
|
||||
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 GetPassword(pass string, opts *ConfigOpts) string {
|
||||
return getExternalConfigDirectiveValue(pass, opts)
|
||||
}
|
||||
|
||||
func (remoteConfig *Host) GetProxyJumpFromConfig(hosts map[string]*Host) error {
|
||||
@ -442,7 +383,7 @@ func (remoteConfig *Host) GetProxyJumpConfig(hosts map[string]*Host, opts *Confi
|
||||
return sshConfigFileOpenErr
|
||||
}
|
||||
} else {
|
||||
defaultConfig, _ := resolveDir("~/.ssh/config")
|
||||
defaultConfig, _ := getFullPathWithHomeDir("~/.ssh/config")
|
||||
configFile, sshConfigFileOpenErr = os.Open(defaultConfig)
|
||||
if sshConfigFileOpenErr != nil {
|
||||
return sshConfigFileOpenErr
|
||||
@ -480,7 +421,6 @@ 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
|
||||
@ -492,10 +432,7 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
env: command.Environment,
|
||||
}
|
||||
)
|
||||
// Get the command type
|
||||
// This must be done before concatenating the arguments
|
||||
command.Type = strings.TrimSpace(command.Type)
|
||||
command = getCommandType(command)
|
||||
command = getCommandTypeAndSetCommandInfo(command)
|
||||
|
||||
// Prepare command arguments
|
||||
for _, v := range command.Args {
|
||||
@ -504,8 +441,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", getCommandTypeLabel(command.Type), *command.Host)
|
||||
Str("Host", command.Host).
|
||||
Msgf("Running %s on host %s", getCommandTypeAndSetCommandInfoLabel(command.Type), command.Host)
|
||||
|
||||
// cmdCtxLogger.Debug().Str("cmd", command.Cmd).Strs("args", command.Args).Send()
|
||||
|
||||
@ -536,12 +473,14 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
|
||||
// Handle command execution based on type
|
||||
switch command.Type {
|
||||
case "script":
|
||||
case ScriptCT:
|
||||
return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf)
|
||||
case "scriptFile":
|
||||
case RemoteScriptCT:
|
||||
return command.runRemoteScript(commandSession, cmdCtxLogger, &cmdOutBuf)
|
||||
case ScriptFileCT:
|
||||
return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf)
|
||||
case "package":
|
||||
if command.PackageOperation == "checkVersion" {
|
||||
case PackageCT:
|
||||
if command.PackageOperation == PackOpCheckVersion {
|
||||
commandSession.Stderr = nil
|
||||
// Execute the package version command remotely
|
||||
// Parse the output of package version command
|
||||
@ -558,7 +497,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, true), fmt.Errorf("error running command: %w", err)
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running command: %w", err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
@ -568,13 +507,100 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
}
|
||||
cmdCtxLogger.Debug().Str("cmd + args", ArgsStr).Send()
|
||||
// Run simple command
|
||||
|
||||
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
|
||||
}
|
||||
if err := commandSession.Run(ArgsStr); err != nil {
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.GetOutput), fmt.Errorf("error running command: %w", err)
|
||||
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, true), nil
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil
|
||||
}
|
||||
|
||||
func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandSession *ssh.Session, cmdOutBuf bytes.Buffer) ([]string, error) {
|
||||
@ -593,17 +619,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.GetOutput), fmt.Errorf("error: package %s not listed: %w", command.PackageName, err)
|
||||
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 running %s: %w", ArgsStr, err)
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error running %s: %w", ArgsStr, err)
|
||||
}
|
||||
|
||||
return parsePackageVersion(string(cmdOut), cmdCtxLogger, command, cmdOutBuf)
|
||||
}
|
||||
|
||||
// getCommandTypeLabel returns a human-readable label for the command type.
|
||||
func getCommandTypeLabel(commandType string) string {
|
||||
if commandType == "" {
|
||||
// getCommandTypeAndSetCommandInfoLabel returns a human-readable label for the command type.
|
||||
func getCommandTypeAndSetCommandInfoLabel(commandType CommandType) string {
|
||||
if !commandType.IsACommandType() {
|
||||
return "command"
|
||||
}
|
||||
return fmt.Sprintf("%s command", commandType)
|
||||
@ -625,7 +651,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.GetOutput), nil
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil
|
||||
}
|
||||
|
||||
// runScriptFile handles the execution of script files.
|
||||
@ -644,7 +670,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, true), nil
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.OutputToLog), nil
|
||||
}
|
||||
|
||||
// prepareScriptBuffer prepares a buffer for inline scripts.
|
||||
@ -688,9 +714,28 @@ 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 := resolveDir(filePath)
|
||||
resolvedPath, err := getFullPathWithHomeDir(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -766,3 +811,8 @@ func CheckIfHostHasHostName(host string) (bool, string) {
|
||||
println(HostName)
|
||||
return HostName != "", HostName
|
||||
}
|
||||
|
||||
func IsHostLocal(host string) bool {
|
||||
host = strings.ToLower(host)
|
||||
return host == "127.0.0.1" || host == "localhost" || host == ""
|
||||
}
|
||||
|
8
pkg/backy/tools.go
Normal file
8
pkg/backy/tools.go
Normal file
@ -0,0 +1,8 @@
|
||||
//go:build tools
|
||||
|
||||
package backy
|
||||
|
||||
import (
|
||||
// Protect this entry in go.mod from being removed by go mod tidy.
|
||||
_ "github.com/dmarkham/enumer"
|
||||
)
|
@ -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,100 +51,83 @@ type (
|
||||
Command struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
|
||||
// command to run
|
||||
Cmd string `yaml:"cmd"`
|
||||
|
||||
// Possible values: script, scriptFile
|
||||
// If blank, it is regular command.
|
||||
Type string `yaml:"type,omitempty"`
|
||||
// See CommandType enum further down the page for acceptable values
|
||||
Type CommandType `yaml:"type,omitempty"`
|
||||
|
||||
// host on which to run cmd
|
||||
Host *string `yaml:"host,omitempty"`
|
||||
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"`
|
||||
|
||||
// Output determines if output is requested.
|
||||
//
|
||||
// Only for when command is in a list.
|
||||
GetOutput bool `yaml:"getOutput,omitempty"`
|
||||
GetOutputInList bool `yaml:"getOutputInList,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 specifies the action for package-related commands (e.g., "install" or "remove")
|
||||
PackageOperation string `yaml:"packageOperation,omitempty"`
|
||||
PackageOperation PackageOperation `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"`
|
||||
|
||||
// SystemUser specifies whether the user is a system account
|
||||
SystemUser bool `yaml:"systemUser,omitempty"`
|
||||
UserCreateHome bool `yaml:"userCreateHome,omitempty"`
|
||||
|
||||
UserIsSystem bool `yaml:"userIsSystem,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
|
||||
@ -187,7 +170,7 @@ type (
|
||||
|
||||
// CmdConfigLists holds the lists of commands to be run in order.
|
||||
// Key is the command list name.
|
||||
CmdConfigLists map[string]*CmdList `yaml:"cmd-lists"`
|
||||
CmdConfigLists map[string]*CmdList `yaml:"cmdLists"`
|
||||
|
||||
// Hosts holds the Host config.
|
||||
// key is the host.
|
||||
@ -198,13 +181,14 @@ type (
|
||||
// Global log level
|
||||
BackyLogLvl *string
|
||||
|
||||
// Holds config file
|
||||
CmdStdOut bool
|
||||
|
||||
ConfigFilePath string
|
||||
|
||||
// Holds log file
|
||||
ConfigDir string
|
||||
|
||||
LogFilePath string
|
||||
|
||||
// for command list file
|
||||
CmdListFile string
|
||||
|
||||
// use command lists using cron
|
||||
@ -221,6 +205,8 @@ type (
|
||||
|
||||
List ListConfig
|
||||
|
||||
Vars map[string]string `yaml:"variables"`
|
||||
|
||||
VaultKeys []*VaultKey `yaml:"keys"`
|
||||
|
||||
koanf *koanf.Koanf
|
||||
@ -239,6 +225,7 @@ type (
|
||||
|
||||
VaultKey struct {
|
||||
Name string `yaml:"name"`
|
||||
Key string `yaml:"key"`
|
||||
Path string `yaml:"path"`
|
||||
ValueType string `yaml:"type"`
|
||||
MountPath string `yaml:"mountpath"`
|
||||
@ -254,6 +241,7 @@ 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 {
|
||||
@ -288,4 +276,41 @@ 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
|
||||
)
|
||||
|
@ -6,6 +6,7 @@ package backy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@ -15,6 +16,8 @@ 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"
|
||||
@ -64,6 +67,13 @@ 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) {
|
||||
@ -84,7 +94,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 := resolveDir(envVarsToInject.file)
|
||||
envPath, envPathErr := getFullPathWithHomeDir(envVarsToInject.file)
|
||||
if envPathErr != nil {
|
||||
log.Fatal().Str("envFile", envPath).Err(envPathErr).Send()
|
||||
}
|
||||
@ -100,7 +110,11 @@ func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opt
|
||||
goto errEnvFile
|
||||
}
|
||||
for key, val := range envMap {
|
||||
process.Setenv(key, GetVaultKey(val, opts, log))
|
||||
err = process.Setenv(key, GetVaultKey(val, opts, log))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Send()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,14 +125,18 @@ errEnvFile:
|
||||
if strings.Contains(envVal, "=") {
|
||||
envVarArr := strings.Split(envVal, "=")
|
||||
|
||||
process.Setenv(envVarArr[0], GetVaultKey(envVarArr[1], opts, log))
|
||||
err := process.Setenv(envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Send()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger) {
|
||||
func injectEnvIntoLocalCMD(envVarsToInject environmentVars, process *exec.Cmd, log zerolog.Logger, opts *ConfigOpts) {
|
||||
if envVarsToInject.file != "" {
|
||||
envPath, _ := resolveDir(envVarsToInject.file)
|
||||
envPath, _ := getFullPathWithHomeDir(envVarsToInject.file)
|
||||
|
||||
file, fileErr := os.Open(envPath)
|
||||
if fileErr != nil {
|
||||
@ -140,7 +158,8 @@ errEnvFile:
|
||||
|
||||
for _, envVal := range envVarsToInject.env {
|
||||
if strings.Contains(envVal, "=") {
|
||||
process.Env = append(process.Env, 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, os.Environ()...)
|
||||
@ -173,7 +192,6 @@ func testFile(c string) error {
|
||||
return fileOpenErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -182,10 +200,12 @@ func IsTerminalActive() bool {
|
||||
}
|
||||
|
||||
func IsCmdStdOutEnabled() bool {
|
||||
return os.Getenv("BACKY_STDOUT") == "enabled"
|
||||
return os.Getenv("BACKY_CMDSTDOUT") == "enabled"
|
||||
}
|
||||
|
||||
func resolveDir(path string) (string, error) {
|
||||
func getFullPathWithHomeDir(path string) (string, error) {
|
||||
path = strings.TrimSpace(path)
|
||||
|
||||
if path == "~" {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@ -207,21 +227,39 @@ func resolveDir(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
|
||||
backyEnv, envFileErr := godotenv.Read(envFileInConfigDir)
|
||||
if envFileErr != nil {
|
||||
return
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -229,39 +267,35 @@ func expandEnvVars(backyEnv map[string]string, envVars []string) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// parse env variables using new macros
|
||||
for indx, v := range envVars {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func getCommandTypeAndSetCommandInfo(command *Command) *Command {
|
||||
|
||||
if command.Type == "package" && !command.packageCmdSet {
|
||||
if command.Type == PackageCT && !command.packageCmdSet {
|
||||
command.packageCmdSet = true
|
||||
switch command.PackageOperation {
|
||||
case "install":
|
||||
case PackOpInstall:
|
||||
command.Cmd, command.Args = command.pkgMan.Install(command.PackageName, command.PackageVersion, command.Args)
|
||||
case "remove":
|
||||
case PackOpRemove:
|
||||
command.Cmd, command.Args = command.pkgMan.Remove(command.PackageName, command.Args)
|
||||
case "upgrade":
|
||||
case PackOpUpgrade:
|
||||
command.Cmd, command.Args = command.pkgMan.Upgrade(command.PackageName, command.PackageVersion)
|
||||
case "checkVersion":
|
||||
case PackOpCheckVersion:
|
||||
command.Cmd, command.Args = command.pkgMan.CheckVersion(command.PackageName, command.PackageVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if command.Type == "user" && !command.userCmdSet {
|
||||
if command.Type == UserCT && !command.userCmdSet {
|
||||
command.userCmdSet = true
|
||||
switch command.UserOperation {
|
||||
case "add":
|
||||
@ -269,7 +303,8 @@ func getCommandType(command *Command) *Command {
|
||||
command.Username,
|
||||
command.UserHome,
|
||||
command.UserShell,
|
||||
command.SystemUser,
|
||||
command.UserIsSystem,
|
||||
command.UserCreateHome,
|
||||
command.UserGroups,
|
||||
command.Args)
|
||||
case "modify":
|
||||
@ -297,7 +332,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.GetOutput), err
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), err
|
||||
}
|
||||
|
||||
cmdCtxLogger.Info().
|
||||
@ -322,3 +357,97 @@ 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
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
@ -16,6 +17,7 @@ type CacheData struct {
|
||||
Hash string `yaml:"hash"`
|
||||
Path string `yaml:"path"`
|
||||
Type string `yaml:"type"`
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
@ -71,7 +73,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
|
||||
@ -101,16 +103,20 @@ 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, hash))
|
||||
path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, sourceHash))
|
||||
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
os.MkdirAll(c.dir, 0700)
|
||||
_ = os.MkdirAll(c.dir, 0700)
|
||||
}
|
||||
|
||||
err := os.WriteFile(path, data, 0644)
|
||||
@ -122,9 +128,10 @@ func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheDat
|
||||
Hash: hash,
|
||||
Path: path,
|
||||
Type: dataType,
|
||||
URL: sourceHash,
|
||||
}
|
||||
|
||||
c.store[hash] = cacheData
|
||||
c.store[sourceHash] = cacheData
|
||||
|
||||
// Unlock before calling saveToFile to avoid double-locking
|
||||
c.mu.Unlock()
|
||||
@ -164,6 +171,7 @@ 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 {
|
||||
@ -178,9 +186,35 @@ 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
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func NewRemoteFetcher(source string, cache *Cache, options ...FetcherOption) (Re
|
||||
option(&config)
|
||||
}
|
||||
|
||||
// If FileType is empty (i.e. WithFileType was not called), yaml is the default file type
|
||||
// WithFileType was not called. yaml is the default file type
|
||||
if strings.TrimSpace(config.FileType) == "" {
|
||||
config.FileType = "yaml"
|
||||
}
|
||||
@ -57,11 +57,13 @@ func NewRemoteFetcher(source string, cache *Cache, options ...FetcherOption) (Re
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := fetcher.Hash(data)
|
||||
if cachedData, cacheMeta, exists := cache.Get(hash); exists {
|
||||
URLHash := HashURL(source)
|
||||
if cachedData, cacheMeta, exists := cache.Get(URLHash); exists {
|
||||
println(cachedData)
|
||||
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
|
||||
|
@ -20,7 +20,7 @@ func (l *LocalFetcher) Fetch(source string) ([]byte, error) {
|
||||
if l.config.IgnoreFileNotFound {
|
||||
return nil, ErrIgnoreFileNotFound
|
||||
}
|
||||
return nil, nil
|
||||
return nil, err
|
||||
}
|
||||
file, err := os.Open(source)
|
||||
if err != nil {
|
||||
|
@ -44,11 +44,11 @@ func NewS3Fetcher(endpoint string, options ...FetcherOption) (*S3Fetcher, error)
|
||||
*/
|
||||
|
||||
s3Endpoint := os.Getenv("S3_ENDPOINT")
|
||||
creds, err := getS3Credentials("default", s3Endpoint, cfg.HTTPClient)
|
||||
creds, err := getS3Credentials(os.Getenv("AWS_PROFILE"), 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,12 +128,11 @@ 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"), "default")
|
||||
s3Creds := credentials.NewFileAWSCredentials(path.Join(homeDir, ".aws", "credentials"), profile)
|
||||
credVals, credErr := s3Creds.GetWithContext(&credentials.CredContext{Endpoint: host, Client: httpClient})
|
||||
if credErr != nil {
|
||||
return nil, credErr
|
||||
|
@ -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 bool, groups, args []string) (string, []string) {
|
||||
func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem, createHome bool, groups, args []string) (string, []string) {
|
||||
baseArgs := []string{}
|
||||
|
||||
if isSystem {
|
||||
@ -38,6 +38,10 @@ func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem bool
|
||||
baseArgs = append(baseArgs, args...)
|
||||
}
|
||||
|
||||
if createHome {
|
||||
baseArgs = append(baseArgs, "-m")
|
||||
}
|
||||
|
||||
args = append(baseArgs, username)
|
||||
|
||||
cmd := "useradd"
|
||||
|
@ -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, isSystem bool, groups, args []string) (string, []string)
|
||||
AddUser(username, homeDir, shell string, createHome, 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
|
||||
|
4
release
4
release
@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -eou pipefail
|
||||
export CURRENT_TAG="$(go run backy.go version -V)"
|
||||
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
|
||||
|
1
tests/.gitignore
vendored
Normal file
1
tests/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
private
|
17
tests/ErrorHook.yml
Normal file
17
tests/ErrorHook.yml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
14
tests/HookNotInFile.yaml
Normal file
14
tests/HookNotInFile.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
commands:
|
||||
echoTestFail:
|
||||
cmd: ech
|
||||
shell: bash
|
||||
Args: hello world
|
||||
hooks:
|
||||
error:
|
||||
- errorCm #
|
||||
|
||||
errorCmd:
|
||||
name: get docker version
|
||||
cmd: docker
|
||||
Args:
|
||||
- "-v"
|
16
tests/SuccessHook.yml
Normal file
16
tests/SuccessHook.yml
Normal file
@ -0,0 +1,16 @@
|
||||
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"
|
27
tests/VaultTest.yml
Normal file
27
tests/VaultTest.yml
Normal file
@ -0,0 +1,27 @@
|
||||
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
|
Loading…
x
Reference in New Issue
Block a user