network/pkg/cli/setup.go
anonpenguin23 fe16d503b5 feat: integrate Anyone Relay (Anon) into the development environment
- Added support for installing and configuring the Anyone Relay (Anon) for anonymous networking in the setup process.
- Updated the Makefile to include the Anon client in the development stack, allowing it to run alongside other services.
- Implemented a new HTTP proxy handler for the Anon service, enabling proxied requests through the Anyone network.
- Enhanced the installation script to manage Anon installation, configuration, and firewall settings.
- Introduced tests for the Anon proxy handler to ensure proper request validation and error handling.
- Updated documentation to reflect the new Anon service and its usage in the development environment.
2025-10-30 06:21:32 +02:00

820 lines
24 KiB
Go

package cli
import (
"bufio"
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
)
// HandleSetupCommand handles the interactive 'setup' command for VPS installation
func HandleSetupCommand(args []string) {
// Parse flags
force := false
for _, arg := range args {
if arg == "--force" {
force = true
}
}
fmt.Printf("🚀 DeBros Network Setup\n\n")
// Check if running as root
if os.Geteuid() != 0 {
fmt.Fprintf(os.Stderr, "❌ This command must be run as root (use sudo)\n")
os.Exit(1)
}
// Check OS compatibility
if runtime.GOOS != "linux" {
fmt.Fprintf(os.Stderr, "❌ Setup command is only supported on Linux\n")
fmt.Fprintf(os.Stderr, " For other platforms, please install manually\n")
os.Exit(1)
}
// Detect OS
osInfo := detectLinuxDistro()
fmt.Printf("📋 Detected OS: %s\n", osInfo)
if !isSupportedOS(osInfo) {
fmt.Fprintf(os.Stderr, "⚠️ Unsupported OS: %s\n", osInfo)
fmt.Fprintf(os.Stderr, " Supported: Ubuntu 22.04/24.04/25.04, Debian 12\n")
fmt.Printf("\nContinue anyway? (yes/no): ")
if !promptYesNo() {
fmt.Println("Setup cancelled.")
os.Exit(1)
}
}
// Show setup plan
fmt.Printf("\n" + strings.Repeat("=", 70) + "\n")
fmt.Printf("Setup Plan:\n")
fmt.Printf(" 1. Create 'debros' system user (if needed)\n")
fmt.Printf(" 2. Install system dependencies (curl, git, make, build tools)\n")
fmt.Printf(" 3. Install Go 1.21+ (if needed)\n")
fmt.Printf(" 4. Install RQLite database\n")
fmt.Printf(" 5. Install Anyone Relay (Anon) for anonymous networking\n")
fmt.Printf(" 6. Create directories (/home/debros/bin, /home/debros/src)\n")
fmt.Printf(" 7. Clone and build DeBros Network\n")
fmt.Printf(" 8. Generate configuration files\n")
fmt.Printf(" 9. Create systemd services (debros-node, debros-gateway)\n")
fmt.Printf(" 10. Start and enable services\n")
fmt.Printf(strings.Repeat("=", 70) + "\n\n")
fmt.Printf("Ready to begin setup? (yes/no): ")
if !promptYesNo() {
fmt.Println("Setup cancelled.")
os.Exit(1)
}
fmt.Printf("\n")
// Step 1: Setup debros user
setupDebrosUser()
// Step 2: Install dependencies
installSystemDependencies()
// Step 3: Install Go
ensureGo()
// Step 4: Install RQLite
installRQLite()
// Step 4.5: Install Anon (Anyone relay)
installAnon()
// Step 5: Setup directories
setupDirectories()
// Step 6: Clone and build
cloneAndBuild()
// Step 7: Generate configs (interactive)
generateConfigsInteractive(force)
// Step 8: Create systemd services
createSystemdServices()
// Step 9: Start services
startServices()
// Done!
fmt.Printf("\n" + strings.Repeat("=", 70) + "\n")
fmt.Printf("✅ Setup Complete!\n")
fmt.Printf(strings.Repeat("=", 70) + "\n\n")
fmt.Printf("DeBros Network is now running!\n\n")
fmt.Printf("Service Management:\n")
fmt.Printf(" network-cli service status all\n")
fmt.Printf(" network-cli service logs node --follow\n")
fmt.Printf(" network-cli service restart gateway\n\n")
fmt.Printf("Access DeBros User:\n")
fmt.Printf(" sudo -u debros bash\n\n")
fmt.Printf("Verify Installation:\n")
fmt.Printf(" curl http://localhost:6001/health\n")
fmt.Printf(" curl http://localhost:5001/status\n\n")
fmt.Printf("Anyone Relay (Anon):\n")
fmt.Printf(" sudo systemctl status anon\n")
fmt.Printf(" sudo tail -f /home/debros/.debros/logs/anon/notices.log\n")
fmt.Printf(" Proxy endpoint: POST http://localhost:6001/v1/proxy/anon\n\n")
}
func detectLinuxDistro() string {
if data, err := os.ReadFile("/etc/os-release"); err == nil {
lines := strings.Split(string(data), "\n")
var id, version string
for _, line := range lines {
if strings.HasPrefix(line, "ID=") {
id = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
}
if strings.HasPrefix(line, "VERSION_ID=") {
version = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), "\"")
}
}
if id != "" && version != "" {
return fmt.Sprintf("%s %s", id, version)
}
if id != "" {
return id
}
}
return "unknown"
}
func isSupportedOS(osInfo string) bool {
supported := []string{
"ubuntu 22.04",
"ubuntu 24.04",
"ubuntu 25.04",
"debian 12",
}
for _, s := range supported {
if strings.Contains(strings.ToLower(osInfo), s) {
return true
}
}
return false
}
func promptYesNo() bool {
reader := bufio.NewReader(os.Stdin)
response, _ := reader.ReadString('\n')
response = strings.ToLower(strings.TrimSpace(response))
return response == "yes" || response == "y"
}
// isValidMultiaddr validates bootstrap peer multiaddr format
func isValidMultiaddr(s string) bool {
s = strings.TrimSpace(s)
if s == "" {
return false
}
if !(strings.HasPrefix(s, "/ip4/") || strings.HasPrefix(s, "/ip6/")) {
return false
}
return strings.Contains(s, "/p2p/")
}
// isValidHostPort validates host:port format
func isValidHostPort(s string) bool {
s = strings.TrimSpace(s)
if s == "" {
return false
}
parts := strings.Split(s, ":")
if len(parts) != 2 {
return false
}
host := strings.TrimSpace(parts[0])
port := strings.TrimSpace(parts[1])
if host == "" {
return false
}
// Port must be a valid number between 1 and 65535
if portNum, err := strconv.Atoi(port); err != nil || portNum < 1 || portNum > 65535 {
return false
}
return true
}
func setupDebrosUser() {
fmt.Printf("👤 Setting up 'debros' user...\n")
// Check if user exists
userExists := false
if _, err := exec.Command("id", "debros").CombinedOutput(); err == nil {
fmt.Printf(" ✓ User 'debros' already exists\n")
userExists = true
} else {
// Create user
cmd := exec.Command("useradd", "-r", "-m", "-s", "/bin/bash", "-d", "/home/debros", "debros")
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to create user 'debros': %v\n", err)
os.Exit(1)
}
fmt.Printf(" ✓ Created user 'debros'\n")
}
// Get the user who invoked sudo (the actual user, not root)
sudoUser := os.Getenv("SUDO_USER")
if sudoUser == "" {
// If not running via sudo, skip sudoers setup
return
}
// Create sudoers rule to allow passwordless access to debros user
sudoersRule := fmt.Sprintf("%s ALL=(debros) NOPASSWD: ALL\n", sudoUser)
sudoersFile := "/etc/sudoers.d/debros-access"
// Check if sudoers rule already exists
if existing, err := os.ReadFile(sudoersFile); err == nil {
if strings.Contains(string(existing), sudoUser) {
if !userExists {
fmt.Printf(" ✓ Sudoers access configured\n")
}
return
}
}
// Write sudoers rule
if err := os.WriteFile(sudoersFile, []byte(sudoersRule), 0440); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to create sudoers rule: %v\n", err)
fmt.Fprintf(os.Stderr, " You can manually switch to debros using: sudo -u debros bash\n")
return
}
// Validate the sudoers file
if err := exec.Command("visudo", "-c", "-f", sudoersFile).Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Sudoers rule validation failed, removing file\n")
os.Remove(sudoersFile)
return
}
fmt.Printf(" ✓ Sudoers access configured\n")
fmt.Printf(" You can now run: sudo -u debros bash\n")
}
func installSystemDependencies() {
fmt.Printf("📦 Installing system dependencies...\n")
// Detect package manager
var installCmd *exec.Cmd
if _, err := exec.LookPath("apt"); err == nil {
installCmd = exec.Command("apt", "update")
if err := installCmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ apt update failed: %v\n", err)
}
installCmd = exec.Command("apt", "install", "-y", "curl", "git", "make", "build-essential", "wget")
} else if _, err := exec.LookPath("yum"); err == nil {
installCmd = exec.Command("yum", "install", "-y", "curl", "git", "make", "gcc", "wget")
} else {
fmt.Fprintf(os.Stderr, "❌ No supported package manager found\n")
os.Exit(1)
}
if err := installCmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to install dependencies: %v\n", err)
os.Exit(1)
}
fmt.Printf(" ✓ Dependencies installed\n")
}
func ensureGo() {
fmt.Printf("🔧 Checking Go installation...\n")
// Check if Go is already installed
if _, err := exec.LookPath("go"); err == nil {
fmt.Printf(" ✓ Go already installed\n")
return
}
fmt.Printf(" Installing Go 1.21.6...\n")
// Download Go
arch := "amd64"
if runtime.GOARCH == "arm64" {
arch = "arm64"
}
goTarball := fmt.Sprintf("go1.21.6.linux-%s.tar.gz", arch)
goURL := fmt.Sprintf("https://go.dev/dl/%s", goTarball)
// Download
cmd := exec.Command("wget", "-q", goURL, "-O", "/tmp/"+goTarball)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to download Go: %v\n", err)
os.Exit(1)
}
// Extract
cmd = exec.Command("tar", "-C", "/usr/local", "-xzf", "/tmp/"+goTarball)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to extract Go: %v\n", err)
os.Exit(1)
}
// Add to PATH for current process
os.Setenv("PATH", os.Getenv("PATH")+":/usr/local/go/bin")
// Also add to debros user's .bashrc for persistent availability
debrosHome := "/home/debros"
bashrc := debrosHome + "/.bashrc"
pathLine := "\nexport PATH=$PATH:/usr/local/go/bin\n"
// Read existing bashrc
existing, _ := os.ReadFile(bashrc)
existingStr := string(existing)
// Add PATH if not already present
if !strings.Contains(existingStr, "/usr/local/go/bin") {
if err := os.WriteFile(bashrc, []byte(existingStr+pathLine), 0644); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to update debros .bashrc: %v\n", err)
}
// Fix ownership
exec.Command("chown", "debros:debros", bashrc).Run()
}
fmt.Printf(" ✓ Go installed\n")
}
func installRQLite() {
fmt.Printf("🗄️ Installing RQLite...\n")
// Check if already installed
if _, err := exec.LookPath("rqlited"); err == nil {
fmt.Printf(" ✓ RQLite already installed\n")
return
}
arch := "amd64"
switch runtime.GOARCH {
case "arm64":
arch = "arm64"
case "arm":
arch = "arm"
}
version := "8.43.0"
tarball := fmt.Sprintf("rqlite-v%s-linux-%s.tar.gz", version, arch)
url := fmt.Sprintf("https://github.com/rqlite/rqlite/releases/download/v%s/%s", version, tarball)
// Download
cmd := exec.Command("wget", "-q", url, "-O", "/tmp/"+tarball)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to download RQLite: %v\n", err)
os.Exit(1)
}
// Extract
cmd = exec.Command("tar", "-C", "/tmp", "-xzf", "/tmp/"+tarball)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to extract RQLite: %v\n", err)
os.Exit(1)
}
// Copy binaries
dir := fmt.Sprintf("/tmp/rqlite-v%s-linux-%s", version, arch)
exec.Command("cp", dir+"/rqlited", "/usr/local/bin/").Run()
exec.Command("cp", dir+"/rqlite", "/usr/local/bin/").Run()
exec.Command("chmod", "+x", "/usr/local/bin/rqlited").Run()
exec.Command("chmod", "+x", "/usr/local/bin/rqlite").Run()
fmt.Printf(" ✓ RQLite installed\n")
}
func installAnon() {
fmt.Printf("🔐 Installing Anyone Relay (Anon)...\n")
// Check if already installed
if _, err := exec.LookPath("anon"); err == nil {
fmt.Printf(" ✓ Anon already installed\n")
configureAnonLogs()
configureFirewallForAnon()
return
}
// Install via APT (official method from docs.anyone.io)
fmt.Printf(" Adding Anyone APT repository...\n")
// Add GPG key
cmd := exec.Command("sh", "-c", "curl -fsSL https://deb.anyone.io/gpg.key | gpg --dearmor -o /usr/share/keyrings/anyone-archive-keyring.gpg")
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to add Anyone GPG key: %v\n", err)
fmt.Fprintf(os.Stderr, " You can manually install with:\n")
fmt.Fprintf(os.Stderr, " curl -fsSL https://deb.anyone.io/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/anyone-archive-keyring.gpg\n")
fmt.Fprintf(os.Stderr, " echo 'deb [signed-by=/usr/share/keyrings/anyone-archive-keyring.gpg] https://deb.anyone.io/ anyone main' | sudo tee /etc/apt/sources.list.d/anyone.list\n")
fmt.Fprintf(os.Stderr, " sudo apt update && sudo apt install -y anon\n")
return
}
// Add repository
repoLine := "deb [signed-by=/usr/share/keyrings/anyone-archive-keyring.gpg] https://deb.anyone.io/ anyone main"
if err := os.WriteFile("/etc/apt/sources.list.d/anyone.list", []byte(repoLine+"\n"), 0644); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to add Anyone repository: %v\n", err)
return
}
// Update package list
fmt.Printf(" Updating package list...\n")
exec.Command("apt", "update", "-qq").Run()
// Install anon
fmt.Printf(" Installing Anon package...\n")
cmd = exec.Command("apt", "install", "-y", "anon")
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Anon installation failed: %v\n", err)
return
}
// Verify installation
if _, err := exec.LookPath("anon"); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Anon installation may have failed\n")
return
}
fmt.Printf(" ✓ Anon installed\n")
// Configure with sensible defaults
configureAnonDefaults()
// Configure logs
configureAnonLogs()
// Configure firewall
configureFirewallForAnon()
// Enable and start service
fmt.Printf(" Enabling Anon service...\n")
exec.Command("systemctl", "enable", "anon").Run()
exec.Command("systemctl", "start", "anon").Run()
if exec.Command("systemctl", "is-active", "--quiet", "anon").Run() == nil {
fmt.Printf(" ✓ Anon service is running\n")
} else {
fmt.Fprintf(os.Stderr, "⚠️ Anon service may not be running. Check: systemctl status anon\n")
}
}
func configureAnonDefaults() {
fmt.Printf(" Configuring Anon with default settings...\n")
hostname := "debros-node"
if h, err := os.Hostname(); err == nil && h != "" {
hostname = strings.Split(h, ".")[0]
}
anonrcPath := "/etc/anon/anonrc"
if _, err := os.Stat(anonrcPath); err == nil {
// Backup existing config
exec.Command("cp", anonrcPath, anonrcPath+".bak").Run()
// Read existing config
data, err := os.ReadFile(anonrcPath)
if err != nil {
return
}
config := string(data)
// Add settings if not present
if !strings.Contains(config, "Nickname") {
config += fmt.Sprintf("\nNickname %s\n", hostname)
}
if !strings.Contains(config, "ControlPort") {
config += "ControlPort 9051\n"
}
if !strings.Contains(config, "SocksPort") {
config += "SocksPort 9050\n"
}
// Write back
os.WriteFile(anonrcPath, []byte(config), 0644)
fmt.Printf(" Nickname: %s\n", hostname)
fmt.Printf(" ORPort: 9001 (default)\n")
fmt.Printf(" ControlPort: 9051\n")
fmt.Printf(" SOCKSPort: 9050\n")
}
}
func configureAnonLogs() {
fmt.Printf(" Configuring Anon logs...\n")
// Create log directory
logDir := "/home/debros/.debros/logs/anon"
if err := os.MkdirAll(logDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to create log directory: %v\n", err)
return
}
// Change ownership to debian-anon (the user anon runs as)
exec.Command("chown", "-R", "debian-anon:debian-anon", logDir).Run()
// Update anonrc if it exists
anonrcPath := "/etc/anon/anonrc"
if _, err := os.Stat(anonrcPath); err == nil {
// Read current config
data, err := os.ReadFile(anonrcPath)
if err == nil {
config := string(data)
// Replace log file path
newConfig := strings.ReplaceAll(config,
"Log notice file /var/log/anon/notices.log",
"Log notice file /home/debros/.debros/logs/anon/notices.log")
// Write back
if err := os.WriteFile(anonrcPath, []byte(newConfig), 0644); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to update anonrc: %v\n", err)
} else {
fmt.Printf(" ✓ Anon logs configured to %s\n", logDir)
// Restart anon service if running
if exec.Command("systemctl", "is-active", "--quiet", "anon").Run() == nil {
exec.Command("systemctl", "restart", "anon").Run()
}
}
}
}
}
func configureFirewallForAnon() {
fmt.Printf(" Checking firewall configuration...\n")
// Check for UFW
if _, err := exec.LookPath("ufw"); err == nil {
output, _ := exec.Command("ufw", "status").CombinedOutput()
if strings.Contains(string(output), "Status: active") {
fmt.Printf(" Adding UFW rules for Anon...\n")
exec.Command("ufw", "allow", "9001/tcp", "comment", "Anon ORPort").Run()
exec.Command("ufw", "allow", "9051/tcp", "comment", "Anon ControlPort").Run()
fmt.Printf(" ✓ UFW rules added\n")
return
}
}
// Check for firewalld
if _, err := exec.LookPath("firewall-cmd"); err == nil {
output, _ := exec.Command("firewall-cmd", "--state").CombinedOutput()
if strings.Contains(string(output), "running") {
fmt.Printf(" Adding firewalld rules for Anon...\n")
exec.Command("firewall-cmd", "--permanent", "--add-port=9001/tcp").Run()
exec.Command("firewall-cmd", "--permanent", "--add-port=9051/tcp").Run()
exec.Command("firewall-cmd", "--reload").Run()
fmt.Printf(" ✓ firewalld rules added\n")
return
}
}
// Check for iptables
if _, err := exec.LookPath("iptables"); err == nil {
output, _ := exec.Command("iptables", "-L", "-n").CombinedOutput()
if strings.Contains(string(output), "Chain INPUT") {
fmt.Printf(" Adding iptables rules for Anon...\n")
exec.Command("iptables", "-A", "INPUT", "-p", "tcp", "--dport", "9001", "-j", "ACCEPT", "-m", "comment", "--comment", "Anon ORPort").Run()
exec.Command("iptables", "-A", "INPUT", "-p", "tcp", "--dport", "9051", "-j", "ACCEPT", "-m", "comment", "--comment", "Anon ControlPort").Run()
// Try to save rules
if _, err := exec.LookPath("netfilter-persistent"); err == nil {
exec.Command("netfilter-persistent", "save").Run()
} else if _, err := exec.LookPath("iptables-save"); err == nil {
cmd := exec.Command("sh", "-c", "iptables-save > /etc/iptables/rules.v4")
cmd.Run()
}
fmt.Printf(" ✓ iptables rules added\n")
return
}
}
fmt.Printf(" No active firewall detected\n")
}
func setupDirectories() {
fmt.Printf("📁 Creating directories...\n")
dirs := []string{
"/home/debros/bin",
"/home/debros/src",
"/home/debros/.debros",
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to create %s: %v\n", dir, err)
os.Exit(1)
}
// Change ownership to debros
cmd := exec.Command("chown", "-R", "debros:debros", dir)
cmd.Run()
}
fmt.Printf(" ✓ Directories created\n")
}
func cloneAndBuild() {
fmt.Printf("🔨 Cloning and building DeBros Network...\n")
// Check if already cloned
if _, err := os.Stat("/home/debros/src/.git"); err == nil {
fmt.Printf(" Updating repository...\n")
cmd := exec.Command("sudo", "-u", "debros", "git", "-C", "/home/debros/src", "pull", "origin", "nightly")
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to update repo: %v\n", err)
}
} else {
fmt.Printf(" Cloning repository...\n")
cmd := exec.Command("sudo", "-u", "debros", "git", "clone", "--branch", "nightly", "--depth", "1", "https://github.com/DeBrosOfficial/network.git", "/home/debros/src")
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to clone repo: %v\n", err)
os.Exit(1)
}
}
// Build
fmt.Printf(" Building binaries...\n")
// Ensure Go is in PATH for the build
os.Setenv("PATH", os.Getenv("PATH")+":/usr/local/go/bin")
// Use sudo with --preserve-env=PATH to pass Go path to debros user
cmd := exec.Command("sudo", "--preserve-env=PATH", "-u", "debros", "make", "build")
cmd.Dir = "/home/debros/src"
if output, err := cmd.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to build: %v\n%s\n", err, output)
os.Exit(1)
}
// Copy binaries
exec.Command("sh", "-c", "cp -r /home/debros/src/bin/* /home/debros/bin/").Run()
exec.Command("chown", "-R", "debros:debros", "/home/debros/bin").Run()
exec.Command("chmod", "-R", "755", "/home/debros/bin").Run()
fmt.Printf(" ✓ Built and installed\n")
}
func generateConfigsInteractive(force bool) {
fmt.Printf("⚙️ Generating configurations...\n")
// For single-node VPS setup, use sensible defaults
// This creates a bootstrap node that acts as the cluster leader
fmt.Printf("\n")
fmt.Printf("Setting up single-node configuration...\n")
fmt.Printf(" • Bootstrap node (cluster leader)\n")
fmt.Printf(" • No external peers required\n")
fmt.Printf(" • Gateway connected to local node\n\n")
// Generate bootstrap node config with explicit parameters
// Pass empty bootstrap-peers and no join address for bootstrap node
bootstrapArgs := []string{
"-u", "debros",
"/home/debros/bin/network-cli", "config", "init",
"--type", "bootstrap",
"--bootstrap-peers", "",
}
if force {
bootstrapArgs = append(bootstrapArgs, "--force")
}
cmd := exec.Command("sudo", bootstrapArgs...)
cmd.Stdin = nil // Explicitly close stdin to prevent interactive prompts
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to generate bootstrap config: %v\n", err)
if len(output) > 0 {
fmt.Fprintf(os.Stderr, " Output: %s\n", string(output))
}
} else {
fmt.Printf(" ✓ Bootstrap node config created\n")
}
// Rename bootstrap.yaml to node.yaml so the service can find it
renameCmd := exec.Command("sudo", "-u", "debros", "mv", "/home/debros/.debros/bootstrap.yaml", "/home/debros/.debros/node.yaml")
if err := renameCmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to rename config: %v\n", err)
}
// Generate gateway config with explicit empty bootstrap peers
gatewayArgs := []string{
"-u", "debros",
"/home/debros/bin/network-cli", "config", "init",
"--type", "gateway",
"--bootstrap-peers", "",
}
if force {
gatewayArgs = append(gatewayArgs, "--force")
}
cmd = exec.Command("sudo", gatewayArgs...)
cmd.Stdin = nil // Explicitly close stdin to prevent interactive prompts
output, err = cmd.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to generate gateway config: %v\n", err)
if len(output) > 0 {
fmt.Fprintf(os.Stderr, " Output: %s\n", string(output))
}
} else {
fmt.Printf(" ✓ Gateway config created\n")
}
fmt.Printf(" ✓ Configurations generated\n")
}
func createSystemdServices() {
fmt.Printf("🔧 Creating systemd services...\n")
// Node service
nodeService := `[Unit]
Description=DeBros Network Node
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=debros
Group=debros
WorkingDirectory=/home/debros/src
ExecStart=/home/debros/bin/node --config node.yaml
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=HOME=/home/debros
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=debros-node
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/home/debros
[Install]
WantedBy=multi-user.target
`
if err := os.WriteFile("/etc/systemd/system/debros-node.service", []byte(nodeService), 0644); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to create node service: %v\n", err)
os.Exit(1)
}
// Gateway service
gatewayService := `[Unit]
Description=DeBros Gateway
After=debros-node.service
Wants=debros-node.service
[Service]
Type=simple
User=debros
Group=debros
WorkingDirectory=/home/debros/src
ExecStart=/home/debros/bin/gateway
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=HOME=/home/debros
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=debros-gateway
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/home/debros
[Install]
WantedBy=multi-user.target
`
if err := os.WriteFile("/etc/systemd/system/debros-gateway.service", []byte(gatewayService), 0644); err != nil {
fmt.Fprintf(os.Stderr, "❌ Failed to create gateway service: %v\n", err)
os.Exit(1)
}
// Reload systemd
exec.Command("systemctl", "daemon-reload").Run()
exec.Command("systemctl", "enable", "debros-node").Run()
exec.Command("systemctl", "enable", "debros-gateway").Run()
fmt.Printf(" ✓ Services created and enabled\n")
}
func startServices() {
fmt.Printf("🚀 Starting services...\n")
// Start node
if err := exec.Command("systemctl", "start", "debros-node").Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to start node service: %v\n", err)
} else {
fmt.Printf(" ✓ Node service started\n")
}
// Start gateway
if err := exec.Command("systemctl", "start", "debros-gateway").Run(); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Failed to start gateway service: %v\n", err)
} else {
fmt.Printf(" ✓ Gateway service started\n")
}
}