mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-12 23:18:49 +00:00
- Added a new method `ResolveBinaryPath` to locate required executables in the system PATH and specified extra paths. - Updated `InitializeIPFSRepo` and `Phase5CreateSystemdServices` methods to utilize the new binary resolution logic for IPFS, IPFS Cluster, RQLite, and Olric services. - Modified service generation functions to accept binary paths as parameters, ensuring correct executable paths are used in systemd unit files.
495 lines
17 KiB
Go
495 lines
17 KiB
Go
package production
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ProductionSetup orchestrates the entire production deployment
|
|
type ProductionSetup struct {
|
|
osInfo *OSInfo
|
|
arch string
|
|
debrosHome string
|
|
debrosDir string
|
|
logWriter io.Writer
|
|
forceReconfigure bool
|
|
skipOptionalDeps bool
|
|
privChecker *PrivilegeChecker
|
|
osDetector *OSDetector
|
|
archDetector *ArchitectureDetector
|
|
resourceChecker *ResourceChecker
|
|
fsProvisioner *FilesystemProvisioner
|
|
userProvisioner *UserProvisioner
|
|
stateDetector *StateDetector
|
|
configGenerator *ConfigGenerator
|
|
secretGenerator *SecretGenerator
|
|
serviceGenerator *SystemdServiceGenerator
|
|
serviceController *SystemdController
|
|
binaryInstaller *BinaryInstaller
|
|
branch string
|
|
NodePeerID string // Captured during Phase3 for later display
|
|
}
|
|
|
|
// NewProductionSetup creates a new production setup orchestrator
|
|
func NewProductionSetup(debrosHome string, logWriter io.Writer, forceReconfigure bool) *ProductionSetup {
|
|
debrosDir := debrosHome + "/.debros"
|
|
arch, _ := (&ArchitectureDetector{}).Detect()
|
|
|
|
return &ProductionSetup{
|
|
debrosHome: debrosHome,
|
|
debrosDir: debrosDir,
|
|
logWriter: logWriter,
|
|
forceReconfigure: forceReconfigure,
|
|
arch: arch,
|
|
branch: "main",
|
|
privChecker: &PrivilegeChecker{},
|
|
osDetector: &OSDetector{},
|
|
archDetector: &ArchitectureDetector{},
|
|
resourceChecker: NewResourceChecker(),
|
|
fsProvisioner: NewFilesystemProvisioner(debrosHome),
|
|
userProvisioner: NewUserProvisioner("debros", debrosHome, "/bin/bash"),
|
|
stateDetector: NewStateDetector(debrosDir),
|
|
configGenerator: NewConfigGenerator(debrosDir),
|
|
secretGenerator: NewSecretGenerator(debrosDir),
|
|
serviceGenerator: NewSystemdServiceGenerator(debrosHome, debrosDir),
|
|
serviceController: NewSystemdController(),
|
|
binaryInstaller: NewBinaryInstaller(arch, logWriter),
|
|
}
|
|
}
|
|
|
|
// logf writes a formatted message to the log writer
|
|
func (ps *ProductionSetup) logf(format string, args ...interface{}) {
|
|
if ps.logWriter != nil {
|
|
fmt.Fprintf(ps.logWriter, format+"\n", args...)
|
|
}
|
|
}
|
|
|
|
// Phase1CheckPrerequisites performs initial environment validation
|
|
func (ps *ProductionSetup) Phase1CheckPrerequisites() error {
|
|
ps.logf("Phase 1: Checking prerequisites...")
|
|
|
|
// Check root
|
|
if err := ps.privChecker.CheckRoot(); err != nil {
|
|
return fmt.Errorf("privilege check failed: %w", err)
|
|
}
|
|
ps.logf(" ✓ Running as root")
|
|
|
|
// Check Linux OS
|
|
if err := ps.privChecker.CheckLinuxOS(); err != nil {
|
|
return fmt.Errorf("OS check failed: %w", err)
|
|
}
|
|
ps.logf(" ✓ Running on Linux")
|
|
|
|
// Detect OS
|
|
osInfo, err := ps.osDetector.Detect()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to detect OS: %w", err)
|
|
}
|
|
ps.osInfo = osInfo
|
|
ps.logf(" ✓ Detected OS: %s", osInfo.Name)
|
|
|
|
// Check if supported
|
|
if !ps.osDetector.IsSupportedOS(osInfo) {
|
|
ps.logf(" ⚠️ OS %s is not officially supported (Ubuntu 22/24/25, Debian 12)", osInfo.Name)
|
|
ps.logf(" Proceeding anyway, but issues may occur")
|
|
}
|
|
|
|
// Detect architecture
|
|
arch, err := ps.archDetector.Detect()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to detect architecture: %w", err)
|
|
}
|
|
ps.arch = arch
|
|
ps.logf(" ✓ Detected architecture: %s", arch)
|
|
|
|
// Check basic dependencies
|
|
depChecker := NewDependencyChecker(ps.skipOptionalDeps)
|
|
if missing, err := depChecker.CheckAll(); err != nil {
|
|
ps.logf(" ❌ Missing dependencies:")
|
|
for _, dep := range missing {
|
|
ps.logf(" - %s: %s", dep.Name, dep.InstallHint)
|
|
}
|
|
return err
|
|
}
|
|
ps.logf(" ✓ Basic dependencies available")
|
|
|
|
// Check system resources
|
|
if err := ps.resourceChecker.CheckDiskSpace(ps.debrosHome); err != nil {
|
|
ps.logf(" ❌ %v", err)
|
|
return err
|
|
}
|
|
ps.logf(" ✓ Sufficient disk space available")
|
|
|
|
if err := ps.resourceChecker.CheckRAM(); err != nil {
|
|
ps.logf(" ❌ %v", err)
|
|
return err
|
|
}
|
|
ps.logf(" ✓ Sufficient RAM available")
|
|
|
|
if err := ps.resourceChecker.CheckCPU(); err != nil {
|
|
ps.logf(" ❌ %v", err)
|
|
return err
|
|
}
|
|
ps.logf(" ✓ Sufficient CPU cores available")
|
|
|
|
return nil
|
|
}
|
|
|
|
// Phase2ProvisionEnvironment sets up users and filesystems
|
|
func (ps *ProductionSetup) Phase2ProvisionEnvironment() error {
|
|
ps.logf("Phase 2: Provisioning environment...")
|
|
|
|
// Create debros user
|
|
if !ps.userProvisioner.UserExists() {
|
|
if err := ps.userProvisioner.CreateUser(); err != nil {
|
|
return fmt.Errorf("failed to create debros user: %w", err)
|
|
}
|
|
ps.logf(" ✓ Created 'debros' user")
|
|
} else {
|
|
ps.logf(" ✓ 'debros' user already exists")
|
|
}
|
|
|
|
// Set up sudoers access if invoked via sudo
|
|
sudoUser := os.Getenv("SUDO_USER")
|
|
if sudoUser != "" {
|
|
if err := ps.userProvisioner.SetupSudoersAccess(sudoUser); err != nil {
|
|
ps.logf(" ⚠️ Failed to setup sudoers: %v", err)
|
|
} else {
|
|
ps.logf(" ✓ Sudoers access configured")
|
|
}
|
|
}
|
|
|
|
// Create directory structure
|
|
if err := ps.fsProvisioner.EnsureDirectoryStructure(); err != nil {
|
|
return fmt.Errorf("failed to create directory structure: %w", err)
|
|
}
|
|
ps.logf(" ✓ Directory structure created")
|
|
|
|
// Fix ownership
|
|
if err := ps.fsProvisioner.FixOwnership(); err != nil {
|
|
return fmt.Errorf("failed to fix ownership: %w", err)
|
|
}
|
|
ps.logf(" ✓ Ownership fixed")
|
|
|
|
return nil
|
|
}
|
|
|
|
// Phase2bInstallBinaries installs external binaries and DeBros components
|
|
func (ps *ProductionSetup) Phase2bInstallBinaries() error {
|
|
ps.logf("Phase 2b: Installing binaries...")
|
|
|
|
// Install system dependencies
|
|
if err := ps.binaryInstaller.InstallSystemDependencies(); err != nil {
|
|
ps.logf(" ⚠️ System dependencies warning: %v", err)
|
|
}
|
|
|
|
// Install Go if not present
|
|
if err := ps.binaryInstaller.InstallGo(); err != nil {
|
|
return fmt.Errorf("failed to install Go: %w", err)
|
|
}
|
|
|
|
// Install binaries
|
|
if err := ps.binaryInstaller.InstallRQLite(); err != nil {
|
|
ps.logf(" ⚠️ RQLite install warning: %v", err)
|
|
}
|
|
|
|
if err := ps.binaryInstaller.InstallIPFS(); err != nil {
|
|
ps.logf(" ⚠️ IPFS install warning: %v", err)
|
|
}
|
|
|
|
if err := ps.binaryInstaller.InstallIPFSCluster(); err != nil {
|
|
ps.logf(" ⚠️ IPFS Cluster install warning: %v", err)
|
|
}
|
|
|
|
if err := ps.binaryInstaller.InstallOlric(); err != nil {
|
|
ps.logf(" ⚠️ Olric install warning: %v", err)
|
|
}
|
|
|
|
// Install DeBros binaries
|
|
if err := ps.binaryInstaller.InstallDeBrosBinaries(ps.branch, ps.debrosHome); err != nil {
|
|
return fmt.Errorf("failed to install DeBros binaries: %w", err)
|
|
}
|
|
|
|
ps.logf(" ✓ All binaries installed")
|
|
return nil
|
|
}
|
|
|
|
// Phase2cInitializeServices initializes service repositories and configurations
|
|
func (ps *ProductionSetup) Phase2cInitializeServices(nodeType string) error {
|
|
ps.logf("Phase 2c: Initializing services...")
|
|
|
|
// Build paths with nodeType awareness to match systemd unit definitions
|
|
dataDir := filepath.Join(ps.debrosDir, "data", nodeType)
|
|
|
|
// 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 {
|
|
return fmt.Errorf("failed to initialize IPFS repo: %w", err)
|
|
}
|
|
|
|
// 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 := filepath.Join(dataDir, "rqlite")
|
|
if err := ps.binaryInstaller.InitializeRQLiteDataDir(nodeType, rqliteDataDir); err != nil {
|
|
ps.logf(" ⚠️ RQLite initialization warning: %v", err)
|
|
}
|
|
|
|
ps.logf(" ✓ Services initialized")
|
|
return nil
|
|
}
|
|
|
|
// Phase3GenerateSecrets generates shared secrets and keys
|
|
func (ps *ProductionSetup) Phase3GenerateSecrets(isBootstrap bool) error {
|
|
ps.logf("Phase 3: Generating secrets...")
|
|
|
|
// Cluster secret
|
|
if _, err := ps.secretGenerator.EnsureClusterSecret(); err != nil {
|
|
return fmt.Errorf("failed to ensure cluster secret: %w", err)
|
|
}
|
|
ps.logf(" ✓ Cluster secret ensured")
|
|
|
|
// Swarm key
|
|
if _, err := ps.secretGenerator.EnsureSwarmKey(); err != nil {
|
|
return fmt.Errorf("failed to ensure swarm key: %w", err)
|
|
}
|
|
ps.logf(" ✓ IPFS swarm key ensured")
|
|
|
|
// Node identity
|
|
nodeType := "node"
|
|
if isBootstrap {
|
|
nodeType = "bootstrap"
|
|
}
|
|
|
|
peerID, err := ps.secretGenerator.EnsureNodeIdentity(nodeType)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to ensure node identity: %w", err)
|
|
}
|
|
peerIDStr := peerID.String()
|
|
ps.NodePeerID = peerIDStr // Capture for later display
|
|
ps.logf(" ✓ Node identity ensured (Peer ID: %s)", peerIDStr)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Phase4GenerateConfigs generates node, gateway, and service configs
|
|
func (ps *ProductionSetup) Phase4GenerateConfigs(isBootstrap bool, bootstrapPeers []string, vpsIP string, enableHTTPS bool, domain string, bootstrapJoin string) error {
|
|
ps.logf("Phase 4: Generating configurations...")
|
|
|
|
// Node config
|
|
nodeConfig, err := ps.configGenerator.GenerateNodeConfig(isBootstrap, bootstrapPeers, vpsIP, bootstrapJoin)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate node config: %w", err)
|
|
}
|
|
|
|
var configFile string
|
|
if isBootstrap {
|
|
configFile = "bootstrap.yaml"
|
|
} else {
|
|
configFile = "node.yaml"
|
|
}
|
|
|
|
if err := ps.secretGenerator.SaveConfig(configFile, nodeConfig); err != nil {
|
|
return fmt.Errorf("failed to save node config: %w", err)
|
|
}
|
|
ps.logf(" ✓ Node config generated: %s", configFile)
|
|
|
|
// Gateway config
|
|
olricServers := []string{"127.0.0.1:3320"}
|
|
gatewayConfig, err := ps.configGenerator.GenerateGatewayConfig(bootstrapPeers, enableHTTPS, domain, olricServers)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate gateway config: %w", err)
|
|
}
|
|
|
|
if err := ps.secretGenerator.SaveConfig("gateway.yaml", gatewayConfig); err != nil {
|
|
return fmt.Errorf("failed to save gateway config: %w", err)
|
|
}
|
|
ps.logf(" ✓ Gateway config generated")
|
|
|
|
// Olric config
|
|
olricConfig, err := ps.configGenerator.GenerateOlricConfig("localhost", 3320, 3322)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate olric config: %w", err)
|
|
}
|
|
|
|
// Create olric config directory
|
|
olricConfigDir := ps.debrosDir + "/configs/olric"
|
|
if err := os.MkdirAll(olricConfigDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create olric config directory: %w", err)
|
|
}
|
|
|
|
olricConfigPath := olricConfigDir + "/config.yaml"
|
|
if err := os.WriteFile(olricConfigPath, []byte(olricConfig), 0644); err != nil {
|
|
return fmt.Errorf("failed to save olric config: %w", err)
|
|
}
|
|
exec.Command("chown", "debros:debros", olricConfigPath).Run()
|
|
ps.logf(" ✓ Olric config generated")
|
|
|
|
return nil
|
|
}
|
|
|
|
// Phase5CreateSystemdServices creates and enables systemd units
|
|
func (ps *ProductionSetup) Phase5CreateSystemdServices(nodeType string, vpsIP string) error {
|
|
ps.logf("Phase 5: Creating systemd services...")
|
|
|
|
// Validate all required binaries are available before creating services
|
|
ipfsBinary, err := ps.binaryInstaller.ResolveBinaryPath("ipfs", "/usr/local/bin/ipfs", "/usr/bin/ipfs")
|
|
if err != nil {
|
|
return fmt.Errorf("ipfs binary not available: %w", err)
|
|
}
|
|
clusterBinary, err := ps.binaryInstaller.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 available: %w", err)
|
|
}
|
|
rqliteBinary, err := ps.binaryInstaller.ResolveBinaryPath("rqlited", "/usr/local/bin/rqlited", "/usr/bin/rqlited")
|
|
if err != nil {
|
|
return fmt.Errorf("rqlited binary not available: %w", err)
|
|
}
|
|
olricBinary, err := ps.binaryInstaller.ResolveBinaryPath("olric-server", "/usr/local/bin/olric-server", "/usr/bin/olric-server")
|
|
if err != nil {
|
|
return fmt.Errorf("olric-server binary not available: %w", err)
|
|
}
|
|
|
|
// IPFS service
|
|
ipfsUnit := ps.serviceGenerator.GenerateIPFSService(nodeType, ipfsBinary)
|
|
unitName := fmt.Sprintf("debros-ipfs-%s.service", nodeType)
|
|
if err := ps.serviceController.WriteServiceUnit(unitName, ipfsUnit); err != nil {
|
|
return fmt.Errorf("failed to write IPFS service: %w", err)
|
|
}
|
|
ps.logf(" ✓ IPFS service created: %s", unitName)
|
|
|
|
// IPFS Cluster service
|
|
clusterUnit := ps.serviceGenerator.GenerateIPFSClusterService(nodeType, clusterBinary)
|
|
clusterUnitName := fmt.Sprintf("debros-ipfs-cluster-%s.service", nodeType)
|
|
if err := ps.serviceController.WriteServiceUnit(clusterUnitName, clusterUnit); err != nil {
|
|
return fmt.Errorf("failed to write IPFS Cluster service: %w", err)
|
|
}
|
|
ps.logf(" ✓ IPFS Cluster service created: %s", clusterUnitName)
|
|
|
|
// RQLite service with join address for non-bootstrap nodes
|
|
rqliteJoinAddr := ""
|
|
if nodeType != "bootstrap" && vpsIP != "" {
|
|
rqliteJoinAddr = vpsIP + ":7001"
|
|
}
|
|
|
|
// Log the advertise configuration for verification
|
|
advertiseIP := vpsIP
|
|
if advertiseIP == "" {
|
|
advertiseIP = "127.0.0.1"
|
|
}
|
|
ps.logf(" RQLite will advertise: %s (advertise IP: %s)", rqliteJoinAddr, advertiseIP)
|
|
|
|
rqliteUnit := ps.serviceGenerator.GenerateRQLiteService(nodeType, rqliteBinary, 5001, 7001, rqliteJoinAddr, advertiseIP)
|
|
rqliteUnitName := fmt.Sprintf("debros-rqlite-%s.service", nodeType)
|
|
if err := ps.serviceController.WriteServiceUnit(rqliteUnitName, rqliteUnit); err != nil {
|
|
return fmt.Errorf("failed to write RQLite service: %w", err)
|
|
}
|
|
ps.logf(" ✓ RQLite service created: %s", rqliteUnitName)
|
|
|
|
// Olric service
|
|
olricUnit := ps.serviceGenerator.GenerateOlricService(olricBinary)
|
|
if err := ps.serviceController.WriteServiceUnit("debros-olric.service", olricUnit); err != nil {
|
|
return fmt.Errorf("failed to write Olric service: %w", err)
|
|
}
|
|
ps.logf(" ✓ Olric service created")
|
|
|
|
// Node service
|
|
nodeUnit := ps.serviceGenerator.GenerateNodeService(nodeType)
|
|
nodeUnitName := fmt.Sprintf("debros-node-%s.service", nodeType)
|
|
if err := ps.serviceController.WriteServiceUnit(nodeUnitName, nodeUnit); err != nil {
|
|
return fmt.Errorf("failed to write Node service: %w", err)
|
|
}
|
|
ps.logf(" ✓ Node service created: %s", nodeUnitName)
|
|
|
|
// Gateway service (optional, only on specific nodes)
|
|
gatewayUnit := ps.serviceGenerator.GenerateGatewayService(nodeType)
|
|
if err := ps.serviceController.WriteServiceUnit("debros-gateway.service", gatewayUnit); err != nil {
|
|
return fmt.Errorf("failed to write Gateway service: %w", err)
|
|
}
|
|
ps.logf(" ✓ Gateway service created")
|
|
|
|
// Reload systemd daemon
|
|
if err := ps.serviceController.DaemonReload(); err != nil {
|
|
return fmt.Errorf("failed to reload systemd: %w", err)
|
|
}
|
|
ps.logf(" ✓ Systemd daemon reloaded")
|
|
|
|
// Enable services
|
|
services := []string{unitName, clusterUnitName, rqliteUnitName, "debros-olric.service", nodeUnitName, "debros-gateway.service"}
|
|
for _, svc := range services {
|
|
if err := ps.serviceController.EnableService(svc); err != nil {
|
|
ps.logf(" ⚠️ Failed to enable %s: %v", svc, err)
|
|
} else {
|
|
ps.logf(" ✓ Service enabled: %s", svc)
|
|
}
|
|
}
|
|
|
|
// Start services in dependency order
|
|
ps.logf(" Starting services...")
|
|
|
|
// Start infrastructure first (IPFS, RQLite, Olric)
|
|
infraServices := []string{unitName, rqliteUnitName, "debros-olric.service"}
|
|
for _, svc := range infraServices {
|
|
if err := ps.serviceController.StartService(svc); err != nil {
|
|
ps.logf(" ⚠️ Failed to start %s: %v", svc, err)
|
|
} else {
|
|
ps.logf(" - %s started", svc)
|
|
}
|
|
}
|
|
|
|
// Wait a moment for infrastructure to stabilize
|
|
exec.Command("sleep", "2").Run()
|
|
|
|
// Start IPFS Cluster
|
|
if err := ps.serviceController.StartService(clusterUnitName); err != nil {
|
|
ps.logf(" ⚠️ Failed to start %s: %v", clusterUnitName, err)
|
|
} else {
|
|
ps.logf(" - %s started", clusterUnitName)
|
|
}
|
|
|
|
// Start application services
|
|
appServices := []string{nodeUnitName, "debros-gateway.service"}
|
|
for _, svc := range appServices {
|
|
if err := ps.serviceController.StartService(svc); err != nil {
|
|
ps.logf(" ⚠️ Failed to start %s: %v", svc, err)
|
|
} else {
|
|
ps.logf(" - %s started", svc)
|
|
}
|
|
}
|
|
|
|
ps.logf(" ✓ All services started")
|
|
return nil
|
|
}
|
|
|
|
// LogSetupComplete logs completion information
|
|
func (ps *ProductionSetup) LogSetupComplete(peerID string) {
|
|
ps.logf("\n" + strings.Repeat("=", 70))
|
|
ps.logf("Setup Complete!")
|
|
ps.logf(strings.Repeat("=", 70))
|
|
ps.logf("\nNode Peer ID: %s", peerID)
|
|
ps.logf("\nService Management:")
|
|
ps.logf(" systemctl status debros-ipfs-bootstrap")
|
|
ps.logf(" journalctl -u debros-node-bootstrap -f")
|
|
ps.logf(" tail -f %s/logs/node-bootstrap.log", ps.debrosDir)
|
|
ps.logf("\nLog Files:")
|
|
ps.logf(" %s/logs/ipfs-bootstrap.log", ps.debrosDir)
|
|
ps.logf(" %s/logs/ipfs-cluster-bootstrap.log", ps.debrosDir)
|
|
ps.logf(" %s/logs/rqlite-bootstrap.log", ps.debrosDir)
|
|
ps.logf(" %s/logs/olric.log", ps.debrosDir)
|
|
ps.logf(" %s/logs/node-bootstrap.log", ps.debrosDir)
|
|
ps.logf(" %s/logs/gateway.log", ps.debrosDir)
|
|
ps.logf("\nStart All Services:")
|
|
ps.logf(" systemctl start debros-ipfs-bootstrap debros-ipfs-cluster-bootstrap debros-rqlite-bootstrap debros-olric debros-node-bootstrap debros-gateway")
|
|
ps.logf("\nVerify Installation:")
|
|
ps.logf(" curl http://localhost:6001/health")
|
|
ps.logf(" curl http://localhost:5001/status\n")
|
|
}
|