MRCI/install.py

370 lines
12 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/python3
import os
import subprocess
import shutil
import platform
import sys
import zipfile
import binascii
import tempfile
def cd():
current_dir = os.path.dirname(__file__)
if current_dir != "":
os.chdir(current_dir)
def get_default_install_dir(app_target, app_name):
if platform.system() == "Linux":
return "/opt/" + app_target
else:
return "C:\\Program Files\\" + app_name
def get_default_installer_path(app_ver, app_name):
return os.path.expanduser("~") + os.sep + app_name + "-" + app_ver + ".py"
def get_install_dir(app_target, app_name):
path = get_default_install_dir(app_target, app_name)
print("The default install directory is: " + path)
while(True):
ans = input("Do you want to change it? (y/n): ")
if ans == "y" or ans == "Y":
path = input("Enter a new install directory (leave blank to go back to the default): ")
path = os.path.normpath(path)
break
elif ans == "n" or ans == "N":
break
if path == "":
return get_default_install_dir(app_target, app_name)
else:
return path
def get_installer_path(app_ver, app_name):
path = get_default_installer_path(app_ver, app_name)
print("The built .py installer will placed here: " + path)
while(True):
ans = input("Do you want to change the path? (y/n): ")
if ans == "y" or ans == "Y":
path = input("Enter a new path (leave blank to go back to the default): ")
path = os.path.normpath(path)
break
elif ans == "n" or ans == "N":
break
if path == "":
return get_default_installer_path(app_ver, app_name)
else:
return path
def make_install_dir(path):
try:
if not os.path.exists(path):
os.makedirs(path)
return True
except:
print("Failed to create the install directory, please make sure you are runnning this script with admin rights.")
return False
def replace_text(text, old_text, new_text, offs):
while(True):
try:
index = text.index(old_text, offs)
text = text[:index] + new_text + text[index + len(old_text):]
except ValueError:
break
return text
def sub_copy_file(src, dst, old_text, new_text, offs):
print("set: " + dst + " keyword: " + old_text + " to: " + new_text)
text = ""
with open(src, "r") as rd_file:
text = rd_file.read()
text = replace_text(text, old_text, new_text, offs)
with open(dst, "w") as wr_file:
wr_file.write(text)
def template_to_deploy(src, dst, install_dir, app_name, app_target):
sub_copy_file(src, dst, "$install_dir", install_dir, 0)
sub_copy_file(dst, dst, "$app_name", app_name, 0)
sub_copy_file(dst, dst, "$app_target", app_target, 0)
def verbose_copy(src, dst):
print("cpy: " + src + " --> " + dst)
if os.path.isdir(src):
if os.path.exists(dst) and os.path.isdir(dst):
shutil.rmtree(dst)
shutil.copytree(src, dst)
else:
shutil.copyfile(src, dst)
def verbose_create_symmlink(src, dst):
print("lnk: " + src + " --> " + dst)
if os.path.exists(dst):
os.remove(dst)
os.symlink(src, dst)
def conv_utf8_to_utf16(src, dst):
print("cvt: " + dst + " UTF-8 --> UTF-16")
data = bytearray()
with open(src, 'rb') as source_file:
data = source_file.read()
with open(dst, 'w+b') as dest_file:
dest_file.write(data.decode('utf-8').encode('utf-16'))
def local_install(app_target, app_name):
if platform.system() == "Linux":
if not os.path.exists("app_dir/linux"):
print("An app_dir for the Linux platform could not be found.")
else:
install_dir = get_install_dir(app_target, app_name)
if os.path.exists(install_dir + "/uninstall.sh"):
subprocess.run([install_dir + "/uninstall.sh"])
if make_install_dir(install_dir):
if not os.path.exists("/var/opt/" + app_target):
os.makedirs("/var/opt/" + app_target)
Slimmed down and simplified host administering - I decided to remove the entire concept of a root user. Instead, the host initializes as a blank slate and it will be up to the host admin to create a rank 1 user via the new command line option "-add_admin" to do initial setup with. - There is no longer such a concept as a protected user. Meaning even the last rank 1 user in the host database is allowed to delete or modify the rank of their own account. To prevent permanent "admin lock out" in this scenario the "-elevate" command line option was created. - Host settings are no longer stored in the database. Instead, host settings are now stored in a conf.json file in /etc/mrci/conf.json if running on a linux based OS or in %Programdata%\mrci\conf.json if running on Windows. - Email templates are no longer stored in the database. Instead, the templates can be any file formatted in UTF-8 text stored in the host file system. The files they point to can be modified in the conf.json file. - The conf file also replaced all use env variables so MRCI_DB_PATH, MRCI_WORK_DIR, MRCI_PRIV_KEY and MRCI_PUB_KEY are no longer in use. SSL/TLS cert paths can be modified in the conf file. - Removed email template cmds set_email_template and preview_email. - Also removed cmds close_host, host_config and restart_host. The actions these commands could do is best left to the host system command line. - The database class will now explicitly check for write permissions to the database and throw an appropriate error message if the check fails. "DROP TABLE" SQL abilities were added to make this happen. - Removed async cmds exit(3), maxses(5) and restart(11).
2020-11-10 14:47:00 -05:00
if not os.path.exists("/etc/" + app_target):
os.makedirs("/etc/" + app_target)
template_to_deploy("app_dir/linux/" + app_target + ".sh", install_dir + "/" + app_target + ".sh", install_dir, app_name, app_target)
template_to_deploy("app_dir/linux/uninstall.sh", install_dir + "/uninstall.sh", install_dir, app_name, app_target)
template_to_deploy("app_dir/linux/" + app_target + ".service", "/lib/systemd/system/" + app_target + ".service", install_dir, app_name, app_target)
verbose_copy("app_dir/linux/" + app_target, install_dir + "/" + app_target)
verbose_copy("app_dir/linux/lib", install_dir + "/lib")
verbose_copy("app_dir/linux/sqldrivers", install_dir + "/sqldrivers")
verbose_create_symmlink(install_dir + "/" + app_target + ".sh", "/usr/bin/" + app_target)
subprocess.run(["useradd", "-r", app_target])
subprocess.run(["chmod", "-R", "755", install_dir])
subprocess.run(["chmod", "644", "/lib/systemd/system/" + app_target + ".service"])
Slimmed down and simplified host administering - I decided to remove the entire concept of a root user. Instead, the host initializes as a blank slate and it will be up to the host admin to create a rank 1 user via the new command line option "-add_admin" to do initial setup with. - There is no longer such a concept as a protected user. Meaning even the last rank 1 user in the host database is allowed to delete or modify the rank of their own account. To prevent permanent "admin lock out" in this scenario the "-elevate" command line option was created. - Host settings are no longer stored in the database. Instead, host settings are now stored in a conf.json file in /etc/mrci/conf.json if running on a linux based OS or in %Programdata%\mrci\conf.json if running on Windows. - Email templates are no longer stored in the database. Instead, the templates can be any file formatted in UTF-8 text stored in the host file system. The files they point to can be modified in the conf.json file. - The conf file also replaced all use env variables so MRCI_DB_PATH, MRCI_WORK_DIR, MRCI_PRIV_KEY and MRCI_PUB_KEY are no longer in use. SSL/TLS cert paths can be modified in the conf file. - Removed email template cmds set_email_template and preview_email. - Also removed cmds close_host, host_config and restart_host. The actions these commands could do is best left to the host system command line. - The database class will now explicitly check for write permissions to the database and throw an appropriate error message if the check fails. "DROP TABLE" SQL abilities were added to make this happen. - Removed async cmds exit(3), maxses(5) and restart(11).
2020-11-10 14:47:00 -05:00
subprocess.run(["chown", "-R", app_target + ":" + app_target, "/var/opt/" + app_target])
subprocess.run(["chown", "-R", app_target + ":" + app_target, "/etc/" + app_target])
subprocess.run(["systemctl", "start", app_target])
subprocess.run(["systemctl", "enable", app_target])
print("Installation finished. If you ever need to uninstall this application, run this command with root rights:")
print(" sh " + install_dir + "/uninstall.sh\n")
elif platform.system() == "Windows":
if not os.path.exists("app_dir\\windows"):
print("An app_dir for the Windows platform could not be found.")
else:
install_dir = get_install_dir(app_target, app_name)
if os.path.exists(install_dir + "\\uninstall.bat"):
subprocess.run([install_dir + "\\uninstall.bat"])
if os.path.exists(install_dir):
# this block is here make sure the install_dir is deleted if/when
# the uninstall.bat fails to do so. in my test machine, the .bat
# script will delete install_dir if run directly but not when
# called through subprocess.run() for some reason.
shutil.rmtree(install_dir)
if make_install_dir(install_dir):
verbose_copy("app_dir\\windows", install_dir)
template_to_deploy("app_dir\\windows\\uninstall.bat", install_dir + "\\uninstall.bat", install_dir, app_name, app_target)
template_to_deploy("app_dir\\windows\\shtask.xml", install_dir + "\\shtask.xml", install_dir, app_name, app_target)
sub_copy_file(install_dir + "\\shtask.xml", install_dir + "\\shtask.xml", "UTF-8", "UTF-16", 0)
conv_utf8_to_utf16(install_dir + "\\shtask.xml", install_dir + "\\shtask.xml")
verbose_create_symmlink(install_dir + "\\" + app_target + ".exe", os.environ['WINDIR'] + "\\" + app_target + ".exe")
subprocess.run(["schtasks", "/create", "/xml", install_dir + "\\shtask.xml", "/tn", app_name])
subprocess.run(["schtasks", "/run", "/tn", app_name])
print("Installation finished. If you ever need to uninstall this application, run this batch file with admin rights:")
print(" " + install_dir + "\\uninstall.bat\n")
else:
print("The platform you are running in is not compatible.")
print(" output from platform.system() = " + platform.system())
def dir_tree(path):
ret = []
if os.path.isdir(path):
for entry in os.listdir(path):
full_path = os.path.join(path, entry)
if os.path.isdir(full_path):
for sub_dir_file in dir_tree(full_path):
ret.append(sub_dir_file)
else:
ret.append(full_path)
return ret
def to_hex(data):
return str(binascii.hexlify(data))[2:-1]
def from_hex(text_line):
return binascii.unhexlify(text_line)
def make_install(app_ver, app_name):
path = get_installer_path(app_ver, app_name)
with zipfile.ZipFile("app_dir.zip", "w", compression=zipfile.ZIP_DEFLATED) as zip_file:
print("Compressing app_dir --")
for file in dir_tree("app_dir"):
print("adding file: " + file)
zip_file.write(file)
Slimmed down and simplified host administering - I decided to remove the entire concept of a root user. Instead, the host initializes as a blank slate and it will be up to the host admin to create a rank 1 user via the new command line option "-add_admin" to do initial setup with. - There is no longer such a concept as a protected user. Meaning even the last rank 1 user in the host database is allowed to delete or modify the rank of their own account. To prevent permanent "admin lock out" in this scenario the "-elevate" command line option was created. - Host settings are no longer stored in the database. Instead, host settings are now stored in a conf.json file in /etc/mrci/conf.json if running on a linux based OS or in %Programdata%\mrci\conf.json if running on Windows. - Email templates are no longer stored in the database. Instead, the templates can be any file formatted in UTF-8 text stored in the host file system. The files they point to can be modified in the conf.json file. - The conf file also replaced all use env variables so MRCI_DB_PATH, MRCI_WORK_DIR, MRCI_PRIV_KEY and MRCI_PUB_KEY are no longer in use. SSL/TLS cert paths can be modified in the conf file. - Removed email template cmds set_email_template and preview_email. - Also removed cmds close_host, host_config and restart_host. The actions these commands could do is best left to the host system command line. - The database class will now explicitly check for write permissions to the database and throw an appropriate error message if the check fails. "DROP TABLE" SQL abilities were added to make this happen. - Removed async cmds exit(3), maxses(5) and restart(11).
2020-11-10 14:47:00 -05:00
sub_copy_file(__file__, path, "main(is_sfx=False)", "main(is_sfx=True)\n\n\n", 11000)
with open(path, "a") as dst_file, open("app_dir.zip", "rb") as src_file:
print("Packing the compressed app_dir into the sfx script file --")
dst_file.write("# APP_DIR\n")
stat = os.stat("app_dir.zip")
while(True):
buffer = src_file.read(4000000)
if len(buffer) != 0:
dst_file.write("# " + to_hex(buffer) + "\n")
print(str(src_file.tell()) + "/" + str(stat.st_size))
if len(buffer) < 4000000:
break
os.remove("app_dir.zip")
print("Finished.")
def sfx():
abs_sfx_path = os.path.abspath(__file__)
mark_found = False
os.chdir(tempfile.gettempdir())
with open(abs_sfx_path) as packed_file, open("app_dir.zip", "wb") as zip_file:
stat = os.stat(abs_sfx_path)
print("Unpacking the app_dir compressed file from the sfx script.")
while(True):
line = packed_file.readline()
if not line:
break
elif mark_found:
zip_file.write(from_hex(line[2:-1]))
print(str(packed_file.tell()) + "/" + str(stat.st_size))
else:
if line == "# APP_DIR\n":
mark_found = True
print("Done.")
if not mark_found:
print("The app_dir mark was not found, unable to continue.")
else:
with zipfile.ZipFile("app_dir.zip", "r", compression=zipfile.ZIP_DEFLATED) as zip_file:
print("De-compressing app_dir --")
zip_file.extractall()
print("Preparing for installation.")
os.remove("app_dir.zip")
with open("app_dir" + os.sep + "info.txt") as info_file:
list = info_file.read().split("\n")
local_install(list[0], list[2])
shutil.rmtree("app_dir")
def main(is_sfx):
cd()
app_target = ""
app_ver = ""
app_name = ""
if not is_sfx:
with open("app_dir" + os.sep + "info.txt") as info_file:
list = info_file.read().split("\n")
app_target = list[0]
app_ver = list[1]
app_name = list[2]
if is_sfx:
sfx()
elif "-local" in sys.argv:
local_install(app_target, app_name)
elif "-installer" in sys.argv:
make_install(app_ver, app_name)
else:
print("Do you want to install onto this machine or create an installer?")
print("[1] local machine")
print("[2] create installer")
print("[3] exit")
while(True):
opt = input("select an option: ")
if opt == "1":
local_install(app_target, app_name)
break
elif opt == "2":
make_install(app_ver, app_name)
break
elif opt == "3":
break
if __name__ == "__main__":
main(is_sfx=False)