From 17fc78975d02a6754aff2d9cbd66c351100922e2 Mon Sep 17 00:00:00 2001 From: anonpenguin23 Date: Mon, 10 Nov 2025 06:03:40 +0200 Subject: [PATCH] refactor: reorder production installation phases and enhance service initialization - Adjusted the installation sequence to generate secrets before initializing services, ensuring necessary keys are in place. - Updated service initialization to account for both bootstrap and node variants, improving service status reporting. - Enhanced error handling during IPFS repo and cluster path initialization, providing clearer feedback on failures. - Captured the node peer ID for logging after secret generation, improving visibility during production setup. --- CHANGELOG.md | 16 +++++ Makefile | 2 +- pkg/cli/prod_commands.go | 71 ++++++++++++++------- pkg/environments/production/installers.go | 55 +++++----------- pkg/environments/production/orchestrator.go | 28 ++++---- pkg/environments/production/services.go | 6 +- 6 files changed, 97 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1aa70d..b5bc041 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.62.0] - 2025-11-10 + +### Added +- The `prod status` command now correctly checks for both 'bootstrap' and 'node' service variants. + +### Changed +- The production installation process now generates secrets (like the cluster secret and peer ID) before initializing services. This ensures all necessary secrets are available when services start. +- The `prod install` command now displays the actual Peer ID upon completion instead of a placeholder. + +### Deprecated + +### Removed + +### Fixed +- Fixed an issue where IPFS Cluster initialization was using a hardcoded configuration file instead of relying on the standard `ipfs-cluster-service init` process. + ## [0.61.0] - 2025-11-10 ### Added diff --git a/Makefile b/Makefile index 227ded6..389194b 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,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.61.0 +VERSION := 0.62.0 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/pkg/cli/prod_commands.go b/pkg/cli/prod_commands.go index dc5fbdd..fe66b05 100644 --- a/pkg/cli/prod_commands.go +++ b/pkg/cli/prod_commands.go @@ -128,24 +128,27 @@ func handleProdInstall(args []string) { os.Exit(1) } - // Phase 2c: Initialize services + // Determine node type early nodeType := "node" if isBootstrap { nodeType = "bootstrap" } - fmt.Printf("\nPhase 2c: Initializing services...\n") - if err := setup.Phase2cInitializeServices(nodeType); err != nil { - fmt.Fprintf(os.Stderr, "āŒ Service initialization failed: %v\n", err) - os.Exit(1) - } - // Phase 3: Generate secrets + // Phase 3: Generate secrets FIRST (before service initialization) + // This ensures cluster secret and swarm key exist before repos are seeded fmt.Printf("\nšŸ” Phase 3: Generating secrets...\n") if err := setup.Phase3GenerateSecrets(isBootstrap); err != nil { fmt.Fprintf(os.Stderr, "āŒ Secret generation failed: %v\n", err) os.Exit(1) } + // Phase 2c: Initialize services (after secrets are in place) + fmt.Printf("\nPhase 2c: Initializing services...\n") + if err := setup.Phase2cInitializeServices(nodeType); err != nil { + fmt.Fprintf(os.Stderr, "āŒ Service initialization failed: %v\n", err) + os.Exit(1) + } + // Phase 4: Generate configs fmt.Printf("\nāš™ļø Phase 4: Generating configurations...\n") enableHTTPS := domain != "" @@ -161,8 +164,8 @@ func handleProdInstall(args []string) { os.Exit(1) } - // Log completion - setup.LogSetupComplete("< peer ID from config >") + // Log completion with actual peer ID + setup.LogSetupComplete(setup.NodePeerID) fmt.Printf("āœ… Production installation complete!\n\n") } @@ -205,27 +208,49 @@ func handleProdUpgrade(args []string) { func handleProdStatus() { fmt.Printf("Production Environment Status\n\n") - servicesList := []struct { - name string - desc string - }{ - {"debros-ipfs-bootstrap", "IPFS Daemon (Bootstrap)"}, - {"debros-ipfs-cluster-bootstrap", "IPFS Cluster (Bootstrap)"}, - {"debros-rqlite-bootstrap", "RQLite Database (Bootstrap)"}, - {"debros-olric", "Olric Cache Server"}, - {"debros-node-bootstrap", "DeBros Node (Bootstrap)"}, - {"debros-gateway", "DeBros Gateway"}, + // Check for all possible service names (bootstrap and node variants) + serviceNames := []string{ + "debros-ipfs-bootstrap", + "debros-ipfs-node", + "debros-ipfs-cluster-bootstrap", + "debros-ipfs-cluster-node", + "debros-rqlite-bootstrap", + "debros-rqlite-node", + "debros-olric", + "debros-node-bootstrap", + "debros-node-node", + "debros-gateway", + } + + // Friendly descriptions + descriptions := map[string]string{ + "debros-ipfs-bootstrap": "IPFS Daemon (Bootstrap)", + "debros-ipfs-node": "IPFS Daemon (Node)", + "debros-ipfs-cluster-bootstrap": "IPFS Cluster (Bootstrap)", + "debros-ipfs-cluster-node": "IPFS Cluster (Node)", + "debros-rqlite-bootstrap": "RQLite Database (Bootstrap)", + "debros-rqlite-node": "RQLite Database (Node)", + "debros-olric": "Olric Cache Server", + "debros-node-bootstrap": "DeBros Node (Bootstrap)", + "debros-node-node": "DeBros Node (Node)", + "debros-gateway": "DeBros Gateway", } fmt.Printf("Services:\n") - for _, svc := range servicesList { - cmd := "systemctl" - err := exec.Command(cmd, "is-active", "--quiet", svc.name).Run() + found := false + for _, svc := range serviceNames { + cmd := exec.Command("systemctl", "is-active", "--quiet", svc) + err := cmd.Run() status := "āŒ Inactive" if err == nil { status = "āœ… Active" + found = true } - fmt.Printf(" %s: %s\n", status, svc.desc) + fmt.Printf(" %s: %s\n", status, descriptions[svc]) + } + + if !found { + fmt.Printf(" (No services found - installation may be incomplete)\n") } fmt.Printf("\nDirectories:\n") diff --git a/pkg/environments/production/installers.go b/pkg/environments/production/installers.go index f8f9410..33416ae 100644 --- a/pkg/environments/production/installers.go +++ b/pkg/environments/production/installers.go @@ -5,7 +5,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" ) // BinaryInstaller handles downloading and installing external binaries @@ -227,9 +226,11 @@ func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swa fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Initializing IPFS repo for %s...\n", nodeType) - os.MkdirAll(ipfsRepoPath, 0755) + if err := os.MkdirAll(ipfsRepoPath, 0755); err != nil { + return fmt.Errorf("failed to create IPFS repo directory: %w", err) + } - // Initialize IPFS + // Initialize IPFS with the correct repo path cmd := exec.Command("ipfs", "init", "--profile=server", "--repo-dir="+ipfsRepoPath) if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("failed to initialize IPFS: %v\n%s", err, string(output)) @@ -237,13 +238,20 @@ func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swa // Copy swarm key if present if data, err := os.ReadFile(swarmKeyPath); err == nil { - os.WriteFile(filepath.Join(ipfsRepoPath, "swarm.key"), data, 0600) + if err := os.WriteFile(filepath.Join(ipfsRepoPath, "swarm.key"), data, 0600); err != nil { + return fmt.Errorf("failed to copy swarm key: %w", err) + } } + // Fix ownership + exec.Command("chown", "-R", "debros:debros", ipfsRepoPath).Run() + return nil } // 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. func (bi *BinaryInstaller) InitializeIPFSClusterConfig(nodeType, clusterPath, clusterSecret string, ipfsAPIPort int) error { serviceJSONPath := filepath.Join(clusterPath, "service.json") if _, err := os.Stat(serviceJSONPath); err == nil { @@ -251,43 +259,10 @@ func (bi *BinaryInstaller) InitializeIPFSClusterConfig(nodeType, clusterPath, cl return nil } - fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Initializing IPFS Cluster config for %s...\n", nodeType) + fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Preparing IPFS Cluster path for %s...\n", nodeType) - os.MkdirAll(clusterPath, 0755) - - // For now, just create a minimal service.json - // This will be properly configured during service startup - cfgContent := fmt.Sprintf(`{ - "cluster": { - "peername": "%s", - "secret": "%s", - "listen_multiaddress": ["/ip4/0.0.0.0/tcp/9096"], - "leave_on_shutdown": false - }, - "api": { - "restapi": { - "http_listen_multiaddress": "/ip4/0.0.0.0/tcp/9094" - } - }, - "ipfs_connector": { - "ipfshttp": { - "node_multiaddress": "/ip4/127.0.0.1/tcp/%d" - } - }, - "consensus": { - "crdt": { - "cluster_name": "debros", - "trusted_peers": ["*"] - } - }, - "datastore": { - "type": "badger", - "path": "%s/badger" - } -}`, nodeType, strings.TrimSpace(clusterSecret), ipfsAPIPort, clusterPath) - - if err := os.WriteFile(serviceJSONPath, []byte(cfgContent), 0644); err != nil { - return fmt.Errorf("failed to write cluster config: %w", err) + if err := os.MkdirAll(clusterPath, 0755); err != nil { + return fmt.Errorf("failed to create IPFS Cluster directory: %w", err) } exec.Command("chown", "-R", "debros:debros", clusterPath).Run() diff --git a/pkg/environments/production/orchestrator.go b/pkg/environments/production/orchestrator.go index 32d6c0c..90f220e 100644 --- a/pkg/environments/production/orchestrator.go +++ b/pkg/environments/production/orchestrator.go @@ -5,6 +5,7 @@ import ( "io" "os" "os/exec" + "path/filepath" "strings" ) @@ -29,6 +30,7 @@ type ProductionSetup struct { serviceController *SystemdController binaryInstaller *BinaryInstaller branch string + NodePeerID string // Captured during Phase3 for later display } // NewProductionSetup creates a new production setup orchestrator @@ -199,27 +201,23 @@ func (ps *ProductionSetup) Phase2bInstallBinaries() error { func (ps *ProductionSetup) Phase2cInitializeServices(nodeType string) error { ps.logf("Phase 2c: Initializing services...") - // Get cluster secret for IPFS - clusterSecret, err := os.ReadFile(ps.debrosDir + "/secrets/cluster-secret") - if err != nil { - clusterSecret = []byte("") - } + // Build paths with nodeType awareness to match systemd unit definitions + dataDir := filepath.Join(ps.debrosDir, "data", nodeType) - // Initialize IPFS repo - ipfsRepoPath := ps.debrosDir + "/data/ipfs" - if err := ps.binaryInstaller.InitializeIPFSRepo(nodeType, ipfsRepoPath, ps.debrosDir+"/secrets/swarm.key"); err != nil { + // Initialize IPFS repo with correct path structure + ipfsRepoPath := filepath.Join(dataDir, "ipfs", "repo") + if err := ps.binaryInstaller.InitializeIPFSRepo(nodeType, ipfsRepoPath, filepath.Join(ps.debrosDir, "secrets", "swarm.key")); err != nil { ps.logf(" āš ļø IPFS initialization warning: %v", err) } - // Initialize IPFS Cluster config - clusterPath := ps.debrosDir + "/data/ipfs-cluster" - ipfsAPIPort := 4501 - if err := ps.binaryInstaller.InitializeIPFSClusterConfig(nodeType, clusterPath, string(clusterSecret), ipfsAPIPort); err != nil { + // Initialize IPFS Cluster path (just ensure directory exists, actual init happens in daemon startup) + clusterPath := filepath.Join(dataDir, "ipfs-cluster") + if err := ps.binaryInstaller.InitializeIPFSClusterConfig(nodeType, clusterPath, "", 4501); err != nil { ps.logf(" āš ļø IPFS Cluster initialization warning: %v", err) } // Initialize RQLite data directory - rqliteDataDir := ps.debrosDir + "/data/rqlite" + rqliteDataDir := filepath.Join(dataDir, "rqlite") if err := ps.binaryInstaller.InitializeRQLiteDataDir(nodeType, rqliteDataDir); err != nil { ps.logf(" āš ļø RQLite initialization warning: %v", err) } @@ -254,7 +252,9 @@ func (ps *ProductionSetup) Phase3GenerateSecrets(isBootstrap bool) error { if err != nil { return fmt.Errorf("failed to ensure node identity: %w", err) } - ps.logf(" āœ“ Node identity ensured (Peer ID: %s)", peerID.String()) + peerIDStr := peerID.String() + ps.NodePeerID = peerIDStr // Capture for later display + ps.logf(" āœ“ Node identity ensured (Peer ID: %s)", peerIDStr) return nil } diff --git a/pkg/environments/production/services.go b/pkg/environments/production/services.go index d9817b4..b2038ef 100644 --- a/pkg/environments/production/services.go +++ b/pkg/environments/production/services.go @@ -81,8 +81,8 @@ User=debros Group=debros WorkingDirectory=%s Environment=HOME=%s -Environment=CLUSTER_PATH=%s -ExecStart=/usr/local/bin/ipfs-cluster-service daemon --config %s/service.json +Environment=IPFS_CLUSTER_PATH=%s +ExecStart=/usr/local/bin/ipfs-cluster-service daemon Restart=always RestartSec=5 StandardOutput=journal @@ -96,7 +96,7 @@ ReadWritePaths=%s [Install] WantedBy=multi-user.target -`, nodeType, nodeType, nodeType, nodeType, ssg.debrosHome, ssg.debrosHome, clusterPath, clusterPath, nodeType, ssg.debrosDir) +`, nodeType, nodeType, nodeType, nodeType, ssg.debrosHome, ssg.debrosHome, clusterPath, nodeType, ssg.debrosDir) } // GenerateRQLiteService generates the RQLite systemd unit