#!/bin/bash # Made by Artto 2025 QORTAL_HELPER_VERSION="1.7" QORTAL_FOLDER="/home/$USER/qortal" QADDR="" # Put your own Qortal address here between the quotes, for balance detection. function check() { # Checking if qortal folder and qortal.jar exist in users's Home folder. # Checking if newest Core is in the computer or not. if [[ ! -d $QORTAL_FOLDER ]]; then echo "$QORTAL_FOLDER folder does not exist. This means you cannot currently run Qortal Core locally." echo "Do you want Qortal Core and other files be installed for you? Then type command: qortal download" return fi [[ -d $QORTAL_FOLDER ]] && echo "✅ $QORTAL_FOLDER folder exists." [[ -f $QORTAL_FOLDER/qortal.jar ]] && echo "✅ Core (qortal.jar) exists." [[ -f $QORTAL_FOLDER/apikey.txt ]] && echo "✅ apikey.txt exists." md5sumexists=$(which md5sum) if [[ -z $md5sumexists ]]; then echo "" echo "MD5SUM app is missing from your GNU/Linux system. This is needed for verifying Core MD5 hash sum, for security reasons." echo "Installing MD5SUM app next. Please enter your computer password for the installation, thanks!" sudo apt update -qq >/dev/null 2>&1 sudo apt install -y coreutils >/dev/null 2>&1 echo "Done." echo "" fi if [[ -f $QORTAL_FOLDER/qortal.jar ]]; then jar_md5_local=$(md5sum $QORTAL_FOLDER/qortal.jar | awk -F ' ' '{print $1}'); fi echo "Checking the Core file hash on GitHub and comparing to local one..." jar_md5_github=$(curl -s "https://github.com/Qortal/qortal/releases" | grep -E "MD5:" -B 2 | head -n 4 | grep -oP 'MD5:.*?<' | sed 's//dev/null || echo "Core is not running." else echo "Connections:" $connections echo "Syncing:" $syncing echo "Minting:" $minting echo "Block height:" $blockheight if [[ -n $balance ]]; then echo "Balance:" $balance; fi fi } function downloadzip() { unzipexists=$(which unzip) if [[ -z $unzipexists ]]; then echo "UnZip app is missing from your GNU/Linux system. Let's install it now." echo "Please enter your computer password for the installation, thanks!" sudo apt update -qq >/dev/null 2>&1 sudo apt install -y unzip >/dev/null 2>&1 echo "Done." echo "" fi cd /home/$USER [[ -f qortal.zip ]] && rm qortal.zip echo "Downloading the latest Qortal.zip..." wget -q https://github.com/Qortal/qortal/releases/latest/download/qortal.zip echo "" [[ -f qortal.zip ]] && unzip qortal.zip && sleep 1 && rm qortal.zip && echo "All done! To start the Core now, type command: qortal start" } function getnewjar() { cd $QORTAL_FOLDER echo "Stopping the current Core, IF it might be running..." stopcore sleep 1 rm qortal.jar echo "Downloading qortal.jar..." wget -q "https://github.com/qortal/qortal/releases/latest/download/qortal.jar" echo "" echo "Done." echo "" echo "To start the Core now, type command: qortal start" } function bootstrap() { cd $QORTAL_FOLDER echo "Stopping the current Core, if it might be running..." count=$(pgrep -af "java.*-jar qortal.jar" | wc -l) if (( count >= 2 )); then echo "2 or more instances running" stopcore stopcore else echo "Less than 2 instances running" stopcore fi sleep 1 rm -rf db echo "DB folder deleted, bootstrapped. Starting the Core..." startcore } function stopcore() { # Copied from stop.sh script # Check for color support cd $QORTAL_FOLDER if [ -t 1 ]; then ncolors=$( tput colors ) if [ -n "${ncolors}" -a "${ncolors}" -ge 8 ]; then if normal="$( tput sgr0 )"; then # use terminfo names red="$( tput setaf 1 )" green="$( tput setaf 2)" else # use termcap names for FreeBSD compat normal="$( tput me )" red="$( tput AF 1 )" green="$( tput AF 2)" fi fi fi # Track the pid if we can find it read pid 2>/dev/null /dev/null 2>&1; then success=1 fi fi # Try to kill process with SIGTERM if [ "$success" -ne 1 ] && [ -n "$pid" ]; then echo "Stopping Qortal process $pid..." if kill -15 "${pid}"; then success=1 fi fi # Warn and exit if still no success if [ "$success" -ne 1 ]; then if [ -n "$pid" ]; then echo "${red}Stop command failed - not running with process id ${pid}?${normal}" else echo "${red}Stop command failed - not running?${normal}" fi exit 1 fi if [ "$success" -eq 1 ]; then echo "Qortal node should be shutting down" if [ "${is_pid_valid}" -eq 0 ]; then echo -n "Monitoring for Qortal node to end" while s=`ps -p $pid -o stat=` && [[ "$s" && "$s" != 'Z' ]]; do echo -n . sleep 1 done echo echo "${green}Qortal ended gracefully${normal}" rm -f run.pid fi fi } function startcore() { # Copied from start.sh script cd $QORTAL_FOLDER if [ "$USER" = "root" ]; then echo "Please su to non-root user before running" exit fi MIN_JAVA_VER='11' # Validate Java is installed and the minimum version is available if command -v java > /dev/null 2>&1; then # Example: openjdk version "11.0.6" 2020-01-14 version=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1,2) if echo "${version}" "${MIN_JAVA_VER}" | awk '{ if ($2 > 0 && $1 >= $2) exit 0; else exit 1}'; then echo 'Passed Java version check' else echo "Please upgrade your Java to version ${MIN_JAVA_VER} or greater" exit 1 fi else echo "Java is not available, please install Java ${MIN_JAVA_VER} or greater" exit 1 fi # No qortal.jar but we have a Maven built one? # Be helpful and copy across to correct location if [ ! -e qortal.jar -a -f target/qortal*.jar ]; then echo "Copying Maven-built Qortal JAR to correct pathname" cp target/qortal*.jar qortal.jar fi # Limits Java JVM stack size and maximum heap usage. # Comment out for bigger systems, e.g. non-routers # or when API documentation is enabled # Uncomment (remove '#' sign) line below if your system has less than 12GB of RAM for optimal RAM defaults # JVM_MEMORY_ARGS="-Xss1256k -Xmx3128m" # Although java.net.preferIPv4Stack is supposed to be false # by default in Java 11, on some platforms (e.g. FreeBSD 12), # it is overridden to be true by default. Hence we explicitly # set it to false to obtain desired behaviour. totalram=$(cat /proc/meminfo | grep -i 'memtotal' | grep -o '[[:digit:]]*') if [[ $totalram -lt 15000000 ]]; then JVM_MEMORY_ARGS="-Xss1256k -Xmx3128m"; fi # If the computer has less than 16GB RAM then use the JVM_MEMORY_ARGS setting for the Core count=$(pgrep -af "java.*-jar qortal.jar" | wc -l) case $count in 0) nohup nice -n 20 java -Djava.net.preferIPv4Stack=false ${JVM_MEMORY_ARGS} -jar qortal.jar 1>run.log 2>&1 & # No instances running, starting :) # Save backgrounded process's PID echo $! > run.pid echo "Qortal Core running as pid $!" ;; 1) echo "One instance of the Core is already running" ;; *) echo "$count (multiple) instances of the Core are running! Better stop them and start the Core again. Doing it now" stop_all_java sleep 2 nohup nice -n 20 java -Djava.net.preferIPv4Stack=false ${JVM_MEMORY_ARGS} -jar qortal.jar 1>run.log 2>&1 & # No instances running, starting :) echo $! > run.pid echo "Qortal Core running now as pid $!" ;; esac } function stop_all_java() { echo "Attempting graceful shutdown of all Java processes..." sudo killall -15 java 2>/dev/null local elapsed=0 local interval=3 local timeout=60 while pgrep -x java >/dev/null && (( elapsed < timeout )); do echo "Java still running... waiting (${elapsed}s/${timeout}s)" sleep $interval ((elapsed+=interval)) done if pgrep -x java >/dev/null; then echo "Graceful shutdown failed after ${timeout}s — forcing kill..." sudo killall -9 java 2>/dev/null else echo "All Java processes stopped gracefully. You can now start the Core again, if you want." fi } function qcheck() { cd $QORTAL_FOLDER loop="True" while [[ $loop == "True" ]]; do clear output=$(curl -s "http://localhost:12391/admin/status") connections=$(echo $output | jq '."numberOfConnections"') blockheight=$(echo $output | jq '."height"') minting=$(echo $output | jq '."isMintingPossible"') syncing=$(echo $output | jq '."isSynchronizing"') balance=$(curl -sX 'GET' "http://localhost:12391/addresses/balance/$QADDR" -H 'accept: text/plain') if [[ $connections == "0" ]] || [[ $connections == "" ]]; then count=$(pgrep -af "java.*-jar qortal.jar" | wc -l) case $count in 1) # If one instance of the Core is running, stop it and start a new instance pgrep -af "java.*-jar qortal.jar" >/dev/null && echo "Restarting the Core..." stopcore sleep 5 startcore ;; 2) # If more than one instances running, stop them all first stop_all_java sleep 2 ;; esac pgrep -af "java.*-jar qortal.jar" >/dev/null || echo "Core not running, starting it..." && startcore echo "Started up. Waiting for 500 seconds to make sure Core is running OK, before monitoring starts." sleep 500 else clear echo "Connections:" $connections echo "Syncing:" $syncing echo "Minting:" $minting echo "Block height:" $blockheight if [[ -n $balance ]]; then echo "Balance:" $balance; fi sleep 30 fi done } function apikey() { output=$(curl -s "http://localhost:12391/admin/status" | grep -oP 'number') if [[ $output == "number" ]]; then # Core is running :) curl -X POST http://localhost:12391/admin/apikey/generate -H 'accept: text/plain' -d '' echo "" sleep 1 [[ -f $QORTAL_FOLDER/apikey.txt ]] && echo "✅ /home/$USER/qortal/apikey.txt now also generated :)" else echo "Cannot generate API key, because the Core is not running. Start it first: qortal start" fi } function updatescript() { GHVERSION=$(curl -s https://raw.githubusercontent.com/arrrtto/qortal/main/qortal | grep -E "QORTAL_HELPER_VERSION" | awk -F '"' '{print $2}' | head -n 1) if [[ $GHVERSION != $QORTAL_HELPER_VERSION ]]; then mkdir -p ~/bin && curl -s https://raw.githubusercontent.com/arrrtto/qortal/main/qortal > ~/bin/qortalnew if [[ $QADDR != "" ]]; then sed -i "s/^QADDR=\".*\"/QADDR=\"$QADDR\"/" /home/$USER/bin/qortalnew; fi rm ~/bin/qortal && mv ~/bin/qortalnew ~/bin/qortal && chmod +x ~/bin/qortal && echo "Qortal Helper script updated successfully to version $GHVERSION" else echo "You already have the latest version of this qortal helper script." fi } function show_help() { clear echo "Qortal Helper script by Artto. Here is how to use it:" printf "\n" printf "\e[32m%s qortal help\e[0m Shows this help message\n" printf "\e[32m%s qortal check\e[0m Checks if Qortal Core and the needed files exist in your computer.\n" printf "\e[32m%s qortal download\e[0m Downloads Qortal Core and the needed files for you.\n" printf "\e[32m%s qortal update\e[0m Downloads the newest Core (qortal.jar) from GitHub for you.\n" printf "\e[32m%s qortal upscript\e[0m Updates this Qortal Helper script version, if a newer one exists on GitHub.\n" printf "\e[32m%s qortal start\e[0m Starts the Core.\n" printf "\e[32m%s qortal stop\e[0m Stops the Core from running.\n" printf "\e[32m%s qortal monitor\e[0m Continual overview of the Core. If no connections or Core not running, it gets auto-restarted.\n" printf "\e[32m%s qortal bootstrap\e[0m Deletes the db folder to do the bootstrap. Starts Core to download the newest blockchain data.\n" printf "\e[32m%s qortal apikey\e[0m Generates a new API key and apikey.txt file into Qortal folder for you.\n" printf "\e[32m%s qortal killjava\e[0m Stops all the Java processes to stop the Core instance(s) forcefully.\n" printf "\n\n" } # ------------ PARAMETERS TO RUN THE LIBRARY FILE WITH ------------ if [[ "$1" == "help" ]] || [[ "$1" == "" ]]; then show_help; fi if [[ "$1" == "check" ]]; then check; fi if [[ "$1" == "download" ]]; then downloadzip; fi if [[ "$1" == "update" ]]; then getnewjar; fi if [[ "$1" == "upscript" ]]; then updatescript; fi if [[ "$1" == "start" ]]; then startcore; fi if [[ "$1" == "stop" ]]; then stopcore; fi if [[ "$1" == "bootstrap" ]]; then bootstrap; fi if [[ "$1" == "monitor" ]]; then qcheck; fi if [[ "$1" == "apikey" ]]; then apikey; fi if [[ "$1" == "killjava" ]]; then stop_all_java; fi