mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-12 23:18:49 +00:00
- Added comprehensive tests for production command flag parsing to ensure correct handling of bootstrap, VPS IP, and peer configurations. - Updated production command help output to clarify the usage of new flags, including `--vps-ip` and `--bootstrap-join`. - Modified the configuration generation logic to incorporate the new `bootstrapJoin` parameter for secondary bootstrap nodes. - Enhanced systemd service generation to include the correct advertise IP and join address for non-bootstrap nodes. - Implemented tests for RQLite service generation to verify the inclusion of join addresses and advertise IPs in the generated units.
477 lines
16 KiB
Go
477 lines
16 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 {
|
|
ps.logf(" ⚠️ IPFS initialization warning: %v", 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...")
|
|
|
|
// IPFS service
|
|
ipfsUnit := ps.serviceGenerator.GenerateIPFSService(nodeType)
|
|
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)
|
|
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, 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()
|
|
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")
|
|
}
|