From 9c19c402c034475f3084932aa830b5e8fec94525 Mon Sep 17 00:00:00 2001 From: VetheonGames Date: Wed, 27 Mar 2024 03:26:20 -0600 Subject: [PATCH] Just a few more functions to rough in before we begin iterating. I'm going to bed now --- cmd/config.go | 126 ++++++++++++++-------------- cmd/root.go | 39 +++++---- config/config.go | 177 ++++++++++++++++++++++++++++++++++++++++ config/default.go | 64 +++++++++++++++ go.mod | 29 +++++++ go.sum | 52 ++++++++++++ pkg/backup/interface.go | 51 ++++++++++++ pkg/backup/s3.go | 54 ++++++++++++ pkg/backup/smb.go | 115 ++++++++++++++++++++++++++ pkg/logger/logger.go | 45 ++++++++++ pkg/utils/utils.go | 4 + 11 files changed, 672 insertions(+), 84 deletions(-) create mode 100644 go.sum diff --git a/cmd/config.go b/cmd/config.go index d5dcb40..5e7ee17 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -1,82 +1,76 @@ package cmd import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" + "fmt" + "os" + "os/exec" + "pixelridgesoftworks.com/BackGo/config" + "reflect" + "strconv" + "strings" ) -const configDir = "/etc/PixelRidge/BackupGo" -const configFile = "config.json" +const configFilePath = "/etc/PixelRidge/BackGo/config.json" -// Config represents the application's configuration -type Config struct { - S3ConnectionInfo string `json:"s3_connection_info"` - // Add other configuration options here, all as strings or booleans -} - -// LoadConfig loads the configuration from the JSON file -func LoadConfig() (*Config, error) { - configPath := filepath.Join(configDir, configFile) - file, err := os.Open(configPath) +// OpenConfigInNano opens the configuration file in the nano editor. +func OpenConfigInNano() error { + cmd := exec.Command("nano", configFilePath) + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + err := cmd.Run() if err != nil { - return nil, err + return fmt.Errorf("error opening config in nano: %v", err) } - defer file.Close() - - config := &Config{} - decoder := json.NewDecoder(file) - if err := decoder.Decode(config); err != nil { - return nil, err - } - - return config, nil -} - -// SaveConfig saves the modified configuration back to the JSON file -func SaveConfig(config *Config) error { - configPath := filepath.Join(configDir, configFile) - file, err := os.Create(configPath) - if err != nil { - return err - } - defer file.Close() - - encoder := json.NewEncoder(file) - if err := encoder.Encode(config); err != nil { - return err - } - return nil } // EditConfigOption allows editing a specific configuration option from the CLI func EditConfigOption(optionName, newValue string) error { - config, err := LoadConfig() - if err != nil { - return err - } + // Load the current configuration from file + cfg, err := config.LoadConfigFromFile() + if err != nil { + return fmt.Errorf("loading config: %v", err) + } - // Reflect or a simple switch can be used to match the optionName to Config fields - switch optionName { - case "s3_connection_info": - config.S3ConnectionInfo = newValue - // Add cases for other options here - default: - return fmt.Errorf("unknown configuration option: %s", optionName) - } + // Use reflection to find and set the field in the Config struct + rConfig := reflect.ValueOf(cfg).Elem() + field := rConfig.FieldByNameFunc(func(s string) bool { + return strings.EqualFold(s, optionName) + }) - return SaveConfig(config) -} - -// OpenConfigInNano opens the configuration file in the nano editor -func OpenConfigInNano() error { - configPath := filepath.Join(configDir, configFile) - cmd := exec.Command("nano", configPath) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + if !field.IsValid() { + return fmt.Errorf("unknown configuration option: %s", optionName) + } + + // Ensure the field can be set + if !field.CanSet() { + return fmt.Errorf("cannot set configuration option: %s", optionName) + } + + // Convert and set the field value based on its kind + switch field.Kind() { + case reflect.String: + field.SetString(newValue) + case reflect.Bool: + boolVal, err := strconv.ParseBool(newValue) + if err != nil { + return fmt.Errorf("invalid value for %s: %v", optionName, err) + } + field.SetBool(boolVal) + case reflect.Int: + intVal, err := strconv.Atoi(newValue) + if err != nil { + return fmt.Errorf("invalid value for %s: %v", optionName, err) + } + // Additional checks can be implemented here for specific int fields like LogLevel + field.SetInt(int64(intVal)) + default: + return fmt.Errorf("unsupported configuration type for: %s", optionName) + } + + // Save the updated configuration back to the file + if err := config.SaveConfigToFile(cfg); err != nil { + return fmt.Errorf("saving config: %v", err) + } + + return nil } diff --git a/cmd/root.go b/cmd/root.go index 0b7bfd1..07c3256 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -76,24 +76,27 @@ func ExecuteCommand(rootCmd *Command, args []string) error { func init() { configCmd := NewCommand("config", "Configuration management", nil) - editCmd := NewCommand("edit", "Edit a configuration option", func(cmd *Command, args []string) { - configOption := cmd.FlagSet.Lookup("config-option").Value.String() - if configOption == "" { - fmt.Println("No config option specified") - return - } - newValue := cmd.FlagSet.Arg(0) // Assumes the new value is the first argument after flags - if newValue == "" { - fmt.Println("No new value specified for the config option") - return - } - if err := EditConfigOption(configOption, newValue); err != nil { - fmt.Println("Error editing config option:", err) - return - } - fmt.Println("Config option updated successfully") - }) - editCmd.FlagSet.String("config-option", "", "The configuration option to edit") + editCmd := NewCommand("edit", "Edit a configuration option", func(cmd *Command, args []string) { + // Ensure the flag is parsed before trying to access it + cmd.FlagSet.Parse(args) + configOption := cmd.FlagSet.Lookup("config-option").Value.String() + if configOption == "" { + fmt.Println("No config option specified") + return + } + // The new value should be the first argument after the flags + if len(cmd.FlagSet.Args()) == 0 { + fmt.Println("No new value specified for the config option") + return + } + newValue := cmd.FlagSet.Arg(0) + if err := EditConfigOption(configOption, newValue); err != nil { + fmt.Println("Error editing config option:", err) + return + } + fmt.Println("Config option updated successfully") + }) + editCmd.FlagSet.String("config-option", "", "The configuration option to edit") openCmd := NewCommand("open", "Open the configuration in nano", func(cmd *Command, args []string) { if err := OpenConfigInNano(); err != nil { diff --git a/config/config.go b/config/config.go index e69de29..26f1e06 100644 --- a/config/config.go +++ b/config/config.go @@ -0,0 +1,177 @@ +package config + +import ( + "encoding/json" + "fmt" + "log" + "os" + "reflect" + "strconv" + "strings" +) + +const configFilePath = "/etc/PixelRidge/BackGo/config.json" + +type LogLevel int + +const ( + LogLevelDebug LogLevel = iota + LogLevelInfo + LogLevelWarning + LogLevelError +) + +type Config struct { + LogLevel LogLevel + S3Enabled bool + S3ConnectionAddress string + S3AuthorizationInfo string + S3Region string + S3BucketInfo string + SMBEnabled bool + SMBConnectionAddress string + SMBAuthorizationInfo string + B2Enabled bool + B2ConnectionAddress string + B2AuthorizationInfo string + B2BucketInfo string + LocalStorageEnabled bool + LocalStorageDirectory string + APIServerBindAddress string + APIServerPort string + TransportServerBindAddress string + TransportServerPort string + Nodes []string +} + +func LoadConfig() *Config { + return &Config{ + LogLevel: getEnvAsLogLevel("LOG_LEVEL", LogLevelInfo), + S3Enabled: getEnvAsBool("S3_ENABLED", false), + S3ConnectionAddress: os.Getenv("S3_CONNECTION_ADDRESS"), + S3AuthorizationInfo: os.Getenv("S3_AUTHORIZATION_INFO"), + S3Region: os.Getenv("S3_REGION"), + S3BucketInfo: os.Getenv("S3_BUCKET_INFO"), + SMBEnabled: getEnvAsBool("SMB_ENABLED", false), + SMBConnectionAddress: os.Getenv("SMB_CONNECTION_ADDRESS"), + SMBAuthorizationInfo: os.Getenv("SMB_AUTHORIZATION_INFO"), + B2Enabled: getEnvAsBool("BACKBLAZE_B2_ENABLED", false), + B2ConnectionAddress: os.Getenv("B2_CONNECTION_ADDRESS"), + B2AuthorizationInfo: os.Getenv("B2_AUTHORIZATION_INFO"), + B2BucketInfo: os.Getenv("BACKBLAZE_B2_BUCKET_INFO"), + LocalStorageEnabled: getEnvAsBool("LOCAL_STORAGE_ENABLED", false), + LocalStorageDirectory: os.Getenv("LOCAL_STORAGE_DIRECTORY"), + APIServerBindAddress: os.Getenv("API_SERVER_BIND_ADDRESS"), + APIServerPort: os.Getenv("API_SERVER_PORT"), + TransportServerBindAddress: os.Getenv("TRANSPORT_SERVER_BIND_ADDRESS"), + TransportServerPort: os.Getenv("TRANSPORT_SERVER_PORT"), + Nodes: getEnvAsSlice("NODES", ",", []string{}), + } +} + +func getEnvAsBool(name string, defaultVal bool) bool { + valStr := os.Getenv(name) + if valStr == "" { + return defaultVal + } + val, err := strconv.ParseBool(valStr) + if err != nil { + log.Printf("Error parsing boolean for %s: %v\n", name, err) + return defaultVal + } + return val +} + +func getEnvAsSlice(name, sep string, defaultVal []string) []string { + valStr := os.Getenv(name) + if valStr == "" { + return defaultVal + } + return strings.Split(valStr, sep) +} + +func getEnvAsLogLevel(name string, defaultVal LogLevel) LogLevel { + valStr := os.Getenv(name) + if valStr == "" { + return defaultVal + } + if strings.EqualFold(valStr, "debug") { + return LogLevelDebug + } else if strings.EqualFold(valStr, "info") { + return LogLevelInfo + } else if strings.EqualFold(valStr, "warning") { + return LogLevelWarning + } else if strings.EqualFold(valStr, "error") { + return LogLevelError + } + log.Printf("Unknown LOG_LEVEL '%s', defaulting to INFO.\n", valStr) + return LogLevelInfo +} + +func UpdateConfig(key, value string) error { + config, err := LoadConfigFromFile() + if err != nil { + return fmt.Errorf("loading config: %v", err) + } + + rConfig := reflect.ValueOf(config).Elem() + field := rConfig.FieldByNameFunc(func(s string) bool { + return strings.EqualFold(s, key) + }) + + if !field.IsValid() { + return fmt.Errorf("unknown configuration key: %s", key) + } + + if !field.CanSet() { + return fmt.Errorf("cannot set configuration key: %s", key) + } + + switch field.Kind() { + case reflect.String: + field.SetString(value) + case reflect.Bool: + boolVal, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("parsing boolean value for %s: %v", key, err) + } + field.SetBool(boolVal) + case reflect.Int: + intVal, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("parsing integer value for %s: %v", key, err) + } + field.SetInt(int64(intVal)) + default: + return fmt.Errorf("unsupported configuration type for: %s", key) + } + + return SaveConfigToFile(config) +} + +func LoadConfigFromFile() (*Config, error) { + file, err := os.ReadFile(configFilePath) + if err != nil { + return nil, err + } + + var config Config + if err := json.Unmarshal(file, &config); err != nil { + return nil, err + } + + return &config, nil +} + +func SaveConfigToFile(config *Config) error { + data, err := json.MarshalIndent(config, "", " ") + if err != nil { + return err + } + + return os.WriteFile(configFilePath, data, 0644) +} + +func ReloadConfig() { + fmt.Println("Configuration reloaded") +} diff --git a/config/default.go b/config/default.go index e69de29..24128a9 100644 --- a/config/default.go +++ b/config/default.go @@ -0,0 +1,64 @@ +package config + +import ( + "fmt" + "os" + "time" +) + +// SetDefaultConfig sets the default configuration by writing to environment variables +// and create a default configuration file. +func SetDefaultConfig() error { + // Define default values with explanations + defaults := map[string]string{ + // Logging level: DEBUG, INFO, WARNING, ERROR + "LOG_LEVEL": "INFO", + + // API server configuration + "API_SERVER_BIND_ADDRESS": "0.0.0.0", // IP address to bind the API server + "API_SERVER_PORT": "6678", // Port for the API server + + // Transport server configuration for internal communication + "TRANSPORT_SERVER_BIND_ADDRESS": "0.0.0.0", // IP address for the transport server + "TRANSPORT_SERVER_PORT": "6679", // Port for the transport server + + // S3 storage configuration + "S3_ENABLED": "false", // Enable S3 storage (true/false) + "S3_CONNECTION_ADDRESS": "", // S3 connection address in the format {IP}:{PORT} + "S3_AUTHORIZATION_INFO": "", // S3 authorization information (e.g., access key and secret key) + "S3_REGION": "us-east-1", // AWS region for S3 + "S3_BUCKET_INFO": "", // S3 bucket name + + // SMB storage configuration + "SMB_ENABLED": "false", // Enable SMB storage (true/false) + "SMB_CONNECTION_ADDRESS": "", // SMB connection address in the format {IP}:{PORT} + "SMB_AUTHORIZATION_INFO": "", // SMB authorization information (e.g., username and password) + + // BackBlaze B2 storage configuration + "B2_ENABLED": "false", // Enable BackBlaze B2 storage (true/false) + "B2_CONNECTION_ADDRESS":"", // BackBlaze B2 connection address in the format {IP}:{PORT} + "B2_AUTHORIZATION_INFO":"", // BackBlaze B2 authorization information + "B2_BUCKET_INFO": "", // BackBlaze B2 bucket name + + // Local storage configuration + "LOCAL_STORAGE_ENABLED": "true", // Enable local storage (true/false) + "LOCAL_STORAGE_DIRECTORY": fmt.Sprintf("/etc/PixelRidge/BackGo/local-data-storage/%s-%d", os.Getenv("HOSTNAME"), time.Now().Unix()), // Path for local storage directory + + // Nodes configuration for cluster setup + "NODES": "", // Comma-separated list of node addresses in the cluster + } + + // Write defaults to environment variables + for key, value := range defaults { + if err := os.Setenv(key, value); err != nil { + return fmt.Errorf("failed to set default config for %s: %v", key, err) + } + } + + // write these defaults to a file for persistence + // write to a JSON or YAML file + + return nil +} + +// include a function here to write the configuration to a file diff --git a/go.mod b/go.mod index 1e031cc..fcaa720 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,32 @@ module pixelridgesoftworks.com/BackGo go 1.22.0 + +require ( + github.com/aws/aws-sdk-go-v2/config v1.27.9 + github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0 + github.com/hirochachacha/go-smb2 v1.1.0 + golang.org/x/crypto v0.21.0 +) + +require ( + github.com/aws/aws-sdk-go-v2 v1.26.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.9 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 // indirect + github.com/aws/smithy-go v1.20.1 // indirect + github.com/geoffgarside/ber v1.1.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f123367 --- /dev/null +++ b/go.sum @@ -0,0 +1,52 @@ +github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= +github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1/go.mod h1:sxpLb+nZk7tIfCWChfd+h4QwHNUR57d8hA1cleTkjJo= +github.com/aws/aws-sdk-go-v2/config v1.27.9 h1:gRx/NwpNEFSk+yQlgmk1bmxxvQ5TyJ76CWXs9XScTqg= +github.com/aws/aws-sdk-go-v2/config v1.27.9/go.mod h1:dK1FQfpwpql83kbD873E9vz4FyAxuJtR22wzoXn3qq0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMNQVNtNdUqf6cItao= +github.com/aws/aws-sdk-go-v2/credentials v1.17.9/go.mod h1:446YhIdmSV0Jf/SLafGZalQo+xr2iw7/fzXGDPTU1yQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 h1:af5YzcLf80tv4Em4jWVD75lpnOHSBkPUZxZfGkrI3HI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.4 h1:SIkD6T4zGQ+1YIit22wi37CGNkrE7mXV1vNA5VpI3TI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.4/go.mod h1:XfeqbsG0HNedNs0GT+ju4Bs+pFAwsrlzcRdMvdNVf5s= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.6 h1:NkHCgg0Ck86c5PTOzBZ0JRccI51suJDg5lgFtxBu1ek= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.6/go.mod h1:mjTpxjC8v4SeINTngrnKFgm2QUi+Jm+etTbCxh8W4uU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 h1:b+E7zIUHMmcB4Dckjpkapoy47W6C9QBv/zoUP+Hn8Kc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6/go.mod h1:S2fNV0rxrP78NhPbCZeQgY8H9jdDMeGtwcfZIRxzBqU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.4 h1:uDj2K47EM1reAYU9jVlQ1M5YENI1u6a/TxJpf6AeOLA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.4/go.mod h1:XKCODf4RKHppc96c2EZBGV/oCUC7OClxAo2MEyg4pIk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0 h1:r3o2YsgW9zRcIP3Q0WCmttFVhTuugeKIvT5z9xDspc0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0/go.mod h1:w2E4f8PUfNtyjfL6Iu+mWI96FGttE03z3UdNcUEC4tA= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 h1:mnbuWHOcM70/OFUlZZ5rcdfA8PflGXXiefU/O+1S3+8= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.3/go.mod h1:5HFu51Elk+4oRBZVxmHrSds5jFXmFj8C3w7DVF2gnrs= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 h1:uLq0BKatTmDzWa/Nu4WO0M1AaQDaPpwTKAeByEc6WFM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3/go.mod h1:b+qdhjnxj8GSR6t5YfphOffeoQSQ1KmpoVVuBn+PWxs= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3FajfLxqM5+tepvVXmxg= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0= +github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= +github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= +github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= +github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= +github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/pkg/backup/interface.go b/pkg/backup/interface.go index e69de29..3ff231e 100644 --- a/pkg/backup/interface.go +++ b/pkg/backup/interface.go @@ -0,0 +1,51 @@ +package backup + +import ( + "crypto/tls" + "fmt" + "log" + "net/http" + "golang.org/x/crypto/acme/autocert" +) + +// GenerateCertForDomain generates an SSL certificate for a domain using Let's Encrypt +func GenerateCertForDomain(domain string) { + certManager := autocert.Manager{ + Prompt: autocert.AcceptTOS, + Cache: autocert.DirCache("certs"), // Stores certificates in ./certs + HostPolicy: autocert.HostWhitelist(domain), // Replace with your domain + } + + server := &http.Server{ + Addr: ":https", // Default HTTPS port + TLSConfig: &tls.Config{ + GetCertificate: certManager.GetCertificate, + }, + } + + // Redirect HTTP to HTTPS + go http.ListenAndServe(":http", certManager.HTTPHandler(nil)) + + log.Printf("Starting HTTPS server for %s\n", domain) + log.Fatal(server.ListenAndServeTLS("", "")) // Keys are provided by Let's Encrypt +} + +// HandleJoinRequest handles a join request from a node +func HandleJoinRequest(w http.ResponseWriter, r *http.Request) { + // TODO: Implement join request logic here + fmt.Fprintf(w, "Join request received\n") +} + +// SendCommandToNode sends a CLI command to a specified node in the cluster +func SendCommandToNode(nodeAddress, command string) { + // TODO: Implement command sending logic here + fmt.Printf("Sending command '%s' to node %s\n", command, nodeAddress) +} + +// StartMasterServer starts the master API server that listens for join requests and other commands +func StartMasterServer(domain string) { + http.HandleFunc("/join", HandleJoinRequest) // Endpoint for nodes to join the cluster + + // Generate SSL certificate and start HTTPS server + GenerateCertForDomain(domain) +} diff --git a/pkg/backup/s3.go b/pkg/backup/s3.go index e69de29..2741442 100644 --- a/pkg/backup/s3.go +++ b/pkg/backup/s3.go @@ -0,0 +1,54 @@ +package backup + +import ( + "context" + "fmt" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +// S3Client wraps the AWS S3 client +type S3Client struct { + Client *s3.Client + Bucket string +} + +// NewS3Client initializes and returns a new S3 client +func NewS3Client(bucket string) (*S3Client, error) { + cfg, err := config.LoadDefaultConfig(context.TODO(), + config.WithRegion("us-west-2"), // Specify your region + ) + if err != nil { + return nil, fmt.Errorf("unable to load SDK config, %w", err) + } + + s3Client := s3.NewFromConfig(cfg) + return &S3Client{ + Client: s3Client, + Bucket: bucket, + }, nil +} + +// UploadFile uploads a file to the specified S3 bucket +func (c *S3Client) UploadFile(key string, filePath string) error { + // Implementation for uploading file to S3 + // need to open the file and use PutObject API call + return nil +} + +// DeleteFile deletes a file from the specified S3 bucket +func (c *S3Client) DeleteFile(key string) error { + // Implementation for deleting file from S3 using DeleteObject API call + return nil +} + +// ListFiles lists all files (backups) in the specified S3 bucket +func (c *S3Client) ListFiles() ([]string, error) { + // Implementation for listing files in S3 bucket using ListObjectsV2 API call + return nil, nil +} + +// SetBucket sets the S3 bucket for the client +func (c *S3Client) SetBucket(bucket string) { + c.Bucket = bucket +} diff --git a/pkg/backup/smb.go b/pkg/backup/smb.go index e69de29..2a12f44 100644 --- a/pkg/backup/smb.go +++ b/pkg/backup/smb.go @@ -0,0 +1,115 @@ +package backup + +import ( + "fmt" + "io" + "net" + "os" + + "github.com/hirochachacha/go-smb2" +) + +// SMBClient wraps the SMB client and session +type SMBClient struct { + Share string + Session *smb2.Session +} + +// NewSMBClient initializes and returns a new SMB client +func NewSMBClient(server, user, pass, domain, share string) (*SMBClient, error) { + conn, err := net.Dial("tcp", server) + if err != nil { + return nil, fmt.Errorf("failed to dial: %v", err) + } + defer conn.Close() + + d := &smb2.Dialer{ + Initiator: &smb2.NTLMInitiator{ + User: user, + Password: pass, + Domain: domain, + }, + } + + session, err := d.Dial(conn) + if err != nil { + return nil, fmt.Errorf("failed to start SMB session: %v", err) + } + + return &SMBClient{ + Share: share, + Session: session, + }, nil +} + +// UploadFile uploads a file to the specified SMB share +func (c *SMBClient) UploadFile(remotePath, localPath string) error { + fs, err := c.Session.Mount(c.Share) + if err != nil { + return fmt.Errorf("failed to mount share: %v", err) + } + defer fs.Umount() + + localFile, err := os.Open(localPath) + if err != nil { + return fmt.Errorf("failed to open local file: %v", err) + } + defer localFile.Close() + + remoteFile, err := fs.Create(remotePath) + if err != nil { + return fmt.Errorf("failed to create remote file: %v", err) + } + defer remoteFile.Close() + + _, err = io.Copy(remoteFile, localFile) + if err != nil { + return fmt.Errorf("failed to write to remote file: %v", err) + } + + return nil +} + +// DeleteFile deletes a file from the specified SMB share +func (c *SMBClient) DeleteFile(remotePath string) error { + fs, err := c.Session.Mount(c.Share) + if err != nil { + return fmt.Errorf("failed to mount share: %v", err) + } + defer fs.Umount() + + err = fs.Remove(remotePath) + if err != nil { + return fmt.Errorf("failed to delete remote file: %v", err) + } + + return nil +} + +// ListFiles lists all files (backups) in the specified SMB share +func (c *SMBClient) ListFiles(remoteDir string) ([]string, error) { + fs, err := c.Session.Mount(c.Share) + if err != nil { + return nil, fmt.Errorf("failed to mount share: %v", err) + } + defer fs.Umount() + + entries, err := fs.ReadDir(remoteDir) + if err != nil { + return nil, fmt.Errorf("failed to read directory: %v", err) + } + + var files []string + for _, entry := range entries { + if !entry.IsDir() { + files = append(files, entry.Name()) + } + } + + return files, nil +} + +// SetShare sets the SMB share for the client +func (c *SMBClient) SetShare(share string) { + c.Share = share +} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index e69de29..dea91c7 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -0,0 +1,45 @@ +package logger + +import ( + "io" + "log" + "os" +) + +// Logger holds the log package's Logger instance. +type Logger struct { + *log.Logger +} + +// NewLogger creates a new Logger instance. By default, it logs to stdout. +func NewLogger() *Logger { + return &Logger{ + Logger: log.New(os.Stdout, "", log.LstdFlags), + } +} + +// LogToFile configures the logger to log to a specified file. +func (l *Logger) LogToFile(filePath string) error { + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + return err + } + l.SetOutput(file) + return nil +} + +// LogToStdout configures the logger to log to stdout. +func (l *Logger) LogToStdout() { + l.SetOutput(os.Stdout) +} + +// LogToBoth configures the logger to log to both a file and stdout. +func (l *Logger) LogToBoth(filePath string) error { + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + return err + } + multi := io.MultiWriter(file, os.Stdout) + l.SetOutput(multi) + return nil +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index e69de29..49b15e9 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -0,0 +1,4 @@ +// Package utils contains utility functions for the application. +package utils + +// This file serves as a placeholder for future utility methods.