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

152
internal/tui/screens/add.go Normal file
View File

@@ -0,0 +1,152 @@
package screens
import (
"fmt"
"github.com/calmcacil/wg-admin/internal/config"
"github.com/calmcacil/wg-admin/internal/validation"
"github.com/calmcacil/wg-admin/internal/wireguard"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
)
// AddScreen is a form for adding new WireGuard clients
type AddScreen struct {
form *huh.Form
quitting bool
}
// Styles
var (
addTitleStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("62")).
Bold(true).
MarginBottom(1)
addHelpStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("241")).
MarginTop(1)
)
// NewAddScreen creates a new add screen
func NewAddScreen() *AddScreen {
// Get default DNS from config
cfg, err := config.LoadConfig()
defaultDNS := "8.8.8.8, 8.8.4.4"
if err == nil && cfg.DNSServers != "" {
defaultDNS = cfg.DNSServers
}
// Create the form
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Key("name").
Title("Client Name").
Description("Name for the new client (alphanumeric, -, _)").
Placeholder("e.g., laptop-john").
Validate(func(s string) error {
return validation.ValidateClientName(s)
}),
huh.NewInput().
Key("dns").
Title("DNS Servers").
Description("Comma-separated IPv4 addresses").
Placeholder("e.g., 8.8.8.8, 8.8.4.4").
Value(&defaultDNS).
Validate(func(s string) error {
return validation.ValidateDNSServers(s)
}),
huh.NewConfirm().
Key("use_psk").
Title("Use Preshared Key").
Description("Enable additional security layer with a preshared key").
Affirmative("Yes").
Negative("No"),
),
)
return &AddScreen{
form: form,
quitting: false,
}
}
// Init initializes the add screen
func (s *AddScreen) Init() tea.Cmd {
return s.form.Init()
}
// Update handles messages for the add screen
func (s *AddScreen) Update(msg tea.Msg) (Screen, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c", "esc":
// Cancel and return to list
return nil, nil
}
}
// Update the form
form, cmd := s.form.Update(msg)
if f, ok := form.(*huh.Form); ok {
s.form = f
}
cmds = append(cmds, cmd)
// Check if form is completed
if s.form.State == huh.StateCompleted {
name := s.form.GetString("name")
dns := s.form.GetString("dns")
usePSK := s.form.GetBool("use_psk")
// Create the client
return s, s.createClient(name, dns, usePSK)
}
return s, tea.Batch(cmds...)
}
// View renders the add screen
func (s *AddScreen) View() string {
if s.quitting {
return ""
}
content := lipgloss.JoinVertical(
lipgloss.Left,
addTitleStyle.Render("Add New WireGuard Client"),
s.form.View(),
addHelpStyle.Render("Press Enter to submit • Esc to cancel"),
)
return content
}
// createClient creates a new WireGuard client
func (s *AddScreen) createClient(name, dns string, usePSK bool) tea.Cmd {
return func() tea.Msg {
// Create the client via wireguard package
err := wireguard.CreateClient(name, dns, usePSK)
if err != nil {
return errMsg{err: fmt.Errorf("failed to create client: %w", err)}
}
// Return success message
return ClientCreatedMsg{
Name: name,
}
}
}
// Messages
// ClientCreatedMsg is sent when a client is successfully created
type ClientCreatedMsg struct {
Name string
}