From 98d3e475d3703063f2077f18214adda3fcfae268 Mon Sep 17 00:00:00 2001 From: Mark Crossen Date: Sat, 2 Mar 2024 20:09:41 -0700 Subject: [PATCH] add support for ARM (#485) * build and run on arm64 * multiarch docker build * add ARM64 warning to README.md --- .github/workflows/docker.yml | 4 +- .github/workflows/docker_push.yml | 4 +- README.md | 3 ++ build.py | 87 ++++++++++++++++++++++++------- docker/Dockerfile | 11 +++- docker/files/docker-entrypoint.sh | 14 +++-- 6 files changed, 95 insertions(+), 28 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1ccb1e8..cf17644 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,9 +12,11 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: build env: DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} run: | - ./build.py + ./build.py --multiarch diff --git a/.github/workflows/docker_push.yml b/.github/workflows/docker_push.yml index 4eb8d47..cdbd67c 100644 --- a/.github/workflows/docker_push.yml +++ b/.github/workflows/docker_push.yml @@ -11,10 +11,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: build and push if: ${{ env.DOCKER_USERNAME != '' && env.DOCKER_PASSWORD != '' }} env: DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} run: | - ./build.py --push-tags + ./build.py --push-tags --multiarch diff --git a/README.md b/README.md index 62a7f06..0aba44a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Factorio [![Docker Version](https://img.shields.io/docker/v/factoriotools/factorio?sort=semver)](https://hub.docker.com/r/factoriotools/factorio/) [![Docker Pulls](https://img.shields.io/docker/pulls/factoriotools/factorio.svg?maxAge=600)](https://hub.docker.com/r/factoriotools/factorio/) [![Docker Stars](https://img.shields.io/docker/stars/factoriotools/factorio.svg?maxAge=600)](https://hub.docker.com/r/factoriotools/factorio/) +> [!NOTE] +> Support for ARM is experimental. Expect crashes and lag if you try to run this on a raspberry pi. + [中文](./README_zh_CN.md) diff --git a/build.py b/build.py index 8ed1f77..e4b25a5 100755 --- a/build.py +++ b/build.py @@ -7,14 +7,40 @@ import shutil import sys import tempfile -def build_dockerfile(sha256, version, tags): - build_dir = tempfile.mktemp() - shutil.copytree("docker", build_dir) - build_command = ["docker", "build", "--build-arg", f"VERSION={version}", - "--build-arg", f"SHA256={sha256}", "."] - for tag in tags: - build_command.extend(["-t", f"factoriotools/factorio:{tag}"]) +PLATFORMS = [ + "linux/arm64", + "linux/amd64", +] + + +def create_builder(build_dir, builder_name, platform): + check_exists_command = ["docker", "buildx", "inspect", builder_name] + if subprocess.run(check_exists_command, stderr=subprocess.DEVNULL).returncode != 0: + create_command = ["docker", "buildx", "create", "--platform", platform, "--name", builder_name] + try: + subprocess.run(create_command, cwd=build_dir, check=True) + except subprocess.CalledProcessError: + print("Creating builder failed") + exit(1) + + +def build_and_push_multiarch(build_dir, build_args, push): + builder_name = "factoriotools-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: + build_command.append("--push") + try: + subprocess.run(build_command, cwd=build_dir, check=True) + except subprocess.CalledProcessError: + print("Build and push of image failed") + exit(1) + + +def build_singlearch(build_dir, build_args): + build_command = ["docker", "build"] + build_args try: subprocess.run(build_command, cwd=build_dir, check=True) except subprocess.CalledProcessError: @@ -22,6 +48,30 @@ def build_dockerfile(sha256, version, tags): exit(1) +def push_singlearch(tags): + for tag in tags: + try: + subprocess.run(["docker", "push", f"factoriotools/factorio:{tag}"], + check=True) + except subprocess.CalledProcessError: + print("Docker push failed") + exit(1) + + +def build_and_push(sha256, version, tags, push, multiarch): + build_dir = tempfile.mktemp() + shutil.copytree("docker", build_dir) + build_args = ["--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."] + for tag in tags: + build_args.extend(["-t", f"factoriotools/factorio:{tag}"]) + if multiarch: + build_and_push_multiarch(build_dir, build_args, push) + else: + build_singlearch(build_dir, build_args) + if push: + push_singlearch(tags) + + def login(): try: username = os.environ["DOCKER_USERNAME"] @@ -35,7 +85,7 @@ def login(): exit(1) -def main(push_tags=False): +def main(push_tags=False, multiarch=False): with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle: builddata = json.load(file_handle) @@ -45,18 +95,15 @@ def main(push_tags=False): for version, buildinfo in builddata.items(): sha256 = buildinfo["sha256"] tags = buildinfo["tags"] - build_dockerfile(sha256, version, tags) - if not push_tags: - continue - for tag in tags: - try: - subprocess.run(["docker", "push", f"factoriotools/factorio:{tag}"], - check=True) - except subprocess.CalledProcessError: - print("Docker push failed") - exit(1) + build_and_push(sha256, version, tags, push_tags, multiarch) if __name__ == '__main__': - push_tags = len(sys.argv) > 1 and sys.argv[1] == "--push-tags" - main(push_tags) + 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) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1b227f4..771e670 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,6 +6,7 @@ ARG USER=factorio ARG GROUP=factorio ARG PUID=845 ARG PGID=845 +ARG BOX64_VERSION=v0.2.4 # version checksum of the archive to download ARG VERSION @@ -40,13 +41,21 @@ RUN set -ox pipefail \ && mkdir -p /opt /factorio \ && apt-get -q update \ && DEBIAN_FRONTEND=noninteractive apt-get -qy install ca-certificates curl jq pwgen xz-utils --no-install-recommends \ - && rm -rf /var/lib/apt/lists/* \ && 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" \ + && 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/* \ && ln -s "$SCENARIOS" /opt/factorio/scenarios \ && ln -s "$SAVES" /opt/factorio/saves \ && mkdir -p /opt/factorio/config/ \ diff --git a/docker/files/docker-entrypoint.sh b/docker/files/docker-entrypoint.sh index 0fa6f6a..bf18fb8 100755 --- a/docker/files/docker-entrypoint.sh +++ b/docker/files/docker-entrypoint.sh @@ -43,6 +43,7 @@ if [[ ${UPDATE_MODS_ON_START:-} == "true" ]]; then ./docker-update-mods.sh fi +EXEC="" if [[ $(id -u) = 0 ]]; then # Update the User and Group ID based on the PUID/PGID variables usermod -o -u "$PUID" factorio @@ -50,9 +51,12 @@ if [[ $(id -u) = 0 ]]; then # Take ownership of factorio data if running as root chown -R factorio:factorio "$FACTORIO_VOL" # Drop to the factorio user - SU_EXEC="runuser -u factorio -g factorio --" -else - SU_EXEC="" + EXEC="runuser -u factorio -g factorio --" +fi +if [[ -f /bin/box64 ]]; then + # Use an emulator to run on ARM hosts + # this only gets installed when the target docker platform is linux/arm64 + EXEC="$EXEC /bin/box64" fi sed -i '/write-data=/c\write-data=\/factorio/' /opt/factorio/config/config.ini @@ -71,7 +75,7 @@ if [[ $GENERATE_NEW_SAVE == true ]]; then if [[ -f "$SAVES/$SAVE_NAME.zip" ]]; then echo "Map $SAVES/$SAVE_NAME.zip already exists, skipping map generation" else - $SU_EXEC /opt/factorio/bin/x64/factorio \ + $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" @@ -105,4 +109,4 @@ else fi # shellcheck disable=SC2086 -exec $SU_EXEC /opt/factorio/bin/x64/factorio "${FLAGS[@]}" "$@" +exec $EXEC /opt/factorio/bin/x64/factorio "${FLAGS[@]}" "$@"