diff --git a/.vscode/launch.json b/.vscode/launch.json index b5d9d441..23382b36 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,6 +29,16 @@ "console": "integratedTerminal", "showLog": false, // Hides some Go Debugger(Delve) log stuff that is not useful for debugging atm "args": ["--NoSteamCMD"] + }, + { + "name": "Debug Go Server noSteamCMD noSvelte overrideAdvertisedIp", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/server.go", + "console": "integratedTerminal", + "showLog": false, // Hides some Go Debugger(Delve) log stuff that is not useful for debugging atm + "args": ["--NoSteamCMD", "--OverrideAdvertisedIp=127.0.0.1"] } ] } \ No newline at end of file diff --git a/server.go b/server.go index 94cb1576..a11365b5 100644 --- a/server.go +++ b/server.go @@ -39,21 +39,17 @@ func main() { logger.ConfigureConsole() loader.ParseFlags() loader.HandleSanityCheckFlag() - loader.SanityCheck(&wg) - wg.Wait() + loader.SanityCheck() logger.Main.Info("Initializing resources...") loader.InitVirtFS(v1uiFS) logger.Install.Info("Starting setup...") loader.ReloadConfig() // Load the config file before starting the setup process loader.HandleFlags() - setup.Install(&wg) - wg.Wait() + setup.Install() logger.Main.Debug("Initializing Backend...") - loader.InitBackend(&wg) - wg.Wait() + loader.InitBackend() logger.Main.Debug("Initializing after start tasks...") - loader.AfterStartComplete(&wg) - wg.Wait() + loader.AfterStartComplete() logger.Main.Debug("Starting webserver...") web.StartWebServer(&wg) logger.Main.Debug("Initializing SSUICLI...") diff --git a/src/advertiser/advertiser.go b/src/advertiser/advertiser.go new file mode 100644 index 00000000..eba2d9e8 --- /dev/null +++ b/src/advertiser/advertiser.go @@ -0,0 +1,110 @@ +package advertiser + +import ( + "bytes" + "encoding/json" + "net/http" + "runtime" + "strconv" + "time" + + "github.com/JacksonTheMaster/StationeersServerUI/v5/src/config" + "github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger" + "github.com/JacksonTheMaster/StationeersServerUI/v5/src/managers/detectionmgr" + "github.com/JacksonTheMaster/StationeersServerUI/v5/src/managers/gamemgr" +) + +const StationeersAdvertisementEndpoint = "http://40.82.200.175:8081/Ping" + +type ServerAdMessage struct { + SessionId int + Name string + Password bool + Version string + Address string + Port string + Players int + MaxPlayers int + Type int +} + +type ServerAdResponse struct { + SessionId int + Status string +} + +func StartAdvertiser() { + if config.GetServerVisible() { + logger.Advertiser.Warn("Server advertisement is enabled. Disable it in the config and restart SSUI to use manual advertisement. Skipping for now...") + return + } + go func() { + sessionId := -1 + for { + // Only advertise if we are running + if gamemgr.InternalIsServerRunning() { + // Get max players + maxplayers, err := strconv.Atoi(config.GetServerMaxPlayers()) + if err != nil { + logger.Advertiser.Errorf("ServerAdvertiser failed to convert max players number to int: %s", config.GetServerMaxPlayers()) + return + } + // Get connected players + detector := detectionmgr.GetDetector() + players := len(detectionmgr.GetPlayers(detector)) + // Get platform + platform := 0 + switch runtime.GOOS { + case "windows": + platform = 1 + case "linux": + platform = 2 + } + adMessage := ServerAdMessage{ + SessionId: sessionId, + Name: config.GetServerName(), + Password: config.GetServerPassword() != "", + Version: config.GetExtractedGameVersion(), + Address: config.GetOverrideAdvertisedIp(), + Port: config.GetGamePort(), + Players: players, + MaxPlayers: maxplayers, + Type: platform, + } + body, err := json.Marshal(adMessage) + if err != nil { + logger.Advertiser.Errorf("ServerAdvertiser failed to Serialize to JSON from native Go struct type: %v", err) + return + } + // Send advertisement + resp, err := http.Post(StationeersAdvertisementEndpoint, "application/json", bytes.NewBuffer(body)) + // Check for errors + if err != nil { + logger.Advertiser.Errorf("ServerAdvertiser failed to send request: %v", err) + return + } + defer resp.Body.Close() + // Check the status + if resp.StatusCode != 200 { + logger.Advertiser.Warnf("ServerAdvertiser received non-200 status: %d", resp.StatusCode) + } + // Read the response and update our sessionId if needed + adResponse := ServerAdResponse{} + err = json.NewDecoder(resp.Body).Decode(&adResponse) + if err != nil { + logger.Advertiser.Errorf("Failed to decode response body: %v", err) + return + } + if adResponse.Status != "Success" { + logger.Advertiser.Warnf("ServerAdvertiser received unexpected status: %s", adResponse.Status) + } + sessionId = adResponse.SessionId + } else { + // Reset sessionid for the next run + sessionId = -1 + } + // Sleep for 30 seconds to follow the standard advertisement timer + time.Sleep(30 * time.Second) + } + }() +} diff --git a/src/config/config.go b/src/config/config.go index 26fcf72f..d1a6f52b 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -51,10 +51,11 @@ type JsonConfig struct { StartLocation string `json:"StartLocation"` // Logging and debug settings - Debug *bool `json:"Debug"` - CreateSSUILogFile *bool `json:"CreateSSUILogFile"` - LogLevel int `json:"LogLevel"` - SubsystemFilters []string `json:"subsystemFilters"` + Debug *bool `json:"Debug"` + CreateSSUILogFile *bool `json:"CreateSSUILogFile"` + LogLevel int `json:"LogLevel"` + SubsystemFilters []string `json:"subsystemFilters"` + OverrideAdvertisedIp string `json:"OverrideAdvertisedIp"` // Authentication Settings Users map[string]string `json:"users"` // Map of username to hashed password @@ -296,6 +297,8 @@ func applyConfig(cfg *JsonConfig) { // use Safebackups folder either way. ConfiguredSafeBackupDir = filepath.Join("./saves/", SaveName, "Safebackups") + OverrideAdvertisedIp = getString(cfg.OverrideAdvertisedIp, "OVERRIDE_ADVERTISED_IP", "") + safeSaveConfig() } @@ -365,6 +368,7 @@ func safeSaveConfig() error { AutoStartServerOnStartup: &AutoStartServerOnStartup, SSUIIdentifier: SSUIIdentifier, SSUIWebPort: SSUIWebPort, + OverrideAdvertisedIp: OverrideAdvertisedIp, } file, err := os.Create(ConfigPath) diff --git a/src/config/getters.go b/src/config/getters.go index 27067b83..d5b78aaa 100644 --- a/src/config/getters.go +++ b/src/config/getters.go @@ -513,3 +513,9 @@ func GetIsDockerContainer() bool { defer ConfigMu.RUnlock() return IsDockerContainer } + +func GetOverrideAdvertisedIp() string { + ConfigMu.RLock() + defer ConfigMu.RUnlock() + return OverrideAdvertisedIp +} diff --git a/src/config/setters.go b/src/config/setters.go index 58627abb..438482e7 100644 --- a/src/config/setters.go +++ b/src/config/setters.go @@ -694,3 +694,11 @@ func SetAllowAutoGameServerUpdates(value bool) error { AllowAutoGameServerUpdates = value return safeSaveConfig() } + +func SetOverrideAdvertisedIp(value string) error { + ConfigMu.Lock() + defer ConfigMu.Unlock() + + OverrideAdvertisedIp = value + return safeSaveConfig() +} diff --git a/src/config/vars.go b/src/config/vars.go index a2f3a507..a3aacc24 100644 --- a/src/config/vars.go +++ b/src/config/vars.go @@ -61,6 +61,7 @@ var ( LanguageSetting string AutoStartServerOnStartup bool SSUIIdentifier string + OverrideAdvertisedIp string ) // Runtime only variables diff --git a/src/core/loader/afterstart.go b/src/core/loader/afterstart.go index 91c3824b..1e72fee7 100644 --- a/src/core/loader/afterstart.go +++ b/src/core/loader/afterstart.go @@ -1,8 +1,6 @@ package loader import ( - "sync" - "github.com/JacksonTheMaster/StationeersServerUI/v5/src/config" "github.com/JacksonTheMaster/StationeersServerUI/v5/src/discordrpc" "github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger" @@ -10,9 +8,7 @@ import ( "github.com/JacksonTheMaster/StationeersServerUI/v5/src/setup" ) -func AfterStartComplete(wg *sync.WaitGroup) { - wg.Add(1) - defer wg.Done() +func AfterStartComplete() { config.SetSaveConfig() // Save config after startup through setters err := setup.CleanUpOldUIModFolderFiles() if err != nil { diff --git a/src/core/loader/cmdargs.go b/src/core/loader/cmdargs.go index 1b04418c..d264fa27 100644 --- a/src/core/loader/cmdargs.go +++ b/src/core/loader/cmdargs.go @@ -21,6 +21,7 @@ var recoveryPasswordFlag string var devModeFlag bool var skipSteamCMDFlag bool var sanityCheckFlag bool +var overrideAdvertisedIpFlag string // ParseFlags parses command-line arguments ONCE at startup (called from func main) func ParseFlags() { @@ -39,6 +40,7 @@ func ParseFlags() { flag.BoolVar(&createSSUILogFileFlag, "lf", false, "(Alias) Create log files for SSUI") flag.BoolVar(&skipSteamCMDFlag, "NoSteamCMD", false, "Skips SteamCMD installation") flag.BoolVar(&sanityCheckFlag, "NoSanityCheck", false, "Skips the sanity check. Not recommended.") + flag.StringVar(&overrideAdvertisedIpFlag, "OverrideAdvertisedIp", "", "Override the advertised server IP (to allow server advertisement if you are behind a reverse proxy)") // Parse command-line flags flag.Parse() @@ -100,6 +102,12 @@ func HandleFlags() { logger.Main.Info(fmt.Sprintf("Overriding IsDebugMode from command line: Before=%t, Now=true", oldDebug)) } + if overrideAdvertisedIpFlag != "" { + oldOverrideAdvertisedIp := config.GetOverrideAdvertisedIp() + config.SetOverrideAdvertisedIp(overrideAdvertisedIpFlag) + logger.Main.Info(fmt.Sprintf("Overriding Advertised Server IP from command line: Before=%s, Now=%s", oldOverrideAdvertisedIp, overrideAdvertisedIpFlag)) + } + if createSSUILogFileFlag { oldCreateSSUILogFile := config.GetCreateSSUILogFile() config.SetCreateSSUILogFile(true) diff --git a/src/core/loader/loader.go b/src/core/loader/loader.go index 6c224cc4..fa6e1d3c 100644 --- a/src/core/loader/loader.go +++ b/src/core/loader/loader.go @@ -4,9 +4,9 @@ package loader import ( "embed" "os" - "sync" "time" + "github.com/JacksonTheMaster/StationeersServerUI/v5/src/advertiser" "github.com/JacksonTheMaster/StationeersServerUI/v5/src/config" "github.com/JacksonTheMaster/StationeersServerUI/v5/src/discordbot" "github.com/JacksonTheMaster/StationeersServerUI/v5/src/localization" @@ -19,9 +19,7 @@ import ( ) // only call this once at startup -func InitBackend(wg *sync.WaitGroup) { - wg.Add(1) - defer wg.Done() +func InitBackend() { ReloadConfig() ReloadSSCM() ReloadBackupManager() @@ -29,6 +27,10 @@ func InitBackend(wg *sync.WaitGroup) { ReloadAppInfoPoller() ReloadDiscordBot() InitDetector() + if config.GetOverrideAdvertisedIp() != "" { + logger.Advertiser.Info("Starting server advertiser...") + advertiser.StartAdvertiser() + } } // use this to reload backend at runtime @@ -100,9 +102,7 @@ func InitVirtFS(v1uiFS embed.FS) { config.SetV1UIFS(v1uiFS) } -func SanityCheck(wg *sync.WaitGroup) { - wg.Add(1) - defer wg.Done() +func SanityCheck() { err := runSanityCheck() if err != nil { logger.Main.Error("Sanity check failed, exiting in 10 secconds: " + err.Error()) diff --git a/src/logger/logger.go b/src/logger/logger.go index 958d3971..a505e154 100644 --- a/src/logger/logger.go +++ b/src/logger/logger.go @@ -24,6 +24,7 @@ var ( SSE = &Logger{suffix: SYS_SSE} Security = &Logger{suffix: SYS_SECURITY} Localization = &Logger{suffix: SYS_LOCALIZATION} + Advertiser = &Logger{suffix: SYS_ADVERTISER} ) // Severity Levels @@ -48,6 +49,7 @@ const ( SYS_SSE = "SSE" SYS_SECURITY = "SECURITY" SYS_LOCALIZATION = "LOCALIZATION" + SYS_ADVERTISER = "ADVERTISER" ) const ( @@ -73,6 +75,7 @@ var subsystemColors = map[string]string{ SYS_SSE: colorCyan, // Matches WEB, streaming vibe SYS_SECURITY: colorRed, // Screams "pay attention" SYS_LOCALIZATION: colorCyan, // Matches WEB, localization-related + SYS_ADVERTISER: colorYellow, // Matches Config, advanced feature } // Global channels and mutex for all loggers diff --git a/src/setup/install.go b/src/setup/install.go index 99e1d82d..052338ad 100644 --- a/src/setup/install.go +++ b/src/setup/install.go @@ -23,10 +23,7 @@ import ( var downloadBranch string // Holds the branch to download from // Install performs the entire installation process and ensures the server waits for it to complete -func Install(wg *sync.WaitGroup) { - wg.Add(1) - defer wg.Done() // Signal that installation is complete - +func Install() { // Step 0: Check for updates if err := update.UpdateExecutable(); err != nil { logger.Install.Error("❌Update check went sideways: " + err.Error())