mirror of
https://github.com/factoriotools/factorio-docker.git
synced 2025-10-21 13:19:15 +02:00
Compare commits
1 Commits
feature/ro
...
fix-517-mo
Author | SHA1 | Date | |
---|---|---|---|
|
8a1c6ac27b |
4
.github/workflows/docker-build.yml
vendored
4
.github/workflows/docker-build.yml
vendored
@@ -20,10 +20,10 @@ jobs:
|
|||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: build and push all images
|
- name: build and push
|
||||||
if: ${{ env.DOCKER_USERNAME != '' && env.DOCKER_PASSWORD != '' }}
|
if: ${{ env.DOCKER_USERNAME != '' && env.DOCKER_PASSWORD != '' }}
|
||||||
env:
|
env:
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
run: |
|
run: |
|
||||||
./build.py --push-tags --multiarch --both
|
./build.py --push-tags --multiarch
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,7 +1,2 @@
|
|||||||
# IDE
|
# IDE
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
# Python
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
20
CLAUDE.md
20
CLAUDE.md
@@ -11,9 +11,8 @@ This is a Docker image for running a Factorio headless server. It provides autom
|
|||||||
### Key Components
|
### Key Components
|
||||||
|
|
||||||
1. **Docker Image Build System**
|
1. **Docker Image Build System**
|
||||||
- `build.py` - Unified Python script that builds both regular and rootless Docker images from `buildinfo.json`
|
- `build.py` - Python script that builds Docker images from `buildinfo.json`
|
||||||
- `docker/Dockerfile` - Main Dockerfile that creates the Factorio server image
|
- `docker/Dockerfile` - Main Dockerfile that creates the Factorio server image
|
||||||
- `docker/Dockerfile.rootless` - Dockerfile for rootless variant (runs as UID 1000)
|
|
||||||
- `buildinfo.json` - Contains version info, SHA256 checksums, and tags for all supported versions
|
- `buildinfo.json` - Contains version info, SHA256 checksums, and tags for all supported versions
|
||||||
- Supports multi-architecture builds (linux/amd64, linux/arm64) using Docker buildx
|
- Supports multi-architecture builds (linux/amd64, linux/arm64) using Docker buildx
|
||||||
|
|
||||||
@@ -39,20 +38,11 @@ This is a Docker image for running a Factorio headless server. It provides autom
|
|||||||
### Building Images
|
### Building Images
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build regular images locally (single architecture)
|
# Build a single architecture image locally
|
||||||
python3 build.py
|
python3 build.py
|
||||||
|
|
||||||
# Build rootless images only
|
# Build and push multi-architecture images
|
||||||
python3 build.py --rootless
|
|
||||||
|
|
||||||
# Build both regular and rootless images
|
|
||||||
python3 build.py --both
|
|
||||||
|
|
||||||
# Build and push multi-architecture images (regular only)
|
|
||||||
python3 build.py --multiarch --push-tags
|
python3 build.py --multiarch --push-tags
|
||||||
|
|
||||||
# Build and push both regular and rootless multi-architecture images
|
|
||||||
python3 build.py --multiarch --push-tags --both
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running the Container
|
### Running the Container
|
||||||
@@ -119,8 +109,6 @@ Version updates are automated via GitHub Actions that run `update.sh` periodical
|
|||||||
## Testing Changes
|
## Testing Changes
|
||||||
|
|
||||||
1. Modify `buildinfo.json` to test specific versions
|
1. Modify `buildinfo.json` to test specific versions
|
||||||
2. Run `python3 build.py` to build regular images locally
|
2. Run `python3 build.py` to build locally
|
||||||
- Use `python3 build.py --rootless` for rootless images
|
|
||||||
- Use `python3 build.py --both` to build both variants
|
|
||||||
3. Test the container with your local data volume
|
3. Test the container with your local data volume
|
||||||
4. For production changes, ensure `update.sh` handles version transitions correctly
|
4. For production changes, ensure `update.sh` handles version transitions correctly
|
53
README.md
53
README.md
@@ -439,59 +439,6 @@ stream {
|
|||||||
|
|
||||||
If your factorio host uses multiple IP addresses (very common with IPv6), you might additionally need to bind Factorio to a single IP (otherwise the UDP proxy might get confused with IP mismatches). To do that pass the `BIND` envvar to the container: `docker run --network=host -e BIND=2a02:1234::5678 ...`
|
If your factorio host uses multiple IP addresses (very common with IPv6), you might additionally need to bind Factorio to a single IP (otherwise the UDP proxy might get confused with IP mismatches). To do that pass the `BIND` envvar to the container: `docker run --network=host -e BIND=2a02:1234::5678 ...`
|
||||||
|
|
||||||
## Rootless Docker Support (Experimental)
|
|
||||||
|
|
||||||
> **Note**: Rootless support is currently experimental. Please report any issues you encounter.
|
|
||||||
|
|
||||||
If you're experiencing permission issues or want better security, consider using the rootless images. These images are designed to work seamlessly with rootless Docker installations and avoid common permission problems.
|
|
||||||
|
|
||||||
### What are Rootless Images?
|
|
||||||
|
|
||||||
The rootless images differ from regular images in several ways:
|
|
||||||
- Run as UID 1000 (non-root) by default
|
|
||||||
- No dynamic UID/GID mapping (PUID/PGID not supported)
|
|
||||||
- No runtime chown operations
|
|
||||||
- All directories created with open permissions during build
|
|
||||||
|
|
||||||
### Rootless Image Tags
|
|
||||||
|
|
||||||
Each regular tag has a corresponding rootless version with the `-rootless` suffix:
|
|
||||||
- `latest-rootless` (experimental)
|
|
||||||
- `stable-rootless` (experimental)
|
|
||||||
- `2.0.55-rootless` (experimental)
|
|
||||||
|
|
||||||
### Quick Start with Rootless
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run -d \
|
|
||||||
-p 34197:34197/udp \
|
|
||||||
-p 27015:27015/tcp \
|
|
||||||
-v ~/factorio:/factorio \
|
|
||||||
--name factorio \
|
|
||||||
--restart=unless-stopped \
|
|
||||||
factoriotools/factorio:stable-rootless
|
|
||||||
```
|
|
||||||
|
|
||||||
Key differences:
|
|
||||||
- No `chown` command needed
|
|
||||||
- No PUID/PGID environment variables
|
|
||||||
- Runs as UID 1000 by default
|
|
||||||
- No permission issues with volumes
|
|
||||||
|
|
||||||
### When to Use Rootless Images
|
|
||||||
|
|
||||||
Consider using rootless images if you:
|
|
||||||
- Are running Docker in rootless mode
|
|
||||||
- Experience permission issues with volume mounts
|
|
||||||
- Want to avoid containers running as root
|
|
||||||
- Don't need dynamic UID/GID mapping via PUID/PGID
|
|
||||||
|
|
||||||
### Limitations
|
|
||||||
|
|
||||||
- PUID/PGID environment variables are not supported
|
|
||||||
- Fixed to UID 1000 (may not match your host user)
|
|
||||||
- Experimental feature - may have undiscovered issues
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### My server is listed in the server browser, but nobody can connect
|
### My server is listed in the server browser, but nobody can connect
|
||||||
|
94
build.py
94
build.py
@@ -6,7 +6,6 @@ import subprocess
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import argparse
|
|
||||||
|
|
||||||
|
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
@@ -26,9 +25,9 @@ def create_builder(build_dir, builder_name, platform):
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def build_and_push_multiarch(build_dir, build_args, push, builder_suffix=""):
|
def build_and_push_multiarch(build_dir, build_args, push):
|
||||||
builder_name = f"factoriotools{builder_suffix}-multiarch"
|
builder_name = "factoriotools-multiarch"
|
||||||
platform = ",".join(PLATFORMS)
|
platform=",".join(PLATFORMS)
|
||||||
create_builder(build_dir, builder_name, platform)
|
create_builder(build_dir, builder_name, platform)
|
||||||
build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args
|
build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args
|
||||||
if push:
|
if push:
|
||||||
@@ -36,16 +35,16 @@ def build_and_push_multiarch(build_dir, build_args, push, builder_suffix=""):
|
|||||||
try:
|
try:
|
||||||
subprocess.run(build_command, cwd=build_dir, check=True)
|
subprocess.run(build_command, cwd=build_dir, check=True)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
print(f"Build and push of {builder_suffix or 'regular'} image failed")
|
print("Build and push of image failed")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def build_singlearch(build_dir, build_args, image_type="regular"):
|
def build_singlearch(build_dir, build_args):
|
||||||
build_command = ["docker", "build"] + build_args
|
build_command = ["docker", "build"] + build_args
|
||||||
try:
|
try:
|
||||||
subprocess.run(build_command, cwd=build_dir, check=True)
|
subprocess.run(build_command, cwd=build_dir, check=True)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
print(f"Build of {image_type} image failed")
|
print("Build of image failed")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -59,19 +58,16 @@ def push_singlearch(tags):
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def build_and_push(sha256, version, tags, push, multiarch, dockerfile="Dockerfile", builder_suffix=""):
|
def build_and_push(sha256, version, tags, push, multiarch):
|
||||||
build_dir = tempfile.mktemp()
|
build_dir = tempfile.mktemp()
|
||||||
shutil.copytree("docker", build_dir)
|
shutil.copytree("docker", build_dir)
|
||||||
build_args = ["-f", dockerfile, "--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."]
|
build_args = ["--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."]
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
build_args.extend(["-t", f"factoriotools/factorio:{tag}"])
|
build_args.extend(["-t", f"factoriotools/factorio:{tag}"])
|
||||||
|
|
||||||
image_type = "rootless" if "rootless" in dockerfile.lower() else "regular"
|
|
||||||
|
|
||||||
if multiarch:
|
if multiarch:
|
||||||
build_and_push_multiarch(build_dir, build_args, push, builder_suffix)
|
build_and_push_multiarch(build_dir, build_args, push)
|
||||||
else:
|
else:
|
||||||
build_singlearch(build_dir, build_args, image_type)
|
build_singlearch(build_dir, build_args)
|
||||||
if push:
|
if push:
|
||||||
push_singlearch(tags)
|
push_singlearch(tags)
|
||||||
|
|
||||||
@@ -89,69 +85,25 @@ def login():
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def generate_rootless_tags(original_tags):
|
def main(push_tags=False, multiarch=False):
|
||||||
"""Generate rootless-specific tags from original tags"""
|
|
||||||
return [f"{tag}-rootless" for tag in original_tags]
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description='Build Factorio Docker images')
|
|
||||||
parser.add_argument('--push-tags', action='store_true', help='Push images to Docker Hub')
|
|
||||||
parser.add_argument('--multiarch', action='store_true', help='Build multi-architecture images')
|
|
||||||
parser.add_argument('--rootless', action='store_true', help='Build only rootless images')
|
|
||||||
parser.add_argument('--both', action='store_true', help='Build both regular and rootless images')
|
|
||||||
parser.add_argument('--only-stable-latest', action='store_true',
|
|
||||||
help='Build only stable and latest versions (for rootless by default)')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Default behavior: build regular images unless specified otherwise
|
|
||||||
build_regular = not args.rootless or args.both
|
|
||||||
build_rootless = args.rootless or args.both
|
|
||||||
|
|
||||||
with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle:
|
with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle:
|
||||||
builddata = json.load(file_handle)
|
builddata = json.load(file_handle)
|
||||||
|
|
||||||
if args.push_tags:
|
if push_tags:
|
||||||
login()
|
login()
|
||||||
|
|
||||||
# Filter versions if needed
|
|
||||||
versions_to_build = []
|
|
||||||
for version, buildinfo in sorted(builddata.items(), key=lambda item: item[0], reverse=True):
|
for version, buildinfo in sorted(builddata.items(), key=lambda item: item[0], reverse=True):
|
||||||
if args.only_stable_latest or (build_rootless and not build_regular):
|
sha256 = buildinfo["sha256"]
|
||||||
# For rootless-only builds, default to stable/latest only
|
tags = buildinfo["tags"]
|
||||||
if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]:
|
build_and_push(sha256, version, tags, push_tags, multiarch)
|
||||||
versions_to_build.append((version, buildinfo))
|
|
||||||
else:
|
|
||||||
versions_to_build.append((version, buildinfo))
|
|
||||||
|
|
||||||
# Build regular images
|
|
||||||
if build_regular:
|
|
||||||
print("Building regular images...")
|
|
||||||
for version, buildinfo in versions_to_build:
|
|
||||||
sha256 = buildinfo["sha256"]
|
|
||||||
tags = buildinfo["tags"]
|
|
||||||
build_and_push(sha256, version, tags, args.push_tags, args.multiarch)
|
|
||||||
|
|
||||||
# Build rootless images
|
|
||||||
if build_rootless:
|
|
||||||
print("Building rootless images...")
|
|
||||||
# For rootless, only build stable and latest unless building both
|
|
||||||
rootless_versions = []
|
|
||||||
if not build_regular or args.only_stable_latest:
|
|
||||||
for version, buildinfo in builddata.items():
|
|
||||||
if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]:
|
|
||||||
rootless_versions.append((version, buildinfo))
|
|
||||||
else:
|
|
||||||
rootless_versions = versions_to_build
|
|
||||||
|
|
||||||
for version, buildinfo in rootless_versions:
|
|
||||||
sha256 = buildinfo["sha256"]
|
|
||||||
original_tags = buildinfo["tags"]
|
|
||||||
rootless_tags = generate_rootless_tags(original_tags)
|
|
||||||
build_and_push(sha256, version, rootless_tags, args.push_tags, args.multiarch,
|
|
||||||
dockerfile="Dockerfile.rootless", builder_suffix="-rootless")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
push_tags = False
|
||||||
|
multiarch = False
|
||||||
|
for arg in sys.argv[1:]:
|
||||||
|
if arg == "--push-tags":
|
||||||
|
push_tags = True
|
||||||
|
elif arg == "--multiarch":
|
||||||
|
multiarch = True
|
||||||
|
main(push_tags, multiarch)
|
||||||
|
@@ -1,91 +0,0 @@
|
|||||||
# build rcon client
|
|
||||||
FROM debian:stable-slim AS rcon-builder
|
|
||||||
RUN apt-get -q update \
|
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install build-essential --no-install-recommends
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
COPY rcon/ /src
|
|
||||||
RUN make
|
|
||||||
|
|
||||||
# build factorio image
|
|
||||||
FROM debian:stable-slim
|
|
||||||
LABEL maintainer="https://github.com/factoriotools/factorio-docker"
|
|
||||||
|
|
||||||
ARG BOX64_VERSION=v0.2.4
|
|
||||||
|
|
||||||
# optionally utilize a built-in map-gen-preset (see data/base/prototypes/map-gen-presets
|
|
||||||
ARG PRESET
|
|
||||||
|
|
||||||
# number of retries that curl will use when pulling the headless server tarball
|
|
||||||
ARG CURL_RETRIES=8
|
|
||||||
|
|
||||||
ENV PORT=34197 \
|
|
||||||
RCON_PORT=27015 \
|
|
||||||
SAVES=/factorio/saves \
|
|
||||||
PRESET="$PRESET" \
|
|
||||||
CONFIG=/factorio/config \
|
|
||||||
MODS=/factorio/mods \
|
|
||||||
SCENARIOS=/factorio/scenarios \
|
|
||||||
SCRIPTOUTPUT=/factorio/script-output \
|
|
||||||
DLC_SPACE_AGE="true"
|
|
||||||
|
|
||||||
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 --no-install-recommends \
|
|
||||||
&& if [[ "$(uname -m)" == "aarch64" ]]; then \
|
|
||||||
echo "installing ARM compatability layer" \
|
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install unzip --no-install-recommends \
|
|
||||||
&& curl -LO https://github.com/ptitSeb/box64/releases/download/${BOX64_VERSION}/box64-GENERIC_ARM-RelWithDebInfo.zip \
|
|
||||||
&& unzip box64-GENERIC_ARM-RelWithDebInfo.zip -d /bin \
|
|
||||||
&& rm -f box64-GENERIC_ARM-RelWithDebInfo.zip \
|
|
||||||
&& chmod +x /bin/box64; \
|
|
||||||
fi \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# version checksum of the archive to download
|
|
||||||
ARG VERSION
|
|
||||||
ARG SHA256
|
|
||||||
|
|
||||||
LABEL factorio.version=${VERSION}
|
|
||||||
|
|
||||||
ENV VERSION=${VERSION} \
|
|
||||||
SHA256=${SHA256}
|
|
||||||
|
|
||||||
RUN set -ox pipefail \
|
|
||||||
&& if [[ "${VERSION}" == "" ]]; then \
|
|
||||||
echo "build-arg VERSION is required" \
|
|
||||||
&& exit 1; \
|
|
||||||
fi \
|
|
||||||
&& if [[ "${SHA256}" == "" ]]; then \
|
|
||||||
echo "build-arg SHA256 is required" \
|
|
||||||
&& exit 1; \
|
|
||||||
fi \
|
|
||||||
&& archive="/tmp/factorio_headless_x64_$VERSION.tar.xz" \
|
|
||||||
&& mkdir -p /opt /factorio \
|
|
||||||
&& curl -sSL "https://www.factorio.com/get-download/$VERSION/headless/linux64" -o "$archive" --retry $CURL_RETRIES \
|
|
||||||
&& echo "$SHA256 $archive" | sha256sum -c \
|
|
||||||
|| (sha256sum "$archive" && file "$archive" && exit 1) \
|
|
||||||
&& tar xf "$archive" --directory /opt \
|
|
||||||
&& chmod ugo=rwx /opt/factorio \
|
|
||||||
&& rm "$archive" \
|
|
||||||
&& ln -s "$SCENARIOS" /opt/factorio/scenarios \
|
|
||||||
&& ln -s "$SAVES" /opt/factorio/saves \
|
|
||||||
&& mkdir -p /opt/factorio/config/
|
|
||||||
|
|
||||||
COPY files/*.sh /
|
|
||||||
COPY files/docker-entrypoint-rootless.sh /docker-entrypoint.sh
|
|
||||||
COPY files/config.ini /opt/factorio/config/config.ini
|
|
||||||
COPY --from=rcon-builder /src/rcon /bin/rcon
|
|
||||||
|
|
||||||
# Make all scripts executable and set proper permissions for the factorio directory
|
|
||||||
RUN chmod +x /*.sh \
|
|
||||||
&& chmod -R 777 /opt/factorio /factorio
|
|
||||||
|
|
||||||
VOLUME /factorio
|
|
||||||
EXPOSE $PORT/udp $RCON_PORT/tcp
|
|
||||||
|
|
||||||
# Run as non-root user (UID 1000 is common for the first user in rootless containers)
|
|
||||||
USER 1000:1000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
|
@@ -1,124 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -eoux pipefail
|
|
||||||
INSTALLED_DIRECTORY=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
|
|
||||||
FACTORIO_VOL=/factorio
|
|
||||||
LOAD_LATEST_SAVE="${LOAD_LATEST_SAVE:-true}"
|
|
||||||
GENERATE_NEW_SAVE="${GENERATE_NEW_SAVE:-false}"
|
|
||||||
PRESET="${PRESET:-""}"
|
|
||||||
SAVE_NAME="${SAVE_NAME:-""}"
|
|
||||||
BIND="${BIND:-""}"
|
|
||||||
CONSOLE_LOG_LOCATION="${CONSOLE_LOG_LOCATION:-""}"
|
|
||||||
|
|
||||||
# Create directories if they don't exist
|
|
||||||
# In rootless mode, these should be writable by the container user
|
|
||||||
mkdir -p "$FACTORIO_VOL"
|
|
||||||
mkdir -p "$SAVES"
|
|
||||||
mkdir -p "$CONFIG"
|
|
||||||
mkdir -p "$MODS"
|
|
||||||
mkdir -p "$SCENARIOS"
|
|
||||||
mkdir -p "$SCRIPTOUTPUT"
|
|
||||||
|
|
||||||
# Generate RCON password if needed
|
|
||||||
if [[ ! -f $CONFIG/rconpw ]]; then
|
|
||||||
pwgen 15 1 >"$CONFIG/rconpw"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy default configs if they don't exist
|
|
||||||
if [[ ! -f $CONFIG/server-settings.json ]]; then
|
|
||||||
cp /opt/factorio/data/server-settings.example.json "$CONFIG/server-settings.json"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -f $CONFIG/map-gen-settings.json ]]; then
|
|
||||||
cp /opt/factorio/data/map-gen-settings.example.json "$CONFIG/map-gen-settings.json"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -f $CONFIG/map-settings.json ]]; then
|
|
||||||
cp /opt/factorio/data/map-settings.example.json "$CONFIG/map-settings.json"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up incomplete saves
|
|
||||||
NRTMPSAVES=$( find -L "$SAVES" -iname \*.tmp.zip -mindepth 1 | wc -l )
|
|
||||||
if [[ $NRTMPSAVES -gt 0 ]]; then
|
|
||||||
rm -f "$SAVES"/*.tmp.zip
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update mods if requested
|
|
||||||
if [[ ${UPDATE_MODS_ON_START:-} == "true" ]]; then
|
|
||||||
"${INSTALLED_DIRECTORY}"/docker-update-mods.sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Handle DLC
|
|
||||||
"${INSTALLED_DIRECTORY}"/docker-dlc.sh
|
|
||||||
|
|
||||||
# In rootless mode, we don't need to handle user switching or chown
|
|
||||||
# The container runs as the specified user from the start
|
|
||||||
EXEC=""
|
|
||||||
if [[ -f /bin/box64 ]]; then
|
|
||||||
# Use emulator for ARM hosts
|
|
||||||
EXEC="/bin/box64"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update config path
|
|
||||||
sed -i '/write-data=/c\write-data=\/factorio/' /opt/factorio/config/config.ini
|
|
||||||
|
|
||||||
# Generate new save if needed
|
|
||||||
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
|
|
||||||
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"
|
|
||||||
else
|
|
||||||
if [[ -n "$PRESET" ]]; then
|
|
||||||
$EXEC /opt/factorio/bin/x64/factorio \
|
|
||||||
--create "$SAVES/$SAVE_NAME.zip" \
|
|
||||||
--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" \
|
|
||||||
--map-gen-settings "$CONFIG/map-gen-settings.json" \
|
|
||||||
--map-settings "$CONFIG/map-settings.json"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build command flags
|
|
||||||
FLAGS=(\
|
|
||||||
--port "$PORT" \
|
|
||||||
--server-settings "$CONFIG/server-settings.json" \
|
|
||||||
--server-banlist "$CONFIG/server-banlist.json" \
|
|
||||||
--rcon-port "$RCON_PORT" \
|
|
||||||
--server-whitelist "$CONFIG/server-whitelist.json" \
|
|
||||||
--use-server-whitelist \
|
|
||||||
--server-adminlist "$CONFIG/server-adminlist.json" \
|
|
||||||
--rcon-password "$(cat "$CONFIG/rconpw")" \
|
|
||||||
--server-id /factorio/config/server-id.json \
|
|
||||||
--mod-directory "$MODS" \
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ -n "$CONSOLE_LOG_LOCATION" ]; then
|
|
||||||
FLAGS+=( --console-log "$CONSOLE_LOG_LOCATION" )
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$BIND" ]; then
|
|
||||||
FLAGS+=( --bind "$BIND" )
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $LOAD_LATEST_SAVE == true ]]; then
|
|
||||||
FLAGS+=( --start-server-load-latest )
|
|
||||||
else
|
|
||||||
FLAGS+=( --start-server "$SAVE_NAME" )
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Execute factorio
|
|
||||||
# In rootless mode, we run directly without user switching
|
|
||||||
exec $EXEC /opt/factorio/bin/x64/factorio "${FLAGS[@]}" "$@"
|
|
@@ -23,24 +23,33 @@ print_failure()
|
|||||||
echo "$1"
|
echo "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Checks game version vs version in mod.
|
# Checks if the current game version satisfies the mod's minimum required version.
|
||||||
# Returns 0 if major version differs or mod minor version is less than game version, 1 if ok
|
# Returns 1 if the game version is compatible with the mod, 0 if not
|
||||||
check_game_version() {
|
check_game_version() {
|
||||||
local game_version="$1"
|
local mod_required_version="$1" # The minimum Factorio version required by the mod
|
||||||
local mod_version="$2"
|
local current_game_version="$2" # The current Factorio version
|
||||||
|
|
||||||
local game_major mod_major game_minor mod_minor
|
local mod_major mod_minor game_major game_minor
|
||||||
game_major=$(echo "$game_version" | cut -d '.' -f1)
|
mod_major=$(echo "$mod_required_version" | cut -d '.' -f1)
|
||||||
game_minor=$(echo "$game_version" | cut -d '.' -f2)
|
mod_minor=$(echo "$mod_required_version" | cut -d '.' -f2)
|
||||||
mod_major=$(echo "$mod_version" | cut -d '.' -f1)
|
game_major=$(echo "$current_game_version" | cut -d '.' -f1)
|
||||||
mod_minor=$(echo "$mod_version" | cut -d '.' -f2)
|
game_minor=$(echo "$current_game_version" | cut -d '.' -f2)
|
||||||
|
|
||||||
if [[ "$game_major" -ne "$mod_major" ]]; then
|
# If game major version is greater than mod's required major version, it's compatible
|
||||||
|
if [[ "$game_major" -gt "$mod_major" ]]; then
|
||||||
|
echo 1
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If game major version is less than mod's required major version, it's not compatible
|
||||||
|
if [[ "$game_major" -lt "$mod_major" ]]; then
|
||||||
echo 0
|
echo 0
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$mod_minor" -ge "$game_minor" ]]; then
|
# Major versions are equal, check minor versions
|
||||||
|
# Game minor version must be >= mod's required minor version
|
||||||
|
if [[ "$game_minor" -ge "$mod_minor" ]]; then
|
||||||
echo 1
|
echo 1
|
||||||
else
|
else
|
||||||
echo 0
|
echo 0
|
||||||
@@ -79,7 +88,7 @@ check_dependency_version()
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
">")
|
">")
|
||||||
if [[ "$(printf '%s\n%s\n' "$required_version" "$mod_version" | sort -V | head -n1)" == "$required_version" && "$required_version" != "$FACTORIO_VERSION" ]]; then
|
if [[ "$(printf '%s\n%s\n' "$required_version" "$mod_version" | sort -V | head -n1)" == "$required_version" && "$required_version" != "$mod_version" ]]; then
|
||||||
echo 1
|
echo 1
|
||||||
else
|
else
|
||||||
echo 0
|
echo 0
|
||||||
@@ -93,7 +102,7 @@ check_dependency_version()
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"<")
|
"<")
|
||||||
if [[ "$(printf '%s\n%s\n' "$required_version" "$mod_version" | sort -V | tail -n1)" == "$required_version" && "$required_version" != "$FACTORIO_VERSION" ]]; then
|
if [[ "$(printf '%s\n%s\n' "$required_version" "$mod_version" | sort -V | tail -n1)" == "$required_version" && "$required_version" != "$mod_version" ]]; then
|
||||||
echo 1
|
echo 1
|
||||||
else
|
else
|
||||||
echo 0
|
echo 0
|
||||||
@@ -116,11 +125,15 @@ get_mod_info()
|
|||||||
{
|
{
|
||||||
local mod_info_json="$1"
|
local mod_info_json="$1"
|
||||||
|
|
||||||
|
# Process mod releases from newest to oldest, looking for a compatible version
|
||||||
while IFS= read -r mod_release_info; do
|
while IFS= read -r mod_release_info; do
|
||||||
local mod_version mod_factorio_version
|
local mod_version mod_factorio_version
|
||||||
mod_version=$(echo "$mod_release_info" | jq -r ".version")
|
mod_version=$(echo "$mod_release_info" | jq -r ".version")
|
||||||
mod_factorio_version=$(echo "$mod_release_info" | jq -r ".info_json.factorio_version")
|
mod_factorio_version=$(echo "$mod_release_info" | jq -r ".info_json.factorio_version")
|
||||||
|
|
||||||
|
# Check if this mod version is compatible with our Factorio version
|
||||||
|
# This prevents downloading mods that require a newer Factorio version (fixes #468)
|
||||||
|
# and ensures backward compatibility (e.g., Factorio 2.0 can use 1.x mods) (fixes #517)
|
||||||
if [[ $(check_game_version "$mod_factorio_version" "$FACTORIO_VERSION") == 0 ]]; then
|
if [[ $(check_game_version "$mod_factorio_version" "$FACTORIO_VERSION") == 0 ]]; then
|
||||||
echo " Skipping mod version $mod_version because of factorio version mismatch" >&2
|
echo " Skipping mod version $mod_version because of factorio version mismatch" >&2
|
||||||
continue
|
continue
|
||||||
|
Reference in New Issue
Block a user