package main import ( "bufio" "crypto/rand" "encoding/base64" "encoding/json" "flag" "fmt" "net" "os" "os/exec" "path/filepath" "regexp" "strconv" "strings" "time" "golang.org/x/crypto/curve25519" ) // Colors for terminal output const ( Red = "\033[0;31m" Green = "\033[0;32m" Yellow = "\033[1;33m" Blue = "\033[0;34m" Reset = "\033[0m" ) // Script constants const ( ScriptVersion = "3.0.0" DefaultPort = "51820" MaxHostname = 63 MaxInterface = 15 ) // WireGuard peer configuration type WireGuardPeer struct { Name string PublicKey string AllowedIPs string Endpoint string PersistentKeepalive int Description string } // Node configuration type NodeConfig struct { Hostname string `json:"hostname"` IPAddress string `json:"ip_address"` PrivateKey string `json:"private_key"` PublicKey string `json:"public_key"` RoutingMode string `json:"routing_mode"` Interface string `json:"interface"` Generated string `json:"generated"` ScriptVer string `json:"script_version"` RunningRoot bool `json:"running_as_root"` } // Application state type AppState struct { ForceMode bool RunningAsRoot bool WGDirectory string StepCounter int } // Default peers - automatically included in every config var defaultPeers = []WireGuardPeer{ { Name: "Zion", PublicKey: "2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=", AllowedIPs: "10.8.0.0/24", Endpoint: "ugh.im:51820", PersistentKeepalive: 25, Description: "Central server (always included)", }, { Name: "CTH", PublicKey: "NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0=", AllowedIPs: "10.8.0.10/32", Endpoint: "aw2cd67.glddns.com:53535", PersistentKeepalive: 25, Description: "Secondary server (always included)", }, } // Reserved IP addresses var reservedIPs = map[string]string{ "10.8.0.1": "Zion", "10.8.0.10": "CTH", "10.8.0.2": "Aza", "10.8.0.20": "Nyar", "10.8.0.99": "Galaxy", "10.8.0.7": "nanocube", "10.8.0.42": "jupiter", "10.8.0.8": "HASS", "10.8.0.40": "framebot", } // Static server ports var staticServerPorts = map[string]string{ "10.8.0.1": "51820", // Zion "10.8.0.10": "53535", // CTH "10.8.0.99": "54382", // Galaxy } // Validation regexes var ( ipRegex = regexp.MustCompile(`^10\.8\.0\.\d{1,3}$`) hostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$`) interfaceRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9]*$`) keyRegex = regexp.MustCompile(`^[A-Za-z0-9+/]{43}=$`) ) // Utility functions func step(n int, msg string) { fmt.Printf("%sStep %d:%s %s\n", Blue, n, Reset, msg) } func printStatus(message string) { fmt.Printf("%s[INFO]%s %s\n", Green, Reset, message) } func printWarning(message string) { fmt.Printf("%s[WARNING]%s %s\n", Yellow, Reset, message) } func printError(message string) { fmt.Printf("%s[ERROR]%s %s\n", Red, Reset, message) } func printHeader(title string) { fmt.Printf("%s================================%s\n", Blue, Reset) fmt.Printf("%s%s%s\n", Blue, title, Reset) fmt.Printf("%s================================%s\n", Blue, Reset) fmt.Println() } // Validation functions func validateHostname(hostname string) error { if hostname == "" { return fmt.Errorf("hostname cannot be empty") } if len(hostname) > MaxHostname { return fmt.Errorf("hostname too long (max %d characters)", MaxHostname) } if !hostnameRegex.MatchString(hostname) { return fmt.Errorf("invalid hostname format. Use alphanumeric characters and hyphens only") } return nil } func validateIP(ip string) error { if ip == "" { return fmt.Errorf("IP address cannot be empty") } if !ipRegex.MatchString(ip) { return fmt.Errorf("IP should be in 10.8.0.x range for NextGen network") } if parsedIP := net.ParseIP(ip); parsedIP == nil { return fmt.Errorf("invalid IP address format") } if peerName, exists := reservedIPs[ip]; exists { return fmt.Errorf("IP %s is already reserved for %s", ip, peerName) } return nil } func validateInterface(name string) error { if name == "" { return nil // Allow empty for default } if len(name) > MaxInterface { return fmt.Errorf("interface name too long (max %d characters)", MaxInterface) } if !interfaceRegex.MatchString(name) { return fmt.Errorf("interface name must start with a letter and contain only letters and numbers") } return nil } func validateWireGuardKey(key string) error { if key == "" { return fmt.Errorf("key cannot be empty") } if !keyRegex.MatchString(key) { return fmt.Errorf("invalid WireGuard key format") } return nil } // User input functions func getUserInput(prompt string, validator func(string) error, forceMode bool) string { reader := bufio.NewReader(os.Stdin) for { fmt.Print(prompt) input, _ := reader.ReadString('\n') input = strings.TrimSpace(input) if err := validator(input); err != nil { if forceMode { printWarning(fmt.Sprintf("Validation failed: %s, but continuing due to force mode", err.Error())) return input } printError(err.Error()) fmt.Print("Continue anyway? (y/N): ") response, _ := reader.ReadString('\n') response = strings.TrimSpace(strings.ToLower(response)) if response == "y" || response == "yes" { return input } continue } return input } } func getDirectoryInput(prompt, defaultDir string, forceMode bool) string { reader := bufio.NewReader(os.Stdin) for { fmt.Printf("%s [%s]: ", prompt, defaultDir) input, _ := reader.ReadString('\n') input = strings.TrimSpace(input) if input == "" { input = defaultDir } // Check if directory exists if _, err := os.Stat(input); os.IsNotExist(err) { if forceMode { if err := os.MkdirAll(input, 0755); err != nil { printError(fmt.Sprintf("Failed to create directory '%s': %v", input, err)) continue } } else { fmt.Printf("Directory '%s' doesn't exist. Create it? (Y/n): ", input) response, _ := reader.ReadString('\n') response = strings.TrimSpace(strings.ToLower(response)) if response == "n" || response == "no" { continue } if err := os.MkdirAll(input, 0755); err != nil { printError(fmt.Sprintf("Failed to create directory '%s': %v", input, err)) continue } } } // Check if directory is writable if info, err := os.Stat(input); err == nil { if info.Mode()&0200 == 0 { printError(fmt.Sprintf("Directory '%s' is not writable", input)) continue } } return input } } func getYesNoInput(prompt string, defaultYes bool, forceMode bool) bool { if forceMode { return defaultYes } reader := bufio.NewReader(os.Stdin) for { if defaultYes { fmt.Printf("%s (Y/n): ", prompt) } else { fmt.Printf("%s (y/N): ", prompt) } input, _ := reader.ReadString('\n') input = strings.TrimSpace(strings.ToLower(input)) if input == "" { return defaultYes } if input == "y" || input == "yes" { return true } if input == "n" || input == "no" { return false } printError("Please enter 'y' or 'n'") } } func getChoice(prompt string, choices []string, forceMode bool) int { if forceMode && len(choices) > 0 { return 1 // Default to first choice in force mode } reader := bufio.NewReader(os.Stdin) for { fmt.Println(prompt) for i, choice := range choices { fmt.Printf("%d. %s\n", i+1, choice) } fmt.Print("Enter your choice: ") input, _ := reader.ReadString('\n') input = strings.TrimSpace(input) if choice, err := strconv.Atoi(input); err == nil && choice >= 1 && choice <= len(choices) { return choice } printError(fmt.Sprintf("Invalid choice. Please enter a number between 1 and %d.", len(choices))) } } // WireGuard key generation (safe version) func generateWireGuardKeys() (string, string, error) { // Generate private key privateKeyBytes := make([]byte, 32) if _, err := rand.Read(privateKeyBytes); err != nil { return "", "", fmt.Errorf("failed to generate random bytes: %w", err) } // Ensure the key is valid for curve25519 privateKeyBytes[0] &= 248 privateKeyBytes[31] &= 127 privateKeyBytes[31] |= 64 // Generate public key (safe conversion) var privateKeyArray [32]byte copy(privateKeyArray[:], privateKeyBytes) var publicKeyBytes [32]byte curve25519.ScalarBaseMult(&publicKeyBytes, &privateKeyArray) privateKey := base64.StdEncoding.EncodeToString(privateKeyBytes[:]) publicKey := base64.StdEncoding.EncodeToString(publicKeyBytes[:]) return privateKey, publicKey, nil } // Configuration generation func generateConfig(hostname, ipAddress, privateKey, routingMode, interfaceName string) string { var config strings.Builder // Interface section config.WriteString("[Interface]\n") config.WriteString(fmt.Sprintf("Address = %s/24\n", ipAddress)) config.WriteString(fmt.Sprintf("PrivateKey = %s\n", privateKey)) // Add ListenPort for static servers if port, exists := staticServerPorts[ipAddress]; exists { config.WriteString(fmt.Sprintf("ListenPort = %s\n", port)) } // Add DNS for full tunnel mode if routingMode == "full_tunnel" { config.WriteString("DNS = 1.1.1.1, 8.8.8.8\n") } // Add default peers for _, peer := range defaultPeers { config.WriteString(fmt.Sprintf("\n# %s (%s)\n", peer.Name, peer.Description)) config.WriteString("[Peer]\n") config.WriteString(fmt.Sprintf("PublicKey = %s\n", peer.PublicKey)) // Set AllowedIPs based on routing mode if routingMode == "full_tunnel" { config.WriteString("AllowedIPs = 0.0.0.0/0, ::/0\n") } else { config.WriteString(fmt.Sprintf("AllowedIPs = %s\n", peer.AllowedIPs)) } config.WriteString(fmt.Sprintf("Endpoint = %s\n", peer.Endpoint)) config.WriteString(fmt.Sprintf("PersistentKeepalive = %d\n", peer.PersistentKeepalive)) } return config.String() } // Generate peer configuration for Zion func generateZionPeerConfig(hostname, publicKey, ipAddress string) string { var config strings.Builder config.WriteString(fmt.Sprintf("# %s\n", hostname)) config.WriteString("[Peer]\n") config.WriteString(fmt.Sprintf("PublicKey = %s\n", publicKey)) config.WriteString(fmt.Sprintf("AllowedIPs = %s/32\n", ipAddress)) return config.String() } // System checks func checkDependencies() error { deps := []string{"wg", "wg-quick"} for _, dep := range deps { if _, err := exec.LookPath(dep); err != nil { return fmt.Errorf("missing dependency: %s", dep) } } return nil } func isRunningAsRoot() bool { return os.Geteuid() == 0 } func checkFileExists(filepath string, forceMode bool) bool { if _, err := os.Stat(filepath); err == nil { if forceMode { printWarning(fmt.Sprintf("File '%s' already exists, overwriting due to force mode", filepath)) return true } printWarning(fmt.Sprintf("File '%s' already exists", filepath)) return getYesNoInput("Do you want to overwrite it?", false, false) } return true } // Display instructions func displayZionInstructions(hostname, publicKey, ipAddress string) { printHeader("IMPORTANT: Update Zion Server Configuration") fmt.Println() printWarning("You MUST add this peer to Zion's configuration file:") fmt.Println(" /etc/wireguard/wg0.conf") fmt.Println() fmt.Println("Add the following peer section to Zion's config:") fmt.Println("----------------------------------------") fmt.Println(generateZionPeerConfig(hostname, publicKey, ipAddress)) fmt.Println("----------------------------------------") fmt.Println() printWarning("After updating Zion's config:") fmt.Println("1. Save the file") fmt.Println("2. Restart Zion's WireGuard: sudo systemctl restart wg-quick@wg0") fmt.Println("3. Then start this node's WireGuard: sudo wg-quick up " + hostname) fmt.Println() // Show Zion's current configuration structure fmt.Println("Zion's current configuration structure:") fmt.Println("----------------------------------------") fmt.Println("[Interface]") fmt.Println("Address = 10.8.0.1/24") fmt.Println("ListenPort = 51820") fmt.Println("PrivateKey = ") fmt.Println("PostUp = ") fmt.Println("PostDown = ") fmt.Println() // Show existing peers for name, pubKey := range map[string]string{ "Cth": "NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0=", "Aza": "qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc=", "Nyar": "2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo=", "Galaxy": "QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM=", "nanocube": "/ZImoATDIS0e0N08CD7mqWbhtGlSnynpPuY04Ed4Zyc=", "jupiter": "YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw=", "HASS": "C+Poz/7DaXCxe4HZiL6D5cld4jMt5o1gBq3iPiBzrg0=", "framebot": "loS3yZapqmt6lP53Q+s4EvUzw6FmwgZC8jzgLluJ1Es=", } { fmt.Printf("#%s\n", name) fmt.Println("[Peer]") fmt.Printf("PublicKey = %s\n", pubKey) fmt.Printf("AllowedIPs = 10.8.0.x/32\n") fmt.Println() } fmt.Println("# Add your peer here:") fmt.Printf("# %s\n", hostname) fmt.Println("# [Peer]") fmt.Printf("# PublicKey = %s\n", publicKey) fmt.Printf("# AllowedIPs = %s/32\n", ipAddress) fmt.Println("----------------------------------------") fmt.Println() } func displayFullTunnelInstructions() { fmt.Println() printWarning("FULL TUNNEL MODE DETECTED - Endpoint Changes Required:") fmt.Println() fmt.Println("Since this node will route ALL traffic through the VPN, you need to:") fmt.Println() fmt.Println("1. Update Zion's config (/etc/wireguard/wg0.conf) to allow this peer:") fmt.Println(" - Add the peer section as shown above") fmt.Println(" - Ensure Zion has proper iptables rules for NAT/masquerading") fmt.Println() fmt.Println("2. Check Zion's iptables rules (run on Zion server):") fmt.Println(" sudo iptables -t nat -L POSTROUTING") fmt.Println(" sudo iptables -L FORWARD") fmt.Println() fmt.Println("3. If Zion doesn't have proper NAT rules, add them:") fmt.Println(" sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE") fmt.Println(" sudo iptables -A FORWARD -i wg0 -j ACCEPT") fmt.Println(" sudo iptables -A FORWARD -o wg0 -j ACCEPT") fmt.Println() fmt.Println("4. Enable IP forwarding on Zion (if not already enabled):") fmt.Println(" echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf") fmt.Println(" sudo sysctl -p") fmt.Println() } func displayNextSteps(hostname, interfaceName, publicKey, ipAddress, routingMode string, runningAsRoot bool) { step(4, "Next Steps") fmt.Println() if runningAsRoot { printStatus("Ready to start WireGuard:") fmt.Printf(" systemctl enable --now wg-quick@%s\n", interfaceName) } else { printWarning("To enable WireGuard (requires root):") fmt.Printf(" sudo cp %s /etc/wireguard/\n", filepath.Join("wireguard_configs", fmt.Sprintf("%s.conf", interfaceName))) fmt.Printf(" sudo chmod 600 /etc/wireguard/%s.conf\n", interfaceName) fmt.Printf(" sudo systemctl enable --now wg-quick@%s\n", interfaceName) } fmt.Println() printWarning("IMPORTANT: Update other nodes with this peer info:") fmt.Printf(" PublicKey = %s\n", publicKey) fmt.Printf(" AllowedIPs = %s/32\n", ipAddress) fmt.Println() if routingMode == "full_tunnel" { displayFullTunnelInstructions() } // Zion update instructions fmt.Printf("%s========================================%s\n", Red, Reset) fmt.Printf("%s !!! NOW UPDATE ZION SERVER !!! %s\n", Red, Reset) fmt.Printf("%s========================================%s\n", Red, Reset) fmt.Println() printWarning("You MUST add this peer to Zion's config (/etc/wireguard/wg0.conf):") fmt.Println() fmt.Printf("%s#%s%s\n", Yellow, hostname, Reset) fmt.Printf("%s[Peer]%s\n", Yellow, Reset) fmt.Printf("%sPublicKey = %s%s\n", Yellow, publicKey, Reset) fmt.Printf("%sAllowedIPs = %s/32%s\n", Yellow, ipAddress, Reset) fmt.Println() printWarning("After updating Zion, restart its WireGuard:") fmt.Println(" systemctl restart wg-quick@wg0") fmt.Println() if runningAsRoot { printWarning("Then restart this node's WireGuard:") fmt.Printf(" systemctl restart wg-quick@%s\n", interfaceName) } else { printWarning("Then restart this node's WireGuard:") fmt.Printf(" sudo systemctl restart wg-quick@%s\n", interfaceName) } } // Main function func main() { // Parse command line flags var forceMode bool flag.BoolVar(&forceMode, "force", false, "Skip confirmations and use defaults") flag.Parse() // Initialize application state state := &AppState{ ForceMode: forceMode, RunningAsRoot: isRunningAsRoot(), StepCounter: 1, } printHeader("WireGuard Configuration Setup v" + ScriptVersion) fmt.Println("This script will help you create WireGuard keys and configuration files.") fmt.Println("Based on the CURRENT_WORKING configuration with Zion as central server.") fmt.Println("Default peers (Zion & CTH) will be automatically included.") fmt.Println() if forceMode { printWarning("Force mode enabled - skipping confirmations and using defaults") fmt.Println() } // Determine WireGuard directory if state.RunningAsRoot { state.WGDirectory = "/etc/wireguard" printStatus("Running as root - using system directories") printStatus(fmt.Sprintf("WireGuard directory: %s", state.WGDirectory)) } else { state.WGDirectory = "wireguard_configs" printWarning("Not running as root - using current directory") printStatus(fmt.Sprintf("WireGuard directory: %s", state.WGDirectory)) printWarning("You'll need to manually copy config files to /etc/wireguard/ later") } fmt.Println() // Check dependencies if err := checkDependencies(); err != nil { printError(err.Error()) printStatus("Install with: apt install wireguard-tools") os.Exit(1) } // Get directory for non-root users if !state.RunningAsRoot { step(state.StepCounter, "Directory Selection") state.StepCounter++ printStatus("Choose where to save WireGuard files:") fmt.Printf(" - Current directory: %s\n", state.WGDirectory) fmt.Printf(" - Home directory: %s\n", os.Getenv("HOME")) fmt.Println(" - Custom directory") fmt.Println() state.WGDirectory = getDirectoryInput("Enter directory path for WireGuard files", state.WGDirectory, forceMode) fmt.Println() } // Create directory if err := os.MkdirAll(state.WGDirectory, 0755); err != nil { printError(fmt.Sprintf("Failed to create directory: %v", err)) os.Exit(1) } // Get node information step(state.StepCounter, "Node Information") state.StepCounter++ fmt.Println() hostname := getUserInput("Enter hostname for this node: ", validateHostname, forceMode) // Get IP address fmt.Println() fmt.Println("Available IP ranges:") fmt.Println(" - 10.8.0.x (recommended for NextGen network)") fmt.Println(" - 10.0.0.x (alternative range)") fmt.Println() ipAddress := getUserInput("Enter IP address for this node (e.g., 10.8.0.30): ", validateIP, forceMode) // Get interface name fmt.Println() fmt.Println("Interface name options:") fmt.Println(" - wg0 (default, most common)") fmt.Println(" - wg1, wg2, etc. (if wg0 is already in use)") fmt.Println(" - Custom name (e.g., nextgen, vpn, etc.)") fmt.Println() interfaceName := getUserInput("Enter interface name (default: wg0): ", validateInterface, forceMode) if interfaceName == "" { interfaceName = "wg0" } // Check if configuration file already exists configFile := filepath.Join(state.WGDirectory, fmt.Sprintf("%s.conf", interfaceName)) if !checkFileExists(configFile, forceMode) { printError("Operation cancelled. Please choose a different interface name or remove the existing file.") os.Exit(1) } // Configuration options fmt.Println() step(state.StepCounter, "Configuration Options") state.StepCounter++ fmt.Println() fmt.Println("Choose an option:") fmt.Println("1. Generate keys only (manual config creation)") fmt.Println("2. Generate keys + complete config (recommended)") fmt.Println() var configChoice int if forceMode { configChoice = 2 // Default to complete config in force mode printStatus("Force mode: Using complete config generation") } else { configChoice = getChoice("Enter your choice:", []string{"Generate keys only", "Generate keys + complete config"}, false) } // Traffic routing options var routingMode string if configChoice == 2 { fmt.Println() fmt.Println("Traffic routing options:") fmt.Println("1. WireGuard traffic only (10.8.0.x network only)") fmt.Println("2. All traffic through VPN (full tunnel)") fmt.Println() fmt.Println("Note: Full tunnel routes ALL internet traffic through the VPN.") fmt.Println(" WireGuard-only keeps your regular internet traffic separate.") fmt.Println() var routingChoice int if forceMode { routingChoice = 1 // Default to WireGuard-only in force mode printStatus("Force mode: Using WireGuard-only routing") } else { routingChoice = getChoice("Enter your choice:", []string{"WireGuard traffic only", "All traffic through VPN"}, false) } if routingChoice == 1 { routingMode = "wg_only" printStatus("Selected: WireGuard traffic only") } else { routingMode = "full_tunnel" printStatus("Selected: All traffic through VPN") } } printStatus(fmt.Sprintf("Starting setup for %s (%s)...", hostname, ipAddress)) fmt.Println() // Generate keys printStatus("Generating WireGuard keys...") privateKey, publicKey, err := generateWireGuardKeys() if err != nil { printError(fmt.Sprintf("Failed to generate keys: %v", err)) os.Exit(1) } // Save keys privateKeyFile := filepath.Join(state.WGDirectory, fmt.Sprintf("%s_private.key", hostname)) publicKeyFile := filepath.Join(state.WGDirectory, fmt.Sprintf("%s_public.key", hostname)) if err := os.WriteFile(privateKeyFile, []byte(privateKey), 0600); err != nil { printError(fmt.Sprintf("Failed to save private key: %v", err)) os.Exit(1) } if err := os.WriteFile(publicKeyFile, []byte(publicKey), 0600); err != nil { printError(fmt.Sprintf("Failed to save public key: %v", err)) os.Exit(1) } printStatus("Keys generated successfully!") fmt.Printf(" Private key: %s\n", privateKeyFile) fmt.Printf(" Public key: %s\n", publicKeyFile) fmt.Println() // Display node information step(state.StepCounter, "Node Information") state.StepCounter++ fmt.Println("==========================================") fmt.Printf("HOSTNAME: %s\n", hostname) fmt.Printf("IP ADDRESS: %s\n", ipAddress) fmt.Printf("PRIVATE KEY: %s\n", privateKey) fmt.Printf("PUBLIC KEY: %s\n", publicKey) fmt.Printf("INTERFACE: %s\n", interfaceName) fmt.Printf("ROUTING MODE: %s\n", routingMode) fmt.Println("==========================================") fmt.Println() // Save structured info infoFile := filepath.Join(state.WGDirectory, fmt.Sprintf("%s_wg_info.json", hostname)) if state.RunningAsRoot { infoFile = filepath.Join("/tmp", fmt.Sprintf("%s_wg_info.json", hostname)) } nodeConfig := NodeConfig{ Hostname: hostname, IPAddress: ipAddress, PrivateKey: privateKey, PublicKey: publicKey, RoutingMode: routingMode, Interface: interfaceName, Generated: time.Now().Format(time.RFC3339), ScriptVer: ScriptVersion, RunningRoot: state.RunningAsRoot, } infoData, err := json.MarshalIndent(nodeConfig, "", " ") if err != nil { printError(fmt.Sprintf("Failed to marshal config: %v", err)) os.Exit(1) } if err := os.WriteFile(infoFile, infoData, 0600); err != nil { printError(fmt.Sprintf("Failed to save info file: %v", err)) os.Exit(1) } printStatus(fmt.Sprintf("Information saved to: %s", infoFile)) fmt.Println() // Generate complete config if requested if configChoice == 2 { printStatus(fmt.Sprintf("Generating complete %s.conf...", interfaceName)) configContent := generateConfig(hostname, ipAddress, privateKey, routingMode, interfaceName) if err := os.WriteFile(configFile, []byte(configContent), 0600); err != nil { printError(fmt.Sprintf("Failed to write config file: %v", err)) os.Exit(1) } printStatus(fmt.Sprintf("Config written to: %s", configFile)) if state.RunningAsRoot { printStatus("Permissions set to 600") } fmt.Println() // Display Zion integration instructions displayZionInstructions(hostname, publicKey, ipAddress) // Display next steps displayNextSteps(hostname, interfaceName, publicKey, ipAddress, routingMode, state.RunningAsRoot) // Config preview fmt.Printf("%sConfig Preview:%s\n", Blue, Reset) fmt.Println("----------------------------------------") lines := strings.Split(configContent, "\n") for i, line := range lines { if i >= 8 { // Show more lines to include default peers break } fmt.Println(line) } fmt.Printf(" [... and %d total lines]\n", len(lines)) fmt.Println("----------------------------------------") fmt.Println() } else { // Manual config generation path step(state.StepCounter, "Next Steps") state.StepCounter++ fmt.Println() printStatus("Keys generated successfully!") fmt.Printf(" Private key: %s\n", privateKeyFile) fmt.Printf(" Public key: %s\n", publicKeyFile) fmt.Println() printWarning("Next steps:") fmt.Printf(" - Create %s.conf manually using the keys above\n", interfaceName) fmt.Printf(" - Copy config to %s\n", filepath.Join(state.WGDirectory, fmt.Sprintf("%s.conf", interfaceName))) if state.RunningAsRoot { fmt.Printf(" - Set permissions: chmod 600 %s\n", filepath.Join(state.WGDirectory, fmt.Sprintf("%s.conf", interfaceName))) fmt.Printf(" - Enable/start: systemctl enable --now wg-quick@%s\n", interfaceName) } else { fmt.Printf(" - Copy to system: sudo cp %s /etc/wireguard/\n", filepath.Join(state.WGDirectory, fmt.Sprintf("%s.conf", interfaceName))) fmt.Printf(" - Set permissions: sudo chmod 600 /etc/wireguard/%s.conf\n", interfaceName) fmt.Printf(" - Enable/start: sudo systemctl enable --now wg-quick@%s\n", interfaceName) } fmt.Println() fmt.Printf("%s========================================%s\n", Red, Reset) fmt.Printf("%s !!! NOW UPDATE ZION SERVER !!! %s\n", Red, Reset) fmt.Printf("%s========================================%s\n", Red, Reset) fmt.Println() printWarning("You MUST add this peer to Zion's config (/etc/wireguard/wg0.conf):") fmt.Println() fmt.Printf("%s#%s%s\n", Yellow, hostname, Reset) fmt.Printf("%s[Peer]%s\n", Yellow, Reset) fmt.Printf("%sPublicKey = %s%s\n", Yellow, publicKey, Reset) fmt.Printf("%sAllowedIPs = %s/32%s\n", Yellow, ipAddress, Reset) fmt.Println() printWarning("After updating Zion, restart its WireGuard:") fmt.Println(" systemctl restart wg-quick@wg0") fmt.Println() if state.RunningAsRoot { printWarning("Then restart this node's WireGuard:") fmt.Printf(" systemctl restart wg-quick@%s\n", interfaceName) } else { printWarning("Then restart this node's WireGuard:") fmt.Printf(" sudo systemctl restart wg-quick@%s\n", interfaceName) } } fmt.Println() printStatus(fmt.Sprintf("Setup complete for %s!", hostname)) fmt.Println() }