Holy moly a lot was done in this push

This commit is contained in:
VetheonGames 2024-03-28 00:48:22 -06:00
parent 9c19c402c0
commit f9a5542ca2
8 changed files with 381 additions and 53 deletions

78
cmd/interfacecli.go Normal file
View File

@ -0,0 +1,78 @@
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"pixelridgesoftworks.com/BackGo/pkg/backup"
)
// InterfaceCLI manages commands related to node interface operations
type InterfaceCLI struct {
NodeAddress string
DBManager *backup.DBManager // Assuming DBManager is part of the backup package
}
// NewInterfaceCLI creates a new InterfaceCLI instance
func NewInterfaceCLI(nodeAddress string, dbManager *backup.DBManager) *InterfaceCLI {
return &InterfaceCLI{
NodeAddress: nodeAddress,
DBManager: dbManager,
}
}
// TriggerCommand sends a trigger command to a node
func (cli *InterfaceCLI) TriggerCommand(action, value string) error {
return cli.sendCommandToNode("trigger", action, value)
}
// GetInfo sends a get_info command to a node
func (cli *InterfaceCLI) GetInfo(value string) error {
return cli.sendCommandToNode("get_info", cli.NodeAddress, value)
}
// AddNode handles adding a new node on the master node
func (cli *InterfaceCLI) AddNode(hostname, token string) error {
// Verify the hostname resolves to an expected IP before adding
// This might involve additional logic to determine what the expected IP is
expectedIP := "" // Placeholder for expected IP determination logic
if !cli.DBManager.VerifyHostname(hostname, expectedIP) {
return fmt.Errorf("hostname %s does not resolve to the expected IP %s", hostname, expectedIP)
}
// Create a token for the new node
if err := cli.DBManager.CreateToken(hostname, token); err != nil {
return fmt.Errorf("failed to create token for hostname %s: %v", hostname, err)
}
fmt.Printf("Node %s added successfully with token %s\n", hostname, token)
return nil
}
// GenCert generates a certificate for a node
func (cli *InterfaceCLI) GenCert(nodeName string) error {
return cli.sendCommandToNode("gencert", nodeName, "")
}
// sendCommandToNode sends a command to the specified node
func (cli *InterfaceCLI) sendCommandToNode(action, node, value string) error {
payload := backup.CommandPayload{
Action: action,
Node: node,
Value: value,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("error marshaling command payload: %v", err)
}
resp, err := http.Post(fmt.Sprintf("http://%s/api/command", cli.NodeAddress), "application/json", bytes.NewReader(payloadBytes))
if err != nil {
return fmt.Errorf("error sending command to node %s: %v", node, err)
}
defer resp.Body.Close()
fmt.Printf("Command '%s' sent to node %s successfully\n", action, node)
return nil
}

View File

@ -3,9 +3,10 @@ package cmd
import (
"flag"
"fmt"
"pixelridgesoftworks.com/BackGo/pkg/backup"
"pixelridgesoftworks.com/BackGo/config"
)
// Command represents a CLI command.
type Command struct {
Name string
Description string
@ -14,10 +15,75 @@ type Command struct {
Execute func(cmd *Command, args []string)
}
// RootCmd is now defined at the package level, making it accessible from other packages.
var appConfig = config.LoadConfig()
var dbManager *backup.DBManager
var RootCmd = NewCommand("backup", "Main entry point for backup utility", nil)
// NewCommand creates a new command instance.
func init() {
// Assuming NewDBManager is correctly implemented in the backup package and accepts *config.Config
dbManager = backup.NewDBManager(appConfig)
remoteCmd := NewCommand("interface", "Remote node management", nil)
triggerCmd := NewCommand("trigger", "Trigger an action on a node", handleTrigger)
getInfoCmd := NewCommand("getinfo", "Get information from a node", handleGetInfo)
addNodeCmd := NewCommand("addnode", "Add a new node to the cluster", handleAddNode)
genCertCmd := NewCommand("gencert", "Generate a certificate for a node", handleGenCert)
remoteCmd.AddSubCommand(triggerCmd)
remoteCmd.AddSubCommand(getInfoCmd)
remoteCmd.AddSubCommand(addNodeCmd)
remoteCmd.AddSubCommand(genCertCmd)
RootCmd.AddSubCommand(remoteCmd)
}
func handleTrigger(cmd *Command, args []string) {
nodeFlag := cmd.FlagSet.String("node", "", "Specify the node hostname")
actionFlag := cmd.FlagSet.String("action", "", "Specify the action to trigger")
cmd.FlagSet.Parse(args)
if *nodeFlag == "" || *actionFlag == "" {
fmt.Println("Both node hostname and action are required")
return
}
// Since backup.NewInterfaceCLI is undefined, this part is commented out
// Assuming you will implement or correct it later
// interfaceCLI := backup.NewInterfaceCLI(*nodeFlag, dbManager)
// if err := interfaceCLI.TriggerCommand(*actionFlag, ""); err != nil {
// fmt.Printf("Error triggering command on node %s: %v\n", *nodeFlag, err)
// return
// }
// fmt.Printf("Command '%s' triggered successfully on node %s\n", *actionFlag, *nodeFlag)
}
func handleGetInfo(cmd *Command, args []string) {
// Implementation similar to handleTrigger, tailored for get_info
}
func handleAddNode(cmd *Command, args []string) {
nodeName := cmd.FlagSet.String("node", "", "Specify the node name")
cmd.FlagSet.Parse(args)
if *nodeName == "" {
fmt.Println("Node name is required")
return
}
token := "some_generated_token" // Replace with actual token generation logic
if err := dbManager.CreateToken(*nodeName, token); err != nil {
fmt.Printf("Error adding new node %s: %v\n", *nodeName, err)
return
}
fmt.Printf("New node %s added successfully with token %s\n", *nodeName, token)
}
func handleGenCert(cmd *Command, args []string) {
// Implementation for generating a certificate for a node
}
func NewCommand(name, description string, execute func(cmd *Command, args []string)) *Command {
return &Command{
Name: name,
@ -28,18 +94,15 @@ func NewCommand(name, description string, execute func(cmd *Command, args []stri
}
}
// AddSubCommand adds a subcommand to a command.
func (c *Command) AddSubCommand(subCmd *Command) {
c.SubCommands[subCmd.Name] = subCmd
}
// FindSubCommand looks for a subcommand by name.
func (c *Command) FindSubCommand(name string) (*Command, bool) {
subCmd, found := c.SubCommands[name]
return subCmd, found
}
// ExecuteCommand executes the command with the provided arguments.
func ExecuteCommand(rootCmd *Command, args []string) error {
if len(args) < 1 {
fmt.Println("No command provided")
@ -55,58 +118,17 @@ func ExecuteCommand(rootCmd *Command, args []string) error {
return nil
}
// Look for sub-subcommands if applicable.
if len(subArgs) > 0 {
subCmdName := subArgs[0]
subCmd, found := cmd.FindSubCommand(subCmdName)
if found {
// Parse flags for sub-subcommands.
subCmd.FlagSet.Parse(subArgs[1:])
subCmd.Execute(subCmd, subCmd.FlagSet.Args())
return nil
}
}
// Parse flags for direct subcommands.
cmd.FlagSet.Parse(subArgs)
cmd.Execute(cmd, cmd.FlagSet.Args())
return nil
}
func init() {
configCmd := NewCommand("config", "Configuration management", nil)
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 {
fmt.Println("Error opening config in nano:", err)
return
}
fmt.Println("Configuration opened in nano")
})
configCmd.AddSubCommand(editCmd)
configCmd.AddSubCommand(openCmd)
RootCmd.AddSubCommand(configCmd)
}

View File

@ -23,6 +23,11 @@ const (
type Config struct {
LogLevel LogLevel
MySQLUser string
MySQLPassword string
MySQLHost string
MySQLPort string
MySQLDBName string
S3Enabled bool
S3ConnectionAddress string
S3AuthorizationInfo string
@ -47,6 +52,11 @@ type Config struct {
func LoadConfig() *Config {
return &Config{
LogLevel: getEnvAsLogLevel("LOG_LEVEL", LogLevelInfo),
MySQLUser: os.Getenv("MYSQL_USER"),
MySQLPassword: os.Getenv("MYSQL_PASSWORD"),
MySQLHost: os.Getenv("MYSQL_HOST"),
MySQLPort: os.Getenv("MYSQL_PORT"),
MySQLDBName: os.Getenv("MYSQL_DB_NAME"),
S3Enabled: getEnvAsBool("S3_ENABLED", false),
S3ConnectionAddress: os.Getenv("S3_CONNECTION_ADDRESS"),
S3AuthorizationInfo: os.Getenv("S3_AUTHORIZATION_INFO"),

2
go.mod
View File

@ -5,11 +5,13 @@ 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/go-sql-driver/mysql v1.8.1
github.com/hirochachacha/go-smb2 v1.1.0
golang.org/x/crypto v0.21.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
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

4
go.sum
View File

@ -1,3 +1,5 @@
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.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=
@ -36,6 +38,8 @@ 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/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
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=

View File

@ -0,0 +1,76 @@
package backup
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
"pixelridgesoftworks.com/BackGo/config"
)
type DBManager struct {
DB *sql.DB
}
// NewDBManager creates a new DBManager instance using the provided configuration
func NewDBManager(cfg *config.Config) *DBManager {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true",
cfg.MySQLUser, cfg.MySQLPassword, cfg.MySQLHost, cfg.MySQLPort, cfg.MySQLDBName)
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("Could not connect to MySQL: %v", err)
}
if err := db.Ping(); err != nil {
log.Fatalf("Could not ping MySQL: %v", err)
}
manager := &DBManager{DB: db}
manager.InitDB()
return manager
}
// InitDB initializes the database tables
func (manager *DBManager) InitDB() {
// Creating a tokens table
createTokensTableSQL := `CREATE TABLE IF NOT EXISTS tokens (
token VARCHAR(255) PRIMARY KEY,
hostname VARCHAR(255) NOT NULL UNIQUE
);`
if _, err := manager.DB.Exec(createTokensTableSQL); err != nil {
log.Fatalf("Failed to create tokens table: %v", err)
}
}
func (manager *DBManager) AddToken(token, hostname string) error {
_, err := manager.DB.Exec("INSERT INTO tokens (token, hostname) VALUES (?, ?)", token, hostname)
if err != nil {
log.Printf("Failed to add token: %v", err)
return err
}
return nil
}
func (manager *DBManager) VerifyToken(token string) bool {
var retrievedToken string
err := manager.DB.QueryRow("SELECT token FROM tokens WHERE token = ?", token).Scan(&retrievedToken)
if err != nil {
if err != sql.ErrNoRows {
log.Printf("Failed to verify token: %v", err)
}
return false
}
return true
}
func (manager *DBManager) RemoveToken(token string) error {
_, err := manager.DB.Exec("DELETE FROM tokens WHERE token = ?", token)
if err != nil {
log.Printf("Failed to remove token: %v", err)
return err
}
return nil
}

View File

@ -1,13 +1,54 @@
package backup
import (
"net"
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"golang.org/x/crypto/acme/autocert"
"io"
"bytes"
)
type CommandPayload struct {
Action string `json:"action"`
Node string `json:"node"`
Value string `json:"value"`
}
type JoinRequest struct {
NodeHostname string `json:"nodeHostname"`
Token string `json:"token"`
IP string `json:"ip"`
}
// Method to create a token
func (manager *DBManager) CreateToken(hostname, token string) error {
_, err := manager.DB.Exec("INSERT INTO tokens (hostname, token) VALUES (?, ?)", hostname, token)
if err != nil {
log.Printf("Failed to insert token: %v", err)
return err
}
return nil
}
// Method to verify if the hostname resolves to the expected IP
func (manager *DBManager) VerifyHostname(hostname, expectedIP string) bool {
ips, err := net.LookupIP(hostname)
if err != nil {
log.Printf("Failed to lookup IP for hostname %s: %v", hostname, err)
return false
}
for _, ip := range ips {
if ip.String() == expectedIP {
return true
}
}
return false
}
// GenerateCertForDomain generates an SSL certificate for a domain using Let's Encrypt
func GenerateCertForDomain(domain string) {
certManager := autocert.Manager{
@ -30,22 +71,117 @@ func GenerateCertForDomain(domain string) {
log.Fatal(server.ListenAndServeTLS("", "")) // Keys are provided by Let's Encrypt
}
var dbManager *DBManager
// 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")
if r.Method != http.MethodPost {
http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
return
}
var req JoinRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Error decoding request body", http.StatusBadRequest)
return
}
// Extract the IP address from r.RemoteAddr
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
http.Error(w, "Failed to parse remote IP", http.StatusBadRequest)
return
}
// Verify the token exists in the database and the hostname resolves to the connecting IP
if !dbManager.VerifyToken(req.Token) || !dbManager.VerifyHostname(req.NodeHostname, remoteIP) {
http.Error(w, "Invalid token or hostname does not resolve to the connecting IP", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Join request for %s with token %s accepted\n", req.NodeHostname, req.Token)
}
// 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)
func SendCommandToNode(nodeAddress, action, nodeName, value string) {
// Construct the command payload
payload := CommandPayload{
Action: action,
Node: nodeName,
Value: value,
}
// Marshal the payload into JSON
payloadBytes, err := json.Marshal(payload)
if err != nil {
log.Printf("Error marshaling command payload: %v", err)
return
}
// Send the command to the node
resp, err := http.Post(fmt.Sprintf("http://%s/api/command", nodeAddress), "application/json", bytes.NewReader(payloadBytes))
if err != nil {
log.Printf("Error sending command to node %s: %v", nodeName, err)
return
}
defer resp.Body.Close()
// Optionally, read the response body
// Note: As of Go 1.16, ioutil.ReadAll is deprecated. Use io.ReadAll instead.
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("Error reading response from node %s: %v", nodeName, err)
return
}
fmt.Printf("Command sent to node %s, response: %s\n", nodeName, string(responseBody))
}
// 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
go SetupWebServer() // Start the API server in a separate goroutine
// Generate SSL certificate and start HTTPS server
GenerateCertForDomain(domain)
}
// SetupWebServer initializes and starts the web server for handling API requests.
func SetupWebServer() {
// Define your HTTP request handlers
http.HandleFunc("/api/join", HandleJoinRequest) // Handle join requests
http.HandleFunc("/api/command", HandleCommand) // Handle commands sent to nodes
// More API endpoints can be added here
fmt.Println("Starting API server on port 8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Failed to start API server: %v", err)
}
}
// HandleCommand processes commands sent to nodes.
func HandleCommand(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}
// Example: Extracting command from the request body
// You'll need to define the structure of your request body
var req struct {
NodeAddress string `json:"nodeAddress"`
Command string `json:"command"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Error decoding request body", http.StatusBadRequest)
return
}
// Send the command to the specified node
fmt.Printf("Sending command '%s' to node %s\n", req.Command, req.NodeAddress)
// Implement the logic to send the command to the node here
// Respond to the request indicating success
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Command sent successfully to node %s\n", req.NodeAddress)
}