Skip to content

Instantly share code, notes, and snippets.

@LordH3lmchen
Last active August 29, 2015 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LordH3lmchen/9e2cf53114acd27abe03 to your computer and use it in GitHub Desktop.
Save LordH3lmchen/9e2cf53114acd27abe03 to your computer and use it in GitHub Desktop.
#!/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