Add multi-server data model + legacy DB migration (step 0)
Adds Server.ID/Name, ServerInterface.Name, Client.ServerID, and a new ServerSetting type as additive fields so existing single-server code paths keep working unchanged. jsondb.Init() now detects a pre-existing single-server db/server/ layout and mirrors it into new servers/, server_settings/, server_hashes/ collections plus backfills ServerID on existing clients, without touching/removing the legacy files yet.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
// Client model
|
// Client model
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
ServerID string `json:"server_id,omitempty"`
|
||||||
PrivateKey string `json:"private_key"`
|
PrivateKey string `json:"private_key"`
|
||||||
PublicKey string `json:"public_key"`
|
PublicKey string `json:"public_key"`
|
||||||
PresharedKey string `json:"preshared_key"`
|
PresharedKey string `json:"preshared_key"`
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
|
|
||||||
// Server model
|
// Server model
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
KeyPair *ServerKeypair
|
KeyPair *ServerKeypair
|
||||||
Interface *ServerInterface
|
Interface *ServerInterface
|
||||||
}
|
}
|
||||||
@@ -19,6 +21,7 @@ type ServerKeypair struct {
|
|||||||
|
|
||||||
// ServerInterface model
|
// ServerInterface model
|
||||||
type ServerInterface struct {
|
type ServerInterface struct {
|
||||||
|
Name string `json:"name,omitempty"` // OS interface name, e.g. wg0
|
||||||
Addresses []string `json:"addresses"`
|
Addresses []string `json:"addresses"`
|
||||||
ListenPort int `json:"listen_port,string"` // ,string to get listen_port string input as int
|
ListenPort int `json:"listen_port,string"` // ,string to get listen_port string input as int
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|||||||
@@ -15,3 +15,14 @@ type GlobalSetting struct {
|
|||||||
ConfigFilePath string `json:"config_file_path"`
|
ConfigFilePath string `json:"config_file_path"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServerSetting model holds the per-server-interface subset of settings
|
||||||
|
// that used to live in the single global GlobalSetting. Introduced for the
|
||||||
|
// multi-server extension; not yet wired into store/handler layers.
|
||||||
|
type ServerSetting struct {
|
||||||
|
EndpointAddress string `json:"endpoint_address"`
|
||||||
|
FirewallMark string `json:"firewall_mark"`
|
||||||
|
Table string `json:"table"`
|
||||||
|
ConfigFilePath string `json:"config_file_path"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sdomino/scribble"
|
"github.com/sdomino/scribble"
|
||||||
@@ -17,6 +19,10 @@ import (
|
|||||||
"github.com/ngoduykhanh/wireguard-ui/util"
|
"github.com/ngoduykhanh/wireguard-ui/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// legacyDefaultServerID is the synthetic ID assigned to a pre-existing
|
||||||
|
// single-server installation when it is migrated to the multi-server layout.
|
||||||
|
const legacyDefaultServerID = "wg0"
|
||||||
|
|
||||||
type JsonDB struct {
|
type JsonDB struct {
|
||||||
conn *scribble.Driver
|
conn *scribble.Driver
|
||||||
dbPath string
|
dbPath string
|
||||||
@@ -161,6 +167,11 @@ func (o *JsonDB) Init() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrate legacy single-server layout to the new multi-server layout
|
||||||
|
if err := o.migrateLegacyServer(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// init cache
|
// init cache
|
||||||
for _, i := range results {
|
for _, i := range results {
|
||||||
user := model.User{}
|
user := model.User{}
|
||||||
@@ -186,6 +197,122 @@ func (o *JsonDB) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrateLegacyServer detects a pre-multi-server DB layout (single
|
||||||
|
// "server" collection, no "servers" registry yet) and migrates it into
|
||||||
|
// the new per-server layout ("servers/<id>.json", "server_settings/<id>.json",
|
||||||
|
// "server_hashes/<id>.json"), backfilling ServerID on every existing client.
|
||||||
|
// It is idempotent: it only runs once, guarded by the absence of the
|
||||||
|
// "servers" directory, and never deletes or modifies the legacy "server"
|
||||||
|
// collection - existing store methods keep reading/writing it directly
|
||||||
|
// until they are switched over to the new layout in a later step.
|
||||||
|
func (o *JsonDB) migrateLegacyServer() error {
|
||||||
|
legacyServerPath := path.Join(o.dbPath, "server")
|
||||||
|
legacyInterfacePath := path.Join(legacyServerPath, "interfaces.json")
|
||||||
|
newServersPath := path.Join(o.dbPath, "servers")
|
||||||
|
|
||||||
|
if _, err := os.Stat(legacyInterfacePath); os.IsNotExist(err) {
|
||||||
|
// nothing to migrate (fresh install)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(newServersPath); err == nil {
|
||||||
|
// already migrated
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var legacyInterface model.ServerInterface
|
||||||
|
if err := o.conn.Read("server", "interfaces", &legacyInterface); err != nil {
|
||||||
|
return fmt.Errorf("migration: cannot read legacy server interface: %v", err)
|
||||||
|
}
|
||||||
|
var legacyKeyPair model.ServerKeypair
|
||||||
|
if err := o.conn.Read("server", "keypair", &legacyKeyPair); err != nil {
|
||||||
|
return fmt.Errorf("migration: cannot read legacy server keypair: %v", err)
|
||||||
|
}
|
||||||
|
var legacyGlobalSettings model.GlobalSetting
|
||||||
|
if err := o.conn.Read("server", "global_settings", &legacyGlobalSettings); err != nil {
|
||||||
|
return fmt.Errorf("migration: cannot read legacy global settings: %v", err)
|
||||||
|
}
|
||||||
|
var legacyHashes model.ClientServerHashes
|
||||||
|
hasHashes := true
|
||||||
|
if err := o.conn.Read("server", "hashes", &legacyHashes); err != nil {
|
||||||
|
hasHashes = false
|
||||||
|
}
|
||||||
|
|
||||||
|
serverID := legacyDefaultServerID
|
||||||
|
ifaceName := legacyDefaultServerID
|
||||||
|
if legacyGlobalSettings.ConfigFilePath != "" {
|
||||||
|
base := path.Base(legacyGlobalSettings.ConfigFilePath)
|
||||||
|
if strings.HasSuffix(base, ".conf") {
|
||||||
|
ifaceName = strings.TrimSuffix(base, ".conf")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
legacyInterface.Name = ifaceName
|
||||||
|
|
||||||
|
server := model.Server{
|
||||||
|
ID: serverID,
|
||||||
|
Name: "Default Server",
|
||||||
|
KeyPair: &legacyKeyPair,
|
||||||
|
Interface: &legacyInterface,
|
||||||
|
}
|
||||||
|
if err := o.conn.Write("servers", serverID, server); err != nil {
|
||||||
|
return fmt.Errorf("migration: cannot write servers/%s.json: %v", serverID, err)
|
||||||
|
}
|
||||||
|
if err := util.ManagePerms(path.Join(newServersPath, serverID+".json")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
serverSetting := model.ServerSetting{
|
||||||
|
EndpointAddress: legacyGlobalSettings.EndpointAddress,
|
||||||
|
FirewallMark: legacyGlobalSettings.FirewallMark,
|
||||||
|
Table: legacyGlobalSettings.Table,
|
||||||
|
ConfigFilePath: legacyGlobalSettings.ConfigFilePath,
|
||||||
|
UpdatedAt: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
if err := o.conn.Write("server_settings", serverID, serverSetting); err != nil {
|
||||||
|
return fmt.Errorf("migration: cannot write server_settings/%s.json: %v", serverID, err)
|
||||||
|
}
|
||||||
|
if err := util.ManagePerms(path.Join(o.dbPath, "server_settings", serverID+".json")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasHashes {
|
||||||
|
if err := o.conn.Write("server_hashes", serverID, legacyHashes); err != nil {
|
||||||
|
return fmt.Errorf("migration: cannot write server_hashes/%s.json: %v", serverID, err)
|
||||||
|
}
|
||||||
|
if err := util.ManagePerms(path.Join(o.dbPath, "server_hashes", serverID+".json")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// backfill ServerID on every existing client
|
||||||
|
clientRecords, err := o.conn.ReadAll("clients")
|
||||||
|
if err != nil && err != scribble.ErrMissingCollection {
|
||||||
|
return fmt.Errorf("migration: cannot read clients: %v", err)
|
||||||
|
}
|
||||||
|
for _, rec := range clientRecords {
|
||||||
|
var client model.Client
|
||||||
|
if err := json.Unmarshal(rec, &client); err != nil {
|
||||||
|
return fmt.Errorf("migration: cannot decode client json: %v", err)
|
||||||
|
}
|
||||||
|
if client.ServerID != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
client.ServerID = serverID
|
||||||
|
if err := o.conn.Write("clients", client.ID, client); err != nil {
|
||||||
|
return fmt.Errorf("migration: cannot backfill server_id on client %s: %v", client.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: the legacy "server" directory is intentionally left in place
|
||||||
|
// (not renamed/removed) at this stage. Existing store methods (GetServer,
|
||||||
|
// GetGlobalSettings, SaveServerInterface, ...) still read/write it
|
||||||
|
// directly until they are switched over to the new per-server layout;
|
||||||
|
// removing it now would break the running app. The rename-to-backup
|
||||||
|
// step happens once those methods are migrated.
|
||||||
|
log.Printf("Migrated legacy single-server DB to multi-server layout (server id: %s); legacy db/server/ kept in place for now", serverID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetUsers func to get all users from the database
|
// GetUsers func to get all users from the database
|
||||||
func (o *JsonDB) GetUsers() ([]model.User, error) {
|
func (o *JsonDB) GetUsers() ([]model.User, error) {
|
||||||
var users []model.User
|
var users []model.User
|
||||||
|
|||||||
Reference in New Issue
Block a user