feat: enhance gateway configuration handling with optional config flag

- Added support for a `--config` flag in the gateway configuration parser to allow absolute or relative paths for the config file.
- Improved error handling for determining the config path, ensuring robust loading of `gateway.yaml` from specified locations.
- Updated related functions to maintain backward compatibility while enhancing flexibility in configuration management.
This commit is contained in:
anonpenguin23 2025-11-11 08:15:48 +02:00
parent a33d03c6b2
commit b58b632be9
No known key found for this signature in database
GPG Key ID: 1CBB1FE35AFBEE30
8 changed files with 199 additions and 37 deletions

View File

@ -13,6 +13,23 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
### Deprecated ### Deprecated
### Fixed ### Fixed
## [0.67.5] - 2025-11-11
### Added
- Added `--restart` option to `dbn prod upgrade` to automatically restart services after upgrade.
- The gateway now supports an optional `--config` flag to specify the configuration file path.
### Changed
- Improved `dbn prod upgrade` process to better handle existing installations, including detecting node type and ensuring configurations are updated to the latest format.
- Configuration loading logic for `node` and `gateway` commands now correctly handles absolute paths passed via command line or systemd.
### Deprecated
### Removed
### Fixed
- Fixed an issue during production upgrades where IPFS repositories in private swarms might fail to start due to `AutoConf` not being disabled.
## [0.67.4] - 2025-11-11 ## [0.67.4] - 2025-11-11
### Added ### Added

View File

@ -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 .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.4 VERSION := 0.67.5
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)' LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -40,13 +41,35 @@ func getEnvBoolDefault(key string, def bool) bool {
} }
// parseGatewayConfig loads gateway.yaml from ~/.debros exclusively. // parseGatewayConfig loads gateway.yaml from ~/.debros exclusively.
// It accepts an optional --config flag for absolute paths (used by systemd services).
func parseGatewayConfig(logger *logging.ColoredLogger) *gateway.Config { func parseGatewayConfig(logger *logging.ColoredLogger) *gateway.Config {
// Parse --config flag (optional, for systemd services that pass absolute paths)
configFlag := flag.String("config", "", "Config file path (absolute path or filename in ~/.debros)")
flag.Parse()
// Determine config path // Determine config path
configPath, err := config.DefaultPath("gateway.yaml") var configPath string
if err != nil { var err error
logger.ComponentError(logging.ComponentGeneral, "Failed to determine config path", zap.Error(err)) if *configFlag != "" {
fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err) // If --config flag is provided, use it (handles both absolute and relative paths)
os.Exit(1) if filepath.IsAbs(*configFlag) {
configPath = *configFlag
} else {
configPath, err = config.DefaultPath(*configFlag)
if err != nil {
logger.ComponentError(logging.ComponentGeneral, "Failed to determine config path", zap.Error(err))
fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err)
os.Exit(1)
}
}
} else {
// Default behavior: look for gateway.yaml in ~/.debros/configs/ or ~/.debros/
configPath, err = config.DefaultPath("gateway.yaml")
if err != nil {
logger.ComponentError(logging.ComponentGeneral, "Failed to determine config path", zap.Error(err))
fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err)
os.Exit(1)
}
} }
// Load YAML // Load YAML

View File

@ -245,11 +245,14 @@ func main() {
select_data_dir_check(configName) select_data_dir_check(configName)
// Determine config path (handle both absolute and relative paths) // Determine config path (handle both absolute and relative paths)
// Note: select_data_dir_check already validated the path exists, so we can safely determine it here
var configPath string var configPath string
var err error var err error
if filepath.IsAbs(*configName) { if filepath.IsAbs(*configName) {
// Absolute path passed directly (e.g., from systemd service)
configPath = *configName configPath = *configName
} else { } else {
// Relative path - use DefaultPath which checks both ~/.debros/configs/ and ~/.debros/
configPath, err = config.DefaultPath(*configName) configPath, err = config.DefaultPath(*configName)
if err != nil { if err != nil {
logger.Error("Failed to determine config path", zap.Error(err)) logger.Error("Failed to determine config path", zap.Error(err))

View File

@ -54,6 +54,8 @@ func showProdHelp() {
fmt.Printf(" --bootstrap-join ADDR - Bootstrap raft join address (for secondary bootstrap)\n") fmt.Printf(" --bootstrap-join ADDR - Bootstrap raft join address (for secondary bootstrap)\n")
fmt.Printf(" --domain DOMAIN - Domain for HTTPS (optional)\n") fmt.Printf(" --domain DOMAIN - Domain for HTTPS (optional)\n")
fmt.Printf(" upgrade - Upgrade existing installation (requires root/sudo)\n") fmt.Printf(" upgrade - Upgrade existing installation (requires root/sudo)\n")
fmt.Printf(" Options:\n")
fmt.Printf(" --restart - Automatically restart services after upgrade\n")
fmt.Printf(" status - Show status of production services\n") fmt.Printf(" status - Show status of production services\n")
fmt.Printf(" logs <service> - View production service logs\n") fmt.Printf(" logs <service> - View production service logs\n")
fmt.Printf(" Options:\n") fmt.Printf(" Options:\n")
@ -188,10 +190,14 @@ func handleProdInstall(args []string) {
func handleProdUpgrade(args []string) { func handleProdUpgrade(args []string) {
// Parse arguments // Parse arguments
force := false force := false
restartServices := false
for _, arg := range args { for _, arg := range args {
if arg == "--force" { if arg == "--force" {
force = true force = true
} }
if arg == "--restart" {
restartServices = true
}
} }
if os.Geteuid() != 0 { if os.Geteuid() != 0 {
@ -201,24 +207,108 @@ func handleProdUpgrade(args []string) {
debrosHome := "/home/debros" debrosHome := "/home/debros"
fmt.Printf("🔄 Upgrading production installation...\n") fmt.Printf("🔄 Upgrading production installation...\n")
fmt.Printf(" This will preserve existing configurations and data\n\n") fmt.Printf(" This will preserve existing configurations and data\n")
fmt.Printf(" Configurations will be updated to latest format\n\n")
// For now, just re-run the install with force flag
setup := production.NewProductionSetup(debrosHome, os.Stdout, force) setup := production.NewProductionSetup(debrosHome, os.Stdout, force)
// Phase 1: Check prerequisites
fmt.Printf("\n📋 Phase 1: Checking prerequisites...\n")
if err := setup.Phase1CheckPrerequisites(); err != nil { if err := setup.Phase1CheckPrerequisites(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Prerequisites check failed: %v\n", err) fmt.Fprintf(os.Stderr, "❌ Prerequisites check failed: %v\n", err)
os.Exit(1) os.Exit(1)
} }
// Phase 2: Provision environment (ensures directories exist)
fmt.Printf("\n🛠 Phase 2: Provisioning environment...\n")
if err := setup.Phase2ProvisionEnvironment(); err != nil { if err := setup.Phase2ProvisionEnvironment(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Environment provisioning failed: %v\n", err) fmt.Fprintf(os.Stderr, "❌ Environment provisioning failed: %v\n", err)
os.Exit(1) os.Exit(1)
} }
fmt.Printf("✅ Upgrade complete!\n") // Phase 2b: Install/update binaries
fmt.Printf(" Services will use existing configurations\n") fmt.Printf("\nPhase 2b: Installing/updating binaries...\n")
fmt.Printf(" To restart services: sudo systemctl restart debros-*\n\n") if err := setup.Phase2bInstallBinaries(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Binary installation failed: %v\n", err)
os.Exit(1)
}
// Detect node type from existing installation
nodeType := "node"
if setup.IsUpdate() {
// Check if bootstrap config exists
bootstrapConfig := filepath.Join("/home/debros/.debros", "configs", "bootstrap.yaml")
if _, err := os.Stat(bootstrapConfig); err == nil {
nodeType = "bootstrap"
} else {
// Check data directory structure
bootstrapDataPath := filepath.Join("/home/debros/.debros", "data", "bootstrap")
if _, err := os.Stat(bootstrapDataPath); err == nil {
nodeType = "bootstrap"
}
}
fmt.Printf(" Detected node type: %s\n", nodeType)
} else {
fmt.Printf(" ⚠️ No existing installation detected, treating as fresh install\n")
fmt.Printf(" Use 'dbn prod install --bootstrap' for fresh bootstrap installation\n")
nodeType = "bootstrap" // Default for upgrade if nothing exists
}
// Phase 2c: Ensure services are properly initialized (fixes existing repos)
fmt.Printf("\nPhase 2c: Ensuring services are properly initialized...\n")
if err := setup.Phase2cInitializeServices(nodeType); err != nil {
fmt.Fprintf(os.Stderr, "❌ Service initialization failed: %v\n", err)
os.Exit(1)
}
// Phase 3: Ensure secrets exist (preserves existing secrets)
fmt.Printf("\n🔐 Phase 3: Ensuring secrets...\n")
if err := setup.Phase3GenerateSecrets(nodeType == "bootstrap"); err != nil {
fmt.Fprintf(os.Stderr, "❌ Secret generation failed: %v\n", err)
os.Exit(1)
}
// Phase 4: Regenerate configs (updates to latest format)
// Note: This will overwrite existing configs, but preserves secrets
bootstrapPeers := []string{} // Could be read from existing config if needed
enableHTTPS := false
domain := ""
bootstrapJoin := ""
if err := setup.Phase4GenerateConfigs(nodeType == "bootstrap", bootstrapPeers, "", enableHTTPS, domain, bootstrapJoin); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Config generation warning: %v\n", err)
fmt.Fprintf(os.Stderr, " Existing configs preserved\n")
}
// Phase 5: Update systemd services
fmt.Printf("\n🔧 Phase 5: Updating systemd services...\n")
if err := setup.Phase5CreateSystemdServices(nodeType, ""); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Service update warning: %v\n", err)
}
fmt.Printf("\n✅ Upgrade complete!\n")
if restartServices {
fmt.Printf(" Restarting services...\n")
// Reload systemd daemon
exec.Command("systemctl", "daemon-reload").Run()
// Restart services to apply changes
services := []string{
"debros-ipfs-bootstrap",
"debros-ipfs-cluster-bootstrap",
"debros-rqlite-bootstrap",
"debros-olric",
"debros-node-bootstrap",
"debros-gateway",
}
for _, svc := range services {
exec.Command("systemctl", "restart", svc).Run()
}
fmt.Printf(" ✓ Services restarted\n")
} else {
fmt.Printf(" To apply changes, restart services:\n")
fmt.Printf(" sudo systemctl daemon-reload\n")
fmt.Printf(" sudo systemctl restart debros-*\n")
}
fmt.Printf("\n")
} }
func handleProdStatus() { func handleProdStatus() {

View File

@ -30,7 +30,13 @@ func EnsureConfigDir() (string, error) {
// DefaultPath returns the path to the config file for the given component name. // DefaultPath returns the path to the config file for the given component name.
// component should be e.g., "node.yaml", "bootstrap.yaml", "gateway.yaml" // component should be e.g., "node.yaml", "bootstrap.yaml", "gateway.yaml"
// It checks both ~/.debros/ and ~/.debros/configs/ for backward compatibility. // It checks both ~/.debros/ and ~/.debros/configs/ for backward compatibility.
// If component is already an absolute path, it returns it as-is.
func DefaultPath(component string) (string, error) { func DefaultPath(component string) (string, error) {
// If component is already an absolute path, return it directly
if filepath.IsAbs(component) {
return component, nil
}
dir, err := ConfigDir() dir, err := ConfigDir()
if err != nil { if err != nil {
return "", err return "", err

View File

@ -340,13 +340,14 @@ func (bi *BinaryInstaller) InstallSystemDependencies() error {
// InitializeIPFSRepo initializes an IPFS repository for a node // InitializeIPFSRepo initializes an IPFS repository for a node
func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swarmKeyPath string) error { func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swarmKeyPath string) error {
configPath := filepath.Join(ipfsRepoPath, "config") configPath := filepath.Join(ipfsRepoPath, "config")
repoExists := false
if _, err := os.Stat(configPath); err == nil { if _, err := os.Stat(configPath); err == nil {
// Already initialized repoExists = true
return nil fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " IPFS repo for %s already exists, ensuring configuration...\n", nodeType)
} else {
fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Initializing IPFS repo for %s...\n", nodeType)
} }
fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Initializing IPFS repo for %s...\n", nodeType)
if err := os.MkdirAll(ipfsRepoPath, 0755); err != nil { if err := os.MkdirAll(ipfsRepoPath, 0755); err != nil {
return fmt.Errorf("failed to create IPFS repo directory: %w", err) return fmt.Errorf("failed to create IPFS repo directory: %w", err)
} }
@ -357,25 +358,30 @@ func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swa
return err return err
} }
// Initialize IPFS with the correct repo path // Initialize IPFS if repo doesn't exist
cmd := exec.Command(ipfsBinary, "init", "--profile=server", "--repo-dir="+ipfsRepoPath) if !repoExists {
if output, err := cmd.CombinedOutput(); err != nil { cmd := exec.Command(ipfsBinary, "init", "--profile=server", "--repo-dir="+ipfsRepoPath)
return fmt.Errorf("failed to initialize IPFS: %v\n%s", err, string(output)) if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to initialize IPFS: %v\n%s", err, string(output))
}
} }
// Copy swarm key if present // Copy swarm key if present
swarmKeyExists := false swarmKeyExists := false
if data, err := os.ReadFile(swarmKeyPath); err == nil { if data, err := os.ReadFile(swarmKeyPath); err == nil {
if err := os.WriteFile(filepath.Join(ipfsRepoPath, "swarm.key"), data, 0600); err != nil { swarmKeyDest := filepath.Join(ipfsRepoPath, "swarm.key")
if err := os.WriteFile(swarmKeyDest, data, 0600); err != nil {
return fmt.Errorf("failed to copy swarm key: %w", err) return fmt.Errorf("failed to copy swarm key: %w", err)
} }
swarmKeyExists = true swarmKeyExists = true
} }
// Disable AutoConf for private swarm (required when swarm.key is present) // Always disable AutoConf for private swarm when swarm.key is present
// This prevents IPFS from trying to use the public mainnet AutoConf service // This is critical - IPFS will fail to start if AutoConf is enabled on a private network
// We do this even for existing repos to fix repos initialized before this fix was applied
if swarmKeyExists { if swarmKeyExists {
cmd = exec.Command(ipfsBinary, "config", "--json", "AutoConf.Enabled", "false") fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Disabling AutoConf for private swarm...\n")
cmd := exec.Command(ipfsBinary, "config", "--json", "AutoConf.Enabled", "false")
cmd.Env = append(os.Environ(), "IPFS_PATH="+ipfsRepoPath) cmd.Env = append(os.Environ(), "IPFS_PATH="+ipfsRepoPath)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to disable AutoConf: %v\n%s", err, string(output)) return fmt.Errorf("failed to disable AutoConf: %v\n%s", err, string(output))
@ -390,15 +396,17 @@ func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swa
// InitializeIPFSClusterConfig initializes IPFS Cluster configuration // InitializeIPFSClusterConfig initializes IPFS Cluster configuration
// This runs `ipfs-cluster-service init` to create the service.json configuration file. // This runs `ipfs-cluster-service init` to create the service.json configuration file.
// For existing installations, it ensures the cluster secret is up to date.
func (bi *BinaryInstaller) InitializeIPFSClusterConfig(nodeType, clusterPath, clusterSecret string, ipfsAPIPort int) error { func (bi *BinaryInstaller) InitializeIPFSClusterConfig(nodeType, clusterPath, clusterSecret string, ipfsAPIPort int) error {
serviceJSONPath := filepath.Join(clusterPath, "service.json") serviceJSONPath := filepath.Join(clusterPath, "service.json")
configExists := false
if _, err := os.Stat(serviceJSONPath); err == nil { if _, err := os.Stat(serviceJSONPath); err == nil {
// Already initialized configExists = true
return nil fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " IPFS Cluster config for %s already exists, ensuring it's up to date...\n", nodeType)
} else {
fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Preparing IPFS Cluster path for %s...\n", nodeType)
} }
fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Preparing IPFS Cluster path for %s...\n", nodeType)
if err := os.MkdirAll(clusterPath, 0755); err != nil { if err := os.MkdirAll(clusterPath, 0755); err != nil {
return fmt.Errorf("failed to create IPFS Cluster directory: %w", err) return fmt.Errorf("failed to create IPFS Cluster directory: %w", err)
} }
@ -412,23 +420,28 @@ func (bi *BinaryInstaller) InitializeIPFSClusterConfig(nodeType, clusterPath, cl
return fmt.Errorf("ipfs-cluster-service binary not found: %w", err) return fmt.Errorf("ipfs-cluster-service binary not found: %w", err)
} }
// Initialize cluster config with ipfs-cluster-service init // Initialize cluster config if it doesn't exist
// This creates the service.json file with all required sections if !configExists {
fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Initializing IPFS Cluster config...\n") // Initialize cluster config with ipfs-cluster-service init
cmd := exec.Command(clusterBinary, "init", "--force") // This creates the service.json file with all required sections
cmd.Env = append(os.Environ(), "IPFS_CLUSTER_PATH="+clusterPath) fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Initializing IPFS Cluster config...\n")
if output, err := cmd.CombinedOutput(); err != nil { cmd := exec.Command(clusterBinary, "init", "--force")
return fmt.Errorf("failed to initialize IPFS Cluster config: %v\n%s", err, string(output)) 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 // Always update the cluster secret (for both new and existing configs)
// This ensures existing installations get the secret synchronized
if clusterSecret != "" { if clusterSecret != "" {
fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Updating cluster secret...\n")
if err := bi.updateClusterSecret(clusterPath, clusterSecret); err != nil { if err := bi.updateClusterSecret(clusterPath, clusterSecret); err != nil {
return fmt.Errorf("failed to update cluster secret: %w", err) return fmt.Errorf("failed to update cluster secret: %w", err)
} }
} }
// Fix ownership again after init // Fix ownership again after updates
exec.Command("chown", "-R", "debros:debros", clusterPath).Run() exec.Command("chown", "-R", "debros:debros", clusterPath).Run()
return nil return nil

View File

@ -68,6 +68,11 @@ func (ps *ProductionSetup) logf(format string, args ...interface{}) {
} }
} }
// IsUpdate detects if this is an update to an existing installation
func (ps *ProductionSetup) IsUpdate() bool {
return ps.stateDetector.IsConfigured() || ps.stateDetector.HasIPFSData()
}
// Phase1CheckPrerequisites performs initial environment validation // Phase1CheckPrerequisites performs initial environment validation
func (ps *ProductionSetup) Phase1CheckPrerequisites() error { func (ps *ProductionSetup) Phase1CheckPrerequisites() error {
ps.logf("Phase 1: Checking prerequisites...") ps.logf("Phase 1: Checking prerequisites...")
@ -286,7 +291,12 @@ func (ps *ProductionSetup) Phase3GenerateSecrets(isBootstrap bool) error {
// Phase4GenerateConfigs generates node, gateway, and service configs // Phase4GenerateConfigs generates node, gateway, and service configs
func (ps *ProductionSetup) Phase4GenerateConfigs(isBootstrap bool, bootstrapPeers []string, vpsIP string, enableHTTPS bool, domain string, bootstrapJoin string) error { func (ps *ProductionSetup) Phase4GenerateConfigs(isBootstrap bool, bootstrapPeers []string, vpsIP string, enableHTTPS bool, domain string, bootstrapJoin string) error {
ps.logf("Phase 4: Generating configurations...") if ps.IsUpdate() {
ps.logf("Phase 4: Updating configurations...")
ps.logf(" (Existing configs will be updated to latest format)")
} else {
ps.logf("Phase 4: Generating configurations...")
}
// Node config // Node config
nodeConfig, err := ps.configGenerator.GenerateNodeConfig(isBootstrap, bootstrapPeers, vpsIP, bootstrapJoin) nodeConfig, err := ps.configGenerator.GenerateNodeConfig(isBootstrap, bootstrapPeers, vpsIP, bootstrapJoin)