diff --git a/cmd/hive/cmd/admin.go b/cmd/hive/cmd/admin.go new file mode 100644 index 000000000..ac06d332b --- /dev/null +++ b/cmd/hive/cmd/admin.go @@ -0,0 +1,620 @@ +/* +Copyright Ā© 2025 Mulga Defense Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + _ "embed" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "math/big" + "net" + "os" + "path/filepath" + "text/template" + "time" + + "github.com/mulgadc/viperblock/viperblock" + "github.com/spf13/cobra" + "gopkg.in/ini.v1" +) + +//go:embed templates/hive.toml +var hiveTomlTemplate string + +//go:embed templates/awsgw.toml +var awsgwTomlTemplate string + +//go:embed templates/predastore.toml +var predastoreTomlTemplate string + +//go:embed templates/nats.conf +var natsConfTemplate string + +var supportedArchs = map[string]bool{ + "x86_64": true, + "aarch64": true, + "arm64": true, +} + +// TODO: Confirm suppported platform types +var supportedPlatforms = map[string]bool{ + "Linux/UNIX": true, + "Windows": true, +} + +var adminCmd = &cobra.Command{ + Use: "admin", + Short: "Administrative commands for Hive platform management", + Long: `Administrative commands for initializing and managing the Hive platform.`, +} + +var adminInitCmd = &cobra.Command{ + Use: "init", + Short: "Initialize Hive platform configuration", + Long: `Initialize Hive platform by creating configuration files, generating SSL certificates, +and setting up AWS credentials. This creates the necessary directory structure and +configuration files in ~/hive/config.`, + Run: runAdminInit, +} + +var imagesCmd = &cobra.Command{ + Use: "images", + Short: "Manage OS images", + Long: `Manage OS images for local storage and AMI creation.`, +} + +var imagesImportCmd = &cobra.Command{ + Use: "import", + Short: "Specify local file to import", + Long: `Create a new image from a local file`, + Run: runimagesImportCmd, +} + +func init() { + rootCmd.AddCommand(adminCmd) + adminCmd.AddCommand(adminInitCmd) + + adminCmd.AddCommand(imagesCmd) + imagesCmd.AddCommand(imagesImportCmd) + + homeDir, _ := os.UserHomeDir() + configDir := fmt.Sprintf("%s/hive/config", homeDir) + hiveDir := fmt.Sprintf("%s/hive/", homeDir) + + rootCmd.PersistentFlags().String("config-dir", configDir, "Configuration directory") + rootCmd.PersistentFlags().String("hive-dir", hiveDir, "Hive base directory") + + // Flags for admin init + adminInitCmd.Flags().Bool("force", false, "Force re-initialization (overwrites existing config)") + adminInitCmd.Flags().String("region", "ap-southeast-2", "Mulga region to create") + + imagesImportCmd.Flags().String("file", "", "Import file from specified path (raw, qcow2, compressed)") + imagesImportCmd.Flags().String("distro", "", "Specified distro name (e.g debian)") + imagesImportCmd.Flags().String("version", "", "Specified distro version (e.g 12)") + imagesImportCmd.Flags().String("arch", "", "Specified distro arch (e.g aarch64, arm64, x86_64)") + imagesImportCmd.Flags().String("platform", "Linux/UNIX", "Specified platform (e.g Linux/UNIX, Windows)") + +} + +func runimagesImportCmd(cmd *cobra.Command, args []string) { + + imageFile, _ := cmd.Flags().GetString("file") + + if imageFile == "" { + fmt.Fprintf(os.Stderr, "File required to import image") + os.Exit(1) + } + + imageStat, err := os.Stat(imageFile) + + if err != nil { + fmt.Fprintf(os.Stderr, "File could not be found", err) + os.Exit(1) + } + + //configDir, _ := cmd.Flags().GetString("config-dir") + baseDir, _ := cmd.Flags().GetString("hive-dir") + + // Check the base dir has our images path, and correctlty init + imageDir := fmt.Sprintf("%s/images", baseDir) + + if !fileExists(imageDir) { + fmt.Fprintf(os.Stderr, "Image directory does not exist. Base path specified correctly?", imageDir) + os.Exit(1) + } + + distro, _ := cmd.Flags().GetString("distro") + version, _ := cmd.Flags().GetString("version") + arch, _ := cmd.Flags().GetString("arch") + platform, _ := cmd.Flags().GetString("platform") + + if distro == "" { + fmt.Fprintf(os.Stderr, "Specify distro name") + os.Exit(1) + } + + // Check version specified + if version == "" { + fmt.Fprintf(os.Stderr, "Specify image version") + os.Exit(1) + } + + if !supportedArchs[arch] { + fmt.Fprintf(os.Stderr, "Unsupported architecture") + os.Exit(1) + } + + if !supportedPlatforms[platform] { + fmt.Fprintf(os.Stderr, "Unsupported platform") + os.Exit(1) + } + + // Create the specified image directory + imagePath := fmt.Sprintf("%s/%s/%s/%s", imageDir, distro, version, arch) + + // Create config directory + if err := os.MkdirAll(imagePath, 0700); err != nil { + fmt.Fprintf(os.Stderr, "Error creating config directory: %v\n", err) + os.Exit(1) + } + + fmt.Printf("āœ… Created config directory: %s\n", imagePath) + + // Create the specified manifest file to describe the image/AMI + manifest := viperblock.VolumeConfig{} + + // Calculate the size + manifest.AMIMetadata.Name = fmt.Sprintf("%s-%s-%s-ami", distro, version, arch) + + manifest.AMIMetadata.Description = fmt.Sprintf("%s cloud image prepared for Hive", manifest.AMIMetadata.Name) + manifest.AMIMetadata.Architecture = arch + manifest.AMIMetadata.PlatformDetails = platform + manifest.AMIMetadata.CreationDate = time.Now() + manifest.AMIMetadata.RootDeviceType = "ebs" + manifest.AMIMetadata.Virtualization = "hvm" + manifest.AMIMetadata.ImageOwnerAlias = "system" + manifest.AMIMetadata.VolumeSizeGiB = uint64(imageStat.Size() / 1024 / 1024 / 1024) + + // Volume Data + manifest.VolumeMetadata.VolumeName = manifest.AMIMetadata.Name + manifest.VolumeMetadata.TenantID = "system" + manifest.VolumeMetadata.SizeGiB = manifest.AMIMetadata.VolumeSizeGiB + manifest.VolumeMetadata.State = "available" + manifest.VolumeMetadata.AvailabilityZone = "" // TODO: Confirm + manifest.VolumeMetadata.CreatedAt = time.Now() + manifest.VolumeMetadata.VolumeType = "gp3" + manifest.VolumeMetadata.IOPS = 1000 + + // Write the manifest to disk + // Save as JSON + jsonData, err := json.Marshal(manifest) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not marshal manifest: %v\n", err) + os.Exit(1) + } + + manifestFilename := fmt.Sprintf("%s/%s.json", imagePath, manifest.AMIMetadata.Name) + // Write to file + err = os.WriteFile(manifestFilename, jsonData, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not write manifest: %v\n", err) + os.Exit(1) + } + +} + +func runAdminInit(cmd *cobra.Command, args []string) { + force, _ := cmd.Flags().GetBool("force") + configDir, _ := cmd.Flags().GetString("config-dir") + region, _ := cmd.Flags().GetString("region") + + // Default config directory + if configDir == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting home directory: %v\n", err) + os.Exit(1) + } + configDir = filepath.Join(homeDir, "hive", "config") + } + + fmt.Println("šŸš€ Initializing Hive platform...") + fmt.Printf("Configuration directory: %s\n\n", configDir) + + // Check if already initialized + hiveTomlPath := filepath.Join(configDir, "hive.toml") + if !force && fileExists(hiveTomlPath) { + fmt.Println("āš ļø Hive already initialized!") + fmt.Printf("Config file exists: %s\n", hiveTomlPath) + fmt.Println("\nTo re-initialize, run with --force flag:") + fmt.Println(" hive admin init --force") + os.Exit(0) + } + + // Create config directory + if err := os.MkdirAll(configDir, 0700); err != nil { + fmt.Fprintf(os.Stderr, "Error creating config directory: %v\n", err) + os.Exit(1) + } + fmt.Printf("āœ… Created config directory: %s\n", configDir) + + // Generate AWS credentials + accessKey := generateAWSAccessKey() + secretKey := generateAWSSecretKey() + accountID := generateAccountID() + + fmt.Println("\nšŸ”‘ Generated AWS credentials:") + fmt.Printf(" Access Key: %s\n", accessKey) + fmt.Printf(" Secret Key: %s\n", secretKey) + fmt.Printf(" Account ID: %s\n", accountID) + + // Generate SSL certificates + certPath := filepath.Join(configDir, "server.pem") + keyPath := filepath.Join(configDir, "server.key") + + if force || !fileExists(certPath) || !fileExists(keyPath) { + fmt.Println("\nšŸ” Generating SSL certificates...") + if err := generateSelfSignedCert(certPath, keyPath); err != nil { + fmt.Fprintf(os.Stderr, "Error generating SSL certificates: %v\n", err) + os.Exit(1) + } + fmt.Printf("āœ… SSL certificates generated:\n") + fmt.Printf(" Certificate: %s\n", certPath) + fmt.Printf(" Key: %s\n", keyPath) + } else { + fmt.Println("\nāœ… SSL certificates already exist") + } + + // Generate NATS token + natsToken := generateNATSToken() + fmt.Println("\nšŸ”’ Generated NATS authentication token") + + // Get home directory for data path + homeDir, _ := os.UserHomeDir() + hiveRoot := filepath.Join(homeDir, "hive") + + // Create config files from embedded templates + fmt.Println("\nšŸ“ Creating configuration files...") + + // Create subdirectories + awsgwDir := filepath.Join(configDir, "awsgw") + predastoreDir := filepath.Join(configDir, "predastore") + natsDir := filepath.Join(configDir, "nats") + + for _, dir := range []string{awsgwDir, predastoreDir, natsDir} { + if err := os.MkdirAll(dir, 0700); err != nil { + fmt.Fprintf(os.Stderr, "Error creating directory %s: %v\n", dir, err) + os.Exit(1) + } + } + + // Generate all config files + configs := []struct { + path string + template string + name string + }{ + {hiveTomlPath, hiveTomlTemplate, "hive.toml"}, + {filepath.Join(awsgwDir, "awsgw.toml"), awsgwTomlTemplate, "awsgw/awsgw.toml"}, + {filepath.Join(predastoreDir, "predastore.toml"), predastoreTomlTemplate, "predastore/predastore.toml"}, + {filepath.Join(natsDir, "nats.conf"), natsConfTemplate, "nats/nats.conf"}, + } + + for _, cfg := range configs { + if err := generateConfigFile(cfg.path, cfg.template, accessKey, secretKey, accountID, region, natsToken, hiveRoot); err != nil { + fmt.Fprintf(os.Stderr, "Error creating %s: %v\n", cfg.name, err) + os.Exit(1) + } + fmt.Printf("āœ… Created: %s\n", cfg.name) + } + + // Update ~/.aws/credentials and ~/.aws/config + fmt.Println("\nšŸ”§ Configuring AWS credentials...") + if err := setupAWSCredentials(accessKey, secretKey, region, certPath); err != nil { + fmt.Fprintf(os.Stderr, "Warning: Could not update AWS credentials: %v\n", err) + } else { + fmt.Println("āœ… AWS credentials configured") + } + + // Create additional directories + dirs := []string{ + filepath.Join(hiveRoot, "images"), + filepath.Join(hiveRoot, "amis"), + filepath.Join(hiveRoot, "volumes"), + filepath.Join(hiveRoot, "state"), + filepath.Join(hiveRoot, "logs"), + filepath.Join(hiveRoot, "nats"), + filepath.Join(hiveRoot, "predastore"), + filepath.Join(hiveRoot, "viperblock"), + } + + fmt.Println("\nšŸ“ Creating directory structure...") + for _, dir := range dirs { + if err := os.MkdirAll(dir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Warning: Could not create %s: %v\n", dir, err) + } + } + fmt.Printf("āœ… Directory structure created in %s\n", hiveRoot) + + // Print success message + fmt.Println("\nšŸŽ‰ Hive initialization complete!") + fmt.Println("\nšŸ“‹ Next steps:") + fmt.Println(" 1. Start services:") + fmt.Println(" ./scripts/start-dev.sh") + fmt.Println() + fmt.Println(" 2. Test with AWS CLI:") + fmt.Println(" export AWS_PROFILE=hive") + fmt.Println(" aws ec2 describe-instances") + fmt.Println() + fmt.Println("šŸ”— Configuration:") + fmt.Printf(" Config file: %s\n", hiveTomlPath) + fmt.Printf(" Data directory: %s\n", hiveRoot) + fmt.Println() +} + +// generateAWSAccessKey generates an AWS-style access key +// Format: AKIA + 16 random uppercase alphanumeric characters +func generateAWSAccessKey() string { + const prefix = "AKIA" + const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + const length = 16 + + result := make([]byte, length) + for i := range result { + num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + result[i] = charset[num.Int64()] + } + + return prefix + string(result) +} + +// generateAWSSecretKey generates an AWS-style secret key +// 40 character base64-encoded string +func generateAWSSecretKey() string { + bytes := make([]byte, 30) // 30 bytes = 40 chars in base64 + if _, err := rand.Read(bytes); err != nil { + panic(err) + } + return base64.StdEncoding.EncodeToString(bytes) +} + +// generateAccountID generates a 12-digit AWS account ID +func generateAccountID() string { + // Generate random 12-digit number + num, _ := rand.Int(rand.Reader, big.NewInt(900000000000)) + accountID := num.Int64() + 100000000000 // Ensure it's 12 digits + return fmt.Sprintf("%012d", accountID) +} + +// generateNATSToken generates a secure random token for NATS +func generateNATSToken() string { + bytes := make([]byte, 32) + if _, err := rand.Read(bytes); err != nil { + panic(err) + } + return "nats_" + base64.URLEncoding.EncodeToString(bytes)[:32] +} + +// generateSelfSignedCert generates a self-signed SSL certificate +func generateSelfSignedCert(certPath, keyPath string) error { + // Generate private key + privateKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return fmt.Errorf("failed to generate private key: %w", err) + } + + // Create certificate template + notBefore := time.Now() + notAfter := notBefore.Add(3650 * 24 * time.Hour) // 10 years + + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return fmt.Errorf("failed to generate serial number: %w", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "localhost", + Organization: []string{"Hive Platform"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, + } + + // Create certificate + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return fmt.Errorf("failed to create certificate: %w", err) + } + + // Write certificate to file + certOut, err := os.Create(certPath) + if err != nil { + return fmt.Errorf("failed to create cert file: %w", err) + } + defer certOut.Close() + + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return fmt.Errorf("failed to write cert: %w", err) + } + + // Write private key to file + keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("failed to create key file: %w", err) + } + defer keyOut.Close() + + privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return fmt.Errorf("failed to marshal private key: %w", err) + } + + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return fmt.Errorf("failed to write key: %w", err) + } + + return nil +} + +// generateConfigFile creates a configuration file from a template +func generateConfigFile(path, templateContent, accessKey, secretKey, accountID, region, natsToken, dataDir string) error { + // Parse the embedded template + tmpl, err := template.New("config").Parse(templateContent) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + // Template data + data := struct { + AccessKey string + SecretKey string + AccountID string + Region string + NatsToken string + DataDir string + }{ + AccessKey: accessKey, + SecretKey: secretKey, + AccountID: accountID, + Region: region, + NatsToken: natsToken, + DataDir: dataDir, + } + + // Create file with secure permissions + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("failed to create config file: %w", err) + } + defer f.Close() + + // Execute template + if err := tmpl.Execute(f, data); err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + + return nil +} + +// setupAWSCredentials updates ~/.aws/credentials and ~/.aws/config +func setupAWSCredentials(accessKey, secretKey, region, certPath string) error { + homeDir, err := os.UserHomeDir() + if err != nil { + return err + } + + awsDir := filepath.Join(homeDir, ".aws") + if err := os.MkdirAll(awsDir, 0700); err != nil { + return err + } + + credPath := filepath.Join(awsDir, "credentials") + configPath := filepath.Join(awsDir, "config") + + // Determine profile name + profileName := "default" + if fileExists(credPath) { + // Check if default profile already exists + cfg, err := ini.Load(credPath) + if err == nil && cfg.HasSection("default") { + profileName = "hive" + } + } + + // Update credentials file + if err := updateAWSINIFile(credPath, profileName, map[string]string{ + "aws_access_key_id": accessKey, + "aws_secret_access_key": secretKey, + }); err != nil { + return err + } + + // Update config file + configSection := profileName + if profileName != "default" { + configSection = "profile " + profileName + } + + if err := updateAWSINIFile(configPath, configSection, map[string]string{ + "region": region, + "endpoint_url": "https://localhost:9999", + "ca_bundle": certPath, + "output": "json", + }); err != nil { + return err + } + + fmt.Printf(" Profile: %s\n", profileName) + if profileName != "default" { + fmt.Printf(" Use: export AWS_PROFILE=%s\n", profileName) + } + + return nil +} + +// Helper functions + +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +// updateAWSINIFile updates or creates an AWS INI file section with given key-value pairs +func updateAWSINIFile(path, section string, values map[string]string) error { + var cfg *ini.File + var err error + + // Load existing file or create new one + if fileExists(path) { + cfg, err = ini.Load(path) + if err != nil { + return fmt.Errorf("failed to load INI file: %w", err) + } + } else { + cfg = ini.Empty() + } + + // Get or create section + sec, err := cfg.NewSection(section) + if err != nil { + // Section already exists, get it + sec, err = cfg.GetSection(section) + if err != nil { + return fmt.Errorf("failed to get section: %w", err) + } + } + + // Set key-value pairs + for key, value := range values { + sec.Key(key).SetValue(value) + } + + // Save with proper permissions + return cfg.SaveTo(path) +} diff --git a/cmd/hive/cmd/service.go b/cmd/hive/cmd/service.go index a064a0f45..cda3a66c9 100644 --- a/cmd/hive/cmd/service.go +++ b/cmd/hive/cmd/service.go @@ -21,6 +21,7 @@ import ( "os" "strings" + "github.com/davecgh/go-spew/spew" "github.com/mulgadc/hive/hive/config" "github.com/mulgadc/hive/hive/service" "github.com/mulgadc/hive/hive/services/nats" @@ -430,6 +431,8 @@ var awsgwStartCmd = &cobra.Command{ return } + fmt.Println("Loading config from:", cfgFile) + // TODO: Support ENV vars, CLI, otherwise revert to config.LoadConfig() appConfig, err := config.LoadConfig(cfgFile) @@ -458,8 +461,17 @@ var awsgwStartCmd = &cobra.Command{ appConfig.AWSGW.TLSKey = awsgwTlsKey } + baseDir := viper.GetString("base-dir") + + if awsgwTlsKey != "" { + fmt.Println("Overwriting awsgw base-dir to:", baseDir) + appConfig.BaseDir = baseDir + } + service, err := service.New("awsgw", appConfig) + spew.Dump(appConfig) + if err != nil { fmt.Println("Error starting awsgw service:", err) return diff --git a/config/awsgw/awsgw.toml b/cmd/hive/cmd/templates/awsgw.toml similarity index 55% rename from config/awsgw/awsgw.toml rename to cmd/hive/cmd/templates/awsgw.toml index d454024fd..49fc72226 100644 --- a/config/awsgw/awsgw.toml +++ b/cmd/hive/cmd/templates/awsgw.toml @@ -1,13 +1,13 @@ -# Test configuration file in TOML format. +# AWS Gateway configuration file in TOML format. version = "1.0" -region = "ap-southeast-2" +region = "{{.Region}}" # Enable verbose debugging debug = false [[auth]] -access_key_id = "AKIAIOSFODNN7EXAMPLE" -secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +access_key_id = "{{.AccessKey}}" +secret_access_key = "{{.SecretKey}}" policy = [ { bucket = "predastore", actions = ["s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListAllMyBuckets"] }, ] diff --git a/config/hive.toml b/cmd/hive/cmd/templates/hive.toml similarity index 51% rename from config/hive.toml rename to cmd/hive/cmd/templates/hive.toml index d1f065249..e2ebeb351 100644 --- a/config/hive.toml +++ b/cmd/hive/cmd/templates/hive.toml @@ -1,9 +1,10 @@ -# Domain configration file in TOML format. +# Domain configuration file in TOML format. version = "1.0" -region = "ap-southeast-2" +region = "{{.Region}}" +account_id = "{{.AccountID}}" -access_key = "AKIAIOSFODNN7EXAMPLE" -secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +access_key = "{{.AccessKey}}" +secret_key = "{{.SecretKey}}" [daemon] host = "0.0.0.0:4432" @@ -15,18 +16,19 @@ host = "0.0.0.0:9999" tlskey = "config/server.key" tlscert = "config/server.pem" debug = true +config = "awsgw/awsgw.toml" [nats] host = "0.0.0.0:4222" [nats.acl] -token = "your-nats-token" +token = "{{.NatsToken}}" [nats.sub] [predastore] host = "0.0.0.0:8443" -region = "ap-southeast-2" +region = "{{.Region}}" bucket = "predastore" -access_key = "AKIAIOSFODNN7EXAMPLE" -secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \ No newline at end of file +access_key = "{{.AccessKey}}" +secret_key = "{{.SecretKey}}" \ No newline at end of file diff --git a/cmd/hive/cmd/templates/nats.conf b/cmd/hive/cmd/templates/nats.conf new file mode 100644 index 000000000..7fa47ba52 --- /dev/null +++ b/cmd/hive/cmd/templates/nats.conf @@ -0,0 +1,32 @@ +# NATS Server Configuration +# Generated by Hive admin init + +# Server configuration +port: 4222 +server_name: hive-nats + +# Authorization +authorization { + token: "{{.NatsToken}}" +} + +# JetStream for persistent messaging +jetstream { + store_dir: "{{.DataDir}}/nats/storage" + max_memory_store: 1GB + max_file_store: 10GB +} + +# Logging +debug: false +trace: false +logtime: true +log_file: "{{.DataDir}}/logs/nats.log" + +# Limits +max_connections: 1000 +max_payload: 1MB +max_pending: 64MB + +# Monitoring +http_port: 8222 diff --git a/config/predastore/predastore.toml b/cmd/hive/cmd/templates/predastore.toml similarity index 73% rename from config/predastore/predastore.toml rename to cmd/hive/cmd/templates/predastore.toml index 8f3c75e43..c5213bb37 100644 --- a/config/predastore/predastore.toml +++ b/cmd/hive/cmd/templates/predastore.toml @@ -1,6 +1,6 @@ -# Test configuration file in TOML format. +# Predastore S3-compatible storage configuration file in TOML format. version = "1.0" -region = "ap-southeast-2" +region = "{{.Region}}" # Optionally configure port and host, or specify via daemon CLI options host = "0.0.0.0" @@ -14,15 +14,15 @@ debug = false [[buckets]] name = "predastore" -region = "ap-southeast-2" +region = "{{.Region}}" type = "fs" pathname = "data/predastore" public = false encryption = "" [[auth]] -access_key_id = "AKIAIOSFODNN7EXAMPLE" -secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +access_key_id = "{{.AccessKey}}" +secret_access_key = "{{.SecretKey}}" policy = [ { bucket = "predastore", actions = ["s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListAllMyBuckets"] }, ] diff --git a/cmd/hivectl/LICENSE b/cmd/hivectl/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/cmd/hivectl/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/cmd/hivectl/cmd/net.go b/cmd/hivectl/cmd/net.go deleted file mode 100644 index 350a22f79..000000000 --- a/cmd/hivectl/cmd/net.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright Ā© 2025 NAME HERE - -*/ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -// netCmd represents the net command -var netCmd = &cobra.Command{ - Use: "net", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("net called") - }, -} - -func init() { - rootCmd.AddCommand(netCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // netCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // netCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} diff --git a/cmd/hivectl/cmd/root.go b/cmd/hivectl/cmd/root.go deleted file mode 100644 index 8813bfe2b..000000000 --- a/cmd/hivectl/cmd/root.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright Ā© 2025 Mulga Defense Corporation - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var cfgFile string - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "hive", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -func init() { - cobra.OnInitialize(initConfig) - - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.hive.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".hive" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".hive") - } - - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} diff --git a/cmd/hivectl/cmd/vm.go b/cmd/hivectl/cmd/vm.go deleted file mode 100644 index 3096e3ad6..000000000 --- a/cmd/hivectl/cmd/vm.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright Ā© 2025 NAME HERE - -*/ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -// vmCmd represents the vm command -var vmCmd = &cobra.Command{ - Use: "vm", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("vm called") - }, -} - -func init() { - rootCmd.AddCommand(vmCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // vmCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // vmCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} diff --git a/cmd/hivectl/cmd/volume.go b/cmd/hivectl/cmd/volume.go deleted file mode 100644 index af554b710..000000000 --- a/cmd/hivectl/cmd/volume.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright Ā© 2025 NAME HERE - -*/ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -// volumeCmd represents the volume command -var volumeCmd = &cobra.Command{ - Use: "volume", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("volume called") - }, -} - -func init() { - rootCmd.AddCommand(volumeCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // volumeCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // volumeCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} diff --git a/cmd/hivectl/main.go b/cmd/hivectl/main.go deleted file mode 100644 index 4728078e8..000000000 --- a/cmd/hivectl/main.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright Ā© 2025 Mulga Defense Corporation - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package main - -import "github.com/mulgadc/hive/cmd/hivectl/cmd" - -func main() { - cmd.Execute() -} diff --git a/cmd/smithy/main.go b/cmd/smithy/main.go deleted file mode 100644 index e7d104a6d..000000000 --- a/cmd/smithy/main.go +++ /dev/null @@ -1,300 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "io" - "log" - "os" - "strings" - - "github.com/davecgh/go-spew/spew" -) - -// Parse AWS Smithy models - -type Smithy struct { - Smithy string `json:"smithy"` - Metadata map[string]interface{} - Shapes map[string]interface{} -} - -type SmithyRange struct { - Min int `json:"min"` - Max int `json:"max"` -} - -type SmithyPaginated struct { - InputToken string `json:"inputToken"` - OutputToken string `json:"outputToken"` - Items string `json:"items"` - PageSize string `json:"pageSize"` -} - -type SmithyTrait struct { - Documentation string `json:"smithy.api#documentation"` - XMLName string `json:"smithy.api#xmlName"` - Ec2QueryName string `json:"smithy.api#ec2QueryName"` - - Range SmithyRange `json:"smithy.api#range"` // optional - SmithyPaginated SmithyPaginated `json:"smithy.api#paginated"` // optional - -} - -type SmithyMember struct { - Target string `json:"target"` - Traits SmithyTrait `json:"traits"` -} - -type SmithyMemberStructure struct { - Target string `json:"target"` - Traits SmithyTrait `json:"traits"` -} - -type SmithyRequest struct { - Type string `json:"type"` -} - -type SmithyRequestList struct { - Type string `json:"type"` - Member SmithyMember `json:"member"` -} - -type SmithyRequestStructure struct { - Type string `json:"type"` - Members map[string]SmithyMemberStructure `json:"members"` - Traits SmithyTrait `json:"traits"` -} - -var result Smithy - -var smithyTypes = map[string]string{ - "String": "string", - "Integer": "int", - "Boolean": "bool", - "DateTime": "time.Time", -} - -func main() { - - path := flag.String("path", "", "Path to the Smithy model") - flag.Parse() - - if path == nil { - log.Fatal("path is required") - } - - // Open the file and read JSON - jsonFile, err := os.Open(*path) - if err != nil { - log.Fatal(err) - } - defer jsonFile.Close() - - // Read the file and unmarshal as JSON - byteValue, _ := io.ReadAll(jsonFile) - - json.Unmarshal([]byte(byteValue), &result) - - // spew.Dump(result) - - _, req := findShape("com.amazonaws.ec2#DescribeInstancesRequest") - scanRequest("com.amazonaws.ec2#DescribeInstancesRequest", req, true) - - /* - for k, v := range result.Shapes { - - // Find all request methods - - // "com.amazonaws.ec2#RunInstancesRequest" - // DescribeInstancesRequest - if strings.HasSuffix(k, "DescribeInstancesRequest") { - - //fmt.Println(k) - - scanRequest(k, v.(interface{}), true) - - // Loop in each request - //for k2, v2 := range v.(map[string]interface{}) { - // fmt.Println(k2, v2) - //} - - } - - } - */ -} - -func scanRequest(key string, request interface{}, root bool) { - - //spew.Dump(request) - // Marshal into a SmithyRequest - - // Unmarshal into a SmithyRequest - - var smithyRequest SmithyRequest - - b, _ := json.Marshal(request) // map -> JSON - _ = json.Unmarshal(b, &smithyRequest) // JSON -> struct - - //spew.Dump(smithyRequest) - - keyName := strings.Split(key, "#") - - if len(keyName) == 0 { - fmt.Println("No key found") - return - } - - if root { - fmt.Printf("type %s struct {\n", keyName[1]) - } - - if smithyRequest.Type == "structure" { - findShapeRecursiveStructure(request) - } else if smithyRequest.Type == "list" { - findShapeRecursiveList(request) - } - - //fmt.Println(smithyRequest.Type) - - //for k, v := range request { - // fmt.Println(k, v) - //} - -} - -func findShapeRecursiveList(request interface{}) error { - - var smithyRequest SmithyRequestList - - b, _ := json.Marshal(request) // map -> JSON - _ = json.Unmarshal(b, &smithyRequest) // JSON -> struct - - fmt.Println("findShapeRecursiveList:") - spew.Dump(request) - spew.Dump(smithyRequest) - - memberType := strings.Split(smithyRequest.Member.Target, "#") - - fmt.Println(memberType) - - if len(memberType) == 0 { - fmt.Println("No member type found") - return nil - } - - typeName := memberType[1] - - // If a matching smithyType - goType := smithyTypes[typeName] - - if goType != "" { - fmt.Printf("%s `xml:\"%s\"`\n", goType, smithyRequest.Member.Traits.XMLName) - - } else { - //spew.Dump(v) - fmt.Println("\t", smithyRequest.Member.Target, smithyRequest.Member.Traits.XMLName) - - newSmithyRequest, request := findShape(smithyRequest.Member.Target) - - if newSmithyRequest.Type == "structure" { - findShapeRecursiveStructure(request) - } else if newSmithyRequest.Type == "list" { - findShapeRecursiveList(request) - } - - //findShapeRecursiveStructure(newSmithyRequest) - - } - - //fmt.Println(smithyRequest.Members) - - fmt.Println() - - return nil - -} - -func findShapeRecursiveStructure(request interface{}) error { - - fmt.Println("findShapeRecursiveStructure => \t") - - var smithyRequest SmithyRequestStructure - - b, _ := json.Marshal(request) // map -> JSON - _ = json.Unmarshal(b, &smithyRequest) // JSON -> struct - - for k, v := range smithyRequest.Members { - - //fmt.Println("\t", k, v) - - memberType := strings.Split(v.Target, "#") - - fmt.Println(memberType) - - if len(memberType) == 0 { - fmt.Println("No member type found") - continue - } - - typeName := memberType[1] - - // If a matching smithyType - goType := smithyTypes[typeName] - - if goType != "" { - fmt.Printf("%s %s `xml:\"%s\"`\n", k, goType, v.Traits.XMLName) - - } else { - //spew.Dump(v) - //fmt.Println("\t", k, v.Target, v.Traits.XMLName) - - newSmithyRequest, request := findShape(v.Target) - - if newSmithyRequest.Type == "structure" { - findShapeRecursiveStructure(request) - } else if newSmithyRequest.Type == "list" { - findShapeRecursiveList(request) - } - //findShapeRecursiveStructure(newSmithyRequest) - - } - - } - - //fmt.Println(smithyRequest.Members) - - fmt.Println() - - return nil - -} - -func findShape(key string) (SmithyRequest, interface{}) { - - for k, v := range result.Shapes { - if k == key { - - //fmt.Println("MATCH", k, key) - - //spew.Dump(v) - //fmt.Println(k, v) - var smithyRequest SmithyRequest - - b, _ := json.Marshal(v) // map -> JSON - _ = json.Unmarshal(b, &smithyRequest) // JSON -> struct - - //spew.Dump(v) - - //spew.Dump(smithyRequest) - - return smithyRequest, v - - } - } - - return SmithyRequest{}, nil - -} diff --git a/config/README.md b/config/README.md index 3bbe0a1c8..69faa1467 100644 --- a/config/README.md +++ b/config/README.md @@ -16,6 +16,52 @@ This directory contains the systemd service configuration for running Hive servi 2. Ensure the `hive` binary is available at `/opt/hive/bin/hive` 3. Run the installation script as root +### Setup Configuration + +``` +/opt/hive/bin/hive admin init +``` + +This will init the Hive environment and create the default configuration files in the home directory, e.g `~/hive/`. + +``` +/home/username/hive: +amis config images logs nats predastore state viperblock volumes + +/home/username/hive/amis: + +/home/username/hive/config: +awsgw hive.toml nats predastore server.key server.pem + +/home/username/hive/config/awsgw: +awsgw.toml + +/home/username/hive/config/nats: +nats.conf + +/home/username/hive/config/predastore: +predastore.toml + +/home/username/hive/images: + +/home/username/hive/logs: +awsgw.log awsgw.pid hive.log hive.pid nats.log nats.pid predastore.log predastore.pid viperblock.log viperblock.pid + +/home/username/hive/nats: + +/home/username/hive/predastore: +data + +/home/username/hive/predastore/data: +predastore + +/home/ben/hive/state: + +/home/ben/hive/viperblock: + +/home/ben/hive/volumes: +``` + ### Quick Installation ```bash diff --git a/config/hive/vm.conf b/config/hive/vm.conf deleted file mode 100644 index 31402d993..000000000 --- a/config/hive/vm.conf +++ /dev/null @@ -1,46 +0,0 @@ - - hive-vm-01 - 2048 - 2 - - - hvm - - - - - - - - - - - /usr/bin/qemu-system-x86_64 - - - - - - - - -
- - - - - - - - - - - - - - - - - - - diff --git a/config/server.key b/config/server.key deleted file mode 100644 index 446b2674d..000000000 --- a/config/server.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/zZGieclULKuD -6XrAYY7UWqAxBOxURnzmQ+fPLjmuuC1PiVGRQV9HERvtUqe1aRN31k7uq/sIvd/j -naiF2GbTnB6DslWVAishlcQ1X6qZ6har7HZq8hfiUUgqfe5hlJuOSTGjvZlJmH3F -rqan5iW/LpEVVeu3KEFCpTOZ74vLGejUhw8bWAUbTVpErz9srXnQw6Vye0n+aRPC -uFfSWdVY0iMp6beb8dkfhRP1+PVvSqFFd36Fk6VKz220/2G3WrJ6+y9Zj+1aZVHE -1vsMROYIG6wXjykOuxdWjQuIwYWSVYegaRf//HCfrEE8F6GiwhthoUmwon737VTm -JUZG7D7HAgMBAAECggEAAZ/qR4yL28t890gpdjTsgPvTAqdJeD3OZloVlZBXVTdO -bocsUvDzAF6AIFkpfj9/Xucm5mE8FV2H2RnCYkjo88B/w3Xe9wcvBDAMjUtLE/Eo -Edk3rf2LxjGLRn/QoggS7p1SryKsjxG+Gv4DLsyI7gCr2E7Q01sBb8YA793X+su3 -cHYbI+m1xHKMK3SgVgrSsyfjTxzOhn6J9c4DfQYBDcmRgOsGImJkQDedrc0DuSF3 -q6C4coCQuQizdYfaVQAQJD+higDhcpVIeLrO0XMePmDKWhGG5/kPmbV0vsfPmPxr -TZpvIZqDhiAzACax6yaA7+F6QNyghQTE/N1M7A0w3QKBgQDvOWcyOaChau3Eio06 -xo6T83khHMEOTIznqdYXcIDqpAIXZlsmluvmmARlea5ctc2115x6+BfZ7UjSBPXM -espCu/2HBI6+DDboA36qm5Hf2uReB4svouwenhdwdDuLz7Yyah0A8cpmmy1x6ZRT -M2EwuvIYHE1dah5IAqW0vkqhYwKBgQDNQNntWmOFsWrdjl5zyzG99dM9jD9uLGZV -IHsJZzDBQA+Dq25JNQghT2/iolHtcueMsPeO/B4paBtVNOMqEwk9z2G0P3PT8PJN -+69wDlVbhyMXGtwlGN1nQq6OEirbdEqpwBWaNibXAg/6YCLL4QcY55Bqqpuh/YqN -4GqcmMe8TQKBgQCeLp/dJxvfAHyeNFt0nOyUSi8zRPcnBFk7qiLFtv+Xp8E2RdlJ -6Auum6B0lQRFi3Wtoxd2fGwRDwTS0rLRdfQeMn5O9CWK5zq7Kexs1ZecpPnSbzyK -0Dk/eZvYIRNqvHsmVOI+yKLkPAHNy/znExdWCJeid7x0Y4IIba+ylw10LwKBgBJH -3WELdOO6aoczH+x2Q4bktxy7OgcJA1A/pVWwECKWHwueICN7Cq9U0OVBicR5QHBC -broYcGNFt3F+VWEw8tjDeLNCF4Hf6uTRLs3vKOEPBywOo17WeRmyUbOroI+NKv01 -c3cZrZnmwB+PomnAMA6iyTUG3vetikKao0QxQ1DdAoGBAMNw6wA9ivcWGwt6v5AB -p7X5ychDqC9xR/6xrnp+yFtrNYr4K9huSO2qRdh88YEG2u3fUDcX5v4rSUULx2hg -e0CicdDxtOJYAu3ZLVj/Cm9EN0sXW5xmvxXCJXjB90o5Njft//lL234PTNPqisp/ -ludFyCEl+UT1ky8E5bswZQ+O ------END PRIVATE KEY----- diff --git a/config/server.pem b/config/server.pem deleted file mode 100644 index 027a9b7a1..000000000 --- a/config/server.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDazCCAlOgAwIBAgIUL0W8hP7iGr4njDS6vFO7jd4A0LgwDQYJKoZIhvcNAQEL -BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA0MjgwOTQzNDBaFw0zNDA1 -MDYwOTQzNDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw -HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQC/zZGieclULKuD6XrAYY7UWqAxBOxURnzmQ+fPLjmu -uC1PiVGRQV9HERvtUqe1aRN31k7uq/sIvd/jnaiF2GbTnB6DslWVAishlcQ1X6qZ -6har7HZq8hfiUUgqfe5hlJuOSTGjvZlJmH3Frqan5iW/LpEVVeu3KEFCpTOZ74vL -GejUhw8bWAUbTVpErz9srXnQw6Vye0n+aRPCuFfSWdVY0iMp6beb8dkfhRP1+PVv -SqFFd36Fk6VKz220/2G3WrJ6+y9Zj+1aZVHE1vsMROYIG6wXjykOuxdWjQuIwYWS -VYegaRf//HCfrEE8F6GiwhthoUmwon737VTmJUZG7D7HAgMBAAGjUzBRMB0GA1Ud -DgQWBBQt6t17ADSJtihmhKJULlI5JbrRITAfBgNVHSMEGDAWgBQt6t17ADSJtihm -hKJULlI5JbrRITAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC6 -jQs/iU3mlYR9Ql6L7gN8rzq50Dy0HN7MzeWOJ3KjPLB2g0tYuXvZcStjQ50BXXcT -kKM5wzy4Pp5AYYXFajlXnDZXYi94200KRzi3Ml8Kmb3nQtFd4EFbDuWCUNp9aynE -B2A9H/hmH91f0CozcSyK5jtvBg8bdsC4YiWxPAvWwtqrSPGwBEpn7r764mRkqjue -8g2OwiV3kxTPejcHM5HcyG4qAo4Gkyl5IZ7m9jxKoVrI+dqD6/DV6xvhaIYb73Qe -7+PJNfoJDlNinH8yhbwmG8/DK4e/BFVj9uB64vcmiZ1sUoUsh7ONh50zh+UZNwRf -35ZDkBLxPq5qWMUPsjZg ------END CERTIFICATE----- diff --git a/go.mod b/go.mod index 3769ea2ce..a6f8a75de 100644 --- a/go.mod +++ b/go.mod @@ -52,5 +52,6 @@ require ( golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.12.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0b75d7edb..7f437b04e 100644 --- a/go.sum +++ b/go.sum @@ -120,6 +120,8 @@ golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/hive/config/config.go b/hive/config/config.go index f210e4fca..284cbad66 100644 --- a/hive/config/config.go +++ b/hive/config/config.go @@ -30,6 +30,7 @@ type AWSGWConfig struct { Host string `mapstructure:"host"` TLSKey string `mapstructure:"tlskey"` TLSCert string `mapstructure:"tlscert"` + Config string `mapstructure:"config"` Debug bool `mapstructure:"debug"` } @@ -205,17 +206,19 @@ func LoadConfig(configPath string) (*Config, error) { return nil, fmt.Errorf("error unmarshaling config: %w", err) } - // Validate required fields - if config.NATS.Host == "" { - return nil, fmt.Errorf("NATS host is required") - } + // Validate required fields (can be overwritten by env vars) + /* + if config.NATS.Host == "" { + return nil, fmt.Errorf("NATS host is required") + } - if config.AccessKey == "" { - return nil, fmt.Errorf("access key is required") - } - if config.SecretKey == "" { - return nil, fmt.Errorf("secret key is required") - } + if config.AccessKey == "" { + return nil, fmt.Errorf("access key is required") + } + if config.SecretKey == "" { + return nil, fmt.Errorf("secret key is required") + } + */ // fmt.Println("Config: ", config) diff --git a/hive/gateway/gateway.go b/hive/gateway/gateway.go index 54c0c7976..c339beb8d 100644 --- a/hive/gateway/gateway.go +++ b/hive/gateway/gateway.go @@ -17,9 +17,10 @@ import ( ) type GatewayConfig struct { - Debug bool `json:"debug"` - DisableLogging bool `json:"disable_logging"` - NATSConn *nats.Conn // Shared NATS connection for service communication + Debug bool `json:"debug"` + DisableLogging bool `json:"disable_logging"` + NATSConn *nats.Conn // Shared NATS connection for service communication + Config string // Shared AWS Gateway config for S3 auth } var supportedServices = map[string]bool{ @@ -89,7 +90,7 @@ func (gw *GatewayConfig) SetupRoutes() *fiber.App { s3 := s3.New(&s3.Config{}) // TODO: Support env var for config path, and external IAM - s3.ConfigPath = "config/awsgw/awsgw.toml" + s3.ConfigPath = gw.Config err := s3.ReadConfig() diff --git a/hive/services/awsgw/awsgw.go b/hive/services/awsgw/awsgw.go index f940bc597..19564ff9a 100644 --- a/hive/services/awsgw/awsgw.go +++ b/hive/services/awsgw/awsgw.go @@ -1,9 +1,11 @@ package awsgw import ( + "fmt" "log" "log/slog" "os" + "strings" "time" "github.com/mulgadc/hive/hive/config" @@ -76,11 +78,17 @@ func launchService(config *config.Config) (err error) { slog.Info("Connected to NATS server", "host", config.NATS.Host) + // Append Base dir if config has no leading path + if config.BaseDir != "" && !strings.HasPrefix(config.AWSGW.Config, "/") { + config.AWSGW.Config = fmt.Sprintf("%s/%s", config.BaseDir, config.AWSGW.Config) + } + // Create gateway with NATS connection gw := gateway.GatewayConfig{ Debug: config.AWSGW.Debug, DisableLogging: false, NATSConn: natsConn, + Config: config.AWSGW.Config, } app := gw.SetupRoutes() diff --git a/scripts/dev-setup.sh b/scripts/dev-setup.sh index 6fa8f3274..6ff854068 100755 --- a/scripts/dev-setup.sh +++ b/scripts/dev-setup.sh @@ -14,7 +14,8 @@ NATS_PORT=4222 PREDASTORE_PORT=8443 HIVE_GATEWAY_PORT=9999 DATA_DIR="$HOME/hive" -CONFIG_DIR="$PROJECT_ROOT/config" +# Use CONFIG_DIR environment variable if set, otherwise default to ~/hive/config +CONFIG_DIR="${CONFIG_DIR:-$HOME/hive/config}" echo "šŸš€ Setting up Hive development environment..." echo "Project root: $PROJECT_ROOT" @@ -105,59 +106,26 @@ for port_info in "${ports[@]}"; do fi done -# Generate TLS certificates for development +# Build Hive first (needed for admin init) echo "" -echo "šŸ” Setting up TLS certificates..." +echo "šŸ”Ø Building Hive..." +cd "$PROJECT_ROOT" +make build +echo "āœ… Hive built successfully" -if [[ ! -f "$CONFIG_DIR/server.pem" ]] || [[ ! -f "$CONFIG_DIR/server.key" ]]; then - echo "šŸ“‹ Generating self-signed certificate for development..." - openssl req -x509 -newkey rsa:4096 -keyout "$CONFIG_DIR/server.key" -out "$CONFIG_DIR/server.pem" \ - -days 365 -nodes -subj "/C=US/ST=Dev/L=Development/O=Hive/CN=localhost" \ - -addext "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:::1" - echo "āœ… TLS certificates generated" +# Initialize Hive configuration using admin init +echo "" +echo "šŸ” Initializing Hive configuration..." + +if [[ ! -f "$CONFIG_DIR/hive.toml" ]]; then + echo "šŸ“‹ Running hive admin init..." + ./bin/hive admin init --config-dir "$CONFIG_DIR" + echo "āœ… Hive configuration initialized" else - echo "āœ… TLS certificates already exist" + echo "āœ… Hive configuration already exists" + echo " To re-initialize, run: ./bin/hive admin init --force" fi -# Create development configuration -echo "" -echo "āš™ļø Creating development configuration..." - -cat > "$CONFIG_DIR/dev.yaml" << EOF -# Hive Development Configuration -server: - host: "0.0.0.0" - port: $HIVE_GATEWAY_PORT - tls: - cert_file: "$CONFIG_DIR/server.pem" - key_file: "$CONFIG_DIR/server.key" - -nats: - host: "nats://localhost:$NATS_PORT" - acl: - token: "dev-token" - -services: - predastore: - host: "https://localhost:$PREDASTORE_PORT" - access_key: "EXAMPLEKEY" - secret_key: "EXAMPLEKEY" - bucket: "predastore" - region: "ap-southeast-2" - - viperblock: - base_dir: "$DATA_DIR/viperblock" - -data: - base_dir: "$DATA_DIR" - -logging: - level: "info" - format: "json" -EOF - -echo "āœ… Development configuration created at $CONFIG_DIR/dev.yaml" - # Build components echo "" echo "šŸ”Ø Building components..." @@ -182,12 +150,6 @@ else echo "āš ļø Predastore directory not found, skipping build" fi -# Build Hive -echo "šŸ“¦ Building Hive..." -cd "$PROJECT_ROOT" -make build -echo "āœ… Hive built successfully" - echo "" echo "šŸŽ‰ Development environment setup complete!" echo "" diff --git a/scripts/start-dev.sh b/scripts/start-dev.sh index 859561c88..761987c0d 100755 --- a/scripts/start-dev.sh +++ b/scripts/start-dev.sh @@ -10,7 +10,9 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" MULGA_ROOT="$(cd "$PROJECT_ROOT/.." && pwd)" # Configuration paths -CONFIG_DIR="$PROJECT_ROOT/config" +# Use CONFIG_DIR environment variable if set, otherwise default to ~/hive/config +CONFIG_DIR="${CONFIG_DIR:-$HOME/hive/config}" +echo "Using configuration directory: $CONFIG_DIR" DATA_DIR="$HOME/hive" LOGS_DIR="$DATA_DIR/logs" WAL_DIR="$DATA_DIR/hive" @@ -19,8 +21,14 @@ echo "šŸš€ Starting Hive development environment..." echo "Project root: $PROJECT_ROOT" echo "Data directory: $DATA_DIR" -# Create necessary directories -mkdir -p "$DATA_DIR"/{predastore,viperblock,logs} +# Confirm configuration directory exists +if [ ! -d "$CONFIG_DIR" ]; then + echo "āš ļø Configuration directory $CONFIG_DIR does not exist." + echo "Please init the hive environment using the CLI." + echo "hive admin init" + exit 1 +fi + if [ -d "/mnt/ramdisk" ]; then @@ -170,8 +178,6 @@ export HIVE_VIPERBLOCK_S3_BUCKET=predastore export HIVE_VIPERBLOCK_S3_REGION=ap-southeast-2 export HIVE_VIPERBLOCK_PLUGIN_PATH=$NBD_PLUGIN_PATH export HIVE_NATS_HOST=0.0.0.0:4222 -export HIVE_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE -export HIVE_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY export HIVE_BASE_DIR=$VB_BASE_DIR #VIPERBLOCK_CMD="air -c .air-viperblock.toml" @@ -179,8 +185,6 @@ VIPERBLOCK_CMD="./bin/hive service viperblock start" #VIPERBLOCK_CMD="go run cmd/hive/main.go service viperblock start \ # --nats-host 0.0.0.0:4222 \ -# --access-key AKIAIOSFODNN7EXAMPLE \ -# --secret-key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \ # --base-dir $VB_BASE_DIR \ # --plugin-path $NBD_PLUGIN_PATH" @@ -211,6 +215,7 @@ echo "5ļøāƒ£. Starting AWS Gateway..." unset HIVE_NATS_HOST unset HIVE_PREDASTORE_HOST +export HIVE_BASE_DIR=$CONFIG_DIR export HIVE_CONFIG_PATH=$CONFIG_DIR/hive.toml export HIVE_AWSGW_HOST="0.0.0.0:9999" export HIVE_AWSGW_TLS_CERT=$CONFIG_DIR/server.pem