mirror of
https://github.com/mynodebtc/mynode.git
synced 2024-12-24 13:38:11 +00:00
Add Clone Tool
This commit is contained in:
parent
1f94d3f074
commit
c0dd28d222
272
rootfs/standard/usr/bin/clone_drive.py
Executable file
272
rootfs/standard/usr/bin/clone_drive.py
Executable file
|
@ -0,0 +1,272 @@
|
|||
#!/usr/bin/python3
|
||||
import time
|
||||
import os
|
||||
import subprocess
|
||||
import signal
|
||||
import logging
|
||||
from systemd import journal
|
||||
from threading import Thread
|
||||
|
||||
log = logging.getLogger('mynode')
|
||||
log.addHandler(journal.JournaldLogHandler())
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
def print_and_log(msg):
|
||||
global log
|
||||
print(msg)
|
||||
log.info(msg)
|
||||
|
||||
def set_clone_state(state):
|
||||
print_and_log("Clone State: {}".format(state))
|
||||
try:
|
||||
with open("/tmp/.clone_state", "w") as f:
|
||||
f.write(state)
|
||||
os.system("sync")
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
return False
|
||||
|
||||
def reset_clone_error():
|
||||
os.system("rm /tmp/.clone_error")
|
||||
|
||||
def reset_clone_confirm():
|
||||
os.system("rm /tmp/.clone_confirm")
|
||||
|
||||
def set_clone_error(error_msg):
|
||||
print_and_log("Clone Error: {}".format(error_msg))
|
||||
try:
|
||||
with open("/tmp/.clone_error", "w") as f:
|
||||
f.write(error_msg)
|
||||
os.system("sync")
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
return False
|
||||
|
||||
def check_pid(pid):
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except OSError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def send_usr1_sig(process_id):
|
||||
while check_pid(process_id):
|
||||
os.kill(process_id, signal.SIGUSR1)
|
||||
time.sleep(3)
|
||||
|
||||
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
|
||||
print_and_log(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 find_drives():
|
||||
drives = []
|
||||
try:
|
||||
ls_output = subprocess.check_output("ls /sys/block/ | egrep 'hd.*|vd.*|sd.*|nvme.*'", shell=True).decode("utf-8")
|
||||
drives = ls_output.split()
|
||||
except:
|
||||
pass
|
||||
return drives
|
||||
|
||||
def main():
|
||||
# Set initial state
|
||||
set_clone_state("detecting")
|
||||
reset_clone_error()
|
||||
reset_clone_confirm()
|
||||
os.system("umount /mnt/hdd")
|
||||
os.system("rm /tmp/.clone_target_drive_has_mynode")
|
||||
|
||||
# Detect drives
|
||||
drives = find_drives()
|
||||
print_and_log(f"Drives: {drives}")
|
||||
|
||||
# Check exactly two drives found
|
||||
drive_count = len(drives)
|
||||
if drive_count != 2:
|
||||
print_and_log("Clone tool did not find 2 drives!")
|
||||
set_clone_state("error")
|
||||
set_clone_error("Clone tool needs 2 drives! Found {}.".format(drive_count))
|
||||
return
|
||||
|
||||
# Detect Source and Target Drives
|
||||
mynode_drive = "not_found"
|
||||
mynode_found = False
|
||||
target_drive = "not_found"
|
||||
target_found = False
|
||||
both_drives_have_mynode = False
|
||||
for d in drives:
|
||||
partitions = find_partitions_for_drive(d)
|
||||
print_and_log(f"Drive {d} paritions: {partitions}")
|
||||
|
||||
if len(partitions) == 0:
|
||||
# No partition found - must be target drive since its empty
|
||||
if target_found:
|
||||
set_clone_state("error")
|
||||
set_clone_error("Two target drives found. Is myNode drive missing?")
|
||||
return
|
||||
else:
|
||||
target_found = True
|
||||
target_drive = d
|
||||
else:
|
||||
for p in partitions:
|
||||
a = round(time.time() * 1000)
|
||||
if check_partition_for_mynode(p):
|
||||
if mynode_found:
|
||||
# Second drive has myNode partition (failed clone?) - use size to determine target
|
||||
both_drives_have_mynode = True
|
||||
drive_1_size = get_drive_size(mynode_drive)
|
||||
drive_2_size = get_drive_size(d)
|
||||
if drive_2_size >= drive_1_size:
|
||||
mynode_drive = mynode_drive
|
||||
target_drive = d
|
||||
else:
|
||||
target_drive = mynode_drive
|
||||
mynode_drive = d
|
||||
target_found = True
|
||||
else:
|
||||
print_and_log(f"myNode Partition Found: {p}")
|
||||
mynode_drive = d
|
||||
mynode_found = True
|
||||
else:
|
||||
if target_found:
|
||||
set_clone_state("error")
|
||||
set_clone_error("Two target drives found. Is myNode drive missing?")
|
||||
return
|
||||
else:
|
||||
target_found = True
|
||||
target_drive = d
|
||||
b = round(time.time() * 1000)
|
||||
total_time = b - a
|
||||
print_and_log(f"Checked partition {p} in {total_time}ms")
|
||||
|
||||
# Successfully found source and target, wait for confirm
|
||||
print_and_log(f"Source Drive: {mynode_drive}")
|
||||
print_and_log(f"Target Drive: {target_drive}")
|
||||
if both_drives_have_mynode:
|
||||
os.system("touch /tmp/.clone_target_drive_has_mynode")
|
||||
os.system(f"echo {mynode_drive} > /tmp/.clone_source")
|
||||
os.system(f"echo {target_drive} > /tmp/.clone_target")
|
||||
set_clone_state("need_confirm")
|
||||
while not os.path.isfile("/tmp/.clone_confirm"):
|
||||
time.sleep(1)
|
||||
|
||||
# Clone drives
|
||||
set_clone_state("in_progress")
|
||||
os.system("echo 'Starting clone.' > /tmp/.clone_progress")
|
||||
try:
|
||||
cmd = ["dd","bs=64K",f"if=/dev/{mynode_drive}",f"of=/dev/{target_drive}","conv=sync,noerror"]
|
||||
#cmd = ["dd","bs=512",f"if=/dev/zero",f"of=/dev/null","count=5999999","conv=sync,noerror"]
|
||||
dd = subprocess.Popen(cmd, stderr=subprocess.PIPE)
|
||||
print_and_log("DD PID: {}".format(dd.pid))
|
||||
thread = Thread(target=send_usr1_sig, args=(dd.pid,))
|
||||
thread.start()
|
||||
for l in dd.stderr:
|
||||
l = l.decode("utf-8")
|
||||
if 'bytes' in l:
|
||||
try:
|
||||
out_fd = open('/tmp/.clone_progress','w')
|
||||
out_fd.write(l)
|
||||
out_fd.close()
|
||||
except Exception as e:
|
||||
print_and_log("Write Exception: " + str(e))
|
||||
|
||||
while dd.poll() is None:
|
||||
time.sleep(5)
|
||||
print_and_log("Waiting on dd exit...")
|
||||
|
||||
print_and_log("DD RET CODE: {}".format(dd.returncode))
|
||||
if dd.returncode != 0:
|
||||
# DD had an error - log it
|
||||
if dd.stderr != None:
|
||||
for l in dd.stderr:
|
||||
print_and_log("DD STDERR: "+l.decode("utf-8"))
|
||||
if dd.stdout != None:
|
||||
for l in dd.stdout:
|
||||
print_and_log("DD STDOUT: "+l.decode("utf-8"))
|
||||
set_clone_state("error")
|
||||
set_clone_error("DD failed with return code {}".format(dd.returncode))
|
||||
return
|
||||
print_and_log("DD IS COMPLETE")
|
||||
|
||||
# PAUSE IF DD WAS SUCCESSFUL
|
||||
# if dd.returncode == 0:
|
||||
# set_clone_state("error")
|
||||
# set_clone_error("DD WAS SUCCESSFUL!!!! Remove temp code.")
|
||||
# while True:
|
||||
# time.sleep(60)
|
||||
|
||||
# Update partitions (removes all + makes new without removing data)
|
||||
print_and_log("Updating Partitions...")
|
||||
os.system("echo 'Updating partitions...' > /tmp/.clone_progress")
|
||||
subprocess.check_output(f"/usr/bin/format_drive.sh {target_drive}", shell=True)
|
||||
time.sleep(2)
|
||||
|
||||
# Resize filesystem to fill up whole drive
|
||||
print_and_log("Resizing Filesystem...")
|
||||
os.system("echo 'Resizing filesystem...' > /tmp/.clone_progress")
|
||||
os.system(f"partprobe /dev/{target_drive}")
|
||||
time.sleep(2)
|
||||
subprocess.check_output(f"e2fsck -y -f /dev/{target_drive}1", shell=True)
|
||||
subprocess.check_output(f"resize2fs /dev/{target_drive}1", shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print_and_log("CalledProcessError")
|
||||
print_and_log(e.stderr)
|
||||
print_and_log(e.stdout)
|
||||
set_clone_state("error")
|
||||
set_clone_error("Clone failed: {}".format(e))
|
||||
return
|
||||
except Exception as e:
|
||||
set_clone_state("error")
|
||||
set_clone_error("Clone failed: {}".format(e))
|
||||
return
|
||||
|
||||
|
||||
# Complete - wait for reboot
|
||||
set_clone_state("complete")
|
||||
print_and_log("Clone Complete!")
|
||||
print_and_log("Waiting for reboot...")
|
||||
while True:
|
||||
time.sleep(60)
|
||||
|
||||
|
||||
# This is the main entry point for the program
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print_and_log("Exception: {}".format(str(e)))
|
||||
set_clone_error("Exception: {}".format(str(e)))
|
|
@ -17,10 +17,10 @@ while [ $? -eq 0 ]; do
|
|||
#echo "$drive still found..."
|
||||
|
||||
# Check drive usage
|
||||
usage=$(df -h /mnt/hdd | grep /dev | awk '{print $5}' | cut -d'%' -f1)
|
||||
echo "Usage $usage"
|
||||
if [ $usage -ge 99 ]; then
|
||||
mb_available=$(df --block-size=M /mnt/hdd | grep /dev | awk '{print $4}' | cut -d'M' -f1)
|
||||
if [ $mb_available -le 1000 ]; then
|
||||
# Usage is 99%+, reboot to get into drive_full state with services stopped
|
||||
echo "High Drive Usage: $mb_available MB available"
|
||||
/usr/bin/mynode-reboot
|
||||
fi
|
||||
|
||||
|
|
|
@ -125,11 +125,12 @@ proc runCommand {args} {
|
|||
|
||||
proc mountFileSystems {} {
|
||||
findBlockDevices hardDrives
|
||||
set drive_count [llength $hardDrives]
|
||||
puts "Found these $drive_count drives: ${hardDrives}"
|
||||
|
||||
puts "Found these harddrives: ${hardDrives}"
|
||||
|
||||
findAllPartitionsForBlockDevices $hardDrives partitions
|
||||
puts "Found these existing harddrive partitions: ${partitions}"
|
||||
puts "Found these existing drive partitions: ${partitions}"
|
||||
|
||||
if {![checkPartitionsForExistingMyNodeFs partitions]} {
|
||||
puts "No existing drive found. Creating new one."
|
||||
|
|
|
@ -76,9 +76,23 @@ if [ $IS_RASPI -eq 1 ] || [ $IS_ROCK64 -eq 1 ] || [ $IS_ROCKPRO64 -eq 1 ]; then
|
|||
fi
|
||||
umount /mnt/hdd || true
|
||||
|
||||
# Check drive
|
||||
|
||||
# If multiple drives detected, start clone tool
|
||||
drive_count=$(ls /sys/block/ | egrep "hd.*|vd.*|sd.*|nvme.*" | wc -l)
|
||||
if [ "$drive_count" -gt 1 ] || [ -f /home/bitcoin/open_clone_tool ]; then
|
||||
rm -f /home/bitcoin/open_clone_tool
|
||||
echo "drive_clone" > $MYNODE_STATUS_FILE
|
||||
sync
|
||||
while [ 1 ]; do
|
||||
python3 /usr/bin/clone_drive.py || true
|
||||
sleep 60s
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Check drive (only if exactly 1 is found)
|
||||
set +e
|
||||
if [ $IS_X86 = 0 ]; then
|
||||
if [ $IS_X86 = 0 ] && [ "$drive_count" -eq 1 ]; then
|
||||
touch /tmp/repairing_drive
|
||||
for d in /dev/sd*1 /dev/hd*1 /dev/vd*1 /dev/nvme*p1; do
|
||||
echo "Repairing drive $d ...";
|
||||
|
@ -95,13 +109,13 @@ rm -f /tmp/repairing_drive
|
|||
set -e
|
||||
|
||||
|
||||
# Mount HDD (format if necessary)
|
||||
# Mount HDD (normal boot, format if necessary)
|
||||
while [ ! -f /mnt/hdd/.mynode ]
|
||||
do
|
||||
# Clear status
|
||||
rm -f $MYNODE_STATUS_FILE
|
||||
# Normal boot - find drive
|
||||
rm -f $MYNODE_STATUS_FILE # Clear status
|
||||
mount_drive.tcl || true
|
||||
sleep 5
|
||||
sleep 5s
|
||||
done
|
||||
|
||||
|
||||
|
@ -117,12 +131,12 @@ fi
|
|||
|
||||
|
||||
# Check drive usage
|
||||
usage=$(df -h /mnt/hdd | grep /dev | awk '{print $5}' | cut -d'%' -f1)
|
||||
while [ $usage -ge 98 ]; do
|
||||
mb_available=$(df --block-size=M /mnt/hdd | grep /dev | awk '{print $4}' | cut -d'M' -f1)
|
||||
if [ $mb_available -le 1200 ]; then
|
||||
echo "drive_full" > $MYNODE_STATUS_FILE
|
||||
sleep 10s
|
||||
usage=$(df -h /mnt/hdd | grep /dev | awk '{print $5}' | cut -d'%' -f1)
|
||||
done
|
||||
mb_available=$(df --block-size=M /mnt/hdd | grep /dev | awk '{print $4}' | cut -d'M' -f1)
|
||||
fi
|
||||
|
||||
|
||||
# Setup Drive
|
||||
|
|
|
@ -389,6 +389,7 @@ STATE_DRIVE_MISSING = "drive_missing"
|
|||
STATE_DRIVE_CONFIRM_FORMAT = "drive_format_confirm"
|
||||
STATE_DRIVE_FORMATTING = "drive_formatting"
|
||||
STATE_DRIVE_MOUNTED = "drive_mounted"
|
||||
STATE_DRIVE_CLONE = "drive_clone"
|
||||
STATE_DRIVE_FULL = "drive_full"
|
||||
STATE_GEN_DHPARAM = "gen_dhparam"
|
||||
STATE_QUICKSYNC_DOWNLOAD = "quicksync_download"
|
||||
|
@ -406,16 +407,7 @@ def get_mynode_status():
|
|||
status_file = "/tmp/.mynode_status"
|
||||
status = STATE_UNKNOWN
|
||||
|
||||
# If its been a while, check for error conditions
|
||||
uptime_in_sec = get_system_uptime_in_seconds()
|
||||
if uptime_in_sec > 120:
|
||||
# Check for read-only sd card
|
||||
if is_mount_read_only("/"):
|
||||
return STATE_ROOTFS_READ_ONLY
|
||||
if is_mount_read_only("/mnt/hdd"):
|
||||
return STATE_HDD_READ_ONLY
|
||||
|
||||
# Get status stored on drive
|
||||
# Get status
|
||||
if (os.path.isfile(status_file)):
|
||||
try:
|
||||
with open(status_file, "r") as f:
|
||||
|
@ -424,10 +416,61 @@ def get_mynode_status():
|
|||
status = STATE_DRIVE_MISSING
|
||||
else:
|
||||
status = STATE_DRIVE_MISSING
|
||||
|
||||
# If its been a while, check for error conditions
|
||||
uptime_in_sec = get_system_uptime_in_seconds()
|
||||
if uptime_in_sec > 120:
|
||||
# Check for read-only sd card
|
||||
if is_mount_read_only("/"):
|
||||
return STATE_ROOTFS_READ_ONLY
|
||||
# Check for read-only drive (unless cloning - it purposefully mounts read only)
|
||||
if is_mount_read_only("/mnt/hdd") and status != STATE_DRIVE_CLONE:
|
||||
return STATE_HDD_READ_ONLY
|
||||
except:
|
||||
status = STATE_UNKNOWN
|
||||
return status
|
||||
|
||||
#==================================
|
||||
# myNode Clone Tool
|
||||
#==================================
|
||||
CLONE_STATE_DETECTING = "detecting"
|
||||
CLONE_STATE_ERROR = "error"
|
||||
CLONE_STATE_NEED_CONFIRM = "need_confirm"
|
||||
CLONE_STATE_IN_PROGRESS = "in_progress"
|
||||
CLONE_STATE_COMPLETE = "complete"
|
||||
|
||||
def get_clone_state():
|
||||
return get_file_contents("/tmp/.clone_state")
|
||||
|
||||
def get_clone_error():
|
||||
return get_file_contents("/tmp/.clone_error")
|
||||
|
||||
def get_clone_progress():
|
||||
return get_file_contents("/tmp/.clone_progress")
|
||||
|
||||
def get_clone_source_drive():
|
||||
return get_file_contents("/tmp/.clone_source")
|
||||
|
||||
def get_clone_target_drive():
|
||||
return get_file_contents("/tmp/.clone_target")
|
||||
|
||||
def get_clone_target_drive_has_mynode():
|
||||
return os.path.isfile("/tmp/.clone_target_drive_has_mynode")
|
||||
|
||||
def get_drive_info(drive):
|
||||
data = {}
|
||||
data["name"] = "NOT_FOUND"
|
||||
try:
|
||||
lsblk_output = subprocess.check_output("lsblk -io KNAME,TYPE,SIZE,MODEL,VENDOR /dev/{} | grep disk".format(drive), shell=True).decode("utf-8")
|
||||
parts = lsblk_output.split()
|
||||
data["name"] = parts[0]
|
||||
data["size"] = parts[2]
|
||||
data["model"] = parts[3]
|
||||
data["vendor"] = parts[4]
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
|
||||
#==================================
|
||||
# Log functions (non-systemd based)
|
||||
#==================================
|
||||
|
|
|
@ -217,7 +217,8 @@ def index():
|
|||
message += "<p style='font-size: 16px; width: 800px; margin: auto;'>"
|
||||
message += "To prevent corrupting any data, your device has stopped running most apps until more free space is available. "
|
||||
message += "Please free up some space or attach a larger drive.<br/><br/>"
|
||||
message += "If enabled, disabling QuickSync can save a large amount of space."
|
||||
message += "If enabled, disabling <a href='/settings#quicksync'>QuickSync</a> can save a large amount of space.<br/><br/>"
|
||||
message += "To move to larger drive, try the <a href='/settings#clone_tool'>Clone Tool</a>."
|
||||
message += "</p>"
|
||||
templateData = {
|
||||
"title": "myNode Drive Full",
|
||||
|
@ -226,6 +227,74 @@ def index():
|
|||
"ui_settings": read_ui_settings()
|
||||
}
|
||||
return render_template('state.html', **templateData)
|
||||
elif status == STATE_DRIVE_CLONE:
|
||||
clone_state = get_clone_state()
|
||||
if clone_state == CLONE_STATE_DETECTING:
|
||||
templateData = {
|
||||
"title": "myNode Clone Tool",
|
||||
"header_text": "Cloning Tool",
|
||||
"subheader_text": Markup("Detecting Drives..."),
|
||||
"ui_settings": read_ui_settings(),
|
||||
"refresh_rate": 10
|
||||
}
|
||||
return render_template('state.html', **templateData)
|
||||
elif clone_state == CLONE_STATE_ERROR:
|
||||
error = get_clone_error()
|
||||
templateData = {
|
||||
"title": "myNode Clone Tool",
|
||||
"header_text": "Cloning Tool",
|
||||
"subheader_text": Markup("Clone Error<br/></br>" + error + "<br/><br/><br/><small>Retrying in one minute."),
|
||||
"ui_settings": read_ui_settings(),
|
||||
"refresh_rate": 10
|
||||
}
|
||||
return render_template('state.html', **templateData)
|
||||
elif clone_state == CLONE_STATE_NEED_CONFIRM:
|
||||
# Clone was confirmed
|
||||
if request.args.get('clone_confirm'):
|
||||
os.system("touch /tmp/.clone_confirm")
|
||||
time.sleep(3)
|
||||
return redirect("/")
|
||||
|
||||
source_drive = get_clone_source_drive()
|
||||
target_drive = get_clone_target_drive()
|
||||
target_drive_has_mynode = get_clone_target_drive_has_mynode()
|
||||
source_drive_info = get_drive_info(source_drive)
|
||||
target_drive_info = get_drive_info(target_drive)
|
||||
templateData = {
|
||||
"title": "myNode Clone Tool",
|
||||
"header_text": "Cloning Tool",
|
||||
"target_drive_has_mynode": target_drive_has_mynode,
|
||||
"source_drive_info": source_drive_info,
|
||||
"target_drive_info": target_drive_info,
|
||||
"ui_settings": read_ui_settings(),
|
||||
}
|
||||
return render_template('clone_confirm.html', **templateData)
|
||||
elif clone_state == CLONE_STATE_IN_PROGRESS:
|
||||
progress = get_clone_progress()
|
||||
templateData = {
|
||||
"title": "myNode Clone Tool",
|
||||
"header_text": "Cloning Tool",
|
||||
"subheader_text": Markup("Cloning...<br/><br/>" + progress),
|
||||
"ui_settings": read_ui_settings(),
|
||||
"refresh_rate": 5
|
||||
}
|
||||
return render_template('state.html', **templateData)
|
||||
elif clone_state == CLONE_STATE_COMPLETE:
|
||||
templateData = {
|
||||
"title": "myNode Clone Tool",
|
||||
"header_text": "Cloning Tool",
|
||||
"subheader_text": Markup("Clone Complete!"),
|
||||
"ui_settings": read_ui_settings(),
|
||||
}
|
||||
return render_template('clone_complete.html', **templateData)
|
||||
else:
|
||||
templateData = {
|
||||
"title": "myNode Clone Tool",
|
||||
"header_text": "Cloning Tool",
|
||||
"subheader_text": "Unknown Clone State: " + clone_state,
|
||||
"ui_settings": read_ui_settings()
|
||||
}
|
||||
return render_template('state.html', **templateData)
|
||||
elif status == STATE_GEN_DHPARAM:
|
||||
templateData = {
|
||||
"title": "myNode Generating Data",
|
||||
|
|
|
@ -483,6 +483,27 @@ def reset_docker_page():
|
|||
}
|
||||
return render_template('reboot.html', **templateData)
|
||||
|
||||
@mynode_settings.route("/settings/open-clone-tool")
|
||||
def open_clone_tool_page():
|
||||
check_logged_in()
|
||||
|
||||
check_and_mark_reboot_action("open_clone_tool")
|
||||
|
||||
os.system("touch /home/bitcoin/open_clone_tool")
|
||||
os.system("sync")
|
||||
|
||||
# Trigger reboot
|
||||
t = Timer(1.0, reboot_device)
|
||||
t.start()
|
||||
|
||||
# Display wait page
|
||||
templateData = {
|
||||
"title": "myNode Reboot",
|
||||
"header_text": "Restarting",
|
||||
"subheader_text": "Restarting to Open Clone Tool....",
|
||||
"ui_settings": read_ui_settings()
|
||||
}
|
||||
return render_template('reboot.html', **templateData)
|
||||
|
||||
@mynode_settings.route("/settings/reset-electrs")
|
||||
def reset_electrs_page():
|
||||
|
|
BIN
rootfs/standard/var/www/mynode/static/images/drive2.png
Normal file
BIN
rootfs/standard/var/www/mynode/static/images/drive2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
rootfs/standard/var/www/mynode/static/images/right_arrow.png
Normal file
BIN
rootfs/standard/var/www/mynode/static/images/right_arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
37
rootfs/standard/var/www/mynode/templates/clone_complete.html
Normal file
37
rootfs/standard/var/www/mynode/templates/clone_complete.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html lang="en">
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
{% include 'includes/head.html' %}
|
||||
|
||||
{% if refresh_rate is defined and refresh_rate is not none %}
|
||||
<meta http-equiv="refresh" content="{{ refresh_rate }}">
|
||||
{% else %}
|
||||
<meta http-equiv="refresh" content="30">
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("#reboot-device").on("click", function() {
|
||||
window.location.href="/settings/reboot-device"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% include 'includes/logo_header.html' %}
|
||||
|
||||
<div class="state_header">{{ header_text }}</div>
|
||||
|
||||
<div class="state_subheader">Clone Complete!</div>
|
||||
|
||||
<div class="format_div" style="width: 600px;">
|
||||
<p>Remove the original drive now and click Reboot.</p>
|
||||
<br/><br/>
|
||||
<button id="reboot-device" value="Login" class="ui-button ui-widget ui-corner-all format_button">Reboot</button>
|
||||
|
||||
<br/><br/>
|
||||
<div style="height: 40px;"> </div>
|
||||
{% include 'includes/footer.html' %}
|
||||
</body>
|
||||
</html>
|
94
rootfs/standard/var/www/mynode/templates/clone_confirm.html
Normal file
94
rootfs/standard/var/www/mynode/templates/clone_confirm.html
Normal file
|
@ -0,0 +1,94 @@
|
|||
<!DOCTYPE html lang="en">
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
{% include 'includes/head.html' %}
|
||||
|
||||
{% if refresh_rate is defined and refresh_rate is not none %}
|
||||
<meta http-equiv="refresh" content="{{ refresh_rate }}">
|
||||
{% else %}
|
||||
<meta http-equiv="refresh" content="30">
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
td {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
$("#format-confirm").on("click", function() {
|
||||
window.location.href="/?clone_confirm=1"
|
||||
});
|
||||
$("#reboot-device").on("click", function() {
|
||||
window.location.href="/settings/reboot-device"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% include 'includes/logo_header.html' %}
|
||||
|
||||
<div class="state_header">{{ header_text }}</div>
|
||||
|
||||
<div class="format_div" style="width: 600px;">
|
||||
<table style="margin: auto; width: 600px; text-align: center;">
|
||||
<tr>
|
||||
<td><img style="width: 140px; margin: auto;" src="{{ url_for('static', filename="images/drive2.png")}}"/></td>
|
||||
<td><img style="width: 120px; margin: auto;" src="{{ url_for('static', filename="images/right_arrow.png")}}"/></td>
|
||||
<td><img style="width: 140px; margin: auto;" src="{{ url_for('static', filename="images/drive2.png")}}"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td><b>Source Drive</b></td>
|
||||
<td></td>
|
||||
<td><b>Target Drive</b></td>
|
||||
</tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{source_drive_info['size']}}<br/>
|
||||
{{source_drive_info['model']}}<br/>
|
||||
{{source_drive_info['vendor']}}<br/>
|
||||
/dev/{{source_drive_info['name']}}<br/>
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
{{target_drive_info['size']}}<br/>
|
||||
{{target_drive_info['model']}}<br/>
|
||||
{{target_drive_info['vendor']}}<br/>
|
||||
/dev/{{target_drive_info['name']}}<br/>
|
||||
</td>
|
||||
</tr>
|
||||
{% if target_drive_has_mynode %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<span style="color: red;">myNode Data Detected!</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
<p><b>Warning!</b></p>
|
||||
{% if target_drive_has_mynode %}
|
||||
<p>myNode data was detected on the target drive! This can be caused by a previous clone failure. Be sure the target drive is the new drive you want to overwrite!</p>
|
||||
{% endif %}
|
||||
<p>All existing data will be lost on the target drive. If this is not OK, remove the additional drive now and click Reboot.</p>
|
||||
<p>It is highly recommended that both drives be externally powered. Running two drives from USB power on the device can cause clone failures.</p>
|
||||
|
||||
<br/>
|
||||
<button id="format-confirm" value="Login" class="ui-button ui-widget ui-corner-all format_button">Confirm Clone</button>
|
||||
<br/><br/>
|
||||
<button id="reboot-device" value="Login" class="ui-button ui-widget ui-corner-all format_button">Reboot</button>
|
||||
|
||||
<br/><br/>
|
||||
</div>
|
||||
|
||||
<div style="height: 40px;"> </div>
|
||||
{% include 'includes/footer.html' %}
|
||||
</body>
|
||||
</html>
|
|
@ -20,7 +20,8 @@
|
|||
<div class="main_page_error_block">
|
||||
<center>
|
||||
<p>Your drive is {{drive_usage}} full and free space is running very low. You may need to upgrade to a larger drive.</p>
|
||||
<p>If QuickSync is enabled, try disabling it to save some space.</p>
|
||||
<p>If QuickSync is enabled, try <a href="/settings#quicksync">disabling</a> it to save some space.</p>
|
||||
<p>To migrate to a larger drive, try the <a href="/settings#clone_tool">Clone Tool</a>.</p>
|
||||
</center>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -438,6 +438,7 @@
|
|||
|
||||
<br/>
|
||||
<div class="settings_block">
|
||||
<a id="mynode"></a>
|
||||
<div class="settings_block_header">myNode</div>
|
||||
|
||||
<div class="settings_block_subheader">Version</div>
|
||||
|
@ -509,6 +510,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="applications"></a>
|
||||
<div class="settings_block_header">Applications</div>
|
||||
|
||||
<div class="settings_block_subheader">Manage Applications</div>
|
||||
|
@ -519,6 +521,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="ui"></a>
|
||||
<div class="settings_block_header">User Interface</div>
|
||||
|
||||
<div class="settings_block_subheader">Dark Mode</div>
|
||||
|
@ -541,13 +544,11 @@
|
|||
</label>
|
||||
<br/><br/>
|
||||
<button id="https_forced" style="display: none;" class="ui-button ui-widget ui-corner-all settings_button_small">Save</button>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="device"></a>
|
||||
<div class="settings_block_header">Device</div>
|
||||
|
||||
<div class="settings_block_subheader">Reboot Device</div>
|
||||
|
@ -576,6 +577,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="firewall"></a>
|
||||
<div class="settings_block_header">Firewall</div>
|
||||
|
||||
<div class="settings_block_subheader">Rules</div>
|
||||
|
@ -595,6 +597,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="quicksync"></a>
|
||||
<div class="settings_block_header">QuickSync</div>
|
||||
|
||||
<div class="settings_block_subheader">Toggle QuickSync</div>
|
||||
|
@ -645,6 +648,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="bitcoin"></a>
|
||||
<div class="settings_block_header">Bitcoin</div>
|
||||
|
||||
<div class="settings_block_subheader">Edit Config</div>
|
||||
|
@ -688,6 +692,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="lightning"></a>
|
||||
<div class="settings_block_header">Lightning</div>
|
||||
|
||||
<div class="settings_block_subheader">Download Channel Backup</div>
|
||||
|
@ -719,6 +724,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="electrum"></a>
|
||||
<div class="settings_block_header">Electrum Server</div>
|
||||
|
||||
<div class="settings_block_subheader">Reset Electrum Server</div>
|
||||
|
@ -737,6 +743,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="tor"></a>
|
||||
<div class="settings_block_header">Tor</div>
|
||||
|
||||
<div class="settings_block_subheader">Use Tor for Bitcoin and Lightning</div>
|
||||
|
@ -776,6 +783,7 @@
|
|||
</div>
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="services"></a>
|
||||
<div class="settings_block_header">Services</div>
|
||||
|
||||
<div class="settings_block_subheader">Netdata</div>
|
||||
|
@ -790,6 +798,7 @@
|
|||
</div>
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="docker"></a>
|
||||
<div class="settings_block_header">Docker</div>
|
||||
|
||||
<div class="settings_block_subheader">Reset Docker</div>
|
||||
|
@ -799,6 +808,17 @@
|
|||
</div>
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="clone_tool"></a>
|
||||
<div class="settings_block_header">Clone Tool</div>
|
||||
|
||||
<div class="settings_block_subheader">Clone Tool</div>
|
||||
This will reboot your device and open the clone tool. It can be used to migrate to a larger external drive.
|
||||
<br/>
|
||||
<a href="/settings/open-clone-tool" class="ui-button ui-widget ui-corner-all settings_button">Open Clone Tool</a>
|
||||
</div>
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="advanced"></a>
|
||||
<div class="settings_block_header">Advanced</div>
|
||||
|
||||
<div class="settings_block_subheader">Reset HTTPS Certificates</div>
|
||||
|
@ -883,6 +903,7 @@
|
|||
|
||||
|
||||
<div class="settings_block">
|
||||
<a id="developer"></a>
|
||||
<div class="settings_block_header">Developer</div>
|
||||
|
||||
{% if not product_key_skipped %}
|
||||
|
|
Loading…
Reference in New Issue
Block a user