13 Commits

Author SHA1 Message Date
3c6e3ed914 v0.10.2
All checks were successful
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
2025-03-19 22:42:49 -05:00
02bc040e2a v0.10.2
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-19 22:41:18 -05:00
9f1f36215a v0.10.2 2025-03-19 22:40:06 -05:00
ff75f4bbcd feat: add variable support
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-16 23:43:12 -05:00
5f40713e98 feat: add variable support 2025-03-16 23:42:54 -05:00
cd5f7611a9 notifications: add http service
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-13 23:35:00 -05:00
b542711078 notifications: add http service 2025-03-13 23:34:37 -05:00
52dbc353e5 change: update go toolchain
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-12 23:22:52 -05:00
6bef0c3e5b notifications: add http config
All checks were successful
ci/woodpecker/push/go-lint Pipeline was successful
2025-03-12 10:10:45 -05:00
4d705d78fb fix: golang-ci-lint version
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-03-11 22:22:39 -05:00
62d47ecfa7 fix: pipeline errors
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
2025-03-11 22:08:59 -05:00
32444ff82e fix: docs and pipeline errors
Some checks failed
ci/woodpecker/tag/gitea Pipeline failed
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline failed
2025-03-11 21:55:51 -05:00
a5a7c05640 v0.10.1
Some checks failed
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline failed
ci/woodpecker/tag/publish-docs Pipeline was successful
2025-03-11 21:37:58 -05:00
22 changed files with 344 additions and 59 deletions

View File

@ -1,3 +0,0 @@
kind: Added
body: 'UserCommands: add ssh public keys when running locally'
time: 2025-03-11T15:20:28.487596157-05:00

View File

@ -1,3 +0,0 @@
kind: Added
body: 'UserCommands: add field CreateUserHome'
time: 2025-03-11T15:30:26.824884876-05:00

View File

@ -1,3 +0,0 @@
kind: Changed
body: 'UserCommands: create temp file when modifing password over SSH'
time: 2025-03-11T14:54:10.720370135-05:00

View File

@ -1,3 +0,0 @@
kind: Changed
body: 'UserCommands: change field name'
time: 2025-03-11T15:36:19.802011559-05:00

View File

@ -1,3 +0,0 @@
kind: Changed
body: 'Vault: keys are now referenced by `name`, and the actual data by `data`'
time: 2025-03-11T21:26:38.085271797-05:00

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

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

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

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

View File

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

View File

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

View File

@ -6,6 +6,22 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v0.10.2 - 2025-03-19
### Added
* Notifications: http service added
* Variable support. Can be referenced with `%{var:nameOfVar}%` in select string fields.
### Changed
* vault: initialize vault before validating config
## v0.10.1 - 2025-03-11
### Added
* UserCommands: add ssh public keys when running locally
* UserCommands: add field CreateUserHome
### Changed
* UserCommands: create temp file when modifing password over SSH
* UserCommands: change field name
* Vault: keys are now referenced by `name`, and the actual data by `data`
## v0.10.0 - 2025-03-08 ## v0.10.0 - 2025-03-08
### Added ### Added
* Hooks: improved logging when executing * Hooks: improved logging when executing

View File

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

View File

@ -95,9 +95,9 @@ The following options are available:
The environment variables support expansion: The environment variables support expansion:
- using escaped values `$VAR` or `${VAR}` - using escaped values `$VAR` or `${VAR}`
- using the directive`%{env:VAR}%` - using any external directive, and if using the env directive, the variable will be read from a `.env` file
For now, the variables have to be defined in an `.env` file in the same directory that the program is run from. <!-- For now, the variables expanded have to be defined in an `.env` file in the same directory that the program is run from. -->
If using it with host specified, the SSH server has to be configured to accept those env variables. If using it with host specified, the SSH server has to be configured to accept those env variables.

6
go.mod
View File

@ -2,14 +2,13 @@ module git.andrewnw.xyz/CyberShell/backy
go 1.23 go 1.23
toolchain go1.23.6 toolchain go1.23.7
replace git.andrewnw.xyz/CyberShell/backy => /home/andrew/Projects/backy
require ( require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.76.0
github.com/dmarkham/enumer v1.5.11 github.com/dmarkham/enumer v1.5.11
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/google/uuid v1.6.0
github.com/hashicorp/vault/api v1.15.0 github.com/hashicorp/vault/api v1.15.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/kevinburke/ssh_config v1.2.0 github.com/kevinburke/ssh_config v1.2.0
@ -51,7 +50,6 @@ require (
github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.5 // 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/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect

View File

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

View File

@ -217,7 +217,13 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
command.UserHome = strings.TrimSpace(string(userHome)) command.UserHome = strings.TrimSpace(string(userHome))
userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome) userSshDir := fmt.Sprintf("%s/.ssh", command.UserHome)
os.MkdirAll(userSshDir, 0700) if _, err := os.Stat(userSshDir); os.IsNotExist(err) {
err := os.MkdirAll(userSshDir, 0700)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating directory %s %v", userSshDir, err)
}
}
if _, err := os.Stat(fmt.Sprintf("%s/authorized_keys", userSshDir)); os.IsNotExist(err) { if _, err := os.Stat(fmt.Sprintf("%s/authorized_keys", userSshDir)); os.IsNotExist(err) {
_, err := os.Create(fmt.Sprintf("%s/authorized_keys", userSshDir)) _, err := os.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil { if err != nil {
@ -457,7 +463,7 @@ func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
cmdLogger := opts.Logger.With(). cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "error"). Str("backy-cmd", v).Str("hookType", "error").
Logger() Logger()
errCmd.RunCmd(cmdLogger, opts) _, _ = errCmd.RunCmd(cmdLogger, opts)
} }
case "success": case "success":
@ -467,7 +473,7 @@ func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
cmdLogger := opts.Logger.With(). cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "success"). Str("backy-cmd", v).Str("hookType", "success").
Logger() Logger()
successCmd.RunCmd(cmdLogger, opts) _, _ = successCmd.RunCmd(cmdLogger, opts)
} }
case "final": case "final":
for _, v := range cmd.Hooks.Final { for _, v := range cmd.Hooks.Final {
@ -476,7 +482,7 @@ func (cmd *Command) ExecuteHooks(hookType string, opts *ConfigOpts) {
cmdLogger := opts.Logger.With(). cmdLogger := opts.Logger.With().
Str("backy-cmd", v).Str("hookType", "final"). Str("backy-cmd", v).Str("hookType", "final").
Logger() Logger()
finalCmd.RunCmd(cmdLogger, opts) _, _ = finalCmd.RunCmd(cmdLogger, opts)
} }
} }
} }
@ -531,6 +537,12 @@ func logCommandOutput(command *Command, cmdOutBuf bytes.Buffer, cmdCtxLogger zer
return outputArr return outputArr
} }
func (c *Command) GetVariablesFromConf(opts *ConfigOpts) {
c.ScriptEnvFile = replaceVarInString(opts.Vars, c.ScriptEnvFile, opts.Logger)
c.Name = replaceVarInString(opts.Vars, c.Name, opts.Logger)
c.OutputFile = replaceVarInString(opts.Vars, c.OutputFile, opts.Logger)
}
// func executeUserCommands() []string { // func executeUserCommands() []string {
// } // }

View File

@ -103,6 +103,12 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
backyKoanf := opts.koanf backyKoanf := opts.koanf
if backyKoanf.Exists("variables") {
unmarshalConfigIntoStruct(backyKoanf, "variables", &opts.Vars, opts.Logger)
}
getConfigDir(opts)
opts.loadEnv() opts.loadEnv()
if backyKoanf.Bool(getNestedConfig("logging", "cmd-std-out")) { if backyKoanf.Bool(getNestedConfig("logging", "cmd-std-out")) {
@ -125,14 +131,23 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
log.Info().Str("config file", opts.ConfigFilePath).Send() log.Info().Str("config file", opts.ConfigFilePath).Send()
unmarshalConfig(backyKoanf, "commands", &opts.Cmds, opts.Logger) if err := opts.initVault(); err != nil {
log.Err(err).Send()
}
unmarshalConfigIntoStruct(backyKoanf, "commands", &opts.Cmds, opts.Logger)
getCommandEnvironments(opts) getCommandEnvironments(opts)
unmarshalConfig(backyKoanf, "hosts", &opts.Hosts, opts.Logger) unmarshalConfigIntoStruct(backyKoanf, "hosts", &opts.Hosts, opts.Logger)
resolveHostConfigs(opts) resolveHostConfigs(opts)
for k, v := range opts.Vars {
v = getExternalConfigDirectiveValue(v, opts)
opts.Vars[k] = v
}
loadCommandLists(opts, backyKoanf) loadCommandLists(opts, backyKoanf)
validateCommandLists(opts) validateCommandLists(opts)
@ -148,15 +163,11 @@ func (opts *ConfigOpts) ReadConfig() *ConfigOpts {
filterExecuteLists(opts) filterExecuteLists(opts)
if backyKoanf.Exists("notifications") { if backyKoanf.Exists("notifications") {
unmarshalConfig(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger) unmarshalConfigIntoStruct(backyKoanf, "notifications", &opts.NotificationConf, opts.Logger)
} }
opts.SetupNotify() opts.SetupNotify()
if err := opts.setupVault(); err != nil {
log.Err(err).Send()
}
return opts return opts
} }
@ -220,6 +231,7 @@ func setLoggingOptions(k *koanf.Koanf, opts *ConfigOpts) {
logFile = k.String(getLoggingKeyFromConfig("file")) logFile = k.String(getLoggingKeyFromConfig("file"))
opts.LogFilePath = logFile opts.LogFilePath = logFile
} }
opts.LogFilePath = logFile
zerolog.SetGlobalLevel(zerolog.InfoLevel) zerolog.SetGlobalLevel(zerolog.InfoLevel)
if isLoggingVerbose { if isLoggingVerbose {
@ -239,7 +251,7 @@ func setupLogger(opts *ConfigOpts) zerolog.Logger {
return zerolog.New(writers).With().Timestamp().Logger() return zerolog.New(writers).With().Timestamp().Logger()
} }
func unmarshalConfig(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) { func unmarshalConfigIntoStruct(k *koanf.Koanf, key string, target interface{}, log zerolog.Logger) {
if err := k.UnmarshalWithConf(key, target, koanf.UnmarshalConf{Tag: "yaml"}); err != nil { if err := k.UnmarshalWithConf(key, target, koanf.UnmarshalConf{Tag: "yaml"}); err != nil {
logging.ExitWithMSG(fmt.Sprintf("error unmarshaling key %s into struct: %v", key, err), 1, &log) logging.ExitWithMSG(fmt.Sprintf("error unmarshaling key %s into struct: %v", key, err), 1, &log)
} }
@ -281,16 +293,22 @@ func resolveProxyHosts(host *Host, opts *ConfigOpts) {
} }
} }
func getConfigDir(opts *ConfigOpts) {
if isRemoteURL(opts.ConfigFilePath) {
p, _ := getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p
} else {
opts.ConfigDir = path.Dir(opts.ConfigFilePath)
}
}
func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) { func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
var listConfigFiles []string var listConfigFiles []string
var u *url.URL var u *url.URL
var p string var p string
// if config file is remote, use the directory of the remote file
if isRemoteURL(opts.ConfigFilePath) { if isRemoteURL(opts.ConfigFilePath) {
p, u = getRemoteDir(opts.ConfigFilePath) p, u = getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p opts.ConfigDir = p
println(p)
// // Still use local list files if a remote config file is used, but use them last
listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()} listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()}
} else { } else {
opts.ConfigDir = path.Dir(opts.ConfigFilePath) opts.ConfigDir = path.Dir(opts.ConfigFilePath)
@ -313,7 +331,7 @@ func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
if backyKoanf.Exists("cmdLists.file") { if backyKoanf.Exists("cmdLists.file") {
loadCmdListsFile(backyKoanf, listsConfig, opts) loadCmdListsFile(backyKoanf, listsConfig, opts)
} else { } else {
unmarshalConfig(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger) unmarshalConfigIntoStruct(backyKoanf, "cmdLists", &opts.CmdConfigLists, opts.Logger)
} }
} }
} }
@ -353,7 +371,7 @@ func loadListConfigFile(filePath string, k *koanf.Koanf, opts *ConfigOpts) bool
return false return false
} }
unmarshalConfig(k, "cmdLists", &opts.CmdConfigLists, opts.Logger) unmarshalConfigIntoStruct(k, "cmdLists", &opts.CmdConfigLists, opts.Logger)
keyNotSupported("cmd-lists", "cmdLists", k, opts, true) keyNotSupported("cmd-lists", "cmdLists", k, opts, true)
opts.CmdListFile = filePath opts.CmdListFile = filePath
return true return true
@ -381,7 +399,7 @@ func loadCmdListsFile(backyKoanf *koanf.Koanf, listsConfig *koanf.Koanf, opts *C
} }
keyNotSupported("cmd-lists", "cmdLists", listsConfig, opts, true) keyNotSupported("cmd-lists", "cmdLists", listsConfig, opts, true)
unmarshalConfig(listsConfig, "cmdLists", &opts.CmdConfigLists, opts.Logger) unmarshalConfigIntoStruct(listsConfig, "cmdLists", &opts.CmdConfigLists, opts.Logger)
opts.Logger.Info().Str("using lists config file", opts.CmdListFile).Send() opts.Logger.Info().Str("using lists config file", opts.CmdListFile).Send()
} }
@ -438,7 +456,7 @@ func getLoggingKeyFromConfig(key string) string {
// return fmt.Sprintf("cmdLists.%s", list) // return fmt.Sprintf("cmdLists.%s", list)
// } // }
func (opts *ConfigOpts) setupVault() error { func (opts *ConfigOpts) initVault() error {
if !opts.koanf.Bool("vault.enabled") { if !opts.koanf.Bool("vault.enabled") {
return nil return nil
} }
@ -459,7 +477,7 @@ func (opts *ConfigOpts) setupVault() error {
token = os.Getenv("VAULT_TOKEN") token = os.Getenv("VAULT_TOKEN")
} }
if strings.TrimSpace(token) == "" { if strings.TrimSpace(token) == "" {
return fmt.Errorf("no token found, but one was required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN") return fmt.Errorf("no token found. One is required. \n\nSet the config key vault.token or the environment variable VAULT_TOKEN")
} }
client.SetToken(token) client.SetToken(token)
@ -471,6 +489,11 @@ func (opts *ConfigOpts) setupVault() error {
opts.vaultClient = client opts.vaultClient = client
for _, v := range opts.VaultKeys {
v.Name = replaceVarInString(opts.Vars, v.Key, opts.Logger)
v.MountPath = replaceVarInString(opts.Vars, v.MountPath, opts.Logger)
}
return nil return nil
} }
@ -478,7 +501,10 @@ func processCmds(opts *ConfigOpts) error {
// process commands // process commands
for cmdName, cmd := range opts.Cmds { for cmdName, cmd := range opts.Cmds {
for i, v := range cmd.Args {
v = replaceVarInString(opts.Vars, v, opts.Logger)
cmd.Args[i] = v
}
if cmd.Name == "" { if cmd.Name == "" {
cmd.Name = cmdName cmd.Name = cmdName
} }
@ -503,6 +529,10 @@ func processCmds(opts *ConfigOpts) error {
// resolve hosts // resolve hosts
if cmd.Host != nil { if cmd.Host != nil {
cmdHost := replaceVarInString(opts.Vars, *cmd.Host, opts.Logger)
if cmdHost != *cmd.Host {
cmd.Host = &cmdHost
}
host, hostFound := opts.Hosts[*cmd.Host] host, hostFound := opts.Hosts[*cmd.Host]
if hostFound { if hostFound {
cmd.RemoteHost = host cmd.RemoteHost = host
@ -562,7 +592,7 @@ func processCmds(opts *ConfigOpts) error {
if cmd.Username == "" { if cmd.Username == "" {
return fmt.Errorf("username is required for user command %s", cmd.Name) return fmt.Errorf("username is required for user command %s", cmd.Name)
} }
cmd.Username = replaceVarInString(opts.Vars, cmd.Username, opts.Logger)
err := detectOSType(cmd, opts) err := detectOSType(cmd, opts)
if err != nil { if err != nil {
opts.Logger.Info().Err(err).Str("command", cmdName).Send() opts.Logger.Info().Err(err).Str("command", cmdName).Send()
@ -688,3 +718,24 @@ func keyNotSupported(oldKey, newKey string, koanf *koanf.Koanf, opts *ConfigOpts
} }
} }
} }
func replaceVarInString(vars map[string]string, str string, logger zerolog.Logger) string {
if strings.Contains(str, "%{var:") && strings.Contains(str, "}%") {
logger.Debug().Msgf("replacing vars in string %s", str)
for k, v := range vars {
if strings.Contains(str, "%{var:"+k+"}%") {
str = strings.ReplaceAll(str, "%{var:"+k+"}%", v)
}
}
if strings.Contains(str, "%{var:") && strings.Contains(str, "}%") {
logger.Warn().Msg("could not replace all vars in string")
}
}
return str
}
func VariadicFunctionParameterTest(allowedKeys ...string) {
if contains(allowedKeys, "file") {
println("file param included")
}
}

View File

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

View File

@ -523,12 +523,19 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating file /tmp/%s: %v", uuidFile.String(), passFileErr) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating file /tmp/%s: %v", uuidFile.String(), passFileErr)
} }
passFile.Write([]byte(userNamePass)) _, err = passFile.Write([]byte(userNamePass))
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error writing to file /tmp/%s: %v", uuidFile.String(), err)
}
ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath) ArgsStr = fmt.Sprintf("cat %s | chpasswd", passFilePath)
defer passFile.Close() defer passFile.Close()
defer client.Remove(passFilePath) rmFileFunc := func() {
_ = client.Remove(passFilePath)
}
defer rmFileFunc()
// commandSession.Stdin = command.stdin // commandSession.Stdin = command.stdin
} }
if err := commandSession.Run(ArgsStr); err != nil { if err := commandSession.Run(ArgsStr); err != nil {
@ -561,7 +568,10 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating sftp client: %v", err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating sftp client: %v", err)
} }
client.MkdirAll(userSshDir) err = client.MkdirAll(userSshDir)
if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error creating directory %s: %v", userSshDir, err)
}
_, err = client.Create(fmt.Sprintf("%s/authorized_keys", userSshDir)) _, err = client.Create(fmt.Sprintf("%s/authorized_keys", userSshDir))
if err != nil { if err != nil {
return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err) return collectOutput(&cmdOutBuf, command.Name, cmdCtxLogger, command.OutputToLog), fmt.Errorf("error opening file %s/authorized_keys: %v", userSshDir, err)

View File

@ -205,6 +205,8 @@ type (
List ListConfig List ListConfig
Vars map[string]string `yaml:"variables"`
VaultKeys []*VaultKey `yaml:"keys"` VaultKeys []*VaultKey `yaml:"keys"`
koanf *koanf.Koanf koanf *koanf.Koanf
@ -239,6 +241,7 @@ type (
Notifications struct { Notifications struct {
MailConfig map[string]MailConfig `yaml:"mail,omitempty"` MailConfig map[string]MailConfig `yaml:"mail,omitempty"`
MatrixConfig map[string]MatrixStruct `yaml:"matrix,omitempty"` MatrixConfig map[string]MatrixStruct `yaml:"matrix,omitempty"`
HttpConfig map[string]HttpConfig `yaml:"http,omitempty"`
} }
CmdOutput struct { CmdOutput struct {
@ -277,6 +280,7 @@ type (
// use ints so we can use enums // use ints so we can use enums
CommandType int CommandType int
PackageOperation int PackageOperation int
AllowedExternalDirectives int
) )
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType //go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType
@ -299,3 +303,14 @@ const (
PackOpCheckVersion // checkVersion PackOpCheckVersion // checkVersion
PackOpIsInstalled // isInstalled PackOpIsInstalled // isInstalled
) )
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=AllowedExternalDirectives
const (
DefaultExternalDir AllowedExternalDirectives = iota
AllowedExternalDirectiveVault // vault
AllowedExternalDirectiveVaultFile // vault-file
AllowedExternalDirectiveAll // vault-file-env
AllowedExternalDirectiveFileEnv // file-env
AllowedExternalDirectiveFile // file
AllowedExternalDirectiveEnv // env
)

View File

@ -110,7 +110,11 @@ func injectEnvIntoSSH(envVarsToInject environmentVars, process *ssh.Session, opt
goto errEnvFile goto errEnvFile
} }
for key, val := range envMap { for key, val := range envMap {
process.Setenv(key, GetVaultKey(val, opts, log)) err = process.Setenv(key, GetVaultKey(val, opts, log))
if err != nil {
log.Error().Err(err).Send()
}
} }
} }
@ -121,7 +125,11 @@ errEnvFile:
if strings.Contains(envVal, "=") { if strings.Contains(envVal, "=") {
envVarArr := strings.Split(envVal, "=") envVarArr := strings.Split(envVal, "=")
process.Setenv(envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts)) err := process.Setenv(envVarArr[0], getExternalConfigDirectiveValue(envVarArr[1], opts))
if err != nil {
log.Error().Err(err).Send()
}
} }
} }
} }
@ -354,6 +362,7 @@ func getExternalConfigDirectiveValue(key string, opts *ConfigOpts) string {
if !(strings.HasPrefix(key, externDirectiveStart) && strings.HasSuffix(key, externDirectiveEnd)) { if !(strings.HasPrefix(key, externDirectiveStart) && strings.HasSuffix(key, externDirectiveEnd)) {
return key return key
} }
key = replaceVarInString(opts.Vars, key, opts.Logger)
opts.Logger.Debug().Str("expanding external key", key).Send() opts.Logger.Debug().Str("expanding external key", key).Send()
if strings.HasPrefix(key, envExternDirectiveStart) { if strings.HasPrefix(key, envExternDirectiveStart) {
key = strings.TrimPrefix(key, envExternDirectiveStart) key = strings.TrimPrefix(key, envExternDirectiveStart)
@ -385,7 +394,6 @@ func getExternalConfigDirectiveValue(key string, opts *ConfigOpts) string {
key = strings.TrimSuffix(key, externDirectiveEnd) key = strings.TrimSuffix(key, externDirectiveEnd)
key = GetVaultKey(key, opts, opts.Logger) key = GetVaultKey(key, opts, opts.Logger)
} }
println(key)
return key return key
} }
@ -410,7 +418,6 @@ func getVaultSecret(vaultClient *vault.Client, key *VaultKey) (string, error) {
} }
value, ok := secret.Data[key.Key].(string) value, ok := secret.Data[key.Key].(string)
println(value)
if !ok { if !ok {
return "", fmt.Errorf("value type assertion failed for vault key %s: %T %#v", key.Name, secret.Data[key.Name], secret.Data[key.Name]) return "", fmt.Errorf("value type assertion failed for vault key %s: %T %#v", key.Name, secret.Data[key.Name], secret.Data[key.Name])
} }

View File

@ -116,7 +116,7 @@ func (c *Cache) Set(source, hash string, data []byte, dataType string) (CacheDat
path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, sourceHash)) path := filepath.Join(c.dir, fmt.Sprintf("%s-%s", fileName, sourceHash))
if _, err := os.Stat(path); os.IsNotExist(err) { if _, err := os.Stat(path); os.IsNotExist(err) {
os.MkdirAll(c.dir, 0700) _ = os.MkdirAll(c.dir, 0700)
} }
err := os.WriteFile(path, data, 0644) err := os.WriteFile(path, data, 0644)
@ -171,7 +171,7 @@ func (cf *CachedFetcher) Hash(data []byte) string {
func LoadMetadataFromFile(filePath string) ([]*CacheData, error) { func LoadMetadataFromFile(filePath string) ([]*CacheData, error) {
if _, err := os.Stat(filePath); os.IsNotExist(err) { if _, err := os.Stat(filePath); os.IsNotExist(err) {
// Create the file if it does not exist // Create the file if it does not exist
os.MkdirAll(path.Dir(filePath), 0700) _ = os.MkdirAll(path.Dir(filePath), 0700)
emptyData := []byte("[]") emptyData := []byte("[]")
err := os.WriteFile(filePath, emptyData, 0644) err := os.WriteFile(filePath, emptyData, 0644)
if err != nil { if err != nil {

View File

@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
set -eou pipefail set -eou pipefail
go mod tidy
go generate ./... go generate ./...
CURRENT_TAG="$(go run backy.go version -V)" CURRENT_TAG="$(go run backy.go version -V)"
goreleaser -f .goreleaser/github.yml check goreleaser -f .goreleaser/github.yml check