Large python refactor to use 'pynode' and Premium+ basics

This commit is contained in:
Taylor Helsper 2022-02-25 12:54:42 -06:00
parent cd06269f9c
commit 4a5fe2f73c
26 changed files with 654 additions and 350 deletions

View File

@ -8,6 +8,7 @@ After=network-online.target
[Service]
Type=simple
KillMode=control-group
ExecStart=/usr/local/bin/python3 /usr/bin/mynode_check_in.py
User=root
Group=root

View File

@ -8,7 +8,10 @@ After=lnd.service
[Service]
Type=simple
ExecStart=/usr/bin/mynode_lnd_channel_backup.sh
ExecStartPre=/usr/bin/is_not_shutting_down.sh
ExecStartPre=/usr/bin/wait_on_bitcoin.sh
ExecStartPre=/usr/bin/wait_on_lnd.sh
ExecStart=/usr/local/bin/python3 /usr/bin/mynode_backup_scb.py
Restart=always
RestartSec=1
User=bitcoin

View File

@ -2,17 +2,15 @@
import time
import os
import subprocess
import signal
import logging
from threading import Thread
from systemd import journal
from utilities import *
from drive_info import *
log = logging.getLogger('mynode')
log.addHandler(journal.JournaldLogHandler())
log.setLevel(logging.INFO)
def log_message(msg):
global log
print(msg)
log.info(msg)
set_logger(log)
def set_clone_state(state):
log_message("Clone State: {}".format(state))
@ -49,74 +47,6 @@ def wait_on_clone_error_dismiss():
while os.path.isfile("/tmp/.clone_error"):
time.sleep(1)
def get_drive_size(drive):
size = -1
try:
lsblk_output = subprocess.check_output(f"lsblk -b /dev/{drive} | grep disk", shell=True).decode("utf-8")
parts = lsblk_output.split()
size = int(parts[3])
except:
pass
log_message(f"Drive {drive} size: {size}")
return size
def check_partition_for_mynode(partition):
is_mynode = False
try:
subprocess.check_output(f"mount -o ro /dev/{partition} /mnt/hdd", shell=True)
if os.path.isfile("/mnt/hdd/.mynode"):
is_mynode = True
except Exception as e:
# Mount failed, could be target drive
pass
finally:
time.sleep(1)
os.system("umount /mnt/hdd")
return is_mynode
def find_partitions_for_drive(drive):
partitions = []
try:
ls_output = subprocess.check_output(f"ls /sys/block/{drive}/ | grep {drive}", shell=True).decode("utf-8")
partitions = ls_output.split()
except:
pass
return partitions
def is_drive_detected_by_fdisk(d):
detected = False
try:
# Command fails and throws exception if not mounted
ls_output = subprocess.check_output(f"fdisk -l /dev/{d}", shell=True).decode("utf-8")
detected = True
except:
pass
return detected
def is_drive_mounted(d):
mounted = True
try:
# Command fails and throws exception if not mounted
ls_output = subprocess.check_output(f"grep -qs '/dev/{d}' /proc/mounts", shell=True).decode("utf-8")
except:
mounted = False
return mounted
def find_drives():
drives = []
try:
ls_output = subprocess.check_output("ls /sys/block/ | egrep 'hd.*|vd.*|sd.*|nvme.*'", shell=True).decode("utf-8")
all_drives = ls_output.split()
# Only return drives that are not mounted (VM may have /dev/sda as OS drive)
for d in all_drives:
if is_drive_detected_by_fdisk(d) and not is_drive_mounted(d):
drives.append(d)
except:
pass
return drives
def main():
# Set initial state
@ -130,7 +60,7 @@ def main():
os.system("rm /tmp/.clone_target_drive_has_mynode")
# Detect drives
drives = find_drives()
drives = find_unmounted_drives()
log_message(f"Drives: {drives}")
# Check exactly two drives found

View File

@ -0,0 +1,143 @@
#!/usr/local/bin/python3
import os
import requests
import time
import subprocess
import logging
import shutil
from utilities import *
from drive_info import *
from device_info import *
from inotify_simple import INotify, flags
from systemd import journal
import random
BACKUP_SCB_URL = "https://www.mynodebtc.com/device_api/backup_scb.php"
LND_MAINNET_CHANNEL_FILE = "/mnt/hdd/mynode/lnd/data/chain/bitcoin/mainnet/channel.backup"
LND_MAINNET_CHANNEL_FILE_BACKUP = "/home/bitcoin/lnd_backup/channel.backup"
LND_TESTNET_CHANNEL_FILE = "/mnt/hdd/mynode/lnd/data/chain/bitcoin/testnet/channel.backup"
LND_TESTNET_CHANNEL_FILE_BACKUP = "/home/bitcoin/lnd_backup/channel_testnet.backup"
log = logging.getLogger('lndbackup')
log.addHandler(journal.JournaldLogHandler())
log.setLevel(logging.INFO)
set_logger(log)
# Helper functions
# Local Backup
def local_backup(original_scb, backup_scb):
if os.path.isfile(original_scb):
md5_1 = get_md5_file_hash(original_scb)
md5_2 = "REPLACE_FILE"
if os.path.isfile(backup_scb):
md5_2 = get_md5_file_hash(backup_scb)
log_message(" Hash 1: {}".format(md5_1))
log_message(" Hash 2: {}".format(md5_2))
# If file is missing or different, back it up!
if md5_1 != md5_2:
shutil.copyfile(original_scb, backup_scb)
log_message("Local Backup: Backup Updated!")
else:
log_message("Local Backup: Hashes Match. Skipping Backup.")
else:
log_message("Local Backup: Missing File")
# Remote Backup
def remote_backup(original, backup):
# Mainnet only
if is_testnet_enabled():
log_message("Remote Backup: Skipping (testnet enabled")
return
# Premium+ Feature
if not has_premium_plus_token() or get_premium_plus_token_status != "OK":
log_message("Remote Backup: Skipping (not Premium+)")
return
# POST Data
data = {
"token": get_premium_plus_token(),
"product_key": get_product_key(),
"scb_file": "REPLACE ME WITH FILE CONTENTS"
}
# Setup tor proxy
session = requests.session()
session.proxies = {}
session.proxies['http'] = 'socks5h://localhost:9050'
session.proxies['https'] = 'socks5h://localhost:9050'
# Backup to server
fail_count = 0
backup_success = False
while not backup_success:
try:
# Use tor for check in unless there have been tor 5 failures in a row
r = None
if (fail_count+1) % 5 == 0:
r = requests.post(BACKUP_SCB_URL, data=data, timeout=20)
else:
r = session.post(BACKUP_SCB_URL, data=data, timeout=20)
if r.status_code == 200:
if r.text == "OK":
log_message("Remote Backup: Success ({})".format(r.text))
else:
log_message("Remote Backup: Error: ({})".format(r.text))
backup_success = True
else:
log_message("Remote Backup: Connect Failed. Retrying... Code {}".format(r.status_code))
except Exception as e:
log_message("Remote Backup: Connect Failed. Retrying... Exception {}".format(e))
if not backup_success:
# Check in failed, try again in 1 minute
time.sleep(60)
fail_count = fail_count + 1
return True
def backup(original_scb, backup_scb):
log_message("Backing up SCB file...")
local_backup(original_scb, backup_scb)
remote_backup(original_scb, backup_scb)
log_message("Backup Complete.")
# Backup SCB file
if __name__ == "__main__":
one_hour_in_ms = 60 * 60 * 1000
while True:
try:
# Wait for drive to be mounted
while not is_mynode_drive_mounted():
log_message("Checking if drive mounted...")
time.sleep(10)
log_message("Drive mounted!")
# Determine backup file
original_scb = LND_MAINNET_CHANNEL_FILE
backup_scb = LND_MAINNET_CHANNEL_FILE_BACKUP
if is_testnet_enabled():
original_scb = LND_TESTNET_CHANNEL_FILE
backup_scb = LND_TESTNET_CHANNEL_FILE_BACKUP
# Perform backup
backup(original_scb, backup_scb)
# Watch for updates
inotify = INotify()
watch_flags = flags.CREATE | flags.DELETE | flags.MODIFY | flags.DELETE_SELF
wd = inotify.add_watch(original_scb, watch_flags)
for event in inotify.read(timeout=one_hour_in_ms):
log_message("File changed: " + str(event))
except Exception as e:
log_message("Error: {}".format(e))
time.sleep(60)

View File

@ -4,79 +4,17 @@ import requests
import time
import subprocess
import random
from drive_info import *
from device_info import *
CHECKIN_URL = "https://www.mynodebtc.com/device_api/check_in.php"
latest_version_check_count = 0
# Helper functions
def unset_skipped_product_key():
os.system("rm -rf /home/bitcoin/.mynode/.product_key_skipped")
os.system("rm -rf /mnt/hdd/mynode/settings/.product_key_skipped")
def delete_product_key_error():
os.system("rm -rf /home/bitcoin/.mynode/.product_key_error")
os.system("rm -rf /mnt/hdd/mynode/settings/.product_key_error")
def has_product_key_error():
if os.path.isfile("/home/bitcoin/.mynode/.product_key_error") or os.path.isfile("/mnt/hdd/mynode/settings/.product_key_error"):
return True
return False
def get_current_version():
current_version = "0.0"
try:
with open("/usr/share/mynode/version", "r") as f:
current_version = f.read().strip()
except:
current_version = "error"
return current_version
def get_device_type():
device = subprocess.check_output("mynode-get-device-type", shell=True).decode("utf-8").strip()
return device
def get_device_arch():
arch = subprocess.check_output("uname -m", shell=True).decode("utf-8").strip()
return arch
def get_device_serial():
serial = subprocess.check_output("mynode-get-device-serial", shell=True).decode("utf-8").strip()
return serial
def skipped_product_key():
return os.path.isfile("/home/bitcoin/.mynode/.product_key_skipped") or \
os.path.isfile("/mnt/hdd/mynode/settings/.product_key_skipped")
def has_product_key():
return os.path.isfile("/home/bitcoin/.mynode/.product_key")
def get_product_key():
product_key = "no_product_key"
if skipped_product_key():
return "community_edition"
if not has_product_key():
return "product_key_missing"
try:
with open("/home/bitcoin/.mynode/.product_key", "r") as f:
product_key = f.read().strip()
except:
product_key = "product_key_error"
return product_key
def is_drive_mounted():
mounted = True
try:
# Command fails and throws exception if not mounted
output = subprocess.check_output(f"grep -qs '/mnt/hdd ext4' /proc/mounts", shell=True).decode("utf-8")
except:
mounted = False
return mounted
def get_drive_size():
size = -1
if not is_drive_mounted():
return -3
try:
size = subprocess.check_output("df /mnt/hdd | grep /dev | awk '{print $2}'", shell=True).strip()
size = int(size) / 1000 / 1000
except Exception as e:
size = -2
return size
def get_quicksync_enabled():
enabled = 1
if not is_drive_mounted():
if not is_mynode_drive_mounted():
return -3
if os.path.isfile("/mnt/hdd/mynode/settings/quicksync_disabled"):
enabled = 0
@ -117,7 +55,7 @@ def check_in():
"device_arch": get_device_arch(),
"version": get_current_version(),
"product_key": product_key,
"drive_size": get_drive_size(),
"drive_size": get_mynode_drive_size(),
"quicksync_enabled": get_quicksync_enabled(),
}

View File

@ -1,4 +1,5 @@
#!/bin/bash
# NO LONGER USED - MIGRATED TO PYTHON
source /usr/share/mynode/mynode_config.sh

View File

@ -206,12 +206,15 @@ if ! skip_base_upgrades ; then
[ -d /usr/local/lib/python3.8/site-packages ] && echo "/var/pynode" > /usr/local/lib/python3.8/site-packages/pynode.pth
# Remove old python files so new copies are used (files migrated to pynode)
set +x
PYNODE_FILES="/var/pynode/*.py"
for pynode_file in $PYNODE_FILES
do
echo "Migrating pynode file $pynode_file..."
pynode_file="$(basename -- $pynode_file)"
rm -f /var/www/mynode/$pynode_file*
rm -f /var/www/mynode/${pynode_file} # .py
rm -f /var/www/mynode/${pynode_file}c # .pyc
done
set -x
# Install any pip3 software

View File

@ -6,99 +6,25 @@ import subprocess
import logging
from inotify_simple import INotify, flags
from systemd import journal
import random
from utilities import *
from device_info import *
from drive_info import *
PREMIUM_PLUS_CONNECT_URL = "https://www.mynodebtc.com/device_api/premium_plus_connect.php"
log = logging.getLogger('premium_plus_connect')
log.addHandler(journal.JournaldLogHandler())
log.setLevel(logging.INFO)
set_logger(log)
# Helper functions
def log_message(msg):
global log
print(msg)
log.info(msg)
def set_premium_plus_token_error(msg):
os.system("echo '{}' > /home/bitcoin/.mynode/.premium_plus_token_error".format(msg))
def delete_premium_plus_token_error():
os.system("rm -rf /home/bitcoin/.mynode/.premium_plus_token_error")
os.system("rm -rf /mnt/hdd/mynode/settings/.premium_plus_token_error")
def has_premium_plus_token_error():
if os.path.isfile("/home/bitcoin/.mynode/.premium_plus_token_error") or \
os.path.isfile("/mnt/hdd/mynode/settings/.premium_plus_token_error"):
return True
return False
def get_current_version():
current_version = "0.0"
try:
with open("/usr/share/mynode/version", "r") as f:
current_version = f.read().strip()
except:
current_version = "error"
return current_version
def get_device_type():
device = subprocess.check_output("mynode-get-device-type", shell=True).decode("utf-8").strip()
return device
def get_device_arch():
arch = subprocess.check_output("uname -m", shell=True).decode("utf-8").strip()
return arch
def get_device_serial():
serial = subprocess.check_output("mynode-get-device-serial", shell=True).decode("utf-8").strip()
return serial
def has_premium_plus_token():
return os.path.isfile("/home/bitcoin/.mynode/.premium_plus_token") or \
os.path.isfile("/mnt/hdd/mynode/settings/.premium_plus_token")
def get_premium_plus_token():
token = "no_token"
if not has_premium_plus_token():
return "no_token"
try:
if os.path.isfile("/home/bitcoin/.mynode/.premium_plus_token"):
with open("/home/bitcoin/.mynode/.premium_plus_token", "r") as f:
token = f.read().strip()
elif os.path.isfile("/mnt/hdd/mynode/settings/.premium_plus_token"):
with open("/mnt/hdd/mynode/settings/.premium_plus_token", "r") as f:
token = f.read().strip()
except:
token = "token_error"
return token
def get_product_key():
try:
with open("/home/bitcoin/.mynode/.product_key", "r") as f:
product_key = f.read().strip()
except:
product_key = "product_key_error"
return product_key
def is_drive_mounted():
mounted = True
try:
# Command fails and throws exception if not mounted
output = subprocess.check_output(f"grep -qs '/mnt/hdd ext4' /proc/mounts", shell=True).decode("utf-8")
except:
mounted = False
return mounted
def get_drive_size():
size = -1
if not is_drive_mounted():
return -3
try:
size = subprocess.check_output("df /mnt/hdd | grep /dev | awk '{print $2}'", shell=True).strip()
size = int(size) / 1000 / 1000
except Exception as e:
size = -2
return size
def get_drive_usage():
return "TODO"
# Update hourly
def premium_plus_connect():
# Check in
token = get_premium_plus_token()
data = {
"serial": get_device_serial(),
"device_type": get_device_type(),
@ -106,8 +32,8 @@ def premium_plus_connect():
"version": get_current_version(),
"token": get_premium_plus_token(),
"product_key": get_product_key(),
"drive_size": get_drive_size(),
"drive_usage": get_drive_usage(),
"drive_size": get_mynode_drive_size(),
"drive_usage": get_mynode_drive_usage(),
}
# Setup tor proxy
@ -129,10 +55,10 @@ def premium_plus_connect():
r = session.post(PREMIUM_PLUS_CONNECT_URL, data=data, timeout=20)
if r.status_code == 200:
set_premium_plus_token_status(r.text)
if r.text == "OK":
log_message("Premium+ Connect Success: {}".format(r.text))
else:
set_premium_plus_token_error(r.text)
log_message("Check In Returned Error: {}".format(r.text))
os.system("rm -f /tmp/premium_plus_connect_error")
@ -142,9 +68,11 @@ def premium_plus_connect():
except Exception as e:
log_message("Premium+ Connect Failed. Retrying... Exception {}".format(e))
update_premium_plus_last_sync_time()
if not premium_plus_connect_success:
# Check in failed, try again in 1 minute
os.system("touch /tmp/premium_plus_connect_error")
set_premium_plus_token_status("CONNECTION_ERROR")
time.sleep(60)
fail_count = fail_count + 1
@ -157,26 +85,27 @@ if __name__ == "__main__":
while True:
try:
# Wait for drive to be mounted
while not is_drive_mounted():
while not is_mynode_drive_mounted():
log_message("Checking if drive mounted...")
time.sleep(10)
log_message("Drive mounted!")
# Wait on token
log_message("Waiting on Premium+ Token")
log_message("Looking for Premium+ Token...")
while not os.path.isfile("/home/bitcoin/.mynode/.premium_plus_token"):
time.sleep(10)
log_message("Token found!")
premium_plus_connect()
# Watch for updates
log_message("")
log_message("Watching for file changes or 1hr...")
inotify = INotify()
watch_flags = flags.CREATE | flags.DELETE | flags.MODIFY | flags.DELETE_SELF
wd = inotify.add_watch('/home/bitcoin/.mynode/', watch_flags)
for event in inotify.read(timeout=one_hour_in_ms):
log_message("File changed: " + str(event))
log_message("Running connect again: " + str(event))
premium_plus_connect()
except Exception as e:
log_message("Error: {}".format(e))
time.sleep(60)

View File

@ -78,6 +78,11 @@ rm -rf /etc/motd # Remove simple motd for update-motd.d
mkdir -p /mnt/hdd
mkdir -p /mnt/usb_extras
# Add to python path
[ -d /usr/local/lib/python2.7/dist-packages ] && echo "/var/pynode" > /usr/local/lib/python2.7/dist-packages/pynode.pth
[ -d /usr/local/lib/python3.7/site-packages ] && echo "/var/pynode" > /usr/local/lib/python3.7/site-packages/pynode.pth
[ -d /usr/local/lib/python3.8/site-packages ] && echo "/var/pynode" > /usr/local/lib/python3.8/site-packages/pynode.pth
# Customize logo for resellers
if [ -f /opt/mynode/custom/logo_custom.png ]; then
cp -f /opt/mynode/custom/logo_custom.png /var/www/mynode/static/images/logo_custom.png

View File

@ -2,20 +2,20 @@
import time
import os
import subprocess
import signal
import logging
import random
import string
import json
import atexit
from http.server import HTTPServer, SimpleHTTPRequestHandler
import pyudev
from systemd import journal
from threading import Thread
from utilities import *
from drive_info import *
log = logging.getLogger('mynode')
log.addHandler(journal.JournaldLogHandler())
log.setLevel(logging.INFO)
set_logger(log)
################################
## USB Device Cache
@ -54,12 +54,6 @@ def write_usb_devices_json():
################################
## Utility Functions
################################
def log_message(msg):
global log
print(msg)
log.info(msg)
def set_usb_extras_state(state):
log_message("USB Extras State: {}".format(state))
try:
@ -71,71 +65,6 @@ def set_usb_extras_state(state):
return False
return False
def get_drive_size(drive):
size = -1
try:
lsblk_output = subprocess.check_output(f"lsblk -b /dev/{drive} | grep disk", shell=True).decode("utf-8")
parts = lsblk_output.split()
size = int(parts[3])
except:
pass
log_message(f"Drive {drive} size: {size}")
return size
def mount_partition(partition, folder_name, permissions="ro"):
try:
subprocess.check_output(f"mkdir -p /mnt/usb_extras/{folder_name}", shell=True)
subprocess.check_output(f"mount -o {permissions} /dev/{partition} /mnt/usb_extras/{folder_name}", shell=True)
return True
except Exception as e:
return False
def unmount_partition(folder_name):
os.system(f"umount /mnt/usb_extras/{folder_name}")
os.system(f"rm -rf /mnt/usb_extras/{folder_name}")
time.sleep(1)
def find_partitions_for_drive(drive):
partitions = []
try:
ls_output = subprocess.check_output(f"ls /sys/block/{drive}/ | grep {drive}", shell=True).decode("utf-8")
partitions = ls_output.split()
except:
pass
return partitions
def is_drive_detected_by_fdisk(d):
detected = False
try:
# Command fails and throws exception if not mounted
ls_output = subprocess.check_output(f"fdisk -l /dev/{d}", shell=True).decode("utf-8")
detected = True
except:
pass
return detected
def is_drive_mounted(d):
mounted = True
try:
# Command fails and throws exception if not mounted
ls_output = subprocess.check_output(f"grep -qs '/dev/{d}' /proc/mounts", shell=True).decode("utf-8")
except:
mounted = False
return mounted
def find_unmounted_drives():
drives = []
try:
ls_output = subprocess.check_output("ls /sys/block/ | egrep 'hd.*|vd.*|sd.*|nvme.*'", shell=True).decode("utf-8")
all_drives = ls_output.split()
# Only return drives that are not mounted (VM may have /dev/sda as OS drive)
for d in all_drives:
if is_drive_detected_by_fdisk(d) and not is_drive_mounted(d):
drives.append(d)
except:
pass
return drives
################################
## HTTP Server Functions

View File

@ -290,11 +290,11 @@
"short_name": "premium_plus",
"hide_status_icon": true,
"can_enable_disable": false,
"app_tile_button_text": "Open",
"app_tile_button_text": "Manage",
"app_tile_default_status_text": "Access and Backup",
"app_tile_button_href": "/premium_plus",
"app_tile_button_href": "/premium-plus",
"show_on_application_page": false,
"show_on_homepage": false,
"show_on_homepage": true,
"can_uninstall": false,
"can_reinstall": false,
"journalctl_log_name": "premium_plus_connect",

View File

@ -81,7 +81,7 @@ def check_and_mark_reboot_action(tmp_marker):
def reload_throttled_data():
global cached_data
if os.path.isfile("/tmp/get_throttled_data"):
cached_data["get_throttled_data"] = get_file_contents("/tmp/get_throttled_data")
cached_data["get_throttled_data"] = to_string( get_file_contents("/tmp/get_throttled_data") )
def get_throttled_data():
global cached_data
@ -207,7 +207,7 @@ def cleanup_log(log):
return log
def get_recent_upgrade_log():
log = get_file_contents("/home/admin/upgrade_logs/upgrade_log_latest.txt").decode("utf8")
log = to_string( get_file_contents("/home/admin/upgrade_logs/upgrade_log_latest.txt") )
return cleanup_log(log)
def get_all_upgrade_logs():
@ -237,7 +237,7 @@ def get_all_upgrade_logs():
log["name"] = f
modTimeSeconds = os.path.getmtime(fullpath)
log["date"] = time.strftime('%Y-%m-%d', time.localtime(modTimeSeconds))
log["log"] = cleanup_log( get_file_contents(fullpath).decode("utf8") )
log["log"] = cleanup_log( to_string( get_file_contents(fullpath) ) )
log_list.append( log )
log_id += 1
except Exception as e:
@ -362,7 +362,7 @@ def set_swap_size(size):
return set_file_contents("/mnt/hdd/mynode/settings/swap_size", size)
def get_swap_size():
return get_file_contents("/mnt/hdd/mynode/settings/swap_size")
return to_string( get_file_contents("/mnt/hdd/mynode/settings/swap_size") )
#==================================
# myNode Status
@ -423,19 +423,19 @@ CLONE_STATE_IN_PROGRESS = "in_progress"
CLONE_STATE_COMPLETE = "complete"
def get_clone_state():
return get_file_contents("/tmp/.clone_state")
return to_string( get_file_contents("/tmp/.clone_state") )
def get_clone_error():
return get_file_contents("/tmp/.clone_error")
return to_string( get_file_contents("/tmp/.clone_error") )
def get_clone_progress():
return get_file_contents("/tmp/.clone_progress")
return to_string( get_file_contents("/tmp/.clone_progress") )
def get_clone_source_drive():
return get_file_contents("/tmp/.clone_source")
return to_string( get_file_contents("/tmp/.clone_source") )
def get_clone_target_drive():
return get_file_contents("/tmp/.clone_target")
return to_string( get_file_contents("/tmp/.clone_target") )
def get_clone_target_drive_has_mynode():
return os.path.isfile("/tmp/.clone_target_drive_has_mynode")
@ -563,7 +563,7 @@ def regen_https_cert():
def get_flask_secret_key():
if os.path.isfile("/home/bitcoin/.mynode/flask_secret_key"):
key = get_file_contents("/home/bitcoin/.mynode/flask_secret_key")
key = to_string( get_file_contents("/home/bitcoin/.mynode/flask_secret_key") )
else:
letters = string.ascii_letters
key = ''.join(random.choice(letters) for i in range(32))
@ -573,7 +573,7 @@ def get_flask_secret_key():
def get_flask_session_timeout():
try:
if os.path.isfile("/home/bitcoin/.mynode/flask_session_timeout"):
timeout = get_file_contents("/home/bitcoin/.mynode/flask_session_timeout")
timeout = to_string( get_file_contents("/home/bitcoin/.mynode/flask_session_timeout") )
parts = timeout.split(",")
d = parts[0]
h = parts[1]
@ -607,21 +607,6 @@ def set_www_python3(use_python3):
delete_file("/home/bitcoin/.mynode/.www_use_python3")
#==================================
# Drive Functions
#==================================
def is_uas_usb_enabled():
return os.path.isfile('/home/bitcoin/.mynode/.uas_usb_enabled') or \
os.path.isfile('/mnt/hdd/mynode/settings/.uas_usb_enabled')
def set_uas_usb_enabled(use_uas):
if use_uas:
touch("/home/bitcoin/.mynode/.uas_usb_enabled")
touch("/mnt/hdd/mynode/settings/.uas_usb_enabled")
else:
delete_file("/home/bitcoin/.mynode/.uas_usb_enabled")
delete_file("/mnt/hdd/mynode/settings/.uas_usb_enabled")
#==================================
# Web Server Functions
#==================================
@ -743,6 +728,74 @@ def recheck_product_key():
os.system("systemctl restart check_in")
#==================================
# Premium+ Token Functions
#==================================
def delete_premium_plus_token():
delete_file("/home/bitcoin/.mynode/.premium_plus_token")
delete_file("/mnt/hdd/mynode/settings/.premium_plus_token")
def has_premium_plus_token():
return os.path.isfile("/home/bitcoin/.mynode/.premium_plus_token") or \
os.path.isfile("/mnt/hdd/mynode/settings/.premium_plus_token")
def get_premium_plus_token():
token = "error_1"
if not has_premium_plus_token():
return ""
try:
if os.path.isfile("/home/bitcoin/.mynode/.premium_plus_token"):
with open("/home/bitcoin/.mynode/.premium_plus_token", "r") as f:
token = f.read().strip()
elif os.path.isfile("/mnt/hdd/mynode/settings/.premium_plus_token"):
with open("/mnt/hdd/mynode/settings/.premium_plus_token", "r") as f:
token = f.read().strip()
except:
token = "error_2"
return token
def reset_premium_plus_token_status():
delete_file("/home/bitcoin/.mynode/.premium_plus_token_status")
def set_premium_plus_token_status(msg):
os.system("echo '{}' > /home/bitcoin/.mynode/.premium_plus_token_status".format(msg))
def get_premium_plus_token_status():
status = "UNKNOWN"
if not has_premium_plus_token():
return "No Token Set"
if not os.path.isfile("/home/bitcoin/.mynode/.premium_plus_token_status"):
return "Updating..."
try:
with open("/home/bitcoin/.mynode/.premium_plus_token_status", "r") as f:
status = f.read().strip()
except:
status = "STATUS_ERROR_2"
return status
def get_premium_plus_is_connected():
status = get_premium_plus_token_status()
if status == "OK":
return True
return False
def update_premium_plus_last_sync_time():
t = int(round(time.time()))
os.system("echo '{}' > /home/bitcoin/.mynode/.premium_plus_last_sync".format(t))
def get_premium_plus_last_sync():
try:
now = int(round(time.time()))
last = int(get_file_contents("/home/bitcoin/.mynode/.premium_plus_last_sync"))
diff_min = int((now - last) / 60)
if diff_min == 0:
return "Now"
else:
return "{} minutes(s) ago".format(diff_min)
except Exception as e:
return "Unknown"
def save_premium_plus_token(token):
set_file_contents("/home/bitcoin/.mynode/.premium_plus_token", token)
set_file_contents("/mnt/hdd/mynode/settings/.premium_plus_token", token)
def recheck_premium_plus_token():
reset_premium_plus_token_status()
os.system("systemctl restart premium_plus_connect")
#==================================
# Drive Repair Functions
#==================================

View File

@ -0,0 +1,137 @@
from config import *
from utilities import *
import time
import json
import os
import subprocess
import random
import string
import re
#==================================
# Drive Functions
#==================================
def is_mynode_drive_mounted():
mounted = True
try:
# Command fails and throws exception if not mounted
output = to_string(subprocess.check_output("grep -qs '/mnt/hdd ext4' /proc/mounts", shell=True))
except:
mounted = False
return mounted
def is_device_mounted(d):
mounted = True
try:
# Command fails and throws exception if not mounted
ls_output = to_string(subprocess.check_output("grep -qs '/dev/{}' /proc/mounts".format(d), shell=True))
except:
mounted = False
return mounted
def get_drive_size(drive):
size = -1
try:
lsblk_output = to_string(subprocess.check_output("lsblk -b /dev/{} | grep disk".format(drive), shell=True))
parts = lsblk_output.split()
size = int(parts[3])
except:
pass
#log_message(f"Drive {drive} size: {size}")
return size
def get_mynode_drive_size():
size = -1
if not is_mynode_drive_mounted():
return -3
try:
size = to_string(subprocess.check_output("df /mnt/hdd | grep /dev | awk '{print $2}'", shell=True)).strip()
size = int(size) / 1000 / 1000
except Exception as e:
size = -2
return size
def get_mynode_drive_usage():
return "TODO"
def check_partition_for_mynode(partition):
is_mynode = False
try:
subprocess.check_output("mount -o ro /dev/{} /mnt/hdd".format(partition), shell=True)
if os.path.isfile("/mnt/hdd/.mynode"):
is_mynode = True
except Exception as e:
# Mount failed, could be target drive
pass
finally:
time.sleep(1)
os.system("umount /mnt/hdd")
return is_mynode
def find_partitions_for_drive(drive):
partitions = []
try:
ls_output = to_string(subprocess.check_output("ls /sys/block/{}/ | grep {}".format(drive, drive), shell=True))
partitions = ls_output.split()
except:
pass
return partitions
def is_device_detected_by_fdisk(d):
detected = False
try:
# Command fails and throws exception if not mounted
output = to_string(subprocess.check_output("fdisk -l /dev/{}".format(d), shell=True))
detected = True
except:
pass
return detected
def find_unmounted_drives():
drives = []
try:
ls_output = subprocess.check_output("ls /sys/block/ | egrep 'hd.*|vd.*|sd.*|nvme.*'", shell=True).decode("utf-8")
all_drives = ls_output.split()
# Only return drives that are not mounted (VM may have /dev/sda as OS drive)
for d in all_drives:
if is_device_detected_by_fdisk(d) and not is_device_mounted(d):
drives.append(d)
except:
pass
return drives
#==================================
# Mount / Unmount Parition Functions
#==================================
def mount_partition(partition, folder_name, permissions="ro"):
try:
subprocess.check_output("mkdir -p /mnt/usb_extras/{}".format(folder_name), shell=True)
subprocess.check_output("mount -o {} /dev/{} /mnt/usb_extras/{}".format(permissions, partition, folder_name), shell=True)
return True
except Exception as e:
return False
def unmount_partition(folder_name):
os.system("umount /mnt/usb_extras/{}".format(folder_name))
os.system("rm -rf /mnt/usb_extras/{}".format(folder_name))
time.sleep(1)
#==================================
# Drive Driver Functions
#==================================
def is_uas_usb_enabled():
return os.path.isfile('/home/bitcoin/.mynode/.uas_usb_enabled') or \
os.path.isfile('/mnt/hdd/mynode/settings/.uas_usb_enabled')
def set_uas_usb_enabled(use_uas):
if use_uas:
touch("/home/bitcoin/.mynode/.uas_usb_enabled")
touch("/mnt/hdd/mynode/settings/.uas_usb_enabled")
else:
delete_file("/home/bitcoin/.mynode/.uas_usb_enabled")
delete_file("/mnt/hdd/mynode/settings/.uas_usb_enabled")

View File

@ -409,9 +409,6 @@ def restart_lnd():
time.sleep(1)
def is_testnet_enabled():
return os.path.isfile("/mnt/hdd/mynode/settings/.testnet_enabled")
def get_lightning_wallet_file():
if is_testnet_enabled():
return "/mnt/hdd/mynode/lnd/data/chain/bitcoin/testnet/wallet.db"

View File

@ -11,10 +11,10 @@ def clear_service_enabled_cache():
global service_enabled_cache
service_enabled_cache = {}
def is_service_enabled(service_name):
def is_service_enabled(service_name, force_refresh=False):
global service_enabled_cache
if service_name in service_enabled_cache:
if service_name in service_enabled_cache and force_refresh == False:
return service_enabled_cache[service_name]
code = os.system("systemctl is-enabled {} > /dev/null".format(service_name))

View File

@ -18,23 +18,25 @@ def isPython3():
def to_bytes(s):
if type(s) is bytes:
return s
elif type(s) is str or (sys.version_info[0] < 3 and type(s) is unicode):
elif type(s) is str or (not isPython3() and type(s) is unicode):
return codecs.encode(s, 'utf-8', 'ignore')
else:
raise TypeError("to_bytes: Expected bytes or string, but got %s." % type(s))
def to_string(s):
b = to_bytes(s)
return b.decode("utf-8")
r = b.decode("utf-8")
print("S TYPE: "+str(type(r)))
return r
def quote_plus(s):
if (sys.version_info > (3, 0)):
if isPython3():
return urllib.parse.quote_plus(s)
else:
return urllib.quote_plus(s)
def unquote_plus(s):
if (sys.version_info > (3, 0)):
if isPython3():
return urllib.parse.unquote_plus(s)
else:
return urllib.unquote_plus(s)
@ -66,7 +68,7 @@ def get_file_contents(filename):
contents = "UNKNOWN"
try:
with open(filename, "r") as f:
contents = f.read().strip()
contents = to_string(f.read()).strip()
except:
contents = "ERROR"
return to_bytes(contents)
@ -90,11 +92,12 @@ def log_message(msg):
# Logs to www log
global mynode_logger
if mynode_logger != None:
print(msg)
mynode_logger.info(msg)
def set_logger(l):
def set_logger(logger):
global mynode_logger
mynode_logger = l
mynode_logger = logger
def get_logger():
global mynode_logger
@ -153,3 +156,15 @@ def download_file(directory, filename, downloaded_file_name=None, as_attachment=
return send_from_directory(directory=directory, path=filename, filename=None, as_attachment=as_attachment)
else:
return send_from_directory(directory=directory, filename=filename, as_attachment=as_attachment)
#==================================
# Hashing Functions
#==================================
def get_md5_file_hash(path):
import hashlib
if not os.path.isfile(path):
return "MISSING_FILE"
try:
return hashlib.md5(open(path,'rb').read()).hexdigest()
except Exception as e:
return "ERROR ({})".format(e)

View File

@ -381,9 +381,9 @@ def get_app_version_data():
def get_custom_app_version_data():
if os.path.isfile("/usr/share/mynode/mynode_app_versions_custom.sh"):
return get_file_contents("/usr/share/mynode/mynode_app_versions_custom.sh")
return to_string( get_file_contents("/usr/share/mynode/mynode_app_versions_custom.sh") )
if os.path.isfile("/mnt/hdd/mynode/settings/mynode_app_versions_custom.sh"):
return get_file_contents("/mnt/hdd/mynode/settings/mynode_app_versions_custom.sh")
return to_string( get_file_contents("/mnt/hdd/mynode/settings/mynode_app_versions_custom.sh") )
return ""
def save_custom_app_version_data(data):

View File

@ -8,7 +8,6 @@ from subprocess import check_output, check_call
from electrum_info import *
from user_management import check_logged_in
import socket
import hashlib
import json
import time

View File

@ -15,6 +15,7 @@ from wardenterminal import mynode_wardenterminal
from lndmanage import mynode_lndmanage
from manage_apps import mynode_manage_apps
from usb_extras import mynode_usb_extras
from premium_plus import mynode_premium_plus
from tor import mynode_tor
from vpn import mynode_vpn
from electrum_server import *
@ -97,6 +98,7 @@ app.register_blueprint(mynode_wardenterminal)
app.register_blueprint(mynode_lndmanage)
app.register_blueprint(mynode_manage_apps)
app.register_blueprint(mynode_tor)
app.register_blueprint(mynode_premium_plus)
app.register_blueprint(mynode_electrum_server)
app.register_blueprint(mynode_vpn)
app.register_blueprint(mynode_usb_extras)
@ -256,6 +258,8 @@ def index():
return render_template('state.html', **templateData)
elif status == STATE_DRIVE_CLONE:
clone_state = get_clone_state()
log_message("CLONE_STATE")
log_message(clone_state)
if clone_state == CLONE_STATE_DETECTING:
templateData = {
"title": "myNode Clone Tool",

View File

@ -0,0 +1,60 @@
from flask import Blueprint, render_template, session, abort, Markup, request, redirect, flash
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
from pprint import pprint, pformat
from device_info import *
from user_management import check_logged_in
from enable_disable_functions import restart_service
import json
import time
mynode_premium_plus = Blueprint('mynode_premium_plus',__name__)
### Page functions
@mynode_premium_plus.route("/premium-plus")
def premium_plus_page():
check_logged_in()
# Load page
templateData = {
"title": "myNode Premium+",
"has_access_token": has_premium_plus_token(),
"access_token": get_premium_plus_token(),
"status": get_premium_plus_token_status(),
"is_connected": get_premium_plus_is_connected(),
"last_sync": get_premium_plus_last_sync(),
"ui_settings": read_ui_settings()
}
return render_template('premium_plus.html', **templateData)
@mynode_premium_plus.route("/premium-plus/sync")
def premium_plus_sync_page():
check_logged_in()
restart_service("premium_plus_connect")
time.sleep(3)
flash("Syncing...", category="message")
return redirect("/premium-plus")
@mynode_premium_plus.route("/premium-plus/clear-token")
def premium_plus_clear_token_page():
check_logged_in()
delete_premium_plus_token()
reset_premium_plus_token_status()
restart_service("premium_plus_connect")
time.sleep(3)
flash("Token Cleared", category="message")
return redirect("/premium-plus")
@mynode_premium_plus.route("/premium-plus/set-token", methods=["POST"])
def premium_plus_set_token_page():
check_logged_in()
token = request.form.get('token')
if token == None:
flash("Missing Token", category="error")
return redirect("/premium-plus")
save_premium_plus_token(token)
restart_service("premium_plus_connect")
time.sleep(3)
flash("Token Set", category="message")
return redirect("/premium-plus")

View File

@ -11,6 +11,7 @@ from user_management import check_logged_in
from lightning_info import *
from price_info import *
from utilities import *
from drive_info import *
from application_info import *
import pam
import time
@ -346,7 +347,7 @@ def upgrade_beta_page():
def get_upgrade_log_page():
check_logged_in()
log = get_file_contents("/home/admin/upgrade_logs/upgrade_log_latest.txt").decode("utf8")
log = to_string( get_file_contents("/home/admin/upgrade_logs/upgrade_log_latest.txt") )
if (log == "ERROR"):
log = "No log file found"

View File

@ -0,0 +1,156 @@
<!DOCTYPE html lang="en">
<head>
<title>{{ title }}</title>
{% include 'includes/head.html' %}
<script>
$(document).ready(function() {
$( document ).tooltip();
token="{{access_token}}"
function set_token() {
$("#set_token_form").submit();
set_token_dialog.dialog( "close" );
}
set_token_dialog = $( "#set-token-dialog" ).dialog({
autoOpen: false,
resizable: false,
height: "auto",
width: 600,
modal: true,
position: { my: "center top", at: "center top", of: window, collision: "none" },
buttons: {
"Save": set_token,
Cancel: function() {
set_token_dialog.dialog( "close" );
}
},
open: function() {
$("#token").val(token)
}
});
$("#change_token_button").on("click", function() {
set_token_dialog.dialog( "open" );
});
$("#set_token_button").on("click", function() {
set_token_dialog.dialog( "open" );
});
$("#clear_token_button").on("click", function() {
if (confirm("Are you sure you want to clear your Premium+ Access Token?")) {
window.location.href='/premium-plus/clear-token';
}
});
});
</script>
</head>
<body>
{% include 'includes/logo_header.html' %}
<div class="mynode_back_div">
<a class="ui-button ui-widget ui-corner-all mynode_back" href="/"><span class="ui-icon ui-icon-home"></span>home&nbsp;</a>
</div>
{% include 'includes/message_display.html' %}
<div class="main_header">Premium+</div>
<br/>
<!-- Config and Status Row -->
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Access Token and Status</div>
<div class="info_tile_contents">
<table class="info_table">
<tr>
<th>Access Token</th>
<td>
{% if has_access_token %}
{{access_token}}
<button id="change_token_button" class="ui-button ui-widget ui-corner-all settings_button_small">Change</button>
<button id="clear_token_button" class="ui-button ui-widget ui-corner-all settings_button_small">Clear</button>
{% else %}
<button id="set_token_button" class="ui-button ui-widget ui-corner-all settings_button_small">Set Token</button>
{% endif %}
</td>
</tr>
{% if has_access_token %}
<tr>
<th>Status</th>
<td>{{status}}</td>
</tr>
<tr>
<th>Last Sync</th>
<td>{{last_sync}}</td>
</tr>
<tr>
<th>Actions</th>
<td><a href="/premium-plus/sync" class="ui-button ui-widget ui-corner-all mynode_button_small">Force Sync</a></td>
</tr>
{% endif %}
</table>
</div>
</div>
</div>
<br/>
<!-- Premium+ Services -->
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Premium+ Services</div>
<div class="info_tile_contents">
<table class="info_table">
<tr>
<th>Sync Device Status</th>
<td>{{mempool_tx}}</td>
</tr>
<tr>
<th>Backup Lightning SCB File</th>
<td>{{mempool_tx}}</td>
</tr>
<tr>
<th>myNode Watchtower</th>
<td>{{mempool_size}}</td>
</tr>
<tr>
<th>Proxy</th>
<td>
<p id="rpc_password" style="display: none; margin: 0;">{{rpc_password}} <span id="copy_rpc_password" class="ui-icon ui-icon-copy" style="cursor: pointer; display: none;" title="Copy Password"></span></p>
<a id="show_rpc_password" class="ui-button ui-widget ui-corner-all mynode_button_small" style="width: 70%;">show</a>
</td>
</tr>
<tr>
<th>Peer Bloom Filters (BIP 37)</th>
<td>
<label class="switch">
<input type="checkbox" id="bip37_checkbox" {% if bip37_enabled %}checked{% endif %}>
<span class="slider round"></span>
</label>
<button id="bip37_save" style="display: none; float: right;" class="ui-button ui-widget ui-corner-all settings_button_small">Save</button>
</td>
</tr>
</table>
</div>
</div>
</div>
<div id="set-token-dialog" title="Set Premium+ Access Token" style="display:none;">
<form id="set_token_form" name="set_token_form" action="/premium-plus/set-token" method="POST">
<p>The Premium+ Access Token is the key that syncs your node data for backup and remote access.</p>
<b>Access Token:</b>
<br/>
<input type="text" name="token" id="token" value="" class="text ui-widget-content ui-corner-all" size="40" maxlength="64">
<!-- Allow form submission with keyboard without duplicating the dialog button -->
<input type="submit" tabindex="-1" style="position:absolute; top:-1000px">
</form>
</div>
{% include 'includes/footer.html' %}
</body>
</html>