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

141
internal/tui/screens/qr.go Normal file
View File

@@ -0,0 +1,141 @@
package screens
import (
"fmt"
"strings"
"github.com/calmcacil/wg-admin/internal/wireguard"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/mdp/qrterminal/v3"
)
// QRScreen displays a QR code for a WireGuard client configuration
type QRScreen struct {
clientName string
configContent string
qrCode string
inlineMode bool
width, height int
errorMsg string
}
// NewQRScreen creates a new QR screen for displaying client config QR codes
func NewQRScreen(clientName string) *QRScreen {
return &QRScreen{
clientName: clientName,
inlineMode: true, // Start in inline mode
}
}
// Init initializes the QR screen
func (s *QRScreen) Init() tea.Cmd {
return s.loadConfig
}
// Update handles messages for the QR screen
func (s *QRScreen) Update(msg tea.Msg) (Screen, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "escape":
// Return to list screen (parent should handle this)
return nil, nil
case "f":
// Toggle between inline and fullscreen mode
s.inlineMode = !s.inlineMode
s.generateQRCode()
}
case tea.WindowSizeMsg:
// Handle terminal resize
s.width = msg.Width
s.height = msg.Height
s.generateQRCode()
case configLoadedMsg:
s.configContent = msg.content
s.generateQRCode()
case errMsg:
s.errorMsg = msg.err.Error()
}
return s, nil
}
// View renders the QR screen
func (s *QRScreen) View() string {
if s.errorMsg != "" {
return s.renderError()
}
if s.qrCode == "" {
return "Loading QR code..."
}
return s.renderQR()
}
// loadConfig loads the client configuration
func (s *QRScreen) loadConfig() tea.Msg {
content, err := wireguard.GetClientConfigContent(s.clientName)
if err != nil {
return errMsg{err: err}
}
return configLoadedMsg{content: content}
}
// generateQRCode generates the QR code based on current mode and terminal size
func (s *QRScreen) generateQRCode() {
if s.configContent == "" {
return
}
// Generate QR code and capture output
var builder strings.Builder
// Generate ANSI QR code using half-block characters
qrterminal.GenerateHalfBlock(s.configContent, qrterminal.L, &builder)
s.qrCode = builder.String()
}
// renderQR renders the QR code with styling
func (s *QRScreen) renderQR() string {
styleTitle := lipgloss.NewStyle().
Foreground(lipgloss.Color("62")).
Bold(true).
MarginBottom(1)
styleHelp := lipgloss.NewStyle().
Foreground(lipgloss.Color("241")).
MarginTop(1)
styleQR := lipgloss.NewStyle().
MarginLeft(2)
title := styleTitle.Render(fmt.Sprintf("QR Code: %s", s.clientName))
help := "Press [f] to toggle fullscreen • Press [q/Escape] to return"
return title + "\n\n" + styleQR.Render(s.qrCode) + "\n" + styleHelp.Render(help)
}
// renderError renders an error message
func (s *QRScreen) renderError() string {
styleError := lipgloss.NewStyle().
Foreground(lipgloss.Color("196")).
Bold(true)
styleHelp := lipgloss.NewStyle().
Foreground(lipgloss.Color("241")).
MarginTop(1)
title := styleError.Render("Error")
message := s.errorMsg
help := "Press [q/Escape] to return"
return title + "\n\n" + message + "\n" + styleHelp.Render(help)
}
// Messages
// configLoadedMsg is sent when the client configuration is loaded
type configLoadedMsg struct {
content string
}