Skip to content
Merged
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
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ docker logs factorio
Stop the server:

```shell
docker stop factorio
docker stop --timeout 120 factorio
```

Allow enough time for the world to save before Docker sends SIGKILL. Increase the timeout for large saves; with Compose, configure `stop_grace_period` instead.

Now there's a `server-settings.json` file in the folder `/opt/factorio/config`. Modify this to your liking and restart the server:

```shell
Expand Down Expand Up @@ -114,7 +116,7 @@ Now run the server as before. In about a minute the new version of Factorio shou

### Saves

A new map named `_autosave1.zip` is generated the first time the server is started. The `map-gen-settings.json` and `map-settings.json` files in `/opt/factorio/config` are used for the map settings. On subsequent runs the newest save is used.
A new map named `_autosave1.zip` (or `SAVE_NAME` when set) is generated the first time the server is started. The `map-gen-settings.json` and `map-settings.json` files in `/opt/factorio/config` are used for the map settings. On subsequent runs the newest save is used.

To load an old save stop the server and run the command `touch oldsave.zip`. This resets the date. Then restart the server. Another option is to delete all saves except one.

Expand All @@ -124,7 +126,7 @@ To generate a new map stop the server, delete all of the saves and restart the s

You can specify a specific save to load by configuring the server through a set of environment variables:

To load an existing save set `SAVE_NAME` to the name of your existing save file located within the `saves` directory, without the `.zip` extension:
To load an existing save, set `SAVE_NAME` to its name in the `saves` directory, with or without `.zip`, or to an absolute file path. The same path is used for generation and subsequent starts. A missing selected save produces an error rather than loading another world:

```shell
sudo docker run -d \
Expand Down Expand Up @@ -312,6 +314,8 @@ These are the environment variables which can be specified at container run time

**Note:** All environment variables are compared as strings. Quote YAML mapping values, for example `DLC_SPACE_AGE: "false"`. In list syntax, quote the whole item (`- "DLC_SPACE_AGE=elevated-rails"`), not only the value: `- DLC_SPACE_AGE="elevated-rails"` passes literal quotation marks to the container.

An explicit port in `BIND` overrides `PORT`, for example `127.0.0.1:34201` or `[::1]:34201`. A bare IPv6 address still uses `PORT`.

#### Host file ownership

The regular image starts as root, adjusts ownership inside `/factorio`, and runs the server as `PUID:PGID` (845:845 by default). To keep a bind-mounted data directory owned by your host user, set these to the numeric output of `id -u` and `id -g`. Changing them changes ownership of the mounted data, so use a dedicated directory—not your entire home directory.
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ ENV PORT=34197 \
SHELL ["/bin/bash", "-eo", "pipefail", "-c"]

RUN apt-get -q update \
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install ca-certificates curl jq pwgen xz-utils procps gettext-base file --no-install-recommends \
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install ca-certificates curl jq pwgen xz-utils procps gettext-base file util-linux --no-install-recommends \
&& if [[ "$(uname -m)" == "aarch64" && -z "${SHA256_ARM64}" ]]; then \
echo "installing ARM compatability layer" \
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install unzip --no-install-recommends \
Expand Down
28 changes: 21 additions & 7 deletions docker/files/docker-entrypoint-rootless.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,26 +67,32 @@ sed -i '/write-data=/c\write-data=\/factorio/' /opt/factorio/config/config.ini
NRSAVES=$(find -L "$SAVES" -iname \*.zip -mindepth 1 | wc -l)
if [[ $GENERATE_NEW_SAVE != true && $NRSAVES == 0 ]]; then
GENERATE_NEW_SAVE=true
SAVE_NAME=_autosave1
SAVE_NAME="${SAVE_NAME:-_autosave1}"
fi

if [[ "$SAVE_NAME" == /* ]]; then
SAVE_FILE="$SAVE_NAME"
else
SAVE_FILE="$SAVES/${SAVE_NAME%.zip}.zip"
fi

if [[ $GENERATE_NEW_SAVE == true ]]; then
if [[ -z "$SAVE_NAME" ]]; then
echo "If \$GENERATE_NEW_SAVE is true, you must specify \$SAVE_NAME"
exit 1
fi
if [[ -f "$SAVES/$SAVE_NAME.zip" ]]; then
echo "Map $SAVES/$SAVE_NAME.zip already exists, skipping map generation"
if [[ -f "$SAVE_FILE" ]]; then
echo "Map $SAVE_FILE already exists, skipping map generation"
else
if [[ -n "$PRESET" ]]; then
$EXEC /opt/factorio/bin/x64/factorio \
--create "$SAVES/$SAVE_NAME.zip" \
--create "$SAVE_FILE" \
--preset "$PRESET" \
--map-gen-settings "$CONFIG/map-gen-settings.json" \
--map-settings "$CONFIG/map-settings.json"
else
$EXEC /opt/factorio/bin/x64/factorio \
--create "$SAVES/$SAVE_NAME.zip" \
--create "$SAVE_FILE" \
--map-gen-settings "$CONFIG/map-gen-settings.json" \
--map-settings "$CONFIG/map-settings.json"
fi
Expand All @@ -95,7 +101,6 @@ fi

# Build command flags
FLAGS=(\
--port "$PORT" \
--server-settings "$SERVER_SETTINGS_FILE" \
--server-banlist "$SERVER_BANLIST_FILE" \
--rcon-port "$RCON_PORT" \
Expand All @@ -115,10 +120,19 @@ if [ -n "$BIND" ]; then
FLAGS+=( --bind "$BIND" )
fi

# An explicit bind port wins; bare IPv6 addresses still use PORT.
if [[ ! "$BIND" =~ ^[^:]+:[0-9]+$ && ! "$BIND" =~ ^\[[^]]+\]:[0-9]+$ ]]; then
FLAGS+=( --port "$PORT" )
fi

if [[ $LOAD_LATEST_SAVE == true ]]; then
FLAGS+=( --start-server-load-latest )
else
FLAGS+=( --start-server "$SAVE_NAME" )
if [[ -z "$SAVE_NAME" || ! -f "$SAVE_FILE" ]]; then
echo "Selected save does not exist: $SAVE_FILE. Set SAVE_NAME or GENERATE_NEW_SAVE=true." >&2
exit 1
fi
FLAGS+=( --start-server "$SAVE_FILE" )
fi

# Execute factorio
Expand Down
32 changes: 23 additions & 9 deletions docker/files/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ if [[ $(id -u) == 0 ]]; then
groupmod -o -g "$PGID" factorio
# Take ownership of factorio data if running as root
chown -R factorio:factorio "$FACTORIO_VOL"
# Drop to the factorio user
EXEC="runuser -u factorio -g factorio --"
# Replace the process when dropping privileges; runuser kills slow saves after SIGTERM.
EXEC="setpriv --reuid=factorio --regid=factorio --init-groups --"
fi

# Setup ARM64 emulation support
Expand All @@ -69,34 +69,39 @@ sed -i '/write-data=/c\write-data=\/factorio/' /opt/factorio/config/config.ini
NRSAVES=$(find -L "$SAVES" -iname \*.zip -mindepth 1 | wc -l)
if [[ $GENERATE_NEW_SAVE != true && $NRSAVES == 0 ]]; then
GENERATE_NEW_SAVE=true
SAVE_NAME=_autosave1
SAVE_NAME="${SAVE_NAME:-_autosave1}"
fi

if [[ "$SAVE_NAME" == /* ]]; then
SAVE_FILE="$SAVE_NAME"
else
SAVE_FILE="$SAVES/${SAVE_NAME%.zip}.zip"
fi

if [[ $GENERATE_NEW_SAVE == true ]]; then
if [[ -z "$SAVE_NAME" ]]; then
echo "If \$GENERATE_NEW_SAVE is true, you must specify \$SAVE_NAME"
exit 1
fi
if [[ -f "$SAVES/$SAVE_NAME.zip" ]]; then
echo "Map $SAVES/$SAVE_NAME.zip already exists, skipping map generation"
if [[ -f "$SAVE_FILE" ]]; then
echo "Map $SAVE_FILE already exists, skipping map generation"
else
if [[ -n "$PRESET" ]]; then
$EXEC /opt/factorio/bin/x64/factorio \
--create "$SAVES/$SAVE_NAME.zip" \
--create "$SAVE_FILE" \
--preset "$PRESET" \
--map-gen-settings "$CONFIG/map-gen-settings.json" \
--map-settings "$CONFIG/map-settings.json"
else
$EXEC /opt/factorio/bin/x64/factorio \
--create "$SAVES/$SAVE_NAME.zip" \
--create "$SAVE_FILE" \
--map-gen-settings "$CONFIG/map-gen-settings.json" \
--map-settings "$CONFIG/map-settings.json"
fi
fi
fi

FLAGS=(\
--port "$PORT" \
--server-settings "$SERVER_SETTINGS_FILE" \
--server-banlist "$SERVER_BANLIST_FILE" \
--rcon-port "$RCON_PORT" \
Expand All @@ -116,10 +121,19 @@ if [ -n "$BIND" ]; then
FLAGS+=( --bind "$BIND" )
fi

# An explicit bind port wins; bare IPv6 addresses still use PORT.
if [[ ! "$BIND" =~ ^[^:]+:[0-9]+$ && ! "$BIND" =~ ^\[[^]]+\]:[0-9]+$ ]]; then
FLAGS+=( --port "$PORT" )
fi

if [[ $LOAD_LATEST_SAVE == true ]]; then
FLAGS+=( --start-server-load-latest )
else
FLAGS+=( --start-server "$SAVE_NAME" )
if [[ -z "$SAVE_NAME" || ! -f "$SAVE_FILE" ]]; then
echo "Selected save does not exist: $SAVE_FILE. Set SAVE_NAME or GENERATE_NEW_SAVE=true." >&2
exit 1
fi
FLAGS+=( --start-server "$SAVE_FILE" )
fi

# shellcheck disable=SC2086
Expand Down
1 change: 1 addition & 0 deletions docker/files/scenario.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ source "${INSTALLED_DIRECTORY}/setup-exec.sh"
exec $EXEC /opt/factorio/bin/x64/factorio \
--port "$PORT" \
--start-server-load-scenario "$SERVER_SCENARIO" \
--mod-directory "$MODS" \
--preset "$PRESET" \
--map-gen-settings "$CONFIG/map-gen-settings.json" \
--map-settings "$CONFIG/map-settings.json" \
Expand Down
5 changes: 4 additions & 1 deletion docker/files/scenario2map.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ if [[ ! -f $CONFIG/map-settings.json ]]; then
cp /opt/factorio/data/map-settings.example.json "$CONFIG/map-settings.json"
fi

"${INSTALLED_DIRECTORY}"/docker-dlc.sh

# Setup ARM64 emulation support
EXEC=""
# shellcheck disable=SC1091
source "${INSTALLED_DIRECTORY}/setup-exec.sh"

exec $EXEC /opt/factorio/bin/x64/factorio \
--scenario2map "$SERVER_SCENARIO"
--scenario2map "$SERVER_SCENARIO" \
--mod-directory "$MODS"
3 changes: 2 additions & 1 deletion tests/test_config_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ def test_scenario_conversion_uses_custom_settings(self):
settings = self.work / "conversion" / "settings.json"
self.run_script("scenario2map.sh", {"SERVER_SETTINGS_FILE": str(settings)})
self.assertEqual(json.loads(settings.read_text()), self.example)
self.assertEqual(json.loads(self.calls.read_text()), ["--scenario2map", "freeplay"])
self.assertEqual(json.loads(self.calls.read_text()),
["--scenario2map", "freeplay", "--mod-directory", self.env["MODS"]])


if __name__ == "__main__":
Expand Down
Loading