mirror of
				https://github.com/factoriotools/factorio-docker.git
				synced 2025-10-31 00:48:07 +01:00 
			
		
		
		
	feat: Add rootless Docker support (#574)
* feat: Add rootless Docker support Implements #547 - Add support for rootless Docker images to avoid permission issues. Key changes: - Add Dockerfile.rootless that runs as UID 1000 by default - Create simplified entrypoint script without chown operations - Add build-rootless.py to build rootless variants with -rootless suffix - Document rootless usage in README-ROOTLESS.md - Update main README with rootless section The rootless images eliminate common permission problems by: - Running as non-root from the start (USER 1000:1000) - Avoiding recursive chown operations that can cause race conditions - Using open permissions (777) on directories during build - Not supporting PUID/PGID environment variables This provides a cleaner solution for rootless Docker users and those experiencing permission issues with volumes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Address linting issues in rootless Docker implementation - Add --no-install-recommends to apt-get install in Dockerfile - Consolidate consecutive RUN instructions in Dockerfile - Fix shellcheck warnings: quote variables and use -n instead of \! -z - These changes improve best practices without affecting functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: Add rootless image building to CI pipeline - Update docker-build.yml workflow to build rootless variants - Rootless images are built after regular images with -rootless suffix - Both use the same multi-architecture build process - Triggered automatically when buildinfo.json changes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: Unify build system for regular and rootless images - Create build-unified.py that handles both regular and rootless builds - Convert build.py and build-rootless.py to wrapper scripts for backwards compatibility - Update CI workflow to use unified build command - Add BUILD_MIGRATION.md documentation - Eliminate code duplication between build scripts - Support flexible build options: --rootless, --both, --only-stable-latest This maintains all existing functionality while providing a cleaner, more maintainable build system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: Add Python cache to .gitignore and remove from repo - Add __pycache__/ and Python compiled files to .gitignore - Remove accidentally committed __pycache__ directory - Prevent future Python cache files from being tracked 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: Replace build system with unified solution - Remove old build.py and build-rootless.py wrapper scripts - Rename build-unified.py to build.py as the main build script - Delete BUILD_MIGRATION.md (no longer needed) - Update CI workflow to use new build.py syntax - Update documentation in CLAUDE.md and README-ROOTLESS.md The new build system provides all functionality in a single script: - Default: builds regular images - --rootless: builds only rootless images - --both: builds both regular and rootless images - --multiarch and --push-tags: work as before This creates a cleaner, more maintainable build system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Consolidate rootless documentation and mark as experimental - Remove separate README-ROOTLESS.md file - Integrate rootless documentation into main README.md - Mark rootless support as experimental - Add clear documentation about limitations and use cases - Include warning about experimental nature This consolidates all documentation in one place and makes it clear that rootless support is still experimental. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										4
									
								
								.github/workflows/docker-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/docker-build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -20,10 +20,10 @@ jobs: | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v3 | ||||
|  | ||||
|       - name: build and push | ||||
|       - name: build and push all images | ||||
|         if: ${{ env.DOCKER_USERNAME != '' && env.DOCKER_PASSWORD != '' }} | ||||
|         env: | ||||
|           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||
|           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | ||||
|         run: | | ||||
|           ./build.py --push-tags --multiarch | ||||
|           ./build.py --push-tags --multiarch --both | ||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,7 @@ | ||||
| # IDE | ||||
| .idea | ||||
|  | ||||
| # Python | ||||
| __pycache__/ | ||||
| *.py[cod] | ||||
| *$py.class | ||||
|   | ||||
							
								
								
									
										20
									
								
								CLAUDE.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								CLAUDE.md
									
									
									
									
									
								
							| @@ -11,8 +11,9 @@ This is a Docker image for running a Factorio headless server. It provides autom | ||||
| ### Key Components | ||||
|  | ||||
| 1. **Docker Image Build System** | ||||
|    - `build.py` - Python script that builds Docker images from `buildinfo.json` | ||||
|    - `build.py` - Unified Python script that builds both regular and rootless Docker images from `buildinfo.json` | ||||
|    - `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 | ||||
|    - Supports multi-architecture builds (linux/amd64, linux/arm64) using Docker buildx | ||||
|  | ||||
| @@ -38,11 +39,20 @@ This is a Docker image for running a Factorio headless server. It provides autom | ||||
| ### Building Images | ||||
|  | ||||
| ```bash | ||||
| # Build a single architecture image locally | ||||
| # Build regular images locally (single architecture) | ||||
| python3 build.py | ||||
|  | ||||
| # Build and push multi-architecture images | ||||
| # Build rootless images only | ||||
| 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 | ||||
|  | ||||
| # Build and push both regular and rootless multi-architecture images | ||||
| python3 build.py --multiarch --push-tags --both | ||||
| ``` | ||||
|  | ||||
| ### Running the Container | ||||
| @@ -109,6 +119,8 @@ Version updates are automated via GitHub Actions that run `update.sh` periodical | ||||
| ## Testing Changes | ||||
|  | ||||
| 1. Modify `buildinfo.json` to test specific versions | ||||
| 2. Run `python3 build.py` to build locally | ||||
| 2. Run `python3 build.py` to build regular images 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 | ||||
| 4. For production changes, ensure `update.sh` handles version transitions correctly | ||||
							
								
								
									
										53
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								README.md
									
									
									
									
									
								
							| @@ -450,6 +450,59 @@ 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 ...` | ||||
|  | ||||
| ## 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 | ||||
|  | ||||
| ### My server is listed in the server browser, but nobody can connect | ||||
|   | ||||
							
								
								
									
										94
									
								
								build.py
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								build.py
									
									
									
									
									
								
							| @@ -6,6 +6,7 @@ import subprocess | ||||
| import shutil | ||||
| import sys | ||||
| import tempfile | ||||
| import argparse | ||||
|  | ||||
|  | ||||
| PLATFORMS = [ | ||||
| @@ -25,9 +26,9 @@ def create_builder(build_dir, builder_name, platform): | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| def build_and_push_multiarch(build_dir, build_args, push): | ||||
|     builder_name = "factoriotools-multiarch" | ||||
|     platform=",".join(PLATFORMS) | ||||
| def build_and_push_multiarch(build_dir, build_args, push, builder_suffix=""): | ||||
|     builder_name = f"factoriotools{builder_suffix}-multiarch" | ||||
|     platform = ",".join(PLATFORMS) | ||||
|     create_builder(build_dir, builder_name, platform) | ||||
|     build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args | ||||
|     if push: | ||||
| @@ -35,16 +36,16 @@ def build_and_push_multiarch(build_dir, build_args, push): | ||||
|     try: | ||||
|         subprocess.run(build_command, cwd=build_dir, check=True) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Build and push of image failed") | ||||
|         print(f"Build and push of {builder_suffix or 'regular'} image failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def build_singlearch(build_dir, build_args): | ||||
| def build_singlearch(build_dir, build_args, image_type="regular"): | ||||
|     build_command = ["docker", "build"] + build_args | ||||
|     try: | ||||
|         subprocess.run(build_command, cwd=build_dir, check=True) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Build of image failed") | ||||
|         print(f"Build of {image_type} image failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| @@ -58,16 +59,19 @@ def push_singlearch(tags): | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| def build_and_push(sha256, version, tags, push, multiarch): | ||||
| def build_and_push(sha256, version, tags, push, multiarch, dockerfile="Dockerfile", builder_suffix=""): | ||||
|     build_dir = tempfile.mktemp() | ||||
|     shutil.copytree("docker", build_dir) | ||||
|     build_args = ["--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."] | ||||
|     build_args = ["-f", dockerfile, "--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."] | ||||
|     for tag in tags: | ||||
|         build_args.extend(["-t", f"factoriotools/factorio:{tag}"]) | ||||
|      | ||||
|     image_type = "rootless" if "rootless" in dockerfile.lower() else "regular" | ||||
|      | ||||
|     if multiarch: | ||||
|         build_and_push_multiarch(build_dir, build_args, push) | ||||
|         build_and_push_multiarch(build_dir, build_args, push, builder_suffix) | ||||
|     else: | ||||
|         build_singlearch(build_dir, build_args) | ||||
|         build_singlearch(build_dir, build_args, image_type) | ||||
|         if push: | ||||
|             push_singlearch(tags) | ||||
|  | ||||
| @@ -85,25 +89,69 @@ def login(): | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def main(push_tags=False, multiarch=False): | ||||
| def generate_rootless_tags(original_tags): | ||||
|     """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: | ||||
|         builddata = json.load(file_handle) | ||||
|  | ||||
|     if push_tags: | ||||
|     if args.push_tags: | ||||
|         login() | ||||
|  | ||||
|     # Filter versions if needed | ||||
|     versions_to_build = [] | ||||
|     for version, buildinfo in sorted(builddata.items(), key=lambda item: item[0], reverse=True): | ||||
|         sha256 = buildinfo["sha256"] | ||||
|         tags = buildinfo["tags"] | ||||
|         build_and_push(sha256, version, tags, push_tags, multiarch) | ||||
|         if args.only_stable_latest or (build_rootless and not build_regular): | ||||
|             # For rootless-only builds, default to stable/latest only | ||||
|             if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]: | ||||
|                 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__': | ||||
|     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) | ||||
|     main() | ||||
							
								
								
									
										91
									
								
								docker/Dockerfile.rootless
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								docker/Dockerfile.rootless
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| # 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"] | ||||
							
								
								
									
										124
									
								
								docker/files/docker-entrypoint-rootless.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										124
									
								
								docker/files/docker-entrypoint-rootless.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| #!/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[@]}" "$@" | ||||
		Reference in New Issue
	
	Block a user