Files
GHOSTWIRE/clientconf.go
T
Daniel Redetzke 4793e8dfba GHOSTWIRE 0.1.2: WireGuard server manager with web UI and API
Single Go binary that manages a WireGuard server based on pivpn's defaults:
- config.json as the single source of truth, reconciled to the kernel via
  netlink, wgctrl and its own nftables table (NAT, forward, input)
- web interface (dashboard, peers, peer detail, add peer, server, settings)
  and a JSON API for the future iOS app, with session and API-token auth
- client private keys are never stored; configs and QR codes shown once
- per-peer traffic statistics in stats.json, logs in GHOSTWIRE.jsonl
- HTTPS via Let's Encrypt, self-signed, certificate files or off
- self-managing: install, update (restores the old binary on failure),
  uninstall and passwd subcommands; systemd unit generated by the binary

Tested end to end on Ubuntu 26.04 (kernel 7.0) at dev.redetzke.aero.

Claude-Session: https://claude.ai/code/session_01RAnLbyQZ5ZTA7KqwXP98nw
2026-10-03 16:54:37 +03:00

110 lines
3.0 KiB
Go

package main
import (
"encoding/base64"
"fmt"
"net"
"net/netip"
"strconv"
"strings"
qrcode "github.com/skip2/go-qrcode"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func newPrivateKey() (wgtypes.Key, error) { return wgtypes.GeneratePrivateKey() }
func newPresharedKey() (wgtypes.Key, error) { return wgtypes.GenerateKey() }
func serverPublicKey(c *Config) string {
k, err := wgtypes.ParseKey(c.Server.PrivateKey)
if err != nil {
return ""
}
return k.PublicKey().String()
}
// effective returns the peer's value or the server default.
func peerDNS(c *Config, p *Peer) []string {
if p.DNS != nil {
return p.DNS
}
return c.Server.ClientDefaults.DNS
}
func peerAllowedIPs(c *Config, p *Peer) []string {
if p.AllowedIPs != nil {
return p.AllowedIPs
}
return c.Server.ClientDefaults.AllowedIPs
}
func peerKeepalive(c *Config, p *Peer) int {
if p.Keepalive != nil {
return *p.Keepalive
}
return c.Server.ClientDefaults.Keepalive
}
// peerAddresses returns the peer's tunnel addresses as host routes for the
// server side (/32, /128).
func peerAddresses(c *Config, p *Peer) []netip.Prefix {
v4 := netip.MustParseAddr(p.IPv4)
out := []netip.Prefix{netip.PrefixFrom(v4, 32)}
if c.Server.IPv6Enabled {
out = append(out, netip.PrefixFrom(mapIPv6(netip.MustParsePrefix(c.Server.IPv6), v4), 128))
}
return out
}
func endpointString(c *Config) string {
port := c.Server.EndpointPort
if port == 0 {
port = c.Server.ListenPort
}
host := c.Server.Endpoint
if host == "" {
host = "SET-ENDPOINT-IN-SERVER-SETTINGS"
}
return net.JoinHostPort(host, strconv.Itoa(port))
}
// clientConfig renders the wg-quick file for a peer. privateKey may be empty
// when the client generated its own keys.
func clientConfig(c *Config, p *Peer, privateKey string) string {
v4net := netip.MustParsePrefix(c.Server.IPv4)
v4 := netip.MustParseAddr(p.IPv4)
addr := fmt.Sprintf("%s/%d", v4, v4net.Bits())
if c.Server.IPv6Enabled {
v6net := netip.MustParsePrefix(c.Server.IPv6)
addr += fmt.Sprintf(",%s/%d", mapIPv6(v6net, v4), v6net.Bits())
}
if privateKey == "" {
privateKey = "<the private key of this device>"
}
var b strings.Builder
fmt.Fprintf(&b, "[Interface]\nPrivateKey = %s\nAddress = %s\n", privateKey, addr)
if dns := peerDNS(c, p); len(dns) > 0 {
fmt.Fprintf(&b, "DNS = %s\n", strings.Join(dns, ", "))
}
// No MTU line: the client picks one for the network it is on, as pivpn does.
fmt.Fprintf(&b, "\n[Peer]\nPublicKey = %s\n", serverPublicKey(c))
if p.PresharedKey != "" {
fmt.Fprintf(&b, "PresharedKey = %s\n", p.PresharedKey)
}
fmt.Fprintf(&b, "Endpoint = %s\nAllowedIPs = %s\n", endpointString(c), strings.Join(peerAllowedIPs(c, p), ", "))
if ka := peerKeepalive(c, p); ka > 0 {
fmt.Fprintf(&b, "PersistentKeepalive = %d\n", ka)
}
return b.String()
}
// qrDataURL returns a PNG QR code as a data: URL for direct use in <img src>.
func qrDataURL(text string) (string, error) {
png, err := qrcode.Encode(text, qrcode.Medium, 512)
if err != nil {
return "", err
}
return "data:image/png;base64," + base64.StdEncoding.EncodeToString(png), nil
}