diff --git a/Dockerfile b/.docker/Dockerfile
similarity index 77%
rename from Dockerfile
rename to .docker/Dockerfile
index e4406cab..9bd45c49 100644
--- a/Dockerfile
+++ b/.docker/Dockerfile
@@ -29,7 +29,7 @@ COPY . ./
COPY --from=frontend-builder /src/UIMod /src/UIMod
# Build the server (embed will include UIMod/* at build time)
-RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/StationeersServerControl ./server.go
+RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/StationeersServerUI ./server.go
############
# Runtime
@@ -47,20 +47,18 @@ RUN dpkg --add-architecture i386 \
ca-certificates \
locales \
lib32gcc-s1 \
- file \
+ file \
&& rm -rf /var/lib/apt/lists/*
-RUN useradd -m -d /app -s /bin/sh app
-
# Copy compiled binary and license
-COPY --from=go-builder /out/StationeersServerControl /usr/local/bin/StationeersServerControl
-COPY ./LICENSE /app/LICENSE
-RUN chmod +x /usr/local/bin/StationeersServerControl
-
-RUN chown -R app:app /app
+RUN mkdir -p /opt/SSUIBuildFiles
+COPY --from=go-builder /out/StationeersServerUI /opt/SSUIBuildFiles/StationeersServerUI
+COPY ./LICENSE /opt/SSUIBuildFiles/LICENSE
+COPY ./.docker/entrypoint.sh /entrypoint.sh
+RUN chmod +x /opt/SSUIBuildFiles/StationeersServerUI
+RUN chmod +x /entrypoint.sh
-USER app
EXPOSE 8443/tcp 27016/udp 27015/udp
-ENTRYPOINT ["/usr/local/bin/StationeersServerControl"]
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/compose.yml b/.docker/compose.yml
similarity index 67%
rename from compose.yml
rename to .docker/compose.yml
index aa382616..38e693bd 100644
--- a/compose.yml
+++ b/.docker/compose.yml
@@ -1,8 +1,12 @@
services:
stationeers-server:
container_name: stationeers-server
- build : .
+ build:
+ context: ..
+ dockerfile: ./.docker/Dockerfile
image: stationeers-server-ui:latest
+ stdin_open: true
+ tty: true
deploy:
resources:
limits:
@@ -18,14 +22,10 @@ services:
- "27015:27015/udp"
- "27015:27015/tcp"
volumes:
- # Persist app data between container restarts
- app-data:/app
- # Mount saves and config for easy access/editing outside the container
- ./saves:/app/saves:rw
- - ./UIMod/config:/app/UIMod/config:rw
- - ./UIMod/tls:/app/UIMod/tls:rw
+ - ./UIMod:/app/UIMod:rw
restart: unless-stopped
- command: []
volumes:
app-data:
\ No newline at end of file
diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh
new file mode 100644
index 00000000..0069b069
--- /dev/null
+++ b/.docker/entrypoint.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+cp /opt/SSUIBuildFiles/StationeersServerUI /app/StationeersServerUI
+cp /opt/SSUIBuildFiles/LICENSE /app/LICENSE
+chmod +x /app/StationeersServerUI
+exec /app/StationeersServerUI "$@"
\ No newline at end of file
diff --git a/.github/workflows/ghcr-build-nightly.yaml b/.github/workflows/ghcr-build-nightly.yaml
index 302364ad..7fcfe13e 100644
--- a/.github/workflows/ghcr-build-nightly.yaml
+++ b/.github/workflows/ghcr-build-nightly.yaml
@@ -51,6 +51,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
+ file: ./.docker/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
diff --git a/.github/workflows/ghcr-build-release.yaml b/.github/workflows/ghcr-build-release.yaml
index 43e3df7e..560e0c05 100644
--- a/.github/workflows/ghcr-build-release.yaml
+++ b/.github/workflows/ghcr-build-release.yaml
@@ -50,6 +50,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
+ file: ./.docker/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
diff --git a/.gitignore b/.gitignore
index a2349175..ecfce688 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,7 @@ UIMod/tls/cert.pem
UIMod/tls/key.pem
steamapps/**
steamcmd/**
+Steam/**
rocketstation_BurstDebugInformation_DoNotShip/**
StationeersServerControlv*
UnityPlayer.so
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5e1d543e..b5d9d441 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -19,6 +19,16 @@
"program": "${workspaceFolder}/server.go",
"console": "integratedTerminal",
"showLog": false, // Hides some Go Debugger(Delve) log stuff that is not useful for debugging atm
+ },
+ {
+ "name": "Debug Go Server noSteamCMD noSvelte",
+ "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"]
}
]
}
\ No newline at end of file
diff --git a/UIMod/onboard_bundled/twoboxform/twoboxform.js b/UIMod/onboard_bundled/twoboxform/twoboxform.js
index 3423361b..57f2feb1 100644
--- a/UIMod/onboard_bundled/twoboxform/twoboxform.js
+++ b/UIMod/onboard_bundled/twoboxform/twoboxform.js
@@ -138,6 +138,12 @@ document.addEventListener('DOMContentLoaded', () => {
} else if (configField === "SaveInfo") {
const primaryValue = document.getElementById('primary-field').value.trim();
+ // If the world name contains a space, it's invalid
+ if (primaryValue.includes(' ')) {
+ showNotification('The world name cannot contain spaces!', 'error');
+ hidePreloader();
+ return; // Prevent submission
+ }
const secondaryValue = document.getElementById('secondary-field').value.trim();
if (secondaryValue === '' || secondaryValue === document.getElementById('secondary-field').placeholder) {
showNotification('Please select a world type!', 'error');
diff --git a/UIMod/onboard_bundled/ui/index.html b/UIMod/onboard_bundled/ui/index.html
index 3ffbd622..4e70928e 100644
--- a/UIMod/onboard_bundled/ui/index.html
+++ b/UIMod/onboard_bundled/ui/index.html
@@ -107,7 +107,7 @@
+
ℹ️
Transitioning to SteamServerUI (click to expand)
diff --git a/server.go b/server.go
index 7c5c6677..94cb1576 100644
--- a/server.go
+++ b/server.go
@@ -37,13 +37,17 @@ var v1uiFS embed.FS
func main() {
var wg sync.WaitGroup
logger.ConfigureConsole()
+ loader.ParseFlags()
+ loader.HandleSanityCheckFlag()
+ loader.SanityCheck(&wg)
+ wg.Wait()
+ 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.LoadCmdArgs()
+ loader.HandleFlags()
setup.Install(&wg)
wg.Wait()
- logger.Main.Debug("Initializing resources...")
- loader.InitVirtFS(v1uiFS)
logger.Main.Debug("Initializing Backend...")
loader.InitBackend(&wg)
wg.Wait()
diff --git a/src/cli/runtimecommands.go b/src/cli/runtimecommands.go
index 9207d780..1e282b82 100644
--- a/src/cli/runtimecommands.go
+++ b/src/cli/runtimecommands.go
@@ -273,6 +273,8 @@ func supportPackage() {
delete(configMap, "users")
delete(configMap, "JwtKey")
delete(configMap, "AdminPassword")
+ delete(configMap, "ServerAuthSecret")
+ delete(configMap, "ServerPassword")
sanitizedConfig, err := json.MarshalIndent(configMap, "", " ")
if err != nil {
logger.Core.Error("Failed to marshal sanitized config into support package")
diff --git a/src/config/config.go b/src/config/config.go
index ab63a20f..f5c35304 100644
--- a/src/config/config.go
+++ b/src/config/config.go
@@ -11,7 +11,7 @@ import (
var (
// All configuration variables can be found in vars.go
- Version = "5.6.5"
+ Version = "5.6.6"
Branch = "release"
)
diff --git a/src/config/getters.go b/src/config/getters.go
index f07be86a..e1683922 100644
--- a/src/config/getters.go
+++ b/src/config/getters.go
@@ -487,3 +487,27 @@ func GetAllowAutoGameServerUpdates() bool {
defer ConfigMu.RUnlock()
return AllowAutoGameServerUpdates
}
+
+func GetExtractedGameVersion() string {
+ ConfigMu.RLock()
+ defer ConfigMu.RUnlock()
+ return ExtractedGameVersion
+}
+
+func GetSkipSteamCMD() bool {
+ ConfigMu.RLock()
+ defer ConfigMu.RUnlock()
+ return SkipSteamCMD
+}
+
+func GetNoSanityCheck() bool {
+ ConfigMu.RLock()
+ defer ConfigMu.RUnlock()
+ return NoSanityCheck
+}
+
+func GetIsDockerContainer() bool {
+ ConfigMu.RLock()
+ defer ConfigMu.RUnlock()
+ return IsDockerContainer
+}
diff --git a/src/config/setters.go b/src/config/setters.go
index 10bf86fd..9928d5cc 100644
--- a/src/config/setters.go
+++ b/src/config/setters.go
@@ -38,6 +38,38 @@ func SetCurrentBranchBuildID(value string) error {
return nil
}
+func SetExtractedGameVersion(value string) error {
+ ConfigMu.Lock()
+ defer ConfigMu.Unlock()
+
+ ExtractedGameVersion = value
+ return nil
+}
+
+func SetSkipSteamCMD(value bool) error {
+ ConfigMu.Lock()
+ defer ConfigMu.Unlock()
+
+ SkipSteamCMD = value
+ return nil
+}
+
+func SetIsDockerContainer(value bool) error {
+ ConfigMu.Lock()
+ defer ConfigMu.Unlock()
+
+ IsDockerContainer = value
+ return nil
+}
+
+func SetNoSanityCheck(value bool) error {
+ ConfigMu.Lock()
+ defer ConfigMu.Unlock()
+
+ NoSanityCheck = value
+ return nil
+}
+
// ALL SETTERS BELOW THIS LINE ARE UNUSED AT THE MOMENT
// ALL SETTERS BELOW THIS LINE ARE UNUSED AT THE MOMENT
// ALL SETTERS BELOW THIS LINE ARE UNUSED AT THE MOMENT
diff --git a/src/config/vars.go b/src/config/vars.go
index 3c37b51d..edcb3c1d 100644
--- a/src/config/vars.go
+++ b/src/config/vars.go
@@ -61,7 +61,16 @@ var (
LanguageSetting string
AutoStartServerOnStartup bool
SSUIIdentifier string
- CurrentBranchBuildID string // ONLY RUNTIME
+)
+
+// Runtime only variables
+
+var (
+ CurrentBranchBuildID string // ONLY RUNTIME
+ ExtractedGameVersion string // ONLY RUNTIME
+ SkipSteamCMD bool // ONLY RUNTIME
+ IsDockerContainer bool // ONLY RUNTIME
+ NoSanityCheck bool // ONLY RUNTIME
)
// Discord integration
diff --git a/src/core/loader/cmdargs.go b/src/core/loader/cmdargs.go
index 787d63e9..1b04418c 100644
--- a/src/core/loader/cmdargs.go
+++ b/src/core/loader/cmdargs.go
@@ -4,42 +4,51 @@ import (
"flag"
"fmt"
"strings"
+ "time"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/core/security"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger"
)
-// LoadCmdArgs parses command-line arguments ONCE at startup (called from func main) and applies them using the config setters.
-// Because this is using the config rather than adding features to it, it is a part of the loader package.
-func LoadCmdArgs() {
- // Define flags matching the config variable names
- var backendEndpointPort string
- var gameBranch string
- var logLevel int
- var isDebugMode bool
- var createSSUILogFile bool
- var recoveryPassword string
- var devMode bool
+// Define flags matching the config variable names
+var backendEndpointPortFlag string
+var gameBranchFlag string
+var logLevelFlag int
+var isDebugModeFlag bool
+var createSSUILogFileFlag bool
+var recoveryPasswordFlag string
+var devModeFlag bool
+var skipSteamCMDFlag bool
+var sanityCheckFlag bool
- flag.StringVar(&backendEndpointPort, "BackendEndpointPort", "", "Override the backend endpoint port (e.g., 8080)")
- flag.StringVar(&backendEndpointPort, "p", "", "(Alias) Override the backend endpoint port (e.g., 8080)")
- flag.StringVar(&gameBranch, "GameBranch", "", "Override the game branch (e.g., beta)")
- flag.StringVar(&gameBranch, "b", "", "(Alias) Override the game branch (e.g., beta)")
- flag.StringVar(&recoveryPassword, "RecoveryPassword", "", "Adds a 'recovery' user (expects password as argument)")
- flag.StringVar(&recoveryPassword, "r", "", "(Alias) Adds a 'recovery' user (expects password as argument)")
- flag.BoolVar(&devMode, "dev", false, "Enable dev mode: Auth, and enables cli-console. For development only.")
- flag.IntVar(&logLevel, "LogLevel", 0, "Override the log level (e.g., 10)")
- flag.IntVar(&logLevel, "ll", 0, "(Alias) Override the log level (e.g., 10)")
- flag.BoolVar(&isDebugMode, "IsDebugMode", false, "Enable debug mode")
- flag.BoolVar(&isDebugMode, "debug", false, "(Alias) Enable debug mode")
- flag.BoolVar(&createSSUILogFile, "CreateSSUILogFile", false, "Create a log file for SSUI")
- flag.BoolVar(&createSSUILogFile, "lf", false, "(Alias) Create a log file for SSUI")
+// ParseFlags parses command-line arguments ONCE at startup (called from func main)
+func ParseFlags() {
+ flag.StringVar(&backendEndpointPortFlag, "BackendEndpointPort", "", "Override the backend endpoint port (e.g., 8080)")
+ flag.StringVar(&backendEndpointPortFlag, "p", "", "(Alias) Override the backend endpoint port (e.g., 8080)")
+ flag.StringVar(&gameBranchFlag, "GameBranch", "", "Override the game branch (e.g., beta)")
+ flag.StringVar(&gameBranchFlag, "b", "", "(Alias) Override the game branch (e.g., beta)")
+ flag.StringVar(&recoveryPasswordFlag, "RecoveryPassword", "", "Adds a 'recovery' user (expects password as argument)")
+ flag.StringVar(&recoveryPasswordFlag, "r", "", "(Alias) Adds a 'recovery' user (expects password as argument)")
+ flag.BoolVar(&devModeFlag, "dev", false, "Enable dev mode: Auth, and enables cli-console. For development only.")
+ flag.IntVar(&logLevelFlag, "LogLevel", 0, "Override the log level (e.g., 10)")
+ flag.IntVar(&logLevelFlag, "ll", 0, "(Alias) Override the log level (e.g., 10)")
+ flag.BoolVar(&isDebugModeFlag, "IsDebugMode", false, "Enable debug mode")
+ flag.BoolVar(&isDebugModeFlag, "debug", false, "(Alias) Enable debug mode")
+ flag.BoolVar(&createSSUILogFileFlag, "LogToFiles", false, "Create log files for SSUI")
+ 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.")
// Parse command-line flags
flag.Parse()
+}
- if devMode {
+// HandleCmdArgs handles command-line arguments ONCE at startup (called from func main) and applies them using the config setters.
+// Because this is using the config rather than adding features to it, it is a part of the loader package.
+func HandleFlags() {
+
+ if devModeFlag {
config.SetAuthEnabled(true)
config.SetIsFirstTimeSetup(false)
config.SetUsers(map[string]string{"admin": "$2a$10$7QQhPkNAfT.MXhJhnnodXOyn3KKE/1eu7nYb0y2O1UBoAWc0Y/fda"}) // admin:admin
@@ -47,49 +56,63 @@ func LoadCmdArgs() {
logger.Main.Info("Dev mode enabled: Auth enabled, admin user set to admin:admin:superadmin, console enabled")
}
- if backendEndpointPort != "" && backendEndpointPort != "8443" {
+ if skipSteamCMDFlag {
+ config.SetSkipSteamCMD(true)
+ }
+
+ if backendEndpointPortFlag != "" && backendEndpointPortFlag != "8443" {
oldPort := config.GetSSUIWebPort()
- config.SetSSUIWebPort(backendEndpointPort)
- logger.Main.Info(fmt.Sprintf("Overriding SetSSUIWebPort from command line: Before=%s, Now=%s", oldPort, backendEndpointPort))
+ config.SetSSUIWebPort(backendEndpointPortFlag)
+ logger.Main.Info(fmt.Sprintf("Overriding SetSSUIWebPort from command line: Before=%s, Now=%s", oldPort, backendEndpointPortFlag))
}
- if gameBranch != "" {
+ if gameBranchFlag != "" {
oldBranch := config.GetGameBranch()
- config.SetGameBranch(gameBranch)
- logger.Main.Info(fmt.Sprintf("Overriding GameBranch from command line: Before=%s, Now=%s", oldBranch, gameBranch))
+ config.SetGameBranch(gameBranchFlag)
+ logger.Main.Info(fmt.Sprintf("Overriding GameBranch from command line: Before=%s, Now=%s", oldBranch, gameBranchFlag))
}
- if recoveryPassword != "" {
- recoveryPassword = strings.TrimSpace(recoveryPassword)
- if recoveryPassword == "" {
+ if recoveryPasswordFlag != "" {
+ recoveryPasswordFlag = strings.TrimSpace(recoveryPasswordFlag)
+ if recoveryPasswordFlag == "" {
logger.Main.Error("Recovery flag provided but password is empty. Skipping recovery user creation.")
} else {
- hashedPassword, err := security.HashPassword(recoveryPassword)
+ hashedPassword, err := security.HashPassword(recoveryPasswordFlag)
if err != nil {
logger.Main.Error(fmt.Sprintf("Failed to hash recovery password: %v", err))
return
}
config.SetUsers(map[string]string{"recovery": hashedPassword})
- logger.Main.Warn(fmt.Sprintf("Recovery user added with access level superadmin. Login with username 'recovery' and password '%s'", recoveryPassword))
+ logger.Main.Warn(fmt.Sprintf("Recovery user added with access level superadmin. Login with username 'recovery' and password '%s'", recoveryPasswordFlag))
}
}
- if logLevel != 0 {
+ if logLevelFlag != 0 {
oldLevel := config.GetLogLevel()
- config.SetLogLevel(logLevel)
- logger.Main.Info(fmt.Sprintf("Overriding LogLevel from command line: Before=%d, Now=%d", oldLevel, logLevel))
+ config.SetLogLevel(logLevelFlag)
+ logger.Main.Info(fmt.Sprintf("Overriding LogLevel from command line: Before=%d, Now=%d", oldLevel, logLevelFlag))
}
- if isDebugMode {
+ if isDebugModeFlag {
oldDebug := config.GetIsDebugMode()
config.SetIsDebugMode(true)
config.SetLogLevel(10)
logger.Main.Info(fmt.Sprintf("Overriding IsDebugMode from command line: Before=%t, Now=true", oldDebug))
}
- if createSSUILogFile {
+ if createSSUILogFileFlag {
oldCreateSSUILogFile := config.GetCreateSSUILogFile()
config.SetCreateSSUILogFile(true)
logger.Main.Info(fmt.Sprintf("Overriding CreateSSUILogFile from command line: Before=%t, Now=true", oldCreateSSUILogFile))
}
}
+
+// HandleSanityCheckFlag has special handling to allow usage directly at startup before other systems are initialized.
+func HandleSanityCheckFlag() {
+ if sanityCheckFlag {
+ config.NoSanityCheck = true
+ logger.Main.Warn("Sanity check flag enabled, skipping sanity check. Not recommended.")
+ logger.Main.Info("Sleeping for 5 seconds to remind you again to not use this flag in production.")
+ time.Sleep(5 * time.Second)
+ }
+}
diff --git a/src/core/loader/helpers.go b/src/core/loader/helpers.go
index 92d6f897..2c636319 100644
--- a/src/core/loader/helpers.go
+++ b/src/core/loader/helpers.go
@@ -1,8 +1,11 @@
package loader
import (
+ "bufio"
"fmt"
+ "os"
"strings"
+ "sync"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger"
@@ -161,3 +164,33 @@ func PrintConfigDetails(logLevel ...string) {
logger.Config.Debug("=======================================")
}
+
+func IsInsideContainer(wg *sync.WaitGroup) {
+ wg.Add(1)
+ defer wg.Done()
+ // Check .dockerenv file (Docker-specific)
+ if _, err := os.Stat("/.dockerenv"); err == nil {
+ config.SetIsDockerContainer(true)
+ return
+ }
+ // Check cgroup (works for Docker and other container runtimes)
+ file, err := os.Open("/proc/1/cgroup")
+ if err != nil {
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+ // Check for various container runtime indicators
+ if strings.Contains(line, "docker") ||
+ strings.Contains(line, "containerd") ||
+ strings.Contains(line, "kubepods") ||
+ strings.Contains(line, "crio") ||
+ strings.Contains(line, "libpod") {
+ config.SetIsDockerContainer(true)
+ return
+ }
+ }
+}
diff --git a/src/core/loader/loader.go b/src/core/loader/loader.go
index 72830a12..1e64959a 100644
--- a/src/core/loader/loader.go
+++ b/src/core/loader/loader.go
@@ -3,7 +3,9 @@ package loader
import (
"embed"
+ "os"
"sync"
+ "time"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/discordbot"
@@ -98,3 +100,16 @@ func ReloadAppInfoPoller() {
func InitVirtFS(v1uiFS embed.FS) {
config.SetV1UIFS(v1uiFS)
}
+
+func SanityCheck(wg *sync.WaitGroup) {
+ wg.Add(1)
+ defer wg.Done()
+ err := runSanityCheck()
+ if err != nil {
+ logger.Main.Error("Sanity check failed, exiting in 10 secconds: " + err.Error())
+ logger.Main.Info("If you want to continue anyway, run SSUI with the --noSanityCheck flag, but be aware there may be Dragons ahead.")
+ logger.Main.Info("This is not recommended nor supported and may cause unexpected behavior, including potential data loss!")
+ time.Sleep(10 * time.Second)
+ os.Exit(1)
+ }
+}
diff --git a/src/core/loader/sanitycheck.go b/src/core/loader/sanitycheck.go
new file mode 100644
index 00000000..4e84fe3e
--- /dev/null
+++ b/src/core/loader/sanitycheck.go
@@ -0,0 +1,82 @@
+package loader
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+
+ "github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
+)
+
+var containerCheckWG = sync.WaitGroup{}
+
+func runSanityCheck() error {
+
+ if runtime.GOOS == "windows" {
+ return nil
+ }
+
+ if config.GetNoSanityCheck() {
+ return nil
+ }
+
+ IsInsideContainer(&containerCheckWG)
+ containerCheckWG.Wait()
+
+ // Check if running as root (UID 0)
+ if os.Geteuid() == 0 {
+ // Check if running inside a container
+ if !config.GetIsDockerContainer() {
+ return fmt.Errorf("root: SSUI should not be run as root")
+ }
+ }
+
+ // Get the current executable path from /proc/self/exe
+ exePath, err := os.Readlink("/proc/self/exe")
+ if err != nil {
+ return err
+ }
+ // Get the directory path of the executable
+ dirPath := filepath.Dir(exePath)
+ // Change the working directory to the executable's directory
+ cwd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+
+ if cwd != dirPath && !strings.Contains(dirPath, "/tmp") {
+ err = os.Chdir(dirPath)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Check if current working directory is writable
+ workDir, err := os.Getwd()
+ if err != nil {
+ return fmt.Errorf("failed to get working directory: %w", err)
+ }
+
+ // Try to create a temporary file to test write permissions
+ testFile := filepath.Join(workDir, ".write_test")
+ if err := os.WriteFile(testFile, []byte("test"), 0600); err != nil {
+ return fmt.Errorf("cannot write to working directory, please make sure your user has write permissions in %s: %w", workDir, err)
+ }
+ // Clean up test file
+ if err := os.Remove(testFile); err != nil {
+ return fmt.Errorf("failed to clean up sanity check writetest file: %w", err)
+ }
+
+ // Check if steamcmd package is installed (requires further testing, disabled for now)
+ cmd := exec.Command("dpkg-query", "-W", "-f='${Status}'", "steamcmd")
+ output, err := cmd.CombinedOutput()
+ if err == nil && strings.Contains(string(output), "install ok installed") {
+ return fmt.Errorf("steamcmd apt package is installed, it is not recommended to run SSUI when the apt steamcmd package is installed. Please uninstall the steamcmd package or have a look at our Docker image and try again")
+ }
+
+ return nil
+}
diff --git a/src/core/loader/terminalmsg.go b/src/core/loader/terminalmsg.go
index 22125f41..588eed6f 100644
--- a/src/core/loader/terminalmsg.go
+++ b/src/core/loader/terminalmsg.go
@@ -23,7 +23,7 @@ func printStartupMessage() {
logger.Core.Cleanf(" ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝ ╚══════╝ ╚═════╝ ╚═╝")
logger.Core.Cleanf(" ╔═══════════════════════════════════════════════════════════════════════════════════════════════════╗")
logger.Core.Cleanf(" ║ 🎮 YOUR ONE-STOP SHOP FOR RUNNING A STATIONEERS SERVER 🎮 ║")
- logger.Core.Cleanf(" ║ 🚀 Version: %s 📅 %s 💻 Runtime: %.3s/%s ║",
+ logger.Core.Cleanf(" ║ 🚀 Version: %s 📅 %s 💻 Runtime: %.3s/%s ║",
config.GetVersion(),
time.Now().Format("2006-01-02 15:04"),
runtime.GOOS,
@@ -31,7 +31,7 @@ func printStartupMessage() {
logger.Core.Cleanf(" ╚═══════════════════════════════════════════════════════════════════════════════════════════════════╝")
// Web UI info
- logger.Core.Cleanf("\n 🌐 Web UI available at: https://localhost:8443 (default) or https://:" + config.GetSSUIWebPort())
+ logger.Core.Cleanf("\n 🌐 Web UI available at: https://localhost:8443 (default) or https://:%s", config.GetSSUIWebPort())
logger.Core.Cleanf("\n 🌐 Support available at: https://discord.gg/8n3vN92MyJ")
// Quote
@@ -48,6 +48,6 @@ func printFirstTimeSetupMessage() {
logger.Core.Cleanf(" │ • Configure your server by visiting the WebUI! │")
logger.Core.Cleanf(" │ • Support is provided at https://discord.gg/8n3vN92MyJ │")
logger.Core.Cleanf(" │ • For more details, check the GitHub Wiki: │")
- logger.Core.Cleanf(" │ • https://github.com/JacksonTheMaster/StationeersServerUI/v5/wiki │")
+ logger.Core.Cleanf(" │ • https://github.com/SteamServerUI/StationeersServerUI/v5/wiki │")
logger.Core.Cleanf(" └─────────────────────────────────────────────────────────────────────────────────────────────┘")
}
diff --git a/src/managers/detectionmgr/detector.go b/src/managers/detectionmgr/detector.go
index ddccf19c..0aec9a7d 100644
--- a/src/managers/detectionmgr/detector.go
+++ b/src/managers/detectionmgr/detector.go
@@ -7,6 +7,7 @@ import (
"strings"
"time"
+ "github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/discordbot"
)
@@ -240,6 +241,19 @@ func (d *Detector) processRegexPatterns(logMessage string) {
})
},
},
+ {
+ pattern: regexp.MustCompile(`Version\s*:\s*(\d+\.\d+\.\d+\.\d+)`),
+ handler: func(matches []string, logMessage string) {
+ version := matches[1]
+ d.triggerEvent(Event{
+ Type: EventVersionExtracted,
+ Message: fmt.Sprintf("Version %s detected", version),
+ RawLog: logMessage,
+ Timestamp: time.Now().Format(time.RFC3339),
+ })
+ config.SetExtractedGameVersion(version)
+ },
+ },
}
for _, p := range patterns {
diff --git a/src/managers/detectionmgr/handlers.go b/src/managers/detectionmgr/handlers.go
index abcc6b9c..d8ff75f3 100644
--- a/src/managers/detectionmgr/handlers.go
+++ b/src/managers/detectionmgr/handlers.go
@@ -66,6 +66,12 @@ func DefaultHandlers() map[EventType]Handler {
ssestream.BroadcastDetectionEvent(message)
discordbot.SendMessageToStatusChannel(message)
},
+ EventVersionExtracted: func(event Event) {
+ message := fmt.Sprintf("🎮 [Gameserver] 📦 Version %s detected", event.Message)
+ logger.Detection.Info(message)
+ ssestream.BroadcastDetectionEvent(message)
+ discordbot.SendMessageToStatusChannel(message)
+ },
EventServerRunning: func(event Event) {
message := "🎮 [Gameserver] ✅ Server process has started!"
logger.Detection.Info(message)
diff --git a/src/managers/detectionmgr/types.go b/src/managers/detectionmgr/types.go
index c24773f6..efa327d2 100644
--- a/src/managers/detectionmgr/types.go
+++ b/src/managers/detectionmgr/types.go
@@ -18,6 +18,7 @@ const (
EventSettingsChanged EventType = "SETTINGS_CHANGED"
EventServerHosted EventType = "SERVER_HOSTED"
EventNewGameStarted EventType = "NEW_GAME_STARTED"
+ EventVersionExtracted EventType = "VERSION_EXTRACTED"
EventServerRunning EventType = "SERVER_RUNNING"
EventCustomDetection EventType = "CUSTOM_DETECTION"
)
diff --git a/src/setup/install.go b/src/setup/install.go
index 12a33e80..99e1d82d 100644
--- a/src/setup/install.go
+++ b/src/setup/install.go
@@ -42,7 +42,9 @@ func Install(wg *sync.WaitGroup) {
logger.Install.Info("✅Blacklist.txt verified or created.")
// Step 3: Install and run SteamCMD
logger.Install.Info("🔄Installing and running SteamCMD...")
- if config.GetBranch() != "indev-no-steamcmd" {
+ if config.GetSkipSteamCMD() {
+ logger.Install.Info("✅Skipping SteamCMD installation, SkipSteamCMD is true")
+ } else {
steamcmd.InstallAndRunSteamCMD()
}
logger.Install.Info("✅Setup complete!")
@@ -350,7 +352,7 @@ func checkAndCreateBlacklist() {
logger.Install.Info("✅Created Blacklist.txt with dummy steamID64.")
} else {
- logger.Install.Info("♻️Blacklist.txt already exists. Skipping creation.")
+ logger.Install.Debug("♻️Blacklist.txt already exists. Skipping creation.")
}
}
diff --git a/src/steamcmd/getappinfo.go b/src/steamcmd/getappinfo.go
index ba6c6f89..53e68541 100644
--- a/src/steamcmd/getappinfo.go
+++ b/src/steamcmd/getappinfo.go
@@ -4,10 +4,12 @@ import (
"bytes"
"fmt"
"maps"
+ "os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
+ "strings"
"sync"
"time"
@@ -64,6 +66,13 @@ func AppInfoPoller() {
// getAppInfo fetches the branches and their build IDs for the specified app ID using SteamCMD
// and stores them in the package-level branches map.
func getAppInfo() error {
+
+ currentDir, err := os.Getwd()
+ if err != nil {
+ logger.Install.Error("❌ Error getting current working directory: " + err.Error() + "\n")
+ return err
+ }
+
if steamMu.TryLock() {
// Successfully acquired the lock; no other func holds it
logger.Core.Debug("🔄 Locking SteamMu for SteamCMD AppInfo...")
@@ -90,14 +99,32 @@ func getAppInfo() error {
cmd.Stdout = &stdout
cmd.Stderr = &stderr
+ if runtime.GOOS == "linux" {
+ env := os.Environ()
+ // Replace or set HOME
+ newEnv := make([]string, 0, len(env)+1)
+ foundHome := false
+ for _, e := range env {
+ if !strings.HasPrefix(e, "HOME=") {
+ newEnv = append(newEnv, e)
+ } else {
+ newEnv = append(newEnv, "HOME="+currentDir)
+ foundHome = true
+ }
+ }
+ if !foundHome {
+ newEnv = append(newEnv, "HOME="+currentDir)
+ }
+ cmd.Env = newEnv
+ }
+
// Log the command
//if config.GetLogLevel() == 10 {
// cmdString := strings.Join(cmd.Args, " ")
// logger.Install.Debug("🕑 Running SteamCMD for app info: " + cmdString)
//}
-
// Run the command
- err := cmd.Run()
+ err = cmd.Run()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
logger.Install.Errorf("❌ SteamCMD app info failed (code %d): %s\n", exitErr.ExitCode(), stderr.String())
diff --git a/src/steamcmd/steamcmd.go b/src/steamcmd/steamcmd.go
index 292ed1ce..7312e714 100644
--- a/src/steamcmd/steamcmd.go
+++ b/src/steamcmd/steamcmd.go
@@ -101,6 +101,25 @@ func runSteamCMD(steamCMDDir string) (int, error) {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
+ if runtime.GOOS == "linux" {
+ env := os.Environ()
+ // Replace or set HOME
+ newEnv := make([]string, 0, len(env)+1)
+ foundHome := false
+ for _, e := range env {
+ if !strings.HasPrefix(e, "HOME=") {
+ newEnv = append(newEnv, e)
+ } else {
+ newEnv = append(newEnv, "HOME="+currentDir)
+ foundHome = true
+ }
+ }
+ if !foundHome {
+ newEnv = append(newEnv, "HOME="+currentDir)
+ }
+ cmd.Env = newEnv
+ }
+
// Run the command
if config.GetLogLevel() == 10 {
cmdString := strings.Join(cmd.Args, " ")