mirror of
https://github.com/factoriotools/factorio-docker.git
synced 2025-07-03 23:48:05 +02:00
Compare commits
4 Commits
fix-preset
...
feature/ro
Author | SHA1 | Date | |
---|---|---|---|
7e59315e3d | |||
9464758a7b | |||
50f04fb096 | |||
15d31c9a2e |
136
README-ROOTLESS.md
Normal file
136
README-ROOTLESS.md
Normal file
@ -0,0 +1,136 @@
|
||||
# Rootless Docker Support
|
||||
|
||||
This document describes the rootless Docker images for Factorio, which are designed to work better with rootless Docker installations and avoid permission issues.
|
||||
|
||||
## What is Rootless Docker?
|
||||
|
||||
Rootless Docker allows running the Docker daemon and containers as a non-root user, which improves security by eliminating the need for root privileges. However, it introduces complexity with UID/GID mapping that can cause permission issues with volumes.
|
||||
|
||||
## Rootless Image Tags
|
||||
|
||||
For each regular Factorio image tag, there's a corresponding rootless tag with the `-rootless` suffix:
|
||||
|
||||
- `latest` → `latest-rootless`
|
||||
- `stable` → `stable-rootless`
|
||||
- `2.0.55` → `2.0.55-rootless`
|
||||
- etc.
|
||||
|
||||
## Key Differences from Regular Images
|
||||
|
||||
1. **No dynamic UID/GID mapping**: The rootless images run as UID 1000 by default and don't support PUID/PGID environment variables
|
||||
2. **No runtime chown operations**: Eliminates the recursive chown that can cause race conditions
|
||||
3. **Simplified permissions**: All directories are created with open permissions (777) during build
|
||||
4. **USER directive**: The container runs as non-root from the start
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 34197:34197/udp \
|
||||
-p 27015:27015/tcp \
|
||||
-v /opt/factorio:/factorio \
|
||||
--name factorio \
|
||||
factoriotools/factorio:stable-rootless
|
||||
```
|
||||
|
||||
### With Rootless Docker
|
||||
|
||||
If you're running rootless Docker, the container will work out of the box:
|
||||
|
||||
```bash
|
||||
# As your regular user (not root)
|
||||
docker run -d \
|
||||
-p 34197:34197/udp \
|
||||
-p 27015:27015/tcp \
|
||||
-v ~/factorio:/factorio \
|
||||
--name factorio \
|
||||
factoriotools/factorio:stable-rootless
|
||||
```
|
||||
|
||||
### With Regular Docker
|
||||
|
||||
If you're running regular Docker but want to avoid permission issues:
|
||||
|
||||
```bash
|
||||
# Pre-create the volume directory with your user's permissions
|
||||
mkdir -p /opt/factorio
|
||||
sudo chown -R $(id -u):$(id -g) /opt/factorio
|
||||
|
||||
# Run the container
|
||||
docker run -d \
|
||||
--user $(id -u):$(id -g) \
|
||||
-p 34197:34197/udp \
|
||||
-p 27015:27015/tcp \
|
||||
-v /opt/factorio:/factorio \
|
||||
--name factorio \
|
||||
factoriotools/factorio:stable-rootless
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
All the same environment variables from the regular image are supported, except:
|
||||
- `PUID` - Not supported (container runs as UID 1000)
|
||||
- `PGID` - Not supported (container runs as GID 1000)
|
||||
|
||||
## Migrating from Regular Images
|
||||
|
||||
If you're switching from a regular image to a rootless image:
|
||||
|
||||
1. Stop your existing container
|
||||
2. Fix permissions on your volume (one time only):
|
||||
```bash
|
||||
sudo chown -R 1000:1000 /opt/factorio
|
||||
# Or if you want to match your user:
|
||||
sudo chown -R $(id -u):$(id -g) /opt/factorio
|
||||
```
|
||||
3. Start the new rootless container
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Permission Denied Errors
|
||||
|
||||
If you get permission errors, ensure your volume directory is writable by UID 1000 or your user:
|
||||
|
||||
```bash
|
||||
# Check current ownership
|
||||
ls -la /opt/factorio
|
||||
|
||||
# Fix ownership for UID 1000 (default)
|
||||
sudo chown -R 1000:1000 /opt/factorio
|
||||
|
||||
# Or fix for your current user
|
||||
sudo chown -R $(id -u):$(id -g) /opt/factorio
|
||||
```
|
||||
|
||||
### Running as a Different User
|
||||
|
||||
If you need to run as a different UID, override it at runtime:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--user 2000:2000 \
|
||||
-v /opt/factorio:/factorio \
|
||||
factoriotools/factorio:stable-rootless
|
||||
```
|
||||
|
||||
## Building Rootless Images
|
||||
|
||||
To build rootless images locally:
|
||||
|
||||
```bash
|
||||
# Build for current architecture
|
||||
python3 build-rootless.py
|
||||
|
||||
# Build and push multi-arch images
|
||||
python3 build-rootless.py --multiarch --push-tags
|
||||
```
|
||||
|
||||
## Why Use Rootless Images?
|
||||
|
||||
1. **Avoid permission issues**: No more files with unexpected ownership
|
||||
2. **Better security**: Runs as non-root by default
|
||||
3. **Simpler**: No complex permission logic at startup
|
||||
4. **Faster startup**: No recursive chown operations
|
||||
5. **Rootless Docker compatible**: Works seamlessly with rootless Docker installations
|
87
README.md
87
README.md
@ -6,59 +6,9 @@
|
||||
[中文](./README_zh_CN.md)
|
||||
|
||||
<!-- start autogeneration tags -->
|
||||
* `2.0.58`, `latest`
|
||||
* `2.0.57`
|
||||
* `2`, `2.0`, `2.0.55`, `stable`, `stable-2.0.55`
|
||||
* `2.0.54`
|
||||
* `2.0.53`
|
||||
* `2.0.52`
|
||||
* `2.0.51`
|
||||
* `2.0.50`
|
||||
* `2.0.49`
|
||||
* `2.0.48`
|
||||
* `2.0`, `2.0.47`, `stable-2.0.47`
|
||||
* `2.0.46`
|
||||
* `2.0.45`
|
||||
* `2.0.44`
|
||||
* `2.0`, `2.0.43`, `stable-2.0.43`
|
||||
* `2.0`, `2.0.42`, `stable-2.0.42`
|
||||
* `2.0`, `2.0.41`, `stable-2.0.41`
|
||||
* `2.0.40`
|
||||
* `2.0`, `2.0.39`, `stable-2.0.39`
|
||||
* `2.0.38`
|
||||
* `2.0.37`
|
||||
* `2.0.36`
|
||||
* `2.0.35`
|
||||
* `2.0.34`
|
||||
* `2.0.33`
|
||||
* `2.0`, `2.0.32`, `stable-2.0.32`
|
||||
* `2.0.31`
|
||||
* `2.0`, `2.0.30`, `stable-2.0.30`
|
||||
* `2.0.29`
|
||||
* `2.0`, `2.0.28`, `stable-2.0.28`
|
||||
* `2.0.27`
|
||||
* `2.0.26`
|
||||
* `2.0.25`
|
||||
* `2.0.24`
|
||||
* `2.0`, `2.0.23`, `stable-2.0.23`
|
||||
* `2.0.22`
|
||||
* `2.0`, `2.0.21`, `stable-2.0.21`
|
||||
* `2.0`, `2.0.20`, `stable-2.0.20`
|
||||
* `2.0.19`
|
||||
* `2.0.18`
|
||||
* `2.0.17`
|
||||
* `2.0.16`
|
||||
* `2.0`, `2.0.15`, `stable-2.0.15`
|
||||
* `2.0`, `2.0.14`, `stable-2.0.14`
|
||||
* `2.0`, `2.0.13`, `stable-2.0.13`
|
||||
* `1`, `1.1`, `1.1.110`, `stable-1.1.110`
|
||||
* `1.0`, `1.0.0`
|
||||
* `0.17`, `0.17.79`
|
||||
* `0.16`, `0.16.51`
|
||||
* `0.15`, `0.15.40`
|
||||
* `0.14`, `0.14.23`
|
||||
* `0.13`, `0.13.20`
|
||||
* `0.12`, `0.12.35`<!-- end autogeneration tags -->
|
||||
* `latest, 2.0.58`
|
||||
* `2, 2.0, 2.0.55, stable, stable-2.0.55`
|
||||
<!-- end autogeneration tags -->
|
||||
|
||||
## Tag descriptions
|
||||
|
||||
@ -489,6 +439,37 @@ 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
|
||||
|
||||
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.
|
||||
|
||||
### Rootless Image Tags
|
||||
|
||||
Each regular tag has a corresponding rootless version with the `-rootless` suffix:
|
||||
- `latest-rootless`
|
||||
- `stable-rootless`
|
||||
- `2.0.55-rootless`
|
||||
|
||||
### 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
|
||||
|
||||
For more information, see the [Rootless Docker documentation](README-ROOTLESS.md).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### My server is listed in the server browser, but nobody can connect
|
||||
|
128
build-rootless.py
Executable file
128
build-rootless.py
Executable file
@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
PLATFORMS = [
|
||||
"linux/arm64",
|
||||
"linux/amd64",
|
||||
]
|
||||
|
||||
|
||||
def create_builder(build_dir, builder_name, platform):
|
||||
check_exists_command = ["docker", "buildx", "inspect", builder_name]
|
||||
if subprocess.run(check_exists_command, stderr=subprocess.DEVNULL).returncode != 0:
|
||||
create_command = ["docker", "buildx", "create", "--platform", platform, "--name", builder_name]
|
||||
try:
|
||||
subprocess.run(create_command, cwd=build_dir, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
print("Creating builder failed")
|
||||
exit(1)
|
||||
|
||||
|
||||
def build_and_push_multiarch(build_dir, build_args, push):
|
||||
builder_name = "factoriotools-rootless-multiarch"
|
||||
platform=",".join(PLATFORMS)
|
||||
create_builder(build_dir, builder_name, platform)
|
||||
build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args
|
||||
if push:
|
||||
build_command.append("--push")
|
||||
try:
|
||||
subprocess.run(build_command, cwd=build_dir, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
print("Build and push of rootless image failed")
|
||||
exit(1)
|
||||
|
||||
|
||||
def build_singlearch(build_dir, build_args):
|
||||
build_command = ["docker", "build"] + build_args
|
||||
try:
|
||||
subprocess.run(build_command, cwd=build_dir, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
print("Build of rootless image failed")
|
||||
exit(1)
|
||||
|
||||
|
||||
def push_singlearch(tags):
|
||||
for tag in tags:
|
||||
try:
|
||||
subprocess.run(["docker", "push", f"factoriotools/factorio:{tag}"],
|
||||
check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
print("Docker push failed")
|
||||
exit(1)
|
||||
|
||||
|
||||
def build_and_push(sha256, version, tags, push, multiarch):
|
||||
build_dir = tempfile.mktemp()
|
||||
shutil.copytree("docker", build_dir)
|
||||
# Use the rootless Dockerfile
|
||||
build_args = ["-f", "Dockerfile.rootless", "--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."]
|
||||
for tag in tags:
|
||||
build_args.extend(["-t", f"factoriotools/factorio:{tag}"])
|
||||
if multiarch:
|
||||
build_and_push_multiarch(build_dir, build_args, push)
|
||||
else:
|
||||
build_singlearch(build_dir, build_args)
|
||||
if push:
|
||||
push_singlearch(tags)
|
||||
|
||||
|
||||
def login():
|
||||
try:
|
||||
username = os.environ["DOCKER_USERNAME"]
|
||||
password = os.environ["DOCKER_PASSWORD"]
|
||||
subprocess.run(["docker", "login", "-u", username, "-p", password], check=True)
|
||||
except KeyError:
|
||||
print("Username and password need to be given")
|
||||
exit(1)
|
||||
except subprocess.CalledProcessError:
|
||||
print("Docker login failed")
|
||||
exit(1)
|
||||
|
||||
|
||||
def generate_rootless_tags(original_tags):
|
||||
"""Generate rootless-specific tags from original tags"""
|
||||
rootless_tags = []
|
||||
for tag in original_tags:
|
||||
# Add -rootless suffix to each tag
|
||||
rootless_tags.append(f"{tag}-rootless")
|
||||
return rootless_tags
|
||||
|
||||
|
||||
def main(push_tags=False, multiarch=False):
|
||||
with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle:
|
||||
builddata = json.load(file_handle)
|
||||
|
||||
if push_tags:
|
||||
login()
|
||||
|
||||
# Build only the latest stable and experimental versions for rootless
|
||||
versions_to_build = []
|
||||
|
||||
# Find latest stable and experimental versions
|
||||
for version, buildinfo in builddata.items():
|
||||
if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]:
|
||||
versions_to_build.append((version, buildinfo))
|
||||
|
||||
for version, buildinfo in versions_to_build:
|
||||
sha256 = buildinfo["sha256"]
|
||||
original_tags = buildinfo["tags"]
|
||||
rootless_tags = generate_rootless_tags(original_tags)
|
||||
build_and_push(sha256, version, rootless_tags, push_tags, multiarch)
|
||||
|
||||
|
||||
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)
|
93
docker/Dockerfile.rootless
Normal file
93
docker/Dockerfile.rootless
Normal file
@ -0,0 +1,93 @@
|
||||
# build rcon client
|
||||
FROM debian:stable-slim AS rcon-builder
|
||||
RUN apt-get -q update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install build-essential
|
||||
|
||||
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
|
||||
RUN chmod +x /*.sh
|
||||
|
||||
# Set proper permissions for the factorio directory
|
||||
RUN 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 [[ ! -z "$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[@]}" "$@"
|
@ -23,6 +23,128 @@ print_failure()
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
# Checks game version vs version in mod.
|
||||
# Returns 0 if major version differs or mod minor version is less than game version, 1 if ok
|
||||
check_game_version() {
|
||||
local game_version="$1"
|
||||
local mod_version="$2"
|
||||
|
||||
local game_major mod_major game_minor mod_minor
|
||||
game_major=$(echo "$game_version" | cut -d '.' -f1)
|
||||
game_minor=$(echo "$game_version" | cut -d '.' -f2)
|
||||
mod_major=$(echo "$mod_version" | cut -d '.' -f1)
|
||||
mod_minor=$(echo "$mod_version" | cut -d '.' -f2)
|
||||
|
||||
if [[ "$game_major" -ne "$mod_major" ]]; then
|
||||
echo 0
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$mod_minor" -ge "$game_minor" ]]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks dependency string with provided version.
|
||||
# Only checks for operator based string, ignoring everything else
|
||||
# Returns 1 if check is ok, 0 if not
|
||||
check_dependency_version()
|
||||
{
|
||||
local dependency="$1"
|
||||
local mod_version="$2"
|
||||
|
||||
if [[ "$dependency" =~ ^(\?|!|~|\(~\)) ]]; then
|
||||
echo 1
|
||||
fi
|
||||
|
||||
local condition
|
||||
condition=$(echo "$dependency" | grep -oE '(>=|<=|>|<|=) [0-9]+(\.[0-9]+)*')
|
||||
|
||||
if [[ -z "$condition" ]]; then
|
||||
echo 1
|
||||
fi
|
||||
|
||||
local operator required_version
|
||||
operator=$(echo "$condition" | awk '{print $1}')
|
||||
required_version=$(echo "$condition" | awk '{print $2}')
|
||||
|
||||
case "$operator" in
|
||||
">=")
|
||||
if [[ "$(printf '%s\n%s\n' "$required_version" "$mod_version" | sort -V | head -n1)" == "$required_version" ]]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
;;
|
||||
">")
|
||||
if [[ "$(printf '%s\n%s\n' "$required_version" "$mod_version" | sort -V | head -n1)" == "$required_version" && "$required_version" != "$FACTORIO_VERSION" ]]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
;;
|
||||
"<=")
|
||||
if [[ "$(printf '%s\n%s\n' "$required_version" "$mod_version" | sort -V | tail -n1)" == "$required_version" ]]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
;;
|
||||
"<")
|
||||
if [[ "$(printf '%s\n%s\n' "$required_version" "$mod_version" | sort -V | tail -n1)" == "$required_version" && "$required_version" != "$FACTORIO_VERSION" ]]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
;;
|
||||
"=")
|
||||
if [[ "$mod_version" == "$required_version" ]]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_mod_info()
|
||||
{
|
||||
local mod_info_json="$1"
|
||||
|
||||
while IFS= read -r mod_release_info; do
|
||||
local mod_version mod_factorio_version
|
||||
mod_version=$(echo "$mod_release_info" | jq -r ".version")
|
||||
mod_factorio_version=$(echo "$mod_release_info" | jq -r ".info_json.factorio_version")
|
||||
|
||||
if [[ $(check_game_version "$mod_factorio_version" "$FACTORIO_VERSION") == 0 ]]; then
|
||||
echo " Skipping mod version $mod_version because of factorio version mismatch" >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
# If we found 'dependencies' element, we also check versions there
|
||||
if [[ $(echo "$mod_release_info" | jq -e '.info_json | has("dependencies") and (.dependencies | length > 0)') == true ]]; then
|
||||
while IFS= read -r dependency; do
|
||||
|
||||
# We only check for 'base' dependency
|
||||
if [[ "$dependency" == base* ]] && [[ $(check_dependency_version "$dependency" "$FACTORIO_VERSION") == 0 ]]; then
|
||||
echo " Skipping mod version $mod_version, unsatisfied base dependency: $dependency" >&2
|
||||
continue 2
|
||||
fi
|
||||
|
||||
done < <(echo "$mod_release_info" | jq -r '.info_json.dependencies[]')
|
||||
fi
|
||||
|
||||
echo "$mod_release_info" | jq -j ".file_name, \";\", .download_url, \";\", .sha1"
|
||||
break
|
||||
|
||||
done < <(echo "$mod_info_json" | jq -c ".releases|sort_by(.released_at)|reverse|.[]")
|
||||
}
|
||||
|
||||
update_mod()
|
||||
{
|
||||
MOD_NAME="$1"
|
||||
@ -30,7 +152,7 @@ update_mod()
|
||||
|
||||
print_step "Checking for update of mod $MOD_NAME for factorio $FACTORIO_VERSION ..."
|
||||
|
||||
MOD_INFO_URL="$MOD_BASE_URL/api/mods/$MOD_NAME_ENCODED"
|
||||
MOD_INFO_URL="$MOD_BASE_URL/api/mods/$MOD_NAME_ENCODED/full"
|
||||
MOD_INFO_JSON=$(curl --silent "$MOD_INFO_URL")
|
||||
|
||||
if ! echo "$MOD_INFO_JSON" | jq -e .name >/dev/null; then
|
||||
@ -38,7 +160,12 @@ update_mod()
|
||||
return 0
|
||||
fi
|
||||
|
||||
MOD_INFO=$(echo "$MOD_INFO_JSON" | jq -j --arg version "$FACTORIO_VERSION" ".releases|reverse|map(select(.info_json.factorio_version as \$mod_version | \$version | startswith(\$mod_version)))[0]|.file_name, \";\", .download_url, \";\", .sha1")
|
||||
MOD_INFO=$(get_mod_info "$MOD_INFO_JSON")
|
||||
|
||||
if [[ "$MOD_INFO" == "" ]]; then
|
||||
print_failure " Not compatible with version"
|
||||
return 0
|
||||
fi
|
||||
|
||||
MOD_FILENAME=$(echo "$MOD_INFO" | cut -f1 -d";")
|
||||
MOD_URL=$(echo "$MOD_INFO" | cut -f2 -d";")
|
||||
@ -90,7 +217,7 @@ update_mod()
|
||||
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
|
||||
update_mod "$mod"
|
||||
update_mod "$mod" || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
62
update.sh
62
update.sh
@ -91,10 +91,64 @@ if [[ $experimental_online_version != "$stable_online_version" ]]; then
|
||||
fi
|
||||
rm -f -- "$tmpfile"
|
||||
|
||||
readme_tags=$(jq --sort-keys 'keys[]' buildinfo.json | tac | (while read -r line
|
||||
do
|
||||
tags="$tags\n* "$(jq --sort-keys ".$line.tags | sort | .[]" buildinfo.json | sed 's/"/`/g' | sed ':a; /$/N; s/\n/, /; ta')
|
||||
done && printf "%s\n\n" "$tags"))
|
||||
# Generate README tags with logical sorting and de-duplication
|
||||
# First, collect all unique tags with their versions
|
||||
declare -A 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
|
||||
if [[ -n "${tag_versions[$tag]}" ]]; then
|
||||
# Compare version strings - keep the higher one
|
||||
if [[ "$version" > "${tag_versions[$tag]}" ]]; then
|
||||
tag_versions[$tag]="$version"
|
||||
fi
|
||||
else
|
||||
tag_versions[$tag]="$version"
|
||||
fi
|
||||
done < <(jq -r ".\"$version\".tags[]" buildinfo.json)
|
||||
done < <(jq -r 'keys[]' buildinfo.json | sort -V -r)
|
||||
|
||||
# Build the tags list for README
|
||||
readme_tags=""
|
||||
# First add the current latest and stable tags
|
||||
latest_version=$(jq -r 'to_entries | map(select(.value.tags | contains(["latest"]))) | .[0].key' buildinfo.json)
|
||||
stable_version=$(jq -r 'to_entries | map(select(.value.tags | index("stable"))) | .[0].key' buildinfo.json)
|
||||
|
||||
if [[ -n "$latest_version" ]]; then
|
||||
latest_tags=$(jq -r ".\"$latest_version\".tags | map(select(. == \"latest\" or . == \"$latest_version\")) | join(\", \")" buildinfo.json | sed 's/"/`/g')
|
||||
readme_tags="${readme_tags}\n* \`${latest_tags}\`"
|
||||
fi
|
||||
|
||||
if [[ -n "$stable_version" ]] && [[ "$stable_version" != "$latest_version" ]]; then
|
||||
stable_tags=$(jq -r ".\"$stable_version\".tags | sort | join(\", \")" buildinfo.json | sed 's/"/`/g')
|
||||
readme_tags="${readme_tags}\n* \`${stable_tags}\`"
|
||||
fi
|
||||
|
||||
# Add major.minor tags (e.g., 2.0, 1.1) - only the latest version for each
|
||||
declare -A major_minor_seen
|
||||
while IFS= read -r version; do
|
||||
if [[ "$version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
major="${BASH_REMATCH[1]}"
|
||||
minor="${BASH_REMATCH[2]}"
|
||||
major_minor="$major.$minor"
|
||||
|
||||
# Skip if this is the latest or stable version (already added above)
|
||||
if [[ "$version" == "$latest_version" ]] || [[ "$version" == "$stable_version" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Only add if we haven't seen this major.minor yet
|
||||
if [[ -z "${major_minor_seen[$major_minor]}" ]]; then
|
||||
major_minor_seen[$major_minor]=1
|
||||
tags=$(jq -r ".\"$version\".tags | join(\", \")" buildinfo.json | sed 's/"/`/g')
|
||||
if [[ -n "$tags" ]]; then
|
||||
readme_tags="${readme_tags}\n* \`${tags}\`"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done < <(jq -r 'keys[]' buildinfo.json | sort -V -r)
|
||||
|
||||
readme_tags="${readme_tags}\n"
|
||||
|
||||
perl -i -0777 -pe "s/<!-- start autogeneration tags -->.+<!-- end autogeneration tags -->/<!-- start autogeneration tags -->$readme_tags<!-- end autogeneration tags -->/s" README.md
|
||||
|
||||
|
Reference in New Issue
Block a user