-
-
Save Freddo3000/a5cd0494f649db75e43611122c9c3f15 to your computer and use it in GitHub Desktop.
| #!/usr/bin/python3 | |
| # MIT License | |
| # | |
| # Copyright (c) 2017 Marcel de Vries | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |
| # of this software and associated documentation files (the "Software"), to deal | |
| # in the Software without restriction, including without limitation the rights | |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| # copies of the Software, and to permit persons to whom the Software is | |
| # furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in all | |
| # copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| # SOFTWARE. | |
| import os | |
| import os.path | |
| import re | |
| import shutil | |
| import time | |
| from datetime import datetime | |
| from urllib import request | |
| #region Configuration | |
| STEAM_CMD = "/home/steam/arma3/steam/steamcmd.sh" | |
| STEAM_USER = "" | |
| STEAM_PASS = "" | |
| A3_SERVER_ID = "233780" | |
| A3_SERVER_DIR = "/home/steam/arma3/install" | |
| A3_WORKSHOP_ID = "107410" | |
| A3_WORKSHOP_DIR = "{}/steamapps/workshop/content/{}".format(A3_SERVER_DIR, A3_WORKSHOP_ID) | |
| A3_MODS_DIR = "/home/steam/arma3/mods" | |
| MODS = { | |
| "@cba_a3": "450814997", | |
| "@ace3": "463939057", | |
| "@alive": "620260972", | |
| "@cup_terrains_core": "583496184", | |
| "@cup_terrains_maps": "583544987", | |
| "@cup_weapons": "497660133", | |
| "@cup_units": "497661914", | |
| "@cup_vehicles": "541888371" | |
| } | |
| PATTERN = re.compile(r"workshopAnnouncement.*?<p id=\"(\d+)\">", re.DOTALL) | |
| WORKSHOP_CHANGELOG_URL = "https://steamcommunity.com/sharedfiles/filedetails/changelog" | |
| #endregion | |
| #region Functions | |
| def log(msg): | |
| print("") | |
| print("{{0:=<{}}}".format(len(msg)).format("")) | |
| print(msg); | |
| print("{{0:=<{}}}".format(len(msg)).format("")) | |
| def call_steamcmd(params): | |
| os.system("{} {}".format(STEAM_CMD, params)) | |
| print("") | |
| def update_server(): | |
| steam_cmd_params = " +login {} {}".format(STEAM_USER, STEAM_PASS) | |
| steam_cmd_params += " +force_install_dir {}".format(A3_SERVER_DIR) | |
| steam_cmd_params += " +app_update {} validate".format(A3_SERVER_ID) | |
| steam_cmd_params += " +quit" | |
| call_steamcmd(steam_cmd_params) | |
| def mod_needs_update(mod_id, path): | |
| if os.path.isdir(path): | |
| response = request.urlopen("{}/{}".format(WORKSHOP_CHANGELOG_URL, mod_id)).read() | |
| response = response.decode("utf-8") | |
| match = PATTERN.search(response) | |
| if match: | |
| updated_at = datetime.fromtimestamp(int(match.group(1))) | |
| created_at = datetime.fromtimestamp(os.path.getctime(path)) | |
| return (updated_at >= created_at) | |
| return False | |
| def update_mods(): | |
| for mod_name, mod_id in MODS.items(): | |
| path = "{}/{}".format(A3_WORKSHOP_DIR, mod_id) | |
| # Check if mod needs to be updated | |
| if os.path.isdir(path): | |
| if mod_needs_update(mod_id, path): | |
| # Delete existing folder so that we can verify whether the | |
| # download succeeded | |
| shutil.rmtree(path) | |
| else: | |
| print("No update required for \"{}\" ({})... SKIPPING".format(mod_name, mod_id)) | |
| continue | |
| # Keep trying until the download actually succeeded | |
| tries = 0 | |
| while os.path.isdir(path) == False and tries < 10: | |
| log("Updating \"{}\" ({}) | {}".format(mod_name, mod_id, tries + 1)) | |
| steam_cmd_params = " +login {} {}".format(STEAM_USER, STEAM_PASS) | |
| steam_cmd_params += " +force_install_dir {}".format(A3_SERVER_DIR) | |
| steam_cmd_params += " +workshop_download_item {} {} validate".format( | |
| A3_WORKSHOP_ID, | |
| mod_id | |
| ) | |
| steam_cmd_params += " +quit" | |
| call_steamcmd(steam_cmd_params) | |
| # Sleep for a bit so that we can kill the script if needed | |
| time.sleep(5) | |
| tries = tries + 1 | |
| if tries >= 10: | |
| log("!! Updating {} failed after {} tries !!".format(mod_name, tries)) | |
| def lowercase_workshop_dir(): | |
| os.system("(cd {} && find . -depth -exec rename -v 's/(.*)\/([^\/]*)/$1\/\L$2/' {{}} \;)".format(A3_WORKSHOP_DIR)) | |
| def create_mod_symlinks(): | |
| for mod_name, mod_id in MODS.items(): | |
| link_path = "{}/{}".format(A3_MODS_DIR, mod_name) | |
| real_path = "{}/{}".format(A3_WORKSHOP_DIR, mod_id) | |
| if os.path.isdir(real_path): | |
| if not os.path.islink(link_path): | |
| os.symlink(real_path, link_path) | |
| print("Creating symlink '{}'...".format(link_path)) | |
| else: | |
| print("Mod '{}' does not exist! ({})".format(mod_name, real_path)) | |
| #endregion | |
| log("Updating A3 server ({})".format(A3_SERVER_ID)) | |
| update_server() | |
| log("Updating mods") | |
| update_mods() | |
| log("Converting uppercase files/folders to lowercase...") | |
| lowercase_workshop_dir() | |
| log("Creating symlinks...") | |
| create_mod_symlinks() |
It also now generates a preset file importable through the Arma Launcher.
You can also host this file on a webserver, and import it by pasting the link in the "File Name" field.
Fixed an issue if keys aren't found
👌
Hey, it would be awesome if this also generated a startup script, which would load mods according to this config
@Buri For that I'd recommend you check out https://github.com/Dahlgren/arma-server-web-admin
The rename step was failing for me for some reason. But I re-implemented it in Python and it worked fine, which I think makes more sense anyways.
def lowercase_workshop_dir():
def rename_all(root, items):
for name in items:
try:
os.rename(os.path.join(root, name), os.path.join(root, name.lower()))
except OSError:
pass
for root, dirs, files in os.walk(A3_WORKSHOP_DIR, topdown=False):
rename_all(root, dirs)
rename_all(root, files)Also, something that was useful to me was generate the -mod parameter and printing them at the end (although can easily be extending to add the -servermod param too".
def print_launch_params():
rel_path = os.path.relpath(A3_MODS_DIR, A3_SERVER_DIR)
params = "-mod"
for mod_name, mod_id in MODS.items():
params += "{}/{}\;".format(rel_path, mod_name)
print(params)there has been some changes to steamCMD.
now this script throws an error Please use force_install_dir before logon!
to fix this simply swap places for " +force_install_dir {}".format(A3_SERVER_DIR)and " +login {} {}".format(STEAM_USER, STEAM_PASS)
so it ends up looking like:
def update_server():
steam_cmd_params = " +force_install_dir {}".format(A3_SERVER_DIR)
steam_cmd_params += " +login {} {}".format(STEAM_USER, STEAM_PASS)
steam_cmd_params += " +app_update {} validate".format(A3_SERVER_ID)
steam_cmd_params += " +quit"
...
def update_mods():
....
steam_cmd_params = " +force_install_dir {}".format(A3_SERVER_DIR)
steam_cmd_params += " +login {} {}".format(STEAM_USER, STEAM_PASS)
steam_cmd_params += " +workshop_download_item {} {} validate".format(a small discussion where I learned about this:
ValveSoftware/steam-for-linux#8298
ps. downloading workshop items(missions) works only if the steam account owns the game!
Added server key handling