- Add Go TUI with bubbletea for WireGuard management - Implement client CRUD operations with QR code generation - Add configuration and validation modules - Install/update scripts for client setup - Update Makefile to build binaries to bin/ directory - Add .gitignore for Go projects
107 lines
3.2 KiB
Go
107 lines
3.2 KiB
Go
package wireguard
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// GenerateServerConfig generates a server-side WireGuard [Peer] configuration
|
|
// and writes it to /etc/wireguard/conf.d/client-<name>.conf
|
|
func GenerateServerConfig(name, publicKey string, hasPSK bool, ipv4, ipv6, psk string) (string, error) {
|
|
var builder strings.Builder
|
|
|
|
builder.WriteString(fmt.Sprintf("[Peer]\n"))
|
|
builder.WriteString(fmt.Sprintf("# %s\n", name))
|
|
builder.WriteString(fmt.Sprintf("PublicKey = %s\n", publicKey))
|
|
|
|
if hasPSK {
|
|
builder.WriteString(fmt.Sprintf("PresharedKey = %s\n", psk))
|
|
}
|
|
|
|
builder.WriteString(fmt.Sprintf("AllowedIPs = %s/32", ipv4))
|
|
if ipv6 != "" {
|
|
builder.WriteString(fmt.Sprintf(", %s/128", ipv6))
|
|
}
|
|
builder.WriteString("\n")
|
|
|
|
configContent := builder.String()
|
|
|
|
configDir := "/etc/wireguard/conf.d"
|
|
configPath := filepath.Join(configDir, fmt.Sprintf("client-%s.conf", name))
|
|
|
|
if err := atomicWrite(configPath, configContent); err != nil {
|
|
return "", fmt.Errorf("failed to write server config: %w", err)
|
|
}
|
|
|
|
log.Printf("Generated server config: %s", configPath)
|
|
return configPath, nil
|
|
}
|
|
|
|
// GenerateClientConfig generates a client-side WireGuard configuration
|
|
// with [Interface] and [Peer] sections and writes it to /etc/wireguard/clients/<name>.conf
|
|
func GenerateClientConfig(name, privateKey, ipv4, ipv6, dns, serverPublicKey, endpoint string, port int, hasPSK bool, psk string) (string, error) {
|
|
var builder strings.Builder
|
|
|
|
// [Interface] section
|
|
builder.WriteString("[Interface]\n")
|
|
builder.WriteString(fmt.Sprintf("PrivateKey = %s\n", privateKey))
|
|
builder.WriteString(fmt.Sprintf("Address = %s/24", ipv4))
|
|
if ipv6 != "" {
|
|
builder.WriteString(fmt.Sprintf(", %s/64", ipv6))
|
|
}
|
|
builder.WriteString("\n")
|
|
builder.WriteString(fmt.Sprintf("DNS = %s\n", dns))
|
|
|
|
// [Peer] section
|
|
builder.WriteString("\n[Peer]\n")
|
|
builder.WriteString(fmt.Sprintf("PublicKey = %s\n", serverPublicKey))
|
|
|
|
if hasPSK {
|
|
builder.WriteString(fmt.Sprintf("PresharedKey = %s\n", psk))
|
|
}
|
|
|
|
builder.WriteString(fmt.Sprintf("Endpoint = %s:%d\n", endpoint, port))
|
|
builder.WriteString("AllowedIPs = 0.0.0.0/0, ::/0\n")
|
|
builder.WriteString("PersistentKeepalive = 25\n")
|
|
|
|
configContent := builder.String()
|
|
|
|
clientsDir := "/etc/wireguard/clients"
|
|
configPath := filepath.Join(clientsDir, fmt.Sprintf("%s.conf", name))
|
|
|
|
if err := atomicWrite(configPath, configContent); err != nil {
|
|
return "", fmt.Errorf("failed to write client config: %w", err)
|
|
}
|
|
|
|
log.Printf("Generated client config: %s", configPath)
|
|
return configPath, nil
|
|
}
|
|
|
|
// atomicWrite writes content to a file atomically
|
|
// Uses temp file + rename pattern for atomicity and sets permissions to 0600
|
|
func atomicWrite(path, content string) error {
|
|
// Ensure parent directory exists
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
|
|
// Write to temp file first
|
|
tempPath := path + ".tmp"
|
|
if err := os.WriteFile(tempPath, []byte(content), 0600); err != nil {
|
|
return fmt.Errorf("failed to write temp file: %w", err)
|
|
}
|
|
|
|
// Atomic rename
|
|
if err := os.Rename(tempPath, path); err != nil {
|
|
// Clean up temp file if rename fails
|
|
_ = os.Remove(tempPath)
|
|
return fmt.Errorf("failed to rename temp file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|