|
|
|
@@ -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
|
|
|
|
|