mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 15:43:49 +01:00
Only the database password is truly mandatory to get started, but config.example.json was 267 lines with 90+ options. Newcomers faced a wall of settings with no indication of what matters. Add registerDefaults() with all sane defaults via Viper so a minimal config (just DB credentials) produces a fully working server. Gameplay multipliers default to 1.0 instead of Go's zero value 0.0, which previously zeroed out all quest rewards for minimal configs. Uses dot-notation defaults for GameplayOptions/DebugOptions so users can override individual fields without losing other defaults. - Shrink config.example.json from 267 to 10 lines - Rename full original to config.reference.json as documentation - Simplify wizard buildDefaultConfig() from ~220 to ~12 lines - Fix latent bug: SaveDumps default used wrong key DevModeOptions - Add tests: minimal config, backward compat, single-field override - Update release workflow, README, CONTRIBUTING, docker/README
160 lines
5.0 KiB
Go
160 lines
5.0 KiB
Go
package setup
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
)
|
|
|
|
// clientModes returns all supported client version strings.
|
|
func clientModes() []string {
|
|
return []string{
|
|
"S1.0", "S1.5", "S2.0", "S2.5", "S3.0", "S3.5", "S4.0", "S5.0", "S5.5", "S6.0", "S7.0",
|
|
"S8.0", "S8.5", "S9.0", "S10", "FW.1", "FW.2", "FW.3", "FW.4", "FW.5", "G1", "G2", "G3",
|
|
"G3.1", "G3.2", "GG", "G5", "G5.1", "G5.2", "G6", "G6.1", "G7", "G8", "G8.1", "G9", "G9.1",
|
|
"G10", "G10.1", "Z1", "Z2", "ZZ",
|
|
}
|
|
}
|
|
|
|
// FinishRequest holds the user's configuration choices from the wizard.
|
|
type FinishRequest struct {
|
|
DBHost string `json:"dbHost"`
|
|
DBPort int `json:"dbPort"`
|
|
DBUser string `json:"dbUser"`
|
|
DBPassword string `json:"dbPassword"`
|
|
DBName string `json:"dbName"`
|
|
Host string `json:"host"`
|
|
ClientMode string `json:"clientMode"`
|
|
AutoCreateAccount bool `json:"autoCreateAccount"`
|
|
}
|
|
|
|
// buildDefaultConfig produces a minimal config map with only user-provided values.
|
|
// All other settings are filled by Viper's registered defaults at load time.
|
|
func buildDefaultConfig(req FinishRequest) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"Host": req.Host,
|
|
"ClientMode": req.ClientMode,
|
|
"AutoCreateAccount": req.AutoCreateAccount,
|
|
"Database": map[string]interface{}{
|
|
"Host": req.DBHost,
|
|
"Port": req.DBPort,
|
|
"User": req.DBUser,
|
|
"Password": req.DBPassword,
|
|
"Database": req.DBName,
|
|
},
|
|
}
|
|
}
|
|
|
|
// writeConfig writes the config map to config.json with pretty formatting.
|
|
func writeConfig(config map[string]interface{}) error {
|
|
data, err := json.MarshalIndent(config, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("marshalling config: %w", err)
|
|
}
|
|
if err := os.WriteFile("config.json", data, 0600); err != nil {
|
|
return fmt.Errorf("writing config.json: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// detectOutboundIP returns the preferred outbound IPv4 address.
|
|
func detectOutboundIP() (string, error) {
|
|
conn, err := net.Dial("udp4", "8.8.8.8:80")
|
|
if err != nil {
|
|
return "", fmt.Errorf("detecting outbound IP: %w", err)
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
|
return localAddr.IP.To4().String(), nil
|
|
}
|
|
|
|
// testDBConnection tests connectivity to the PostgreSQL server and checks
|
|
// whether the target database and its tables exist.
|
|
func testDBConnection(host string, port int, user, password, dbName string) (*DBStatus, error) {
|
|
status := &DBStatus{}
|
|
|
|
// Connect to the 'postgres' maintenance DB to check if target DB exists.
|
|
adminConn := fmt.Sprintf(
|
|
"host='%s' port='%d' user='%s' password='%s' dbname='postgres' sslmode=disable",
|
|
host, port, user, password,
|
|
)
|
|
adminDB, err := sql.Open("postgres", adminConn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("connecting to PostgreSQL: %w", err)
|
|
}
|
|
defer func() { _ = adminDB.Close() }()
|
|
|
|
if err := adminDB.Ping(); err != nil {
|
|
return nil, fmt.Errorf("cannot reach PostgreSQL: %w", err)
|
|
}
|
|
status.ServerReachable = true
|
|
|
|
var exists bool
|
|
err = adminDB.QueryRow("SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)", dbName).Scan(&exists)
|
|
if err != nil {
|
|
return status, fmt.Errorf("checking database existence: %w", err)
|
|
}
|
|
status.DatabaseExists = exists
|
|
|
|
if !exists {
|
|
return status, nil
|
|
}
|
|
|
|
// Connect to the target DB to check for tables.
|
|
targetConn := fmt.Sprintf(
|
|
"host='%s' port='%d' user='%s' password='%s' dbname='%s' sslmode=disable",
|
|
host, port, user, password, dbName,
|
|
)
|
|
targetDB, err := sql.Open("postgres", targetConn)
|
|
if err != nil {
|
|
return status, nil
|
|
}
|
|
defer func() { _ = targetDB.Close() }()
|
|
|
|
var tableCount int
|
|
err = targetDB.QueryRow("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public'").Scan(&tableCount)
|
|
if err != nil {
|
|
return status, nil
|
|
}
|
|
status.TablesExist = tableCount > 0
|
|
status.TableCount = tableCount
|
|
|
|
return status, nil
|
|
}
|
|
|
|
// DBStatus holds the result of a database connectivity check.
|
|
type DBStatus struct {
|
|
ServerReachable bool `json:"serverReachable"`
|
|
DatabaseExists bool `json:"databaseExists"`
|
|
TablesExist bool `json:"tablesExist"`
|
|
TableCount int `json:"tableCount"`
|
|
}
|
|
|
|
// createDatabase creates the target database by connecting to the 'postgres' maintenance DB.
|
|
func createDatabase(host string, port int, user, password, dbName string) error {
|
|
adminConn := fmt.Sprintf(
|
|
"host='%s' port='%d' user='%s' password='%s' dbname='postgres' sslmode=disable",
|
|
host, port, user, password,
|
|
)
|
|
db, err := sql.Open("postgres", adminConn)
|
|
if err != nil {
|
|
return fmt.Errorf("connecting to PostgreSQL: %w", err)
|
|
}
|
|
defer func() { _ = db.Close() }()
|
|
|
|
// Database names can't be parameterized; validate it's alphanumeric + underscores.
|
|
for _, c := range dbName {
|
|
if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_' {
|
|
return fmt.Errorf("invalid database name %q: only alphanumeric characters and underscores allowed", dbName)
|
|
}
|
|
}
|
|
|
|
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", dbName))
|
|
if err != nil {
|
|
return fmt.Errorf("creating database: %w", err)
|
|
}
|
|
return nil
|
|
}
|