mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-12 23:18:49 +00:00
- Updated production command handling to reflect that RQLite is now managed internally by the node process, eliminating the need for separate service definitions and binary paths. - Adjusted logging and service management functions to streamline operations and improve clarity regarding RQLite's integration. - Enhanced log file creation to be node-type specific, ensuring only relevant logs are generated based on the node type being installed.
248 lines
7.2 KiB
Go
248 lines
7.2 KiB
Go
package production
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// FilesystemProvisioner manages directory creation and permissions
|
|
type FilesystemProvisioner struct {
|
|
debrosHome string
|
|
debrosDir string
|
|
logWriter interface{} // Can be io.Writer for logging
|
|
}
|
|
|
|
// NewFilesystemProvisioner creates a new provisioner
|
|
func NewFilesystemProvisioner(debrosHome string) *FilesystemProvisioner {
|
|
return &FilesystemProvisioner{
|
|
debrosHome: debrosHome,
|
|
debrosDir: filepath.Join(debrosHome, ".debros"),
|
|
}
|
|
}
|
|
|
|
// EnsureDirectoryStructure creates all required directories
|
|
// nodeType can be "bootstrap", "node", or "" (empty string means create base directories only)
|
|
func (fp *FilesystemProvisioner) EnsureDirectoryStructure(nodeType string) error {
|
|
// Base directories that are always needed
|
|
dirs := []string{
|
|
fp.debrosDir,
|
|
filepath.Join(fp.debrosDir, "configs"),
|
|
filepath.Join(fp.debrosDir, "secrets"),
|
|
filepath.Join(fp.debrosDir, "data"),
|
|
filepath.Join(fp.debrosDir, "logs"),
|
|
filepath.Join(fp.debrosDir, "tls-cache"),
|
|
filepath.Join(fp.debrosDir, "backups"),
|
|
filepath.Join(fp.debrosHome, "bin"),
|
|
filepath.Join(fp.debrosHome, "src"),
|
|
}
|
|
|
|
// Only create directories for the requested node type
|
|
if nodeType == "bootstrap" || nodeType == "node" {
|
|
dirs = append(dirs,
|
|
filepath.Join(fp.debrosDir, "data", nodeType, "ipfs", "repo"),
|
|
filepath.Join(fp.debrosDir, "data", nodeType, "ipfs-cluster"),
|
|
filepath.Join(fp.debrosDir, "data", nodeType, "rqlite"),
|
|
)
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
}
|
|
|
|
// Create log files with correct permissions so systemd can write to them
|
|
// Only create logs for the specific nodeType being installed
|
|
logsDir := filepath.Join(fp.debrosDir, "logs")
|
|
logFiles := []string{
|
|
"olric.log",
|
|
"gateway.log",
|
|
}
|
|
|
|
// Add node-type-specific log files only if nodeType is specified
|
|
if nodeType == "bootstrap" {
|
|
logFiles = append(logFiles,
|
|
"ipfs-bootstrap.log",
|
|
"ipfs-cluster-bootstrap.log",
|
|
"node-bootstrap.log",
|
|
)
|
|
} else if nodeType == "node" {
|
|
logFiles = append(logFiles,
|
|
"ipfs-node.log",
|
|
"ipfs-cluster-node.log",
|
|
"node-node.log",
|
|
)
|
|
}
|
|
|
|
for _, logFile := range logFiles {
|
|
logPath := filepath.Join(logsDir, logFile)
|
|
// Create empty file if it doesn't exist
|
|
if _, err := os.Stat(logPath); os.IsNotExist(err) {
|
|
if err := os.WriteFile(logPath, []byte{}, 0644); err != nil {
|
|
return fmt.Errorf("failed to create log file %s: %w", logPath, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FixOwnership changes ownership of .debros directory to debros user
|
|
func (fp *FilesystemProvisioner) FixOwnership() error {
|
|
// Fix entire .debros directory recursively (includes all data, configs, logs, etc.)
|
|
cmd := exec.Command("chown", "-R", "debros:debros", fp.debrosDir)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to set ownership for %s: %w\nOutput: %s", fp.debrosDir, err, string(output))
|
|
}
|
|
|
|
// Also fix home directory ownership
|
|
cmd = exec.Command("chown", "debros:debros", fp.debrosHome)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to set ownership for %s: %w\nOutput: %s", fp.debrosHome, err, string(output))
|
|
}
|
|
|
|
// Fix bin directory
|
|
binDir := filepath.Join(fp.debrosHome, "bin")
|
|
cmd = exec.Command("chown", "-R", "debros:debros", binDir)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to set ownership for %s: %w\nOutput: %s", binDir, err, string(output))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UserProvisioner manages system user creation and sudoers setup
|
|
type UserProvisioner struct {
|
|
username string
|
|
home string
|
|
shell string
|
|
}
|
|
|
|
// NewUserProvisioner creates a new user provisioner
|
|
func NewUserProvisioner(username, home, shell string) *UserProvisioner {
|
|
if shell == "" {
|
|
shell = "/bin/bash"
|
|
}
|
|
return &UserProvisioner{
|
|
username: username,
|
|
home: home,
|
|
shell: shell,
|
|
}
|
|
}
|
|
|
|
// UserExists checks if the system user exists
|
|
func (up *UserProvisioner) UserExists() bool {
|
|
cmd := exec.Command("id", up.username)
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
// CreateUser creates the system user
|
|
func (up *UserProvisioner) CreateUser() error {
|
|
if up.UserExists() {
|
|
return nil // User already exists
|
|
}
|
|
|
|
cmd := exec.Command("useradd", "-r", "-m", "-s", up.shell, "-d", up.home, up.username)
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to create user %s: %w", up.username, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetupSudoersAccess creates sudoers rule for the invoking user
|
|
func (up *UserProvisioner) SetupSudoersAccess(invokerUser string) error {
|
|
if invokerUser == "" {
|
|
return nil // Skip if no invoker
|
|
}
|
|
|
|
sudoersRule := fmt.Sprintf("%s ALL=(debros) NOPASSWD: ALL\n", invokerUser)
|
|
sudoersFile := "/etc/sudoers.d/debros-access"
|
|
|
|
// Check if rule already exists
|
|
if existing, err := os.ReadFile(sudoersFile); err == nil {
|
|
if strings.Contains(string(existing), invokerUser) {
|
|
return nil // Rule already set
|
|
}
|
|
}
|
|
|
|
// Write sudoers rule
|
|
if err := os.WriteFile(sudoersFile, []byte(sudoersRule), 0440); err != nil {
|
|
return fmt.Errorf("failed to create sudoers rule: %w", err)
|
|
}
|
|
|
|
// Validate sudoers file
|
|
cmd := exec.Command("visudo", "-c", "-f", sudoersFile)
|
|
if err := cmd.Run(); err != nil {
|
|
os.Remove(sudoersFile) // Clean up on validation failure
|
|
return fmt.Errorf("sudoers rule validation failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StateDetector checks for existing production state
|
|
type StateDetector struct {
|
|
debrosDir string
|
|
}
|
|
|
|
// NewStateDetector creates a state detector
|
|
func NewStateDetector(debrosDir string) *StateDetector {
|
|
return &StateDetector{
|
|
debrosDir: debrosDir,
|
|
}
|
|
}
|
|
|
|
// IsConfigured checks if basic configs exist
|
|
func (sd *StateDetector) IsConfigured() bool {
|
|
nodeConfig := filepath.Join(sd.debrosDir, "configs", "node.yaml")
|
|
gatewayConfig := filepath.Join(sd.debrosDir, "configs", "gateway.yaml")
|
|
_, err1 := os.Stat(nodeConfig)
|
|
_, err2 := os.Stat(gatewayConfig)
|
|
return err1 == nil || err2 == nil
|
|
}
|
|
|
|
// HasSecrets checks if cluster secret and swarm key exist
|
|
func (sd *StateDetector) HasSecrets() bool {
|
|
clusterSecret := filepath.Join(sd.debrosDir, "secrets", "cluster-secret")
|
|
swarmKey := filepath.Join(sd.debrosDir, "secrets", "swarm.key")
|
|
_, err1 := os.Stat(clusterSecret)
|
|
_, err2 := os.Stat(swarmKey)
|
|
return err1 == nil && err2 == nil
|
|
}
|
|
|
|
// HasIPFSData checks if IPFS repo is initialized
|
|
func (sd *StateDetector) HasIPFSData() bool {
|
|
ipfsRepoPath := filepath.Join(sd.debrosDir, "data", "bootstrap", "ipfs", "repo", "config")
|
|
_, err := os.Stat(ipfsRepoPath)
|
|
return err == nil
|
|
}
|
|
|
|
// HasRQLiteData checks if RQLite data exists
|
|
func (sd *StateDetector) HasRQLiteData() bool {
|
|
rqliteDataPath := filepath.Join(sd.debrosDir, "data", "bootstrap", "rqlite")
|
|
info, err := os.Stat(rqliteDataPath)
|
|
return err == nil && info.IsDir()
|
|
}
|
|
|
|
// CheckBinaryInstallation checks if required binaries are in PATH
|
|
func (sd *StateDetector) CheckBinaryInstallation() error {
|
|
binaries := []string{"ipfs", "ipfs-cluster-service", "rqlited", "olric-server"}
|
|
var missing []string
|
|
|
|
for _, bin := range binaries {
|
|
if _, err := exec.LookPath(bin); err != nil {
|
|
missing = append(missing, bin)
|
|
}
|
|
}
|
|
|
|
if len(missing) > 0 {
|
|
return fmt.Errorf("missing binaries: %s", strings.Join(missing, ", "))
|
|
}
|
|
|
|
return nil
|
|
}
|