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.
This commit is contained in:
anonpenguin23 2025-11-10 06:03:40 +02:00
parent 6a86592cad
commit 17fc78975d
No known key found for this signature in database
GPG Key ID: 1CBB1FE35AFBEE30
6 changed files with 97 additions and 81 deletions

View File

@ -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

View File

@ -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)'

View File

@ -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")

View File

@ -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()

View File

@ -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
}

View File

@ -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