Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/ghcr-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: GHCR

on:
push:
branches:
- "**"

permissions:
contents: read
packages: write

jobs:
build:
name: Build and push GHCR image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Docker - GHCR Login
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Docker - Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}

- name: Docker - Build / Push
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ ENV PUID=1000 \
GAME_ID="" \
DATA_PATH="${STEAMAPPDATADIR}" \
MAX_PLAYERS=10 \
OVERRIDE_SERVER_CONFIG=false \
CUSTOM_LAUNCH_PARAMS="" \
SEASON="" \
SERVER_IP="" \
SERVER_PORT="" \
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ These are the arguments you can use to customize server behavior with default va
| MODIO_API_KEY | "" | mod.io API key |
| MODIO_API_URL | "" | mod.io API path |
| MODS | "" | List of mods to install |

## Mod Support

The container supports automatically installing mods from [mod.io](https://mod.io/g/corekeeper).
Expand Down Expand Up @@ -185,6 +186,58 @@ Server operators must open and forward the necessary ports on their router or fi
> Setting a value switches to Direct Connect, which requires opening and forwarding ports.<br>
> Only set this if you specifically want Direct Connect.

## Advanced ServerConfig Overrides

By default, the image keeps the existing behavior and starts Core Keeper with command-line arguments built from environment variables. Most servers do not need to use this section.

Set `OVERRIDE_SERVER_CONFIG=true` to additionally generate `/home/steam/core-keeper-data/ServerConfig.generated.json` on each container start and pass it to the server with `-serverconfig`. Use this only for server settings that Core Keeper does not expose as command-line arguments.

| Argument | Default | Description |
| :---: | :---: | :--- |
| OVERRIDE_SERVER_CONFIG | false | Enables generated `ServerConfig.generated.json` support when set to `true`. |
| MAX_NUMBER_PACKETS_SENT_PER_FRAME | No Default | Only used when `OVERRIDE_SERVER_CONFIG=true`. Sets `maxNumberPacketsSentPerFrame` in the generated `ServerConfig`. If unset, the generated config uses the game default of `1`. |
| NETWORK_SEND_RATE | No Default | Only used when `OVERRIDE_SERVER_CONFIG=true`. Sets `networkSendRate` in the generated `ServerConfig`. If unset, the generated config uses the game default of `20`. |
| CUSTOM_LAUNCH_PARAMS | "" | Appends extra launch arguments after the built-in container arguments. Intended for mod-specific or advanced server flags. |

The generated file is rebuilt from environment variables every startup, so edit the environment variables instead of editing `ServerConfig.generated.json` by hand. Existing environment variables such as `WORLD_NAME`, `MAX_PLAYERS`, `PASSWORD`, and `SERVER_PORT` are still emitted as command-line arguments, so they continue to work with `OVERRIDE_SERVER_CONFIG=true`.

### Network send tuning

`MAX_NUMBER_PACKETS_SENT_PER_FRAME` controls how much snapshot data the server is allowed to send per frame. Raising it can help clients receive dense world areas faster, for example when many automation machines or tiles appear late after players move quickly into a base. Higher values can increase outbound bandwidth usage.

> [!IMPORTANT]
> In the dedicated server code, Core Keeper calculates the snapshot target size as `min(maxNumberPacketsSentPerFrame * 1200, 9440)` and applies it to Unity NetCode's `NetworkStreamSnapshotTargetSize`. This means values above `8` are unlikely to increase this specific snapshot budget further.

Example values:

| MAX_NUMBER_PACKETS_SENT_PER_FRAME | Approximate snapshot budget | Expected effect |
| :---: | :---: | :--- |
| 1 | 1200 bytes/frame | Game default. Lowest bandwidth, but dense areas may stream in slowly. |
| 4 | 4800 bytes/frame | More world/object data can be sent each frame. Good first test for automation-heavy servers. |
| 8 | 9440 bytes/frame | Near the observed server cap. Higher values are unlikely to increase this budget further. |

`NETWORK_SEND_RATE` controls the server network tick rate. The game default is `20`, and values above the server simulation tick rate may be clamped by the game, so this is usually less useful than increasing `MAX_NUMBER_PACKETS_SENT_PER_FRAME`. Keep it at `20` unless you are specifically testing network tick behavior.

For automation-heavy servers where tiles or machines appear late but player movement does not rubber-band, start with:

```env
OVERRIDE_SERVER_CONFIG=true
MAX_NUMBER_PACKETS_SENT_PER_FRAME=4
NETWORK_SEND_RATE=20
```

### Custom launch parameters

Use `CUSTOM_LAUNCH_PARAMS` for additional dedicated server launch arguments that are not covered by the container's normal environment variables.

```env
CUSTOM_LAUNCH_PARAMS=-keepserverrunning
CUSTOM_LAUNCH_PARAMS=-keepserverrunning -someflag -someparam value
CUSTOM_LAUNCH_PARAMS=-someparam "value with spaces"
```

Standalone flags and `-flag value` pairs can be mixed in the same value. Single-quoted and double-quoted values are preserved as one argument. These arguments are appended after the container's built-in arguments, so use them only for settings that do not already have a dedicated environment variable above.

### Contributors
<a href="https://github.com/escapingnetwork/core-keeper-dedicated/graphs/contributors">
<img src="https://contrib.rocks/image?repo=escapingnetwork/core-keeper-dedicated" />
Expand Down
167 changes: 167 additions & 0 deletions scripts/compile-parameters.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,178 @@ add_flag() {
fi
}

append_custom_launch_params() {
local input="${CUSTOM_LAUNCH_PARAMS:-}"
local current=""
local quote=""
local char
local escape=0
local token_started=0
local i
local -a custom_params=()

if [ -z "$input" ]; then
return
fi

for ((i = 0; i < ${#input}; i++)); do
char="${input:i:1}"

if [ "$escape" -eq 1 ]; then
current+="$char"
token_started=1
escape=0
continue
fi

if [ "$quote" = "\"" ]; then
if [ "$char" = "\\" ]; then
escape=1
elif [ "$char" = "\"" ]; then
quote=""
else
current+="$char"
token_started=1
fi
continue
fi

if [ "$quote" = "'" ]; then
if [ "$char" = "'" ]; then
quote=""
else
current+="$char"
token_started=1
fi
continue
fi

case "$char" in
[[:space:]])
if [ "$token_started" -eq 1 ]; then
custom_params+=("$current")
current=""
token_started=0
fi
;;
"'")
quote="'"
token_started=1
;;
"\"")
quote="\""
token_started=1
;;
"\\")
escape=1
token_started=1
;;
*)
current+="$char"
token_started=1
;;
esac
done

if [ "$escape" -eq 1 ]; then
current+="\\"
fi

if [ -n "$quote" ]; then
echo "Invalid CUSTOM_LAUNCH_PARAMS: unmatched quote" >&2
exit 1
fi

if [ "$token_started" -eq 1 ]; then
custom_params+=("$current")
fi

params+=("${custom_params[@]}")
}

json_string() {
local value="$1"

value="${value//\\/\\\\}"
value="${value//\"/\\\"}"
value="${value//$'\n'/\\n}"
value="${value//$'\r'/\\r}"
value="${value//$'\t'/\\t}"

printf '"%s"' "$value"
}

json_int_or_default() {
local name="$1"
local value="$2"
local default="$3"

if [ -z "$value" ]; then
printf '%s' "$default"
return
fi

if [[ "$value" =~ ^-?[0-9]+$ ]]; then
printf '%s' "$value"
return
fi

echo "Invalid integer value for ${name}: ${value}" >&2
exit 1
}

generate_server_config() {
local data_path="${DATA_PATH:-${STEAMAPPDATADIR:-}}"
local server_config="${data_path}/ServerConfig.generated.json"
local world
local hashed_world_seed
local max_players
local max_number_packets_sent_per_frame
local network_send_rate
local world_mode
local season_override

world="$(json_int_or_default "WORLD_INDEX" "${WORLD_INDEX:-}" 0)" || exit 1
hashed_world_seed="$(json_int_or_default "HASHED_WORLD_SEED" "${HASHED_WORLD_SEED:-}" 0)" || exit 1
max_players="$(json_int_or_default "MAX_PLAYERS" "${MAX_PLAYERS:-}" 10)" || exit 1
max_number_packets_sent_per_frame="$(json_int_or_default "MAX_NUMBER_PACKETS_SENT_PER_FRAME" "${MAX_NUMBER_PACKETS_SENT_PER_FRAME:-}" 1)" || exit 1
network_send_rate="$(json_int_or_default "NETWORK_SEND_RATE" "${NETWORK_SEND_RATE:-}" 20)" || exit 1
world_mode="$(json_int_or_default "WORLD_MODE" "${WORLD_MODE:-}" 0)" || exit 1
season_override="$(json_int_or_default "SEASON" "${SEASON:-}" -1)" || exit 1

mkdir -p "$(dirname "$server_config")"

cat > "$server_config" <<EOF
{
"gameId": $(json_string "${GAME_ID:-}"),
"password": $(json_string "${PASSWORD:-}"),
"world": ${world},
"worldName": $(json_string "${WORLD_NAME:-}"),
"worldSeed": $(json_string "${WORLD_SEED:-}"),
"hashedWorldSeed": ${hashed_world_seed},
"maxNumberPlayers": ${max_players},
"maxNumberPacketsSentPerFrame": ${max_number_packets_sent_per_frame},
"networkSendRate": ${network_send_rate},
"worldMode": ${world_mode},
"seasonOverride": ${season_override}
}
EOF

params+=("-serverconfig" "$server_config")
}

# Makes log file avaliable for other uses.
logfile="${STEAMAPPDIR}/logs/$(date '+%Y-%m-%d_%H-%M-%S').log"
params=(
"-batchmode"
"-logfile" "$logfile"
)

override_server_config="${OVERRIDE_SERVER_CONFIG:-}"
if [ "${override_server_config,,}" = "true" ]; then
generate_server_config
fi

add_param "-world" "${WORLD_INDEX}"
add_param "-worldname" "${WORLD_NAME}"
add_param "-worldseed" "${WORLD_SEED}"
Expand All @@ -49,4 +214,6 @@ add_param "-allowonlyplatform" "${ALLOW_ONLY_PLATFORM}"

add_flag "-activateallcontent" "${ACTIVATE_ALL_CONTENT}"

append_custom_launch_params

echo "${params[@]}"
Loading