├── .github ├── dependabot.yml └── workflows │ ├── docker-build.yml │ ├── docker-description.yml │ ├── lint.yml │ └── update.yml ├── .gitignore ├── .hadolint.yaml ├── LICENSE ├── README.md ├── README_zh_CN.md ├── build.py ├── build_legacy.sh ├── buildinfo.json ├── docker-compose.yml ├── docker ├── Dockerfile ├── docker-compose.yml ├── files │ ├── config.ini │ ├── docker-dlc.sh │ ├── docker-entrypoint.sh │ ├── docker-update-mods.sh │ ├── players-online.sh │ ├── scenario.sh │ ├── scenario2map.sh │ └── update-mods.sh └── rcon │ ├── Makefile │ └── main.c ├── lint.sh └── update.sh /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: docker 9 | directory: "/docker" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | name: Docker build & push 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - buildinfo.json 9 | # workaround for #526 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up QEMU 21 | uses: docker/setup-qemu-action@v3 22 | 23 | - name: build and push 24 | if: ${{ env.DOCKER_USERNAME != '' && env.DOCKER_PASSWORD != '' }} 25 | env: 26 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 27 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 28 | run: | 29 | ./build.py --push-tags --multiarch -------------------------------------------------------------------------------- /.github/workflows/docker-description.yml: -------------------------------------------------------------------------------- 1 | name: Docker Hub Description 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - README.md 9 | # workaround for #526 10 | workflow_dispatch: 11 | 12 | jobs: 13 | docker-description: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Docker Hub Description 20 | uses: peter-evans/dockerhub-description@v4.0.2 21 | if: ${{ env.DOCKER_USERNAME != '' && env.DOCKER_PASSWORD != '' }} 22 | env: 23 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 24 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 25 | with: 26 | username: ${{ secrets.DOCKER_USERNAME }} 27 | password: ${{ secrets.DOCKER_PASSWORD }} 28 | repository: factoriotools/factorio 29 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: 'Linter' 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | shellcheck: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: shellcheck 17 | uses: reviewdog/action-shellcheck@v1 18 | with: 19 | github_token: ${{ secrets.github_token }} 20 | reporter: github-pr-review 21 | 22 | hadolint: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | 28 | - name: hadolint 29 | uses: reviewdog/action-hadolint@v1 30 | with: 31 | github_token: ${{ secrets.github_token }} 32 | reporter: github-pr-review 33 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Check Update 2 | 3 | on: 4 | schedule: 5 | - cron: "0 * * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | if: github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository == 'factoriotools/factorio-docker') 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | with: 17 | token: ${{ secrets.REPO_TOKEN }} 18 | 19 | - name: Run update script 20 | run: ./update.sh 21 | shell: bash 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea 3 | -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | ignored: 2 | # ignore apt version pinning 3 | - DL3008 4 | # ignore pip version pinning 5 | - DL3013 6 | # ignore apk version pinning 7 | - DL3018 8 | # ignore pipefail cause Balena/resin.io images do not work with it 9 | - DL4006 10 | # ignore false positive regex 11 | - SC1083 12 | - SC2086 13 | # ignore as need for debug 14 | - SC2005 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Factorio [![Docker Version](https://img.shields.io/docker/v/factoriotools/factorio?sort=semver)](https://hub.docker.com/r/factoriotools/factorio/) [![Docker Pulls](https://img.shields.io/docker/pulls/factoriotools/factorio.svg?maxAge=600)](https://hub.docker.com/r/factoriotools/factorio/) [![Docker Stars](https://img.shields.io/docker/stars/factoriotools/factorio.svg?maxAge=600)](https://hub.docker.com/r/factoriotools/factorio/) 2 | 3 | > [!NOTE] 4 | > Support for ARM is experimental. Expect crashes and lag if you try to run this on a raspberry pi. 5 | 6 | [中文](./README_zh_CN.md) 7 | 8 | 9 | * `2`, `2.0`, `2.0.55`, `latest`, `stable`, `stable-2.0.55` 10 | * `2.0.54` 11 | * `2.0.53` 12 | * `2.0.52` 13 | * `2.0.51` 14 | * `2.0.50` 15 | * `2.0.49` 16 | * `2.0.48` 17 | * `2.0`, `2.0.47`, `stable-2.0.47` 18 | * `2.0.46` 19 | * `2.0.45` 20 | * `2.0.44` 21 | * `2.0`, `2.0.43`, `stable-2.0.43` 22 | * `2.0`, `2.0.42`, `stable-2.0.42` 23 | * `2.0`, `2.0.41`, `stable-2.0.41` 24 | * `2.0.40` 25 | * `2.0`, `2.0.39`, `stable-2.0.39` 26 | * `2.0.38` 27 | * `2.0.37` 28 | * `2.0.36` 29 | * `2.0.35` 30 | * `2.0.34` 31 | * `2.0.33` 32 | * `2.0`, `2.0.32`, `stable-2.0.32` 33 | * `2.0.31` 34 | * `2.0`, `2.0.30`, `stable-2.0.30` 35 | * `2.0.29` 36 | * `2.0`, `2.0.28`, `stable-2.0.28` 37 | * `2.0.27` 38 | * `2.0.26` 39 | * `2.0.25` 40 | * `2.0.24` 41 | * `2.0`, `2.0.23`, `stable-2.0.23` 42 | * `2.0.22` 43 | * `2.0`, `2.0.21`, `stable-2.0.21` 44 | * `2.0`, `2.0.20`, `stable-2.0.20` 45 | * `2.0.19` 46 | * `2.0.18` 47 | * `2.0.17` 48 | * `2.0.16` 49 | * `2.0`, `2.0.15`, `stable-2.0.15` 50 | * `2.0`, `2.0.14`, `stable-2.0.14` 51 | * `2.0`, `2.0.13`, `stable-2.0.13` 52 | * `1`, `1.1`, `1.1.110`, `stable-1.1.110` 53 | * `1.0`, `1.0.0` 54 | * `0.17`, `0.17.79` 55 | * `0.16`, `0.16.51` 56 | * `0.15`, `0.15.40` 57 | * `0.14`, `0.14.23` 58 | * `0.13`, `0.13.20` 59 | * `0.12`, `0.12.35` 60 | 61 | ## Tag descriptions 62 | 63 | * `latest` - most up-to-date version (may be experimental). 64 | * `stable` - version declared stable on [factorio.com](https://www.factorio.com) ([FFF-435 Since 2.0 versions gets released as experimental first, once stable it will be marked as stable](https://factorio.com/blog/post/fff-435)). 65 | * `0.x` - latest version in a branch. 66 | * `0.x.y` - a specific version. 67 | * `0.x-z` - incremental fix for that version. 68 | 69 | ## What is Factorio? 70 | 71 | [Factorio](https://www.factorio.com) is a game in which you build and maintain factories. 72 | 73 | You will be mining resources, researching technologies, building infrastructure, automating production and fighting enemies. Use your imagination to design your factory, combine simple elements into ingenious structures, apply management skills to keep it working and finally protect it from the creatures who don't really like you. 74 | 75 | The game is very stable and optimized for building massive factories. You can create your own maps, write mods in Lua or play with friends via Multiplayer. 76 | 77 | NOTE: This is only the server. The full game is available at [Factorio.com](https://www.factorio.com), [Steam](https://store.steampowered.com/app/427520/), [GOG.com](https://www.gog.com/game/factorio) and [Humble Bundle](https://www.humblebundle.com/store/factorio). 78 | 79 | ## Usage 80 | 81 | ### Quick Start 82 | 83 | Run the server to create the necessary folder structure and configuration files. For this example data is stored in `/opt/factorio`. 84 | 85 | ```shell 86 | sudo mkdir -p /opt/factorio 87 | sudo chown 845:845 /opt/factorio 88 | sudo docker run -d \ 89 | -p 34197:34197/udp \ 90 | -p 27015:27015/tcp \ 91 | -v /opt/factorio:/factorio \ 92 | --name factorio \ 93 | --restart=unless-stopped \ 94 | factoriotools/factorio 95 | ``` 96 | 97 | For those new to Docker, here is an explanation of the options: 98 | 99 | * `-d` - Run as a daemon ("detached"). 100 | * `-p` - Expose ports. 101 | * `-v` - Mount `/opt/factorio` on the local file system to `/factorio` in the container. 102 | * `--restart` - Restart the server if it crashes and at system start 103 | * `--name` - Name the container "factorio" (otherwise it has a funny random name). 104 | 105 | The `chown` command is needed because in 0.16+, we no longer run the game server as root for security reasons, but rather as a 'factorio' user with user id 845. The host must therefore allow these files to be written by that user. 106 | 107 | Check the logs to see what happened: 108 | 109 | ```shell 110 | docker logs factorio 111 | ``` 112 | 113 | Stop the server: 114 | 115 | ```shell 116 | docker stop factorio 117 | ``` 118 | 119 | Now there's a `server-settings.json` file in the folder `/opt/factorio/config`. Modify this to your liking and restart the server: 120 | 121 | ```shell 122 | docker start factorio 123 | ``` 124 | 125 | Try to connect to the server. Check the logs if it isn't working. 126 | 127 | ### Console 128 | 129 | To issue console commands to the server, start the server in interactive mode with `-it`. Open the console with `docker attach` and then type commands. 130 | 131 | ```shell 132 | docker run -d -it \ 133 | --name factorio \ 134 | factoriotools/factorio 135 | docker attach factorio 136 | ``` 137 | 138 | ### RCON (2.0.18+) 139 | 140 | Alternativly (e.g. for scripting) the RCON connection can be used to send commands to the running factorio server. 141 | This does not require the RCON connection to be exposed. 142 | 143 | ```shell 144 | docker exec factorio rcon /h 145 | ``` 146 | 147 | ### Upgrading 148 | 149 | Before upgrading backup the save. It's easy to make a save in the client. 150 | 151 | Ensure `-v` was used to run the server so the save is outside of the Docker container. The `docker rm` command completely destroys the container, which includes the save if it isn't stored in a data volume. 152 | 153 | Delete the container and refresh the image: 154 | 155 | ```shell 156 | docker stop factorio 157 | docker rm factorio 158 | docker pull factoriotools/factorio 159 | ``` 160 | 161 | Now run the server as before. In about a minute the new version of Factorio should be up and running, complete with saves and config! 162 | 163 | ### Saves 164 | 165 | A new map named `_autosave1.zip` is generated the first time the server is started. The `map-gen-settings.json` and `map-settings.json` files in `/opt/factorio/config` are used for the map settings. On subsequent runs the newest save is used. 166 | 167 | To load an old save stop the server and run the command `touch oldsave.zip`. This resets the date. Then restart the server. Another option is to delete all saves except one. 168 | 169 | To generate a new map stop the server, delete all of the saves and restart the server. 170 | 171 | #### Specify a save directly (0.17.79-2+) 172 | 173 | You can specify a specific save to load by configuring the server through a set of environment variables: 174 | 175 | To load an existing save set `SAVE_NAME` to the name of your existing save file located within the `saves` directory, without the `.zip` extension: 176 | 177 | ```shell 178 | sudo docker run -d \ 179 | -p 34197:34197/udp \ 180 | -p 27015:27015/tcp \ 181 | -v /opt/factorio:/factorio \ 182 | -e LOAD_LATEST_SAVE=false \ 183 | -e SAVE_NAME=replaceme \ 184 | --name factorio \ 185 | --restart=unless-stopped \ 186 | factoriotools/factorio 187 | ``` 188 | 189 | To generate a new map set `GENERATE_NEW_SAVE=true` and specify `SAVE_NAME`: 190 | 191 | ```shell 192 | sudo docker run -d \ 193 | -p 34197:34197/udp \ 194 | -p 27015:27015/tcp \ 195 | -v /opt/factorio:/factorio \ 196 | -e LOAD_LATEST_SAVE=false \ 197 | -e GENERATE_NEW_SAVE=true \ 198 | -e SAVE_NAME=replaceme \ 199 | --name factorio \ 200 | --restart=unless-stopped \ 201 | factoriotools/factorio 202 | ``` 203 | 204 | ### Mods 205 | 206 | Copy mods into the mods folder and restart the server. 207 | 208 | 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. 209 | 210 | ### Scenarios 211 | 212 | 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. 213 | 214 | ```shell 215 | docker run -d \ 216 | -p 34197:34197/udp \ 217 | -p 27015:27015/tcp \ 218 | -v /opt/factorio:/factorio \ 219 | --name factorio \ 220 | --restart=unless-stopped \ 221 | --entrypoint "/scenario.sh" \ 222 | factoriotools/factorio \ 223 | MyScenarioName 224 | ``` 225 | 226 | ### Converting Scenarios to Regular Maps 227 | 228 | If you would like to export your scenario to a saved map, you can use the example entrypoint similar to the Scenario usage above. Factorio will run once, converting the Scenario to a saved Map in your saves directory. A restart of the docker image using the standard options will then load that map, just as if the scenario were just started by the Scenarios example noted above. 229 | 230 | ```shell 231 | docker run -d \ 232 | -p 34197:34197/udp \ 233 | -p 27015:27015/tcp \ 234 | -v /opt/factorio:/factorio \ 235 | --name factorio \ 236 | --restart=unless-stopped \ 237 | --entrypoint "/scenario2map.sh" \ 238 | factoriotools/factorio 239 | MyScenarioName 240 | ``` 241 | 242 | ### RCON 243 | 244 | Set the RCON password in the `rconpw` file. A random password is generated if `rconpw` doesn't exist. 245 | 246 | To change the password, stop the server, modify `rconpw`, and restart the server. 247 | 248 | To "disable" RCON don't expose port 27015, i.e. start the server without `-p 27015:27015/tcp`. RCON is still running, but nobody can to connect to it. 249 | 250 | ### Whitelisting (0.15.3+) 251 | 252 | Create file `config/server-whitelist.json` and add the whitelisted users. 253 | 254 | ```json 255 | [ 256 | "you", 257 | "friend" 258 | ] 259 | ``` 260 | 261 | ### Banlisting (0.17.1+) 262 | 263 | Create file `config/server-banlist.json` and add the banlisted users. 264 | 265 | ```json 266 | [ 267 | "bad_person", 268 | "other_bad_person" 269 | ] 270 | ``` 271 | 272 | ### Adminlisting (0.17.1+) 273 | 274 | Create file `config/server-adminlist.json` and add the adminlisted users. 275 | 276 | ```json 277 | [ 278 | "you", 279 | "friend" 280 | ] 281 | ``` 282 | 283 | ### Customize configuration files (0.17.x+) 284 | 285 | Out-of-the box, factorio does not support environment variables inside the configuration files. A workaround is the usage of `envsubst` which generates the configuration files dynamically during startup from environment variables set in docker-compose: 286 | 287 | Example which replaces the server-settings.json: 288 | 289 | ```yaml 290 | factorio_1: 291 | image: factoriotools/factorio 292 | ports: 293 | - "34197:34197/udp" 294 | volumes: 295 | - /opt/factorio:/factorio 296 | - ./server-settings.json:/server-settings.json 297 | environment: 298 | - INSTANCE_NAME=Your Instance's Name 299 | - INSTANCE_DESC=Your Instance's Description 300 | entrypoint: /bin/sh -c "mkdir -p /factorio/config && envsubst < /server-settings.json > /factorio/config/server-settings.json && exec /docker-entrypoint.sh" 301 | ``` 302 | 303 | The `server-settings.json` file may then contain the variable references like this: 304 | 305 | ```json 306 | "name": "${INSTANCE_NAME}", 307 | "description": "${INSTANCE_DESC}", 308 | ``` 309 | 310 | ### Environment Variables 311 | 312 | These are the environment variables which can be specified at container run time. 313 | 314 | | Variable Name | Description | Default | Available in | 315 | |----------------------|----------------------------------------------------------------------|----------------|--------------| 316 | | GENERATE_NEW_SAVE | Generate a new save if one does not exist before starting the server | false | 0.17+ | 317 | | LOAD_LATEST_SAVE | Load latest when true. Otherwise load SAVE_NAME | true | 0.17+ | 318 | | PORT | UDP port the server listens on | 34197 | 0.15+ | 319 | | BIND | IP address (v4 or v6) the server listens on (IP\[:PORT]) | | 0.15+ | 320 | | RCON_PORT | TCP port the rcon server listens on | 27015 | 0.15+ | 321 | | SAVE_NAME | Name to use for the save file | _autosave1 | 0.17+ | 322 | | TOKEN | factorio.com token | | 0.17+ | 323 | | UPDATE_MODS_ON_START | If mods should be updated before starting the server | | 0.17+ | 324 | | USERNAME | factorio.com username | | 0.17+ | 325 | | CONSOLE_LOG_LOCATION | Saves the console log to the specifies location | | | 326 | | DLC_SPACE_AGE | Enables or disables the mods for DLC Space Age in mod-list.json[^1] | true | 2.0.8+ | 327 | | MODS | Mod directory to use | /factorio/mods | 2.0.8+ | 328 | 329 | **Note:** All environment variables are compared as strings 330 | 331 | ## Container Details 332 | 333 | The philosophy is to [keep it simple](http://wiki.c2.com/?KeepItSimple). 334 | 335 | * The server should bootstrap itself. 336 | * Prefer configuration files over environment variables. 337 | * Use one volume for data. 338 | 339 | ### Volumes 340 | 341 | To keep things simple, the container uses a single volume mounted at `/factorio`. This volume stores configuration, mods, and saves. 342 | 343 | The files in this volume should be owned by the factorio user, uid 845. 344 | 345 | ```text 346 | factorio 347 | |-- config 348 | | |-- map-gen-settings.json 349 | | |-- map-settings.json 350 | | |-- rconpw 351 | | |-- server-adminlist.json 352 | | |-- server-banlist.json 353 | | |-- server-settings.json 354 | | `-- server-whitelist.json 355 | |-- mods 356 | | `-- fancymod.zip 357 | `-- saves 358 | `-- _autosave1.zip 359 | ``` 360 | 361 | ## Docker Compose 362 | 363 | [Docker Compose](https://docs.docker.com/compose/install/) is an easy way to run Docker containers. 364 | 365 | * docker-engine >= 1.10.0 is required 366 | * docker-compose >=1.6.0 is required 367 | 368 | First get a [docker-compose.yml](https://github.com/factoriotools/factorio-docker/blob/master/docker/docker-compose.yml) file. To get it from this repository: 369 | 370 | ```shell 371 | git clone https://github.com/factoriotools/factorio-docker.git 372 | cd factorio-docker/docker 373 | ``` 374 | 375 | Or make your own: 376 | 377 | ```yaml 378 | version: '2' 379 | services: 380 | factorio: 381 | image: factoriotools/factorio 382 | ports: 383 | - "34197:34197/udp" 384 | - "27015:27015/tcp" 385 | volumes: 386 | - /opt/factorio:/factorio 387 | ``` 388 | 389 | Now cd to the directory with docker-compose.yml and run: 390 | 391 | ```shell 392 | sudo mkdir -p /opt/factorio 393 | sudo chown 845:845 /opt/factorio 394 | sudo docker-compose up -d 395 | ``` 396 | 397 | ### Ports 398 | 399 | * `34197/udp` - Game server (required). This can be changed with the `PORT` environment variable. 400 | * `27015/tcp` - RCON (optional). 401 | 402 | ## LAN Games 403 | 404 | Ensure the `lan` setting in server-settings.json is `true`. 405 | 406 | ```json 407 | "visibility": 408 | { 409 | "public": false, 410 | "lan": true 411 | }, 412 | ``` 413 | 414 | Start the container with the `--network=host` option so clients can automatically find LAN games. Refer to the Quick Start to create the `/opt/factorio` directory. 415 | 416 | ```shell 417 | sudo docker run -d \ 418 | --network=host \ 419 | -p 34197:34197/udp \ 420 | -p 27015:27015/tcp \ 421 | -v /opt/factorio:/factorio \ 422 | --name factorio \ 423 | --restart=unless-stopped \ 424 | factoriotools/factorio 425 | ``` 426 | 427 | ## Deploy to other plaforms 428 | 429 | ### Vagrant 430 | 431 | [Vagrant](https://www.vagrantup.com/) is a easy way to setup a virtual machine (VM) to run Docker. The [Factorio Vagrant box repository](https://github.com/dtandersen/factorio-lan-vagrant) contains a sample Vagrantfile. 432 | 433 | For LAN games the VM needs an internal IP in order for clients to connect. One way to do this is with a public network. The VM uses DHCP to acquire an IP address. The VM must also forward port 34197. 434 | 435 | ```ruby 436 | config.vm.network "public_network" 437 | config.vm.network "forwarded_port", guest: 34197, host: 34197 438 | ``` 439 | 440 | ### Amazon Web Services (AWS) Deployment 441 | 442 | If you're looking for a simple way to deploy this to the Amazon Web Services Cloud, check out the [Factorio Server Deployment (CloudFormation) repository](https://github.com/m-chandler/factorio-spot-pricing). This repository contains a CloudFormation template that will get you up and running in AWS in a matter of minutes. Optionally it uses Spot Pricing so the server is very cheap, and you can easily turn it off when not in use. 443 | 444 | ## Using a reverse proxy 445 | 446 | If you need to use a reverse proxy you can use the following nginx snippet: 447 | 448 | ``` 449 | stream { 450 | server { 451 | listen 34197 udp reuseport; 452 | proxy_pass my.upstream.host:34197; 453 | } 454 | } 455 | ``` 456 | 457 | 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 ...` 458 | 459 | ## Troubleshooting 460 | 461 | ### My server is listed in the server browser, but nobody can connect 462 | 463 | 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. 464 | 465 | By default, Docker routes traffic through a proxy. The proxy changes the source UDP port, so the wrong port is detected. See the forum post *[Incorrect port detected for docker hosted server](https://forums.factorio.com/viewtopic.php?f=49&t=35255)* for details. 466 | 467 | To fix the incorrect port, start the Docker service with the `--userland-proxy=false` switch. Docker will route traffic with iptables rules instead of a proxy. Add the switch to the `DOCKER_OPTS` environment variable or `ExecStart` in the Docker systemd service definition. The specifics vary by operating system. 468 | 469 | ### When I run a server on a port besides 34197 nobody can connect from the server browser 470 | 471 | Use the `PORT` environment variable to start the server on the a different port, .e.g. `docker run -e "PORT=34198"`. This changes the source port on the packets used for port detection. `-p 34198:34197` works fine for private servers, but the server browser detects the wrong port. 472 | 473 | ## Contributors 474 | 475 | * [dtandersen](https://github.com/dtandersen) - Maintainer 476 | * [Fank](https://github.com/Fankserver) - Programmer of the Factorio watchdog that keeps the version up-to-date. 477 | * [SuperSandro2000](https://github.com/supersandro2000) - CI Guy, Maintainer and runner of the Factorio watchdog. Contributed version updates and wrote the Travis scripts. 478 | * [DBendit](https://github.com/DBendit/docker_factorio_server) - Coded admin list, ban list support and contributed version updates 479 | * [Zopanix](https://github.com/zopanix/docker_factorio_server) - Original Author 480 | * [Rfvgyhn](https://github.com/Rfvgyhn/docker-factorio) - Coded randomly generated RCON password 481 | * [gnomus](https://github.com/gnomus/docker_factorio_server) - Coded white listing support 482 | * [bplein](https://github.com/bplein/docker_factorio_server) - Coded scenario support 483 | * [jaredledvina](https://github.com/jaredledvina/docker_factorio_server) - Contributed version updates 484 | * [carlbennett](https://github.com/carlbennett) - Contributed version updates and bugfixes 485 | 486 | [^1]: Space Age mods can also be individually enabled by using their name separated by space. 487 | Example 1: Enable all by using `true` 488 | Example 2: Enable all by listing the mod names `space-age elevated-rails quality` 489 | Example 3: Enable only Elevated rails `elevated-rails` 490 | -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # Factorio-异星工厂 [![Build Status](https://travis-ci.org/factoriotools/factorio-docker.svg?branch=master)](https://travis-ci.org/factoriotools/factorio-docker) ![Updater status](https://img.shields.io/endpoint?label=Updater%20status&logo=a&url=https%3A%2F%2Fhealthchecks.supersandro.de%2Fbadge%2F1a0a7698-445d-4e54-9e4b-f61a1544e01f%2FBO8VukOA%2Fmaintainer.shields) [![Docker Version](https://images.microbadger.com/badges/version/factoriotools/factorio.svg)](https://hub.docker.com/r/factoriotools/factorio/) [![Docker Pulls](https://img.shields.io/docker/pulls/factoriotools/factorio.svg?maxAge=600)](https://hub.docker.com/r/factoriotools/factorio/) [![Docker Stars](https://img.shields.io/docker/stars/factoriotools/factorio.svg?maxAge=600)](https://hub.docker.com/r/factoriotools/factorio/) [![Microbadger Layers](https://images.microbadger.com/badges/image/factoriotools/factorio.svg)](https://microbadger.com/images/factoriotools/factorio "Get your own image badge on microbadger.com") 2 | 3 | 版本信息可以在[这里](https://github.com/factoriotools/factorio-docker/blob/master/README.md#factorio------)找到 4 | 5 | ## 标签描述 6 | 7 | * `latest` - 最新版本 (可能含有实验性功能). 8 | * `stable` - 最新的稳定版本 [factorio.com](https://www.factorio.com). 9 | * `0.x` - 某个分支上的最新版本 10 | * `0.x.y` - 具体的版本 11 | * `0.x-z` - 在该版本上的增量更新 12 | 13 | ## 什么是 Factorio? 14 | 15 | > 摘录自 [steam factorio 页面](https://store.steampowered.com/app/427520/Factorio/) 16 | 17 | 「异星工厂」Factorio 是一款建造工业生产流水线并保持其高效运转的游戏。 18 | 19 | 在游戏中,你可以抠矿、搞科研、盖工厂、建设自动生产流水线,同时还要与异星虫子们互相伤害。 20 | 21 | 你将从一无所有艰辛起步。挥斧砍树,抡镐抠矿,手搓机械臂和传送带,然而像这样一直搞下去并没有什么卵用。因此,你需要高效的大规模发电厂,庞大的石油化工体系,壮观的全自动化产业链,以及替你东奔西走的机器人大队,让你成为物资储备丰盈工业帝国的真正操控者! 22 | 23 | 然而,总有一群刁民想害你。这个星球上的土著虫群对你在自家后院里瞎折腾的行为很不爽,总有一天这群刁民会联合起来找你麻烦。因此,你要制造武器、建立防御、准备镇压,让它们知道谁才是真正的主宰者。 24 | 25 | 你可以在多人游戏中加入不同的阵营,在大触们的带领下与朋友们分工协作, 一起建设恢弘无比的工业园区。 26 | 27 | Factorio的模组支持吸引了全世界的设计师参与到对游戏的完善和革新中来,从优化调整到游戏辅助,甚至对游戏的彻底翻新,日新月异的模组将为你不断提供新的乐趣。 28 | 除了游戏核心的自由模式和沙盒模式之外,任务包还提供了更多不同形式的游戏挑战,这已经作为一个免费的DLC提供给玩家了。 29 | 30 | 对随机生成的地图不满意?不满足于原生游戏任务?这都不是事儿。通过内置的地图编辑器,你可以任意修改地图,配置地形、建筑、敌人等各种元素。如果你是大触,还可以添加自定义脚本,让你的游戏更具独创性、更加阴吹思婷! 31 | 32 | **注意**:这个仓库仅包含游戏服务端. 游戏本体可以在 [factorio.com](factorio.com)、 [Steam](https://store.steampowered.com/app/427520/Factorio/)、[GOG.com](https://www.gog.com/game/factorio) 和 [Humble Bundle](https://www.humblebundle.com/store/factorio) 上找到。 33 | 34 | ## 使用方法 35 | 36 | ### 快速入门 37 | 38 | 运行服务端以在指定目录下生成必要的配置文件以及存档,`/opt/factorio` 也许是一个不错的选择。 39 | 40 | ```shell 41 | sudo mkdir -p /opt/factorio 42 | sudo chown 845:845 /opt/factorio 43 | sudo docker run -d \ 44 | -p 34197:34197/udp \ 45 | -p 27015:27015/tcp \ 46 | -v /opt/factorio:/factorio \ 47 | --name factorio \ 48 | --restart=always \ 49 | factoriotools/factorio 50 | ``` 51 | 52 | 这样一来, 服务端会使用 `/opt/factorio/saves` 中最新的存档进行游戏。 53 | 54 | 你一定想知道上面那些咒语是什么意思: 55 | 56 | * `-d` - 以守护进程方式运行 ("detached")。 57 | * `-p` - 暴露宿主机 (host) 某些端口。 58 | * `-v` - 将宿主机中 `/opt/factorio` 目录挂载到docker容器的 `/factorio` 目录。 59 | * `--restart` - 在宿主重启或服务端运行崩溃后重启服务端。 60 | * `--name` - 将docker容器命名为 "factorio" (否则docker会给他随机起一个搞怪名字)。 61 | 62 | `chown` 命令用来更改服务端所在目录的所有权用户以及用户组,为了安全起见我们并不希望游戏直接在root用户权限下运行,因此将用户id更改为845,从而服务端允许该用户在目录中进行读、写、运行操作。 63 | 64 | 查看日志以搞清楚发生了什么。 65 | 66 | ```shell 67 | docker logs factorio 68 | ``` 69 | 70 | 停止docker容器 (服务端)。 71 | 72 | ```shell 73 | docker stop factorio 74 | ``` 75 | 76 | 在运行过服务端之后可以在 `/opt/factorio/config` 目录中找到 `server-settings.json` 文件,修改改文件以定制你自己的服务端。 77 | 78 | ```shell 79 | docker start factorio 80 | ``` 81 | 82 | 现在试试连接服务端。如果没有正常运行的话请按照上面步骤查看日志。 83 | 84 | ### Console-终端 85 | 86 | 为了运行在服务端终端中运行命令,需要通过 `-it` 参数在交互模式下启动服务端。通过 `docker attach` 连接终端从而可以输入命令。 87 | 88 | ```shell 89 | docker run -d -it \ 90 | --name factorio \ 91 | factoriotools/factorio 92 | 93 | docker attach factorio 94 | ``` 95 | 96 | ### 升级服务端 97 | 98 | 在升级服务端之前请务必**备份存档**,在客户端(也就是你的游戏中)备份存档相当容易(保存就好)。 99 | 100 | 请确保在启动服务端时使用了 `-v` 参数,从而服务端将会把存档写在你指定的挂在目录中。`docker rm` 命令会彻底删除运行 facotrio 服务端的容器,也同时会删除容器的整个文件系统实例,因此如果没有挂载外部目录的话,存档也会被删除哦。 101 | 102 | ```shell 103 | docker stop factorio 104 | docker rm factorio 105 | docker pull factoriotools/factorio 106 | ``` 107 | 108 | 然后就像前面说的那样启动服务端,大概一分钟后新的服务端就已经在运行中啦,并且存档和设置还和原来一样! 109 | 110 | ### 存档 111 | 112 | 在第一次运行服务端的时候,服务端会根据 `/opt/factorio/config` 目录中的 `map-gen-settings.json` 和 `map-settings.json` 配置文件的内容,在 `/opt/factorio/saves` 目录下会生成一张新地图(存档)`_autosave1.zip`。之后如果在停掉之后再次运行,服务端会载入最新的存档。 113 | 114 | 如果想要运行一个旧存档,你需要停止运行中的服务端,并且运行一个命令 `touch oldsave.zip`。 这会重置其日期,然后重新启动服务端。或者你可以通过删除所有其他存档只留下想要运行的存档来完成同样的目的。 115 | 116 | 如果想生成一个新的存档,你需要停止运行中的服务端,然后删除所有的存档再启动服务端就好。 117 | 118 | #### 在运行命令中直接指定存档(需要 0.17.79-2+ 版本) 119 | 120 | 你可以在启动服务端时通过设置一个特殊的环境变量来载入一个特定的存档: 121 | 122 | 设置 `SAVE_NAME` 为 `saves` 中你想运行的存档名,去掉 `.zip` 后缀: 123 | 124 | ```shell 125 | sudo docker run -d \ 126 | -p 34197:34197/udp \ 127 | -p 27015:27015/tcp \ 128 | -v /opt/factorio:/factorio \ 129 | -e LOAD_LATEST_SAVE=false \ 130 | -e SAVE_NAME=replaceme \ 131 | --name factorio \ 132 | --restart=always \ 133 | factoriotools/factorio 134 | ``` 135 | 136 | 若要生成一个新存档,设置 `GENERATE_NEW_SAVE=true`,同时指定存档名 `SAVE_NAME`: 137 | 138 | ```shell 139 | sudo docker run -d \ 140 | -p 34197:34197/udp \ 141 | -p 27015:27015/tcp \ 142 | -v /opt/factorio:/factorio \ 143 | -e LOAD_LATEST_SAVE=false \ 144 | -e GENERATE_NEW_SAVE=true \ 145 | -e SAVE_NAME=replaceme \ 146 | --name factorio \ 147 | --restart=always \ 148 | factoriotools/factorio 149 | ``` 150 | 151 | ### Mods-模组 152 | 153 | 将模组拷贝至 `mods` 目录下,然后重启服务端即可。 154 | 155 | 对于 `0.17` 及以上版本,新增 `UPDATE_MODES_ON_START` 环境变量,如果将其设置为 `true`,在服务端启动时将会更新所有的模组。请注意,应用此设置时,必须通过 docker secrets、环境变量或者在 `server-settings.json` 中填写相应字段来提供一个合法的 [Facotrio 用户名以及 Token](https://www.factorio.com/profile),否则服务端就不会启动。 156 | 157 | ### Scenarios-场景 158 | 159 | 如果你希望新启动一个场景(而不是从某一个存档中启动),你需要通过另一个备选 `entrypoint` 来启动我们的 factorio-docker 镜像:通过运行以下命令,使用 `/factorio/entrypoints` 目录中的示例 entrypoint 文件来启动服务端。仔细观察后就能发现这只是在之前的命令基础上增加了 `--entrypoint` 设置并在最后新增了一个参数用来指示 `scenarios` 目录中想要启动的场景的文件名。 160 | 161 | 162 | ```shell 163 | docker run -d \ 164 | -p 34197:34197/udp \ 165 | -p 27015:27015/tcp \ 166 | -v /opt/factorio:/factorio \ 167 | --name factorio \ 168 | --restart=always \ 169 | --entrypoint "/scenario.sh" \ 170 | factoriotools/factorio \ 171 | MyScenarioName 172 | ``` 173 | 174 | ### 将场景转换为常规地图 175 | 176 | 如果你想把你的场景导出为一个常规的地图存档,类似启动一个新的场景,我们可以通过一个备选 `entrypoint` 文件来达到这个效果:服务端在运行后会将场景转换成一个常规地图存档放置在你的 `saves` 目录中,然后你就可以像平常那样启动服务端了。 177 | 178 | ```shell 179 | docker run -d \ 180 | -p 34197:34197/udp \ 181 | -p 27015:27015/tcp \ 182 | -v /opt/factorio:/factorio \ 183 | --name factorio \ 184 | --restart=always \ 185 | --entrypoint "/scenario2map.sh" \ 186 | factoriotools/factorio 187 | MyScenarioName 188 | ``` 189 | 190 | ### RCON 191 | 192 | 在 config/rconpw 文件中设置RCON密码。 如果 rconpw 文件不存在,将会自动生成含有随机密码的该文件。 193 | 194 | 如果想要更改密码,请停止服务端,编辑rconpw文件并重启服务端。 195 | 196 | 如果想要禁用RCON,请不要在启动命令中加入 -p 27015:27015/tcp 参数,在宿主机(服务器)中停止暴露rcon端口,这时,RCON将继续在docker容器中运行,但不可达。 197 | 198 | 199 | ### 白名单 (0.15.3+) 200 | 201 | 创建文件 `config/server-whitelist.json` 然后将用户名加入到该json中。 202 | 203 | ```json 204 | [ 205 | "you", 206 | "friend" 207 | ] 208 | ``` 209 | 210 | ### 黑名单 (0.17.1+) 211 | 212 | 创建文件 `config/server-banlist.json` 然后将用户名加入到该json中。 213 | 214 | ```json 215 | [ 216 | "bad_person", 217 | "other_bad_person" 218 | ] 219 | ``` 220 | 221 | ### 管理员列表 (0.17.1+) 222 | 223 | 创建文件 `config/server-adminlist.json` 然后将用户名加入到该json中。 224 | 225 | ```json 226 | [ 227 | "you", 228 | "friend" 229 | ] 230 | ``` 231 | 232 | ### 自定义配置文件 (0.17.x+) 233 | 234 | 原始的 factorio 服务端并不支持在配置文件中添加环境变量,这里提供一个变通办法:通过在 docker-compose 中使用 `envsubst` 命令,在服务端启动时根据环境变量来动态生成配置文件: 235 | 236 | 下面的例子将用相应的环境变量来填充 `server-settings.json` 中的字段。 237 | 238 | ```yaml 239 | factorio_1: 240 | image: factoriotools/factorio 241 | ports: 242 | - "34197:34197/udp" 243 | volumes: 244 | - /opt/factorio:/factorio 245 | - ./server-settings.json:/server-settings.json 246 | environment: 247 | - INSTANCE_NAME=Your Instance's Name 248 | - INSTANCE_DESC=Your Instance's Description 249 | entrypoint: /bin/sh -c "mkdir -p /factorio/config && envsubst < /server-settings.json > /factorio/config/server-settings.json && exec /docker-entrypoint.sh" 250 | ``` 251 | 252 | `server-settings.json` 中可能提供一些供环境变量来替换的字段: 253 | 254 | ```json 255 | "name": "${INSTANCE_NAME}", 256 | "description": "${INSTANCE_DESC}", 257 | ``` 258 | 259 | ### 容器相关的细节 260 | 261 | [保持简单](http://wiki.c2.com/?KeepItSimple)的哲学。 262 | 263 | + 服务端应当可以自启动 264 | + 在环境变量和配置文件中倾向于配置文件 265 | + 只使用一个数据卷(挂载目录) 266 | 267 | ### 数据卷 268 | 269 | 为了保持简单,我们的 docker 服务端只使用一个数据卷挂载到容器中的 `/factorio` 目录。其中包含了所有的配置,模组和存档。 270 | 271 | 在这个数据卷中所有的文件应当被 uid 为 845 的 factorio 专有用户所拥有(为了安全) 272 | 273 | ```text 274 | factorio 275 | |-- config 276 | | |-- map-gen-settings.json 277 | | |-- map-settings.json 278 | | |-- rconpw 279 | | |-- server-adminlist.json 280 | | |-- server-banlist.json 281 | | |-- server-settings.json 282 | | `-- server-whitelist.json 283 | |-- mods 284 | | `-- fancymod.zip 285 | `-- saves 286 | `-- _autosave1.zip 287 | ``` 288 | 289 | ### Docker Compose 290 | 291 | [Docker Compose](https://docs.docker.com/compose/install/) 提供了一种便捷的容器运行方式。 292 | 293 | 首先获取一个 [docker-compose.yml](https://github.com/factoriotools/factorio-docker/blob/master/0.17/docker-compose.yml) 文件。假设你准备使用我们提供的: 294 | 295 | ```shell 296 | git clone https://github.com/factoriotools/factorio-docker.git 297 | cd docker_factorio_server/0.17 298 | ``` 299 | 300 | 或者假设你想自己编写一个: 301 | 302 | ```shell 303 | version: '2' 304 | services: 305 | factorio: 306 | image: factoriotools/factorio 307 | ports: 308 | - "34197:34197/udp" 309 | - "27015:27015/tcp" 310 | volumes: 311 | - /opt/factorio:/factorio 312 | ``` 313 | 314 | 现在通过 cd 命令进入到 `docker-compose.yml` 所在的目录然后运行下面的命令: 315 | 316 | ```shell 317 | sudo mkdir -p /opt/factorio 318 | sudo chown 845:845 /opt/factorio 319 | sudo docker-compose up -d 320 | ``` 321 | 322 | ### 端口 323 | 324 | - `34197/udp` - 游戏服务端(必要)。可以通过改变 `PORT` 环境变量来改变。 325 | - `27015/tcp` - RCON(可选)。 326 | 327 | ## 局域网游戏 328 | 329 | 确保 `server-settings.json` 中的 `lan` 字段被设置为 `true`。 330 | 331 | ```shell 332 | "visibility": 333 | { 334 | "public": false, 335 | "lan": true 336 | }, 337 | ``` 338 | 339 | 在启动服务端时假如 `--network=host` 参数,从而客户端可以自动找到局域网游戏,参考 快速入门 章节。 340 | 341 | ```shell 342 | sudo docker run -d \ 343 | --network=host \ 344 | -p 34197:34197/udp \ 345 | -p 27015:27015/tcp \ 346 | -v /opt/factorio:/factorio \ 347 | --name factorio \ 348 | --restart=always \ 349 | factoriotools/factorio 350 | ``` 351 | 352 | ## 在其他平台上部署 353 | 354 | ### Vagrant 355 | 356 | [Vagrant](https://www.vagrantup.com/) 是一种通过虚拟机来运行 Docker 的便捷方式。 在 [Factorio Vagrant box repository](https://github.com/dtandersen/factorio-lan-vagrant) 中有一个示例的 Vagrantfile。 357 | 358 | 对于局域网游戏,Vagrant 虚拟机需要一个内部 IP 从而使游戏可达。一种方式是通过在一个空开网络中部署。虚拟机使用 DHCP 方式来获取一个 IP 地址。同时必须转发到 34197 端口。 359 | 360 | ```ruby 361 | config.vm.network "public_network" 362 | config.vm.network "forwarded_port", guest: 34197, host: 34197 363 | ``` 364 | 365 | ### AWS 部署 366 | 367 | 如果你想找一个傻瓜教程,请看这个[仓库](https://github.com/m-chandler/factorio-spot-pricing)。这个仓库中包含一个可以让你在几分钟内在 AWS 上搭建服务端的 CloudFormation 模板。同时它支持 Spot Pricing 因此费用会非常便宜,而且你可以在不用的时候把服务器关掉。 368 | 369 | ## 疑难杂症 370 | 371 | ### 我可以在服务器列表中看到我的服务器但就是无法连接 372 | 373 | 查看 log,如果有一行说 `Own address is RIGHT IP:WRONG PORT`,那么这个问题就有可能是 Docker Proxy 导致的。 如果 IP 和端口都是正确的,那么有可能是端口转发或者防火墙出了问题。 374 | 375 | 在默认情况下,Docker 通过一个代理来转发网络请求。这个代理会改变 UDP 端口,因此会监测到上面的端口错误。更多细节请移步 *[Incorrect port detected for docker hosted server](https://forums.factorio.com/viewtopic.php?f=49&t=35255)*。 376 | 377 | 为了修复错误端口问题,在启动 Docker 服务时加上 `--userland-proxy=false`。这样一来 Docker 就会通过 iptables 来转发请求从而不走代理。可以通过设置 `DOCKER_OPTS` 环境变量或者改变 Docker systemd service 中的 `ExecStart` 字段来添加这一参数。不同的操作系统可能有不同的配置方式。 378 | 379 | ### 我不用 34197 端口就没人可以连我的服务器 380 | 381 | 如果一定要改端口,请使用 `PORT` 环境变量更改。例如 `docker run -e PORT=34198`。这样会更改端口监测中的目标端口。通过 `-p 34198:34197` 方式更改端口对于私人服务器来说是可行的,但这样一来服务器浏览器就没有办法检测到正确的端口了。 382 | 383 | ## 贡献者 384 | 385 | * [dtandersen](https://github.com/dtandersen) - Maintainer 386 | * [Fank](https://github.com/Fankserver) - Programmer of the Factorio watchdog that keeps the version up-to-date. 387 | * [SuperSandro2000](https://github.com/supersandro2000) - CI Guy, Maintainer and runner of the Factorio watchdog. Contributed version updates and wrote the Travis scripts. 388 | * [DBendit](https://github.com/DBendit/docker_factorio_server) - Coded admin list, ban list support and contributed version updates 389 | * [Zopanix](https://github.com/zopanix/docker_factorio_server) - Original Author 390 | * [Rfvgyhn](https://github.com/Rfvgyhn/docker-factorio) - Coded randomly generated RCON password 391 | * [gnomus](https://github.com/gnomus/docker_factorio_server) - Coded wite listing support 392 | * [bplein](https://github.com/bplein/docker_factorio_server) - Coded scenario support 393 | * [jaredledvina](https://github.com/jaredledvina/docker_factorio_server) - Contributed version updates 394 | * [carlbennett](https://github.com/carlbennett) - Contributed version updates and bugfixes 395 | * [Thrimbda](https://github.com/Thrimbda) - 中文翻译 396 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import json 5 | import subprocess 6 | import shutil 7 | import sys 8 | import tempfile 9 | 10 | 11 | PLATFORMS = [ 12 | "linux/arm64", 13 | "linux/amd64", 14 | ] 15 | 16 | 17 | def create_builder(build_dir, builder_name, platform): 18 | check_exists_command = ["docker", "buildx", "inspect", builder_name] 19 | if subprocess.run(check_exists_command, stderr=subprocess.DEVNULL).returncode != 0: 20 | create_command = ["docker", "buildx", "create", "--platform", platform, "--name", builder_name] 21 | try: 22 | subprocess.run(create_command, cwd=build_dir, check=True) 23 | except subprocess.CalledProcessError: 24 | print("Creating builder failed") 25 | exit(1) 26 | 27 | 28 | def build_and_push_multiarch(build_dir, build_args, push): 29 | builder_name = "factoriotools-multiarch" 30 | platform=",".join(PLATFORMS) 31 | create_builder(build_dir, builder_name, platform) 32 | build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args 33 | if push: 34 | build_command.append("--push") 35 | try: 36 | subprocess.run(build_command, cwd=build_dir, check=True) 37 | except subprocess.CalledProcessError: 38 | print("Build and push of image failed") 39 | exit(1) 40 | 41 | 42 | def build_singlearch(build_dir, build_args): 43 | build_command = ["docker", "build"] + build_args 44 | try: 45 | subprocess.run(build_command, cwd=build_dir, check=True) 46 | except subprocess.CalledProcessError: 47 | print("Build of image failed") 48 | exit(1) 49 | 50 | 51 | def push_singlearch(tags): 52 | for tag in tags: 53 | try: 54 | subprocess.run(["docker", "push", f"factoriotools/factorio:{tag}"], 55 | check=True) 56 | except subprocess.CalledProcessError: 57 | print("Docker push failed") 58 | exit(1) 59 | 60 | 61 | def build_and_push(sha256, version, tags, push, multiarch): 62 | build_dir = tempfile.mktemp() 63 | shutil.copytree("docker", build_dir) 64 | build_args = ["--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."] 65 | for tag in tags: 66 | build_args.extend(["-t", f"factoriotools/factorio:{tag}"]) 67 | if multiarch: 68 | build_and_push_multiarch(build_dir, build_args, push) 69 | else: 70 | build_singlearch(build_dir, build_args) 71 | if push: 72 | push_singlearch(tags) 73 | 74 | 75 | def login(): 76 | try: 77 | username = os.environ["DOCKER_USERNAME"] 78 | password = os.environ["DOCKER_PASSWORD"] 79 | subprocess.run(["docker", "login", "-u", username, "-p", password], check=True) 80 | except KeyError: 81 | print("Username and password need to be given") 82 | exit(1) 83 | except subprocess.CalledProcessError: 84 | print("Docker login failed") 85 | exit(1) 86 | 87 | 88 | def main(push_tags=False, multiarch=False): 89 | with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle: 90 | builddata = json.load(file_handle) 91 | 92 | if push_tags: 93 | login() 94 | 95 | for version, buildinfo in sorted(builddata.items(), key=lambda item: item[0], reverse=True): 96 | sha256 = buildinfo["sha256"] 97 | tags = buildinfo["tags"] 98 | build_and_push(sha256, version, tags, push_tags, multiarch) 99 | 100 | 101 | if __name__ == '__main__': 102 | push_tags = False 103 | multiarch = False 104 | for arg in sys.argv[1:]: 105 | if arg == "--push-tags": 106 | push_tags = True 107 | elif arg == "--multiarch": 108 | multiarch = True 109 | main(push_tags, multiarch) 110 | -------------------------------------------------------------------------------- /build_legacy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eoux pipefail 3 | 4 | if [[ -z ${1:-} && -n ${CI:-} ]]; then 5 | echo 'Usage: ./build.sh VERSION_SHORT' 6 | exit 1 7 | elif [[ ${CI:-} == true || -n ${1:-} ]]; then 8 | VERSION_SHORT="$1" 9 | else 10 | VERSION_SHORT=$(find . -maxdepth 1 -type d | sort | tail -1 | grep -o "[[0-9]].[[0-9]]*") 11 | EXTRA_TAG=latest 12 | fi 13 | 14 | cd "$VERSION_SHORT" || exit 1 15 | 16 | VERSION=$(grep -oP '[0-9]+\.[0-9]+\.[0-9]+' Dockerfile | head -1) 17 | DOCKER_REPO=factoriotools/factorio 18 | 19 | BRANCH=${GITHUB_REF#refs/*/} 20 | 21 | if [[ -n ${GITHUB_BASE_REF:-} ]]; then 22 | TAGS="-t $DOCKER_REPO:$GITHUB_BASE_REF" 23 | else 24 | if [[ -n ${CI:-} ]]; then 25 | # we are either on master or on a tag build 26 | if [[ ${BRANCH:-} == master || ${BRANCH:-} == "$VERSION" ]]; then 27 | TAGS="-t $DOCKER_REPO:$VERSION -t $DOCKER_REPO:$VERSION_SHORT" 28 | # we are on an incremental build of a tag 29 | elif [[ $VERSION == "${BRANCH%-*}" ]]; then 30 | TAGS="-t $DOCKER_REPO:$BRANCH -t $DOCKER_REPO:$VERSION -t $DOCKER_REPO:$VERSION_SHORT" 31 | # we build a other branch than master and exclude dependabot branches from tags cause the / is not supported by docker 32 | elif [[ -n ${BRANCH:-} && ! $BRANCH =~ "/" ]]; then 33 | TAGS="-t $DOCKER_REPO:$BRANCH" 34 | fi 35 | else 36 | # we are not in CI and tag version and version short 37 | TAGS="-t $DOCKER_REPO:$VERSION -t $DOCKER_REPO:$VERSION_SHORT" 38 | fi 39 | 40 | if [[ -n ${EXTRA_TAG:-} ]]; then 41 | IFS="," 42 | for TAG in $EXTRA_TAG; do 43 | TAGS+=" -t $DOCKER_REPO:$TAG" 44 | done 45 | fi 46 | 47 | if [[ ${STABLE:-} == "$VERSION" ]]; then 48 | TAGS+=" -t $DOCKER_REPO:stable" 49 | fi 50 | fi 51 | 52 | # Travis gets rate limited by Docker HUB. 53 | if [[ ${CI:-} == true && -n ${DOCKER_PASSWORD:-} && -n ${DOCKER_USERNAME:-} ]]; then 54 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin 55 | fi 56 | 57 | # shellcheck disable=SC2068 58 | eval docker build . ${TAGS[@]:-} 59 | docker images 60 | 61 | # remove -1 from incremental tag 62 | # eg before: 0.18.24-1, after 0.18.24 63 | if [[ ${BRANCH:-} ]]; then 64 | BRANCH_VERSION=${BRANCH%-*} 65 | fi 66 | 67 | # only push when: 68 | # or we build a tag and we don't build a PR 69 | if [[ $VERSION == "${BRANCH_VERSION:-}" && ${GITHUB_BASE_REF:-} == "" ]] || 70 | # or we are not in CI 71 | [[ -z ${CI:-} ]]; then 72 | 73 | if [[ ${CI:-} == true ]]; then 74 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin 75 | fi 76 | 77 | # push a tag on a branch other than master except dependabot branches cause docker does not support / 78 | if [[ -n ${BRANCH:-} && $VERSION != "${BRANCH_VERSION:-}" && ${BRANCH:-} != "master" && ! ${BRANCH:-} =~ "/" ]]; then 79 | docker push "$DOCKER_REPO:$BRANCH" 80 | fi 81 | 82 | # push an incremental tag 83 | # eg 0.18.24-1 84 | if [[ $VERSION == "${BRANCH_VERSION:-}" ]]; then 85 | docker push "$DOCKER_REPO:$BRANCH" 86 | fi 87 | 88 | # only push on tags or when manually running the script 89 | if [[ -n ${BRANCH_VERSION:-} || -z ${CI:-} ]]; then 90 | docker push "$DOCKER_REPO:$VERSION" 91 | docker push "$DOCKER_REPO:$VERSION_SHORT" 92 | fi 93 | 94 | if [[ -n ${EXTRA_TAG:-} ]]; then 95 | IFS="," 96 | for TAG in $EXTRA_TAG; do 97 | docker push "$DOCKER_REPO:$TAG" 98 | done 99 | fi 100 | 101 | if [[ ${STABLE:-} == "$VERSION" ]]; then 102 | docker push "$DOCKER_REPO:stable" 103 | fi 104 | fi 105 | -------------------------------------------------------------------------------- /buildinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.12.35": { 3 | "sha256": "ab9cf01a56dde3073aaaa5152c628bbf9a5bb85638b87dc3d7fdb77fb169aedd", 4 | "tags": [ 5 | "0.12.35", 6 | "0.12" 7 | ] 8 | }, 9 | "0.13.20": { 10 | "sha256": "cbf5481e4b7e0efcc07c7b6a1fc3ff1404ad5597f3c9d37914a52ffb58d7c159", 11 | "tags": [ 12 | "0.13.20", 13 | "0.13" 14 | ] 15 | }, 16 | "0.14.23": { 17 | "sha256": "96c3e7acd4e0f066a499baba01823cac7c1caf0e50dbddcea5793f57bd60dc8c", 18 | "tags": [ 19 | "0.14.23", 20 | "0.14" 21 | ] 22 | }, 23 | "0.15.40": { 24 | "sha256": "1041ef61ea4aecd1f425e6030a909f0c349a9c01d1b3324d84a61b1cfef5ba6c", 25 | "tags": [ 26 | "0.15.40", 27 | "0.15" 28 | ] 29 | }, 30 | "0.16.51": { 31 | "sha256": "6cb09f5ac87f16f8d5b43cef26c0ae26cc46a57a0382e253dfda032dc5bb367f", 32 | "tags": [ 33 | "0.16.51", 34 | "0.16" 35 | ] 36 | }, 37 | "0.17.79": { 38 | "sha256": "9ace12fa986df028dc1851bf4de2cb038044d743e98823bc1c48ba21aa4d23df", 39 | "tags": [ 40 | "0.17.79", 41 | "0.17" 42 | ] 43 | }, 44 | "1.0.0": { 45 | "sha256": "81d9e1aa94435aeec4131c8869fa6e9331726bea1ea31db750b65ba42dbd1464", 46 | "tags": [ 47 | "1.0.0", 48 | "1.0" 49 | ] 50 | }, 51 | "1.1.110": { 52 | "sha256": "485fe6db36e5decd7dd0d70e7c97e61f818100fa3e48d87884b287027c7a646a", 53 | "tags": [ 54 | "stable-1.1.110", 55 | "1", 56 | "1.1", 57 | "1.1.110" 58 | ] 59 | }, 60 | "2.0.13": { 61 | "sha256": "27b36901a39e593adf28418c0286142c6c7a9f83d156963c7369bd405a25c7d1", 62 | "tags": [ 63 | "stable-2.0.13", 64 | "2.0", 65 | "2.0.13" 66 | ] 67 | }, 68 | "2.0.14": { 69 | "sha256": "5a4bc4c3b2a97ed1fc58eb796321e848dcc64435bd91013dd9c78a14a8ce8815", 70 | "tags": [ 71 | "stable-2.0.14", 72 | "2.0", 73 | "2.0.14" 74 | ] 75 | }, 76 | "2.0.15": { 77 | "sha256": "70b441cb807811a60586c01107248c1d8d7ae043bd1f23675fc924fbaaa538d8", 78 | "tags": [ 79 | "stable-2.0.15", 80 | "2.0", 81 | "2.0.15" 82 | ] 83 | }, 84 | "2.0.16": { 85 | "sha256": "f2069b4b746500d945eeb67ef7eda5e7aebe7fd0294c2af4e117af22a3bbaea3", 86 | "tags": [ 87 | "2.0.16" 88 | ] 89 | }, 90 | "2.0.17": { 91 | "sha256": "183407f2fb21e05152442ffb5f15ffb283994339ca6a51b3559a257c30505e5e", 92 | "tags": [ 93 | "2.0.17" 94 | ] 95 | }, 96 | "2.0.18": { 97 | "sha256": "f378a1dc8a545c13d8ca616cbe72d245aa3ce93e3f219d8d60d3c06c7df82dc0", 98 | "tags": [ 99 | "2.0.18" 100 | ] 101 | }, 102 | "2.0.19": { 103 | "sha256": "2e27aca3a7f65b50916d14a62203b6861cbe657e8d2dbd8f813e0a606efce9c7", 104 | "tags": [ 105 | "2.0.19" 106 | ] 107 | }, 108 | "2.0.20": { 109 | "sha256": "c4a901f2f1dbedbb41654560db4c6fab683a30c20334e805d4ef740c0416515a", 110 | "tags": [ 111 | "stable-2.0.20", 112 | "2.0", 113 | "2.0.20" 114 | ] 115 | }, 116 | "2.0.21": { 117 | "sha256": "1d6d2785006d6a8d9d5fdcdaa7097a189ec35ba95f3521025dc4e046f7a1398e", 118 | "tags": [ 119 | "stable-2.0.21", 120 | "2.0", 121 | "2.0.21" 122 | ] 123 | }, 124 | "2.0.22": { 125 | "sha256": "14c3eea7600fbe7f35bca52fe4c277e8f5e23b34c35ebebaa46c6752c750cb85", 126 | "tags": [ 127 | "2.0.22" 128 | ] 129 | }, 130 | "2.0.23": { 131 | "sha256": "e819fc9ad6df061bf9d4bffc91988dd18d0e3982c8b1c22c0525d78bda3ef216", 132 | "tags": [ 133 | "stable-2.0.23", 134 | "2.0", 135 | "2.0.23" 136 | ] 137 | }, 138 | "2.0.24": { 139 | "sha256": "4644acc4195391fe19a7468c546d10a494ce1a188964c79f20cb0fa050b67120", 140 | "tags": [ 141 | "2.0.24" 142 | ] 143 | }, 144 | "2.0.25": { 145 | "sha256": "0d1698f1f29759ff27faa6a5d9c3804377cb1767f2692003a8e9d4c294845e5a", 146 | "tags": [ 147 | "2.0.25" 148 | ] 149 | }, 150 | "2.0.26": { 151 | "sha256": "a401024039372a53b9a29b7deb4ac279cd9a34abe69426a109a13a9a1c599f1f", 152 | "tags": [ 153 | "2.0.26" 154 | ] 155 | }, 156 | "2.0.27": { 157 | "sha256": "63c75ce74cd9d1e4b65ae9f98e9865abdbe3d600fb3259dcda5ea69a512b2993", 158 | "tags": [ 159 | "2.0.27" 160 | ] 161 | }, 162 | "2.0.28": { 163 | "sha256": "ea9937b6adc7a18e17a4e1e64992ec389407497b36e68280bb14fcdd4c884dd3", 164 | "tags": [ 165 | "stable-2.0.28", 166 | "2.0", 167 | "2.0.28" 168 | ] 169 | }, 170 | "2.0.29": { 171 | "sha256": "54088c9cacfddbce2e7bf90604fea095ff723e70d9bb056e1fb469b900a50f09", 172 | "tags": [ 173 | "2.0.29" 174 | ] 175 | }, 176 | "2.0.30": { 177 | "sha256": "4137824a20e1f3298410432c85e62d0eb46b0dab1a8411c233699f890d4c1668", 178 | "tags": [ 179 | "stable-2.0.30", 180 | "2.0", 181 | "2.0.30" 182 | ] 183 | }, 184 | "2.0.31": { 185 | "sha256": "0ee39ff6181ef41b606b7ba1ab5c04d8f81579ef56ec4947e4d74ce5d192b5d5", 186 | "tags": [ 187 | "2.0.31" 188 | ] 189 | }, 190 | "2.0.32": { 191 | "sha256": "2a6102ae42dcc5e8fe923bd68bcd326a569e35912acde121301e5d4d2d856417", 192 | "tags": [ 193 | "stable-2.0.32", 194 | "2.0", 195 | "2.0.32" 196 | ] 197 | }, 198 | "2.0.33": { 199 | "sha256": "9365a34d1724e5c9f592cc9da511485e2fa7da1c12df08029bce478586ba4b7b", 200 | "tags": [ 201 | "2.0.33" 202 | ] 203 | }, 204 | "2.0.34": { 205 | "sha256": "9511462203ebb2763f9f8623bb17f3070041ae3cbd7d80284c1e9bb38c09fc40", 206 | "tags": [ 207 | "2.0.34" 208 | ] 209 | }, 210 | "2.0.35": { 211 | "sha256": "31cd58eaf4b06cc0dc5d82640f7adf2366aa9da64133d2c228f1308f1060a990", 212 | "tags": [ 213 | "2.0.35" 214 | ] 215 | }, 216 | "2.0.36": { 217 | "sha256": "e94567b986654f1f7c3ec5c8bd151e3768b4ab9ab9cc389f6b9fd8e0dab32ce2", 218 | "tags": [ 219 | "2.0.36" 220 | ] 221 | }, 222 | "2.0.37": { 223 | "sha256": "5f105131fe4f48d47fd813f57b6bd275840a47b21e39b30d22bf5da30075a786", 224 | "tags": [ 225 | "2.0.37" 226 | ] 227 | }, 228 | "2.0.38": { 229 | "sha256": "ad9650f7456aecc8adb5369eedb418507c7643bede0da60fc1a239878d4902de", 230 | "tags": [ 231 | "2.0.38" 232 | ] 233 | }, 234 | "2.0.39": { 235 | "sha256": "0f8a3d0e43797b5ff4d8b85d7c334b095a3f07d9aa7f80b1e87f94939a93df34", 236 | "tags": [ 237 | "stable-2.0.39", 238 | "2.0", 239 | "2.0.39" 240 | ] 241 | }, 242 | "2.0.40": { 243 | "sha256": "eac1f24afb68acbfcf1d72d2ad142e8584d77f2d100a3af743f106e50ac176d3", 244 | "tags": [ 245 | "2.0.40" 246 | ] 247 | }, 248 | "2.0.41": { 249 | "sha256": "77ebccae8167fc1a9fc4da8c11e8410f6017b92b1a0913eb58ac5285c9eec399", 250 | "tags": [ 251 | "stable-2.0.41", 252 | "2.0", 253 | "2.0.41" 254 | ] 255 | }, 256 | "2.0.42": { 257 | "sha256": "b5b8b8bdc915e67dbc1710cd3d6aa6802d397b7c0f47db07da8acf39d5bd6376", 258 | "tags": [ 259 | "stable-2.0.42", 260 | "2.0", 261 | "2.0.42" 262 | ] 263 | }, 264 | "2.0.43": { 265 | "sha256": "bde6e167330c4439ce7df3ac519ea445120258ef676f1f6ad31d0c2816d3aee3", 266 | "tags": [ 267 | "stable-2.0.43", 268 | "2.0", 269 | "2.0.43" 270 | ] 271 | }, 272 | "2.0.44": { 273 | "sha256": "9468c5e07080c01eb7a734036160bf806d62cafc11465a23150cfbd210e1036d", 274 | "tags": [ 275 | "2.0.44" 276 | ] 277 | }, 278 | "2.0.45": { 279 | "sha256": "4fd7e04bb3ea7d12da8e1c3befc6b53b3c0064775c960a5a9db6a943f2259fc2", 280 | "tags": [ 281 | "2.0.45" 282 | ] 283 | }, 284 | "2.0.46": { 285 | "sha256": "fc611b6d4078b5d9448284c2890f7e0b6b1f203d52f622c655d3600982489c3e", 286 | "tags": [ 287 | "2.0.46" 288 | ] 289 | }, 290 | "2.0.47": { 291 | "sha256": "f0f320c77616a4794227eb637a70b557108f3141a4633276593220a768f49a26", 292 | "tags": [ 293 | "stable-2.0.47", 294 | "2.0", 295 | "2.0.47" 296 | ] 297 | }, 298 | "2.0.48": { 299 | "sha256": "f0038835e96bbacc19d52d22d47469882d9ebe41a4e5213c0471020647a1ee2d", 300 | "tags": [ 301 | "2.0.48" 302 | ] 303 | }, 304 | "2.0.49": { 305 | "sha256": "ef0648ca1ba44c145a3a3e4c174ccd276eb4a335155a20df1ae0e47156fa34ff", 306 | "tags": [ 307 | "2.0.49" 308 | ] 309 | }, 310 | "2.0.50": { 311 | "sha256": "81d4aec735473c5bd2c87f09abcd793c31cb9a07d9fdf3c3d7275c78ebe4bc18", 312 | "tags": [ 313 | "2.0.50" 314 | ] 315 | }, 316 | "2.0.51": { 317 | "sha256": "fc940dea67d25d3fd403531520e8afda2779ff1fa8050f535ac1351b7873a070", 318 | "tags": [ 319 | "2.0.51" 320 | ] 321 | }, 322 | "2.0.52": { 323 | "sha256": "be8d6216890089890693d6d94f141f745d35c53e52c6b942f6c944f5c00c8c26", 324 | "tags": [ 325 | "2.0.52" 326 | ] 327 | }, 328 | "2.0.53": { 329 | "sha256": "40a57076f80dbee0238dab62f16585def06f7d7e5b41f6b677be41b4d2cae811", 330 | "tags": [ 331 | "2.0.53" 332 | ] 333 | }, 334 | "2.0.54": { 335 | "sha256": "ad47c541b70763552bcf597202ee84aaac727d0ba158873134dc163a3a0506f0", 336 | "tags": [ 337 | "2.0.54" 338 | ] 339 | }, 340 | "2.0.55": { 341 | "sha256": "ef12a54d1556ae1f84ff99edc23706d13b7ad41f1c02d74ca1dfadf9448fcbae", 342 | "tags": [ 343 | "latest", 344 | "stable", 345 | "stable-2.0.55", 346 | "2", 347 | "2.0", 348 | "2.0.55" 349 | ] 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | factorio: 4 | container_name: factorio 5 | image: factoriotools/factorio:stable 6 | restart: unless-stopped 7 | ports: 8 | - "34197:34197/udp" 9 | - "27015:27015/tcp" 10 | volumes: 11 | - ./data:/factorio 12 | environment: 13 | - UPDATE_MODS_ON_START=true 14 | 15 | # Uncomment to enable autoupdate via watchtower 16 | #labels: 17 | # # Labels to allow watchtower autoupdate only if no players are online 18 | # - com.centurylinklabs.watchtower.enable=true 19 | # - com.centurylinklabs.watchtower.scope=factorio 20 | # - com.centurylinklabs.watchtower.lifecycle.pre-update="/players-online.sh" 21 | 22 | # Uncomment the following files to use watchtower for updating the factorio container 23 | # Full documentation of watchtower: https://github.com/containrrr/watchtower 24 | #watchtower: 25 | # container_name: watchtower_factorio 26 | # image: containrrr/watchtower 27 | # restart: unless-stopped 28 | # volumes: 29 | # - /var/run/docker.sock:/var/run/docker.sock 30 | # environment: 31 | # # Only update containers which have the option 'watchtower.enable=true' set 32 | # - WATCHTOWER_TIMEOUT=30s 33 | # - WATCHTOWER_LABEL_ENABLE=true 34 | # - WATCHTOWER_POLL_INTERVAL=3600 35 | # - WATCHTOWER_LIFECYCLE_HOOKS=true 36 | # - WATCHTOWER_SCOPE=factorio 37 | # labels: 38 | # - com.centurylinklabs.watchtower.scope=factorio 39 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # build rcon client 2 | FROM debian:stable-slim AS rcon-builder 3 | RUN apt-get -q update \ 4 | && DEBIAN_FRONTEND=noninteractive apt-get -qy install build-essential 5 | 6 | WORKDIR /src 7 | COPY rcon/ /src 8 | RUN make 9 | 10 | # build factorio image 11 | FROM debian:stable-slim 12 | LABEL maintainer="https://github.com/factoriotools/factorio-docker" 13 | 14 | ARG USER=factorio 15 | ARG GROUP=factorio 16 | ARG PUID=845 17 | ARG PGID=845 18 | ARG BOX64_VERSION=v0.2.4 19 | 20 | # optionally utilize a built-in map-gen-preset (see data/base/prototypes/map-gen-presets 21 | # if this is used, the preset will be used over any .json files supplied 22 | # vanilla factorio provides the following presets: 23 | # rich-resources, marathon, death-world, death-world-marathon, rail-world, ribbon-world, island 24 | # a modded factorio example for using this: 25 | # space-exploration 26 | ARG PRESET 27 | 28 | # number of retries that curl will use when pulling the headless server tarball 29 | ARG CURL_RETRIES=8 30 | 31 | ENV PORT=34197 \ 32 | RCON_PORT=27015 \ 33 | SAVES=/factorio/saves \ 34 | PRESET="$PRESET" \ 35 | CONFIG=/factorio/config \ 36 | MODS=/factorio/mods \ 37 | SCENARIOS=/factorio/scenarios \ 38 | SCRIPTOUTPUT=/factorio/script-output \ 39 | PUID="$PUID" \ 40 | PGID="$PGID" \ 41 | DLC_SPACE_AGE="true" 42 | 43 | SHELL ["/bin/bash", "-eo", "pipefail", "-c"] 44 | 45 | RUN apt-get -q update \ 46 | && DEBIAN_FRONTEND=noninteractive apt-get -qy install ca-certificates curl jq pwgen xz-utils procps gettext-base --no-install-recommends \ 47 | && if [[ "$(uname -m)" == "aarch64" ]]; then \ 48 | echo "installing ARM compatability layer" \ 49 | && DEBIAN_FRONTEND=noninteractive apt-get -qy install unzip --no-install-recommends \ 50 | && curl -LO https://github.com/ptitSeb/box64/releases/download/${BOX64_VERSION}/box64-GENERIC_ARM-RelWithDebInfo.zip \ 51 | && unzip box64-GENERIC_ARM-RelWithDebInfo.zip -d /bin \ 52 | && rm -f box64-GENERIC_ARM-RelWithDebInfo.zip \ 53 | && chmod +x /bin/box64; \ 54 | fi \ 55 | && rm -rf /var/lib/apt/lists/* 56 | 57 | RUN addgroup --system --gid "$PGID" "$GROUP" \ 58 | && adduser --system --uid "$PUID" --gid "$PGID" --no-create-home --disabled-password --shell /bin/sh "$USER" 59 | 60 | # version checksum of the archive to download 61 | ARG VERSION 62 | ARG SHA256 63 | 64 | LABEL factorio.version=${VERSION} 65 | 66 | ENV VERSION=${VERSION} \ 67 | SHA256=${SHA256} 68 | 69 | RUN set -ox pipefail \ 70 | && if [[ "${VERSION}" == "" ]]; then \ 71 | echo "build-arg VERSION is required" \ 72 | && exit 1; \ 73 | fi \ 74 | && if [[ "${SHA256}" == "" ]]; then \ 75 | echo "build-arg SHA256 is required" \ 76 | && exit 1; \ 77 | fi \ 78 | && archive="/tmp/factorio_headless_x64_$VERSION.tar.xz" \ 79 | && mkdir -p /opt /factorio \ 80 | && curl -sSL "https://www.factorio.com/get-download/$VERSION/headless/linux64" -o "$archive" --retry $CURL_RETRIES \ 81 | && echo "$SHA256 $archive" | sha256sum -c \ 82 | || (sha256sum "$archive" && file "$archive" && exit 1) \ 83 | && tar xf "$archive" --directory /opt \ 84 | && chmod ugo=rwx /opt/factorio \ 85 | && rm "$archive" \ 86 | && ln -s "$SCENARIOS" /opt/factorio/scenarios \ 87 | && ln -s "$SAVES" /opt/factorio/saves \ 88 | && mkdir -p /opt/factorio/config/ \ 89 | && chown -R "$USER":"$GROUP" /opt/factorio /factorio 90 | 91 | COPY files/*.sh / 92 | COPY files/config.ini /opt/factorio/config/config.ini 93 | COPY --from=rcon-builder /src/rcon /bin/rcon 94 | 95 | VOLUME /factorio 96 | EXPOSE $PORT/udp $RCON_PORT/tcp 97 | ENTRYPOINT ["/docker-entrypoint.sh"] 98 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | factorio: 4 | build: 5 | context: . 6 | args: 7 | # Check buildinfo.json for supported versions and SHAs 8 | # https://github.com/factoriotools/factorio-docker/blob/master/buildinfo.json 9 | - VERSION=2.0.55 10 | - SHA256=ef12a54d1556ae1f84ff99edc23706d13b7ad41f1c02d74ca1dfadf9448fcbae 11 | ports: 12 | - "34197:34197/udp" 13 | - "27015:27015/tcp" 14 | volumes: 15 | - /opt/factorio:/factorio 16 | # environment: 17 | # - PUID=1000 18 | # - PGID=1000 19 | # - UPDATE_MODS_ON_START=true 20 | # - USERNAME=FactorioUsername 21 | # - TOKEN=FactorioToken 22 | # - PORT=34198 23 | # - PRESET=deathworld 24 | # - ADDR=::1 25 | # # Uncomment the following line to enable the use of the host's network stack, 26 | # # which may be necessary for some setups like NAS or when using some proxy service like firewall rules. 27 | # extra_hosts: 28 | # - "host.docker.internal:host-gateway" 29 | -------------------------------------------------------------------------------- /docker/files/config.ini: -------------------------------------------------------------------------------- 1 | ; version=8 2 | ; This is INI file : https://en.wikipedia.org/wiki/INI_file#Format 3 | ; Semicolons (;) at the beginning of the line indicate a comment. Comment lines are ignored. 4 | [path] 5 | read-data=__PATH__executable__/../../data 6 | write-data=__PATH__executable__/../.. 7 | 8 | [general] 9 | locale= 10 | 11 | [other] 12 | ; Options: true, false 13 | ; verbose-logging=false 14 | 15 | ; Options: true, false 16 | ; log-saving-statistics=false 17 | 18 | ; autosave-interval=5 19 | 20 | ; autosave-slots=3 21 | 22 | ; In ticks 23 | ; minimum-latency-in-multiplayer=0 24 | 25 | ; In seconds 26 | ; multiplayer-initial-connection-timeout=10 27 | 28 | ; port=34197 29 | 30 | ; max-map-preview-chunk-side=64 31 | 32 | ; max-map-preview-threads=7 33 | 34 | ; In bytes 35 | ; max-multiplayer-script-reload-size=1048576 36 | 37 | ; Options: true, false 38 | ; enable-steam-networking=true 39 | 40 | ; proxy= 41 | 42 | ; proxy-username= 43 | 44 | ; proxy-password= 45 | 46 | ; Options: true, false 47 | ; check-updates=true 48 | 49 | ; Options: true, false 50 | ; enable-experimental-updates=false 51 | 52 | ; Options: true, false 53 | ; enable-new-mods=true 54 | 55 | ; Options: true, false 56 | ; use-mod-settings-per-save=true 57 | 58 | ; Options: true, false 59 | ; disable-minimal-mode=false 60 | 61 | ; Options: true, false 62 | ; disable-blueprint-storage=false 63 | 64 | ; Disables tracking which mod created/changed what prototype. Mainly for faster startup during development. 65 | ; 66 | ; Options: true, false 67 | ; disable-prototype-history=false 68 | 69 | ; Print a warning for all prototype values that were not accessed. 70 | ; 71 | ; Options: true, false 72 | ; check-unused-prototype-data=false 73 | 74 | ; Cache data stage prototype data for faster startup. Experimental. 75 | ; 76 | ; Options: true, false 77 | ; cache-prototype-data=false 78 | 79 | ; Options: true, false 80 | ; enable-razer-chroma-support=true 81 | 82 | ; Options: true, false 83 | ; enable-logitech-led-support=true 84 | 85 | ; Options: true, false 86 | ; enable-crash-log-uploading=true 87 | 88 | ; Options: true, false 89 | ; enable-heap-validation=true 90 | 91 | ; Options: true, false 92 | ; enable-threaded-message-pump=true 93 | 94 | ; Options: true, false 95 | ; enable-taskbar-animation=true 96 | 97 | ; Does nothing on Windows 98 | ; 99 | ; Options: true, false 100 | ; non-blocking-saving=false 101 | 102 | ; Related to MacOS 103 | ; 104 | ; Options: true, false 105 | ; discard-mouse-events-when-accessibility-zoomed=false 106 | 107 | ; Options: true, false 108 | ; enable-blueprint-storage-cloud-sync=false 109 | 110 | ; Options: true, false 111 | ; force-enable-factorio-version-check=false 112 | 113 | ; Options: true, false 114 | ; bring-window-to-top-on-click=true 115 | 116 | ; Options: fast, maximum 117 | ; multiplayer-compression-level=fast 118 | 119 | ; Options: none, fast, maximum 120 | ; autosave-compression-level=fast 121 | 122 | ; Socket to host RCON on when lauching MP server from the menu. 123 | ; local-rcon-socket=0.0.0.0:0 124 | 125 | ; Password for RCON when launching MP server from the menu. 126 | ; local-rcon-password= 127 | 128 | 129 | [interface] 130 | ; Options: true, false 131 | ; automatic-ui-scale=true 132 | 133 | ; custom-ui-scale=1.000000 134 | 135 | ; tooltip-delay=0.040000 136 | 137 | ; entity-tooltip-delay=0.000000 138 | 139 | ; tooltip-offset=20 140 | 141 | ; output-console-delay=1200 142 | 143 | ; train-stop-label-angle=0.085526 144 | 145 | ; active-quick-bars=2 146 | 147 | ; shortcut-bar-rows=2 148 | 149 | ; Options: true, false 150 | ; autosort-inventory=true 151 | 152 | ; Options: true, false 153 | ; research-finished-stops-game=false 154 | 155 | ; Options: true, false 156 | ; use-item-groups=true 157 | 158 | ; Options: true, false 159 | ; use-item-subgroups=true 160 | 161 | ; Options: true, false 162 | ; use-version-filter-in-browse-games-gui=true 163 | 164 | ; Options: true, false 165 | ; use-version-filter-in-install-mods-gui=true 166 | 167 | ; Options: true, false 168 | ; play-sound-for-chat-messages=true 169 | 170 | ; Options: true, false 171 | ; fuzzy-search-enabled=false 172 | 173 | ; Options: true, false 174 | ; pick-ghost-cursor=false 175 | 176 | ; Options: true, false 177 | ; show-minimap=true 178 | 179 | ; Options: true, false 180 | ; show-tips-and-tricks=true 181 | 182 | ; Options: true, false 183 | ; show-tutorial-notifications=true 184 | 185 | ; Options: true, false 186 | ; show-turret-radius-when-blueprinting=false 187 | 188 | ; Options: true, false 189 | ; show-item-labels-in-cursor=true 190 | 191 | ; Options: true, false 192 | ; show-rail-block-visualization=true 193 | 194 | ; Options: true, false 195 | ; show-missing-logistic-network-icon=true 196 | 197 | ; Options: true, false 198 | ; show-interaction-indications=true 199 | 200 | ; Options: true, false 201 | ; show-grid-when-paused=true 202 | 203 | ; Options: true, false 204 | ; show-inserter-arrows-when-selected=true 205 | 206 | ; Options: true, false 207 | ; show-inserter-arrows-when-detailed-info-is-on=false 208 | 209 | ; Options: true, false 210 | ; show-pump-arrows-when-detailed-info-is-on=true 211 | 212 | ; Options: true, false 213 | ; show-mining-drill-arrows-when-detailed-info-is-on=true 214 | 215 | ; Options: true, false 216 | ; show-combinator-settings-when-detailed-info-is-on=false 217 | 218 | ; Options: true, false 219 | ; entity-tooltip-on-the-side=true 220 | 221 | ; Options: true, false 222 | ; show-mod-owners-in-tooltips=true 223 | 224 | ; Options: true, false 225 | ; show-descriptions-in-tooltips=true 226 | 227 | ; Options: true, false 228 | ; show-total-raw-in-recipe-tooltips=true 229 | 230 | ; debug-font-size=18 231 | 232 | ; train-visualization-length=5 233 | 234 | 235 | [sound] 236 | ; master-volume=0.800000 237 | 238 | ; music-volume=0.500000 239 | 240 | ; game-effects-volume=0.700000 241 | 242 | ; gui-effects-volume=0.600000 243 | 244 | ; walking-sound-volume=0.250000 245 | 246 | ; environment-sounds-volume=0.550000 247 | 248 | ; alerts-volume=0.500000 249 | 250 | ; wind-volume=0.350000 251 | 252 | ; audible-distance=40.000000 253 | 254 | ; environment-audible-distance=30.000000 255 | 256 | ; maximum-environment-sounds=50 257 | 258 | ; active-gui-volume-modifier=0.800000 259 | 260 | ; active-gui-environment-volume-modifier=0.400000 261 | 262 | ; The maximum volume allowed for any sound. 263 | ; maximum-volume=2.000000 264 | 265 | ; ambient-music-pause-mean-seconds=45.000000 266 | 267 | ; ambient-music-pause-variance-seconds=30.000000 268 | 269 | ; Options: main-tracks-only, interleave-main-tracks-with-interludes, randomize-all 270 | ; ambient-music-mode=interleave-main-tracks-with-interludes 271 | 272 | ; zoom-audible-distance-coefficient=0.500000 273 | 274 | ; zoom-volume-coefficient=0.750000 275 | 276 | 277 | [map-view] 278 | ; Options: true, false 279 | ; show-logistic-network=false 280 | 281 | ; Options: true, false 282 | ; show-electric-network=false 283 | 284 | ; Options: true, false 285 | ; show-turret-range=false 286 | 287 | ; Options: true, false 288 | ; show-pollution=true 289 | 290 | ; Options: true, false 291 | ; show-networkless-logistic-members=false 292 | 293 | ; Options: true, false 294 | ; show-train-station-names=true 295 | 296 | ; Options: true, false 297 | ; show-player-names=true 298 | 299 | ; Options: true, false 300 | ; show-non-standard-map-info=false 301 | 302 | 303 | [debug] 304 | ; force=enemy 305 | 306 | ; Options: true, false 307 | ; capture-perf-statistics=false 308 | 309 | ; Options: always, debug, never 310 | ; show-fps=debug 311 | 312 | ; Options: always, debug, never 313 | ; show-detailed-info=debug 314 | 315 | ; Options: always, debug, never 316 | ; show-time-usage=debug 317 | 318 | ; Options: always, debug, never 319 | ; show-gpu-time-usage=debug 320 | 321 | ; Options: always, debug, never 322 | ; show-sprite-counts=never 323 | 324 | ; Options: always, debug, never 325 | ; show-lua-object-statistics=never 326 | 327 | ; Options: always, debug, never 328 | ; show-heat-buffer-info=never 329 | 330 | ; Options: always, debug, never 331 | ; show-multiplayer-waiting-icon=debug 332 | 333 | ; Options: always, debug, never 334 | ; show-multiplayer-statistics=debug 335 | 336 | ; Options: always, debug, never 337 | ; show-multiplayer-selection-rectangles=never 338 | 339 | ; Options: always, debug, never 340 | ; show-debug-info-in-tooltips=debug 341 | 342 | ; Options: always, debug, never 343 | ; hide-mod-guis=never 344 | 345 | ; Options: always, debug, never 346 | ; show-tile-grid=never 347 | 348 | ; Options: always, debug, never 349 | ; show-collision-rectangles=never 350 | 351 | ; Options: always, debug, never 352 | ; show-selection-rectangles=never 353 | 354 | ; Options: always, debug, never 355 | ; show-render-rectangles=never 356 | 357 | ; Options: always, debug, never 358 | ; show-entity-positions=never 359 | 360 | ; Options: always, debug, never 361 | ; show-entity-velocities=never 362 | 363 | ; Options: always, debug, never 364 | ; show-selected-entity-advanced-tiles=never 365 | 366 | ; Options: always, debug, never 367 | ; show-selected-input-transport-belts=never 368 | 369 | ; Options: always, debug, never 370 | ; show-paths=never 371 | 372 | ; Options: always, debug, never 373 | ; show-path-requests=never 374 | 375 | ; Options: always, debug, never 376 | ; show-next-waypoint-bb=never 377 | 378 | ; Options: always, debug, never 379 | ; show-target=never 380 | 381 | ; Options: always, debug, never 382 | ; show-unit-group-info=never 383 | 384 | ; Options: always, debug, never 385 | ; show-unit-behavior-info=never 386 | 387 | ; Options: always, debug, never 388 | ; show-pathfinder-fringe=never 389 | 390 | ; Options: always, debug, never 391 | ; show-path-cache=never 392 | 393 | ; Options: always, debug, never 394 | ; show-path-cache-paths=never 395 | 396 | ; Options: always, debug, never 397 | ; show-rail-paths=never 398 | 399 | ; Options: always, debug, never 400 | ; show-rolling-stock-count=never 401 | 402 | ; Options: always, debug, never 403 | ; show-rail-connections=never 404 | 405 | ; Options: always, debug, never 406 | ; show-rail-joints=never 407 | 408 | ; Options: always, debug, never 409 | ; show-rail-signal-states=never 410 | 411 | ; Options: always, debug, never 412 | ; show-rail-segment-collision-boxes=never 413 | 414 | ; Options: always, debug, never 415 | ; show-train-stop-point=never 416 | 417 | ; Options: always, debug, never 418 | ; show-train-braking-distance=never 419 | 420 | ; Options: always, debug, never 421 | ; show-train-signals=never 422 | 423 | ; Options: always, debug, never 424 | ; show-train-repathing=never 425 | 426 | ; Options: always, debug, never 427 | ; show-network-connected-entities=never 428 | 429 | ; Options: always, debug, never 430 | ; show-circuit-network-numbers=never 431 | 432 | ; Options: always, debug, never 433 | ; show-energy-sources-networks=never 434 | 435 | ; Options: always, debug, never 436 | ; show-active-state=never 437 | 438 | ; Options: always, debug, never 439 | ; show-wakeup-lists=never 440 | 441 | ; Options: always, debug, never 442 | ; show-transport-lines=never 443 | 444 | ; Options: always, debug, never 445 | ; show-transport-line-gaps=never 446 | 447 | ; Options: always, debug, never 448 | ; show-pollution-values=never 449 | 450 | ; Options: always, debug, never 451 | ; show-active-entities-on-chunk-counts=never 452 | 453 | ; Options: always, debug, never 454 | ; show-active-chunks=never 455 | 456 | ; Options: always, debug, never 457 | ; show-polluted-chunks=never 458 | 459 | ; Options: always, debug, never 460 | ; hide-chart-tags=never 461 | 462 | ; Options: always, debug, never 463 | ; show-enemy-expansion-candidate-chunks=never 464 | 465 | ; Options: always, debug, never 466 | ; show-enemy-expansion-candidate-chunk-values=never 467 | 468 | ; Options: always, debug, never 469 | ; show-bad-attack-chunks=never 470 | 471 | ; Options: always, debug, never 472 | ; show-tile-variations=never 473 | 474 | ; Options: always, debug, never 475 | ; show-raw-tile-transitions=never 476 | 477 | ; Options: always, debug, never 478 | ; show-fluid-box-fluid-info=never 479 | 480 | ; Options: always, debug, never 481 | ; show-environment-sound-info=never 482 | 483 | ; Options: always, debug, never 484 | ; show-environment-sound-area=never 485 | 486 | ; Options: always, debug, never 487 | ; show-selected-entity-audible-range=never 488 | 489 | ; Options: always, debug, never 490 | ; show-recently-played-sound-info=never 491 | 492 | ; Options: always, debug, never 493 | ; show-logistic-robot-targets=never 494 | 495 | ; Options: always, debug, never 496 | ; show-logistic-robots-on-map=never 497 | 498 | ; Options: always, debug, never 499 | ; show-recipe-icons-on-map=never 500 | 501 | ; Options: always, debug, never 502 | ; show-player-robots=never 503 | 504 | ; Options: always, debug, never 505 | ; show-fire-info=never 506 | 507 | ; Options: always, debug, never 508 | ; show-sticker-info=never 509 | 510 | ; Options: always, debug, never 511 | ; show-decorative-names=never 512 | 513 | ; Options: always, debug, never 514 | ; show-decorative-collision-rectangles=never 515 | 516 | ; Options: always, debug, never 517 | ; allow-increased-zoom=never 518 | 519 | ; Options: always, debug, never 520 | ; show-chunk-components=never 521 | 522 | 523 | [multiplayer-lobby] 524 | ; name= 525 | 526 | ; description= 527 | 528 | ; Options: true, false 529 | ; visibility-public=true 530 | 531 | ; Options: true, false 532 | ; visibility-steam=true 533 | 534 | ; Options: true, false 535 | ; visibility-lan=true 536 | 537 | ; max-players=0 538 | 539 | ; Options: true, false 540 | ; ignore-player-limit-when-returning=false 541 | 542 | ; max-upload-in-kilobytes-per-second=0 543 | 544 | ; max-upload-slots=5 545 | 546 | ; password= 547 | 548 | ; tag-list= 549 | 550 | ; afk-auto-kick=0 551 | 552 | ; Options: true, false, admins-only 553 | ; allowed-commands=admins-only 554 | 555 | ; Options: true, false 556 | ; only-admins-can-pause=true 557 | 558 | ; Options: true, false 559 | ; autosave-only-on-server=true 560 | 561 | ; Options: true, false 562 | ; non-blocking-saving=true 563 | 564 | ; Options: true, false 565 | ; verify-user-identity=true 566 | 567 | ; Options: true, false 568 | ; enable-whitelist=false 569 | 570 | 571 | [graphics] 572 | ; lights-render-quality=0.250000 573 | 574 | ; Default preferred display index should force finding primary monitor 575 | ; preferred-display-index=255 576 | 577 | ; screenshots-threads-count=8 578 | 579 | ; cache-sprite-atlas-count=1 580 | 581 | ; Options: true, false 582 | ; cache-sprite-atlas=false 583 | 584 | ; Options: true, false 585 | ; compress-sprite-atlas-cache=false 586 | 587 | ; Options: true, false 588 | ; texture-streaming=true 589 | 590 | ; streamed-atlas-physical-vram-size=0 591 | 592 | ; sprite-vertex-buffer-size=1048576 593 | 594 | ; max-texture-size=0 595 | 596 | ; max-threads=8 597 | 598 | ; 'low' and 'very-low' options are deprecated and will be migrated to 'normal' 599 | ; 600 | ; Options: high, normal, low, very-low 601 | ; graphics-quality=normal 602 | 603 | ; brightness=0 604 | 605 | ; contrast=0 606 | 607 | ; saturation=100 608 | 609 | ; Options: true, false 610 | ; full-screen=true 611 | 612 | ; Options: true, false 613 | ; minimize-on-focus-loss=false 614 | 615 | ; Options: true, false 616 | ; show-smoke=true 617 | 618 | ; Options: true, false 619 | ; show-clouds=true 620 | 621 | ; Options: true, false 622 | ; show-decoratives=true 623 | 624 | ; Options: true, false 625 | ; show-particles=true 626 | 627 | ; Options: true, false 628 | ; show-item-shadows=true 629 | 630 | ; Options: true, false 631 | ; show-inserter-shadows=true 632 | 633 | ; Options: true, false 634 | ; show-animated-water=true 635 | 636 | ; Options: true, false 637 | ; show-tree-distortion=true 638 | 639 | ; Options: true, false 640 | ; force-opengl=false 641 | 642 | ; Options: true, false 643 | ; v-sync=true 644 | 645 | ; Options: true, false 646 | ; high-quality-animations=true 647 | 648 | ; Options: true, false 649 | ; high-quality-shadows=false 650 | 651 | ; Options: true, false 652 | ; high-quality-terrain=true 653 | 654 | ; Minimum number of turrets required to turn on the turret range overdraw optimization 655 | ; turret-overdraw-minimum-count=4 656 | 657 | ; Scale at which the turret range overdraw optimization will start being applied 658 | ; turret-overdraw-scale-threshold=0.200000 659 | 660 | ; Options: true, false 661 | ; skip-vram-detection=false 662 | 663 | ; Options: true, false 664 | ; halt-rendering-when-minimized=true 665 | 666 | ; Options: true, false 667 | ; runtime-sprite-reload=false 668 | 669 | ; Options: true, false 670 | ; full-color-depth=true 671 | 672 | ; Options: true, false 673 | ; render-in-native-resolution=true 674 | 675 | ; Options: true, false 676 | ; use-flip-presentation-model=false 677 | 678 | ; Options: true, false 679 | ; debug-api=false 680 | 681 | ; Options: true, false 682 | ; discard-buffers-on-begin-frame=true 683 | 684 | ; Options: all, high, medium, low 685 | ; video-memory-usage=high 686 | 687 | ; Options: none, high-quality, low-quality 688 | ; texture-compression-level=high-quality 689 | 690 | ; Options: true, false 691 | ; compress-virtual-atlas=true 692 | 693 | ; Options: copy, copy-sequential, flip, flip-discard 694 | ; dxgi-presentation-model=copy 695 | 696 | ; Options: none, flush, wait-for-vblank, flush-and-wait-for-vblank 697 | ; dxgi-action-before-present=none 698 | 699 | ; relevant only for flip presentation models 700 | ; 701 | ; Options: true, false 702 | ; dxgi-allow-tearing=false 703 | 704 | ; Options: false, true, auto 705 | ; dxgi-flip-do-not-wait=false 706 | 707 | ; Options: true, false 708 | ; dxgi-present-restart=false 709 | 710 | ; dxgi-swap-chain-buffer-count=0 711 | 712 | ; dxgi-max-frame-latency=0 713 | 714 | ; dxgi-adapter-index=-1 715 | 716 | ; max-sprite-loading-threads=32 717 | 718 | ; Options: true, false 719 | ; gpu-accelerated-compression=true 720 | 721 | ; Options: true, false 722 | ; gpu-accelerated-mipmap-compression=true 723 | 724 | ; Options: true, false 725 | ; wait-until-mipmap-generation-finished=true 726 | 727 | ; Options: true, false 728 | ; check-for-unused-pixels=false 729 | 730 | ; ogl-depth-buffer-bit-depth=0 731 | 732 | ; Options: false, true, auto 733 | ; ogl-accelerated-renderer=auto 734 | 735 | ; Options: true, false 736 | ; ogl-double-buffered=true 737 | 738 | ; Set to true if mipmapped sprites render very blurry on your GPU. Limited support. 739 | ; 740 | ; Options: true, false 741 | ; legacy-gpu-no-mipmaps=false 742 | 743 | ; Options: true, false 744 | ; force-linear-magnification=false 745 | 746 | ; Options: true, false 747 | ; custom-mipmap-workaround=false 748 | 749 | ; Options: true, false 750 | ; buffer-rename-workaround=false 751 | 752 | ; Comma separated list of OpenGL extensions that should not be used (for example: ARB_copy_image,KHR_debug) 753 | ; disabled-opengl-extensions= 754 | 755 | 756 | -------------------------------------------------------------------------------- /docker/files/docker-dlc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eou pipefail 3 | 4 | # Path to the mod-list.json file 5 | MOD_LIST_FILE="$MODS/mod-list.json" 6 | 7 | ALL_SPACE_AGE_MODS=("elevated-rails" "quality" "space-age") 8 | 9 | if [[ ! -f "$MOD_LIST_FILE" ]]; then 10 | # Create the mod-list.json file if it doesn't exist 11 | echo '{"mods":[{"name":"base","enabled":true}]}' > "$MOD_LIST_FILE" 12 | fi 13 | 14 | enable_mod() 15 | { 16 | echo "Enable mod $1 for DLC" 17 | jq --arg mod_name "$1" 'if .mods | map(.name) | index($mod_name) then .mods |= map(if .name == $mod_name and .enabled == false then .enabled = true else . end) else . end' "$MOD_LIST_FILE" > "$MOD_LIST_FILE.tmp" 18 | mv "$MOD_LIST_FILE.tmp" "$MOD_LIST_FILE" 19 | } 20 | 21 | disable_mod() 22 | { 23 | echo "Disable mod $1 for DLC" 24 | jq --arg mod_name "$1" 'if .mods | map(.name) | index($mod_name) then .mods |= map(if .name == $mod_name and .enabled == true then .enabled = false else . end) else .mods += [{"name": $mod_name, "enabled": false}] end' "$MOD_LIST_FILE" > "$MOD_LIST_FILE.tmp" 25 | mv "$MOD_LIST_FILE.tmp" "$MOD_LIST_FILE" 26 | } 27 | 28 | # Enable or disable DLCs if configured 29 | if [[ ${DLC_SPACE_AGE:-} == "true" ]]; then 30 | # Define the DLC mods 31 | ENABLE_MODS=(${ALL_SPACE_AGE_MODS[@]}) 32 | elif [[ ${DLC_SPACE_AGE:-} == "false" ]]; then 33 | # Define the DLC mods 34 | DISABLE_MODS=(${ALL_SPACE_AGE_MODS[@]}) 35 | else 36 | ENABLE_MODS=() 37 | DISABLE_MODS=() 38 | 39 | for SPACE_AGE_MOD in "${ALL_SPACE_AGE_MODS[@]}"; do 40 | REGEX="(^|\s)$SPACE_AGE_MOD($|\s)" 41 | if [[ "$DLC_SPACE_AGE" =~ $REGEX ]]; then 42 | ENABLE_MODS+=($SPACE_AGE_MOD) 43 | else 44 | DISABLE_MODS+=($SPACE_AGE_MOD) 45 | fi 46 | done 47 | fi 48 | 49 | # Iterate over each DLC mod to enable 50 | for MOD in "${ENABLE_MODS[@]}"; do 51 | enable_mod "$MOD" 52 | done 53 | 54 | # Iterate over each DLC mod to disable 55 | for MOD in "${DISABLE_MODS[@]}"; do 56 | disable_mod "$MOD" 57 | done 58 | -------------------------------------------------------------------------------- /docker/files/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eoux pipefail 3 | INSTALLED_DIRECTORY=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") 4 | FACTORIO_VOL=/factorio 5 | LOAD_LATEST_SAVE="${LOAD_LATEST_SAVE:-true}" 6 | GENERATE_NEW_SAVE="${GENERATE_NEW_SAVE:-false}" 7 | PRESET="${PRESET:-""}" 8 | SAVE_NAME="${SAVE_NAME:-""}" 9 | BIND="${BIND:-""}" 10 | CONSOLE_LOG_LOCATION="${CONSOLE_LOG_LOCATION:-""}" 11 | 12 | mkdir -p "$FACTORIO_VOL" 13 | mkdir -p "$SAVES" 14 | mkdir -p "$CONFIG" 15 | mkdir -p "$MODS" 16 | mkdir -p "$SCENARIOS" 17 | mkdir -p "$SCRIPTOUTPUT" 18 | 19 | if [[ ! -f $CONFIG/rconpw ]]; then 20 | # Generate a new RCON password if none exists 21 | pwgen 15 1 >"$CONFIG/rconpw" 22 | fi 23 | 24 | if [[ ! -f $CONFIG/server-settings.json ]]; then 25 | # Copy default settings if server-settings.json doesn't exist 26 | cp /opt/factorio/data/server-settings.example.json "$CONFIG/server-settings.json" 27 | fi 28 | 29 | if [[ ! -f $CONFIG/map-gen-settings.json ]]; then 30 | cp /opt/factorio/data/map-gen-settings.example.json "$CONFIG/map-gen-settings.json" 31 | fi 32 | 33 | if [[ ! -f $CONFIG/map-settings.json ]]; then 34 | cp /opt/factorio/data/map-settings.example.json "$CONFIG/map-settings.json" 35 | fi 36 | 37 | NRTMPSAVES=$( find -L "$SAVES" -iname \*.tmp.zip -mindepth 1 | wc -l ) 38 | if [[ $NRTMPSAVES -gt 0 ]]; then 39 | # Delete incomplete saves (such as after a forced exit) 40 | rm -f "$SAVES"/*.tmp.zip 41 | fi 42 | 43 | if [[ ${UPDATE_MODS_ON_START:-} == "true" ]]; then 44 | ${INSTALLED_DIRECTORY}/docker-update-mods.sh 45 | fi 46 | 47 | ${INSTALLED_DIRECTORY}/docker-dlc.sh 48 | 49 | EXEC="" 50 | if [[ $(id -u) == 0 ]]; then 51 | # Update the User and Group ID based on the PUID/PGID variables 52 | usermod -o -u "$PUID" factorio 53 | groupmod -o -g "$PGID" factorio 54 | # Take ownership of factorio data if running as root 55 | chown -R factorio:factorio "$FACTORIO_VOL" 56 | # Drop to the factorio user 57 | EXEC="runuser -u factorio -g factorio --" 58 | fi 59 | if [[ -f /bin/box64 ]]; then 60 | # Use an emulator to run on ARM hosts 61 | # this only gets installed when the target docker platform is linux/arm64 62 | EXEC="$EXEC /bin/box64" 63 | fi 64 | 65 | sed -i '/write-data=/c\write-data=\/factorio/' /opt/factorio/config/config.ini 66 | 67 | NRSAVES=$(find -L "$SAVES" -iname \*.zip -mindepth 1 | wc -l) 68 | if [[ $GENERATE_NEW_SAVE != true && $NRSAVES == 0 ]]; then 69 | GENERATE_NEW_SAVE=true 70 | SAVE_NAME=_autosave1 71 | fi 72 | 73 | if [[ $GENERATE_NEW_SAVE == true ]]; then 74 | if [[ -z "$SAVE_NAME" ]]; then 75 | echo "If \$GENERATE_NEW_SAVE is true, you must specify \$SAVE_NAME" 76 | exit 1 77 | fi 78 | if [[ -f "$SAVES/$SAVE_NAME.zip" ]]; then 79 | echo "Map $SAVES/$SAVE_NAME.zip already exists, skipping map generation" 80 | else 81 | if [[ ! -z "$PRESET" ]]; then 82 | $EXEC /opt/factorio/bin/x64/factorio \ 83 | --create "$SAVES/$SAVE_NAME.zip" \ 84 | --preset "$PRESET" \ 85 | --map-gen-settings "$CONFIG/map-gen-settings.json" \ 86 | --map-settings "$CONFIG/map-settings.json" 87 | else 88 | $EXEC /opt/factorio/bin/x64/factorio \ 89 | --create "$SAVES/$SAVE_NAME.zip" \ 90 | --map-gen-settings "$CONFIG/map-gen-settings.json" \ 91 | --map-settings "$CONFIG/map-settings.json" 92 | fi 93 | fi 94 | fi 95 | 96 | FLAGS=(\ 97 | --port "$PORT" \ 98 | --server-settings "$CONFIG/server-settings.json" \ 99 | --server-banlist "$CONFIG/server-banlist.json" \ 100 | --rcon-port "$RCON_PORT" \ 101 | --server-whitelist "$CONFIG/server-whitelist.json" \ 102 | --use-server-whitelist \ 103 | --server-adminlist "$CONFIG/server-adminlist.json" \ 104 | --rcon-password "$(cat "$CONFIG/rconpw")" \ 105 | --server-id /factorio/config/server-id.json \ 106 | --mod-directory "$MODS" \ 107 | ) 108 | 109 | if [ -n "$CONSOLE_LOG_LOCATION" ]; then 110 | FLAGS+=( --console-log "$CONSOLE_LOG_LOCATION" ) 111 | fi 112 | 113 | if [ -n "$BIND" ]; then 114 | FLAGS+=( --bind "$BIND" ) 115 | fi 116 | 117 | if [[ $LOAD_LATEST_SAVE == true ]]; then 118 | FLAGS+=( --start-server-load-latest ) 119 | else 120 | FLAGS+=( --start-server "$SAVE_NAME" ) 121 | fi 122 | 123 | # shellcheck disable=SC2086 124 | exec $EXEC /opt/factorio/bin/x64/factorio "${FLAGS[@]}" "$@" 125 | -------------------------------------------------------------------------------- /docker/files/docker-update-mods.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eou pipefail 3 | 4 | if [[ -f /run/secrets/username ]]; then 5 | USERNAME=$(cat /run/secrets/username) 6 | fi 7 | 8 | if [[ -f /run/secrets/token ]]; then 9 | TOKEN=$(cat /run/secrets/token) 10 | fi 11 | 12 | if [[ -z ${USERNAME:-} ]]; then 13 | USERNAME="$(jq -j ".username" "$CONFIG/server-settings.json")" 14 | fi 15 | 16 | if [[ -z ${TOKEN:-} ]]; then 17 | TOKEN="$(jq -j ".token" "$CONFIG/server-settings.json")" 18 | fi 19 | 20 | if [[ -z ${USERNAME:-} ]]; then 21 | echo "You need to provide your Factorio username to update mods." 22 | fi 23 | 24 | if [[ -z ${TOKEN:-} ]]; then 25 | echo "You need to provide your Factorio token to update mods." 26 | fi 27 | 28 | ./update-mods.sh "$VERSION" "$MODS" "$USERNAME" "$TOKEN" 29 | -------------------------------------------------------------------------------- /docker/files/players-online.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PLAYERS=$(rcon /players) 4 | ONLINE_COUNT=$(echo "$PLAYERS" | grep -c " (online)$") 5 | 6 | if [[ "$ONLINE_COUNT" -gt "0" ]]; then 7 | echo "$PLAYERS" 8 | # exit with 75 (EX_TEMPFAIL) so watchtower skips the update 9 | # https://containrrr.dev/watchtower/lifecycle-hooks/ 10 | exit 75 11 | fi 12 | -------------------------------------------------------------------------------- /docker/files/scenario.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eoux pipefail 3 | 4 | if [[ -z ${1:-} ]]; then 5 | echo "No argument supplied" 6 | fi 7 | 8 | SERVER_SCENARIO="$1" 9 | PRESET="${PRESET:-""}" 10 | 11 | mkdir -p "$SAVES" 12 | mkdir -p "$CONFIG" 13 | mkdir -p "$MODS" 14 | mkdir -p "$SCENARIOS" 15 | 16 | #chown -R factorio /factorio 17 | 18 | if [[ ! -f $CONFIG/rconpw ]]; then 19 | pwgen 15 1 >"$CONFIG/rconpw" 20 | fi 21 | 22 | if [[ ! -f $CONFIG/server-settings.json ]]; then 23 | cp /opt/factorio/data/server-settings.example.json "$CONFIG/server-settings.json" 24 | fi 25 | 26 | if [[ ! -f $CONFIG/map-gen-settings.json ]]; then 27 | cp /opt/factorio/data/map-gen-settings.example.json "$CONFIG/map-gen-settings.json" 28 | fi 29 | 30 | if [[ ! -f $CONFIG/map-settings.json ]]; then 31 | cp /opt/factorio/data/map-settings.example.json "$CONFIG/map-settings.json" 32 | fi 33 | 34 | exec /opt/factorio/bin/x64/factorio \ 35 | --port "$PORT" \ 36 | --start-server-load-scenario "$SERVER_SCENARIO" \ 37 | --preset "$PRESET" \ 38 | --map-gen-settings "$CONFIG/map-gen-settings.json" \ 39 | --map-settings "$CONFIG/map-settings.json" \ 40 | --server-settings "$CONFIG/server-settings.json" \ 41 | --server-banlist "$CONFIG/server-banlist.json" \ 42 | --server-whitelist "$CONFIG/server-whitelist.json" \ 43 | --use-server-whitelist \ 44 | --server-adminlist "$CONFIG/server-adminlist.json" \ 45 | --rcon-port "$RCON_PORT" \ 46 | --rcon-password "$(cat "$CONFIG/rconpw")" \ 47 | --server-id /factorio/config/server-id.json 48 | -------------------------------------------------------------------------------- /docker/files/scenario2map.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eoux pipefail 3 | 4 | if [[ -z ${1:-} ]]; then 5 | echo "No argument supplied" 6 | fi 7 | 8 | SERVER_SCENARIO="$1" 9 | mkdir -p "$SAVES" 10 | mkdir -p "$CONFIG" 11 | mkdir -p "$MODS" 12 | mkdir -p "$SCENARIOS" 13 | 14 | if [[ ! -f $CONFIG/server-settings.json ]]; then 15 | cp /opt/factorio/data/server-settings.example.json "$CONFIG/server-settings.json" 16 | fi 17 | 18 | if [[ ! -f $CONFIG/map-gen-settings.json ]]; then 19 | cp /opt/factorio/data/map-gen-settings.example.json "$CONFIG/map-gen-settings.json" 20 | fi 21 | 22 | if [[ ! -f $CONFIG/map-settings.json ]]; then 23 | cp /opt/factorio/data/map-settings.example.json "$CONFIG/map-settings.json" 24 | fi 25 | 26 | exec /opt/factorio/bin/x64/factorio \ 27 | --scenario2map "$SERVER_SCENARIO" 28 | -------------------------------------------------------------------------------- /docker/files/update-mods.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eou pipefail 3 | 4 | FACTORIO_VERSION=$1 5 | MOD_DIR=$2 6 | USERNAME=$3 7 | TOKEN=$4 8 | 9 | MOD_BASE_URL="https://mods.factorio.com" 10 | 11 | print_step() 12 | { 13 | echo "$1" 14 | } 15 | 16 | print_success() 17 | { 18 | echo "$1" 19 | } 20 | 21 | print_failure() 22 | { 23 | echo "$1" 24 | } 25 | 26 | update_mod() 27 | { 28 | MOD_NAME="$1" 29 | MOD_NAME_ENCODED="${1// /%20}" 30 | 31 | print_step "Checking for update of mod $MOD_NAME for factorio $FACTORIO_VERSION ..." 32 | 33 | MOD_INFO_URL="$MOD_BASE_URL/api/mods/$MOD_NAME_ENCODED" 34 | MOD_INFO_JSON=$(curl --silent "$MOD_INFO_URL") 35 | 36 | if ! echo "$MOD_INFO_JSON" | jq -e .name >/dev/null; then 37 | print_success " Custom mod not on $MOD_BASE_URL, skipped." 38 | return 0 39 | fi 40 | 41 | 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") 42 | 43 | MOD_FILENAME=$(echo "$MOD_INFO" | cut -f1 -d";") 44 | MOD_URL=$(echo "$MOD_INFO" | cut -f2 -d";") 45 | MOD_SHA1=$(echo "$MOD_INFO" | cut -f3 -d";") 46 | 47 | if [[ $MOD_FILENAME == null ]]; then 48 | print_failure " Not compatible with version" 49 | return 0 50 | fi 51 | 52 | if [[ -f $MOD_DIR/$MOD_FILENAME ]]; then 53 | print_success " Already up-to-date." 54 | return 0 55 | fi 56 | 57 | print_step " Downloading $MOD_FILENAME" 58 | FULL_URL="$MOD_BASE_URL$MOD_URL?username=$USERNAME&token=$TOKEN" 59 | HTTP_STATUS=$(curl --silent -L -w "%{http_code}" -o "$MOD_DIR/$MOD_FILENAME" "$FULL_URL") 60 | 61 | if [[ $HTTP_STATUS != 200 ]]; then 62 | print_failure " Download failed: Code $HTTP_STATUS." 63 | rm -f "$MOD_DIR/$MOD_FILENAME" 64 | return 1 65 | fi 66 | 67 | if [[ ! -f $MOD_DIR/$MOD_FILENAME ]]; then 68 | print_failure " Downloaded file missing!" 69 | return 1 70 | fi 71 | 72 | if ! [[ $(sha1sum "$MOD_DIR/$MOD_FILENAME") =~ $MOD_SHA1 ]]; then 73 | print_failure " SHA1 mismatch!" 74 | rm -f "$MOD_DIR/$MOD_FILENAME" 75 | return 1 76 | fi 77 | 78 | print_success " Download complete." 79 | 80 | for file in "$MOD_DIR/${MOD_NAME}_"*".zip"; do # wildcard does usually not work in quotes: https://unix.stackexchange.com/a/67761 81 | if [[ $file != $MOD_DIR/$MOD_FILENAME ]]; then 82 | print_success " Deleting old version: $file" 83 | rm -f "$file" 84 | fi 85 | done 86 | 87 | return 0 88 | } 89 | 90 | if [[ -f $MOD_DIR/mod-list.json ]]; then 91 | jq -r ".mods|map(select(.enabled))|.[].name" "$MOD_DIR/mod-list.json" | while read -r mod; do 92 | if [[ $mod != base ]]; then 93 | update_mod "$mod" 94 | fi 95 | done 96 | fi 97 | -------------------------------------------------------------------------------- /docker/rcon/Makefile: -------------------------------------------------------------------------------- 1 | # Optimization 2 | OPT = -O3 -flto 3 | TARGET = rcon 4 | 5 | CC = gcc 6 | CFLAGS = -std=c17 -Wall -Wextra -pedantic $(OPT) 7 | REMOVE = rm -f 8 | 9 | all: 10 | $(CC) $(CFLAGS) main.c -o $(TARGET) 11 | 12 | clean: 13 | $(REMOVE) $(TARGET) 14 | -------------------------------------------------------------------------------- /docker/rcon/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #define MIN_PACKET 10 13 | #define MAX_PACKET 4096 14 | #define MAX_BODY (MAX_PACKET - (3 * sizeof(uint32_t)) - 2) 15 | 16 | #define RCON_HOST "127.0.0.1" 17 | 18 | typedef enum { 19 | RCON_TYPE_RESPONSE = 0, 20 | RCON_TYPE_EXECCOMMAND = 2, 21 | RCON_TYPE_AUTH_RESPONSE = 2, 22 | RCON_TYPE_AUTH = 3, 23 | } packet_type; 24 | 25 | typedef struct { 26 | uint32_t length; 27 | uint32_t id; 28 | packet_type type; 29 | char body[MAX_BODY]; 30 | } packet; 31 | 32 | int rcon_open(const char *port); 33 | void rcon_create(packet* pkt, packet_type type, const char* body); 34 | bool rcon_send(int rcon_socket, const packet* pkt); 35 | bool rcon_auth(int rcon_socket, const char* password); 36 | bool rcon_recv(int rcon_socket, packet* pkt, packet_type expected_type); 37 | char* combine_args(int argc, char* argv[]); 38 | char* read_password(const char* conf_dir); 39 | 40 | int main(int argc, char* argv[]) { 41 | if (argc < 2) { 42 | fprintf(stderr, "error: missing command argument\n"); 43 | return EXIT_FAILURE; 44 | } 45 | 46 | srand((unsigned int)time(NULL)); 47 | 48 | const char* port = getenv("RCON_PORT"); 49 | if (port == NULL) { 50 | fprintf(stderr, "error: missing $RCON_PORT env\n"); 51 | return EXIT_FAILURE; 52 | } 53 | 54 | const char* conf_dir = getenv("CONFIG"); 55 | if (conf_dir == NULL) { 56 | fprintf(stderr, "error: missing $CONFIG env"); 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | int rcon_socket = rcon_open(port); 61 | if (rcon_socket == -1) { 62 | fprintf(stderr, "error: could not connect\n"); 63 | return EXIT_FAILURE; 64 | } 65 | 66 | if (!rcon_auth(rcon_socket, read_password(conf_dir))) { 67 | fprintf(stderr, "error: login failed\n"); 68 | return EXIT_FAILURE; 69 | } 70 | 71 | packet pkt; 72 | rcon_create(&pkt, RCON_TYPE_EXECCOMMAND, combine_args(argc, argv)); 73 | if (!rcon_send(rcon_socket, &pkt)) { 74 | fprintf(stderr, "error: send command failed\n"); 75 | return EXIT_FAILURE; 76 | } 77 | 78 | if (rcon_recv(rcon_socket, &pkt, RCON_TYPE_RESPONSE) && pkt.length > 0) { 79 | puts(pkt.body); 80 | } 81 | 82 | return EXIT_SUCCESS; 83 | } 84 | 85 | char* combine_args(int argc, char* argv[]) { 86 | // combine all cli arguments 87 | char* command = malloc(MAX_BODY); 88 | memset(command, 0, MAX_BODY); 89 | strcat(command, argv[1]); 90 | 91 | for (int idx = 2; idx < argc; idx++) { 92 | strcat(command, " "); 93 | strcat(command, argv[idx]); 94 | } 95 | 96 | return command; 97 | } 98 | 99 | char* read_password(const char* conf_dir) { 100 | char* path = malloc(strlen(conf_dir) + 64); 101 | strcpy(path, conf_dir); 102 | strcat(path, "/rconpw"); 103 | 104 | FILE* fptr = fopen(path, "r"); 105 | fseek(fptr, 0, SEEK_END); 106 | long fsize = ftell(fptr); 107 | fseek(fptr, 0, SEEK_SET); /* same as rewind(f); */ 108 | 109 | char *password = malloc(fsize + 1); 110 | fread(password, fsize, 1, fptr); 111 | fclose(fptr); 112 | 113 | password[fsize] = 0; 114 | if (password[fsize-1] == '\n') { 115 | password[fsize-1] = 0; 116 | } 117 | 118 | return password; 119 | } 120 | 121 | int rcon_open(const char *port) { 122 | struct sockaddr_in address = { 123 | .sin_family = AF_INET, 124 | .sin_port = htons(atoi(port)) 125 | }; 126 | inet_aton(RCON_HOST, &address.sin_addr); 127 | 128 | int rcon_socket = socket(AF_INET, SOCK_STREAM, 0); 129 | if (connect(rcon_socket, (struct sockaddr*) &address, sizeof(address)) < 0) { 130 | return -1; 131 | } else { 132 | return rcon_socket; 133 | } 134 | } 135 | 136 | void rcon_create(packet* pkt, packet_type type, const char* body) { 137 | size_t body_length = strlen(body); 138 | if (body_length >= MAX_BODY - 2) { 139 | fprintf(stderr, "error: command to long"); 140 | exit(EXIT_FAILURE); 141 | } 142 | 143 | pkt->id = abs(rand()); 144 | pkt->type = type; 145 | pkt->length = (uint32_t)(sizeof(pkt->id) + sizeof(pkt->type) + body_length + 2); 146 | 147 | memset(pkt->body, 0, MAX_BODY); 148 | strncpy(pkt->body, body, MAX_BODY); 149 | } 150 | 151 | bool rcon_recv(int rcon_socket, packet* pkt, packet_type expected_type) { 152 | memset(pkt, 0, sizeof(*pkt)); 153 | 154 | // Read response packet length 155 | ssize_t expected_length_bytes = sizeof(pkt->length); 156 | ssize_t rx_bytes = recv(rcon_socket, &(pkt->length), expected_length_bytes, 0); 157 | 158 | if (rx_bytes == -1) { 159 | perror("error: socket error"); 160 | return false; 161 | } else if (rx_bytes == 0) { 162 | fprintf(stderr, "error: no data recieved\n"); 163 | return false; 164 | } else if (rx_bytes < expected_length_bytes || pkt->length < MIN_PACKET || pkt->length > MAX_PACKET) { 165 | fprintf(stderr, "error: invalid data\n"); 166 | return false; 167 | } 168 | 169 | ssize_t received = 0; 170 | while (received < pkt->length) { 171 | rx_bytes = recv(rcon_socket, (char *)pkt + sizeof(pkt->length) + received, pkt->length - received, 0); 172 | if (rx_bytes < 0) { 173 | perror("error: socket error"); 174 | return false; 175 | } else if (rx_bytes == 0) { 176 | fprintf(stderr, "error: connection lost\n"); 177 | return false; 178 | } 179 | 180 | received += rx_bytes; 181 | } 182 | 183 | return pkt->type == expected_type; 184 | } 185 | 186 | bool rcon_send(int rcon_socket, const packet* pkt) { 187 | size_t length = sizeof(pkt->length) + pkt->length; 188 | char *ptr = (char*) pkt; 189 | 190 | while (length > 0) { 191 | ssize_t ret = send(rcon_socket, ptr, length, 0); 192 | 193 | if (ret == -1) { 194 | return false; 195 | } 196 | 197 | ptr += ret; 198 | length -= ret; 199 | } 200 | 201 | return true; 202 | } 203 | 204 | bool rcon_auth(int rcon_socket, const char* password) { 205 | packet pkt; 206 | rcon_create(&pkt, RCON_TYPE_AUTH, password); 207 | 208 | if (!rcon_send(rcon_socket, &pkt)) { 209 | return false; 210 | } 211 | 212 | if (!rcon_recv(rcon_socket, &pkt, RCON_TYPE_AUTH_RESPONSE)) { 213 | return false; 214 | } 215 | 216 | return true; 217 | } 218 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git ls-files --exclude='*Dockerfile' --ignored | xargs --max-lines=1 ./hadolint 3 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | SEMVER_REGEX="^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$" 4 | 5 | stable_online_version=$(curl 'https://factorio.com/api/latest-releases' | jq '.stable.headless' -r) 6 | experimental_online_version=$(curl 'https://factorio.com/api/latest-releases' | jq '.experimental.headless' -r) 7 | 8 | stable_sha256=$(curl "https://factorio.com/download/sha256sums/" | grep -E "(factorio_headless_x64_|factorio-headless_linux_)${stable_online_version}.tar.xz" | awk '{print $1}') 9 | experimental_sha256=$(curl "https://factorio.com/download/sha256sums/" | grep -E "(factorio_headless_x64_|factorio-headless_linux_)${experimental_online_version}.tar.xz" | awk '{print $1}') 10 | 11 | stable_current_version=$(jq 'with_entries(select(.value.tags | index("stable"))) | keys | .[0]' buildinfo.json -r) 12 | latest_current_version=$(jq 'with_entries(select(.value.tags | index("latest"))) | keys | .[0]' buildinfo.json -r) 13 | 14 | echo "stable_online_version=${stable_online_version} experimental_online_version=${experimental_online_version}" 15 | echo "stable_current_version=${stable_current_version} latest_current_version=${latest_current_version}" 16 | 17 | if [[ -z "${stable_online_version}" ]] || [[ -z "${experimental_online_version}" ]]; then 18 | exit 19 | fi 20 | if [[ "${stable_current_version}" == "${stable_online_version}" ]] && [[ "${latest_current_version}" == "${experimental_online_version}" ]]; then 21 | exit 22 | fi 23 | 24 | function get-semver(){ 25 | local ver=$1 26 | local type=$2 27 | if [[ "$ver" =~ $SEMVER_REGEX ]]; then 28 | local major=${BASH_REMATCH[1]} 29 | local minor=${BASH_REMATCH[2]} 30 | local patch=${BASH_REMATCH[3]} 31 | fi 32 | case $type in 33 | major) 34 | echo "$major" 35 | ;; 36 | minor) 37 | echo "$minor" 38 | ;; 39 | patch) 40 | echo "$patch" 41 | ;; 42 | esac 43 | } 44 | 45 | stableOnlineVersionMajor=$(get-semver "${stable_online_version}" major) 46 | stableOnlineVersionMinor=$(get-semver "${stable_online_version}" minor) 47 | experimentalOnlineVersionMajor=$(get-semver "${experimental_online_version}" major) 48 | experimentalOnlineVersionMinor=$(get-semver "${experimental_online_version}" minor) 49 | stableCurrentVersionMajor=$(get-semver "${stable_current_version}" major) 50 | stableCurrentVersionMinor=$(get-semver "${stable_current_version}" minor) 51 | latestCurrentVersionMajor=$(get-semver "${latest_current_version}" major) 52 | latestCurrentVersionMinor=$(get-semver "${latest_current_version}" minor) 53 | 54 | stableOnlineVersionShort=$stableOnlineVersionMajor.$stableOnlineVersionMinor 55 | experimentalOnlineVersionShort=$experimentalOnlineVersionMajor.$experimentalOnlineVersionMinor 56 | stableCurrentVersionShort=$stableCurrentVersionMajor.$stableCurrentVersionMinor 57 | latestCurrentVersionShort=$latestCurrentVersionMajor.$latestCurrentVersionMinor 58 | 59 | echo "stableOnlineVersionShort=${stableOnlineVersionShort} experimentalOnlineVersionShort=${experimentalOnlineVersionShort}" 60 | echo "stableCurrentVersionShort=${stableCurrentVersionShort} latestCurrentVersionShort=${latestCurrentVersionShort}" 61 | 62 | tmpfile=$(mktemp) 63 | 64 | # Remove stable tag 65 | cp buildinfo.json "$tmpfile" 66 | jq --arg stable_current_version "$stable_current_version" 'with_entries(if .key == $stable_current_version then .value.tags |= . - ["stable"] else . end)' "$tmpfile" > buildinfo.json 67 | rm -f -- "$tmpfile" 68 | 69 | # Remove latest tag 70 | cp buildinfo.json "$tmpfile" 71 | jq --arg latest_current_version "$latest_current_version" 'with_entries(if .key == $latest_current_version then .value.tags |= . - ["latest"] else . end)' "$tmpfile" > buildinfo.json 72 | rm -f -- "$tmpfile" 73 | 74 | # Update tag by stable 75 | cp buildinfo.json "$tmpfile" 76 | if [[ "$stable_online_version" == "$stable_current_version" ]]; then 77 | 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 78 | else 79 | 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 80 | fi 81 | rm -f -- "$tmpfile" 82 | 83 | # Update tag by latest 84 | cp buildinfo.json "$tmpfile" 85 | if [[ $experimental_online_version != "$stable_online_version" ]]; then 86 | if [[ $stableOnlineVersionShort == "$experimentalOnlineVersionShort" ]]; then 87 | 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 88 | else 89 | 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 90 | fi 91 | fi 92 | rm -f -- "$tmpfile" 93 | 94 | readme_tags=$(jq --sort-keys 'keys[]' buildinfo.json | tac | (while read -r line 95 | do 96 | tags="$tags\n* "$(jq --sort-keys ".$line.tags | sort | .[]" buildinfo.json | sed 's/"/`/g' | sed ':a; /$/N; s/\n/, /; ta') 97 | done && printf "%s\n\n" "$tags")) 98 | 99 | perl -i -0777 -pe "s/.+/$readme_tags/s" README.md 100 | 101 | # Replace VERSION and SHA256 args in docker-compose.yaml with latest stable values. 102 | docker_compose_path="docker/docker-compose.yml" 103 | sov="VERSION=${stable_online_version}" yq -i '.services.factorio.build.args[0] = env(sov)' "$docker_compose_path" 104 | sha="SHA256=${stable_sha256}" yq -i '.services.factorio.build.args[1] = env(sha)' "$docker_compose_path" 105 | 106 | git config user.name github-actions[bot] 107 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 108 | 109 | git add buildinfo.json 110 | git add README.md 111 | git add docker/docker-compose.yml 112 | git commit -a -m "Auto Update Factorio to stable version: ${stable_online_version} experimental version: ${experimental_online_version}" 113 | 114 | git tag -f latest 115 | git push 116 | git push origin --tags -f 117 | --------------------------------------------------------------------------------