Add WireGuard TUI implementation

- 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
This commit is contained in:
Calmcacil
2026-01-12 19:03:35 +01:00
parent 5ac68db854
commit 26120b8bc2
37 changed files with 6330 additions and 97 deletions

View File

@@ -0,0 +1,106 @@
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
}