diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da1385..f8fd6b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,22 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant ### Deprecated ### Fixed +## [0.67.4] - 2025-11-11 + +### Added +\n +### Changed +- Improved configuration file loading logic to support absolute paths for config files. +- Updated IPFS Cluster initialization during setup to run `ipfs-cluster-service init` and automatically configure the cluster secret. +- IPFS repositories initialized with a private swarm key will now automatically disable AutoConf. + +### Deprecated + +### Removed + +### Fixed +- Fixed configuration path resolution to correctly check for config files in both the legacy (`~/.debros/`) and production (`~/.debros/configs/`) directories. + ## [0.67.3] - 2025-11-11 ### Added diff --git a/Makefile b/Makefile index d38a6f5..224f9ab 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ test-e2e: .PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports install-hooks kill -VERSION := 0.67.3 +VERSION := 0.67.4 COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)' diff --git a/cmd/node/main.go b/cmd/node/main.go index 451c8f8..b665101 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -66,23 +66,32 @@ func check_if_should_open_help(help *bool) { func select_data_dir_check(configName *string) { logger := setup_logger(logging.ComponentNode) - // Ensure config directory exists and is writable - _, err := config.EnsureConfigDir() - if err != nil { - logger.Error("Failed to ensure config directory", zap.Error(err)) - fmt.Fprintf(os.Stderr, "\n❌ Configuration Error:\n") - fmt.Fprintf(os.Stderr, "Failed to create/access config directory: %v\n", err) - fmt.Fprintf(os.Stderr, "\nPlease ensure:\n") - fmt.Fprintf(os.Stderr, " 1. Home directory is accessible: %s\n", os.ExpandEnv("~")) - fmt.Fprintf(os.Stderr, " 2. You have write permissions to home directory\n") - fmt.Fprintf(os.Stderr, " 3. Disk space is available\n") - os.Exit(1) - } + var configPath string + var err error - configPath, err := config.DefaultPath(*configName) - if err != nil { - logger.Error("Failed to determine config path", zap.Error(err)) - os.Exit(1) + // Check if configName is an absolute path + if filepath.IsAbs(*configName) { + // Use absolute path directly + configPath = *configName + } else { + // Ensure config directory exists and is writable + _, err = config.EnsureConfigDir() + if err != nil { + logger.Error("Failed to ensure config directory", zap.Error(err)) + fmt.Fprintf(os.Stderr, "\n❌ Configuration Error:\n") + fmt.Fprintf(os.Stderr, "Failed to create/access config directory: %v\n", err) + fmt.Fprintf(os.Stderr, "\nPlease ensure:\n") + fmt.Fprintf(os.Stderr, " 1. Home directory is accessible: %s\n", os.ExpandEnv("~")) + fmt.Fprintf(os.Stderr, " 2. You have write permissions to home directory\n") + fmt.Fprintf(os.Stderr, " 3. Disk space is available\n") + os.Exit(1) + } + + configPath, err = config.DefaultPath(*configName) + if err != nil { + logger.Error("Failed to determine config path", zap.Error(err)) + os.Exit(1) + } } if _, err := os.Stat(configPath); err != nil { @@ -232,15 +241,21 @@ func main() { check_if_should_open_help(help) - // Check if config file exists + // Check if config file exists and determine path select_data_dir_check(configName) - // Load configuration from ~/.debros/node.yaml - configPath, err := config.DefaultPath(*configName) - if err != nil { - logger.Error("Failed to determine config path", zap.Error(err)) - fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err) - os.Exit(1) + // Determine config path (handle both absolute and relative paths) + var configPath string + var err error + if filepath.IsAbs(*configName) { + configPath = *configName + } else { + configPath, err = config.DefaultPath(*configName) + if err != nil { + logger.Error("Failed to determine config path", zap.Error(err)) + fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err) + os.Exit(1) + } } var cfg *config.Config diff --git a/pkg/config/paths.go b/pkg/config/paths.go index 4e8ecec..ca3fd5a 100644 --- a/pkg/config/paths.go +++ b/pkg/config/paths.go @@ -29,10 +29,26 @@ func EnsureConfigDir() (string, error) { // DefaultPath returns the path to the config file for the given component name. // component should be e.g., "node.yaml", "bootstrap.yaml", "gateway.yaml" +// It checks both ~/.debros/ and ~/.debros/configs/ for backward compatibility. func DefaultPath(component string) (string, error) { dir, err := ConfigDir() if err != nil { return "", err } - return filepath.Join(dir, component), nil + + // First check in ~/.debros/configs/ (production installer location) + configsPath := filepath.Join(dir, "configs", component) + if _, err := os.Stat(configsPath); err == nil { + return configsPath, nil + } + + // Fallback to ~/.debros/ (legacy/development location) + legacyPath := filepath.Join(dir, component) + if _, err := os.Stat(legacyPath); err == nil { + return legacyPath, nil + } + + // Return configs path as default (even if it doesn't exist yet) + // This allows the error message to show the expected production location + return configsPath, nil } diff --git a/pkg/environments/production/installers.go b/pkg/environments/production/installers.go index 5b33667..d9a2b44 100644 --- a/pkg/environments/production/installers.go +++ b/pkg/environments/production/installers.go @@ -1,6 +1,7 @@ package production import ( + "encoding/json" "fmt" "os" "os/exec" @@ -363,10 +364,22 @@ func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swa } // Copy swarm key if present + swarmKeyExists := false if data, err := os.ReadFile(swarmKeyPath); err == nil { if err := os.WriteFile(filepath.Join(ipfsRepoPath, "swarm.key"), data, 0600); err != nil { return fmt.Errorf("failed to copy swarm key: %w", err) } + swarmKeyExists = true + } + + // Disable AutoConf for private swarm (required when swarm.key is present) + // This prevents IPFS from trying to use the public mainnet AutoConf service + if swarmKeyExists { + cmd = exec.Command(ipfsBinary, "config", "--json", "AutoConf.Enabled", "false") + cmd.Env = append(os.Environ(), "IPFS_PATH="+ipfsRepoPath) + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to disable AutoConf: %v\n%s", err, string(output)) + } } // Fix ownership @@ -376,8 +389,7 @@ func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swa } // InitializeIPFSClusterConfig initializes IPFS Cluster configuration -// Note: This is a placeholder config. The full initialization will occur via `ipfs-cluster-service init` -// which is run during Phase2cInitializeServices with the IPFS_CLUSTER_PATH env var set. +// This runs `ipfs-cluster-service init` to create the service.json configuration file. func (bi *BinaryInstaller) InitializeIPFSClusterConfig(nodeType, clusterPath, clusterSecret string, ipfsAPIPort int) error { serviceJSONPath := filepath.Join(clusterPath, "service.json") if _, err := os.Stat(serviceJSONPath); err == nil { @@ -391,7 +403,72 @@ func (bi *BinaryInstaller) InitializeIPFSClusterConfig(nodeType, clusterPath, cl return fmt.Errorf("failed to create IPFS Cluster directory: %w", err) } + // Fix ownership before running init exec.Command("chown", "-R", "debros:debros", clusterPath).Run() + + // Resolve ipfs-cluster-service binary path + clusterBinary, err := bi.ResolveBinaryPath("ipfs-cluster-service", "/usr/local/bin/ipfs-cluster-service", "/usr/bin/ipfs-cluster-service") + if err != nil { + return fmt.Errorf("ipfs-cluster-service binary not found: %w", err) + } + + // Initialize cluster config with ipfs-cluster-service init + // This creates the service.json file with all required sections + fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Initializing IPFS Cluster config...\n") + cmd := exec.Command(clusterBinary, "init", "--force") + cmd.Env = append(os.Environ(), "IPFS_CLUSTER_PATH="+clusterPath) + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to initialize IPFS Cluster config: %v\n%s", err, string(output)) + } + + // Update the cluster secret in service.json if provided + if clusterSecret != "" { + if err := bi.updateClusterSecret(clusterPath, clusterSecret); err != nil { + return fmt.Errorf("failed to update cluster secret: %w", err) + } + } + + // Fix ownership again after init + exec.Command("chown", "-R", "debros:debros", clusterPath).Run() + + return nil +} + +// updateClusterSecret updates the secret field in IPFS Cluster service.json +func (bi *BinaryInstaller) updateClusterSecret(clusterPath, secret string) error { + serviceJSONPath := filepath.Join(clusterPath, "service.json") + + // Read existing config + data, err := os.ReadFile(serviceJSONPath) + if err != nil { + return fmt.Errorf("failed to read service.json: %w", err) + } + + // Parse JSON + var config map[string]interface{} + if err := json.Unmarshal(data, &config); err != nil { + return fmt.Errorf("failed to parse service.json: %w", err) + } + + // Update cluster secret + if cluster, ok := config["cluster"].(map[string]interface{}); ok { + cluster["secret"] = secret + } else { + config["cluster"] = map[string]interface{}{ + "secret": secret, + } + } + + // Write back + updatedData, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal service.json: %w", err) + } + + if err := os.WriteFile(serviceJSONPath, updatedData, 0644); err != nil { + return fmt.Errorf("failed to write service.json: %w", err) + } + return nil } diff --git a/pkg/environments/production/orchestrator.go b/pkg/environments/production/orchestrator.go index 06ba571..e28b7f6 100644 --- a/pkg/environments/production/orchestrator.go +++ b/pkg/environments/production/orchestrator.go @@ -231,10 +231,14 @@ func (ps *ProductionSetup) Phase2cInitializeServices(nodeType string) error { return fmt.Errorf("failed to initialize IPFS repo: %w", err) } - // Initialize IPFS Cluster path (just ensure directory exists, actual init happens in daemon startup) + // Initialize IPFS Cluster config (runs ipfs-cluster-service init) clusterPath := filepath.Join(dataDir, "ipfs-cluster") - if err := ps.binaryInstaller.InitializeIPFSClusterConfig(nodeType, clusterPath, "", 4501); err != nil { - ps.logf(" ⚠️ IPFS Cluster initialization warning: %v", err) + clusterSecret, err := ps.secretGenerator.EnsureClusterSecret() + if err != nil { + return fmt.Errorf("failed to get cluster secret: %w", err) + } + if err := ps.binaryInstaller.InitializeIPFSClusterConfig(nodeType, clusterPath, clusterSecret, 4501); err != nil { + return fmt.Errorf("failed to initialize IPFS Cluster: %w", err) } // Initialize RQLite data directory