Compare commits

..
5 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
5 changed files with 31 additions and 4 deletions

No files matched your search

+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{ return map[string]any{
"command": "DEPLOY_SERVER", "command": "DEPLOY_SERVER",
"serverId": "boottest-" + id, "serverId": "boottest-" + id,
@@ -166,7 +173,7 @@ func bootTestPayload(gamesDir, id string, fillBlank bool) (map[string]any, error
"configFilePath": g["configFilePath"], "configFilePath": g["configFilePath"],
"configFileFormat": g["configFileFormat"], "configFileFormat": g["configFileFormat"],
"configFormatOptions": g["configFormatOptions"], "configFormatOptions": g["configFormatOptions"],
"startupCommand": g["defaultStartupCommand"], "startupCommand": startup,
"memoryLimitMb": g["requiredMemoryMb"], "memoryLimitMb": g["requiredMemoryMb"],
"containerUser": g["containerUser"], "containerUser": g["containerUser"],
"dockerNetwork": bootTestNetwork, "dockerNetwork": bootTestNetwork,
+15
View File
@@ -132,3 +132,18 @@ func TestSteamCacheStampAge(t *testing.T) {
t.Error("old, missing or garbled stamps are not fresh") 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", "defaultConfig", "configSchema", "configFilePath", "configFileFormat", "configFormatOptions",
"agnosticCommandConfig", "clusterConfig", "agnosticCommandConfig", "clusterConfig",
"logFilePath", "readyPattern", "failPatterns", "backupPaths", "maxBackupSizeMb", "logFilePath", "readyPattern", "failPatterns", "backupPaths", "maxBackupSizeMb",
"publicIpConfigKey", "maxPlayersConfigKey", "mapConfigKey", "publicIpConfigKey", "portMode", "maxPlayersConfigKey", "mapConfigKey",
"aiCoverPrompt", "aiCoverPrompt",
} }
@@ -2,7 +2,7 @@
id: runescape-dragonwilds-native-linux id: runescape-dragonwilds-native-linux
name: 'RuneScape: Dragonwilds (Native Linux Server)' name: 'RuneScape: Dragonwilds (Native Linux Server)'
defaultDockerImage: cm2network/steamcmd:root defaultDockerImage: cm2network/steamcmd:root
defaultStartupCommand: ./RSDragonwildsServer.sh -log -Port=7777 defaultStartupCommand: ./RSDragonwildsServer.sh -log -Port={PUBLIC_PORT_7777} -BeaconPort={PUBLIC_PORT_8888}
requiredMemoryMb: 10240 requiredMemoryMb: 10240
requiredDiskMb: 20480 requiredDiskMb: 20480
defaultPorts: defaultPorts:
@@ -106,12 +106,13 @@ agnosticCommandConfig:
Save: Save:
type: UNSUPPORTED type: UNSUPPORTED
playerJoinRegex: 'LogNet: Join succeeded: ([^\r\n]+)' playerJoinRegex: 'LogNet: Join succeeded: ([^\r\n]+)'
playerLeaveRegex: 'LogNet: UNetConnection::.*Player(?: name)?:\s*([^,\r\n]+)' playerLeaveRegex: 'LogDomMatcherSession: Player Removed from session \[[^\]]*\]-\[([^\]]+)\]'
queryType: LOG_ONLY queryType: LOG_ONLY
logFilePath: RSDragonwilds/Saved/Logs/RSDragonwilds.log logFilePath: RSDragonwilds/Saved/Logs/RSDragonwilds.log
backupPaths: backupPaths:
- RSDragonwilds/Saved - RSDragonwilds/Saved
maxBackupSizeMb: 5000 maxBackupSizeMb: 5000
publicIpConfigKey: MULTIHOME publicIpConfigKey: MULTIHOME
portMode: MATCH_PUBLIC
maxPlayersConfigKey: MaxPlayers 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.' 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.'
+4
View File
@@ -69,6 +69,10 @@
"maxBackupSizeMb": { "type": ["integer", "null"], "minimum": 1 }, "maxBackupSizeMb": { "type": ["integer", "null"], "minimum": 1 },
"publicIpConfigKey": { "description": "Config key that receives the server's public address.", "type": ["string", "null"] }, "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"] }, "maxPlayersConfigKey": { "description": "Config key locked to the pricing tier's player count.", "type": ["string", "null"] },
"mapConfigKey": { "type": ["string", "null"] }, "mapConfigKey": { "type": ["string", "null"] },