Compare commits

...

4 Commits

Author SHA1 Message Date
b4073aebac Add comprehensive permission issues guide
- Created PERMISSION_ISSUES_GUIDE.md with detailed solutions for permission problems
- Based on thorough analysis of issues #558, #556, #555, #549, #496, #501, #492, #420
- Includes platform-specific solutions (NixOS, macOS, Windows, Synology)
- Documents critical Docker version requirements (20.x+ recommended)
- Highlights rootless image as recommended solution for permission issues
- Added link to guide in README.md troubleshooting section

Key findings documented:
- Most "Operation not permitted" errors resolved by updating Docker
- Rootless Docker requires using actual Docker UID (e.g., 100999) not 845
- Interrupted chown operations cause inconsistent file ownership
- Rootless images avoid most permission issues entirely

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-12 14:13:38 +09:00
e8adbf55c1 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>
2025-07-12 13:46:33 +09:00
533789470f Fix buildinfo.json pollution by only keeping latest versions (#579)
Previously, update.sh would accumulate all historical versions in buildinfo.json,
leading to an ever-growing file. This change modifies the script to rebuild
buildinfo.json from scratch with only the current stable and experimental versions.

Changes:
- Replace incremental update logic with complete rebuild
- Create fresh buildinfo.json containing only latest versions
- Preserve all proper tags (stable, latest, version numbers, etc.)
- Fix bash compatibility issue with associative arrays

This ensures buildinfo.json remains clean and only contains the versions
currently being built and published.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-12 12:35:59 +09:00
5b6e0cde8b Add file package to Docker image for better debugging (#578)
The Docker builds have been failing since June 2025 with the error:
'/bin/bash: line 1: file: command not found'

This error occurs when the SHA256 checksum verification fails and the
Dockerfile attempts to run 'file' command to help debug what type of
file was downloaded. However, the 'file' package is not installed in
the debian:stable-slim base image.

This commit adds the 'file' package to the apt-get install list so that
when SHA256 verification fails, we can get better debugging information
about what was actually downloaded (e.g., whether it's an HTML error
page instead of the expected tar.xz file).

While this doesn't fix the root cause of the SHA256 failures, it will
provide better diagnostics to help identify the actual issue.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Took 55 minutes
2025-07-12 12:21:42 +09:00
11 changed files with 808 additions and 411 deletions

View File

@ -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
View File

@ -1,2 +1,7 @@
# IDE
.idea
# Python
__pycache__/
*.py[cod]
*$py.class

View File

@ -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

408
PERMISSION_ISSUES_GUIDE.md Normal file
View File

@ -0,0 +1,408 @@
# Factorio Docker Permission Issues - Solutions and Workarounds
This document provides comprehensive solutions and workarounds for permission-related issues in the Factorio Docker container, based on detailed analysis of issues #558, #556, #555, #549, #496, #501, #492, and #420.
## Table of Contents
- [Root Cause Analysis](#root-cause-analysis)
- [Critical Prerequisites](#critical-prerequisites)
- [General Solutions](#general-solutions)
- [Platform-Specific Issues](#platform-specific-issues)
- [Docker System Requirements](#docker-system-requirements)
- [Advanced Troubleshooting](#advanced-troubleshooting)
- [Known Issues and Limitations](#known-issues-and-limitations)
## Root Cause Analysis
Based on detailed investigation by maintainer @Fank and community reports, the permission issues stem from:
1. **Container Architecture Issues**:
- No `USER` directive in Dockerfile despite creating a factorio user
- Container starts as root and performs recursive `chown` on every start
- The recursive `chown -R factorio:factorio /factorio` can be interrupted, leaving inconsistent permissions
- Dynamic UID/GID mapping using PUID/PGID environment variables adds complexity
2. **Rootless Docker Complications**:
- UID namespace remapping (e.g., container UID 845 → host UID 100844)
- Rootless Docker daemons cannot change ownership of bind-mounted volumes
- Different rootless implementations use different UID mappings
3. **Host System Dependencies**:
- Older Docker versions (especially pre-20.x) have permission handling bugs
- Some kernel versions have issues with user namespace operations
- SELinux and AppArmor can interfere with volume permissions
## Critical Prerequisites
### Update Your System First!
Many permission issues are caused by outdated system components:
```bash
# For Ubuntu/Debian
sudo apt-get update
sudo apt-get upgrade
# Specifically update Docker to 27.x or newer
# Follow: https://docs.docker.com/engine/install/ubuntu/#install-docker-engine
```
**Important**: Multiple users reported that updating Docker resolved their "Operation not permitted" errors.
## General Solutions
### Solution A: Pre-create Directories with Correct Permissions
```bash
# Create the directory structure
sudo mkdir -p /opt/factorio/{saves,mods,config,scenarios,script-output}
# Set ownership to factorio user (845:845)
sudo chown -R 845:845 /opt/factorio
# Set appropriate permissions (note the 'u+rwx' for write access)
sudo chmod -R u+rwx /opt/factorio
```
### Solution B: Use the Rootless Docker Image (Recommended)
The project now provides a rootless variant that runs as UID 1000, which avoids most permission issues:
```bash
docker run -d \
-p 34197:34197/udp \
-p 27015:27015/tcp \
-v /opt/factorio:/factorio \
--name factorio \
factoriotools/factorio:latest-rootless
```
**Benefits of rootless images**:
- No `chown` operations on startup
- No need to pre-create directories with specific permissions
- Works seamlessly with rootless Docker installations
- Avoids the recursive permission changes that can be interrupted
**Available rootless tags**:
- `latest-rootless`
- `stable-rootless`
- `2.0.55-rootless` (or any specific version with `-rootless` suffix)
## Platform-Specific Issues and Solutions
### NixOS with Rootless Docker
**Problem**: Permission denied errors when creating directories, even after setting ownership to 845:845. Files show ownership by UID 100844 instead of 845.
**Solutions**:
1. **Find and use your actual rootless Docker user ID**:
```bash
# Method 1: Check your user ID
id -u
# Method 2: Check existing Docker volumes for the UID Docker is using
ls -lan /path/to/other/docker/volumes
# Common rootless Docker UIDs:
# - 100999 (NixOS default)
# - 100844 (as reported in issue #558)
# - 1000 (some configurations)
# Apply the correct ownership
sudo chown -R 100999:100999 ./factorio
```
2. **Configure NixOS Docker properly**:
```nix
# In configuration.nix
virtualisation.docker.rootless = {
enable = true;
setSocketVariable = true;
};
```
3. **Port Mapping Issues**: Rootless Docker on NixOS has issues with userland-proxy that can cause random port assignments. Consider using host networking if possible.
### macOS with Colima
**Problem**: `copy_file` permission denied errors, even with correct ownership. Permission errors when running docker-dlc.sh.
**Solutions**:
1. **Set broader permissions before mounting**:
```bash
# Create directory structure
mkdir -p ./factorio-server/{saves,mods,config,scenarios}
# Set ownership AND permissions
sudo chown -R 845:845 ./factorio-server
sudo chmod -R 775 ./factorio-server
```
2. **Use Docker Desktop instead of Colima** if the issues persist, as it has better macOS integration
3. **Specify PUID/PGID explicitly**:
```yaml
environment:
- PUID=502 # Common macOS user ID
- PGID=20 # Common macOS staff group
```
### Windows
**Problem**: Cannot remove temporary locale files due to Windows-Linux permission translation. Errors like "Permission denied trying to remove /factorio/temp/currently-playing/locale/de".
**Solutions**:
1. **Use WSL2 backend** for Docker Desktop (required for proper Linux filesystem semantics)
2. **Store volumes in WSL2 filesystem** instead of Windows filesystem:
```bash
# Inside WSL2 terminal
mkdir -p ~/factorio
chmod -R 777 ~/factorio
```
```yaml
# docker-compose.yml - use WSL2 path
volumes:
- ~/factorio:/factorio
```
3. **Avoid Windows drive mounts** (like `W:\docker\factorio`) as they have inherent permission translation issues
4. **Add :Z flag for SELinux context** (some Windows Docker setups benefit from this):
```yaml
volumes:
- ~/factorio:/factorio:Z
```
### Synology NAS
**Problem**: Permission denied when accessing mounted volumes. Error: "filesystem error: status: Permission denied [/factorio/saves]".
**Solutions**:
1. **Create and set permissions via SSH**:
```bash
# SSH into Synology
sudo mkdir -p /volume1/docker/factorio
sudo chown -R 845:845 /volume1/docker/factorio
sudo chmod -R u+rwx /volume1/docker/factorio # Important: u+rwx for write access
```
2. **Use the correct volume path in your container**:
```bash
docker run -d \
-p 34197:34197/udp \
-p 27015:27015/tcp \
-v /volume1/docker/factorio:/factorio \
--name factorio \
--restart=always \
factoriotools/factorio
```
3. **Check DSM Docker permissions** - ensure the Docker package has proper permissions to the shared folder
## Docker System Requirements
### Minimum Docker Version
Based on community reports, these Docker versions are known to work:
- **Docker 27.4.1** - Confirmed working
- **Docker 20.x+** - Generally stable
- **Docker 19.x and below** - Known permission issues
**Check your Docker version**:
```bash
docker --version
# If below 20.x, update immediately!
```
### "Operation not permitted" at Util.cpp:81
This specific error is often caused by:
1. **Outdated Docker version** - Update Docker first!
2. **Outdated kernel** - Run system updates
3. **Missing kernel capabilities** - Check Docker daemon configuration
## Docker Compose Best Practices
### Basic Configuration
```yaml
version: '3'
services:
factorio:
image: factoriotools/factorio:stable
container_name: factorio
ports:
- "34197:34197/udp"
- "27015:27015/tcp"
volumes:
- ./factorio:/factorio
restart: unless-stopped
stdin_open: true # For interactive console
tty: true
```
### Advanced Configuration for Permission Issues
```yaml
version: '3'
services:
factorio:
image: factoriotools/factorio:stable
container_name: factorio
ports:
- "34197:34197/udp"
- "27015:27015/tcp"
volumes:
- ./factorio:/factorio:Z # :Z for SELinux systems
restart: unless-stopped
# user: "845:845" # WARNING: This might break the entrypoint script
environment:
- PUID=845
- PGID=845
- UPDATE_MODS_ON_START=false # Disable if having permission issues
```
### Rootless Docker Configuration
```yaml
version: '3'
services:
factorio:
image: factoriotools/factorio:latest-rootless
container_name: factorio
ports:
- "34197:34197/udp"
- "27015:27015/tcp"
volumes:
- ./factorio:/factorio
restart: unless-stopped
environment:
- PUID=1000 # Rootless default
- PGID=1000
```
## Advanced Troubleshooting
### Step-by-Step Diagnosis
1. **Check Current Ownership**:
```bash
ls -lan ./factorio
# Look for UIDs like 845, 1000, 100844, 100999
```
2. **Verify Docker User Mapping**:
```bash
# Check what user the container is running as
docker exec factorio id
# Check file ownership inside container
docker exec factorio ls -lan /factorio
```
3. **Test Without Volume Mount** (isolates host permission issues):
```bash
docker run --rm -it factoriotools/factorio:stable
# If this works, the issue is with your host volume permissions
```
4. **Check Security Modules**:
```bash
# SELinux (Fedora, RHEL, CentOS)
getenforce
# If "Enforcing", try adding :Z to volume mount
# AppArmor (Ubuntu, Debian)
sudo apparmor_status | grep docker
```
5. **Debug the Entrypoint Script**:
```bash
# Run with debug output
docker run --rm -it \
-e DEBUG=true \
-v ./factorio:/factorio \
factoriotools/factorio:stable
```
### Common Error Messages and Solutions
| Error | Cause | Solution |
|-------|-------|----------|
| `Util.cpp:81: Operation not permitted` | Outdated Docker/kernel | Update Docker and system packages |
| `chown: Operation not permitted` | Rootless Docker | Use rootless Docker UID for ownership |
| `Permission denied [/factorio/saves]` | Wrong directory permissions | `chmod -R u+rwx` on host directory |
| `Couldn't create lock file /factorio/.lock` | Container can't write to volume | Check volume mount and permissions |
| `Map version X cannot be loaded` | Version mismatch | Use correct Docker image version |
## Known Issues and Limitations
### Interrupted chown Operations
The container performs `chown -R factorio:factorio /factorio` on every start. If the container is killed during this operation:
- Files will have inconsistent ownership
- Some files owned by 845, others by different UIDs
- Solution: Let the container complete startup before stopping
### Rootless Docker Port Mapping
**Issue #496**: Rootless Docker with userland-proxy causes random port assignments instead of the configured 34197.
- **Workaround**: Use host networking mode if possible
- **Note**: This is a Docker limitation, not specific to this image
### Map Version Compatibility
**Problem**: "Map version 2.0.23-0 cannot be loaded because it is higher than the game version".
**Solution**:
```bash
# Use a version that matches or exceeds your save
docker pull factoriotools/factorio:2.0.23
# Or always use latest for newest features
docker pull factoriotools/factorio:latest
```
## Recommended Approach
### For New Installations
1. **Update your system first** - Many issues are caused by old Docker versions
2. **Try the rootless image first** - It avoids most permission issues entirely
3. **Pre-create directories** with correct permissions if using the standard image
4. **Test without volumes** first to ensure the image works
### For Existing Installations with Issues
1. **Stop the container** and let it shut down cleanly
2. **Backup your data** before making changes
3. **Check Docker version** - update if below 20.x
4. **Fix permissions** using the platform-specific solution
5. **Consider rootless variant** for easier permission management
### Best Practices
- **Let the container start fully** before stopping (avoid interrupted chown)
- **Use named volumes** instead of bind mounts when possible
- **Monitor first startup** to ensure permissions are set correctly
- **Keep Docker updated** to avoid known bugs
## Community Solutions
### Proposed Improvements (from @Fank)
1. **Add USER directive** in Dockerfile after creating directories
2. **Optimize chown logic** to only run when ownership is wrong
3. **Implement fixuid** for better UID/GID mapping
4. **Add health checks** to ensure permissions are correct before starting
### Alternative Images
Some users have tried other Factorio Docker images (e.g., goofball222/factorio) but report the same Util.cpp:81 errors, suggesting this is a broader ecosystem issue related to Docker versions and system configurations.
## Quick Reference
| Platform | Common UID | Recommended Approach |
|----------|-----------|---------------------|
| Standard Docker | 845 | Update Docker, use `chown 845:845` |
| Rootless Docker (NixOS) | 100999, 100844 | Find actual UID, chown to that |
| macOS (Docker Desktop) | 502 (user), 20 (staff) | Use PUID/PGID env vars |
| Windows | N/A | Use WSL2 filesystem |
| Synology NAS | varies | Check DSM user, ensure Docker has folder access |
## Getting Help
If these solutions don't work:
1. **Update everything first** (Docker, kernel, system packages)
2. **Provide full details** when reporting issues:
- Docker version (`docker --version`)
- OS and version
- Full error messages
- Output of `ls -lan` on your volume
3. **Try the rootless image** as an alternative
4. **Check issue #558** for ongoing discussions
Remember: The vast majority of permission issues are resolved by updating Docker to version 20.x or newer!

View File

@ -450,8 +450,74 @@ 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
### Permission Issues
If you're experiencing permission errors such as:
- `chown: Operation not permitted`
- `Permission denied [/factorio/saves]`
- `Util.cpp:81: Operation not permitted`
- Files owned by unexpected UIDs (like 100844 instead of 845)
Please refer to our comprehensive [Permission Issues Guide](./PERMISSION_ISSUES_GUIDE.md) for detailed solutions. Common fixes include:
- **Updating Docker** to version 20.x or newer (this resolves many issues)
- **Using the rootless image** variants (e.g., `factoriotools/factorio:stable-rootless`)
- **Setting correct ownership** for your specific Docker configuration
### My server is listed in the server browser, but nobody can connect
Check the logs. If there is the line `Own address is RIGHT IP:WRONG PORT`, then this could be caused by the Docker proxy. If the the IP and port is correct it's probably a port forwarding or firewall issue instead.

View File

@ -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()

View File

@ -1,368 +1,12 @@
{
"0.12.35": {
"sha256": "ab9cf01a56dde3073aaaa5152c628bbf9a5bb85638b87dc3d7fdb77fb169aedd",
"tags": [
"0.12.35",
"0.12"
]
},
"0.13.20": {
"sha256": "cbf5481e4b7e0efcc07c7b6a1fc3ff1404ad5597f3c9d37914a52ffb58d7c159",
"tags": [
"0.13.20",
"0.13"
]
},
"0.14.23": {
"sha256": "96c3e7acd4e0f066a499baba01823cac7c1caf0e50dbddcea5793f57bd60dc8c",
"tags": [
"0.14.23",
"0.14"
]
},
"0.15.40": {
"sha256": "1041ef61ea4aecd1f425e6030a909f0c349a9c01d1b3324d84a61b1cfef5ba6c",
"tags": [
"0.15.40",
"0.15"
]
},
"0.16.51": {
"sha256": "6cb09f5ac87f16f8d5b43cef26c0ae26cc46a57a0382e253dfda032dc5bb367f",
"tags": [
"0.16.51",
"0.16"
]
},
"0.17.79": {
"sha256": "9ace12fa986df028dc1851bf4de2cb038044d743e98823bc1c48ba21aa4d23df",
"tags": [
"0.17.79",
"0.17"
]
},
"1.0.0": {
"sha256": "81d9e1aa94435aeec4131c8869fa6e9331726bea1ea31db750b65ba42dbd1464",
"tags": [
"1.0.0",
"1.0"
]
},
"1.1.110": {
"sha256": "485fe6db36e5decd7dd0d70e7c97e61f818100fa3e48d87884b287027c7a646a",
"tags": [
"stable-1.1.110",
"1",
"1.1",
"1.1.110"
]
},
"2.0.13": {
"sha256": "27b36901a39e593adf28418c0286142c6c7a9f83d156963c7369bd405a25c7d1",
"tags": [
"stable-2.0.13",
"2.0",
"2.0.13"
]
},
"2.0.14": {
"sha256": "5a4bc4c3b2a97ed1fc58eb796321e848dcc64435bd91013dd9c78a14a8ce8815",
"tags": [
"stable-2.0.14",
"2.0",
"2.0.14"
]
},
"2.0.15": {
"sha256": "70b441cb807811a60586c01107248c1d8d7ae043bd1f23675fc924fbaaa538d8",
"tags": [
"stable-2.0.15",
"2.0",
"2.0.15"
]
},
"2.0.16": {
"sha256": "f2069b4b746500d945eeb67ef7eda5e7aebe7fd0294c2af4e117af22a3bbaea3",
"tags": [
"2.0.16"
]
},
"2.0.17": {
"sha256": "183407f2fb21e05152442ffb5f15ffb283994339ca6a51b3559a257c30505e5e",
"tags": [
"2.0.17"
]
},
"2.0.18": {
"sha256": "f378a1dc8a545c13d8ca616cbe72d245aa3ce93e3f219d8d60d3c06c7df82dc0",
"tags": [
"2.0.18"
]
},
"2.0.19": {
"sha256": "2e27aca3a7f65b50916d14a62203b6861cbe657e8d2dbd8f813e0a606efce9c7",
"tags": [
"2.0.19"
]
},
"2.0.20": {
"sha256": "c4a901f2f1dbedbb41654560db4c6fab683a30c20334e805d4ef740c0416515a",
"tags": [
"stable-2.0.20",
"2.0",
"2.0.20"
]
},
"2.0.21": {
"sha256": "1d6d2785006d6a8d9d5fdcdaa7097a189ec35ba95f3521025dc4e046f7a1398e",
"tags": [
"stable-2.0.21",
"2.0",
"2.0.21"
]
},
"2.0.22": {
"sha256": "14c3eea7600fbe7f35bca52fe4c277e8f5e23b34c35ebebaa46c6752c750cb85",
"tags": [
"2.0.22"
]
},
"2.0.23": {
"sha256": "e819fc9ad6df061bf9d4bffc91988dd18d0e3982c8b1c22c0525d78bda3ef216",
"tags": [
"stable-2.0.23",
"2.0",
"2.0.23"
]
},
"2.0.24": {
"sha256": "4644acc4195391fe19a7468c546d10a494ce1a188964c79f20cb0fa050b67120",
"tags": [
"2.0.24"
]
},
"2.0.25": {
"sha256": "0d1698f1f29759ff27faa6a5d9c3804377cb1767f2692003a8e9d4c294845e5a",
"tags": [
"2.0.25"
]
},
"2.0.26": {
"sha256": "a401024039372a53b9a29b7deb4ac279cd9a34abe69426a109a13a9a1c599f1f",
"tags": [
"2.0.26"
]
},
"2.0.27": {
"sha256": "63c75ce74cd9d1e4b65ae9f98e9865abdbe3d600fb3259dcda5ea69a512b2993",
"tags": [
"2.0.27"
]
},
"2.0.28": {
"sha256": "ea9937b6adc7a18e17a4e1e64992ec389407497b36e68280bb14fcdd4c884dd3",
"tags": [
"stable-2.0.28",
"2.0",
"2.0.28"
]
},
"2.0.29": {
"sha256": "54088c9cacfddbce2e7bf90604fea095ff723e70d9bb056e1fb469b900a50f09",
"tags": [
"2.0.29"
]
},
"2.0.30": {
"sha256": "4137824a20e1f3298410432c85e62d0eb46b0dab1a8411c233699f890d4c1668",
"tags": [
"stable-2.0.30",
"2.0",
"2.0.30"
]
},
"2.0.31": {
"sha256": "0ee39ff6181ef41b606b7ba1ab5c04d8f81579ef56ec4947e4d74ce5d192b5d5",
"tags": [
"2.0.31"
]
},
"2.0.32": {
"sha256": "2a6102ae42dcc5e8fe923bd68bcd326a569e35912acde121301e5d4d2d856417",
"tags": [
"stable-2.0.32",
"2.0",
"2.0.32"
]
},
"2.0.33": {
"sha256": "9365a34d1724e5c9f592cc9da511485e2fa7da1c12df08029bce478586ba4b7b",
"tags": [
"2.0.33"
]
},
"2.0.34": {
"sha256": "9511462203ebb2763f9f8623bb17f3070041ae3cbd7d80284c1e9bb38c09fc40",
"tags": [
"2.0.34"
]
},
"2.0.35": {
"sha256": "31cd58eaf4b06cc0dc5d82640f7adf2366aa9da64133d2c228f1308f1060a990",
"tags": [
"2.0.35"
]
},
"2.0.36": {
"sha256": "e94567b986654f1f7c3ec5c8bd151e3768b4ab9ab9cc389f6b9fd8e0dab32ce2",
"tags": [
"2.0.36"
]
},
"2.0.37": {
"sha256": "5f105131fe4f48d47fd813f57b6bd275840a47b21e39b30d22bf5da30075a786",
"tags": [
"2.0.37"
]
},
"2.0.38": {
"sha256": "ad9650f7456aecc8adb5369eedb418507c7643bede0da60fc1a239878d4902de",
"tags": [
"2.0.38"
]
},
"2.0.39": {
"sha256": "0f8a3d0e43797b5ff4d8b85d7c334b095a3f07d9aa7f80b1e87f94939a93df34",
"tags": [
"stable-2.0.39",
"2.0",
"2.0.39"
]
},
"2.0.40": {
"sha256": "eac1f24afb68acbfcf1d72d2ad142e8584d77f2d100a3af743f106e50ac176d3",
"tags": [
"2.0.40"
]
},
"2.0.41": {
"sha256": "77ebccae8167fc1a9fc4da8c11e8410f6017b92b1a0913eb58ac5285c9eec399",
"tags": [
"stable-2.0.41",
"2.0",
"2.0.41"
]
},
"2.0.42": {
"sha256": "b5b8b8bdc915e67dbc1710cd3d6aa6802d397b7c0f47db07da8acf39d5bd6376",
"tags": [
"stable-2.0.42",
"2.0",
"2.0.42"
]
},
"2.0.43": {
"sha256": "bde6e167330c4439ce7df3ac519ea445120258ef676f1f6ad31d0c2816d3aee3",
"tags": [
"stable-2.0.43",
"2.0",
"2.0.43"
]
},
"2.0.44": {
"sha256": "9468c5e07080c01eb7a734036160bf806d62cafc11465a23150cfbd210e1036d",
"tags": [
"2.0.44"
]
},
"2.0.45": {
"sha256": "4fd7e04bb3ea7d12da8e1c3befc6b53b3c0064775c960a5a9db6a943f2259fc2",
"tags": [
"2.0.45"
]
},
"2.0.46": {
"sha256": "fc611b6d4078b5d9448284c2890f7e0b6b1f203d52f622c655d3600982489c3e",
"tags": [
"2.0.46"
]
},
"2.0.47": {
"sha256": "f0f320c77616a4794227eb637a70b557108f3141a4633276593220a768f49a26",
"tags": [
"stable-2.0.47",
"2.0",
"2.0.47"
]
},
"2.0.48": {
"sha256": "f0038835e96bbacc19d52d22d47469882d9ebe41a4e5213c0471020647a1ee2d",
"tags": [
"2.0.48"
]
},
"2.0.49": {
"sha256": "ef0648ca1ba44c145a3a3e4c174ccd276eb4a335155a20df1ae0e47156fa34ff",
"tags": [
"2.0.49"
]
},
"2.0.50": {
"sha256": "81d4aec735473c5bd2c87f09abcd793c31cb9a07d9fdf3c3d7275c78ebe4bc18",
"tags": [
"2.0.50"
]
},
"2.0.51": {
"sha256": "fc940dea67d25d3fd403531520e8afda2779ff1fa8050f535ac1351b7873a070",
"tags": [
"2.0.51"
]
},
"2.0.52": {
"sha256": "be8d6216890089890693d6d94f141f745d35c53e52c6b942f6c944f5c00c8c26",
"tags": [
"2.0.52"
]
},
"2.0.53": {
"sha256": "40a57076f80dbee0238dab62f16585def06f7d7e5b41f6b677be41b4d2cae811",
"tags": [
"2.0.53"
]
},
"2.0.54": {
"sha256": "ad47c541b70763552bcf597202ee84aaac727d0ba158873134dc163a3a0506f0",
"tags": [
"2.0.54"
]
},
"2.0.55": {
"sha256": "ef12a54d1556ae1f84ff99edc23706d13b7ad41f1c02d74ca1dfadf9448fcbae",
"tags": [
"stable",
"stable-2.0.55",
"2",
"2.0",
"2.0.55",
"stable"
]
},
"2.0.57": {
"sha256": "22b232afb77067c68a3afe087be6a0ee760479262598a12a709e1b03ea9508a6",
"tags": [
"2.0.57"
]
},
"2.0.58": {
"sha256": "be82e1aeba4169420e1b00c12a3e00ec2309a41327f9d6c335feec27bbc885e6",
"tags": [
"2.0.58"
]
},
"2.0.59": {
"sha256": "fdc467bf80e3611d6dd08c79492228ffec53f3fe914f24d793cac254d9353ff7",
"tags": [
"2.0.59"
"2.0.55"
]
},
"2.0.60": {

View File

@ -43,7 +43,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 --no-install-recommends \
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install ca-certificates curl jq pwgen xz-utils procps gettext-base file --no-install-recommends \
&& if [[ "$(uname -m)" == "aarch64" ]]; then \
echo "installing ARM compatability layer" \
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install unzip --no-install-recommends \

View 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"]

View 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[@]}" "$@"

View File

@ -59,41 +59,40 @@ latestCurrentVersionShort=$latestCurrentVersionMajor.$latestCurrentVersionMinor
echo "stableOnlineVersionShort=${stableOnlineVersionShort} experimentalOnlineVersionShort=${experimentalOnlineVersionShort}"
echo "stableCurrentVersionShort=${stableCurrentVersionShort} latestCurrentVersionShort=${latestCurrentVersionShort}"
# Create new buildinfo.json with only current versions
tmpfile=$(mktemp)
# Remove stable tag
cp buildinfo.json "$tmpfile"
jq --arg stable_current_version "$stable_current_version" 'with_entries(if .key == $stable_current_version then .value.tags |= . - ["stable"] else . end)' "$tmpfile" > buildinfo.json
rm -f -- "$tmpfile"
# Start with empty JSON object
echo '{}' > "$tmpfile"
# Remove latest tag
cp buildinfo.json "$tmpfile"
jq --arg latest_current_version "$latest_current_version" 'with_entries(if .key == $latest_current_version then .value.tags |= . - ["latest"] else . end)' "$tmpfile" > buildinfo.json
rm -f -- "$tmpfile"
# Update tag by stable
cp buildinfo.json "$tmpfile"
if [[ "$stable_online_version" == "$stable_current_version" ]]; then
jq --arg stable_current_version "$stable_current_version" --arg stable_online_version "$stable_online_version" --arg sha256 "$stable_sha256" 'with_entries(if .key == $stable_current_version then .key |= $stable_online_version | .value.sha256 |= $sha256 | .value.tags |= . - [$stable_current_version] + [$stable_online_version, "stable"] else . end)' "$tmpfile" > buildinfo.json
# Add stable version
if [[ "$stable_online_version" == "$experimental_online_version" ]]; then
# Stable and experimental are the same version
jq --arg stable_online_version "$stable_online_version" --arg sha256 "$stable_sha256" --arg stableOnlineVersionShort "$stableOnlineVersionShort" --arg stableOnlineVersionMajor "$stableOnlineVersionMajor" \
'. + {($stable_online_version): {sha256: $sha256, tags: ["latest", "stable", ("stable-" + $stable_online_version), $stableOnlineVersionMajor, $stableOnlineVersionShort, $stable_online_version]}}' "$tmpfile" > buildinfo.json
else
jq --arg stable_current_version "$stable_current_version" --arg stable_online_version "$stable_online_version" --arg sha256 "$stable_sha256" --arg stableOnlineVersionShort "$stableOnlineVersionShort" --arg stableOnlineVersionMajor "$stableOnlineVersionMajor" 'with_entries(if .key == $stable_current_version then .value.tags |= . - ["latest","stable",$stableOnlineVersionMajor] else . end) | to_entries | . + [{ key: $stable_online_version, value: { sha256: $sha256, tags: ["latest","stable",("stable-" + $stable_online_version),$stableOnlineVersionMajor,$stableOnlineVersionShort,$stable_online_version]}}] | from_entries' "$tmpfile" > buildinfo.json
fi
rm -f -- "$tmpfile"
# Update tag by latest
cp buildinfo.json "$tmpfile"
if [[ $experimental_online_version != "$stable_online_version" ]]; then
# Different stable and experimental versions
# First add stable
jq --arg stable_online_version "$stable_online_version" --arg sha256 "$stable_sha256" --arg stableOnlineVersionShort "$stableOnlineVersionShort" --arg stableOnlineVersionMajor "$stableOnlineVersionMajor" \
'. + {($stable_online_version): {sha256: $sha256, tags: ["stable", ("stable-" + $stable_online_version), $stableOnlineVersionMajor, $stableOnlineVersionShort, $stable_online_version]}}' "$tmpfile" > buildinfo.json.tmp
mv buildinfo.json.tmp "$tmpfile"
# Then add experimental
if [[ $stableOnlineVersionShort == "$experimentalOnlineVersionShort" ]]; then
jq --arg experimental_online_version "$experimental_online_version" --arg stable_online_version "$stable_online_version" --arg sha256 "$experimental_sha256" 'with_entries(if .key == $stable_online_version then .value.tags |= . - ["latest"] else . end) | to_entries | . + [{ key: $experimental_online_version, value: { sha256: $sha256, tags: ["latest", $experimental_online_version]}}] | from_entries' "$tmpfile" > buildinfo.json
jq --arg experimental_online_version "$experimental_online_version" --arg sha256 "$experimental_sha256" \
'. + {($experimental_online_version): {sha256: $sha256, tags: ["latest", $experimental_online_version]}}' "$tmpfile" > buildinfo.json
else
jq --arg experimental_online_version "$experimental_online_version" --arg stable_online_version "$stable_online_version" --arg sha256 "$experimental_sha256" --arg experimentalOnlineVersionShort "$experimentalOnlineVersionShort" --arg experimentalOnlineVersionMajor "$experimentalOnlineVersionMajor" 'with_entries(if .key == $stable_online_version then .value.tags |= . - ["latest"] else . end) | to_entries | . + [{ key: $experimental_online_version, value: { sha256: $sha256, tags: ["latest",$experimentalOnlineVersionMajor,$experimentalOnlineVersionShort,$experimental_online_version]}}] | from_entries' "$tmpfile" > buildinfo.json
jq --arg experimental_online_version "$experimental_online_version" --arg sha256 "$experimental_sha256" --arg experimentalOnlineVersionShort "$experimentalOnlineVersionShort" --arg experimentalOnlineVersionMajor "$experimentalOnlineVersionMajor" \
'. + {($experimental_online_version): {sha256: $sha256, tags: ["latest", $experimentalOnlineVersionMajor, $experimentalOnlineVersionShort, $experimental_online_version]}}' "$tmpfile" > buildinfo.json
fi
fi
rm -f -- "$tmpfile"
# Generate README tags with logical sorting and de-duplication
# First, collect all unique tags with their versions
declare -A tag_versions
# Use regular arrays for bash compatibility
declare tag_versions
while IFS= read -r version; do
while IFS= read -r tag; do
# If this tag is already seen, compare versions to keep the latest