Compare commits

..
12 Commits
Author SHA1 Message Date
git_pipeline 7c27519497 Merge pull request 'fix(dragonwilds): match the real player-leave line' (#30) from fix/dragonwilds-leave-regex into main
Validate game templates / validate (push) Successful in 48s
Validate game templates / boot-test (push) Successful in 3m22s
Validate game templates / publish (push) Successful in 18s
2026-11-08 02:15:56 +00:00
Greg ReedandClaude Opus 5.5 5be0d08b84 fix(dragonwilds): match the real player-leave line
Validate game templates / validate (push) Successful in 21s
Validate game templates / boot-test (push) Successful in 3m24s
Validate game templates / publish (push) Skipped
The old leave pattern never matched anything the server logs. Players now
leave on "LogDomMatcherSession: Player Removed from session [id]-[name]",
which carries the same name as the join line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZxvxViV6QwAtVWT3saGvs
2026-11-08 02:10:50 +00:00
git_pipeline f80c715b72 Merge pull request 'feat(dragonwilds): MATCH_PUBLIC so the join code works behind a relay' (#29) from feat/dragonwilds-match-public into main
Validate game templates / validate (push) Successful in 24s
Builder image / publish (push) Successful in 25s
Validate game templates / boot-test (push) Successful in 3m19s
Validate game templates / publish (push) Successful in 18s
2026-11-08 01:48:00 +00:00
Greg ReedandClaude Opus 5.5 bb4fd3e11c fix(boot-test): fill {PUBLIC_*} in the startup command like the panel does
Validate game templates / validate (push) Successful in 20s
Validate game templates / boot-test (push) Successful in 3m19s
Validate game templates / publish (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZxvxViV6QwAtVWT3saGvs
2026-11-08 01:39:57 +00:00
Greg ReedandClaude Opus 5.5 1a28f6c52e feat(dragonwilds): MATCH_PUBLIC so the join code works behind a relay
Validate game templates / validate (push) Successful in 28s
Validate game templates / boot-test (push) Failing after 3m51s
Validate game templates / publish (push) Skipped
The server advertises the address its outgoing traffic comes from, so it
binds its public ports (-Port / -BeaconPort from {PUBLIC_PORT_n}) and, behind
Public Bypass, sends its traffic out through the relay. Adds portMode to the
schema and to import's field order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZxvxViV6QwAtVWT3saGvs
2026-11-08 01:32:23 +00:00
git_pipeline cb79e172b9 Merge pull request 'feat(satisfactory): ready pattern 'Engine is initialized'' (#28) from feat/satisfactory-ready-pattern into main
Validate game templates / validate (push) Successful in 21s
Validate game templates / boot-test (push) Successful in 2m16s
Validate game templates / publish (push) Successful in 18s
2026-11-07 16:20:56 +00:00
Greg ReedandClaude Opus 5.5 1a5959c46e feat(satisfactory): mark the server ready on 'Engine is initialized'
Validate game templates / validate (push) Successful in 20s
Validate game templates / boot-test (push) Successful in 2m16s
Validate game templates / publish (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZxvxViV6QwAtVWT3saGvs
2026-11-07 16:17:44 +00:00
git_pipeline ac3febf109 Merge pull request 'fix(satisfactory): official SteamCMD server, and builder uses SteamCMD only for Linux Steam servers' (#27) from fix/satisfactory-steamcmd into main
Validate game templates / validate (push) Successful in 23s
Builder image / publish (push) Successful in 25s
Validate game templates / boot-test (push) Successful in 4m39s
Validate game templates / publish (push) Successful in 18s
2026-11-07 02:20:48 +00:00
Greg Reed 1902c62366 fix(builder): SteamCMD is the only option for Linux Steam servers; settings must reach the server
Validate game templates / validate (push) Successful in 20s
Validate game templates / boot-test (push) Successful in 3m49s
Validate game templates / publish (push) Skipped
2026-11-07 02:16:19 +00:00
git_pipelineandClaude Opus 5.5 a918704ec6 fix(satisfactory): restore server settings, written to Game.ini/Engine.ini each start
Validate game templates / validate (push) Successful in 19s
Validate game templates / boot-test (push) Successful in 4m2s
Validate game templates / publish (push) Skipped
The official server reads no env vars, so the startup command writes Max Players, autosave count, tick rate, timeout, object limit and server streaming into its ini files, and passes seasonal events and multihome as launch options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZxvxViV6QwAtVWT3saGvs
2026-11-07 01:54:26 +00:00
git_pipelineandClaude Opus 5.5 5c192127f4 test(satisfactory): allow 40 minutes for the SteamCMD install and first start
Validate game templates / validate (push) Successful in 19s
Validate game templates / boot-test (push) Successful in 3m48s
Validate game templates / publish (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZxvxViV6QwAtVWT3saGvs
2026-11-07 01:45:59 +00:00
git_pipelineandClaude Opus 5.5 b9d445c57d fix(satisfactory): run the official SteamCMD server instead of wolveix/satisfactory-server
Validate game templates / validate (push) Successful in 45s
Validate game templates / boot-test (push) Successful in 5m57s
Validate game templates / publish (push) Skipped
The wolveix image keeps its game files and saves in /config and /home/steam, outside the server volume, so they were lost on every start and the weekly boot test failed. Now: cm2network/steamcmd with Steam app 1690800, HOME=/server, ports 7777/udp+tcp and 8888/tcp, MaxPlayers in Game.ini, -ExternalReliablePort for the public reliable port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZxvxViV6QwAtVWT3saGvs
2026-11-07 01:45:00 +00:00
10 changed files with 192 additions and 168 deletions

No files matched your search

+30
View File
@@ -39,10 +39,26 @@ Know this before writing anything:
- No real credentials, tokens or IDs anywhere in this repo. A value a server can't start without (a
player ID, a license token) goes in `test.yaml` under `secrets`, mapped to a CI secret name.
### The image: SteamCMD first
- If the game's dedicated server is on Steam and ships a Linux build, the template is
`defaultDockerImage: cm2network/steamcmd` with `installMethod: STEAMCMD` and the **server's** app id
(not the game's). Never a community image of that server (`wolveix/...`, `lloesche/...` and the like):
they keep game files and saves in their own folders (`/config`, `/home/<user>`), outside `/server`, so
everything is lost on the next start, and no setting can move them.
- Community images are only for servers that aren't on Steam for Linux: Windows-only servers that need
Proton or Wine, and games not on Steam.
- Many Linux servers save under `$HOME` (Unreal Engine games: `~/.config/Epic/<Game>/Saved`). In
`cm2network/steamcmd`, `$HOME` is `/home/steam`, outside the volume. Start the command with
`export HOME=/server;` so those saves land in the volume, and list them in `backupPaths` relative to
`/server` (e.g. `.config/Epic/<Game>/Saved/SaveGames`).
### Ports
- `defaultPorts` lists **only what players connect to** (game and query ports). RCON, REST and admin
ports are **never** published.
- Use `"7777/udp"` or `"8211:8211/udp"`. Every port needs its protocol.
- A port the game uses over both protocols is listed twice: `"7777/udp"` and `"7777/tcp"`.
- A game that tells players a second port (a reliable-messaging or query port it announces) needs it as
`{PUBLIC_PORT_<port>}` passed to the game's "external port" option, since nodes may publish it elsewhere.
- Nodes always publish these container ports (only the host side moves when servers share a node).
So the game's own port is **not a setting**: write it into the startup command or config file. A
game that must announce the port players use gets `{PUBLIC_PORT_<port>}`.
@@ -77,6 +93,20 @@ Know this before writing anything:
- Take keys and default values from what the server really writes: run
`gbrgames first-boot -game <id>` and read the settings in its report.
- Every key in `defaultConfig` has a `configSchema` property, and the other way round.
- **Every setting must reach the server.** Settings only arrive as environment variables (and in
`configFilePath`, if set). A server that doesn't read environment variables (most official servers,
every Unreal Engine server) ignores a setting unless the template delivers it: in `configFilePath`, as a
launch option the startup command builds from the variable, or written into the server's own config
file by the startup command before it starts. A setting that nothing reads looks editable in the panel
and does nothing: leave it out instead.
- `configFilePath` writes **every** setting into **one** file. When a server reads its settings from
several files (Unreal: `Game.ini`, `Engine.ini`, `GameUserSettings.ini` under
`<Game>/Saved/Config/LinuxServer/`), leave `configFilePath` out and write each file from the startup
command before the server starts:
`printf '[/Script/Engine.GameSession]\nMaxPlayers=%s\n' "${MAX_PLAYERS:-4}" > "$cfg/Game.ini";`.
The server may rewrite these files when it stops; writing them on every start keeps the panel in charge.
- Never carry over a community image's variable names (`MAXPLAYERS`, `AUTOSAVENUM`, `PUID`, `SKIPUPDATE`...)
into a SteamCMD template: only that image's own scripts read them. Use the game's real setting names.
- Every property has a `title`, a short `description` for a player, a `type`, a `default` equal to the
`defaultConfig` value, and a `category`: one of `Security & Access`, `Gameplay & Rules`,
`World & Environment`, `System & General`. Mark the few that matter when deploying with
+8 -1
View File
@@ -153,6 +153,13 @@ func bootTestPayload(gamesDir, id string, fillBlank bool) (map[string]any, error
}
}
// The panel fills {PUBLIC_IP}, {PUBLIC_HOST} and {PUBLIC_PORT_n} in the startup command too
startup := g["defaultStartupCommand"]
if cmd, ok := startup.(string); ok {
cmd = strings.NewReplacer("{PUBLIC_IP}", "127.0.0.1", "{PUBLIC_HOST}", "127.0.0.1").Replace(cmd)
startup = publicPort.ReplaceAllString(cmd, "$1")
}
return map[string]any{
"command": "DEPLOY_SERVER",
"serverId": "boottest-" + id,
@@ -166,7 +173,7 @@ func bootTestPayload(gamesDir, id string, fillBlank bool) (map[string]any, error
"configFilePath": g["configFilePath"],
"configFileFormat": g["configFileFormat"],
"configFormatOptions": g["configFormatOptions"],
"startupCommand": g["defaultStartupCommand"],
"startupCommand": startup,
"memoryLimitMb": g["requiredMemoryMb"],
"containerUser": g["containerUser"],
"dockerNetwork": bootTestNetwork,
+15
View File
@@ -132,3 +132,18 @@ func TestSteamCacheStampAge(t *testing.T) {
t.Error("old, missing or garbled stamps are not fresh")
}
}
func TestBootTestPayloadFillsPublicPlaceholdersInStartupCommand(t *testing.T) {
dir := t.TempDir()
game := filepath.Join(dir, "example")
os.MkdirAll(game, 0o755)
os.WriteFile(filepath.Join(game, "game.yaml"), []byte("id: example\nname: Example\ndefaultDockerImage: x\n"+
"defaultStartupCommand: ./server -Port={PUBLIC_PORT_7777} -ip={PUBLIC_IP}\ndefaultPorts:\n - 7777/udp\n"), 0o644)
p, err := bootTestPayload(dir, "example", false)
if err != nil {
t.Fatal(err)
}
if p["startupCommand"] != "./server -Port=7777 -ip=127.0.0.1" {
t.Errorf("startupCommand %v", p["startupCommand"])
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ var gameFields = []string{
"defaultConfig", "configSchema", "configFilePath", "configFileFormat", "configFormatOptions",
"agnosticCommandConfig", "clusterConfig",
"logFilePath", "readyPattern", "failPatterns", "backupPaths", "maxBackupSizeMb",
"publicIpConfigKey", "maxPlayersConfigKey", "mapConfigKey",
"publicIpConfigKey", "portMode", "maxPlayersConfigKey", "mapConfigKey",
"aiCoverPrompt",
}
+25 -7
View File
@@ -174,9 +174,10 @@ const researchPrompt = "You research how to host a game's dedicated server for G
"the ways this game's dedicated server can be run (variants): separate editions of the game with their own server, " +
"crossplay builds, or notably different server images. For each variant give:\n" +
"- images: how to run this server, best first. When the game has a dedicated server on Steam that runs on Linux, " +
"cm2network/steamcmd (the plain SteamCMD route, installing the server into /server) always comes first and that " +
"variant is the recommended one. Then Docker Hub images that appear in the pages and run as a non-root user " +
"(needed for servers that only exist for Windows).\n" +
"cm2network/steamcmd (the plain SteamCMD route, installing the server into /server) is its only image, and " +
"there is no separate variant for a community image of the same server: those keep their saves outside /server. " +
"Docker Hub images that appear in the pages and run as a non-root user are only for servers that aren't on Steam " +
"for Linux (Windows-only servers that need Proton or Wine, games not on Steam).\n" +
"- steamAppId: the Steam app id of the DEDICATED SERVER (not the game itself), if installed via SteamCMD; else \"\".\n" +
"- startupHint: the command that starts the server from its install folder /server, with the launch options " +
"for name, port, world and save location exactly as the pages document them; \"\" if unknown.\n" +
@@ -311,7 +312,7 @@ func verifyVariants(res *researchResult) {
}
}
apps := steamApps(appIDs)
anyRunnable := false
anyRunnable, linuxSteam := false, false
for i := range res.Variants {
v := &res.Variants[i]
var images []string
@@ -333,7 +334,8 @@ func verifyVariants(res *researchResult) {
v.Verified = append(v.Verified, fmt.Sprintf("Steam app %s is %q", v.SteamAppID, app.name))
v.Images = withSteamCMD(v.Images, app.linux)
if app.linux {
v.Verified = append(v.Verified, "it ships a Linux server, so the SteamCMD option (installing it into /server) is recommended")
linuxSteam = true
v.Verified = append(v.Verified, "it ships a Linux server, so it is installed with SteamCMD into /server")
}
} else {
v.Problems = append(v.Problems, "Steam app "+v.SteamAppID+" not found for anonymous SteamCMD")
@@ -352,10 +354,26 @@ func verifyVariants(res *researchResult) {
res.Reason = "unknown"
res.Message = "No variant has a Docker image or Steam app that could be confirmed. " + res.Message
}
if linuxSteam {
res.Variants = steamCMDOnly(res.Variants)
}
res.Variants = oneOptionPerImage(res.Variants)
recommend(res.Variants)
}
// steamCMDOnly keeps only the SteamCMD options once the game has a Linux server on Steam. Ready-made images
// of such servers keep their files and saves in their own folders (/config, /home/...), outside /server,
// and read settings from their own variables, so they can't be made or repaired into a working template.
func steamCMDOnly(variants []variant) []variant {
var out []variant
for _, v := range variants {
if len(v.Images) > 0 && isSteamCMDImage(v.Images[0]) && v.SteamAppID != "" {
out = append(out, v)
}
}
return out
}
// recommend marks exactly one variant recommended, among the runnable ones. A variant that installs a Linux
// server with SteamCMD comes before the model's choice: it keeps everything under /server, as nodes need.
func recommend(variants []variant) {
@@ -422,7 +440,7 @@ func steamApps(apps []string) map[string]steamApp {
}
// withSteamCMD adds the plain SteamCMD route to a variant's images. When the Steam app ships a Linux server
// it goes first: it installs into /server, which nodes keep, while ready-made images often keep their
// it is the only route: it installs into /server, which nodes keep, while ready-made images keep their
// files elsewhere. For Windows-only servers it stays last, behind images that bring Proton or Wine.
func withSteamCMD(images []string, linux bool) []string {
var others []string
@@ -432,7 +450,7 @@ func withSteamCMD(images []string, linux bool) []string {
}
}
if linux {
return append([]string{"cm2network/steamcmd"}, others...)
return []string{"cm2network/steamcmd"}
}
return append(others, "cm2network/steamcmd")
}
+15 -3
View File
@@ -59,10 +59,10 @@ func TestParseAppInfoFindsLinuxServers(t *testing.T) {
}
}
func TestWithSteamCMDGoesFirstOnlyForLinuxServers(t *testing.T) {
func TestWithSteamCMDIsTheOnlyRouteForLinuxServers(t *testing.T) {
images := []string{"someone/example-server", "cm2network/steamcmd", "other/example"}
got := withSteamCMD(images, true)
if len(got) != 3 || got[0] != "cm2network/steamcmd" || got[1] != "someone/example-server" {
if len(got) != 1 || got[0] != "cm2network/steamcmd" {
t.Errorf("linux: %v", got)
}
got = withSteamCMD([]string{"someone/proton-example"}, false)
@@ -89,10 +89,22 @@ func TestRecommendPrefersTheSteamCMDVariant(t *testing.T) {
}
}
func TestSteamCMDOnlyDropsImageVariantsOfLinuxSteamGames(t *testing.T) {
// A Linux Steam server researched as two variants: SteamCMD, and a community image under its own title
got := steamCMDOnly([]variant{
{ID: "dedicated", SteamAppID: "1000000", Images: []string{"cm2network/steamcmd"}},
{ID: "vanilla-stable", Images: []string{"someone/example-server"}},
})
if len(got) != 1 || got[0].ID != "dedicated" {
t.Errorf("got %+v", got)
}
}
func TestOneOptionPerImageThenSteamCMDRecommended(t *testing.T) {
// A variant runnable two ways becomes one option per way, SteamCMD recommended
v := oneOptionPerImage([]variant{{
ID: "dedicated", Title: "Example Dedicated Server", Recommended: true, SteamAppID: "1000000",
Images: withSteamCMD([]string{"someone/example-server"}, true),
Images: []string{"cm2network/steamcmd", "someone/example-server"},
Verified: []string{"image someone/example-server exists", "Steam app 1000000 is \"Example Dedicated Server\""},
}})
recommend(v)
@@ -2,7 +2,7 @@
id: runescape-dragonwilds-native-linux
name: 'RuneScape: Dragonwilds (Native Linux Server)'
defaultDockerImage: cm2network/steamcmd:root
defaultStartupCommand: ./RSDragonwildsServer.sh -log -Port=7777
defaultStartupCommand: ./RSDragonwildsServer.sh -log -Port={PUBLIC_PORT_7777} -BeaconPort={PUBLIC_PORT_8888}
requiredMemoryMb: 10240
requiredDiskMb: 20480
defaultPorts:
@@ -106,12 +106,13 @@ agnosticCommandConfig:
Save:
type: UNSUPPORTED
playerJoinRegex: 'LogNet: Join succeeded: ([^\r\n]+)'
playerLeaveRegex: 'LogNet: UNetConnection::.*Player(?: name)?:\s*([^,\r\n]+)'
playerLeaveRegex: 'LogDomMatcherSession: Player Removed from session \[[^\]]*\]-\[([^\]]+)\]'
queryType: LOG_ONLY
logFilePath: RSDragonwilds/Saved/Logs/RSDragonwilds.log
backupPaths:
- RSDragonwilds/Saved
maxBackupSizeMb: 5000
publicIpConfigKey: MULTIHOME
portMode: MATCH_PUBLIC
maxPlayersConfigKey: MaxPlayers
aiCoverPrompt: 'A cinematic wide shot of Ashenfall in the fantasy world of Gielinor from RuneScape: Dragonwilds. Ancient stone spires and forgotten fortresses rise amidst untamed wilderness. Colossal ancient dragons circle through stormy twilight skies illuminated by magical embers, while adventurers gather around a runic encampment wielding enchanted sorcery and survival gear.'
+88 -153
View File
@@ -1,165 +1,115 @@
# yaml-language-server: $schema=../../schema/game.schema.json
# The official dedicated server (Steam app 1690800), installed by the node with anonymous SteamCMD.
# Satisfactory keeps its saves under $HOME/.config/Epic/FactoryGame, so HOME is pointed at the server
# volume; otherwise they would land in /home/steam and be lost on the next start.
# The server reads no environment variables, so the startup command writes the settings into its
# Game.ini and Engine.ini before every start, and passes the rest as launch options.
id: satisfactory-vanilla-stable
name: Satisfactory (Vanilla Stable)
defaultDockerImage: wolveix/satisfactory-server:latest
name: Satisfactory
defaultDockerImage: cm2network/steamcmd
defaultStartupCommand: >-
export HOME=/server;
cfg=FactoryGame/Saved/Config/LinuxServer;
mkdir -p "$HOME/.steam/sdk64" "$cfg";
cp -f /home/steam/steamcmd/linux64/steamclient.so "$HOME/.steam/sdk64/" 2>/dev/null;
case "${SERVER_STREAMING:-true}" in true|True|1) ss=1;; *) ss=0;; esac;
printf '[/Script/Engine.GameSession]\nMaxPlayers=%s\n' "${MAX_PLAYERS:-4}" > "$cfg/Game.ini";
printf '[/Script/FactoryGame.FGSaveSession]\nmNumRotatingAutosaves=%s\n\n[/Script/OnlineSubsystemUtils.IpNetDriver]\nNetServerMaxTickRate=%s\nConnectionTimeout=%s\nInitialConnectTimeout=%s\n\n[/Script/Engine.GarbageCollectionSettings]\ngc.MaxObjectsInEditor=%s\ngc.MaxObjectsInGame=%s\n\n[ConsoleVariables]\nwp.Runtime.EnableServerStreaming=%s\n'
"${AUTOSAVE_NUM:-5}" "${MAX_TICK_RATE:-30}" "${CONNECTION_TIMEOUT:-30}" "${CONNECTION_TIMEOUT:-30}"
"${MAX_OBJECTS:-2162688}" "${MAX_OBJECTS:-2162688}" "$ss" > "$cfg/Engine.ini";
args=(-Port=7777 -ReliablePort=8888 -ExternalReliablePort="${EXTERNAL_RELIABLE_PORT:-8888}" -log -unattended);
case "${DISABLE_SEASONAL_EVENTS:-false}" in true|True|1) args+=(-DisableSeasonalEvents);; esac;
[ -n "${MULTIHOME:-}" ] && args+=(-multihome="$MULTIHOME");
exec ./FactoryServer.sh "${args[@]}"
requiredMemoryMb: 8192
requiredDiskMb: 16384
requiredDiskMb: 20480
defaultPorts:
- 7777/tcp
- 7777/udp
- 7777/tcp
- 8888/tcp
installMethod: NONE
installMethod: STEAMCMD
installConfig:
appId: "1690800"
defaultConfig:
AUTOSAVENUM: 5
DEBUG: false
DISABLESEASONALEVENTS: false
LOG: false
MAXOBJECTS: 2162688
MAXPLAYERS: 4
MAXTICKRATE: 30
MULTIHOME: '::'
PGID: 1000
PUID: 1000
SERVERGAMEPORT: 7777
SERVERMESSAGINGPORT: 8888
SERVERSTREAMING: true
SKIPUPDATE: false
STEAMBETA: false
STEAMBETAID: ""
STEAMBETAKEY: ""
TIMEOUT: 30
VMOVERRIDE: false
MaxPlayers: 4
AutosaveNum: 5
MaxTickRate: 30
ConnectionTimeout: 30
MaxObjects: 2162688
ServerStreaming: true
DisableSeasonalEvents: false
Multihome: ""
ExternalReliablePort: '{PUBLIC_PORT_8888}'
configSchema:
$schema: http://json-schema.org/draft-07/schema#
type: object
properties:
AUTOSAVENUM:
MaxPlayers:
title: Max Players
description: How many players can be on the server at once. Set by your plan.
type: integer
category: Gameplay & Rules
default: 4
AutosaveNum:
title: Autosave Rotation Count
description: How many rotating autosave files to keep in the save folder.
type: integer
minimum: 1
category: World & Environment
default: 5
description: The number of rotating autosave files to keep in the save folder.
title: Autosave Rotation Count
MaxTickRate:
title: Max Tick Rate
description: The highest simulation tick rate the server runs at, in Hz.
type: integer
DEBUG:
minimum: 10
maximum: 120
category: System & General
default: false
description: Prints diagnostic environment and system details on startup, then stops.
title: Debug Mode
type: boolean
DISABLESEASONALEVENTS:
category: Gameplay & Rules
default: false
description: Disables seasonal in-game events such as FICSMAS holiday events.
title: Disable Seasonal Events
type: boolean
LOG:
default: 30
ConnectionTimeout:
title: Connection Timeout
description: Seconds before a player who stops responding is disconnected.
type: integer
minimum: 5
category: System & General
default: false
description: Disables automatic pruning of previous Satisfactory server log files on container startup.
title: Retain Old Logs
type: boolean
MAXOBJECTS:
default: 30
MaxObjects:
title: Max Objects Limit
description: Unreal Engine object limit. Raise it for very large late-game factories.
type: integer
category: World & Environment
default: 2162688
description: Unreal Engine garbage collector UObject limit to support large late-game factories.
title: Max Objects Limit
type: integer
MAXPLAYERS:
category: System & General
default: 4
description: Sets the maximum number of concurrent players allowed on the server.
title: Max Players
type: integer
MAXTICKRATE:
category: System & General
default: 30
description: Sets the maximum simulation tick rate for the server in Hz.
title: Max Tick Rate
type: integer
MULTIHOME:
category: System & General
default: '::'
description: 'Network interface binding IP address (defaults to :: for dual-stack IPv4/IPv6).'
title: Multihome Address
type: string
PGID:
category: System & General
default: 1000
description: Sets the Linux group ID under which the dedicated server process runs.
title: Process Group ID
type: integer
PUID:
category: System & General
default: 1000
description: Sets the Linux user ID under which the dedicated server process runs.
title: Process User ID
type: integer
SERVERGAMEPORT:
category: System & General
default: 7777
description: Primary game port for UDP and TCP network communication.
title: Server Game Port
type: integer
SERVERMESSAGINGPORT:
category: System & General
default: 8888
description: Port used for reliable server messaging and beacon network traffic.
title: Server Messaging Port
type: integer
SERVERSTREAMING:
ServerStreaming:
title: Server Streaming
description: Lets the server stream world data in and out instead of keeping it all loaded.
type: boolean
category: System & General
default: true
description: Toggles whether the game server utilizes Unreal Engine World Partition asset streaming.
title: Server Streaming
DisableSeasonalEvents:
title: Disable Seasonal Events
description: Turns off seasonal events such as FICSMAS.
type: boolean
SKIPUPDATE:
category: System & General
category: Gameplay & Rules
default: false
description: Skips automatic SteamCMD game file update checks on container startup.
title: Skip Update
type: boolean
STEAMBETA:
category: System & General
default: false
description: When enabled, updates and runs the Experimental branch instead of the stable release.
title: Steam Beta Branch
type: boolean
STEAMBETAID:
Multihome:
title: Multihome Address
description: Bind the server to one IP address instead of all of them. Leave empty unless you know you need it.
type: string
category: System & General
default: ""
description: Specific beta branch identifier to download via SteamCMD.
title: Steam Beta Branch Name
ExternalReliablePort:
title: Reliable Messaging Port (public)
description: The port players' games use for reliable messaging. Filled in for you; only change it if you know you need to.
type: string
STEAMBETAKEY:
category: Security & Access
default: ""
description: Password required to access a private Steam beta branch.
title: Steam Beta Password
type: string
TIMEOUT:
category: System & General
default: 30
description: Client network connection timeout threshold in seconds.
title: Connection Timeout
type: integer
VMOVERRIDE:
category: System & General
default: false
description: Skips CPU model checking on hypervisors to prevent crash warnings on KVM/QEMU hosts.
title: VM CPU Check Override
type: boolean
default: '{PUBLIC_PORT_8888}'
required: []
type: object
configFileFormat: INI
configFormatOptions:
assignment: =
falseValue: "false"
footer: ""
header: ""
quoteStrings: false
separator: "\n"
trueValue: "true"
unquotedKeys: []
uppercaseKeys: false
maxPlayersConfigKey: MaxPlayers
logFilePath: FactoryGame/Saved/Logs/FactoryGame.log
readyPattern: 'Engine is initialized'
agnosticCommandConfig:
queryType: LOG_ONLY
playerJoinRegex: 'LogNet: Join succeeded: ([^\r\n]+)'
playerLeaveRegex: 'UNetConnection::Close:.*?\[PlayerName:\s*([^\]]+)\]'
actions:
Ban:
type: UNSUPPORTED
@@ -169,22 +119,7 @@ agnosticCommandConfig:
type: UNSUPPORTED
Save:
type: UNSUPPORTED
pingPort: null
pingPortKey: null
playerJoinRegex: '.*Join succeeded: (\S+).*'
playerLeaveRegex: .*UNetConnection::Close:.*?\[PlayerName:\s*([^\]]+)\].*
playersCommand: null
playersFormat: null
playersRegex: null
queryPortKey: null
queryType: LOG_ONLY
rconPasswordKey: null
rconPortKey: null
restEndpoint: null
restPasswordKey: null
restPortKey: null
restUser: null
backupPaths:
- saved
maxBackupSizeMb: 1024
maxPlayersConfigKey: MAXPLAYERS
- .config/Epic/FactoryGame/Saved/SaveGames
- FactoryGame/Saved/Config
maxBackupSizeMb: 2048
+3 -1
View File
@@ -1,3 +1,5 @@
# yaml-language-server: $schema=../../schema/test.schema.json
# Values CI uses to boot-test this game. Never put real credentials here: map them under 'secrets'.
timeoutSec: 900
# SteamCMD install of ~8 GB plus first start
timeoutSec: 2400
readyExample: "LogInit: Display: Engine is initialized. Leaving FEngineLoop::Init()"
+4
View File
@@ -69,6 +69,10 @@
"maxBackupSizeMb": { "type": ["integer", "null"], "minimum": 1 },
"publicIpConfigKey": { "description": "Config key that receives the server's public address.", "type": ["string", "null"] },
"portMode": {
"description": "MATCH_PUBLIC: for games that advertise the address their own traffic comes from. The container binds the port players use (the relay port behind Public Bypass) and, behind a relay, all its outgoing traffic leaves through the relay. Tell the game its port with {PUBLIC_PORT_<defaultPort>} in defaultStartupCommand.",
"enum": ["DEFAULT", "MATCH_PUBLIC", null]
},
"maxPlayersConfigKey": { "description": "Config key locked to the pricing tier's player count.", "type": ["string", "null"] },
"mapConfigKey": { "type": ["string", "null"] },