First Running Server
This commit is contained in:
commit
93c34d8cbe
8 changed files with 429 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
inventory.yml
|
11
Readme.md
Normal file
11
Readme.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Factorio Server Managment
|
||||||
|
|
||||||
|
|
||||||
|
Templates Source:
|
||||||
|
https://github.com/wube/factorio-data
|
||||||
|
|
||||||
|
|
||||||
|
ToDo:
|
||||||
|
* Run game as own user
|
||||||
|
* Download Saved game from server
|
||||||
|
* Auto upload last game from server
|
43
factorio-mod-downloader/mod-downloader.py
Normal file
43
factorio-mod-downloader/mod-downloader.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from os.path import exists
|
||||||
|
|
||||||
|
def login(username, password):
|
||||||
|
print("> Login for user "+username)
|
||||||
|
payload = {'username': username, 'password': password', 'api_version':'4'}
|
||||||
|
headers = {'content-type': 'application/json'}
|
||||||
|
|
||||||
|
r = requests.post("https://auth.factorio.com/api-login", data=payload)
|
||||||
|
|
||||||
|
return r.json()["token"]
|
||||||
|
|
||||||
|
|
||||||
|
def downloadRelease(releaseInfo, username, token):
|
||||||
|
downloadURL = "https://mods.factorio.com"+releaseInfo["download_url"]+"?username="+username+"&token="+token
|
||||||
|
r = requests.get(downloadURL)
|
||||||
|
with open(releaseInfo["file_name"], "wb") as f:
|
||||||
|
f.write(r.content)
|
||||||
|
|
||||||
|
def checkReleaseExistsOnHDD(releaseInfo):
|
||||||
|
file_exists = exists(releaseInfo["file_name"])
|
||||||
|
return file_exists
|
||||||
|
|
||||||
|
def getReleaseInfo(name, version):
|
||||||
|
print("> Get Release Info")
|
||||||
|
r = requests.get('https://mods.factorio.com/api/mods/'+name)
|
||||||
|
data = r.json()
|
||||||
|
if version == "latest":
|
||||||
|
return data["releases"][-1]
|
||||||
|
|
||||||
|
for r in data["releases"]:
|
||||||
|
if r["version"] == version:
|
||||||
|
return r
|
||||||
|
|
||||||
|
sys.exit(200)
|
||||||
|
|
||||||
|
|
||||||
|
r = getReleaseInfo(sys.argv[1], sys.argv[2])
|
||||||
|
if checkReleaseExistsOnHDD(r) == False:
|
||||||
|
token = login(sys.argv[3], sys.argv[4])
|
||||||
|
downloadRelease(r, sys.argv[3], token)
|
86
setup.yml
Normal file
86
setup.yml
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
---
|
||||||
|
- name: Setup Factorio
|
||||||
|
hosts: all
|
||||||
|
remote_user: root
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: check if factorio download exists on the server
|
||||||
|
stat:
|
||||||
|
path: "/opt/factorio-{{ factorio_version }}.tar.gz"
|
||||||
|
register: factorio_download
|
||||||
|
|
||||||
|
- name: "Download Factorio Version {{ factorio_version }}"
|
||||||
|
get_url:
|
||||||
|
url: "https://www.factorio.com/get-download/{{ factorio_version }}/headless/linux64"
|
||||||
|
dest: "/opt/factorio-{{ factorio_version }}.tar.gz"
|
||||||
|
mode: '0440'
|
||||||
|
when: factorio_download.stat.exists == False
|
||||||
|
|
||||||
|
- name: check if factorio exists on the server
|
||||||
|
stat:
|
||||||
|
path: "/opt/factorio"
|
||||||
|
register: factorio
|
||||||
|
|
||||||
|
- name: Extract Factorio
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "/opt/factorio-{{ factorio_version }}.tar.gz"
|
||||||
|
dest: "/opt/"
|
||||||
|
remote_src: yes
|
||||||
|
when: factorio.stat.exists == False
|
||||||
|
|
||||||
|
- name: Upload factorio-mod-downloader to server
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: ./factorio-mod-downloader/mod-downloader.py
|
||||||
|
dest: /opt/mod-downloader.py
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Create the mod directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /opt/factorio/mods
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Download Mod
|
||||||
|
ansible.builtin.shell: "python3 /opt/mod-downloader.py \"{{ item }}\" latest {{ factorio_username }} {{ factorio_password }}"
|
||||||
|
args:
|
||||||
|
chdir: /opt/factorio/mods/
|
||||||
|
loop: "{{ mods }}"
|
||||||
|
|
||||||
|
- name: Check if Map exists on server
|
||||||
|
stat:
|
||||||
|
path: "/opt/factorio/saves/my-save.zip"
|
||||||
|
register: map
|
||||||
|
|
||||||
|
- name: Template a file to /opt/factorio/map-gen-settings.json
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: map-gen-settings.json.j2
|
||||||
|
dest: /opt/factorio/map-gen-settings.json
|
||||||
|
when: map.stat.exists == False
|
||||||
|
|
||||||
|
- name: Template a file to /opt/factorio/map-settings.json
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: map-settings.json.j2
|
||||||
|
dest: /opt/factorio/map-settings.json
|
||||||
|
when: map.stat.exists == False
|
||||||
|
|
||||||
|
- name: Generate new Map
|
||||||
|
ansible.builtin.shell: "./bin/x64/factorio --create saves/my-save.zip --map-gen-settings map-gen-settings.json --map-settings map-settings.json"
|
||||||
|
args:
|
||||||
|
chdir: /opt/factorio/
|
||||||
|
when: map.stat.exists == False
|
||||||
|
|
||||||
|
- name: Template a file to /opt/factorio/server-settings.json
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: server-settings.json.j2
|
||||||
|
dest: /opt/factorio/server-settings.json
|
||||||
|
|
||||||
|
- name: Template a file to /etc/systemd/system/factorio.service
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: factorio.service.j2
|
||||||
|
dest: /etc/systemd/system/factorio.service
|
||||||
|
|
||||||
|
- name: Restart factorio
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
state: restarted
|
||||||
|
daemon_reload: yes
|
||||||
|
name: factorio
|
18
templates/factorio.service.j2
Normal file
18
templates/factorio.service.j2
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#place in /etc/systemd/system/factorio.service
|
||||||
|
# systemctl start factorio.service
|
||||||
|
# systemctl enable factorio.service #autostarts the server
|
||||||
|
# systemctl stop factorio.service
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Descritpion=Factorio Headless Server
|
||||||
|
After=network.target
|
||||||
|
After=systemd-user-sessions.service
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
ExecStart=/opt/factorio/bin/x64/factorio --start-server /opt/factorio/saves/my-save.zip --server-settings /opt/factorio/server-settings.json
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
78
templates/map-gen-settings.json.j2
Normal file
78
templates/map-gen-settings.json.j2
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"_terrain_segmentation_comment": "The inverse of 'water scale' in the map generator GUI.",
|
||||||
|
"terrain_segmentation": 1,
|
||||||
|
|
||||||
|
"_water_comment":
|
||||||
|
[
|
||||||
|
"The equivalent to 'water coverage' in the map generator GUI. Higher coverage means more water in larger oceans.",
|
||||||
|
"Water level = 10 * log2(this value)"
|
||||||
|
],
|
||||||
|
"water": 1,
|
||||||
|
|
||||||
|
"_comment_width+height": "Width and height of map, in tiles; 0 means infinite",
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
|
||||||
|
"_starting_area_comment": "Multiplier for 'biter free zone radius'",
|
||||||
|
"starting_area": 1,
|
||||||
|
|
||||||
|
"peaceful_mode": false,
|
||||||
|
"autoplace_controls":
|
||||||
|
{
|
||||||
|
"coal": {"frequency": 1, "size": 1, "richness": 1},
|
||||||
|
"stone": {"frequency": 1, "size": 1, "richness": 1},
|
||||||
|
"copper-ore": {"frequency": 1, "size": 1,"richness": 1},
|
||||||
|
"iron-ore": {"frequency": 1, "size": 1, "richness": 1},
|
||||||
|
"uranium-ore": {"frequency": 1, "size": 1, "richness": 1},
|
||||||
|
"crude-oil": {"frequency": 1, "size": 1, "richness": 1},
|
||||||
|
"trees": {"frequency": 1, "size": 1, "richness": 1},
|
||||||
|
"enemy-base": {"frequency": 1, "size": 1, "richness": 1}
|
||||||
|
},
|
||||||
|
|
||||||
|
"cliff_settings":
|
||||||
|
{
|
||||||
|
"_name_comment": "Name of the cliff prototype",
|
||||||
|
"name": "cliff",
|
||||||
|
|
||||||
|
"_cliff_elevation_0_comment": "Elevation of first row of cliffs",
|
||||||
|
"cliff_elevation_0": 10,
|
||||||
|
|
||||||
|
"_cliff_elevation_interval_comment":
|
||||||
|
[
|
||||||
|
"Elevation difference between successive rows of cliffs.",
|
||||||
|
"This is inversely proportional to 'frequency' in the map generation GUI. Specifically, when set from the GUI the value is 40 / frequency."
|
||||||
|
],
|
||||||
|
"cliff_elevation_interval": 40,
|
||||||
|
|
||||||
|
"_richness_comment": "Called 'cliff continuity' in the map generator GUI. 0 will result in no cliffs, 10 will make all cliff rows completely solid",
|
||||||
|
"richness": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"_property_expression_names_comment":
|
||||||
|
[
|
||||||
|
"Overrides for property value generators (map type)",
|
||||||
|
"Leave 'elevation' blank to get 'normal' terrain.",
|
||||||
|
"Use 'elevation': '0_16-elevation' to reproduce terrain from 0.16.",
|
||||||
|
"Use 'elevation': '0_17-island' to get an island.",
|
||||||
|
"Moisture and terrain type are also controlled via this.",
|
||||||
|
"'control-setting:moisture:frequency:multiplier' is the inverse of the 'moisture scale' in the map generator GUI.",
|
||||||
|
"'control-setting:moisture:bias' is the 'moisture bias' in the map generator GUI.",
|
||||||
|
"'control-setting:aux:frequency:multiplier' is the inverse of the 'terrain type scale' in the map generator GUI.",
|
||||||
|
"'control-setting:aux:bias' is the 'terrain type bias' in the map generator GUI."
|
||||||
|
],
|
||||||
|
"property_expression_names":
|
||||||
|
{
|
||||||
|
"control-setting:moisture:frequency:multiplier": "1",
|
||||||
|
"control-setting:moisture:bias": "0",
|
||||||
|
"control-setting:aux:frequency:multiplier": "1",
|
||||||
|
"control-setting:aux:bias": "0"
|
||||||
|
},
|
||||||
|
|
||||||
|
"starting_points":
|
||||||
|
[
|
||||||
|
{ "x": 0, "y": 0}
|
||||||
|
],
|
||||||
|
|
||||||
|
"_seed_comment": "Use null for a random seed, number for a specific seed.",
|
||||||
|
"seed": null
|
||||||
|
}
|
120
templates/map-settings.json.j2
Normal file
120
templates/map-settings.json.j2
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
{
|
||||||
|
"difficulty_settings":
|
||||||
|
{
|
||||||
|
"recipe_difficulty": 0,
|
||||||
|
"technology_difficulty": 0,
|
||||||
|
"technology_price_multiplier": 1,
|
||||||
|
"research_queue_setting": "after-victory"
|
||||||
|
},
|
||||||
|
"pollution":
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"_comment_min_to_diffuse_1": "these are values for 60 ticks (1 simulated second)",
|
||||||
|
"_comment_min_to_diffuse_2": "amount that is diffused to neighboring chunk",
|
||||||
|
"diffusion_ratio": 0.02,
|
||||||
|
"min_to_diffuse": 15,
|
||||||
|
"ageing": 1,
|
||||||
|
"expected_max_per_chunk": 150,
|
||||||
|
"min_to_show_per_chunk": 50,
|
||||||
|
"min_pollution_to_damage_trees": 60,
|
||||||
|
"pollution_with_max_forest_damage": 150,
|
||||||
|
"pollution_per_tree_damage": 50,
|
||||||
|
"pollution_restored_per_tree_damage": 10,
|
||||||
|
"max_pollution_to_restore_trees": 20,
|
||||||
|
"enemy_attack_pollution_consumption_modifier": 1
|
||||||
|
},
|
||||||
|
"enemy_evolution":
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"time_factor": 0.000004,
|
||||||
|
"destroy_factor": 0.002,
|
||||||
|
"pollution_factor": 0.0000009
|
||||||
|
},
|
||||||
|
"enemy_expansion":
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"min_base_spacing": 3,
|
||||||
|
"max_expansion_distance": 7,
|
||||||
|
"friendly_base_influence_radius": 2,
|
||||||
|
"enemy_building_influence_radius": 2,
|
||||||
|
"building_coefficient": 0.1,
|
||||||
|
"other_base_coefficient": 2.0,
|
||||||
|
"neighbouring_chunk_coefficient": 0.5,
|
||||||
|
"neighbouring_base_chunk_coefficient": 0.4,
|
||||||
|
"max_colliding_tiles_coefficient": 0.9,
|
||||||
|
"settler_group_min_size": 5,
|
||||||
|
"settler_group_max_size": 20,
|
||||||
|
"min_expansion_cooldown": 14400,
|
||||||
|
"max_expansion_cooldown": 216000
|
||||||
|
},
|
||||||
|
"unit_group":
|
||||||
|
{
|
||||||
|
"min_group_gathering_time": 3600,
|
||||||
|
"max_group_gathering_time": 36000,
|
||||||
|
"max_wait_time_for_late_members": 7200,
|
||||||
|
"max_group_radius": 30.0,
|
||||||
|
"min_group_radius": 5.0,
|
||||||
|
"max_member_speedup_when_behind": 1.4,
|
||||||
|
"max_member_slowdown_when_ahead": 0.6,
|
||||||
|
"max_group_slowdown_factor": 0.3,
|
||||||
|
"max_group_member_fallback_factor": 3,
|
||||||
|
"member_disown_distance": 10,
|
||||||
|
"tick_tolerance_when_member_arrives": 60,
|
||||||
|
"max_gathering_unit_groups": 30,
|
||||||
|
"max_unit_group_size": 200
|
||||||
|
},
|
||||||
|
"steering":
|
||||||
|
{
|
||||||
|
"default":
|
||||||
|
{
|
||||||
|
"radius": 1.2,
|
||||||
|
"separation_force": 0.005,
|
||||||
|
"separation_factor": 1.2,
|
||||||
|
"force_unit_fuzzy_goto_behavior": false
|
||||||
|
},
|
||||||
|
"moving":
|
||||||
|
{
|
||||||
|
"radius": 3,
|
||||||
|
"separation_force": 0.01,
|
||||||
|
"separation_factor": 3,
|
||||||
|
"force_unit_fuzzy_goto_behavior": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path_finder":
|
||||||
|
{
|
||||||
|
"fwd2bwd_ratio": 5,
|
||||||
|
"goal_pressure_ratio": 2,
|
||||||
|
"max_steps_worked_per_tick": 100,
|
||||||
|
"max_work_done_per_tick": 8000,
|
||||||
|
"use_path_cache": true,
|
||||||
|
"short_cache_size": 5,
|
||||||
|
"long_cache_size": 25,
|
||||||
|
"short_cache_min_cacheable_distance": 10,
|
||||||
|
"short_cache_min_algo_steps_to_cache": 50,
|
||||||
|
"long_cache_min_cacheable_distance": 30,
|
||||||
|
"cache_max_connect_to_cache_steps_multiplier": 100,
|
||||||
|
"cache_accept_path_start_distance_ratio": 0.2,
|
||||||
|
"cache_accept_path_end_distance_ratio": 0.15,
|
||||||
|
"negative_cache_accept_path_start_distance_ratio": 0.3,
|
||||||
|
"negative_cache_accept_path_end_distance_ratio": 0.3,
|
||||||
|
"cache_path_start_distance_rating_multiplier": 10,
|
||||||
|
"cache_path_end_distance_rating_multiplier": 20,
|
||||||
|
"stale_enemy_with_same_destination_collision_penalty": 30,
|
||||||
|
"ignore_moving_enemy_collision_distance": 5,
|
||||||
|
"enemy_with_different_destination_collision_penalty": 30,
|
||||||
|
"general_entity_collision_penalty": 10,
|
||||||
|
"general_entity_subsequent_collision_penalty": 3,
|
||||||
|
"extended_collision_penalty": 3,
|
||||||
|
"max_clients_to_accept_any_new_request": 10,
|
||||||
|
"max_clients_to_accept_short_new_request": 100,
|
||||||
|
"direct_distance_to_consider_short_request": 100,
|
||||||
|
"short_request_max_steps": 1000,
|
||||||
|
"short_request_ratio": 0.5,
|
||||||
|
"min_steps_to_check_path_find_termination": 2000,
|
||||||
|
"start_to_goal_cost_multiplier_to_terminate_path_find": 500.0,
|
||||||
|
"overload_levels": [0, 100, 500],
|
||||||
|
"overload_multipliers": [2, 3, 4],
|
||||||
|
"negative_path_cache_delay_interval": 20
|
||||||
|
},
|
||||||
|
"max_failed_behavior_count": 3
|
||||||
|
}
|
72
templates/server-settings.json.j2
Normal file
72
templates/server-settings.json.j2
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"name": "Name of the game as it will appear in the game listing",
|
||||||
|
"description": "Description of the game that will appear in the listing",
|
||||||
|
"tags": ["game", "tags"],
|
||||||
|
|
||||||
|
"_comment_max_players": "Maximum number of players allowed, admins can join even a full server. 0 means unlimited.",
|
||||||
|
"max_players": 0,
|
||||||
|
|
||||||
|
"_comment_visibility": ["public: Game will be published on the official Factorio matching server",
|
||||||
|
"lan: Game will be broadcast on LAN"],
|
||||||
|
"visibility":
|
||||||
|
{
|
||||||
|
"public": false,
|
||||||
|
"lan": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"_comment_credentials": "Your factorio.com login credentials. Required for games with visibility public",
|
||||||
|
"username": "{{ factorio_username }}",
|
||||||
|
"password": "{{ factorio_password }}",
|
||||||
|
|
||||||
|
"_comment_token": "Authentication token. May be used instead of 'password' above.",
|
||||||
|
"token": "",
|
||||||
|
|
||||||
|
"game_password": "{{ game_password }}",
|
||||||
|
|
||||||
|
"_comment_require_user_verification": "When set to true, the server will only allow clients that have a valid Factorio.com account",
|
||||||
|
"require_user_verification": true,
|
||||||
|
|
||||||
|
"_comment_max_upload_in_kilobytes_per_second" : "optional, default value is 0. 0 means unlimited.",
|
||||||
|
"max_upload_in_kilobytes_per_second": 0,
|
||||||
|
|
||||||
|
"_comment_max_upload_slots" : "optional, default value is 5. 0 means unlimited.",
|
||||||
|
"max_upload_slots": 5,
|
||||||
|
|
||||||
|
"_comment_minimum_latency_in_ticks": "optional one tick is 16ms in default speed, default value is 0. 0 means no minimum.",
|
||||||
|
"minimum_latency_in_ticks": 0,
|
||||||
|
|
||||||
|
"_comment_max_heartbeats_per_second": "Network tick rate. Maximum rate game updates packets are sent at before bundling them together. Minimum value is 6, maximum value is 240.",
|
||||||
|
"max_heartbeats_per_second": 60,
|
||||||
|
|
||||||
|
"_comment_ignore_player_limit_for_returning_players": "Players that played on this map already can join even when the max player limit was reached.",
|
||||||
|
"ignore_player_limit_for_returning_players": false,
|
||||||
|
|
||||||
|
"_comment_allow_commands": "possible values are, true, false and admins-only",
|
||||||
|
"allow_commands": "admins-only",
|
||||||
|
|
||||||
|
"_comment_autosave_interval": "Autosave interval in minutes",
|
||||||
|
"autosave_interval": 10,
|
||||||
|
|
||||||
|
"_comment_autosave_slots": "server autosave slots, it is cycled through when the server autosaves.",
|
||||||
|
"autosave_slots": 5,
|
||||||
|
|
||||||
|
"_comment_afk_autokick_interval": "How many minutes until someone is kicked when doing nothing, 0 for never.",
|
||||||
|
"afk_autokick_interval": 0,
|
||||||
|
|
||||||
|
"_comment_auto_pause": "Whether should the server be paused when no players are present.",
|
||||||
|
"auto_pause": true,
|
||||||
|
|
||||||
|
"only_admins_can_pause_the_game": true,
|
||||||
|
|
||||||
|
"_comment_autosave_only_on_server": "Whether autosaves should be saved only on server or also on all connected clients. Default is true.",
|
||||||
|
"autosave_only_on_server": true,
|
||||||
|
|
||||||
|
"_comment_non_blocking_saving": "Highly experimental feature, enable only at your own risk of losing your saves. On UNIX systems, server will fork itself to create an autosave. Autosaving on connected Windows clients will be disabled regardless of autosave_only_on_server option.",
|
||||||
|
"non_blocking_saving": false,
|
||||||
|
|
||||||
|
"_comment_segment_sizes": "Long network messages are split into segments that are sent over multiple ticks. Their size depends on the number of peers currently connected. Increasing the segment size will increase upload bandwidth requirement for the server and download bandwidth requirement for clients. This setting only affects server outbound messages. Changing these settings can have a negative impact on connection stability for some clients.",
|
||||||
|
"minimum_segment_size": 25,
|
||||||
|
"minimum_segment_size_peer_count": 20,
|
||||||
|
"maximum_segment_size": 100,
|
||||||
|
"maximum_segment_size_peer_count": 10
|
||||||
|
}
|
Loading…
Reference in a new issue