mirror of
https://github.com/factoriotools/factorio-docker.git
synced 2025-07-12 12:05:21 +02:00
Compare commits
8 Commits
fix-517-mo
...
add-permis
Author | SHA1 | Date | |
---|---|---|---|
b4073aebac | |||
e8adbf55c1 | |||
533789470f | |||
5b6e0cde8b | |||
00038b5184 | |||
72c3590cd6 | |||
23942e3117 | |||
15d38ea739 |
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
|
408
PERMISSION_ISSUES_GUIDE.md
Normal file
408
PERMISSION_ISSUES_GUIDE.md
Normal 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!
|
79
README.md
79
README.md
@ -6,8 +6,17 @@
|
||||
[中文](./README_zh_CN.md)
|
||||
|
||||
<!-- start autogeneration tags -->
|
||||
* `latest, 2.0.58`
|
||||
* `latest, 2.0.60`
|
||||
* `2, 2.0, 2.0.55, stable, stable-2.0.55`
|
||||
* `2.0.59`
|
||||
* `stable-1.1.110, 1, 1.1, 1.1.110`
|
||||
* `1.0.0, 1.0`
|
||||
* `0.17.79, 0.17`
|
||||
* `0.16.51, 0.16`
|
||||
* `0.15.40, 0.15`
|
||||
* `0.14.23, 0.14`
|
||||
* `0.13.20, 0.13`
|
||||
* `0.12.35, 0.12`
|
||||
<!-- end autogeneration tags -->
|
||||
|
||||
## Tag descriptions
|
||||
@ -175,6 +184,8 @@ Copy mods into the mods folder and restart the server.
|
||||
|
||||
As of 0.17 a new environment variable was added ``UPDATE_MODS_ON_START`` which if set to ``true`` will cause the mods get to updated on server start. If set a valid [Factorio Username and Token](https://www.factorio.com/profile) must be supplied or else the server will not start. They can either be set as docker secrets, environment variables, or pulled from the server-settings.json file.
|
||||
|
||||
**Note:** When using the Space Age DLC, the built-in mods (`elevated-rails`, `quality`, and `space-age`) are automatically skipped during mod updates to prevent conflicts. These mods are included with the DLC and should not be downloaded separately.
|
||||
|
||||
### Scenarios
|
||||
|
||||
If you want to launch a scenario from a clean start (not from a saved map) you'll need to start the docker image from an alternate entrypoint. To do this, use the example entrypoint file stored in the /factorio/entrypoints directory in the volume, and launch the image with the following syntax. Note that this is the normal syntax with the addition of the --entrypoint setting AND the additional argument at the end, which is the name of the Scenario in the Scenarios folder.
|
||||
@ -439,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.
|
||||
|
90
build.py
90
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):
|
||||
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, push_tags, multiarch)
|
||||
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()
|
354
buildinfo.json
354
buildinfo.json
@ -1,363 +1,19 @@
|
||||
{
|
||||
"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.55"
|
||||
]
|
||||
},
|
||||
"2.0.57": {
|
||||
"sha256": "22b232afb77067c68a3afe087be6a0ee760479262598a12a709e1b03ea9508a6",
|
||||
"tags": [
|
||||
"2.0.57"
|
||||
]
|
||||
},
|
||||
"2.0.58": {
|
||||
"sha256": "be82e1aeba4169420e1b00c12a3e00ec2309a41327f9d6c335feec27bbc885e6",
|
||||
"2.0.60": {
|
||||
"sha256": "69b5be1a867fd99524f9914dfee900a1ac386cf4e74c4a63768c05dc4d2b2b0b",
|
||||
"tags": [
|
||||
"latest",
|
||||
"2.0.58"
|
||||
"2.0.60"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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 \
|
||||
|
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[@]}" "$@"
|
@ -227,9 +227,12 @@ update_mod()
|
||||
return 0
|
||||
}
|
||||
|
||||
# Process all enabled mods from mod-list.json, but skip built-in mods
|
||||
# The Space Age DLC includes built-in mods (elevated-rails, quality, space-age) that should not be downloaded
|
||||
if [[ -f $MOD_DIR/mod-list.json ]]; then
|
||||
jq -r ".mods|map(select(.enabled))|.[].name" "$MOD_DIR/mod-list.json" | while read -r mod; do
|
||||
if [[ $mod != base ]]; then
|
||||
# Skip base mod and DLC built-in mods
|
||||
if [[ $mod != base ]] && [[ $mod != elevated-rails ]] && [[ $mod != quality ]] && [[ $mod != space-age ]]; then
|
||||
update_mod "$mod" || true
|
||||
fi
|
||||
done
|
||||
|
43
update.sh
43
update.sh
@ -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"
|
||||
# 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"
|
||||
|
||||
# Update tag by latest
|
||||
cp buildinfo.json "$tmpfile"
|
||||
if [[ $experimental_online_version != "$stable_online_version" ]]; then
|
||||
# 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
|
||||
|
Reference in New Issue
Block a user