Files
wireguard-ui-multi/internal/server/model.go
T
sysopsandClaude Sonnet 5 3b3ffd8ebf Add wireguard-ui-multi core: multi-server DB, WireGuard manager, REST API, UI, installers
Implements the from-scratch multi-server WireGuard management fork per
CLAUDE.md spec: sqlite schema (servers/peers/audit_log/users), Curve25519
key generation, per-interface config rendering + wg-quick/systemd control,
nftables hook scaffolding, session+CSRF-protected REST API with QR code
and config download endpoints, a minimal vanilla-JS web UI, legacy
wg0.conf migration, and both a native installer and a Proxmox LXC
provisioning script (with auto-detected latest Debian template).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 02:53:14 +02:00

206 lines
5.8 KiB
Go

package server
import (
"database/sql"
"errors"
"time"
"gitea.perlbach24.de/scripte/wireguard-ui-multi/internal/database"
)
// Server represents a single, independent WireGuard interface.
type Server struct {
ID int64
Name string
InterfaceName string
ListenPort int
PrivateKey string
PublicKey string
AddressRange string
DNS string
MTU int
Enabled bool
CreatedAt time.Time
UpdatedAt time.Time
}
// Peer represents a WireGuard client belonging to a Server.
type Peer struct {
ID int64
ServerID int64
Name string
Email string
PublicKey string
PrivateKey string
PresharedKey string
AllowedIPs string
Endpoint string
PersistentKeepalive int
Enabled bool
ExpiresAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
var ErrNotFound = errors.New("not found")
// Store provides CRUD access to servers and peers.
type Store struct {
db *database.DB
}
func NewStore(db *database.DB) *Store {
return &Store{db: db}
}
func (s *Store) CreateServer(srv *Server) (int64, error) {
res, err := s.db.Exec(`INSERT INTO servers
(name, interface_name, listen_port, private_key, public_key, address_range, dns, mtu, enabled)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
srv.Name, srv.InterfaceName, srv.ListenPort, srv.PrivateKey, srv.PublicKey,
srv.AddressRange, srv.DNS, srv.MTU, boolToInt(srv.Enabled))
if err != nil {
return 0, err
}
return res.LastInsertId()
}
func (s *Store) UpdateServer(srv *Server) error {
_, err := s.db.Exec(`UPDATE servers SET
name = ?, interface_name = ?, listen_port = ?, private_key = ?, public_key = ?,
address_range = ?, dns = ?, mtu = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
srv.Name, srv.InterfaceName, srv.ListenPort, srv.PrivateKey, srv.PublicKey,
srv.AddressRange, srv.DNS, srv.MTU, boolToInt(srv.Enabled), srv.ID)
return err
}
func (s *Store) DeleteServer(id int64) error {
_, err := s.db.Exec(`DELETE FROM servers WHERE id = ?`, id)
return err
}
func (s *Store) GetServer(id int64) (*Server, error) {
row := s.db.QueryRow(`SELECT id, name, interface_name, listen_port, private_key, public_key,
address_range, dns, mtu, enabled, created_at, updated_at FROM servers WHERE id = ?`, id)
return scanServer(row)
}
func (s *Store) ListServers() ([]*Server, error) {
rows, err := s.db.Query(`SELECT id, name, interface_name, listen_port, private_key, public_key,
address_range, dns, mtu, enabled, created_at, updated_at FROM servers ORDER BY name`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []*Server
for rows.Next() {
srv, err := scanServerRows(rows)
if err != nil {
return nil, err
}
out = append(out, srv)
}
return out, rows.Err()
}
func (s *Store) CreatePeer(p *Peer) (int64, error) {
res, err := s.db.Exec(`INSERT INTO peers
(server_id, name, email, public_key, private_key, preshared_key, allowed_ips, endpoint,
persistent_keepalive, enabled, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
p.ServerID, p.Name, p.Email, p.PublicKey, p.PrivateKey, p.PresharedKey, p.AllowedIPs,
p.Endpoint, p.PersistentKeepalive, boolToInt(p.Enabled), p.ExpiresAt)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
func (s *Store) UpdatePeer(p *Peer) error {
_, err := s.db.Exec(`UPDATE peers SET
name = ?, email = ?, public_key = ?, preshared_key = ?, allowed_ips = ?, endpoint = ?,
persistent_keepalive = ?, enabled = ?, expires_at = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
p.Name, p.Email, p.PublicKey, p.PresharedKey, p.AllowedIPs, p.Endpoint,
p.PersistentKeepalive, boolToInt(p.Enabled), p.ExpiresAt, p.ID)
return err
}
func (s *Store) DeletePeer(id int64) error {
_, err := s.db.Exec(`DELETE FROM peers WHERE id = ?`, id)
return err
}
func (s *Store) GetPeer(id int64) (*Peer, error) {
row := s.db.QueryRow(`SELECT id, server_id, name, email, public_key, private_key, preshared_key,
allowed_ips, endpoint, persistent_keepalive, enabled, expires_at, created_at, updated_at
FROM peers WHERE id = ?`, id)
return scanPeer(row)
}
func (s *Store) ListPeersByServer(serverID int64) ([]*Peer, error) {
rows, err := s.db.Query(`SELECT id, server_id, name, email, public_key, private_key, preshared_key,
allowed_ips, endpoint, persistent_keepalive, enabled, expires_at, created_at, updated_at
FROM peers WHERE server_id = ? ORDER BY name`, serverID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []*Peer
for rows.Next() {
p, err := scanPeerRows(rows)
if err != nil {
return nil, err
}
out = append(out, p)
}
return out, rows.Err()
}
type scanner interface {
Scan(dest ...any) error
}
func scanServer(row scanner) (*Server, error) {
var srv Server
var enabled int
if err := row.Scan(&srv.ID, &srv.Name, &srv.InterfaceName, &srv.ListenPort, &srv.PrivateKey,
&srv.PublicKey, &srv.AddressRange, &srv.DNS, &srv.MTU, &enabled, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
srv.Enabled = enabled != 0
return &srv, nil
}
func scanServerRows(rows *sql.Rows) (*Server, error) { return scanServer(rows) }
func scanPeer(row scanner) (*Peer, error) {
var p Peer
var enabled int
if err := row.Scan(&p.ID, &p.ServerID, &p.Name, &p.Email, &p.PublicKey, &p.PrivateKey,
&p.PresharedKey, &p.AllowedIPs, &p.Endpoint, &p.PersistentKeepalive, &enabled,
&p.ExpiresAt, &p.CreatedAt, &p.UpdatedAt); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
p.Enabled = enabled != 0
return &p, nil
}
func scanPeerRows(rows *sql.Rows) (*Peer, error) { return scanPeer(rows) }
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}