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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3d6608ef80
commit
3b3ffd8ebf
@@ -0,0 +1,205 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user