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:
@@ -4,9 +4,11 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sdomino/scribble"
|
||||
@@ -17,6 +19,10 @@ import (
|
||||
"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 {
|
||||
conn *scribble.Driver
|
||||
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
|
||||
for _, i := range results {
|
||||
user := model.User{}
|
||||
@@ -186,6 +197,122 @@ func (o *JsonDB) Init() error {
|
||||
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
|
||||
func (o *JsonDB) GetUsers() ([]model.User, error) {
|
||||
var users []model.User
|
||||
|
||||
Reference in New Issue
Block a user