eq2go/internal/login/config.go

240 lines
6.1 KiB
Go

package login
import (
"fmt"
"strings"
)
// ServerConfig represents the login server configuration
type ServerConfig struct {
// Network settings
ListenAddr string `json:"listen_addr"`
ListenPort int `json:"listen_port"`
MaxClients int `json:"max_clients"`
// Web interface settings
WebAddr string `json:"web_addr"`
WebPort int `json:"web_port"`
WebCertFile string `json:"web_cert_file"`
WebKeyFile string `json:"web_key_file"`
WebKeyPassword string `json:"web_key_password"`
WebUser string `json:"web_user"`
WebPassword string `json:"web_password"`
// Database settings
DatabaseAddress string `json:"database_address"` // MySQL server address
DatabasePort int `json:"database_port"` // MySQL server port
DatabaseUsername string `json:"database_username"` // MySQL username
DatabasePassword string `json:"database_password"` // MySQL password
DatabaseName string `json:"database_name"` // MySQL database name
// Server settings
ServerName string `json:"server_name"`
LogLevel string `json:"log_level"`
// World servers configuration
WorldServers []WorldServerInfo `json:"world_servers"`
}
// WorldServerInfo represents information about a world server
type WorldServerInfo struct {
ID int `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
Port int `json:"port"`
AdminPort int `json:"admin_port"`
Key string `json:"key"`
Status string `json:"status"` // "up", "down", "locked"
Population int `json:"population"` // Current player count
MaxPlayers int `json:"max_players"` // Maximum allowed players
Description string `json:"description"`
// Server flags
Locked bool `json:"locked"`
Hidden bool `json:"hidden"`
// Connection tracking
LastHeartbeat int64 `json:"last_heartbeat"`
}
// Validate validates the server configuration
func (c *ServerConfig) Validate() error {
if c.ListenAddr == "" {
return fmt.Errorf("listen_addr is required")
}
if c.ListenPort <= 0 || c.ListenPort > 65535 {
return fmt.Errorf("listen_port must be between 1 and 65535")
}
if c.MaxClients <= 0 {
c.MaxClients = 1000 // Default value
}
// Database configuration validation
if c.DatabaseAddress == "" {
c.DatabaseAddress = "localhost" // Default to localhost
}
if c.DatabasePort <= 0 {
c.DatabasePort = 3306 // Default MySQL port
}
if c.DatabaseUsername == "" {
return fmt.Errorf("database_username is required")
}
if c.DatabasePassword == "" {
return fmt.Errorf("database_password is required")
}
if c.DatabaseName == "" {
return fmt.Errorf("database_name is required")
}
if c.ServerName == "" {
c.ServerName = "EQ2Go Login Server"
}
// Validate log level
logLevel := strings.ToLower(c.LogLevel)
switch logLevel {
case "debug", "info", "warn", "error":
c.LogLevel = logLevel
case "":
c.LogLevel = "info" // Default
default:
return fmt.Errorf("invalid log_level: %s (must be debug, info, warn, or error)", c.LogLevel)
}
// Validate web configuration
if c.WebPort > 0 {
if c.WebPort <= 0 || c.WebPort > 65535 {
return fmt.Errorf("web_port must be between 1 and 65535")
}
if c.WebAddr == "" {
c.WebAddr = "0.0.0.0"
}
// If TLS files are specified, both cert and key must be provided
if c.WebCertFile != "" && c.WebKeyFile == "" {
return fmt.Errorf("web_key_file is required when web_cert_file is specified")
}
if c.WebKeyFile != "" && c.WebCertFile == "" {
return fmt.Errorf("web_cert_file is required when web_key_file is specified")
}
}
// Validate world servers
for i, ws := range c.WorldServers {
if err := ws.Validate(); err != nil {
return fmt.Errorf("world_server[%d]: %w", i, err)
}
}
return nil
}
// BuildDatabaseDSN builds a MySQL DSN string from the database configuration
func (c *ServerConfig) BuildDatabaseDSN() string {
// Format: username:password@tcp(address:port)/database?parseTime=true&charset=utf8mb4
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&charset=utf8mb4",
c.DatabaseUsername,
c.DatabasePassword,
c.DatabaseAddress,
c.DatabasePort,
c.DatabaseName)
}
// Validate validates a world server configuration
func (w *WorldServerInfo) Validate() error {
if w.ID <= 0 {
return fmt.Errorf("id must be positive")
}
if w.Name == "" {
return fmt.Errorf("name is required")
}
if w.Address == "" {
return fmt.Errorf("address is required")
}
if w.Port <= 0 || w.Port > 65535 {
return fmt.Errorf("port must be between 1 and 65535")
}
if w.AdminPort <= 0 || w.AdminPort > 65535 {
return fmt.Errorf("admin_port must be between 1 and 65535")
}
if w.AdminPort == w.Port {
return fmt.Errorf("admin_port cannot be the same as port")
}
if w.MaxPlayers <= 0 {
w.MaxPlayers = 1000 // Default value
}
// Normalize status
status := strings.ToLower(w.Status)
switch status {
case "up", "down", "locked":
w.Status = status
case "":
w.Status = "down" // Default
default:
return fmt.Errorf("invalid status: %s (must be up, down, or locked)", w.Status)
}
return nil
}
// IsOnline returns whether the world server is currently online
func (w *WorldServerInfo) IsOnline() bool {
return w.Status == "up"
}
// IsLocked returns whether the world server is locked
func (w *WorldServerInfo) IsLocked() bool {
return w.Locked || w.Status == "locked"
}
// IsHidden returns whether the world server should be hidden from the server list
func (w *WorldServerInfo) IsHidden() bool {
return w.Hidden
}
// GetPopulationPercentage returns the current population as a percentage of max capacity
func (w *WorldServerInfo) GetPopulationPercentage() float64 {
if w.MaxPlayers <= 0 {
return 0
}
return float64(w.Population) / float64(w.MaxPlayers) * 100
}
// GetPopulationLevel returns a string representation of the population level
func (w *WorldServerInfo) GetPopulationLevel() string {
pct := w.GetPopulationPercentage()
switch {
case pct >= 95:
return "FULL"
case pct >= 80:
return "HIGH"
case pct >= 50:
return "MEDIUM"
case pct >= 25:
return "LOW"
default:
return "LIGHT"
}
}
// Clone creates a deep copy of the WorldServerInfo
func (w *WorldServerInfo) Clone() *WorldServerInfo {
clone := *w
return &clone
}