mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-11 08:18:49 +00:00
- Replaced all instances of DeBros with Orama throughout the codebase, including CLI commands and configuration paths. - Updated documentation to reflect the new naming convention and paths for configuration files. - Removed the outdated PRODUCTION_INSTALL.md file and added new scripts for local domain setup and testing. - Introduced a new interactive TUI installer for Orama Network, enhancing the installation experience. - Improved logging and error handling across various components to provide clearer feedback during operations.
242 lines
7.1 KiB
Go
242 lines
7.1 KiB
Go
package production
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// FilesystemProvisioner manages directory creation and permissions
|
|
type FilesystemProvisioner struct {
|
|
oramaHome string
|
|
oramaDir string
|
|
logWriter interface{} // Can be io.Writer for logging
|
|
}
|
|
|
|
// NewFilesystemProvisioner creates a new provisioner
|
|
func NewFilesystemProvisioner(oramaHome string) *FilesystemProvisioner {
|
|
return &FilesystemProvisioner{
|
|
oramaHome: oramaHome,
|
|
oramaDir: filepath.Join(oramaHome, ".orama"),
|
|
}
|
|
}
|
|
|
|
// EnsureDirectoryStructure creates all required directories (unified structure)
|
|
func (fp *FilesystemProvisioner) EnsureDirectoryStructure() error {
|
|
// All directories needed for unified node structure
|
|
dirs := []string{
|
|
fp.oramaDir,
|
|
filepath.Join(fp.oramaDir, "configs"),
|
|
filepath.Join(fp.oramaDir, "secrets"),
|
|
filepath.Join(fp.oramaDir, "data"),
|
|
filepath.Join(fp.oramaDir, "data", "ipfs", "repo"),
|
|
filepath.Join(fp.oramaDir, "data", "ipfs-cluster"),
|
|
filepath.Join(fp.oramaDir, "data", "rqlite"),
|
|
filepath.Join(fp.oramaDir, "logs"),
|
|
filepath.Join(fp.oramaDir, "tls-cache"),
|
|
filepath.Join(fp.oramaDir, "backups"),
|
|
filepath.Join(fp.oramaHome, "bin"),
|
|
filepath.Join(fp.oramaHome, "src"),
|
|
}
|
|
|
|
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
|
|
logsDir := filepath.Join(fp.oramaDir, "logs")
|
|
logFiles := []string{
|
|
"olric.log",
|
|
"gateway.log",
|
|
"ipfs.log",
|
|
"ipfs-cluster.log",
|
|
"node.log",
|
|
"rqlite.log",
|
|
"anyone-client.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 .orama directory to debros user
|
|
func (fp *FilesystemProvisioner) FixOwnership() error {
|
|
// Fix entire .orama directory recursively (includes all data, configs, logs, etc.)
|
|
cmd := exec.Command("chown", "-R", "debros:debros", fp.oramaDir)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to set ownership for %s: %w\nOutput: %s", fp.oramaDir, err, string(output))
|
|
}
|
|
|
|
// Also fix home directory ownership
|
|
cmd = exec.Command("chown", "debros:debros", fp.oramaHome)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to set ownership for %s: %w\nOutput: %s", fp.oramaHome, err, string(output))
|
|
}
|
|
|
|
// Fix bin directory
|
|
binDir := filepath.Join(fp.oramaHome, "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 {
|
|
oramaDir string
|
|
}
|
|
|
|
// NewStateDetector creates a state detector
|
|
func NewStateDetector(oramaDir string) *StateDetector {
|
|
return &StateDetector{
|
|
oramaDir: oramaDir,
|
|
}
|
|
}
|
|
|
|
// IsConfigured checks if basic configs exist
|
|
func (sd *StateDetector) IsConfigured() bool {
|
|
nodeConfig := filepath.Join(sd.oramaDir, "configs", "node.yaml")
|
|
gatewayConfig := filepath.Join(sd.oramaDir, "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.oramaDir, "secrets", "cluster-secret")
|
|
swarmKey := filepath.Join(sd.oramaDir, "secrets", "swarm.key")
|
|
_, err1 := os.Stat(clusterSecret)
|
|
_, err2 := os.Stat(swarmKey)
|
|
return err1 == nil && err2 == nil
|
|
}
|
|
|
|
// HasIPFSData checks if IPFS repo is initialized (unified path)
|
|
func (sd *StateDetector) HasIPFSData() bool {
|
|
// Check unified path first
|
|
ipfsRepoPath := filepath.Join(sd.oramaDir, "data", "ipfs", "repo", "config")
|
|
if _, err := os.Stat(ipfsRepoPath); err == nil {
|
|
return true
|
|
}
|
|
// Fallback: check legacy bootstrap path for migration
|
|
legacyPath := filepath.Join(sd.oramaDir, "data", "bootstrap", "ipfs", "repo", "config")
|
|
_, err := os.Stat(legacyPath)
|
|
return err == nil
|
|
}
|
|
|
|
// HasRQLiteData checks if RQLite data exists (unified path)
|
|
func (sd *StateDetector) HasRQLiteData() bool {
|
|
// Check unified path first
|
|
rqliteDataPath := filepath.Join(sd.oramaDir, "data", "rqlite")
|
|
if info, err := os.Stat(rqliteDataPath); err == nil && info.IsDir() {
|
|
return true
|
|
}
|
|
// Fallback: check legacy bootstrap path for migration
|
|
legacyPath := filepath.Join(sd.oramaDir, "data", "bootstrap", "rqlite")
|
|
info, err := os.Stat(legacyPath)
|
|
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
|
|
}
|