Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
1ad50ebcf8 | |||
c483a1056f | |||
3b9f569310 | |||
843be7968b | |||
d477d850ac | |||
8eb3229af7 | |||
d89a208bbd | |||
0d28d6afcf | |||
7c42a9a7cd | |||
31339fb4d8 | |||
c642e827f5 | |||
a328239021 | |||
4ee60184bf | |||
161ad31577 | |||
7c5f4a95da | |||
4981acbf9d | |||
932d5c380f | |||
f84d76badf | |||
6ee6e10621 | |||
127d38c076 | |||
0218dee76d | |||
67a1eab908 | |||
c618ca33f8 | |||
6e7d912fa2 | |||
b90d1958b2 | |||
c187fbb735 | |||
c3de4386ab | |||
e20141043c | |||
11ec1a98d8 | |||
8788d473a5 | |||
edc669b340 | |||
086835453b | |||
5d3c265ce9 | |||
8c633fd4b2 | |||
a664edaed7 | |||
e88773e289 | |||
5c2bfcc940 | |||
aee513f786 | |||
6b99cfa196 |
@ -1,3 +1,3 @@
|
||||
## v0.6.1 - 2025-01-04
|
||||
### Fixed
|
||||
* Hooks now run explicitly after the command executes. Fixed panic due to improper logic.
|
||||
* When running a list, hooks now run explicitly after the command executes. Fixed panic due to improper logic.
|
16
.changes/v0.7.0.md
Normal file
16
.changes/v0.7.0.md
Normal file
@ -0,0 +1,16 @@
|
||||
## v0.7.0 - 2025-02-11
|
||||
### Added
|
||||
* [feat]: package `packageOperation` option `checkVersion` implemented
|
||||
* user management added - see docs
|
||||
* Support for remote config sources. Only config file and list can be used for now.
|
||||
* Cache functionality - still a WIP
|
||||
* Flag `--s3-endpoint` for config file fetching from S3
|
||||
### Changed
|
||||
* Internal refactoring of config setup
|
||||
* Formatting and sending for notifications
|
||||
* name of `configfetcher` to `remotefetcher`
|
||||
* Flags that took comma-separated lists now have to be passed multiple times for each argument.
|
||||
* Hosts passed to `exec host` now checked against default SSH config files
|
||||
### Fixed
|
||||
* Parsing of remote URLs when determining list config file path
|
||||
* Incorrect error notification template value
|
3
.changes/v0.7.1.md
Normal file
3
.changes/v0.7.1.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.7.1 - 2025-02-14
|
||||
### Fixed
|
||||
* Incorrect local config file loading logic caused files to not be detected
|
3
.changes/v0.7.2.md
Normal file
3
.changes/v0.7.2.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.7.2 - 2025-02-14
|
||||
### Fixed
|
||||
* CI configs
|
3
.changes/v0.7.3.md
Normal file
3
.changes/v0.7.3.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v0.7.3 - 2025-02-14
|
||||
### Changed
|
||||
* GoReleaser configs
|
5
.changes/v0.7.4.md
Normal file
5
.changes/v0.7.4.md
Normal file
@ -0,0 +1,5 @@
|
||||
## v0.7.4 - 2025-02-14
|
||||
### Changed
|
||||
* CI configs
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
5
.changes/v0.7.5.md
Normal file
5
.changes/v0.7.5.md
Normal file
@ -0,0 +1,5 @@
|
||||
## v0.7.5 - 2025-02-14
|
||||
### Changed
|
||||
* CI configs
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
4
.changes/v0.7.6.md
Normal file
4
.changes/v0.7.6.md
Normal file
@ -0,0 +1,4 @@
|
||||
## v0.7.6 - 2025-02-14
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
* CI configs
|
4
.changes/v0.7.7.md
Normal file
4
.changes/v0.7.7.md
Normal file
@ -0,0 +1,4 @@
|
||||
## v0.7.7 - 2025-02-14
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
* CI configs
|
4
.changes/v0.7.8.md
Normal file
4
.changes/v0.7.8.md
Normal file
@ -0,0 +1,4 @@
|
||||
## v0.7.8 - 2025-02-14
|
||||
### Fixed
|
||||
* Github CI config
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
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
|
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@ -15,26 +15,26 @@ jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: git fetch --force --tags
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.20'
|
||||
go-version: '1.23'
|
||||
cache: true
|
||||
# More assembly might be required: Docker logins, GPG, etc. It all depends
|
||||
# on your needs.
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
- uses: olegtarasov/get-tag@v2.1.4
|
||||
id: tagName
|
||||
with:
|
||||
# Optionally strip `v` prefix
|
||||
strip_v: false
|
||||
- uses: goreleaser/goreleaser-action@v4
|
||||
# tagRegex: "foobar-(.*)" # Optional. Returns specified group text as tag name. Full tag string is returned if regex is not defined.
|
||||
tagRegexGroup: 1 # Optional. Default is 1.
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --release-notes=".changes/${{steps.tag.outputs.tag}}.md" -f .goreleaser/github.yml --clean
|
||||
version: 2.7.0
|
||||
args: release --release-notes=".changes/${{ env.GIT_TAG_NAME }}.md" -f .goreleaser/github.yml --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
|
||||
GIT_TAG_NAME: ${{ steps.tagName.outputs.tag }}
|
||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,4 +1,12 @@
|
||||
|
||||
dist/
|
||||
.codegpt
|
||||
|
||||
.codegpt
|
||||
*.log
|
||||
*.sh
|
||||
*.yaml
|
||||
*.yml
|
||||
|
||||
|
||||
+.changie.yaml
|
||||
+.changes/
|
@ -6,7 +6,6 @@ before:
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
- GOPROXY=https://goproxy.io
|
||||
goos:
|
||||
- freebsd
|
||||
- linux
|
||||
@ -16,7 +15,7 @@ builds:
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of uname.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_{{ .Version }}_
|
||||
@ -28,7 +27,7 @@ archives:
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: [zip]
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
|
@ -1,4 +1,3 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
version: 2
|
||||
before:
|
||||
@ -17,7 +16,7 @@ builds:
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of uname.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_{{ .Version }}_
|
||||
@ -29,7 +28,7 @@ archives:
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: [zip]
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -1,6 +1,7 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Cmds",
|
||||
"remotefetcher",
|
||||
"knadh",
|
||||
"koanf",
|
||||
"mattn",
|
||||
|
@ -1,11 +1,20 @@
|
||||
name: goreleaser release
|
||||
steps:
|
||||
release:
|
||||
image: goreleaser/goreleaser
|
||||
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:
|
||||
# image: goreleaser/goreleaser
|
||||
# commands:
|
||||
|
||||
when:
|
||||
- event: tag
|
||||
|
@ -4,7 +4,8 @@ steps:
|
||||
commands:
|
||||
- git submodule foreach 'git fetch origin; git checkout $(git describe --tags `git rev-list --tags --max-count=1`);'
|
||||
- cd docs
|
||||
- hugo mod get -u ./...
|
||||
- hugo mod get -u github.com/divinerites/plausible-hugo
|
||||
- hugo mod get -u github.com/McShelby/hugo-theme-relearn@7.3.1
|
||||
- hugo
|
||||
|
||||
deploy:
|
||||
@ -22,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/**'
|
66
CHANGELOG.md
66
CHANGELOG.md
@ -6,10 +6,72 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v0.8.0 - 2025-02-15
|
||||
### Changed
|
||||
* Breaking: `cmd-lists` key changed to `cmdLists`
|
||||
* Properly load list config
|
||||
* Config file loading properly errors
|
||||
* CI Configs
|
||||
|
||||
## v0.7.8 - 2025-02-14
|
||||
### Fixed
|
||||
* Github CI config
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
|
||||
## v0.7.7 - 2025-02-14
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
* CI configs
|
||||
|
||||
## v0.7.6 - 2025-02-14
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
* CI configs
|
||||
|
||||
## v0.7.5 - 2025-02-14
|
||||
### Changed
|
||||
* CI configs
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
|
||||
## v0.7.4 - 2025-02-14
|
||||
### Changed
|
||||
* CI configs
|
||||
### Fixed
|
||||
* v0.7.1: Incorrect local config file loading logic caused files to not be detected
|
||||
|
||||
## v0.7.3 - 2025-02-14
|
||||
### Changed
|
||||
* GoReleaser configs
|
||||
|
||||
## v0.7.2 - 2025-02-14
|
||||
### Fixed
|
||||
* CI configs
|
||||
|
||||
## v0.7.1 - 2025-02-14
|
||||
### Fixed
|
||||
* Incorrect local config file loading logic caused files to not be detected
|
||||
|
||||
## v0.7.0 - 2025-02-11
|
||||
### Added
|
||||
* [feat]: package `packageOperation` option `checkVersion` implemented
|
||||
* user management added - see docs
|
||||
* Support for remote config sources. Only config file and list can be used for now.
|
||||
* Cache functionality - still a WIP
|
||||
* Flag `--s3-endpoint` for config file fetching from S3
|
||||
### Changed
|
||||
* Internal refactoring of config setup
|
||||
* Formatting and sending for notifications
|
||||
* name of `configfetcher` to `remotefetcher`
|
||||
* Flags that took comma-separated lists now have to be passed multiple times for each argument.
|
||||
* Hosts passed to `exec host` now checked against default SSH config files
|
||||
### Fixed
|
||||
* Parsing of remote URLs when determining list config file path
|
||||
* Incorrect error notification template value
|
||||
|
||||
## v0.6.1 - 2025-01-04
|
||||
### Fixed
|
||||
* Hooks now run explicitly after the command executes. Fixed panic due to improper logic.
|
||||
|
||||
* When running a list, hooks now run explicitly after the command executes. Fixed panic due to improper logic.
|
||||
## v0.6.0 - 2025-01-04
|
||||
### Added
|
||||
* Command Type Package - allows one to perform package operations [docs](https://backy.cybershell.xyz/config/packages/)
|
||||
|
22
backy.code-workspace
Normal file
22
backy.code-workspace
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "backy",
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"cSpell.words": [
|
||||
"Cmds",
|
||||
"CMDSTDOUT",
|
||||
"knadh",
|
||||
"koanf",
|
||||
"mattn",
|
||||
"maunium",
|
||||
"mautrix",
|
||||
"nikoksr",
|
||||
"remotefetcher",
|
||||
"Strs"
|
||||
]
|
||||
}
|
||||
}
|
@ -12,8 +12,8 @@ import (
|
||||
|
||||
var (
|
||||
backupCmd = &cobra.Command{
|
||||
Use: "backup [--lists=list1,list2,... | -l list1, list2,...]",
|
||||
Short: "Runs commands defined in config file.",
|
||||
Use: "backup [--lists=list1 --lists list2 ... | -l list1 -l list2 ...]",
|
||||
Short: "Runs commands defined in config file. Use -l flag multiple times to run multiple lists.",
|
||||
Long: "Backup executes commands defined in config file.\nUse the --lists or -l flag to execute the specified lists. If not flag is not given, all lists will be executed.",
|
||||
Run: Backup,
|
||||
}
|
||||
@ -23,16 +23,16 @@ var (
|
||||
var cmdLists []string
|
||||
|
||||
func init() {
|
||||
parseS3Config()
|
||||
|
||||
backupCmd.Flags().StringSliceVarP(&cmdLists, "lists", "l", nil, "Accepts comma-separated names of command lists to execute.")
|
||||
backupCmd.Flags().StringArrayVarP(&cmdLists, "lists", "l", nil, "Accepts comma-separated names of command lists to execute.")
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
backy.ReadConfig(backyConfOpts)
|
||||
backyConfOpts.ReadConfig()
|
||||
|
||||
backyConfOpts.RunListConfig("")
|
||||
for _, host := range backyConfOpts.Hosts {
|
||||
|
@ -16,9 +16,11 @@ var (
|
||||
)
|
||||
|
||||
func cron(cmd *cobra.Command, args []string) {
|
||||
parseS3Config()
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.CronEnabled())
|
||||
opts := backy.NewOpts(cfgFile, backy.EnableCron(), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
|
||||
opts.InitConfig()
|
||||
backy.ReadConfig(opts)
|
||||
opts.ReadConfig()
|
||||
|
||||
opts.Cron()
|
||||
}
|
||||
|
11
cmd/exec.go
11
cmd/exec.go
@ -23,18 +23,17 @@ var (
|
||||
func init() {
|
||||
execCmd.AddCommand(hostExecCommand)
|
||||
|
||||
hostExecCommand.Flags().StringSliceVarP(&hostsList, "hosts", "m", nil, "Accepts comma-separated names of hosts.")
|
||||
hostExecCommand.Flags().StringSliceVarP(&cmdList, "commands", "c", nil, "Accepts comma-separated names of commands.")
|
||||
|
||||
}
|
||||
|
||||
func execute(cmd *cobra.Command, args []string) {
|
||||
parseS3Config()
|
||||
|
||||
if len(args) < 1 {
|
||||
logging.ExitWithMSG("Please provide a command to run. Pass --help to see options.", 1, nil)
|
||||
}
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.AddCommands(args))
|
||||
opts := backy.NewOpts(cfgFile, backy.AddCommands(args), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
|
||||
opts.InitConfig()
|
||||
// opts.InitMongo()
|
||||
backy.ReadConfig(opts).ExecuteCmds(opts)
|
||||
opts.ReadConfig()
|
||||
opts.ExecuteCmds()
|
||||
}
|
||||
|
26
cmd/host.go
26
cmd/host.go
@ -8,19 +8,25 @@ import (
|
||||
|
||||
var (
|
||||
hostExecCommand = &cobra.Command{
|
||||
Use: "host [--commands=command1,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] ",
|
||||
Use: "host [--command=command1 --command=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] ",
|
||||
Short: "Runs command defined in config file on the hosts in order specified.",
|
||||
Long: "Host executes specified commands on the hosts defined in config file.\nUse the --commands or -c flag to choose the commands.",
|
||||
Run: Host,
|
||||
}
|
||||
)
|
||||
|
||||
// Holds command list to run
|
||||
// Holds list of hosts to run commands on
|
||||
var hostsList []string
|
||||
|
||||
// Holds command list to run
|
||||
var cmdList []string
|
||||
|
||||
func init() {
|
||||
|
||||
hostExecCommand.Flags().StringArrayVarP(&hostsList, "hosts", "m", nil, "Accepts space-separated names of hosts. Specify multiple times for multiple hosts.")
|
||||
hostExecCommand.Flags().StringArrayVarP(&cmdList, "command", "c", nil, "Accepts space-separated names of commands. Specify multiple times for multiple commands.")
|
||||
parseS3Config()
|
||||
|
||||
}
|
||||
|
||||
// cli input should be hosts and commands. Hosts are defined in config files.
|
||||
@ -29,21 +35,27 @@ func init() {
|
||||
// 2. stdin (on command line) (TODO)
|
||||
|
||||
func Host(cmd *cobra.Command, args []string) {
|
||||
backyConfOpts := backy.NewOpts(cfgFile)
|
||||
backyConfOpts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut))
|
||||
backyConfOpts.InitConfig()
|
||||
|
||||
backy.ReadConfig(backyConfOpts)
|
||||
backyConfOpts.ReadConfig()
|
||||
|
||||
// check CLI input
|
||||
if hostsList == nil {
|
||||
logging.ExitWithMSG("error: hosts must be specified", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
// host is only checked when we read the SSH File
|
||||
// so a check may not be needed here
|
||||
|
||||
for _, h := range hostsList {
|
||||
// check if h exists in the config file
|
||||
_, hostFound := backyConfOpts.Hosts[h]
|
||||
if !hostFound {
|
||||
logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger)
|
||||
// check if h exists in the SSH config file
|
||||
hostFoundInConfig, s := backy.CheckIfHostHasHostName(h)
|
||||
if !hostFoundInConfig {
|
||||
logging.ExitWithMSG("host "+h+" not found", 1, &backyConfOpts.Logger)
|
||||
}
|
||||
// create host with hostname and host
|
||||
backyConfOpts.Hosts[h] = &backy.Host{Host: h, HostName: s}
|
||||
}
|
||||
}
|
||||
if cmdList == nil {
|
||||
|
63
cmd/list.go
63
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...]",
|
||||
Use: "list [command]",
|
||||
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,
|
||||
}
|
||||
|
||||
listCmds = &cobra.Command{
|
||||
Use: "cmds [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: ListCmds,
|
||||
}
|
||||
listCmdLists = &cobra.Command{
|
||||
Use: "lists [list1 list2 ...]",
|
||||
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: 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) {
|
||||
|
||||
// settup based on whats passed in:
|
||||
// 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)
|
||||
}
|
||||
|
||||
opts := backy.NewOpts(cfgFile)
|
||||
parseS3Config()
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile))
|
||||
|
||||
opts.InitConfig()
|
||||
opts.ReadConfig()
|
||||
|
||||
for _, v := range cmdsToList {
|
||||
opts.ListCommand(v)
|
||||
}
|
||||
}
|
||||
|
||||
func ListCmdLists(cmd *cobra.Command, args []string) {
|
||||
|
||||
parseS3Config()
|
||||
|
||||
if len(args) > 0 {
|
||||
listsToList = args
|
||||
} else {
|
||||
logging.ExitWithMSG("Error: lists subcommand needs lists", 1, nil)
|
||||
}
|
||||
|
||||
opts := backy.NewOpts(cfgFile, backy.SetLogFile(logFile))
|
||||
|
||||
opts.InitConfig()
|
||||
opts.ReadConfig()
|
||||
|
||||
for _, v := range listsToList {
|
||||
opts.ListCommandList(v)
|
||||
}
|
||||
|
||||
opts = backy.ReadConfig(opts)
|
||||
|
||||
opts.ListCommand("rm-sn-db")
|
||||
}
|
||||
|
17
cmd/root.go
17
cmd/root.go
@ -13,8 +13,11 @@ import (
|
||||
|
||||
var (
|
||||
// Used for flags.
|
||||
cfgFile string
|
||||
verbose bool
|
||||
cfgFile string
|
||||
verbose bool
|
||||
cmdStdOut bool
|
||||
logFile string
|
||||
s3Endpoint string
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "backy",
|
||||
@ -32,9 +35,17 @@ 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")
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&s3Endpoint, "s3-endpoint", "", "Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.")
|
||||
rootCmd.AddCommand(backupCmd, execCmd, cronCmd, versionCmd, listCmd)
|
||||
}
|
||||
|
||||
func parseS3Config() {
|
||||
if s3Endpoint != "" {
|
||||
os.Setenv("S3_ENDPOINT", s3Endpoint)
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const versionStr = "0.6.1"
|
||||
const versionStr = "0.8.0"
|
||||
|
||||
var (
|
||||
versionCmd = &cobra.Command{
|
||||
|
@ -14,7 +14,7 @@ Usage:
|
||||
backy [command]
|
||||
|
||||
Available Commands:
|
||||
backup Runs commands defined in config file.
|
||||
backup Runs commands defined in config file. Use -l flag multiple times to run multiple lists.
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
cron Starts a scheduler that runs lists defined in config file.
|
||||
exec Runs commands defined in config file in order given.
|
||||
@ -23,9 +23,11 @@ Available Commands:
|
||||
version Prints the version and exits
|
||||
|
||||
Flags:
|
||||
-f, --config string config file to read from
|
||||
-h, --help help for backy
|
||||
-v, --verbose Sets verbose level
|
||||
-f, --config string config file to read from
|
||||
-h, --help help for backy
|
||||
--log-file string log file to write to
|
||||
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
|
||||
-v, --verbose Sets verbose level
|
||||
|
||||
Use "backy [command] --help" for more information about a command.
|
||||
```
|
||||
@ -39,15 +41,17 @@ Backup executes commands defined in config file.
|
||||
Use the --lists or -l flag to execute the specified lists. If not flag is not given, all lists will be executed.
|
||||
|
||||
Usage:
|
||||
backy backup [--lists=list1,list2,... | -l list1, list2,...] [flags]
|
||||
backy backup [--lists=list1 --lists list2 ... | -l list1 -l list2 ...] [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for backup
|
||||
-l, --lists strings Accepts comma-separated names of command lists to execute.
|
||||
-h, --help help for backup
|
||||
-l, --lists stringArray Accepts comma-separated names of command lists to execute.
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
-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
|
||||
```
|
||||
|
||||
## cron
|
||||
@ -62,8 +66,10 @@ Flags:
|
||||
-h, --help help for cron
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
-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
|
||||
```
|
||||
|
||||
## exec
|
||||
@ -82,8 +88,10 @@ Flags:
|
||||
-h, --help help for exec
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
-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 exec [command] --help" for more information about a command.
|
||||
```
|
||||
@ -95,16 +103,18 @@ Host executes specified commands on the hosts defined in config file.
|
||||
Use the --commands or -c flag to choose the commands.
|
||||
|
||||
Usage:
|
||||
backy exec host [--commands=command1,command2, ... | -c command1,command2, ...] [--hosts=host1,hosts2, ... | -m host1,host2, ...] [flags]
|
||||
backy exec host [--command=command1 --command=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -m host2 ...] [flags]
|
||||
|
||||
Flags:
|
||||
-c, --commands strings Accepts comma-separated names of commands.
|
||||
-h, --help help for host
|
||||
-m, --hosts strings Accepts comma-separated names of hosts.
|
||||
-c, --command stringArray Accepts space-separated names of commands. Specify multiple times for multiple commands.
|
||||
-h, --help help for host
|
||||
-m, --hosts stringArray Accepts space-separated names of hosts. Specify multiple times for multiple hosts.
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
-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
|
||||
```
|
||||
|
||||
## version
|
||||
@ -121,8 +131,10 @@ Flags:
|
||||
-V, --vpre Output the version with v prefixed.
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
-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
|
||||
@ -140,6 +152,8 @@ Flags:
|
||||
-l, --lists strings Accepts comma-separated names of command lists to list.
|
||||
|
||||
Global Flags:
|
||||
-f, --config string config file to read from
|
||||
-v, --verbose Sets verbose level
|
||||
-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
|
||||
```
|
||||
|
@ -10,13 +10,31 @@ This is the section on the config file.
|
||||
To use a specific file:
|
||||
```backy [command] -f /path/to/file```
|
||||
|
||||
You can also use a remote file:
|
||||
```
|
||||
backy [command] -f `s3/http source`
|
||||
```
|
||||
|
||||
See remote resources docs for specific info.
|
||||
|
||||
If you leave the config path blank, the following paths will be searched in order:
|
||||
|
||||
1. `./backy.yml`
|
||||
2. `./backy.yaml`
|
||||
3. `~/.config/backy.yml`
|
||||
4. `~/.config/backy.yaml`
|
||||
3. The same two files above contained in a `backy` subdirectory under in what is returned by Go's `os` package function `UserConfigDir()`.
|
||||
|
||||
Create a file at `~/.config/backy.yml`.
|
||||
{{% expand title="`UserConfigDir()` documentation:" %}}
|
||||
|
||||
See the rest of the documentation in this section to configure it.
|
||||
Up-to date documentation for this function may be found on [GoDoc](https://pkg.go.dev/os#UserConfigDir).
|
||||
|
||||
>UserConfigDir returns the default root directory to use for user-specific configuration data. Users should create their own application-specific subdirectory within this one and use that.
|
||||
|
||||
>On Unix systems, it returns $XDG_CONFIG_HOME as specified by https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if non-empty, else $HOME/.config. On Darwin, it returns $HOME/Library/Application Support. On Windows, it returns %AppData%. On Plan 9, it returns $home/lib.
|
||||
|
||||
>If the location cannot be determined (for example, $HOME is not defined), then it will return an error.
|
||||
|
||||
{{% /expand %}}
|
||||
|
||||
See the rest of the documentation, titles included below, in this section to configure it.
|
||||
|
||||
{{% children description="true" %}}
|
@ -2,7 +2,7 @@
|
||||
title: "Command Lists"
|
||||
weight: 2
|
||||
description: >
|
||||
This page tells you how to get started with Backy.
|
||||
This page tells you how to use command lists.
|
||||
---
|
||||
|
||||
Command lists are for executing commands in sequence and getting notifications from them.
|
||||
@ -11,10 +11,18 @@ 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 found
|
||||
2. hosts.yml or hosts.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)
|
||||
|
||||
```yaml
|
||||
{{% notice info %}}
|
||||
The lists file is also checked in remote resources.
|
||||
|
||||
The lists file is ignored under the following condition:
|
||||
|
||||
If a remote config file is specified (on the command-line using `-f`) and the lists file is not found in the same directory, the lists file is assumed to not exist.
|
||||
{{% /notice %}}
|
||||
|
||||
```yaml {lineNos="true" wrap="true" title="yaml"}
|
||||
test2:
|
||||
name: test2
|
||||
order:
|
||||
@ -62,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.
|
||||
Note: Backy uses the second field of cron, so add anything except `*` to the beginning of a regular cron expression.
|
||||
{{% /notice %}}
|
||||
|
||||
```yaml
|
||||
cmd-lists:
|
||||
```yaml {lineNos="true" wrap="true" title="yaml"}
|
||||
cmdLists:
|
||||
docker-container-backup: # this can be any name you want
|
||||
# all commands have to be defined
|
||||
order:
|
||||
|
@ -1,11 +1,10 @@
|
||||
---
|
||||
title: "Commands"
|
||||
description: Commands are just that, commands
|
||||
weight: 1
|
||||
---
|
||||
|
||||
The yaml top-level map can be any string.
|
||||
|
||||
The top-level name must be unique.
|
||||
|
||||
### Example Config
|
||||
|
||||
@ -42,8 +41,8 @@ Values available for this section **(case-sensitive)**:
|
||||
| --- | --- | --- | --- |
|
||||
| `cmd` | Defines the command to execute | `string` | yes |
|
||||
| `Args` | Defines the arguments to the command | `[]string` | no |
|
||||
| `environment` | Defines evironment variables for the command | `[]string` | no |
|
||||
| `type` | May be `scriptFile`, `script`, or `package`. Runs script from local machine on remote. `Package` is the only one that can be run on local and remote hosts. | `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 |
|
||||
@ -107,13 +106,14 @@ This is useful for specifying environment variables or other things so they don'
|
||||
|
||||
### type
|
||||
|
||||
May be `scriptFile` or `script`. Runs script from local machine on remote host passed to the SSH session as standard input.
|
||||
The following options are available:
|
||||
|
||||
If `type` is `script`, `cmd` is used as the script.
|
||||
|
||||
If `type` is `scriptFile`, cmd must be a script file.
|
||||
|
||||
If `type` is `package`, there are additional fields that must be specified.
|
||||
| name | description |
|
||||
| --- | --- |
|
||||
| script | `cmd` is used as the script |
|
||||
| scriptFile | Can only be run on a host. `cmd` is read and used as the script, and `scriptEnvFile` can be used to add env variables |
|
||||
| package | Run package operations. See [dedicated page](/config/packages) for configuring package commands |
|
||||
| user | Run user operations. See [dedicated page](/config/user-commands) for configuring package commands |
|
||||
|
||||
### environment
|
||||
|
||||
@ -121,7 +121,7 @@ The environment variables support expansion:
|
||||
|
||||
- using escaped values `$VAR` or `${VAR}`
|
||||
|
||||
For now, the variables have to be defined in an `.env` file in the same directory as the config file.
|
||||
For now, the variables have to be defined in an `.env` file in the same directory that the program is run from.
|
||||
|
||||
If using it with host specified, the SSH server has to be configured to accept those env variables.
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Packages"
|
||||
weight: 2
|
||||
description: This is dedicated to package commands.
|
||||
---
|
||||
|
||||
This is dedicated to `package` commands. The command `type` field must be `package`. Package is a type that allows one to perform package operations. There are several additional options available when `type` is `package`:
|
||||
@ -9,8 +10,8 @@ This is dedicated to `package` commands. The command `type` field must be `packa
|
||||
| --- | --- | --- | --- |
|
||||
| `packageName` | The name of a package to be modified. | `string` | yes |
|
||||
| `packageManager` | The name of the package manger to be used. | `string` | yes |
|
||||
| `packageOperation` | The type of operation to be perform. | `string` | yes |
|
||||
| `packageVersion` | The version of a package to be modified. | `string` | no |
|
||||
| `packageOperation` | The type of operation to perform. | `string` | yes |
|
||||
| `packageVersion` | The version of a package. | `string` | no |
|
||||
|
||||
|
||||
#### example
|
||||
@ -34,6 +35,7 @@ The following package operations are supported:
|
||||
- `install`
|
||||
- `remove`
|
||||
- `upgrade`
|
||||
- `checkVersion`
|
||||
|
||||
#### packageManager
|
||||
|
||||
@ -45,11 +47,11 @@ The following package managers are recognized:
|
||||
|
||||
#### package command args
|
||||
|
||||
You can add additional arguments using the standard `Args` key. This is useful for adding more packages.
|
||||
You can add additional arguments using the standard `Args` key. This is useful for adding more packages, yet it does not work with `checkVersion`.
|
||||
|
||||
### Development
|
||||
|
||||
The PackageManager interface provides an easy to enforce functions and options. There are two interfaces, `PackageManager` and `ConfigurablePackageManager` in the directory `pkg/pkgman`. Go's import-cycle "feature" caused me to implement functional options using a third interface. `PackageManagerOption`is a function that takes an interface.
|
||||
The PackageManager interface provides an easy way to enforce functions and options. There are two interfaces, `PackageManager` and `ConfigurablePackageManager` in the directory `pkg/pkgman`. Go's import-cycle "feature" caused me to implement functional options using a third interface. `PackageManagerOption`is a function that takes an interface.
|
||||
|
||||
#### PackageManager
|
||||
|
62
docs/content/config/commands/user-commands.md
Normal file
62
docs/content/config/commands/user-commands.md
Normal file
@ -0,0 +1,62 @@
|
||||
---
|
||||
title: "User commands"
|
||||
weight: 2
|
||||
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 |
|
||||
|
||||
|
||||
#### example
|
||||
|
||||
The following is an example of a package command:
|
||||
|
||||
```yaml
|
||||
addUser:
|
||||
name: add user backy with custom home dir
|
||||
type: user
|
||||
userName: backy
|
||||
userHome: /opt/backy
|
||||
userOperation: add
|
||||
host: some-host
|
||||
```
|
||||
|
||||
#### userOperation
|
||||
|
||||
The following package operations are supported:
|
||||
|
||||
- `add`
|
||||
- `remove`
|
||||
- `modify`
|
||||
- `password`
|
||||
- `checkIfExists`
|
||||
|
||||
### Development
|
||||
|
||||
The UserManager interface provides an way easy to add new commands. There is one interface `Usermanager` in directory `pkg/usermanager`.
|
||||
|
||||
#### UserManager
|
||||
|
||||
```go
|
||||
// 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)
|
||||
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
|
||||
// Should return a password as the last argument
|
||||
// TODO: refactor when adding more systems instead of Linux
|
||||
ModifyPassword(username, password string) (string, *strings.Reader, string)
|
||||
UserExists(username string) (string, []string)
|
||||
}
|
||||
```
|
24
docs/content/config/hosts.md
Normal file
24
docs/content/config/hosts.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "Hosts"
|
||||
weight: 2
|
||||
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 |
|
||||
|
||||
## exec host subcommand
|
||||
|
||||
Backy has a subcommand `exec host`. This subcommand takes the flags of `-m host1 -m host2`. For now these hosts need to be defined in the config file.
|
@ -5,6 +5,7 @@ description: >
|
||||
This page tells you how to get set up Backy notifications.
|
||||
---
|
||||
|
||||
Notifications are only configurable for command lists, as of right now.
|
||||
|
||||
Notifications can be sent on command list completion and failure.
|
||||
|
||||
|
17
docs/content/config/remote-resources.md
Normal file
17
docs/content/config/remote-resources.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
title: "Remote resources"
|
||||
weight: 2
|
||||
description: This is dedicated to configuring remote resources.
|
||||
---
|
||||
|
||||
Remote resources can be used for a lot of things, including config files and scripts.
|
||||
|
||||
## Config file
|
||||
|
||||
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.
|
||||
|
||||
## Scripts
|
||||
|
||||
Scripts will be coming later.
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Vault"
|
||||
weight: 4
|
||||
description: Set up and configure vault.
|
||||
---
|
||||
|
||||
[Vault](https://www.vaultproject.io/) is a tool for storing secrets and other data securely.
|
||||
|
@ -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:
|
||||
|
@ -28,9 +28,23 @@ commands:
|
||||
cmd: hostname
|
||||
update-docker:
|
||||
type: package
|
||||
packageManager: apt
|
||||
shell: zsh # best to run package commands in a shell
|
||||
packageName: docker-ce
|
||||
packageVersion: "5:27.4.1-1~debian.12~bookworm"
|
||||
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
|
||||
@ -67,7 +81,7 @@ hosts:
|
||||
# optional
|
||||
logging:
|
||||
verbose: true
|
||||
file: /path/to/logs/commands.log
|
||||
file: ./backy.log
|
||||
console: false
|
||||
cmd-std-out: false
|
||||
|
||||
|
90
go.mod
90
go.mod
@ -1,69 +1,91 @@
|
||||
module git.andrewnw.xyz/CyberShell/backy
|
||||
|
||||
go 1.20
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.6
|
||||
|
||||
replace git.andrewnw.xyz/CyberShell/backy => /home/andrew/Projects/backy
|
||||
|
||||
require (
|
||||
github.com/go-co-op/gocron v1.33.1
|
||||
github.com/hashicorp/vault/api v1.10.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/hashicorp/vault/api v1.15.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kevinburke/ssh_config v1.2.0
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0
|
||||
github.com/knadh/koanf/providers/file v0.1.0
|
||||
github.com/knadh/koanf/v2 v2.0.1
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/nikoksr/notify v0.41.0
|
||||
github.com/knadh/koanf/providers/rawbytes v0.1.0
|
||||
github.com/knadh/koanf/v2 v2.1.2
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/minio/minio-go/v7 v7.0.84
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/nikoksr/notify v1.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/zerolog v1.30.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
golang.org/x/crypto v0.13.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/sethvargo/go-password v0.3.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
golang.org/x/crypto v0.33.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
maunium.net/go/mautrix v0.16.0
|
||||
mvdan.cc/sh/v3 v3.7.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mautrix v0.23.0
|
||||
mvdan.cc/sh/v3 v3.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 // indirect
|
||||
github.com/aws/smithy-go v1.22.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.5 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // 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/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // 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
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.1 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/tidwall/gjson v1.16.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718 // indirect
|
||||
go.mau.fi/util v0.8.4 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
maunium.net/go/maulogger/v2 v2.4.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
)
|
||||
|
215
go.sum
215
go.sum
@ -1,54 +1,82 @@
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 h1:OIHj/nAhVzIXGzbAE+4XmZ8FPvro3THr6NlqErJc3wY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32/go.mod h1:LiBEsDo34OJXqdDlRGsilhlIiXR7DL+6Cx2f4p1EgzI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6 h1:cCBJaT7EeEojpJ4s7wTDbhZlHVJOgNHN7iw6qVurGaw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.6/go.mod h1:WYH1ABybY7JK9TITPnk6ZlP7gQB8psI4c9qDmMsnLSA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 h1:OBsrtam3rk8NfBEq7OLOMm5HtQ9Yyw32X4UQMya/wjw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13/go.mod h1:3U4gFA5pmoCOja7aq4nSaIAGbaOHv2Yl2ug018cmC+Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0 h1:ehvUZNVrGA1Usa6yYo8A8pUqrigRelWXSbcCqYpRLeI=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0/go.mod h1:KuLNrwYJFaC2AVZ+CVVc12k9NyqwgWsoNNHjwqF6QNk=
|
||||
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-co-op/gocron v1.33.1 h1:wjX+Dg6Ae29a/f9BSQjY1Rl+jflTpW9aDyMqseCj78c=
|
||||
github.com/go-co-op/gocron v1.33.1/go.mod h1:NLi+bkm4rRSy1F8U7iacZOz0xPseMoIOnvabGoSe/no=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/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=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
|
||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 h1:FW0YttEnUNDJ2WL9XcrrfteS1xW8u+sh4ggM8pN5isQ=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ=
|
||||
github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8=
|
||||
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
|
||||
github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
@ -57,83 +85,89 @@ github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
|
||||
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
|
||||
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
|
||||
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
|
||||
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
|
||||
github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME=
|
||||
github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c=
|
||||
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
|
||||
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
|
||||
github.com/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=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=
|
||||
github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/nikoksr/notify v0.41.0 h1:4LGE41GpWdHX5M3Xo6DlWRwS2WLDbOq1Rk7IzY4vjmQ=
|
||||
github.com/nikoksr/notify v0.41.0/go.mod h1:FoE0UVPeopz1Vy5nm9vQZ+JVmYjEIjQgbFstbkw+cRE=
|
||||
github.com/nikoksr/notify v1.3.0 h1:UxzfxzAYGQD9a5JYLBTVx0lFMxeHCke3rPCkfWdPgLs=
|
||||
github.com/nikoksr/notify v1.3.0/go.mod h1:Xor2hMmkvrCfkCKvXGbcrESez4brac2zQjhd6U2BbeM=
|
||||
github.com/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
||||
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
|
||||
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
||||
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
|
||||
github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@ -141,39 +175,30 @@ 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=
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718 h1:hmm5bZqE0M8+Uvys0HJPCSbAIZIwYtTkBKYPjAWHuMM=
|
||||
go.mau.fi/util v0.0.0-20230906155759-14bad39a8718/go.mod h1:AxuJUMCxpzgJ5eV9JbPWKRH8aAJJidxetNdUj7qcb84=
|
||||
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
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=
|
||||
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=
|
||||
@ -184,9 +209,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
|
||||
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
|
||||
maunium.net/go/mautrix v0.16.0 h1:iUqCzJE2yqBC1ddAK6eAn159My8rLb4X8g4SFtQh2Dk=
|
||||
maunium.net/go/mautrix v0.16.0/go.mod h1:XAjE9pTSGcr6vXaiNgQGiip7tddJ8FQV1a29u2QdBG4=
|
||||
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
|
||||
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
|
||||
maunium.net/go/mautrix v0.23.0 h1:HNlR19eew5lvrNSL2muhExaGhYdaGk5FfEiA82QqUP4=
|
||||
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
|
||||
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
|
||||
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
|
||||
|
@ -34,24 +34,34 @@ var Sprintf = fmt.Sprintf
|
||||
func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([]string, error) {
|
||||
|
||||
var (
|
||||
outputArr []string
|
||||
ArgsStr string
|
||||
ArgsStr string // concatenating the arguments
|
||||
cmdOutBuf bytes.Buffer
|
||||
cmdOutWriters io.Writer
|
||||
errSSH error
|
||||
|
||||
envVars = environmentVars{
|
||||
file: command.Env,
|
||||
env: command.Environment,
|
||||
}
|
||||
|
||||
outputArr []string // holds the output strings returned by processes
|
||||
)
|
||||
|
||||
// Get the command type
|
||||
// This must be done before concatenating the arguments
|
||||
command = getCommandType(command)
|
||||
|
||||
for _, v := range command.Args {
|
||||
ArgsStr += fmt.Sprintf(" %s", v)
|
||||
}
|
||||
|
||||
command = getPackageCommand(command)
|
||||
// print the user's password if it is updated
|
||||
if command.Type == "user" {
|
||||
if command.UserOperation == "password" {
|
||||
cmdCtxLogger.Info().Str("password", command.UserPassword).Msg("user password to be updated")
|
||||
}
|
||||
}
|
||||
|
||||
var errSSH error
|
||||
// is host defined
|
||||
if command.Host != nil {
|
||||
outputArr, errSSH = command.RunCmdSSH(cmdCtxLogger, opts)
|
||||
@ -60,53 +70,46 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
}
|
||||
} else {
|
||||
|
||||
// Handle package operations
|
||||
if command.Type == "package" && command.PackageOperation == "checkVersion" {
|
||||
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions")
|
||||
|
||||
// Execute the package version command
|
||||
cmd := exec.Command(command.Cmd, command.Args...)
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
cmd.Stdout = cmdOutWriters
|
||||
cmd.Stderr = cmdOutWriters
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("error running command %s: %w", ArgsStr, err)
|
||||
}
|
||||
|
||||
return parsePackageVersion(cmdOutBuf.String(), cmdCtxLogger, command, cmdOutBuf)
|
||||
}
|
||||
|
||||
var localCMD *exec.Cmd
|
||||
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()
|
||||
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
|
||||
localCMD := exec.Command(command.Shell, "-c", ArgsStr)
|
||||
localCMD = exec.Command(command.Shell, "-c", ArgsStr)
|
||||
|
||||
if command.Dir != nil {
|
||||
localCMD.Dir = *command.Dir
|
||||
} else {
|
||||
|
||||
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" {
|
||||
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)
|
||||
} else {
|
||||
localCMD = exec.Command(command.Cmd, command.Args...)
|
||||
}
|
||||
injectEnvIntoLocalCMD(envVars, localCMD, cmdCtxLogger)
|
||||
|
||||
cmdOutWriters = io.MultiWriter(&cmdOutBuf)
|
||||
|
||||
if IsCmdStdOutEnabled() {
|
||||
cmdOutWriters = io.MultiWriter(os.Stdout, &cmdOutBuf)
|
||||
}
|
||||
|
||||
localCMD.Stdout = cmdOutWriters
|
||||
localCMD.Stderr = cmdOutWriters
|
||||
|
||||
err = localCMD.Run()
|
||||
|
||||
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)
|
||||
}
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
|
||||
return outputArr, err
|
||||
}
|
||||
return outputArr, nil
|
||||
}
|
||||
|
||||
cmdCtxLogger.Info().Str("Command", fmt.Sprintf("Running command %s on local machine", command.Name)).Send()
|
||||
|
||||
localCMD := exec.Command(command.Cmd, command.Args...)
|
||||
|
||||
if command.Dir != nil {
|
||||
localCMD.Dir = *command.Dir
|
||||
}
|
||||
@ -134,7 +137,9 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
|
||||
if str, ok := outMap["output"].(string); ok {
|
||||
outputArr = append(outputArr, str)
|
||||
}
|
||||
// if command.GetOutput {
|
||||
cmdCtxLogger.Info().Fields(outMap).Send()
|
||||
// }
|
||||
}
|
||||
if err != nil {
|
||||
cmdCtxLogger.Error().Err(fmt.Errorf("error when running cmd %s: %w", command.Name, err)).Send()
|
||||
@ -173,7 +178,7 @@ func cmdListWorker(msgTemps *msgTemplates, jobs <-chan *CmdList, results chan<-
|
||||
|
||||
// Notify failure
|
||||
if list.NotifyConfig != nil {
|
||||
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun, opts)
|
||||
notifyError(cmdLogger, msgTemps, list, cmdsRan, outStructArr, runErr, cmdToRun)
|
||||
}
|
||||
hasError = true
|
||||
break
|
||||
@ -227,13 +232,14 @@ func cmdsRanContains(cmd string, cmdsRan []string) bool {
|
||||
}
|
||||
|
||||
// Helper to notify errors
|
||||
func notifyError(logger zerolog.Logger, templates *msgTemplates, list *CmdList, cmdsRan []string, outStructArr []outStruct, err error, cmd *Command, opts *ConfigOpts) {
|
||||
func notifyError(logger zerolog.Logger, templates *msgTemplates, list *CmdList, cmdsRan []string, outStructArr []outStruct, err error, cmd *Command) {
|
||||
errStruct := map[string]interface{}{
|
||||
"listName": list.Name,
|
||||
"CmdsRan": cmdsRan,
|
||||
"CmdOutput": outStructArr,
|
||||
"Err": err,
|
||||
"Command": cmd.Name,
|
||||
"CmdName": cmd.Name,
|
||||
"Command": cmd.Cmd,
|
||||
"Args": cmd.Args,
|
||||
}
|
||||
var errMsg bytes.Buffer
|
||||
@ -299,13 +305,7 @@ func (opts *ConfigOpts) RunListConfig(cron string) {
|
||||
opts.closeHostConnections()
|
||||
}
|
||||
|
||||
type CmdResult struct {
|
||||
CmdName string // Name of the command executed
|
||||
ListName string // Name of the command list
|
||||
Error error // Error encountered, if any
|
||||
}
|
||||
|
||||
func (config *ConfigOpts) ExecuteCmds(opts *ConfigOpts) {
|
||||
func (opts *ConfigOpts) ExecuteCmds() {
|
||||
for _, cmd := range opts.executeCmds {
|
||||
cmdToRun := opts.Cmds[cmd]
|
||||
cmdLogger := cmdToRun.GenerateLogger(opts)
|
||||
@ -425,3 +425,14 @@ func (opts *ConfigOpts) ExecCmdsSSH(cmdList []string, hostsList []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func executeUserCommands() []string {
|
||||
|
||||
// }
|
||||
|
||||
// // parseRemoteSources parses source and validates fields using sourceType
|
||||
// func (c *Command) parseRemoteSources(source, sourceType string) {
|
||||
// switch sourceType {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
@ -4,318 +4,181 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/logging"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/usermanager"
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/providers/rawbytes"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
var homeDir string
|
||||
var homeDirErr error
|
||||
var backyHomeConfDir string
|
||||
var configFiles []string
|
||||
|
||||
const macroStart string = "%{"
|
||||
const macroEnd string = "}%"
|
||||
const envMacroStart string = "%{env:"
|
||||
const vaultMacroStart string = "%{env:"
|
||||
const vaultMacroStart string = "%{vault:"
|
||||
|
||||
func (opts *ConfigOpts) InitConfig() {
|
||||
|
||||
homeDir, homeDirErr = os.UserHomeDir()
|
||||
|
||||
if homeDirErr != nil {
|
||||
fmt.Println(homeDirErr)
|
||||
logging.ExitWithMSG(homeDirErr.Error(), 1, nil)
|
||||
var err error
|
||||
homeConfigDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(err.Error(), 1, nil)
|
||||
}
|
||||
homeCacheDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(err.Error(), 1, nil)
|
||||
}
|
||||
|
||||
backyHomeConfDir = homeDir + "/.config/backy/"
|
||||
|
||||
configFiles = []string{"./backy.yml", "./backy.yaml", backyHomeConfDir + "backy.yml", backyHomeConfDir + "backy.yaml"}
|
||||
backyHomeConfDir := path.Join(homeConfigDir, "backy")
|
||||
configFiles := []string{
|
||||
"./backy.yml", "./backy.yaml",
|
||||
path.Join(backyHomeConfDir, "backy.yml"),
|
||||
path.Join(backyHomeConfDir, "backy.yaml"),
|
||||
}
|
||||
|
||||
backyKoanf := koanf.New(".")
|
||||
|
||||
opts.ConfigFilePath = strings.TrimSpace(opts.ConfigFilePath)
|
||||
|
||||
if opts.ConfigFilePath != "" {
|
||||
err := testFile(opts.ConfigFilePath)
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("Could not open config file %s: %v", opts.ConfigFilePath, err), 1, nil)
|
||||
}
|
||||
// metadataFile := "hashMetadataSample.yml"
|
||||
|
||||
if err := backyKoanf.Load(file.Provider(opts.ConfigFilePath), yaml.Parser()); err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
|
||||
}
|
||||
} else {
|
||||
cacheDir := homeCacheDir
|
||||
|
||||
cFileFailures := 0
|
||||
for _, c := range configFiles {
|
||||
if err := backyKoanf.Load(file.Provider(c), yaml.Parser()); err != nil {
|
||||
cFileFailures++
|
||||
} else {
|
||||
opts.ConfigFilePath = c
|
||||
break
|
||||
}
|
||||
}
|
||||
if cFileFailures == len(configFiles) {
|
||||
logging.ExitWithMSG(fmt.Sprintf("could not find a config file. Put one in the following paths: %v", configFiles), 1, &opts.Logger)
|
||||
// Load metadata from file
|
||||
opts.CachedData, err = remotefetcher.LoadMetadataFromFile(path.Join(backyHomeConfDir, "cache.yml"))
|
||||
if err != nil {
|
||||
fmt.Println("Error loading metadata:", err)
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
// Initialize cache with loaded metadata
|
||||
cache, err := remotefetcher.NewCache(path.Join(backyHomeConfDir, "cache.yml"), cacheDir)
|
||||
if err != nil {
|
||||
fmt.Println("Error initializing cache:", err)
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
// Populate cache with loaded metadata
|
||||
for _, data := range opts.CachedData {
|
||||
if err := cache.AddDataToStore(data.Hash, *data); err != nil {
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
}
|
||||
|
||||
opts.Cache, err = remotefetcher.NewCache(path.Join(backyHomeConfDir, "cache.yml"), backyHomeConfDir)
|
||||
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 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
|
||||
}
|
||||
|
||||
// ReadConfig validates and reads the config file.
|
||||
func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
os.Setenv("BACKY_TERM", "enabled")
|
||||
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
||||
os.Setenv("BACKY_TERM", "enabled")
|
||||
} else {
|
||||
os.Setenv("BACKY_TERM", "disabled")
|
||||
func loadConfigFile(fetcher remotefetcher.RemoteFetcher, filePath string, k *koanf.Koanf, opts *ConfigOpts) {
|
||||
data, err := fetcher.Fetch(filePath)
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("Could not fetch config file %s: %v", filePath, err), 1, nil)
|
||||
}
|
||||
|
||||
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
|
||||
}
|
||||
}
|
||||
|
||||
func loadDefaultConfigFiles(fetcher remotefetcher.RemoteFetcher, configFiles []string, k *koanf.Koanf, opts *ConfigOpts) {
|
||||
cFileFailures := 0
|
||||
for _, c := range configFiles {
|
||||
opts.ConfigFilePath = c
|
||||
data, err := fetcher.Fetch(c)
|
||||
if err != nil {
|
||||
cFileFailures++
|
||||
continue
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err == nil {
|
||||
break
|
||||
} else {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error loading config from file %s: %v", c, err), 1, &opts.Logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cFileFailures == len(configFiles) {
|
||||
logging.ExitWithMSG("Could not find any valid local config file", 1, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
|
||||
setTerminalEnv()
|
||||
|
||||
backyKoanf := opts.koanf
|
||||
|
||||
opts.loadEnv()
|
||||
|
||||
if backyKoanf.Bool(getNestedConfig("logging", "cmd-std-out")) {
|
||||
os.Setenv("BACKY_STDOUT", "enabled")
|
||||
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)
|
||||
|
||||
// check for commands in file
|
||||
for _, c := range opts.executeCmds {
|
||||
if !backyKoanf.Exists(getCmdFromConfig(c)) {
|
||||
logging.ExitWithMSG(Sprintf("command %s is not in config file %s", c, opts.ConfigFilePath), 1, nil)
|
||||
}
|
||||
}
|
||||
validateCommands(backyKoanf, opts)
|
||||
|
||||
// TODO: refactor this further down the line
|
||||
|
||||
// for _, l := range opts.executeLists {
|
||||
// if !backyKoanf.Exists(getCmdListFromConfig(l)) {
|
||||
// logging.ExitWithMSG(Sprintf("list %s not found", l), 1, nil)
|
||||
// }
|
||||
// }
|
||||
|
||||
// check for verbosity, via
|
||||
// 1. config file
|
||||
// 2. TODO: CLI flag
|
||||
// 3. TODO: ENV var
|
||||
|
||||
var (
|
||||
isLoggingVerbose bool
|
||||
logFile string
|
||||
)
|
||||
|
||||
isLoggingVerbose = backyKoanf.Bool(getLoggingKeyFromConfig("verbose"))
|
||||
|
||||
logFile = fmt.Sprintf("%s/backy.log", path.Dir(opts.ConfigFilePath)) // get full path to logfile
|
||||
|
||||
if backyKoanf.Exists(getLoggingKeyFromConfig("file")) {
|
||||
logFile = backyKoanf.String(getLoggingKeyFromConfig("file"))
|
||||
}
|
||||
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
|
||||
if isLoggingVerbose {
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
globalLvl := zerolog.GlobalLevel()
|
||||
os.Setenv("BACKY_LOGLEVEL", Sprintf("%v", globalLvl))
|
||||
}
|
||||
|
||||
consoleLoggingDisabled := backyKoanf.Bool(getLoggingKeyFromConfig("console-disabled"))
|
||||
|
||||
os.Setenv("BACKY_CONSOLE_LOGGING", "enabled")
|
||||
// Other qualifiers can go here as well
|
||||
if consoleLoggingDisabled {
|
||||
os.Setenv("BACKY_CONSOLE_LOGGING", "")
|
||||
}
|
||||
|
||||
writers := logging.SetLoggingWriters(logFile)
|
||||
|
||||
log := zerolog.New(writers).With().Timestamp().Logger()
|
||||
setLoggingOptions(backyKoanf, opts)
|
||||
|
||||
log := setupLogger(opts)
|
||||
opts.Logger = log
|
||||
|
||||
log.Info().Str("config file", opts.ConfigFilePath).Send()
|
||||
|
||||
unmarshalErr := backyKoanf.UnmarshalWithConf("commands", &opts.Cmds, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
unmarshalConfig(backyKoanf, "commands", &opts.Cmds, opts.Logger)
|
||||
|
||||
if unmarshalErr != nil {
|
||||
validateCommandEnvironments(opts)
|
||||
|
||||
panic(fmt.Errorf("error unmarshaling cmds struct: %w", unmarshalErr))
|
||||
unmarshalConfig(backyKoanf, "hosts", &opts.Hosts, opts.Logger)
|
||||
|
||||
}
|
||||
resolveHostConfigs(opts)
|
||||
|
||||
for cmdName, cmdConf := range opts.Cmds {
|
||||
envFileErr := testFile(cmdConf.Env)
|
||||
if envFileErr != nil {
|
||||
opts.Logger.Info().Str("cmd", cmdName).Err(envFileErr).Send()
|
||||
os.Exit(1)
|
||||
}
|
||||
loadCommandLists(opts, backyKoanf)
|
||||
|
||||
expandEnvVars(opts.backyEnv, cmdConf.Environment)
|
||||
}
|
||||
validateCommandLists(opts)
|
||||
|
||||
// Get host configurations from config file
|
||||
|
||||
unmarshalErr = backyKoanf.UnmarshalWithConf("hosts", &opts.Hosts, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
if unmarshalErr != nil {
|
||||
panic(fmt.Errorf("error unmarshalling hosts struct: %w", unmarshalErr))
|
||||
}
|
||||
for hostConfigName, host := range opts.Hosts {
|
||||
if host.Host == "" {
|
||||
host.Host = hostConfigName
|
||||
}
|
||||
if host.ProxyJump != "" {
|
||||
proxyHosts := strings.Split(host.ProxyJump, ",")
|
||||
for hostNum, h := range proxyHosts {
|
||||
if hostNum > 1 {
|
||||
proxyHost, defined := opts.Hosts[h]
|
||||
if defined {
|
||||
host.ProxyHost = append(host.ProxyHost, proxyHost)
|
||||
} else {
|
||||
newProxy := &Host{Host: h}
|
||||
host.ProxyHost = append(host.ProxyHost, newProxy)
|
||||
}
|
||||
} else {
|
||||
proxyHost, defined := opts.Hosts[h]
|
||||
if defined {
|
||||
host.ProxyHost = append(host.ProxyHost, proxyHost)
|
||||
} else {
|
||||
newHost := &Host{Host: h}
|
||||
host.ProxyHost = append(host.ProxyHost, newHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// get command lists
|
||||
// command lists should still be in the same file if no:
|
||||
// 1. key 'cmd-lists.file' is found
|
||||
// 2. hosts.yml or hosts.yaml is found in the same directory as the backy config file
|
||||
backyConfigFileDir := path.Dir(opts.ConfigFilePath)
|
||||
|
||||
listsConfig := koanf.New(".")
|
||||
|
||||
listConfigFiles := []string{path.Join(backyConfigFileDir, "lists.yml"), path.Join(backyConfigFileDir, "lists.yaml")}
|
||||
|
||||
log.Info().Strs("list config files", listConfigFiles).Send()
|
||||
for _, l := range listConfigFiles {
|
||||
cFileFailures := 0
|
||||
if err := listsConfig.Load(file.Provider(l), yaml.Parser()); err != nil {
|
||||
cFileFailures++
|
||||
} else {
|
||||
opts.ConfigFilePath = l
|
||||
break
|
||||
}
|
||||
|
||||
if cFileFailures == len(configFiles) {
|
||||
|
||||
logging.ExitWithMSG(fmt.Sprintf("could not find a config file. Put one in the following paths: %v", listConfigFiles), 1, &opts.Logger)
|
||||
|
||||
// logging.ExitWithMSG((fmt.Sprintf("error unmarshalling cmd list struct: %v", unmarshalErr)), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
}
|
||||
_ = listsConfig.UnmarshalWithConf("cmd-lists", &opts.CmdConfigLists, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
|
||||
if backyKoanf.Exists("cmd-lists") {
|
||||
|
||||
unmarshalErr = backyKoanf.UnmarshalWithConf("cmd-lists", &opts.CmdConfigLists, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
// if unmarshalErr is not nil, look for a cmd-lists.file key
|
||||
if unmarshalErr != nil {
|
||||
|
||||
// if file key exists, resolve file path and try to read and unmarshal file into command lists config
|
||||
if backyKoanf.Exists("cmd-lists.file") {
|
||||
opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmd-lists.file"))
|
||||
|
||||
cmdListFilePath := path.Clean(opts.CmdListFile)
|
||||
|
||||
// if path is not absolute, check config directory
|
||||
if !strings.HasPrefix(cmdListFilePath, "/") {
|
||||
opts.CmdListFile = path.Join(backyConfigFileDir, cmdListFilePath)
|
||||
}
|
||||
|
||||
err := testFile(opts.CmdListFile)
|
||||
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("Could not open config file %s: %v. \n\nThe cmd-lists config should be in the main config file or should be in a lists.yml or lists.yaml file.", opts.CmdListFile, err), 1, nil)
|
||||
}
|
||||
|
||||
if err := listsConfig.Load(file.Provider(opts.CmdListFile), yaml.Parser()); err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
log.Info().Str("using lists config file", opts.CmdListFile).Send()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var cmdNotFoundSliceErr []error
|
||||
for cmdListName, cmdList := range opts.CmdConfigLists {
|
||||
if opts.cronEnabled {
|
||||
cron := strings.TrimSpace(cmdList.Cron)
|
||||
if cron == "" {
|
||||
delete(opts.CmdConfigLists, cmdListName)
|
||||
}
|
||||
}
|
||||
for _, cmdInList := range cmdList.Order {
|
||||
_, cmdNameFound := opts.Cmds[cmdInList]
|
||||
if !cmdNameFound {
|
||||
cmdNotFoundStr := fmt.Sprintf("command %s in list %s is not defined in commands section in config file", cmdInList, cmdListName)
|
||||
cmdNotFoundErr := errors.New(cmdNotFoundStr)
|
||||
cmdNotFoundSliceErr = append(cmdNotFoundSliceErr, cmdNotFoundErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit program if command is not found from list
|
||||
if len(cmdNotFoundSliceErr) > 0 {
|
||||
var cmdNotFoundErrorLog = log.Fatal()
|
||||
cmdNotFoundErrorLog.Errs("commands not found", cmdNotFoundSliceErr).Send()
|
||||
}
|
||||
|
||||
if opts.cronEnabled && (len(opts.CmdConfigLists) == 0) {
|
||||
if opts.cronEnabled && len(opts.CmdConfigLists) == 0 {
|
||||
logging.ExitWithMSG("No cron fields detected in any command lists", 1, nil)
|
||||
}
|
||||
|
||||
// process commands
|
||||
if err := processCmds(opts); err != nil {
|
||||
logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
if len(opts.executeLists) > 0 {
|
||||
for l := range opts.CmdConfigLists {
|
||||
if !contains(opts.executeLists, l) {
|
||||
delete(opts.CmdConfigLists, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
filterExecuteLists(opts)
|
||||
|
||||
if backyKoanf.Exists("notifications") {
|
||||
|
||||
unmarshalErr = backyKoanf.UnmarshalWithConf("notifications", &opts.NotificationConf, koanf.UnmarshalConf{Tag: "yaml"})
|
||||
if unmarshalErr != nil {
|
||||
fmt.Printf("error unmarshalling notifications object: %v", unmarshalErr)
|
||||
}
|
||||
unmarshalConfig(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger)
|
||||
}
|
||||
|
||||
opts.SetupNotify()
|
||||
@ -327,6 +190,221 @@ func ReadConfig(opts *ConfigOpts) *ConfigOpts {
|
||||
return opts
|
||||
}
|
||||
|
||||
func setTerminalEnv() {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
||||
os.Setenv("BACKY_TERM", "enabled")
|
||||
} else {
|
||||
os.Setenv("BACKY_TERM", "disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func validateCommands(k *koanf.Koanf, opts *ConfigOpts) {
|
||||
for _, c := range opts.executeCmds {
|
||||
if !k.Exists(getCmdFromConfig(c)) {
|
||||
logging.ExitWithMSG(fmt.Sprintf("command %s is not in config file %s", c, opts.ConfigFilePath), 1, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setLoggingOptions(k *koanf.Koanf, opts *ConfigOpts) {
|
||||
isLoggingVerbose := k.Bool(getLoggingKeyFromConfig("verbose"))
|
||||
|
||||
// if log file is set in config file and not set on command line, use "./backy.log"
|
||||
logFile := "./backy.log"
|
||||
if opts.LogFilePath == "" && k.Exists(getLoggingKeyFromConfig("file")) {
|
||||
logFile = k.String(getLoggingKeyFromConfig("file"))
|
||||
opts.LogFilePath = logFile
|
||||
}
|
||||
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
if isLoggingVerbose {
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
os.Setenv("BACKY_LOGLEVEL", fmt.Sprintf("%v", zerolog.GlobalLevel()))
|
||||
}
|
||||
|
||||
if k.Bool(getLoggingKeyFromConfig("console-disabled")) {
|
||||
os.Setenv("BACKY_CONSOLE_LOGGING", "")
|
||||
} else {
|
||||
os.Setenv("BACKY_CONSOLE_LOGGING", "enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func setupLogger(opts *ConfigOpts) zerolog.Logger {
|
||||
writers := logging.SetLoggingWriters(opts.LogFilePath)
|
||||
return zerolog.New(writers).With().Timestamp().Logger()
|
||||
}
|
||||
|
||||
func unmarshalConfig(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) {
|
||||
if err := k.UnmarshalWithConf(key, target, koanf.UnmarshalConf{Tag: "yaml"}); err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error unmarshalling key %s into struct: %v", key, err), 1, &log)
|
||||
}
|
||||
}
|
||||
|
||||
func validateCommandEnvironments(opts *ConfigOpts) {
|
||||
for cmdName, cmdConf := range opts.Cmds {
|
||||
if err := testFile(cmdConf.Env); err != nil {
|
||||
opts.Logger.Info().Str("cmd", cmdName).Err(err).Send()
|
||||
os.Exit(1)
|
||||
}
|
||||
expandEnvVars(opts.backyEnv, cmdConf.Environment)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveHostConfigs(opts *ConfigOpts) {
|
||||
for hostConfigName, host := range opts.Hosts {
|
||||
if host.Host == "" {
|
||||
host.Host = hostConfigName
|
||||
}
|
||||
if host.ProxyJump != "" {
|
||||
resolveProxyHosts(host, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveProxyHosts(host *Host, opts *ConfigOpts) {
|
||||
proxyHosts := strings.Split(host.ProxyJump, ",")
|
||||
for _, h := range proxyHosts {
|
||||
proxyHost, defined := opts.Hosts[h]
|
||||
if !defined {
|
||||
proxyHost = &Host{Host: h}
|
||||
opts.Hosts[h] = proxyHost
|
||||
}
|
||||
host.ProxyHost = append(host.ProxyHost, proxyHost)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if isRemoteURL(opts.ConfigFilePath) {
|
||||
_, u = getRemoteDir(opts.ConfigFilePath)
|
||||
// // Still use local list files if a remote config file is used, but use them last
|
||||
listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()}
|
||||
} else {
|
||||
backyConfigFileDir = path.Dir(opts.ConfigFilePath)
|
||||
listConfigFiles = []string{
|
||||
// "./lists.yml", "./lists.yaml",
|
||||
path.Join(backyConfigFileDir, "lists.yml"),
|
||||
path.Join(backyConfigFileDir, "lists.yaml"),
|
||||
}
|
||||
}
|
||||
|
||||
listsConfig := koanf.New(".")
|
||||
|
||||
for _, l := range listConfigFiles {
|
||||
if loadListConfigFile(l, listsConfig, opts) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if backyKoanf.Exists("cmdLists") {
|
||||
unmarshalConfig(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger)
|
||||
if backyKoanf.Exists("cmdLists.file") {
|
||||
loadCmdListsFile(backyKoanf, listsConfig, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isRemoteURL(filePath string) bool {
|
||||
return strings.HasPrefix(filePath, "http://") || strings.HasPrefix(filePath, "https://") || strings.HasPrefix(filePath, "s3://")
|
||||
}
|
||||
|
||||
func getRemoteDir(filePath string) (string, *url.URL) {
|
||||
u, err := url.Parse(filePath)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
// u.Path is the path to the file, stripped of scheme and hostname
|
||||
u.Path = path.Dir(u.Path)
|
||||
|
||||
return u.String(), u
|
||||
}
|
||||
|
||||
func loadListConfigFile(filePath string, k *koanf.Koanf, opts *ConfigOpts) bool {
|
||||
fetcher, err := remotefetcher.NewRemoteFetcher(filePath, opts.Cache, remotefetcher.IgnoreFileNotFound())
|
||||
if err != nil {
|
||||
// if file not found, ignore
|
||||
if errors.Is(err, remotefetcher.ErrIgnoreFileNotFound) {
|
||||
println("File not found", filePath)
|
||||
return true
|
||||
}
|
||||
|
||||
logging.ExitWithMSG(fmt.Sprintf("error initializing config fetcher: %v", err), 1, nil)
|
||||
}
|
||||
|
||||
data, err := fetcher.Fetch(filePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := k.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
unmarshalConfig(k, "cmdLists", &opts.CmdConfigLists, opts.Logger)
|
||||
keyNotSupported("cmd-lists", "cmdLists", k, opts, true)
|
||||
opts.CmdListFile = filePath
|
||||
return true
|
||||
}
|
||||
|
||||
func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *ConfigOpts) {
|
||||
opts.CmdListFile = strings.TrimSpace(backyKoanf.String("cmdLists.file"))
|
||||
if !path.IsAbs(opts.CmdListFile) {
|
||||
opts.CmdListFile = path.Join(path.Dir(opts.ConfigFilePath), opts.CmdListFile)
|
||||
}
|
||||
|
||||
fetcher, err := remotefetcher.NewRemoteFetcher(opts.CmdListFile, opts.Cache)
|
||||
|
||||
if err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error initializing config fetcher: %v", err), 1, nil)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if err := listsConfig.Load(rawbytes.Provider(data), yaml.Parser()); err != nil {
|
||||
logging.ExitWithMSG(fmt.Sprintf("error loading config: %v", err), 1, &opts.Logger)
|
||||
}
|
||||
|
||||
keyNotSupported("cmd-lists", "cmdLists", listsConfig, opts, true)
|
||||
unmarshalConfig(listsConfig, "cmdLists", &opts.CmdConfigLists, opts.Logger)
|
||||
opts.Logger.Info().Str("using lists config file", opts.CmdListFile).Send()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
for _, cmdInList := range cmdList.Order {
|
||||
if _, cmdNameFound := opts.Cmds[cmdInList]; !cmdNameFound {
|
||||
cmdNotFoundSliceErr = append(cmdNotFoundSliceErr, fmt.Errorf("command %s in list %s is not defined in commands section in config file", cmdInList, cmdListName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(cmdNotFoundSliceErr) > 0 {
|
||||
opts.Logger.Fatal().Errs("commands not found", cmdNotFoundSliceErr).Send()
|
||||
}
|
||||
}
|
||||
|
||||
func filterExecuteLists(opts *ConfigOpts) {
|
||||
if len(opts.executeLists) > 0 {
|
||||
for l := range opts.CmdConfigLists {
|
||||
if !contains(opts.executeLists, l) {
|
||||
delete(opts.CmdConfigLists, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func getNestedConfig(nestedConfig, key string) string {
|
||||
return fmt.Sprintf("%s.%s", nestedConfig, key)
|
||||
}
|
||||
@ -342,9 +420,9 @@ 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 {
|
||||
if !opts.koanf.Bool("vault.enabled") {
|
||||
@ -448,6 +526,7 @@ func GetVaultKey(str string, opts *ConfigOpts, log zerolog.Logger) string {
|
||||
}
|
||||
|
||||
func processCmds(opts *ConfigOpts) error {
|
||||
|
||||
// process commands
|
||||
for cmdName, cmd := range opts.Cmds {
|
||||
|
||||
@ -486,6 +565,16 @@ func processCmds(opts *ConfigOpts) error {
|
||||
opts.Hosts[*cmd.Host] = &Host{Host: *cmd.Host}
|
||||
cmd.RemoteHost = &Host{Host: *cmd.Host}
|
||||
}
|
||||
} else {
|
||||
|
||||
if cmd.Dir != nil {
|
||||
|
||||
cmdDir, err := resolveDir(*cmd.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Dir = &cmdDir
|
||||
}
|
||||
}
|
||||
|
||||
// Parse package commands
|
||||
@ -503,7 +592,7 @@ func processCmds(opts *ConfigOpts) error {
|
||||
|
||||
// Validate the operation
|
||||
switch cmd.PackageOperation {
|
||||
case "install", "remove", "upgrade":
|
||||
case "install", "remove", "upgrade", "checkVersion":
|
||||
cmd.pkgMan, err = pkgman.PackageManagerFactory(cmd.PackageManager, pkgman.WithoutAuth())
|
||||
if err != nil {
|
||||
return err
|
||||
@ -511,6 +600,40 @@ func processCmds(opts *ConfigOpts) error {
|
||||
default:
|
||||
return fmt.Errorf("unsupported package operation %s for command %s", cmd.PackageOperation, cmd.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse user commands
|
||||
if cmd.Type == "user" {
|
||||
if cmd.Username == "" {
|
||||
return fmt.Errorf("username is required for user command %s", cmd.Name)
|
||||
}
|
||||
|
||||
detectOSType(cmd, opts)
|
||||
var err error
|
||||
|
||||
// Validate the operation
|
||||
switch cmd.UserOperation {
|
||||
case "add", "remove", "modify", "checkIfExists", "delete", "password":
|
||||
cmd.userMan, err = usermanager.NewUserManager(cmd.OS)
|
||||
if cmd.Host != nil {
|
||||
host, ok := opts.Hosts[*cmd.Host]
|
||||
if ok {
|
||||
cmd.userMan, err = usermanager.NewUserManager(host.OS)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported user operation %s for command %s", cmd.UserOperation, cmd.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if cmd.Type == "remoteScript" {
|
||||
if !isRemoteURL(cmd.Cmd) {
|
||||
return fmt.Errorf("remoteScript command %s must be a remote resource", cmdName)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -519,17 +642,9 @@ func processCmds(opts *ConfigOpts) error {
|
||||
|
||||
// processHooks evaluates if hooks are valid Commands
|
||||
//
|
||||
// Takes the following arguments:
|
||||
// The cmd.hookRefs[hookType] is created with any hooks found.
|
||||
//
|
||||
// 1. a []string of hooks
|
||||
// 2. a map of Commands as arguments
|
||||
// 3. a string hookType, must be the hook type
|
||||
//
|
||||
// The cmds.hookRef is modified in this function.
|
||||
//
|
||||
// Returns the following:
|
||||
//
|
||||
// An error, if any, if the command is not 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
|
||||
@ -557,3 +672,43 @@ func processHooks(cmd *Command, hooks []string, opts *ConfigOpts, hookType strin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectOSType(cmd *Command, opts *ConfigOpts) error {
|
||||
if cmd.Host == nil {
|
||||
if runtime.GOOS == "linux" { // also can be specified to FreeBSD
|
||||
cmd.OS = "linux"
|
||||
opts.Logger.Info().Msg("Unix/Linux type OS detected")
|
||||
}
|
||||
}
|
||||
host, ok := opts.Hosts[*cmd.Host]
|
||||
if ok {
|
||||
if host.OS != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
os, err := host.DetectOS(opts)
|
||||
os = strings.TrimSpace(os)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if os == "" {
|
||||
return fmt.Errorf("error detecting os for command %s: empty string", cmd.Name)
|
||||
}
|
||||
if strings.Contains(os, "linux") {
|
||||
os = "linux"
|
||||
}
|
||||
host.OS = os
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,34 @@ 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
|
||||
// is it remote or local
|
||||
if cmdInfo.Host != nil {
|
||||
|
||||
print("Host: ", 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()
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
fmt.Printf("Command %s not found. Check spelling.\n", cmd)
|
||||
@ -66,3 +71,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)
|
||||
|
||||
}
|
||||
}
|
||||
|
133
pkg/backy/ssh.go
133
pkg/backy/ssh.go
@ -119,7 +119,7 @@ func (remoteConfig *Host) ConnectToHost(opts *ConfigOpts) error {
|
||||
return errors.Wrap(err, "could not create hostkeycallback function")
|
||||
}
|
||||
remoteConfig.ClientConfig.HostKeyCallback = hostKeyCallback
|
||||
opts.Logger.Info().Str("user", remoteConfig.ClientConfig.User).Send()
|
||||
// opts.Logger.Info().Str("user", remoteConfig.ClientConfig.User).Send()
|
||||
|
||||
remoteConfig.SshClient, connectErr = remoteConfig.ConnectThroughBastion(opts.Logger)
|
||||
if connectErr != nil {
|
||||
@ -492,9 +492,10 @@ 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 = getPackageCommand(command)
|
||||
command = getCommandType(command)
|
||||
|
||||
// Prepare command arguments
|
||||
for _, v := range command.Args {
|
||||
@ -506,7 +507,7 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
Str("Host", *command.Host).
|
||||
Msgf("Running %s on host %s", getCommandTypeLabel(command.Type), *command.Host)
|
||||
|
||||
cmdCtxLogger.Debug().Str("cmd", command.Cmd).Strs("args", command.Args).Send()
|
||||
// cmdCtxLogger.Debug().Str("cmd", command.Cmd).Strs("args", command.Args).Send()
|
||||
|
||||
// Ensure SSH client is connected
|
||||
if command.RemoteHost.SshClient == nil {
|
||||
@ -516,7 +517,7 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
}
|
||||
|
||||
// Create new SSH session
|
||||
commandSession, err := command.createSSHSession(opts)
|
||||
commandSession, err := command.RemoteHost.createSSHSession(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create SSH session: %w", err)
|
||||
}
|
||||
@ -539,6 +540,27 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
|
||||
return command.runScript(commandSession, cmdCtxLogger, &cmdOutBuf)
|
||||
case "scriptFile":
|
||||
return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf)
|
||||
case "package":
|
||||
if command.PackageOperation == "checkVersion" {
|
||||
commandSession.Stderr = nil
|
||||
// Execute the package version command remotely
|
||||
// Parse the output of package version command
|
||||
// Compare versions
|
||||
// Check if a specific version is specified
|
||||
commandSession.Stdout = nil
|
||||
return checkPackageVersion(cmdCtxLogger, command, commandSession, cmdOutBuf)
|
||||
} else {
|
||||
if command.Shell != "" {
|
||||
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
|
||||
} else {
|
||||
ArgsStr = fmt.Sprintf("%s %s", command.Cmd, ArgsStr)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if command.Shell != "" {
|
||||
ArgsStr = fmt.Sprintf("%s -c '%s %s'", command.Shell, command.Cmd, ArgsStr)
|
||||
@ -548,11 +570,35 @@ 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), fmt.Errorf("error running command: %w", err)
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.GetOutput), fmt.Errorf("error running command: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger), nil
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, true), nil
|
||||
}
|
||||
|
||||
func checkPackageVersion(cmdCtxLogger zerolog.Logger, command *Command, commandSession *ssh.Session, cmdOutBuf bytes.Buffer) ([]string, error) {
|
||||
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions")
|
||||
// Prepare command arguments
|
||||
ArgsStr := command.Cmd
|
||||
for _, v := range command.Args {
|
||||
ArgsStr += fmt.Sprintf(" %s", v)
|
||||
}
|
||||
|
||||
var err error
|
||||
var cmdOut []byte
|
||||
|
||||
if cmdOut, err = commandSession.CombinedOutput(ArgsStr); err != nil {
|
||||
cmdOutBuf.Write(cmdOut)
|
||||
|
||||
_, 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.GetOutput), 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.
|
||||
@ -563,20 +609,6 @@ func getCommandTypeLabel(commandType string) string {
|
||||
return fmt.Sprintf("%s command", commandType)
|
||||
}
|
||||
|
||||
// createSSHSession attempts to create a new SSH session and retries on failure.
|
||||
func (command *Command) createSSHSession(opts *ConfigOpts) (*ssh.Session, error) {
|
||||
session, err := command.RemoteHost.SshClient.NewSession()
|
||||
if err == nil {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Retry connection and session creation
|
||||
if connErr := command.RemoteHost.ConnectToHost(opts); connErr != nil {
|
||||
return nil, fmt.Errorf("session creation failed: %v, connection retry failed: %v", err, connErr)
|
||||
}
|
||||
return command.RemoteHost.SshClient.NewSession()
|
||||
}
|
||||
|
||||
// runScript handles the execution of inline scripts.
|
||||
func (command *Command) runScript(session *ssh.Session, cmdCtxLogger zerolog.Logger, outputBuf *bytes.Buffer) ([]string, error) {
|
||||
script, err := command.prepareScriptBuffer()
|
||||
@ -590,10 +622,10 @@ func (command *Command) runScript(session *ssh.Session, cmdCtxLogger zerolog.Log
|
||||
}
|
||||
|
||||
if err := session.Wait(); err != nil {
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger), fmt.Errorf("error waiting for shell: %w", err)
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err)
|
||||
}
|
||||
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger), nil
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, command.GetOutput), nil
|
||||
}
|
||||
|
||||
// runScriptFile handles the execution of script files.
|
||||
@ -609,10 +641,10 @@ func (command *Command) runScriptFile(session *ssh.Session, cmdCtxLogger zerolog
|
||||
}
|
||||
|
||||
if err := session.Wait(); err != nil {
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger), fmt.Errorf("error waiting for shell: %w", err)
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), fmt.Errorf("error waiting for shell: %w", err)
|
||||
}
|
||||
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger), nil
|
||||
return collectOutput(outputBuf, command.Name, cmdCtxLogger, true), nil
|
||||
}
|
||||
|
||||
// prepareScriptBuffer prepares a buffer for inline scripts.
|
||||
@ -677,13 +709,60 @@ func readFileToBuffer(filePath string) (*bytes.Buffer, error) {
|
||||
}
|
||||
|
||||
// collectOutput collects output from a buffer and logs it.
|
||||
func collectOutput(buf *bytes.Buffer, commandName string, logger zerolog.Logger) []string {
|
||||
func collectOutput(buf *bytes.Buffer, commandName string, logger zerolog.Logger, wantOutput bool) []string {
|
||||
var outputArr []string
|
||||
scanner := bufio.NewScanner(buf)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
outputArr = append(outputArr, line)
|
||||
logger.Info().Str("cmd", commandName).Str("output", line).Send()
|
||||
if wantOutput {
|
||||
logger.Info().Str("cmd", commandName).Str("output", line).Send()
|
||||
}
|
||||
}
|
||||
return outputArr
|
||||
}
|
||||
|
||||
// createSSHSession attempts to create a new SSH session and retries on failure.
|
||||
func (h *Host) createSSHSession(opts *ConfigOpts) (*ssh.Session, error) {
|
||||
session, err := h.SshClient.NewSession()
|
||||
if err == nil {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Retry connection and session creation
|
||||
if connErr := h.ConnectToHost(opts); connErr != nil {
|
||||
return nil, fmt.Errorf("session creation failed: %v, connection retry failed: %v", err, connErr)
|
||||
}
|
||||
return h.SshClient.NewSession()
|
||||
}
|
||||
|
||||
func (h *Host) DetectOS(opts *ConfigOpts) (string, error) {
|
||||
err := h.ConnectToHost(opts)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var session *ssh.Session
|
||||
session, err = h.createSSHSession(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Execute the "uname -a" command on the remote machine
|
||||
output, err := session.CombinedOutput("uname")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute OS detection command: %v", err)
|
||||
}
|
||||
|
||||
// Parse the output to determine the OS
|
||||
osName := string(output)
|
||||
return osName, nil
|
||||
}
|
||||
|
||||
func CheckIfHostHasHostName(host string) (bool, string) {
|
||||
HostName, err := ssh_config.DefaultUserSettings.GetStrict(host, "HostName")
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
println(HostName)
|
||||
return HostName != "", HostName
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
Command list {{.listName }} failed.
|
||||
|
||||
The command run was {{.Cmd}}.
|
||||
The command run was {{.CmdName}}.
|
||||
|
||||
The command executed was {{.Command}} {{ if .Args }} {{- range .Args}} {{.}} {{end}} {{end}}
|
||||
|
||||
{{ if .Err }} The error was {{ .Err }}{{ end }}
|
||||
|
||||
{{ if .Output }} The output was {{- range .Output}} {{.}} {{end}} {{end}}
|
||||
{{ if .Output }} The output was: {{- range .Output}} {{.}} {{end}} {{end}}
|
||||
|
||||
{{ if .CmdsRan }}
|
||||
The following commands ran:
|
||||
@ -15,7 +15,7 @@ The following commands ran:
|
||||
{{end}}
|
||||
{{ end }}
|
||||
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}Command output for {{ .CmdName }}:
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}{{ printf "\n"}}Command output for {{ .CmdName }}:
|
||||
{{- range .Output}}
|
||||
{{ . }}
|
||||
{{ end }}{{ end }}
|
||||
|
@ -5,7 +5,7 @@ The following commands ran:
|
||||
- {{. -}}
|
||||
{{end}}
|
||||
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}Command output for {{ .CmdName }}:
|
||||
{{ if .CmdOutput }}{{- range .CmdOutput }}{{ printf "\n"}}Command output for {{ .CmdName }}:
|
||||
{{- range .Output}}
|
||||
{{ . }}
|
||||
{{ end }}{{ end }}
|
||||
|
@ -4,7 +4,11 @@ import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
|
||||
"strings"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/usermanager"
|
||||
vaultapi "github.com/hashicorp/vault/api"
|
||||
"github.com/kevinburke/ssh_config"
|
||||
"github.com/knadh/koanf/v2"
|
||||
@ -18,6 +22,7 @@ type (
|
||||
// Host defines a host to which to connect.
|
||||
// If not provided, the values will be looked up in the default ssh config files
|
||||
Host struct {
|
||||
OS string `yaml:"OS,omitempty"`
|
||||
ConfigFilePath string `yaml:"config,omitempty"`
|
||||
Host string `yaml:"host,omitempty"`
|
||||
HostName string `yaml:"hostname,omitempty"`
|
||||
@ -62,10 +67,7 @@ type (
|
||||
// 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.
|
||||
Not applicable when host is defined.
|
||||
*/
|
||||
// Shell specifies which shell to run the command in, if any.
|
||||
Shell string `yaml:"shell,omitempty"`
|
||||
|
||||
RemoteHost *Host `yaml:"-"`
|
||||
@ -75,7 +77,6 @@ type (
|
||||
|
||||
/*
|
||||
Dir specifies a directory in which to run the command.
|
||||
Ignored if Host is set.
|
||||
*/
|
||||
Dir *string `yaml:"dir,omitempty"`
|
||||
|
||||
@ -86,11 +87,14 @@ type (
|
||||
Environment []string `yaml:"environment,omitempty"`
|
||||
|
||||
// Output determines if output is requested.
|
||||
// Only works if command is in a list.
|
||||
//
|
||||
// Only for when command is in a list.
|
||||
GetOutput bool `yaml:"getOutput,omitempty"`
|
||||
|
||||
ScriptEnvFile string `yaml:"scriptEnvFile"`
|
||||
|
||||
// BEGIN PACKAGE COMMAND FIELDS
|
||||
|
||||
PackageManager string `yaml:"packageManager,omitempty"`
|
||||
|
||||
PackageName string `yaml:"packageName,omitempty"`
|
||||
@ -104,6 +108,7 @@ type (
|
||||
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"`
|
||||
@ -111,28 +116,47 @@ type (
|
||||
// FetchBeforeExecution determines if the remoteSource should be fetched before running
|
||||
FetchBeforeExecution bool `yaml:"fetchBeforeExecution,omitempty"`
|
||||
|
||||
// BEGIN USER COMMAND FIELDS
|
||||
|
||||
// Username specifies the username for user creation or related operations
|
||||
Username string `yaml:"username,omitempty"`
|
||||
Username string `yaml:"userName,omitempty"`
|
||||
|
||||
// Groups specifies the groups to add the user to
|
||||
Groups []string `yaml:"groups,omitempty"`
|
||||
UserID string `yaml:"userID,omitempty"`
|
||||
|
||||
// Home specifies the home directory for the user
|
||||
Home string `yaml:"home,omitempty"`
|
||||
// UserGroups specifies the groups to add the user to
|
||||
UserGroups []string `yaml:"userGroups,omitempty"`
|
||||
|
||||
// System specifies whether the user is a system account
|
||||
System bool `yaml:"system,omitempty"`
|
||||
// UserHome specifies the home directory for the user
|
||||
UserHome string `yaml:"userHome,omitempty"`
|
||||
|
||||
// Password specifies the password for the user (can be file: or plain text)
|
||||
Password string `yaml:"password,omitempty"`
|
||||
// UserShell specifies the shell for the user
|
||||
UserShell string `yaml:"userShell,omitempty"`
|
||||
|
||||
// Operation specifies the action for user-related commands (e.g., "create" or "remove")
|
||||
Operation string `yaml:"operation,omitempty"`
|
||||
// SystemUser specifies whether the user is a system account
|
||||
SystemUser bool `yaml:"systemUser,omitempty"`
|
||||
|
||||
// UserPassword specifies the password for the user (can be file: or plain text)
|
||||
UserPassword string `yaml:"userPassword,omitempty"`
|
||||
|
||||
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
|
||||
|
||||
// stdin only for userOperation = password (for now)
|
||||
stdin *strings.Reader
|
||||
|
||||
// END USER STRUCT FIELDS
|
||||
}
|
||||
|
||||
RemoteSource struct {
|
||||
URL string `yaml:"url"`
|
||||
Type string `yaml:"type"` // e.g., yaml
|
||||
Type string `yaml:"type"` // e.g., s3, http
|
||||
Auth struct {
|
||||
AccessKey string `yaml:"accessKey"`
|
||||
SecretKey string `yaml:"secretKey"`
|
||||
@ -162,7 +186,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.
|
||||
@ -173,9 +197,14 @@ type (
|
||||
// Global log level
|
||||
BackyLogLvl *string
|
||||
|
||||
CmdStdOut bool
|
||||
|
||||
// Holds config file
|
||||
ConfigFilePath string
|
||||
|
||||
// Holds log file
|
||||
LogFilePath string
|
||||
|
||||
// for command list file
|
||||
CmdListFile string
|
||||
|
||||
@ -198,6 +227,9 @@ type (
|
||||
koanf *koanf.Koanf
|
||||
|
||||
NotificationConf *Notifications `yaml:"notifications"`
|
||||
|
||||
Cache *remotefetcher.Cache
|
||||
CachedData []*remotefetcher.CacheData
|
||||
}
|
||||
|
||||
outStruct struct {
|
||||
@ -252,10 +284,9 @@ type (
|
||||
Final []string `yaml:"final,omitempty"`
|
||||
}
|
||||
|
||||
CmdListResults struct {
|
||||
// name of the list
|
||||
ListName string
|
||||
// command that caused the list to fail
|
||||
ErrCmd string
|
||||
CmdResult struct {
|
||||
CmdName string // Name of the command executed
|
||||
ListName string // Name of the command list
|
||||
Error error // Error encountered, if any
|
||||
}
|
||||
)
|
||||
|
@ -5,6 +5,7 @@
|
||||
package backy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@ -42,7 +43,7 @@ func AddCommandLists(lists []string) BackyOptionFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// AddPrintLists adds lists to print out
|
||||
// SetListsToSearch adds lists to search
|
||||
func SetListsToSearch(lists []string) BackyOptionFunc {
|
||||
return func(bco *ConfigOpts) {
|
||||
bco.List.Lists = append(bco.List.Lists, lists...)
|
||||
@ -56,8 +57,22 @@ func SetCmdsToSearch(cmds []string) BackyOptionFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// cronEnabled enables the execution of command lists at specified times
|
||||
func CronEnabled() BackyOptionFunc {
|
||||
// SetLogFile sets the path to the log file
|
||||
func SetLogFile(logFile string) BackyOptionFunc {
|
||||
return func(bco *ConfigOpts) {
|
||||
bco.LogFilePath = logFile
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
bco.cronEnabled = true
|
||||
}
|
||||
@ -174,10 +189,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) {
|
||||
path = strings.TrimSpace(path)
|
||||
|
||||
if path == "~" {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@ -234,9 +251,10 @@ func expandEnvVars(backyEnv map[string]string, envVars []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// getPackageCommand checks for command type of package and if the command has already been set
|
||||
// Returns the modified Command with the packageManager command as Cmd and the packageOperation as args, plus any additional Args
|
||||
func getPackageCommand(command *Command) *Command {
|
||||
// getCommandType checks for command type and if the command has already been set
|
||||
// Checks for types package and user
|
||||
// Returns the modified Command with the package- or userManager command as Cmd and the package- or userOperation as args, plus any additional Args
|
||||
func getCommandType(command *Command) *Command {
|
||||
|
||||
if command.Type == "package" && !command.packageCmdSet {
|
||||
command.packageCmdSet = true
|
||||
@ -247,9 +265,69 @@ func getPackageCommand(command *Command) *Command {
|
||||
command.Cmd, command.Args = command.pkgMan.Remove(command.PackageName, command.Args)
|
||||
case "upgrade":
|
||||
command.Cmd, command.Args = command.pkgMan.Upgrade(command.PackageName, command.PackageVersion)
|
||||
case "checkVersion":
|
||||
command.Cmd, command.Args = command.pkgMan.CheckVersion(command.PackageName, command.PackageVersion)
|
||||
}
|
||||
} else if command.Type != "package" {
|
||||
command.packageCmdSet = false
|
||||
}
|
||||
|
||||
if command.Type == "user" && !command.userCmdSet {
|
||||
command.userCmdSet = true
|
||||
switch command.UserOperation {
|
||||
case "add":
|
||||
command.Cmd, command.Args = command.userMan.AddUser(
|
||||
command.Username,
|
||||
command.UserHome,
|
||||
command.UserShell,
|
||||
command.SystemUser,
|
||||
command.UserGroups,
|
||||
command.Args)
|
||||
case "modify":
|
||||
command.Cmd, command.Args = command.userMan.ModifyUser(
|
||||
command.Username,
|
||||
command.UserHome,
|
||||
command.UserShell,
|
||||
command.UserGroups)
|
||||
case "checkIfExists":
|
||||
command.Cmd, command.Args = command.userMan.UserExists(command.Username)
|
||||
case "delete":
|
||||
command.Cmd, command.Args = command.userMan.RemoveUser(command.Username)
|
||||
case "password":
|
||||
command.Cmd, command.stdin, command.UserPassword = command.userMan.ModifyPassword(command.Username, command.UserPassword)
|
||||
}
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func parsePackageVersion(output string, cmdCtxLogger zerolog.Logger, command *Command, cmdOutBuf bytes.Buffer) ([]string, error) {
|
||||
|
||||
var err error
|
||||
pkgVersion, err := command.pkgMan.Parse(output)
|
||||
// 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
|
||||
}
|
||||
|
||||
cmdCtxLogger.Info().
|
||||
Str("Installed", pkgVersion.Installed).
|
||||
Str("Candidate", pkgVersion.Candidate).
|
||||
Msg("Package version comparison")
|
||||
|
||||
if command.PackageVersion != "" {
|
||||
if pkgVersion.Installed == command.PackageVersion {
|
||||
cmdCtxLogger.Info().Msgf("Installed version matches specified version: %s", command.PackageVersion)
|
||||
} else {
|
||||
cmdCtxLogger.Info().Msgf("Installed version does not match specified version: %s", command.PackageVersion)
|
||||
err = fmt.Errorf("Installed version does not match specified version: %s", command.PackageVersion)
|
||||
}
|
||||
} else {
|
||||
if pkgVersion.Installed == pkgVersion.Candidate {
|
||||
cmdCtxLogger.Info().Msg("Installed and Candidate versions match")
|
||||
} else {
|
||||
cmdCtxLogger.Info().Msg("Installed and Candidate versions differ")
|
||||
err = errors.New("Installed and Candidate versions differ")
|
||||
}
|
||||
}
|
||||
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, false), err
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package apt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon"
|
||||
)
|
||||
@ -10,6 +12,7 @@ import (
|
||||
type AptManager struct {
|
||||
useAuth bool // Whether to use an authentication command
|
||||
authCommand string // The authentication command, e.g., "sudo"
|
||||
Parser pkgcommon.PackageParser
|
||||
}
|
||||
|
||||
// DefaultAuthCommand is the default command used for authentication.
|
||||
@ -53,7 +56,7 @@ func (a *AptManager) Remove(pkg string, args []string) (string, []string) {
|
||||
// Upgrade returns the command and arguments for upgrading a specific package.
|
||||
func (a *AptManager) Upgrade(pkg, version string) (string, []string) {
|
||||
baseCmd := a.prependAuthCommand(DefaultPackageCommand)
|
||||
baseArgs := []string{"update", "&&", baseCmd, "install", "--only-upgrade", "-y "}
|
||||
baseArgs := []string{"update", "&&", baseCmd, "install", "--only-upgrade", "-y"}
|
||||
if version != "" {
|
||||
baseArgs = append(baseArgs, fmt.Sprintf("%s=%s", pkg, version))
|
||||
} else {
|
||||
@ -62,6 +65,14 @@ func (a *AptManager) Upgrade(pkg, version string) (string, []string) {
|
||||
return baseCmd, baseArgs
|
||||
}
|
||||
|
||||
// CheckVersion returns the command and arguments for checking the info of a specific package.
|
||||
func (a *AptManager) CheckVersion(pkg, version string) (string, []string) {
|
||||
baseCmd := a.prependAuthCommand("apt-cache")
|
||||
baseArgs := []string{"policy", pkg}
|
||||
|
||||
return baseCmd, baseArgs
|
||||
}
|
||||
|
||||
// UpgradeAll returns the command and arguments for upgrading all packages.
|
||||
func (a *AptManager) UpgradeAll() (string, []string) {
|
||||
baseCmd := a.prependAuthCommand(DefaultPackageCommand)
|
||||
@ -93,3 +104,27 @@ func (a *AptManager) SetUseAuth(useAuth bool) {
|
||||
func (a *AptManager) SetAuthCommand(authCommand string) {
|
||||
a.authCommand = authCommand
|
||||
}
|
||||
|
||||
// Parse parses the apt-cache policy output to extract Installed and Candidate versions.
|
||||
func (a *AptManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
|
||||
// Check for error message in the output
|
||||
if strings.Contains(output, "Unable to locate package") {
|
||||
return nil, fmt.Errorf("error: %s", strings.TrimSpace(output))
|
||||
}
|
||||
|
||||
reInstalled := regexp.MustCompile(`Installed:\s*([^\s]+)`)
|
||||
reCandidate := regexp.MustCompile(`Candidate:\s*([^\s]+)`)
|
||||
|
||||
installedMatch := reInstalled.FindStringSubmatch(output)
|
||||
candidateMatch := reCandidate.FindStringSubmatch(output)
|
||||
|
||||
if len(installedMatch) < 2 || len(candidateMatch) < 2 {
|
||||
return nil, fmt.Errorf("failed to parse Installed or Candidate versions from apt output. check package name")
|
||||
}
|
||||
|
||||
return &pkgcommon.PackageVersion{
|
||||
Installed: strings.TrimSpace(installedMatch[1]),
|
||||
Candidate: strings.TrimSpace(candidateMatch[1]),
|
||||
Match: installedMatch[1] == candidateMatch[1],
|
||||
}, nil
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package dnf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon"
|
||||
)
|
||||
@ -74,6 +76,50 @@ func (y *DnfManager) UpgradeAll() (string, []string) {
|
||||
return baseCmd, baseArgs
|
||||
}
|
||||
|
||||
// CheckVersion returns the command and arguments for checking the info of a specific package.
|
||||
func (d *DnfManager) CheckVersion(pkg, version string) (string, []string) {
|
||||
baseCmd := d.prependAuthCommand("dnf")
|
||||
baseArgs := []string{"info", pkg}
|
||||
|
||||
return baseCmd, baseArgs
|
||||
}
|
||||
|
||||
// Parse parses the dnf info output to extract Installed and Candidate versions.
|
||||
func (d DnfManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
|
||||
|
||||
// Check for error message in the output
|
||||
if strings.Contains(output, "No matching packages to list") {
|
||||
return nil, fmt.Errorf("error: package not listed")
|
||||
}
|
||||
|
||||
// Define regular expressions to capture installed and available versions
|
||||
reInstalled := regexp.MustCompile(`(?m)^Installed packages\s*Name\s*:\s*\S+\s*Epoch\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
|
||||
reAvailable := regexp.MustCompile(`(?m)^Available packages\s*Name\s*:\s*\S+\s*Epoch\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
|
||||
|
||||
installedMatch := reInstalled.FindStringSubmatch(output)
|
||||
candidateMatch := reAvailable.FindStringSubmatch(output)
|
||||
|
||||
installedVersion := ""
|
||||
candidateVersion := ""
|
||||
|
||||
if len(installedMatch) >= 3 {
|
||||
installedVersion = fmt.Sprintf("%s-%s", installedMatch[1], installedMatch[2])
|
||||
}
|
||||
|
||||
if len(candidateMatch) >= 3 {
|
||||
candidateVersion = fmt.Sprintf("%s-%s", candidateMatch[1], candidateMatch[2])
|
||||
}
|
||||
|
||||
if installedVersion == "" && candidateVersion == "" {
|
||||
return nil, fmt.Errorf("failed to parse versions from dnf output")
|
||||
}
|
||||
|
||||
return &pkgcommon.PackageVersion{
|
||||
Installed: installedVersion,
|
||||
Candidate: candidateVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// prependAuthCommand prepends the authentication command if UseAuth is true.
|
||||
func (y *DnfManager) prependAuthCommand(baseCmd string) string {
|
||||
if y.useAuth {
|
||||
|
@ -2,3 +2,16 @@ package pkgcommon
|
||||
|
||||
// PackageManagerOption defines a functional option for configuring a PackageManager.
|
||||
type PackageManagerOption func(interface{})
|
||||
|
||||
// PackageParser defines an interface for parsing package version information.
|
||||
type PackageParser interface {
|
||||
Parse(output string) (*PackageVersion, error)
|
||||
}
|
||||
|
||||
// PackageVersion represents the installed and candidate versions of a package.
|
||||
type PackageVersion struct {
|
||||
Installed string
|
||||
Candidate string
|
||||
Match bool
|
||||
Message string
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ type PackageManager interface {
|
||||
Remove(pkg string, args []string) (string, []string)
|
||||
Upgrade(pkg, version string) (string, []string) // Upgrade a specific package
|
||||
UpgradeAll() (string, []string)
|
||||
|
||||
CheckVersion(pkg, version string) (string, []string)
|
||||
Parse(output string) (*pkgcommon.PackageVersion, error)
|
||||
// Configure applies functional options to customize the package manager.
|
||||
Configure(options ...pkgcommon.PackageManagerOption)
|
||||
}
|
||||
@ -67,6 +68,8 @@ func WithoutAuth() pkgcommon.PackageManagerOption {
|
||||
|
||||
// ConfigurablePackageManager defines methods for setting configuration options.
|
||||
type ConfigurablePackageManager interface {
|
||||
pkgcommon.PackageParser
|
||||
SetUseAuth(useAuth bool)
|
||||
SetAuthCommand(authCommand string)
|
||||
SetPackageParser(parser pkgcommon.PackageParser)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package yum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon"
|
||||
)
|
||||
@ -74,6 +75,43 @@ func (y *YumManager) UpgradeAll() (string, []string) {
|
||||
return baseCmd, baseArgs
|
||||
}
|
||||
|
||||
// CheckVersion returns the command and arguments for checking the info of a specific package.
|
||||
func (y *YumManager) CheckVersion(pkg, version string) (string, []string) {
|
||||
baseCmd := y.prependAuthCommand("yum")
|
||||
baseArgs := []string{"info", pkg}
|
||||
|
||||
return baseCmd, baseArgs
|
||||
}
|
||||
|
||||
// Parse parses the dnf info output to extract Installed and Candidate versions.
|
||||
func (y YumManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
|
||||
reInstalled := regexp.MustCompile(`(?m)^Installed Packages\s*Name\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
|
||||
reAvailable := regexp.MustCompile(`(?m)^Available Packages\s*Name\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
|
||||
|
||||
installedMatch := reInstalled.FindStringSubmatch(output)
|
||||
candidateMatch := reAvailable.FindStringSubmatch(output)
|
||||
|
||||
installedVersion := ""
|
||||
candidateVersion := ""
|
||||
|
||||
if len(installedMatch) >= 3 {
|
||||
installedVersion = fmt.Sprintf("%s-%s", installedMatch[1], installedMatch[2])
|
||||
}
|
||||
|
||||
if len(candidateMatch) >= 3 {
|
||||
candidateVersion = fmt.Sprintf("%s-%s", candidateMatch[1], candidateMatch[2])
|
||||
}
|
||||
|
||||
if installedVersion == "" && candidateVersion == "" {
|
||||
return nil, fmt.Errorf("failed to parse versions from dnf output")
|
||||
}
|
||||
|
||||
return &pkgcommon.PackageVersion{
|
||||
Installed: installedVersion,
|
||||
Candidate: candidateVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// prependAuthCommand prepends the authentication command if UseAuth is true.
|
||||
func (y *YumManager) prependAuthCommand(baseCmd string) string {
|
||||
if y.useAuth {
|
||||
|
186
pkg/remotefetcher/cache.go
Normal file
186
pkg/remotefetcher/cache.go
Normal file
@ -0,0 +1,186 @@
|
||||
package remotefetcher
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CacheData struct {
|
||||
Hash string `yaml:"hash"`
|
||||
Path string `yaml:"path"`
|
||||
Type string `yaml:"type"`
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
mu sync.Mutex
|
||||
store map[string]CacheData
|
||||
file string
|
||||
dir string
|
||||
}
|
||||
|
||||
func NewCache(file, dir string) (*Cache, error) {
|
||||
cache := &Cache{
|
||||
store: make(map[string]CacheData),
|
||||
file: file,
|
||||
dir: dir,
|
||||
}
|
||||
err := cache.loadFromFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func (c *Cache) loadFromFile() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if _, err := os.Stat(c.file); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(c.file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var cacheData []CacheData
|
||||
err = yaml.Unmarshal(data, &cacheData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range cacheData {
|
||||
c.store[item.Hash] = item
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) saveToFile() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
var cacheData []CacheData
|
||||
for _, data := range c.store {
|
||||
cacheData = append(cacheData, data)
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(cacheData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(c.file, data, 0644)
|
||||
}
|
||||
|
||||
func (c *Cache) Get(hash string) ([]byte, CacheData, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
cacheData, exists := c.store[hash]
|
||||
if !exists {
|
||||
return nil, CacheData{}, false
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(cacheData.Path)
|
||||
if err != nil {
|
||||
return nil, CacheData{}, false
|
||||
}
|
||||
|
||||
return data, cacheData, true
|
||||
}
|
||||
|
||||
func (c *Cache) AddDataToStore(hash string, cacheData CacheData) error {
|
||||
c.store[hash] = cacheData
|
||||
return c.saveToFile()
|
||||
}
|
||||
|
||||
func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheData, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
fileName := filepath.Base(source)
|
||||
|
||||
path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, hash))
|
||||
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
os.MkdirAll(c.dir, 0700)
|
||||
}
|
||||
|
||||
err := os.WriteFile(path, data, 0644)
|
||||
if err != nil {
|
||||
return CacheData{}, err
|
||||
}
|
||||
|
||||
cacheData := CacheData{
|
||||
Hash: hash,
|
||||
Path: path,
|
||||
Type: dataType,
|
||||
}
|
||||
|
||||
c.store[hash] = cacheData
|
||||
|
||||
// Unlock before calling saveToFile to avoid double-locking
|
||||
c.mu.Unlock()
|
||||
err = c.saveToFile()
|
||||
c.mu.Lock()
|
||||
if err != nil {
|
||||
return CacheData{}, err
|
||||
}
|
||||
|
||||
// fmt.Printf("Cache data: %v", cacheData)
|
||||
return cacheData, nil
|
||||
}
|
||||
|
||||
type CachedFetcher struct {
|
||||
data []byte
|
||||
path string
|
||||
dataType string
|
||||
}
|
||||
|
||||
func (cf *CachedFetcher) Fetch(source string) ([]byte, error) {
|
||||
return cf.data, nil
|
||||
}
|
||||
|
||||
func (cf *CachedFetcher) Parse(data []byte, target interface{}) error {
|
||||
if cf.dataType == "yaml" {
|
||||
return yaml.Unmarshal(data, target)
|
||||
}
|
||||
return errors.New("parse not supported on cached fetcher for scripts")
|
||||
}
|
||||
|
||||
func (cf *CachedFetcher) Hash(data []byte) string {
|
||||
hash := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// Function to read and parse the metadata file
|
||||
func LoadMetadataFromFile(filePath string) ([]*CacheData, error) {
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
// Create the file if it does not exist
|
||||
emptyData := []byte("[]")
|
||||
err := os.WriteFile(filePath, emptyData, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cacheData []*CacheData
|
||||
err = yaml.Unmarshal(data, &cacheData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cacheData, nil
|
||||
}
|
76
pkg/remotefetcher/configfetcher.go
Normal file
76
pkg/remotefetcher/configfetcher.go
Normal file
@ -0,0 +1,76 @@
|
||||
package remotefetcher
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RemoteFetcher interface {
|
||||
// Fetch retrieves the configuration from the specified URL or source
|
||||
// Returns the raw data as bytes or an error
|
||||
Fetch(source string) ([]byte, error)
|
||||
|
||||
// Parse decodes the raw data into a Go structure (e.g., Commands, CommandLists)
|
||||
// Takes the raw data as input and populates the target interface
|
||||
Parse(data []byte, target interface{}) error
|
||||
|
||||
// Hash returns the hash of the configuration data
|
||||
Hash(data []byte) string
|
||||
}
|
||||
|
||||
// ErrIgnoreFileNotFound is returned when the file is not found and should be ignored
|
||||
var ErrIgnoreFileNotFound = errors.New("remotefetcher: file not found")
|
||||
|
||||
func NewRemoteFetcher(source string, cache *Cache, options ...FetcherOption) (RemoteFetcher, error) {
|
||||
var fetcher RemoteFetcher
|
||||
|
||||
config := FetcherConfig{}
|
||||
for _, option := range options {
|
||||
option(&config)
|
||||
}
|
||||
|
||||
// If FileType is empty (i.e. WithFileType was not called), yaml is the default file type
|
||||
if strings.TrimSpace(config.FileType) == "" {
|
||||
config.FileType = "yaml"
|
||||
}
|
||||
if strings.HasPrefix(source, "http") || strings.HasPrefix(source, "https") {
|
||||
fetcher = NewHTTPFetcher(options...)
|
||||
} else if strings.HasPrefix(source, "s3") {
|
||||
var err error
|
||||
fetcher, err = NewS3Fetcher(source, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
fetcher = &LocalFetcher{}
|
||||
|
||||
return fetcher, nil
|
||||
}
|
||||
|
||||
//TODO: should local files be cached?
|
||||
|
||||
data, err := fetcher.Fetch(source)
|
||||
if err != nil {
|
||||
if config.IgnoreFileNotFound && isFileNotFoundError(err) {
|
||||
return nil, ErrIgnoreFileNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := fetcher.Hash(data)
|
||||
if cachedData, cacheMeta, exists := cache.Get(hash); exists {
|
||||
return &CachedFetcher{data: cachedData, path: cacheMeta.Path, dataType: cacheMeta.Type}, nil
|
||||
}
|
||||
|
||||
cacheData, err := cache.Set(source, hash, data, config.FileType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CachedFetcher{data: data, path: cacheData.Path, dataType: cacheData.Type}, nil
|
||||
}
|
||||
|
||||
func isFileNotFoundError(err error) bool {
|
||||
// Implement logic to check if the error is a "file not found" error
|
||||
// This can be based on the error type or message
|
||||
return strings.Contains(err.Error(), "file not found")
|
||||
}
|
60
pkg/remotefetcher/http.go
Normal file
60
pkg/remotefetcher/http.go
Normal file
@ -0,0 +1,60 @@
|
||||
package remotefetcher
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type HTTPFetcher struct {
|
||||
HTTPClient *http.Client
|
||||
config FetcherConfig
|
||||
}
|
||||
|
||||
// NewHTTPFetcher creates a new instance of HTTPFetcher with the provided options.
|
||||
func NewHTTPFetcher(options ...FetcherOption) *HTTPFetcher {
|
||||
cfg := &FetcherConfig{}
|
||||
for _, opt := range options {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
// Initialize HTTP client if not provided
|
||||
if cfg.HTTPClient == nil {
|
||||
cfg.HTTPClient = http.DefaultClient
|
||||
}
|
||||
|
||||
return &HTTPFetcher{HTTPClient: cfg.HTTPClient, config: *cfg}
|
||||
}
|
||||
|
||||
// Fetch retrieves the file from the specified source URL
|
||||
func (h *HTTPFetcher) Fetch(source string) ([]byte, error) {
|
||||
resp, err := http.Get(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound && h.config.IgnoreFileNotFound {
|
||||
return nil, ErrIgnoreFileNotFound
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("failed to fetch remote config: " + resp.Status)
|
||||
}
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// Parse decodes the raw data into the provided target structure
|
||||
func (h *HTTPFetcher) Parse(data []byte, target interface{}) error {
|
||||
return yaml.Unmarshal(data, target)
|
||||
}
|
||||
|
||||
func (h *HTTPFetcher) Hash(data []byte) string {
|
||||
hash := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
42
pkg/remotefetcher/local.go
Normal file
42
pkg/remotefetcher/local.go
Normal file
@ -0,0 +1,42 @@
|
||||
package remotefetcher
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type LocalFetcher struct {
|
||||
config FetcherConfig
|
||||
}
|
||||
|
||||
// Fetch retrieves the file from the specified local file path
|
||||
func (l *LocalFetcher) Fetch(source string) ([]byte, error) {
|
||||
// Check if the file exists
|
||||
if _, err := os.Stat(source); os.IsNotExist(err) {
|
||||
if l.config.IgnoreFileNotFound {
|
||||
return nil, ErrIgnoreFileNotFound
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
file, err := os.Open(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return io.ReadAll(file)
|
||||
}
|
||||
|
||||
// Parse decodes the raw data into the provided target structure
|
||||
func (l *LocalFetcher) Parse(data []byte, target interface{}) error {
|
||||
return yaml.Unmarshal(data, target)
|
||||
}
|
||||
|
||||
func (l *LocalFetcher) Hash(data []byte) string {
|
||||
hash := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
49
pkg/remotefetcher/options.go
Normal file
49
pkg/remotefetcher/options.go
Normal file
@ -0,0 +1,49 @@
|
||||
package remotefetcher
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
)
|
||||
|
||||
// Option is a function that configures a fetcher.
|
||||
type FetcherOption func(*FetcherConfig)
|
||||
|
||||
// FetcherConfig holds the configuration for a fetcher.
|
||||
type FetcherConfig struct {
|
||||
S3Client *s3.Client
|
||||
HTTPClient *http.Client
|
||||
FileType string
|
||||
IgnoreFileNotFound bool
|
||||
}
|
||||
|
||||
// WithS3Client sets the S3 client for the fetcher.
|
||||
func WithS3Client(client *s3.Client) FetcherOption {
|
||||
return func(cfg *FetcherConfig) {
|
||||
cfg.S3Client = client
|
||||
}
|
||||
}
|
||||
|
||||
// WithHTTPClient sets the HTTP client for the fetcher.
|
||||
func WithHTTPClient(client *http.Client) FetcherOption {
|
||||
return func(cfg *FetcherConfig) {
|
||||
cfg.HTTPClient = client
|
||||
}
|
||||
}
|
||||
|
||||
func IgnoreFileNotFound() FetcherOption {
|
||||
return func(cfg *FetcherConfig) {
|
||||
cfg.IgnoreFileNotFound = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithFileType ensures the default FileType will be yaml
|
||||
func WithFileType(fileType string) FetcherOption {
|
||||
return func(cfg *FetcherConfig) {
|
||||
cfg.FileType = fileType
|
||||
if strings.TrimSpace(fileType) == "" {
|
||||
cfg.FileType = "yaml"
|
||||
}
|
||||
}
|
||||
}
|
162
pkg/remotefetcher/s3.go
Normal file
162
pkg/remotefetcher/s3.go
Normal file
@ -0,0 +1,162 @@
|
||||
package remotefetcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type S3Fetcher struct {
|
||||
S3Client *minio.Client
|
||||
config FetcherConfig
|
||||
}
|
||||
|
||||
// NewS3Fetcher creates a new instance of S3Fetcher with the provided options.
|
||||
func NewS3Fetcher(endpoint string, options ...FetcherOption) (*S3Fetcher, error) {
|
||||
cfg := &FetcherConfig{}
|
||||
var s3Client *minio.Client
|
||||
var err error
|
||||
for _, opt := range options {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
/*
|
||||
options for S3 urls:
|
||||
1. s3://bucket.region.endpoint.tld/path/to/object
|
||||
2. alias with path and rest is looked up in file - add FetcherOptions
|
||||
|
||||
|
||||
options for S3 credentials:
|
||||
1. from file ($HOME/.aws/credentials)
|
||||
2. env vars (AWS_SECRET_KEY, etc.)
|
||||
*/
|
||||
|
||||
s3Endpoint := os.Getenv("S3_ENDPOINT")
|
||||
creds, err := getS3Credentials("default", s3Endpoint, cfg.HTTPClient)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
// Initialize S3 client if not provided
|
||||
if cfg.S3Client == nil {
|
||||
s3Client, err = minio.New(s3Endpoint, &minio.Options{
|
||||
Creds: creds,
|
||||
Secure: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &S3Fetcher{S3Client: s3Client, config: *cfg}, nil
|
||||
}
|
||||
|
||||
// Fetch retrieves the configuration from an S3 bucket
|
||||
// Source should be in the format "bucket-name/object-key"
|
||||
func (s *S3Fetcher) Fetch(source string) ([]byte, error) {
|
||||
bucket, object, err := parseS3Source(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
doesObjectExist, objErr := objectExists(bucket, object, s.S3Client)
|
||||
if !doesObjectExist {
|
||||
if objErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.config.IgnoreFileNotFound {
|
||||
return nil, ErrIgnoreFileNotFound
|
||||
}
|
||||
}
|
||||
|
||||
fileObject, err := s.S3Client.GetObject(context.TODO(), bucket, object, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
|
||||
return nil, err
|
||||
}
|
||||
defer fileObject.Close()
|
||||
fileObjectStats, statErr := fileObject.Stat()
|
||||
if statErr != nil {
|
||||
return nil, statErr
|
||||
}
|
||||
buffer := make([]byte, fileObjectStats.Size)
|
||||
|
||||
// Read the object into the buffer
|
||||
_, err = io.ReadFull(fileObject, buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// Parse decodes the raw data into the provided target structure
|
||||
func (s *S3Fetcher) Parse(data []byte, target interface{}) error {
|
||||
return yaml.Unmarshal(data, target)
|
||||
}
|
||||
|
||||
// Helper function to parse S3 source into bucket and key
|
||||
func parseS3Source(source string) (bucket, key string, err error) {
|
||||
parts := strings.SplitN(source, "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", "", errors.New("invalid S3 source format, expected bucket-name/object-key")
|
||||
}
|
||||
u, _ := url.Parse(source)
|
||||
u.Path = strings.TrimPrefix(u.Path, "/")
|
||||
return u.Host, u.Path, nil
|
||||
}
|
||||
|
||||
func (s *S3Fetcher) Hash(data []byte) string {
|
||||
hash := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
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")
|
||||
credVals, credErr := s3Creds.GetWithContext(&credentials.CredContext{Endpoint: host, Client: httpClient})
|
||||
if credErr != nil {
|
||||
return nil, credErr
|
||||
}
|
||||
creds := credentials.NewStaticV4(credVals.AccessKeyID, credVals.SecretAccessKey, "")
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
var (
|
||||
doesNotExist = "The specified key does not exist."
|
||||
)
|
||||
|
||||
// objectExists checks for name in bucket using client.
|
||||
// It returns false and nil if the key does not exist
|
||||
func objectExists(bucket, name string, client *minio.Client) (bool, error) {
|
||||
_, err := client.StatObject(context.TODO(), bucket, name, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
switch err.Error() {
|
||||
case doesNotExist:
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.Join(err, errors.New("error stating object"))
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
7
pkg/usermanager/common/options.go
Normal file
7
pkg/usermanager/common/options.go
Normal file
@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
// ConfigurablePackageManager defines methods for setting configuration options.
|
||||
type ConfigurableUserManager interface {
|
||||
SetUseAuth(useAuth bool)
|
||||
SetAuthCommand(authCommand string)
|
||||
}
|
90
pkg/usermanager/linux/linux.go
Normal file
90
pkg/usermanager/linux/linux.go
Normal file
@ -0,0 +1,90 @@
|
||||
package linux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
passGen "github.com/sethvargo/go-password/password"
|
||||
)
|
||||
|
||||
// LinuxUserManager implements UserManager for Linux systems.
|
||||
type LinuxUserManager struct{}
|
||||
|
||||
func (l LinuxUserManager) NewLinuxManager() *LinuxUserManager {
|
||||
return &LinuxUserManager{}
|
||||
}
|
||||
|
||||
// AddUser adds a new user to the system.
|
||||
func (l LinuxUserManager) AddUser(username, homeDir, shell string, isSystem bool, groups, args []string) (string, []string) {
|
||||
baseArgs := []string{}
|
||||
|
||||
if isSystem {
|
||||
baseArgs = append(baseArgs, "--system")
|
||||
}
|
||||
|
||||
if homeDir != "" {
|
||||
baseArgs = append(baseArgs, "--home", homeDir)
|
||||
}
|
||||
|
||||
if shell != "" {
|
||||
baseArgs = append(baseArgs, "--shell", shell)
|
||||
}
|
||||
|
||||
if len(groups) > 0 {
|
||||
baseArgs = append(baseArgs, "--groups", strings.Join(groups, ","))
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
baseArgs = append(baseArgs, args...)
|
||||
}
|
||||
|
||||
args = append(baseArgs, username)
|
||||
|
||||
cmd := "useradd"
|
||||
return cmd, args
|
||||
}
|
||||
|
||||
func (l LinuxUserManager) ModifyPassword(username, password string) (string, *strings.Reader, string) {
|
||||
cmd := "chpasswd"
|
||||
if password == "" {
|
||||
password = passGen.MustGenerate(20, 5, 5, false, false)
|
||||
}
|
||||
stdin := strings.NewReader(fmt.Sprintf("%s:%s", username, password))
|
||||
return cmd, stdin, password
|
||||
}
|
||||
|
||||
// RemoveUser removes an existing user from the system.
|
||||
func (l LinuxUserManager) RemoveUser(username string) (string, []string) {
|
||||
cmd := "userdel"
|
||||
|
||||
return cmd, []string{username}
|
||||
}
|
||||
|
||||
// ModifyUser modifies an existing user's details.
|
||||
func (l LinuxUserManager) ModifyUser(username, homeDir, shell string, groups []string) (string, []string) {
|
||||
args := []string{}
|
||||
|
||||
if homeDir != "" {
|
||||
args = append(args, "--home", homeDir)
|
||||
}
|
||||
|
||||
if shell != "" {
|
||||
args = append(args, "--shell", shell)
|
||||
}
|
||||
|
||||
if len(groups) > 0 {
|
||||
args = append(args, "--groups", strings.Join(groups, ","))
|
||||
}
|
||||
|
||||
args = append(args, username)
|
||||
|
||||
cmd := "usermod"
|
||||
|
||||
return cmd, args
|
||||
}
|
||||
|
||||
// UserExists checks if a user exists on the system.
|
||||
func (l LinuxUserManager) UserExists(username string) (string, []string) {
|
||||
cmd := "id"
|
||||
return cmd, []string{username}
|
||||
}
|
36
pkg/usermanager/userman.go
Normal file
36
pkg/usermanager/userman.go
Normal file
@ -0,0 +1,36 @@
|
||||
package usermanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.andrewnw.xyz/CyberShell/backy/pkg/usermanager/linux"
|
||||
)
|
||||
|
||||
// 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)
|
||||
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
|
||||
// Should return a password as the last argument
|
||||
// TODO: refactor when adding more systems instead of Linux
|
||||
ModifyPassword(username, password string) (string, *strings.Reader, string)
|
||||
UserExists(username string) (string, []string)
|
||||
}
|
||||
|
||||
// NewUserManager returns a UserManager-compatible struct
|
||||
func NewUserManager(system string) (UserManager, error) {
|
||||
var manager UserManager
|
||||
|
||||
switch system {
|
||||
case "linux", "Linux":
|
||||
manager = linux.LinuxUserManager{}
|
||||
default:
|
||||
return nil, fmt.Errorf("usermanger system %s is not recognized", system)
|
||||
}
|
||||
|
||||
return manager, nil
|
||||
|
||||
}
|
11
release
11
release
@ -1,6 +1,13 @@
|
||||
#!/bin/bash
|
||||
# export GORELEASER_CURRENT_TAG="$(go run backy.go version -V)"
|
||||
git tag "$(go run backy.go version -V)"
|
||||
set -eou pipefail
|
||||
export CURRENT_TAG="$(go run backy.go version -V)"
|
||||
goreleaser -f .goreleaser/github.yml check
|
||||
goreleaser -f .goreleaser/gitea.yml check
|
||||
changie batch $CURRENT_TAG
|
||||
changie merge
|
||||
git add .changes/
|
||||
git commit -am "$CURRENT_TAG"
|
||||
git tag "$CURRENT_TAG"
|
||||
git push all
|
||||
git push all --tags
|
||||
# goreleaser release -f .goreleaser/gitea.yml --clean --release-notes=".changes/$(go run backy.go version -V).md"
|
Loading…
x
Reference in New Issue
Block a user