mynode/rootfs/standard/var/pynode/application_info.py

511 lines
19 KiB
Python
Raw Normal View History

from bitcoin_info import *
from lightning_info import *
from electrum_info import *
from device_info import *
from systemctl_info import *
2021-06-23 04:36:10 +00:00
from utilities import *
import copy
import json
import time
import subprocess
import re
import os
# Globals
DYNAMIC_APPLICATIONS_FOLDER = "/usr/share/mynode_apps"
# Cached data
2022-03-07 04:02:30 +00:00
JSON_APPLICATION_CACHE_FILE = "/tmp/app_cache.json"
mynode_applications = None
# Utility functions
2021-05-08 19:54:20 +00:00
def reinstall_app(app):
if not is_upgrade_running():
mark_upgrade_started()
# Clear app data
clear_application_cache()
touch("/tmp/skip_base_upgrades")
2021-05-08 19:54:20 +00:00
# Reinstall
os.system("mkdir -p /home/admin/upgrade_logs")
file1 = "/home/admin/upgrade_logs/reinstall_{}.txt".format(app)
file2 = "/home/admin/upgrade_logs/upgrade_log_latest.txt"
cmd = "/usr/bin/mynode_reinstall_app.sh {} 2>&1 | tee {} {}".format(app,file1, file2)
subprocess.call(cmd, shell=True)
# Sync
os.system("sync")
time.sleep(1)
# Reboot
reboot_device()
def uninstall_app(app):
# Make sure app is disabled
disable_service(app)
# Clear app data
clear_application_cache()
# Uninstall App
os.system("mkdir -p /home/admin/upgrade_logs")
file1 = "/home/admin/upgrade_logs/uninstall_{}.txt".format(app)
file2 = "/home/admin/upgrade_logs/uninstall_log_latest.txt"
cmd = "/usr/bin/mynode_uninstall_app.sh {} 2>&1 | tee {} {}".format(app,file1, file2)
subprocess.call(cmd, shell=True)
# Sync
os.system("sync")
def is_installed(short_name):
2021-05-18 01:41:26 +00:00
filename1 = "/home/bitcoin/.mynode/install_"+short_name
filename2 = "/mnt/hdd/mynode/settings/install_"+short_name
if os.path.isfile(filename1):
return True
elif os.path.isfile(filename2):
return True
return False
2022-03-22 04:36:54 +00:00
def get_app_current_version_from_file(short_name):
2021-05-08 19:54:20 +00:00
version = "unknown"
filename1 = "/home/bitcoin/.mynode/"+short_name+"_version"
filename2 = "/mnt/hdd/mynode/settings/"+short_name+"_version"
if os.path.isfile(filename1):
version = get_file_contents(filename1)
elif os.path.isfile(filename2):
version = get_file_contents(filename2)
else:
version = "not installed"
# For versions that are hashes, shorten them
version = version[0:16]
2021-11-07 04:40:57 +00:00
return to_string(version)
2021-05-08 19:54:20 +00:00
2022-03-22 04:36:54 +00:00
def get_app_latest_version_from_file(app):
2021-05-08 19:54:20 +00:00
version = "unknown"
filename1 = "/home/bitcoin/.mynode/"+app+"_version_latest"
filename2 = "/mnt/hdd/mynode/settings/"+app+"_version_latest"
if os.path.isfile(filename1):
version = get_file_contents(filename1)
elif os.path.isfile(filename2):
version = get_file_contents(filename2)
else:
version = "error"
# For versions that are hashes, shorten them
version = version[0:16]
2021-11-07 04:40:57 +00:00
return to_string(version)
2021-05-08 19:54:20 +00:00
2021-07-13 01:39:15 +00:00
def initialize_application_defaults(app):
if not "name" in app: app["name"] = "NO_NAME"
if not "short_name" in app: app["short_name"] = "NO_SHORT_NAME"
if not "description" in app: app["description"] = ""
if not "screenshots" in app: app["screenshots"] = []
if not "app_tile_name" in app: app["app_tile_name"] = app["name"]
if not "is_premium" in app: app["is_premium"] = False
2022-03-22 04:36:54 +00:00
if not "current_version" in app: app["current_version"] = get_app_current_version_from_file( app["short_name"] )
if not "latest_version" in app: app["latest_version"] = get_app_latest_version_from_file( app["short_name"] )
2021-07-13 01:39:15 +00:00
if not "is_beta" in app: app["is_beta"] = False
app["is_installed"] = is_installed( app["short_name"] )
if not "can_reinstall" in app: app["can_reinstall"] = True
if not "can_uninstall" in app: app["can_uninstall"] = False
if not "requires_lightning" in app: app["requires_lightning"] = False
if not "requires_electrs" in app: app["requires_electrs"] = False
if not "requires_bitcoin" in app: app["requires_bitcoin"] = False
if not "requires_docker_image_installation" in app: app["requires_docker_image_installation"] = False
if not "supports_testnet" in app: app["supports_testnet"] = False
if not "show_on_homepage" in app: app["show_on_homepage"] = False
if not "show_on_application_page" in app: app["show_on_application_page"] = True
if not "can_enable_disable" in app: app["can_enable_disable"] = True
if not "is_enabled" in app: app["is_enabled"] = is_service_enabled( app["short_name"] )
2022-03-07 04:02:30 +00:00
#app["status"] = get_application_status( app["short_name"] )
#app["status_color"] = get_service_status_color( app["short_name"] )
2021-11-19 20:47:04 +00:00
if not "hide_status_icon" in app: app["hide_status_icon"] = False
2021-07-13 01:39:15 +00:00
if not "log_file" in app: app["log_file"] = get_application_log_file( app["short_name"] )
if not "journalctl_log_name" in app: app["journalctl_log_name"] = None
if not "homepage_order" in app: app["homepage_order"] = 9999
if not "homepage_section" in app: app["homepage_section"] = ""
2021-04-25 19:25:49 +00:00
if app["homepage_section"] == "" and app["show_on_homepage"]:
app["homepage_section"] = "apps"
2021-07-13 01:39:15 +00:00
if not "app_tile_button_text" in app: app["app_tile_button_text"] = app["app_tile_name"]
if not "app_tile_default_status_text" in app: app["app_tile_default_status_text"] = ""
if not "app_tile_running_status_text" in app: app["app_tile_running_status_text"] = app["app_tile_default_status_text"]
if not "app_tile_button_href" in app: app["app_tile_button_href"] = "#"
return app
2022-03-07 04:02:30 +00:00
def update_application(app, include_status=False):
short_name = app["short_name"]
app["is_enabled"] = is_service_enabled(short_name)
2022-03-07 04:02:30 +00:00
if include_status:
app["status"] = get_application_status( app["short_name"] )
app["status_color"] = get_service_status_color( app["short_name"] )
def initialize_applications():
global mynode_applications
apps = []
2021-06-21 05:06:27 +00:00
# Update latest version files
os.system("/usr/bin/mynode_update_latest_version_files.sh")
2021-07-13 01:39:15 +00:00
# Opening JSON file
with open('/usr/share/mynode/application_info.json', 'r') as app_info_file:
apps = json.load(app_info_file)
for index, app in enumerate(apps):
apps[index] = initialize_application_defaults(app)
2022-03-26 03:42:55 +00:00
# Load dynamic app JSON files
dynamic_app_dir = get_dynamic_app_dir()
dynamic_app_names = get_dynamic_app_names()
for app_name in dynamic_app_names:
try:
app_dir = dynamic_app_dir + "/" + app_name
with open(app_dir + "/" + app_name + ".json", 'r') as app_info_file:
app = json.load(app_info_file)
apps.append(initialize_application_defaults(app))
except Exception as e:
log_message("ERROR: Could not initialize dynamic app {} - {}".format(app_name, str(e)))
mynode_applications = copy.deepcopy(apps)
2021-07-13 01:39:15 +00:00
return
2022-03-07 04:02:30 +00:00
def update_applications(include_status=False):
global mynode_applications
for app in mynode_applications:
2022-03-07 04:02:30 +00:00
update_application(app, include_status)
2021-05-08 19:54:20 +00:00
def clear_application_cache():
global mynode_applications
mynode_applications = None
2021-06-21 05:06:27 +00:00
def trigger_application_refresh():
touch("/tmp/need_application_refresh")
2021-06-21 05:06:27 +00:00
2021-06-10 05:14:43 +00:00
def need_application_refresh():
global mynode_applications
if mynode_applications == None:
return True
if os.path.isfile("/tmp/need_application_refresh"):
os.system("rm /tmp/need_application_refresh")
os.system("sync")
return True
return False
2022-03-07 04:02:30 +00:00
def get_all_applications(order_by="none", include_status=False):
global mynode_applications
2021-06-10 05:14:43 +00:00
if need_application_refresh():
initialize_applications()
else:
update_applications()
2022-03-07 04:02:30 +00:00
if include_status:
update_applications(include_status)
apps = copy.deepcopy(mynode_applications)
if order_by == "alphabetic":
apps.sort(key=lambda x: x["name"])
2021-04-25 19:25:49 +00:00
elif order_by == "homepage":
apps.sort(key=lambda x: x["homepage_order"])
return apps
2022-03-07 04:02:30 +00:00
# Only call this from the www python process so status data is available
def update_application_json_cache():
global JSON_APPLICATION_CACHE_FILE
apps = get_all_applications(order_by="alphabetic", include_status=True)
return set_dictionary_file_cache(apps, JSON_APPLICATION_CACHE_FILE)
# Getting the data can be called from any process
def get_all_applications_from_json_cache():
global JSON_APPLICATION_CACHE_FILE
return get_dictionary_file_cache(JSON_APPLICATION_CACHE_FILE)
def get_application(short_name):
apps = get_all_applications()
for app in apps:
if app["short_name"] == short_name:
return app
return None
def is_application_valid(short_name):
apps = get_all_applications()
for app in apps:
if app["short_name"] == short_name:
return True
return False
# Application Functions
def get_application_log(short_name):
app = get_application(short_name)
if app:
if app["log_file"] != None:
return get_file_log( app["log_file"] )
elif app["journalctl_log_name"] != None:
return get_journalctl_log( app["journalctl_log_name"] )
else:
return get_journalctl_log(short_name)
else:
# Log may be custom / non-app service
if short_name == "startup":
return get_journalctl_log("mynode")
elif short_name == "quicksync":
return get_quicksync_log()
elif short_name == "docker":
return get_journalctl_log("docker")
elif short_name == "docker_image_build":
return get_journalctl_log("docker_images")
2022-01-07 03:32:21 +00:00
elif short_name == "usb_extras":
return get_journalctl_log("usb_extras")
2021-07-04 03:39:30 +00:00
elif short_name == "www":
return get_journalctl_log("www")
else:
return "ERROR: App or log not found ({})".format(short_name)
2021-07-13 01:39:15 +00:00
def get_application_log_file(short_name):
if short_name == "bitcoin":
return get_bitcoin_log_file()
return None
2021-04-25 19:25:49 +00:00
def get_application_status_special(short_name):
if short_name == "bitcoin":
return get_bitcoin_status()
elif short_name == "lnd":
return get_lnd_status()
elif short_name == "vpn":
if os.path.isfile("/home/pivpn/ovpns/mynode_vpn.ovpn"):
return "Running"
else:
return "Setting up..."
elif short_name == "electrs":
return get_electrs_status()
elif short_name == "whirlpool":
if not os.path.isfile("/mnt/hdd/mynode/whirlpool/whirlpool-cli-config.properties"):
return "Waiting for initialization..."
elif short_name == "dojo":
try:
2022-01-30 19:58:00 +00:00
dojo_initialized = to_string(subprocess.check_output("docker inspect --format={{.State.Running}} db", shell=True).strip())
2021-04-25 19:25:49 +00:00
except:
dojo_initialized = ""
if dojo_initialized != "true":
return "Error"
return ""
def get_application_status(short_name):
# Make sure app is valid
if not is_application_valid(short_name):
return "APP NOT FOUND"
# Get application
app = get_application(short_name)
# Check Disabled, Testnet, Lightning, Electrum requirements...
if is_testnet_enabled() and not app["supports_testnet"]:
return "Requires Mainnet"
if app["requires_docker_image_installation"] and is_installing_docker_images():
return "Installing..."
if app["requires_lightning"] and not is_lnd_ready():
return "Waiting on Lightning"
if app["requires_electrs"] and not is_electrs_active():
return "Waiting on Electrum"
2021-04-25 19:25:49 +00:00
if not app["is_enabled"]:
2021-11-07 04:40:57 +00:00
return to_string(app["app_tile_default_status_text"])
2021-04-25 19:25:49 +00:00
if app["requires_bitcoin"] and not is_bitcoin_synced():
return "Waiting on Bitcoin"
2021-04-25 19:25:49 +00:00
# Check special cases
special_status = get_application_status_special(short_name)
if special_status != "":
return special_status
# Return
return app["app_tile_running_status_text"]
def get_application_status_color_special(short_name):
if short_name == "lnd":
return get_lnd_status_color()
elif short_name == "whirlpool":
if not os.path.isfile("/mnt/hdd/mynode/whirlpool/whirlpool-cli-config.properties"):
return "yellow"
elif short_name == "dojo":
try:
2022-01-30 19:58:00 +00:00
dojo_initialized = to_string(subprocess.check_output("docker inspect --format={{.State.Running}} db", shell=True).strip())
2021-04-25 19:25:49 +00:00
except:
dojo_initialized = ""
if dojo_initialized != "true":
return "red"
2022-03-07 04:02:30 +00:00
elif short_name == "premium_plus":
if has_premium_plus_token():
if get_premium_plus_is_connected():
return "green"
else:
return "red"
else:
return "gray"
2021-04-25 19:25:49 +00:00
return ""
def get_application_status_color(short_name):
# Make sure app is valid
if not is_application_valid(short_name):
return "gray"
# Get application
app = get_application(short_name)
2021-11-19 20:47:04 +00:00
# Check hidden icon
if app["hide_status_icon"]:
return "clear"
2021-04-25 19:25:49 +00:00
# Check Disabled, Testnet, Lightning, Electrum requirements...
if is_testnet_enabled() and not app["supports_testnet"]:
return "gray"
if app["requires_docker_image_installation"] and is_installing_docker_images():
return "yellow"
if app["requires_lightning"] and not is_lnd_ready():
return "gray"
2021-04-25 19:25:49 +00:00
if app["can_enable_disable"] and not app["is_enabled"]:
return "gray"
if app["requires_bitcoin"] and not is_bitcoin_synced():
return "yellow"
if app["requires_electrs"] and not is_electrs_active():
return "yellow"
# Check special cases
special_status_color = get_application_status_color_special(short_name)
if special_status_color != "":
return special_status_color
# Return service operational status
return get_service_status_color(short_name)
def get_application_sso_token(short_name):
# Make sure app is valid
if not is_application_valid(short_name):
return "APP_NOT_FOUND"
2021-11-07 04:40:57 +00:00
return get_sso_token(short_name)
2021-04-25 19:25:49 +00:00
def get_application_sso_token_enabled(short_name):
# Make sure app is valid
if not is_application_valid(short_name):
return "APP_NOT_FOUND"
return get_sso_token_enabled(short_name)
def restart_application(short_name):
try:
subprocess.check_output('systemctl restart {}'.format(short_name), shell=True)
return True
except Exception as e:
2021-06-21 05:06:27 +00:00
return False
def has_customized_app_versions():
if os.path.isfile("/usr/share/mynode/mynode_app_versions_custom.sh"):
return True
if os.path.isfile("/mnt/hdd/mynode/settings/mynode_app_versions_custom.sh"):
return True
2021-06-23 04:36:10 +00:00
return False
def get_app_version_data():
try:
2022-01-30 19:58:00 +00:00
contents = to_string(subprocess.check_output('cat /usr/share/mynode/mynode_app_versions.sh | grep -v "_VERSION_FILE=" | grep "="', shell=True))
2021-06-23 04:36:10 +00:00
return contents
except Exception as e:
return "ERROR"
def get_custom_app_version_data():
if os.path.isfile("/usr/share/mynode/mynode_app_versions_custom.sh"):
return to_string( get_file_contents("/usr/share/mynode/mynode_app_versions_custom.sh") )
2021-06-23 04:36:10 +00:00
if os.path.isfile("/mnt/hdd/mynode/settings/mynode_app_versions_custom.sh"):
return to_string( get_file_contents("/mnt/hdd/mynode/settings/mynode_app_versions_custom.sh") )
2021-06-23 04:36:10 +00:00
return ""
def save_custom_app_version_data(data):
set_file_contents("/usr/share/mynode/mynode_app_versions_custom.sh", data)
set_file_contents("/mnt/hdd/mynode/settings/mynode_app_versions_custom.sh", data)
os.system("sync")
trigger_application_refresh()
2021-06-23 04:36:10 +00:00
def reset_custom_app_version_data():
os.system("rm -f /usr/share/mynode/mynode_app_versions_custom.sh")
os.system("rm -f /mnt/hdd/mynode/settings/mynode_app_versions_custom.sh")
os.system("sync")
trigger_application_refresh()
######################################################################################
## Dynamic Apps
######################################################################################
def get_dynamic_app_dir():
global DYNAMIC_APPLICATIONS_FOLDER
return DYNAMIC_APPLICATIONS_FOLDER
def get_dynamic_app_names():
app_dir = get_dynamic_app_dir()
app_names = []
for app_folder_name in os.listdir( app_dir ):
if os.path.isdir(app_dir + "/" +app_folder_name):
app_names.append(app_folder_name)
return app_names
def init_dynamic_app(app_info):
app_name = app_info["short_name"]
app_dir = DYNAMIC_APPLICATIONS_FOLDER + "/" + app_name
log_message(" Loading " + app_name + "...")
os.system("cp -f {} {}".format(app_dir+"/app.service", "/etc/systemd/system/"+app_name+".service"))
os.system("cp -f {} {}".format(app_dir+"/"+app_name+".png", "/var/www/mynode/static/images/app_icons/"+app_name+".png"))
if (os.path.isfile(app_dir+"/scripts/pre_"+app_name+".sh")):
os.system("cp -f {} {}".format(app_dir+"/scripts/pre_"+app_name+".sh", "/usr/bin/service_scripts/pre_"+app_name+".sh"))
if (os.path.isfile(app_dir+"/scripts/post_"+app_name+".sh")):
os.system("cp -f {} {}".format(app_dir+"/scripts/post_"+app_name+".sh", "/usr/bin/service_scripts/post_"+app_name+".sh"))
if (os.path.isfile(app_dir+"/scripts/install"+app_name+".sh")):
os.system("cp -f {} {}".format(app_dir+"/scripts/install_"+app_name+".sh", "/usr/bin/service_scripts/install_"+app_name+".sh"))
if (os.path.isfile(app_dir+"/scripts/uninstall"+app_name+".sh")):
os.system("cp -f {} {}".format(app_dir+"/scripts/uninstall"+app_name+".sh", "/usr/bin/service_scripts/uninstall_"+app_name+".sh"))
log_message(" TODO: Install data files")
# For "node" type apps
log_message(" TODO: Need node special files???")
# For "python" type apps
log_message(" TODO: Need python special files???")
# For "docker" type apps
log_message(" TODO: Build dockerfile???")
log_message(" TODO: Install dockerfile???")
2022-03-26 03:42:55 +00:00
# Other init
log_message(" TODO: Other init")
log_message(" TODO: Open Port")
log_message(" TODO: More???")
log_message(" Done.")
def init_dynamic_apps():
# Loop over each app
root_app_dir = get_dynamic_app_dir()
app_names = get_dynamic_app_names()
for app_name in app_names:
log_message("Found Application: {}".format(app_name))
app_dir = root_app_dir + "/" + app_name
try:
app_json_path = app_dir + "/app.json"
with open(app_json_path, 'r') as fp:
app_info = json.load(fp)
init_dynamic_app(app_info)
except Exception as e:
log_message(" ERROR: Error loading app.json file ({})".format(str(e)))
2022-03-26 03:42:55 +00:00
# Reload systemctl files
os.system("systemctl daemon-reload")
# Mark app db for needing reload
# TODO: Need to mark this? all json files should be found early