Last active
August 29, 2015 14:19
-
-
Save LordH3lmchen/9e2cf53114acd27abe03 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# encoding: utf-8 | |
""" | |
ftb-minecraft-server-launcher | |
is a small script that automates the installation of a minecraft server. It's build for Linux noobs to setup a Minecraft Server from the Feed the Beast Launcher. | |
What it does | |
============ | |
Environment Setup | |
----------------- | |
It checks if the following programs are available: | |
* zsh (one of the best Unix shells out there) | |
* screen (to run the Minecraft console in background) | |
It will show the command to install the missing programs and exits, if something is needed. | |
(Optional) Downloads user configurations for zsh, screen and vim (is recommended for noobs) | |
http://git.grml.org/f/grml-etc-core/etc/grml/screenrc_generic --> ~/.screenrc | |
http://git.grml.org/f/grml-etc-core/etc/vim/vimrc --> ~/.vimrc | |
http://git.grml.org/f/grml-etc-core/etc/zsh/zshrc --> ~/.zshrc | |
http://git.grml.org/f/grml-etc-core/etc/skel/.zshrc --> ~/.zshrc.local | |
creates a ~/.jre directory for java installations | |
downloads the latest java to ~/.jre | |
extracts java | |
creates a symbolic link | |
ln -s ~/.jre/jre_XXXX... ~/.jre/current-jre | |
adds java to the PATH variable | |
Server Setup | |
------------ | |
#### Step 1. Fetches the repository server lists | |
http://www.creeperrepo.net/edges.json | |
http://ftb.cursecdn.com/edges.json | |
#### Modpacks.xml von einem der Repo Server laden | |
modpacks.xml isnt the latest version on all mirrors | |
third party modpacks | |
thirdparty.xml | |
...../FTB2/static/modpacks.xml | |
#### User sucht Mod aus | |
#### User sucht Version aus | |
#### ServerZip laden. | |
""" | |
import os | |
import urllib | |
import urllib.request | |
import re | |
import subprocess | |
import json | |
import xml.etree.ElementTree | |
import zipfile | |
import shutil | |
import stat | |
""" | |
Exception wird geworfen wenn eine ungütlige destination angegeben wird. | |
""" | |
class FtbServerLauncherException(Exception): #TODO implement basic errorhandling with that exception | |
def __init__(self, message): | |
self.message = message | |
def create_dir_if_not_exists(path): | |
if((os.path.exists(path) and os.path.isdir(path))==false): | |
os.mkdir(path) | |
def dlfile(req, destination): | |
# create a Request-object if given argument is a string(url) | |
if isinstance(req, urllib.request.Request) == False: | |
req = urllib.request.Request(req) | |
if(os.path.exists(destination) and os.path.isdir(destination)): | |
destination = destination + '/' + os.path.basename(req.full_url) | |
if(os.path.isfile(destination)): | |
print(destination + ' already downloaded') | |
return destination | |
print('Downloading ' + destination) | |
with urllib.request.urlopen(req) as f: | |
open(destination, "wb").write(f.read()) | |
return destination | |
def regex_websearch(url, pattern): | |
resp = urllib.request.urlopen(url) | |
content = resp.read().decode('UTF-8') | |
resp.close() | |
match = re.search(pattern, content) | |
return match | |
def get_java_download_url(java_version=8, packag='server-jre', extension='tar.gz', architecture='linux-x64'): | |
valid_packages = ['jre', 'server-jre', 'jdk'] | |
if package not in valid_packages: | |
print('Invalid Java package selection, valid packages are:') | |
for valid_package in valid_packages: | |
print('\t' + valid_package) | |
return None | |
url = "http://www.oracle.com" | |
url_1 = url + "/technetwork/java/javase/downloads/index.html" | |
pattern_1 = '\/technetwork\/java/\javase\/downloads\/'+ package + str(java_version) + '-downloads-.+?\.html' | |
match = regex_websearch(url_1, pattern_1) | |
if match == None: | |
print('Unable to download Java from ' + url_1) | |
print('Website is down or script is outdated') | |
return None | |
url_2 = url + match.group(0) | |
pattern_2 = "http\:\/\/download.oracle\.com\/otn-pub\/java\/jdk\/[7-9]u[0-9]+?-.+?\/" + package + "-[7-9]u[0-9]+?-" + architecture + "." + extension | |
match = regex_websearch(url_2, pattern_2) | |
if match == None: | |
print('Selected architecture.extension \"' + architecture + '.' + extension + '\" is not available') | |
print('Visit \"' + url_2 + '\" to see available architectures and extensions') | |
return None | |
req = urllib.request.Request(match.group(0)) | |
req.add_header('Cookie', 'oraclelicense=accept-securebackup-cookie') | |
destination = dlfile(req, destination) | |
return destination | |
""" | |
this function downloads the lates Java | |
thx to n0ts for the idea | |
https://gist.github.com/n0ts/40dd9bd45578556f93e7 | |
""" | |
def download_latest_java(destination, java_version=8, package='server-jre', extension='tar.gz', architecture='linux-x64'): | |
valid_packages = ['jre', 'server-jre', 'jdk'] | |
if package not in valid_packages: | |
print('Invalid Java package selection, valid packages are:') | |
for valid_package in valid_packages: | |
print('\t' + valid_package) | |
return None | |
url = "http://www.oracle.com" | |
url_1 = url + "/technetwork/java/javase/downloads/index.html" | |
pattern_1 = '\/technetwork\/java/\javase\/downloads\/'+ package + str(java_version) + '-downloads-.+?\.html' | |
match = regex_websearch(url_1, pattern_1) | |
if match == None: | |
print('Unable to download Java from ' + url_1) | |
print('Website is down or script is outdated') | |
return None | |
url_2 = url + match.group(0) | |
pattern_2 = "http\:\/\/download.oracle\.com\/otn-pub\/java\/jdk\/[7-9]u[0-9]+?-.+?\/" + package + "-[7-9]u[0-9]+?-" + architecture + "." + extension | |
match = regex_websearch(url_2, pattern_2) | |
if match == None: | |
print('Selected architecture.extension \"' + architecture + '.' + extension + '\" is not available') | |
print('Visit \"' + url_2 + '\" to see available architectures and extensions') | |
return None | |
req = urllib.request.Request(match.group(0)) | |
req.add_header('Cookie', 'oraclelicense=accept-securebackup-cookie') | |
destination = dlfile(req, destination) | |
return destination | |
def install_latest_java(destination, java_version=8, package='server-jre', extension='tar.gz', architecture='linux-x64'): | |
latest_java_tar_gz = download_latest_java(destination, java_version=java_version, package=package, extension=extension, architecture=architecture) | |
print('Extracting ' + latest_java_tar_gz) | |
tar_output = subprocess.check_output(['tar', 'xzvf', latest_java_tar_gz, '--directory', destination]) | |
new_java_dir = destination+'/'+tar_output.decode('UTF-8').split('\n')[0] | |
print('Creating symlink for latest java') | |
current_jre_link = destination + '/current-jre' | |
if os.path.exists(current_jre_link): | |
os.remove(current_jre_link) | |
os.symlink(new_java_dir, current_jre_link) | |
def userhome_setup(): | |
missing_programs = [] | |
home_dir=os.environ['HOME'] | |
for program in ["zsh", "screen", "vim" ]: | |
if shutil.which(program) == None: | |
missing_programs.append(program) | |
if(len(missing_programs)!=0): | |
install_command="apt-get install " | |
for program in missing_programs: | |
install_command = install_command + program + ' ' | |
print('to install missing programs use \"'+ install_command + '\" and run this script again') | |
exit(0) | |
print('dowloading configurations for current user') | |
#TODO remove xxxTESTxxx in release versio | |
dlfile('http://git.grml.org/f/grml-etc-core/etc/grml/screenrc_generic', home_dir + '/.screenrc') | |
dlfile('http://git.grml.org/f/grml-etc-core/etc/vim/vimrc', home_dir + '/.vimrcxxxTESTxxx') | |
dlfile('http://git.grml.org/f/grml-etc-core/etc/zsh/zshrc', home_dir + '/.zshrc') | |
dlfile('http://git.grml.org/f/grml-etc-core/etc/skel/.zshrc', home_dir + '/.zshrc.local') | |
if( (os.path.exists(home_dir+"/.jre") and os.path.isdir(home_dir+"/.jre")) == False): | |
print("creating .jre directory") | |
os.mkdir(home_dir + "/.jre") | |
latest_java_tar_gz = install_latest_java(home_dir + '/.jre', java_version=8) | |
path_update_shell_script = '\n\nif [ -d "$HOME/.jre/current-jre/bin" ] ; then\n PATH="$HOME/.jre/current-jre/bin:$PATH"\nfi\n' | |
profile_file = home_dir+'/.profile' | |
if os.path.isfile(profile_file): | |
print('Updating ~/.profile to include java in PATH-variable') | |
f = open(profile_file, 'a') | |
f.write(path_update_shell_script) | |
f.close() | |
zshrc_local_file = home_dir+'/.zshrc.local' | |
if os.path.isfile(zshrc_local_file): | |
print('Updating ~/.zshrc.local to include java in PATH-variable') | |
f = open(zshrc_local_file, 'a') | |
f.write(path_update_shell_script) | |
f.close() | |
open(home_dir+'/.ftb_minecraft_server_home_setup_finished', 'a').close() | |
def interactive_selection(message, data): | |
print(message) | |
i = 0 | |
if isinstance(data, dict): | |
for key in data.keys(): | |
print(str(i) + ': \t' + key + ' - ' + data.get(key)) #TODO replace with printf (for better look and feel | |
i+=1 | |
selection = list(data.keys())[int(input("Selection: "))] # TODO check user input | |
return (selection, data.get(selection)) | |
if isinstance(data, list): | |
for item in data: | |
print(str(i) + ': \t' + item) | |
i+=1 | |
index = int(input("Selection: ")) #TODO check user input | |
return (index, data[index]) | |
def ask_user(message, default_value, input_check_regex): | |
input_str = '' | |
while re.search(input_check_regex, input_str) == None: | |
input_str = input(message + ' [' + default_value + ']: ') | |
if input_str == '': | |
input_str=default_value | |
break | |
return input_str | |
""" | |
returns /cat/proc/meminfo as dictionary with a ( amount, unittype ) tuple as data. If unittype is a empty string | |
the amount is a count | |
""" | |
def meminfo(): | |
meminfo_d = {} | |
if(os.path.exists('/proc/meminfo')): | |
with open('/proc/meminfo', 'r') as proc_meminfo: | |
meminfo_content = proc_meminfo.readlines() | |
for line in meminfo_content: | |
match = re.search('(^[a-zA-Z0-9_\(\)]+): +(\d+) {0,1}(\w*)', line) | |
meminfo_d.update({match.group(1): (int(match.group(2)), match.group(3))}) | |
return meminfo_d | |
else: | |
return None | |
def num_of_processing_units(): | |
return int(subprocess.check_output('nproc').decode('UTF-8')) | |
def update_server_start_sh(server_start_sh_path, xmx_value=2048, xms_value=512, java_version=8, thread_count=1): | |
server_start_sh_content = open(server_start_sh_path, 'r').read() | |
# shutil.move(destination_folder + '/ServerStart.sh', destination_folder + '/ServerStart.sh.bak') | |
shutil.move(server_start_sh_path, server_start_sh_path + '.bak') | |
match = re.search('java .+-jar ([a-zA-Z0-9_\-\.]+) (.*)$', server_start_sh_content, re.MULTILINE) | |
server_start_sh_startline = match.group(0) | |
server_jar=match.group(1) | |
server_jar_cmd_arguments = match.group(2) | |
foldername = os.path.basename(os.path.dirname(server_start_sh_path)) | |
if java_version==8 : | |
#remove depracted options | |
server_start_sh_startline = 'screen -dmS ' + foldername + ' java -Xms'+str(xms_value)+'m -Xmx'+str(xmx_value)+ \ | |
'm -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+CMSIncrementalPacing -XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads='\ | |
+str(thread_count)+' -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -jar '+ server_jar + ' ' + server_jar_cmd_arguments + '\n' | |
server_start_sh_content = re.sub('java .+-jar ([a-zA-Z0-9_\-\.]+) (.*)$', server_start_sh_startline , server_start_sh_content, flags=re.MULTILINE) | |
with open(server_start_sh_path, 'w') as f: | |
f.write(server_start_sh_content) | |
f.close() | |
os.chmod(server_start_sh_path, stat.S_IXUSR | stat.S_IWUSR | stat.S_IRUSR) # chmod 700 | |
def ftb_server_install(): | |
# 1 Download and extract server files | |
repo_servers = json.loads(urllib.request.urlopen('http://www.creeperrepo.net/edges.json').read().decode('UTF-8')) | |
repo_servers.update(json.loads(urllib.request.urlopen('http://ftb.cursecdn.com/edges.json').read().decode('UTF-8'))) | |
selected_repo_mirror = interactive_selection("Select the nearest server to download feed the beast modpack servers: )", repo_servers) | |
# req = urllib.request.urlopen('http://' + selected_repo_mirror[1] + '/FTB2/static/modpacks.xml') # modpacks.xml isn't the latest on all mirrors!!!!!A | |
modpack_xml_files = { 'FTB Modpacks': 'http://ftb.cursecdn.com/FTB2/static/modpacks.xml', '3rd Party Modpacks':'http://ftb.cursecdn.com/FTB2/static/thirdparty.xml' } # Hardcoded mirror for xml-Files | |
xml_url = interactive_selection('Select Modpack Type:', modpack_xml_files)[1] | |
req = urllib.request.urlopen(xml_url) | |
content = req.read() | |
modpacks = xml.etree.ElementTree.fromstring(content.decode('UTF-8')) | |
modpack_list = [] | |
for modpack in modpacks: | |
modpack_list.append(modpack.attrib.get('name')) | |
selected_mod = interactive_selection("Select the Modpack", modpack_list)[0] | |
modpack_versions = modpacks[selected_mod].get('oldVersions').split(';') | |
selected_version = interactive_selection("Select the version of the mod:", modpack_versions)[1] | |
download_url = 'http://' + selected_repo_mirror[1] + '/FTB2/modpacks/' + modpacks[selected_mod].get('dir') + '/' + selected_version.replace('.','_') + '/' + modpacks[selected_mod].get('serverPack') | |
destination_zip_file = os.environ['HOME'] + '/' + modpacks[selected_mod].get('serverPack').replace('.zip', '_' + selected_version.replace('.','_') + '.zip') | |
destination_folder = destination_zip_file.replace('.zip','') | |
dlfile(download_url, destination_zip_file) | |
print('Extracting "' + destination_zip_file + '" to "' + destination_folder + '"') | |
with zipfile.ZipFile(destination_zip_file, 'r') as zipf: | |
zipf.extractall(destination_folder) | |
# 2 Configure minecraft server startscript | |
print('configure ServerStart.sh - \n\tThe default values are calcualted based on your system specification') | |
avail_mem = meminfo().get('MemAvailable')[0]//1024 | |
total_mem = meminfo().get('MemTotal')[0]//1024 | |
xmx_value = avail_mem-(total_mem//10) # keep 10% of total mem free | |
xmx_value = int(ask_user('Java Xmx Size (Maximal Heap Space) in MiB', str(xmx_value), '\d+')) | |
xms_value = xmx_value//2 | |
xms_value = int(ask_user('Java Xms Size (Initial Heap Space) in MiB', str(xms_value), '\d+')) | |
thread_count = num_of_processing_units() | |
thread_count = ask_user('Number of parallel Garbage Collector threads', str(thread_count), '[1-3]{0,1}[0-9]') # more then 30 CPUs are rare in MC Servers maybe wrong? | |
print('update_server_start_sh(' + destination_folder + '/ServerStart.sh, xmx_value=' + str(xmx_value) + ', xms_value=' + str(xms_value) +', thread_count=' + str(thread_count) + ')' ) | |
update_server_start_sh(destination_folder + '/ServerStart.sh', xmx_value=xmx_value, xms_value=xms_value, thread_count=thread_count) | |
if __name__ == "__main__": | |
if os.name != 'posix' : | |
print(os.name + 'is not supported') | |
exit(255) | |
# Step 1 - setup home directory for minecraft installations.a | |
if(os.path.isfile(os.environ['HOME']+'/.ftb_minecraft_server_home_setup_finished')==False): | |
userhome_setup() | |
ftb_server_install() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment