mirror of
https://github.com/factoriotools/factorio-docker.git
synced 2025-07-12 12:05:21 +02:00
* 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>
157 lines
5.8 KiB
Python
Executable File
157 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import json
|
|
import subprocess
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import argparse
|
|
|
|
|
|
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_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:
|
|
build_command.append("--push")
|
|
try:
|
|
subprocess.run(build_command, cwd=build_dir, check=True)
|
|
except subprocess.CalledProcessError:
|
|
print(f"Build and push of {builder_suffix or 'regular'} image failed")
|
|
exit(1)
|
|
|
|
|
|
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(f"Build of {image_type} image failed")
|
|
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, dockerfile="Dockerfile", builder_suffix=""):
|
|
build_dir = tempfile.mktemp()
|
|
shutil.copytree("docker", build_dir)
|
|
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, builder_suffix)
|
|
else:
|
|
build_singlearch(build_dir, build_args, image_type)
|
|
if push:
|
|
push_singlearch(tags)
|
|
|
|
|
|
def login():
|
|
try:
|
|
username = os.environ["DOCKER_USERNAME"]
|
|
password = os.environ["DOCKER_PASSWORD"]
|
|
subprocess.run(["docker", "login", "-u", username, "-p", password], check=True)
|
|
except KeyError:
|
|
print("Username and password need to be given")
|
|
exit(1)
|
|
except subprocess.CalledProcessError:
|
|
print("Docker login failed")
|
|
exit(1)
|
|
|
|
|
|
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 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):
|
|
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__':
|
|
main() |