254 lines
7.0 KiB
Go
254 lines
7.0 KiB
Go
package sqlitedb
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/DigitalTolk/wireguard-ui/model"
|
|
"github.com/labstack/gommon/log"
|
|
)
|
|
|
|
// MigrateFromJSON checks if an old JSON database exists and migrates data to SQLite.
|
|
// It detects the old DB by checking for ./db/server/ directory.
|
|
// After migration, it renames the old DB directory to ./db.json.bak/
|
|
func MigrateFromJSON(sqliteDB *SqliteDB, jsonDBPath string) error {
|
|
serverDir := filepath.Join(jsonDBPath, "server")
|
|
if _, err := os.Stat(serverDir); os.IsNotExist(err) {
|
|
return nil // no old JSON DB to migrate
|
|
}
|
|
|
|
log.Info("Found legacy JSON database, starting migration to SQLite...")
|
|
|
|
// migrate server interface
|
|
if err := migrateServerInterface(sqliteDB, jsonDBPath); err != nil {
|
|
return fmt.Errorf("migrate server interface: %w", err)
|
|
}
|
|
|
|
// migrate server keypair
|
|
if err := migrateServerKeypair(sqliteDB, jsonDBPath); err != nil {
|
|
return fmt.Errorf("migrate server keypair: %w", err)
|
|
}
|
|
|
|
// migrate global settings
|
|
if err := migrateGlobalSettings(sqliteDB, jsonDBPath); err != nil {
|
|
return fmt.Errorf("migrate global settings: %w", err)
|
|
}
|
|
|
|
// migrate hashes
|
|
if err := migrateHashes(sqliteDB, jsonDBPath); err != nil {
|
|
return fmt.Errorf("migrate hashes: %w", err)
|
|
}
|
|
|
|
// NOTE: users are NOT migrated — legacy password users cannot log in with SSO-only auth.
|
|
// The first OIDC login will auto-provision as admin when len(users) == 0.
|
|
|
|
// migrate clients
|
|
if err := migrateClients(sqliteDB, jsonDBPath); err != nil {
|
|
return fmt.Errorf("migrate clients: %w", err)
|
|
}
|
|
|
|
// migrate wake-on-lan hosts
|
|
if err := migrateWakeOnLanHosts(sqliteDB, jsonDBPath); err != nil {
|
|
return fmt.Errorf("migrate wake-on-lan hosts: %w", err)
|
|
}
|
|
|
|
// rename old DB directory
|
|
backupPath := jsonDBPath + ".json.bak"
|
|
if err := os.Rename(jsonDBPath, backupPath); err != nil {
|
|
log.Warnf("Could not rename old JSON DB directory: %v", err)
|
|
log.Warn("Old data remains at:", jsonDBPath)
|
|
} else {
|
|
log.Infof("Legacy JSON database backed up to %s", backupPath)
|
|
}
|
|
|
|
log.Info("Migration to SQLite completed successfully")
|
|
return nil
|
|
}
|
|
|
|
func readJSONFile(path string, v interface{}) error {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(data, v)
|
|
}
|
|
|
|
// legacy structs handle the old `,string` JSON tags from the original codebase
|
|
type legacyServerInterface struct {
|
|
Addresses []string `json:"addresses"`
|
|
ListenPort int `json:"listen_port,string"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
PostUp string `json:"post_up"`
|
|
PreDown string `json:"pre_down"`
|
|
PostDown string `json:"post_down"`
|
|
}
|
|
|
|
type legacyGlobalSetting struct {
|
|
EndpointAddress string `json:"endpoint_address"`
|
|
DNSServers []string `json:"dns_servers"`
|
|
MTU int `json:"mtu,string"`
|
|
PersistentKeepalive int `json:"persistent_keepalive,string"`
|
|
FirewallMark string `json:"firewall_mark"`
|
|
Table string `json:"table"`
|
|
ConfigFilePath string `json:"config_file_path"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
func migrateServerInterface(db *SqliteDB, jsonDBPath string) error {
|
|
filePath := filepath.Join(jsonDBPath, "server", "interfaces.json")
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
var iface legacyServerInterface
|
|
if err := readJSONFile(filePath, &iface); err != nil {
|
|
return err
|
|
}
|
|
|
|
addrJSON, _ := json.Marshal(iface.Addresses)
|
|
_, err := db.db.Exec(
|
|
`INSERT OR REPLACE INTO server_interface (id, addresses, listen_port, post_up, pre_down, post_down, updated_at)
|
|
VALUES (1, ?, ?, ?, ?, ?, ?)`,
|
|
string(addrJSON), iface.ListenPort, iface.PostUp, iface.PreDown, iface.PostDown, iface.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Info(" Migrated server interface")
|
|
return nil
|
|
}
|
|
|
|
func migrateServerKeypair(db *SqliteDB, jsonDBPath string) error {
|
|
filePath := filepath.Join(jsonDBPath, "server", "keypair.json")
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
var kp model.ServerKeypair
|
|
if err := readJSONFile(filePath, &kp); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := db.db.Exec(
|
|
`INSERT OR REPLACE INTO server_keypair (id, private_key, public_key, updated_at) VALUES (1, ?, ?, ?)`,
|
|
kp.PrivateKey, kp.PublicKey, kp.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Info(" Migrated server keypair")
|
|
return nil
|
|
}
|
|
|
|
func migrateGlobalSettings(db *SqliteDB, jsonDBPath string) error {
|
|
filePath := filepath.Join(jsonDBPath, "server", "global_settings.json")
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
var gs legacyGlobalSetting
|
|
if err := readJSONFile(filePath, &gs); err != nil {
|
|
return err
|
|
}
|
|
|
|
dnsJSON, _ := json.Marshal(gs.DNSServers)
|
|
_, err := db.db.Exec(
|
|
`INSERT OR REPLACE INTO global_settings (id, endpoint_address, dns_servers, mtu, persistent_keepalive, firewall_mark, "table", config_file_path, updated_at)
|
|
VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
gs.EndpointAddress, string(dnsJSON), gs.MTU, gs.PersistentKeepalive,
|
|
gs.FirewallMark, gs.Table, gs.ConfigFilePath, gs.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Info(" Migrated global settings")
|
|
return nil
|
|
}
|
|
|
|
func migrateHashes(db *SqliteDB, jsonDBPath string) error {
|
|
filePath := filepath.Join(jsonDBPath, "server", "hashes.json")
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
var h model.ClientServerHashes
|
|
if err := readJSONFile(filePath, &h); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := db.db.Exec(
|
|
`INSERT OR REPLACE INTO hashes (id, client, server) VALUES (1, ?, ?)`,
|
|
h.Client, h.Server,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Info(" Migrated hashes")
|
|
return nil
|
|
}
|
|
|
|
func migrateClients(db *SqliteDB, jsonDBPath string) error {
|
|
clientsDir := filepath.Join(jsonDBPath, "clients")
|
|
entries, err := os.ReadDir(clientsDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
count := 0
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
|
|
continue
|
|
}
|
|
|
|
var c model.Client
|
|
if err := readJSONFile(filepath.Join(clientsDir, entry.Name()), &c); err != nil {
|
|
log.Warnf(" Skipping client file %s: %v", entry.Name(), err)
|
|
continue
|
|
}
|
|
|
|
if err := db.SaveClient(c); err != nil {
|
|
return fmt.Errorf("migrate client %s: %w", c.ID, err)
|
|
}
|
|
count++
|
|
}
|
|
log.Infof(" Migrated %d clients", count)
|
|
return nil
|
|
}
|
|
|
|
func migrateWakeOnLanHosts(db *SqliteDB, jsonDBPath string) error {
|
|
wolDir := filepath.Join(jsonDBPath, "wake_on_lan_hosts")
|
|
entries, err := os.ReadDir(wolDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
count := 0
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
|
|
continue
|
|
}
|
|
|
|
var h model.WakeOnLanHost
|
|
if err := readJSONFile(filepath.Join(wolDir, entry.Name()), &h); err != nil {
|
|
log.Warnf(" Skipping WoL host file %s: %v", entry.Name(), err)
|
|
continue
|
|
}
|
|
|
|
if err := db.SaveWakeOnLanHost(h); err != nil {
|
|
return fmt.Errorf("migrate WoL host %s: %w", h.MacAddress, err)
|
|
}
|
|
count++
|
|
}
|
|
log.Infof(" Migrated %d Wake-on-LAN hosts", count)
|
|
return nil
|
|
}
|