Files

685 lines
26 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# auto-fix-qortal.sh — POSIX /bin/sh, bullet-proofed
# ================= Colors (ANSI) =================
BLACK='\033[0;30m'
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[0;37m'
NC='\033[0m' # No Color
# ================= Script flags =================
ARM_32_DETECTED=false
ARM_64_DETECTED=false
UPDATED_SETTINGS=false
NEW_UBUNTU_VERSION=false
# ================= Global URLs (override via env) =================
DEFAULT_SCRIPT_URL="${AUTO_FIX_SCRIPT_URL:-https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/auto-fix-qortal.sh}"
DEFAULT_SCRIPT_MIRROR="${AUTO_FIX_SCRIPT_MIRROR_URL:-https://gitea.qortal.link/crowetic/QORTector-scripts/raw/branch/main/auto-fix-qortal.sh}"
DEFAULT_SETTINGS_URL="${AUTO_FIX_SETTINGS_URL:-https://raw.githubusercontent.com/crowetic/QORTector-scripts/refs/heads/main/settings.json}"
DEFAULT_SETTINGS_MIRROR="${AUTO_FIX_SETTINGS_MIRROR_URL:-https://gitea.qortal.link/crowetic/QORTector-scripts/raw/branch/main/settings.json}"
PATCH_SETTINGS_URL="${AUTO_FIX_PATCH_URL:-https://raw.githubusercontent.com/crowetic/QORTector-scripts/refs/heads/main/settings-patch.json}"
PATCH_SETTINGS_MIRROR="${AUTO_FIX_PATCH_MIRROR_URL:-https://gitea.qortal.link/crowetic/QORTector-scripts/raw/branch/main/settings-patch.json}"
# ================= Helpers (POSIX-safe) =================
p() { # printf wrapper
# shellcheck disable=SC2059
printf "%b\n" "$*"
}
atomic_write() { # atomic_write TMP DEST
tmp="$1"; dest="$2"
# Create parent dir if missing
mkdir -p "$(dirname "$dest")" 2>/dev/null || true
sync || true
mv -f -- "$tmp" "$dest"
sync || true
}
fetch() { # fetch URL OUTFILE [MIRROR_URL]; retries & validation for .json
url="$1"; out="$2"; mirror="${3:-}"
tmp="$(mktemp "${out}.XXXXXX")" || exit 1
# Try 5 attempts with small backoff
i=1
while [ "$i" -le 5 ]; do
if curl -fsSL --connect-timeout 10 --max-time 60 -o "$tmp" "$url"; then
break
fi
sleep 2
i=$((i+1))
done
if [ ! -s "$tmp" ] && [ -n "$mirror" ]; then
i=1
while [ "$i" -le 5 ]; do
if curl -fsSL --connect-timeout 10 --max-time 60 -o "$tmp" "$mirror"; then
break
fi
sleep 2
i=$((i+1))
done
fi
if [ ! -s "$tmp" ]; then
rm -f -- "$tmp"
return 1
fi
case "$out" in
*.json)
if command -v jq >/dev/null 2>&1; then
if ! jq empty "$tmp" >/dev/null 2>&1; then
p "${RED}Downloaded JSON invalid for $out${NC}"
rm -f -- "$tmp"
return 1
fi
fi
;;
esac
atomic_write "$tmp" "$out"
return 0
}
is_valid_json_file() { # is_valid_json_file FILE
[ -s "$1" ] || return 1
command -v jq >/dev/null 2>&1 || return 1
jq empty "$1" >/dev/null 2>&1
}
# ================== Functions (keep order) ==================
# Function to update the script initially if needed
initial_update() {
if [ ! -f "${HOME}/auto_fix_updated" ]; then
p "${YELLOW}Checking for the latest version of the script...${NC}"
dl="${HOME}/auto-fix-qortal.sh.download"
if fetch "$DEFAULT_SCRIPT_URL" "$dl" "$DEFAULT_SCRIPT_MIRROR"; then
# quick sanity: must contain key functions
if grep -q "initial_update()" "$dl" && grep -q "potentially_update_settings()" "$dl"; then
chmod +x "$dl" 2>/dev/null || true
atomic_write "$dl" "${HOME}/auto-fix-qortal.sh"
: > "${HOME}/auto_fix_updated"
p "${GREEN}Script updated. Restarting...${NC}"
exec "${HOME}/auto-fix-qortal.sh"
else
p "${RED}Downloaded script failed sanity check; continuing with current copy.${NC}"
rm -f -- "$dl"
fi
else
p "${YELLOW}Could not fetch updated script (network/GitHub hiccup). Continuing with current copy.${NC}"
fi
fi
check_internet
}
check_internet() {
p "${CYAN}....................................................................${NC}"
p "${CYAN}THIS SCRIPT RUNS AUTOMATICALLY. LET IT FINISH; DO NOT CLOSE IT EARLY.${NC}"
p "${CYAN}It keeps Qortal updated and synced. Thanks. —crowetic${NC}"
p "${CYAN}....................................................................${NC}"
sleep 2
p "${YELLOW}Checking internet connection...${NC}"
INTERNET_STATUS="UNKNOWN"
TIMESTAMP="$(date +%s)"
test_connectivity() { # HEAD 200 check
URL=$1
curl -s --head --max-time 8 "$URL" | grep -q "200 OK"
}
if ping -c 1 -W 1 8.8.4.4 >/dev/null 2>&1; then
INTERNET_STATUS="UP"
p "${GREEN}Ping successful to 8.8.4.4${NC}"
else
p "${YELLOW}Ping failed, falling back to Qortal domain tests...${NC}"
if test_connectivity "https://qortal.org"; then
INTERNET_STATUS="UP"; p "${GREEN}Internet via qortal.org${NC}"
elif test_connectivity "https://api.qortal.org"; then
INTERNET_STATUS="UP"; p "${GREEN}Internet via api.qortal.org${NC}"
elif test_connectivity "https://ext-node.qortal.link"; then
INTERNET_STATUS="UP"; p "${GREEN}Internet via ext-node.qortal.link${NC}"
else
INTERNET_STATUS="DOWN"
fi
fi
if [ "$INTERNET_STATUS" = "UP" ]; then
p "${BLUE}Internet UP, continuing...${NC}"
rm -f -- "${HOME}/Desktop/check-qortal-status.sh" 2>/dev/null || true
cd || exit 1
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/check-qortal-status.sh" "${HOME}/qortal/check-qortal-status.sh" || true
chmod +x "${HOME}/qortal/check-qortal-status.sh" 2>/dev/null || true
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/start-qortal.sh" "${HOME}/start-qortal.sh" || true
chmod +x "${HOME}/start-qortal.sh" 2>/dev/null || true
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/refresh-qortal.sh" "${HOME}/refresh-qortal.sh" || true
chmod +x "${HOME}/refresh-qortal.sh" 2>/dev/null || true
check_for_raspi
else
p "${RED}Internet is DOWN. Please fix connection and restart device.${NC}"
sleep 30
exit 1
fi
}
check_for_raspi() {
ARCH="$(uname -m)"
if [ "$ARCH" = "armv7l" ] || [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
p "${WHITE}Raspberry Pi detected, checking 32/64-bit...${NC}"
if uname -m | grep -q 'armv7l'; then
p "${WHITE}32-bit ARM detected, using ARM32 start script${NC}"
ARM_32_DETECTED=true
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/start-modified-memory-args.sh" "${HOME}/start-modified-memory-args.sh" || true
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/auto-fix-cron" "${HOME}/auto-fix-cron" || true
crontab "${HOME}/auto-fix-cron" 2>/dev/null || true
chmod +x "${HOME}/start-modified-memory-args.sh" 2>/dev/null || true
mv -f -- "${HOME}/start-modified-memory-args.sh" "${HOME}/qortal/start.sh"
check_qortal
else
p "${WHITE}64-bit ARM detected, proceeding...${NC}"
ARM_64_DETECTED=true
check_memory
fi
else
p "${YELLOW}Not a Raspberry Pi, checking Ubuntu version...${NC}"
if command -v lsb_release >/dev/null 2>&1; then
UBUNTU_VER="$(lsb_release -rs | cut -d. -f1)"
else
UBUNTU_VER="$(grep -o 'VERSION_ID="[0-9]*' /etc/os-release | tr -dc '0-9')"
fi
if [ -n "$UBUNTU_VER" ] && [ "$UBUNTU_VER" -ge 24 ] 2>/dev/null; then
p "${YELLOW}Ubuntu 24+ detected.${NC}"
NEW_UBUNTU_VERSION=true
fi
check_memory
fi
}
check_memory() {
totalm="$(free -m | awk '/^Mem:/{print $2}')"
p "${YELLOW}RAM check: ${totalm} MB — selecting start script...${NC}"
if [ -n "$totalm" ] && [ "$totalm" -le 6000 ] 2>/dev/null; then
p "${WHITE}< 6GB RAM — using 4GB start script${NC}"
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/4GB-start.sh" "${HOME}/4GB-start.sh" || true
mv -f -- "${HOME}/4GB-start.sh" "${HOME}/qortal/start.sh"
chmod +x "${HOME}/qortal/start.sh" 2>/dev/null || true
elif [ -n "$totalm" ] && [ "$totalm" -ge 6001 ] 2>/dev/null && [ "$totalm" -le 16000 ] 2>/dev/null; then
p "${WHITE}616GB RAM — using mid-range start script${NC}"
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/start-6001-to-16000m.sh" "${HOME}/start-6001-to-16000m.sh" || true
mv -f -- "${HOME}/start-6001-to-16000m.sh" "${HOME}/qortal/start.sh"
chmod +x "${HOME}/qortal/start.sh" 2>/dev/null || true
else
p "${WHITE}> 16GB RAM — using high-RAM start script${NC}"
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/start-high-RAM.sh" "${HOME}/start-high-RAM.sh" || true
mv -f -- "${HOME}/start-high-RAM.sh" "${HOME}/qortal/start.sh"
chmod +x "${HOME}/qortal/start.sh" 2>/dev/null || true
fi
check_qortal
}
check_qortal() {
p "${YELLOW}Checking qortal version (local vs remote)...${NC}"
core_running="$(curl -s --max-time 3 localhost:12391/admin/status || true)"
if [ -z "$core_running" ]; then
p "${CYAN}Node not responding. Checking for bootstrapping...${NC}"
if tail -n 20 "${HOME}/qortal/qortal.log" 2>/dev/null | grep -Ei 'bootstrap|bootstrapping' >/dev/null 2>&1; then
p "${RED}Bootstrapping detected. Updating script and exiting current cycle...${NC}"
update_script
fi
p "${RED}Core not running; waiting 2 minutes in case it is starting slowly...${NC}"
sleep 120
fi
LOCAL_VERSION="$(curl -s --max-time 5 localhost:12391/admin/info | grep -o '"buildVersion":"qortal-[^"]*' | sed 's/.*qortal-\([0-9.]*\).*/\1/' | tr -d '.')"
REMOTE_VERSION="$(curl -s --max-time 10 "https://api.github.com/repos/qortal/qortal/releases/latest" | grep -o '"tag_name": "v[^"]*' | sed 's/.*v\([0-9.]*\).*/\1/' | tr -d '.')"
if [ -n "$LOCAL_VERSION" ] && [ -n "$REMOTE_VERSION" ]; then
if [ "$LOCAL_VERSION" -ge "$REMOTE_VERSION" ] 2>/dev/null; then
p "${GREEN}Local >= remote; no core update needed.${NC}"
check_for_GUI
else
check_hash_update_qortal
fi
else
check_hash_update_qortal
fi
}
check_hash_update_qortal() {
p "${RED}Version check inconclusive or outdated. Doing hash check...${NC}"
cd "${HOME}/qortal" || exit 1
md5sum qortal.jar >/dev/null 2>&1 && md5sum qortal.jar > "local.md5"
cd || exit 1
p "${CYAN}Downloading latest core jar for comparison...${NC}"
fetch "https://github.com/qortal/qortal/releases/latest/download/qortal.jar" "${HOME}/qortal.jar" || true
md5sum "${HOME}/qortal.jar" >/dev/null 2>&1 && md5sum "${HOME}/qortal.jar" > "${HOME}/remote.md5"
LOCAL="$(cat "${HOME}/qortal/local.md5" 2>/dev/null || true)"
REMOTE="$(cat "${HOME}/remote.md5" 2>/dev/null || true)"
if [ -n "$LOCAL" ] && [ -n "$REMOTE" ] && [ "$LOCAL" = "$REMOTE" ]; then
p "${CYAN}Hash check: core up-to-date. Checking environment...${NC}"
check_for_GUI
return 0
else
p "${RED}Core outdated. Updating and preparing bootstrap...${NC}"
cd "${HOME}/qortal" || exit 1
killall -9 java 2>/dev/null || true
sleep 3
rm -rf -- db log.t* qortal.log run.log run.pid qortal.jar 2>/dev/null || true
cp -f -- "${HOME}/qortal.jar" "${HOME}/qortal/qortal.jar" 2>/dev/null || true
rm -f -- "${HOME}/qortal.jar" "${HOME}/remote.md5" local.md5 2>/dev/null || true
potentially_update_settings
./start.sh 2>/dev/null || true
cd || exit 1
check_for_GUI
fi
}
check_for_GUI() {
if [ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ] || [ -n "$XDG_CURRENT_DESKTOP" ]; then
p "${CYAN}GUI detected. Setting up GUI auto-fix...${NC}"
if [ "$ARM_32_DETECTED" = true ] || [ "$ARM_64_DETECTED" = true ]; then
p "${WHITE}ARM + GUI — skipping autostart GUI; using cron for reliability.${NC}"
setup_raspi_cron
else
p "${YELLOW}Installing GUI cron + autostart entries...${NC}"
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/auto-fix-GUI-cron" "${HOME}/auto-fix-GUI-cron" || true
crontab "${HOME}/auto-fix-GUI-cron" 2>/dev/null || true
rm -f -- "${HOME}/auto-fix-GUI-cron"
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/auto-fix-qortal-GUI.desktop" "${HOME}/auto-fix-qortal-GUI.desktop" || true
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/start-qortal.desktop" "${HOME}/start-qortal.desktop" || true
mkdir -p "${HOME}/.config/autostart" 2>/dev/null || true
cp -f -- "${HOME}/auto-fix-qortal-GUI.desktop" "${HOME}/.config/autostart" 2>/dev/null || true
cp -f -- "${HOME}/start-qortal.desktop" "${HOME}/.config/autostart" 2>/dev/null || true
rm -f -- "${HOME}/auto-fix-qortal-GUI.desktop" "${HOME}/start-qortal.desktop"
p "${YELLOW}Auto-fix will run in a terminal ~7 minutes after login.${NC}"
p "${CYAN}Continuing to verify node height...${NC}"
check_height
fi
else
p "${YELLOW}Headless system detected, setting cron then checking height...${NC}"
setup_raspi_cron
fi
}
setup_raspi_cron() {
p "${YELLOW}Setting cron for RPi/headless...${NC}"
mkdir -p "${HOME}/backups/cron-backups" 2>/dev/null || true
crontab -l > "${HOME}/backups/cron-backups/crontab-backup-$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
p "${YELLOW}Checking autostart entries to avoid double-launch...${NC}"
if find "${HOME}/.config/autostart" -maxdepth 1 -name "start-qortal*.desktop" 2>/dev/null | grep -q .; then
p "${RED}Autostart entry found; using GUI cron every 3 days for auto-fix...${NC}"
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/main/auto-fix-GUI-cron" "${HOME}/auto-fix-GUI-cron" || true
crontab "${HOME}/auto-fix-GUI-cron" 2>/dev/null || true
rm -f -- "${HOME}/auto-fix-GUI-cron"
check_height
return 0
fi
p "${BLUE}No autostart entries. Setting full headless cron...${NC}"
fetch "https://raw.githubusercontent.com/crowetic/QORTector-scripts/refs/heads/main/auto-fix-cron" "${HOME}/auto-fix-cron" || true
crontab "${HOME}/auto-fix-cron" 2>/dev/null || true
rm -f -- "${HOME}/auto-fix-cron"
check_height
}
check_height() {
local_height="$(curl -sS --connect-timeout 5 "http://localhost:12391/blocks/height" || true)"
if [ -f auto_fix_last_height.txt ]; then
previous_local_height="$(cat auto_fix_last_height.txt 2>/dev/null || true)"
if [ -n "$previous_local_height" ] && [ "$local_height" = "$previous_local_height" ]; then
p "${RED}Height unchanged since last run; waiting ~3 minutes to re-check...${NC}"
sleep 188
checked_height="$(curl -s --connect-timeout 5 "http://localhost:12391/blocks/height" || true)"
sleep 2
if [ "$checked_height" = "$previous_local_height" ]; then
p "${RED}Height still unchanged; final sanity in 10s...${NC}"
sleep 10
new_check_again="$(curl -sS --connect-timeout 5 "http://localhost:12391/blocks/height" || true)"
p "new height = $new_check_again | prev = $previous_local_height"
if [ "$new_check_again" = "$previous_local_height" ]; then
p "${RED}Unchanged; forcing bootstrap...${NC}"
force_bootstrap
return 0
fi
fi
fi
fi
if [ -z "$local_height" ]; then
p "${RED}Local height empty. Is Qortal running?${NC}"
no_local_height
else
printf "%s" "$local_height" > auto_fix_last_height.txt
remote_height_checks
fi
}
no_local_height() {
p "${WHITE}Checking for bootstrapping/log format...${NC}"
if [ -f "${HOME}/qortal/qortal.log" ]; then
if tail -n 30 "${HOME}/qortal/qortal.log" 2>/dev/null | grep -Ei 'bootstrap|bootstrapping' >/dev/null 2>&1; then
p "${RED}Bootstrapping detected. Updating script and exiting this cycle...${NC}"
update_script
return 0
fi
else
old_log_found=false
for log_file in "${HOME}/qortal/log.t"*; do
if [ -f "$log_file" ]; then
old_log_found=true
p "${YELLOW}Old log format found. Migrating logs & config...${NC}"
mkdir -p "${HOME}/qortal/backup/logs" 2>/dev/null || true
mv -f -- "${HOME}/qortal/log.t"* "${HOME}/qortal/backup/logs" 2>/dev/null || true
if [ -f "${HOME}/qortal/log4j2.properties" ]; then
mv -f -- "${HOME}/qortal/log4j2.properties" "${HOME}/qortal/backup/logs" 2>/dev/null || true
fi
fetch "https://raw.githubusercontent.com/Qortal/qortal/master/log4j2.properties" "${HOME}/qortal/log4j2.properties" || true
p "${RED}Stopping Qortal to apply new logging; sleeping 30s...${NC}"
cd "${HOME}/qortal" || exit 1
./stop.sh 2>/dev/null || true
sleep 30
cd || exit 1
break
fi
done
if [ "$old_log_found" = false ]; then
p "No old log files found."
fi
fi
p "${GREEN}Starting Qortal Core; allowing up to ~35 minutes on slow hardware...${NC}"
potentially_update_settings
cd "${HOME}/qortal" || exit 1
./start.sh 2>/dev/null || true
sleep 2100
cd || exit 1
p "${GREEN}Checking if Qortal started correctly...${NC}"
local_height_check="$(curl -sS --connect-timeout 5 "http://localhost:12391/blocks/height" || true)"
if [ -n "$local_height_check" ]; then
p "${GREEN}Local height ${CYAN}${local_height_check}${NC}"
p "${GREEN}Node looks good; re-checking height and continuing...${NC}"
check_height
else
p "${RED}Start failed; forcing bootstrap...${NC}"
force_bootstrap
fi
}
remote_height_checks() {
height_api_qortal_org="$(curl -sS --connect-timeout 10 "https://api.qortal.org/blocks/height" || true)"
height_qortal_link="$(curl -sS --connect-timeout 10 "https://qortal.link/blocks/height" || true)"
local_height="$(curl -sS --connect-timeout 10 "http://localhost:12391/blocks/height" || true)"
if [ -z "$height_api_qortal_org" ] || [ -z "$height_qortal_link" ]; then
p "${RED}Remote height checks failed. Updating script and continuing later.${NC}"
update_script
return 0
fi
# fall back to last known height or 0
case "$local_height" in
''|*[!0-9]*) local_height="${local_height:-0}" ;;
esac
case "$height_api_qortal_org" in
''|*[!0-9]*) height_api_qortal_org=0 ;;
esac
case "$height_qortal_link" in
''|*[!0-9]*) height_qortal_link=0 ;;
esac
# within +/- 1500
min_api=$((local_height - 1500))
max_api=$((local_height + 1500))
if [ "$height_api_qortal_org" -ge "$min_api" ] 2>/dev/null && [ "$height_api_qortal_org" -le "$max_api" ] 2>/dev/null; then
p "${YELLOW}Local (${local_height}) within 1500 of api.qortal.org (${height_api_qortal_org}).${NC}"
p "${GREEN}api.qortal.org checks PASSED, updating script...${NC}"
update_script
else
p "${RED}Outside range vs api.qortal.org. Checking qortal.link...${NC}"
min_link=$((local_height - 1500))
max_link=$((local_height + 1500))
if [ "$height_qortal_link" -ge "$min_link" ] 2>/dev/null && [ "$height_qortal_link" -le "$max_link" ] 2>/dev/null; then
p "${YELLOW}Local (${local_height}) within 1500 of qortal.link (${height_qortal_link}).${NC}"
p "${GREEN}qortal.link checks PASSED, updating script...${NC}"
update_script
else
p "${RED}Both remotes out of range; forcing bootstrap...${NC}"
force_bootstrap
fi
fi
}
force_bootstrap() {
p "${RED}ISSUES DETECTED — forcing bootstrap...${NC}"
cd "${HOME}/qortal" || exit 1
killall -9 java 2>/dev/null || true
sleep 3
rm -rf -- db log.t* qortal.log run.log run.pid *.gz 2>/dev/null || true
sleep 5
./start.sh 2>/dev/null || true
cd || exit 1
p "${GREEN}Core restarted; should bootstrap now. Updating script...${NC}"
update_script
}
potentially_update_settings() {
p "${GREEN}Validating and updating settings.json (numeric max-merge + forced priorities)...${NC}"
QORTAL_DIR="${HOME}/qortal"
SETTINGS_FILE="${QORTAL_DIR}/settings.json"
BACKUP_DIR="${QORTAL_DIR}/qortal-backup/auto-fix-settings-backup"
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
BACKUP_FILE="${BACKUP_DIR}/backup-settings-${TIMESTAMP}.json"
LATEST_GOOD_LINK="${BACKUP_DIR}/latest-good.json"
TMP_FILE="$(mktemp "${QORTAL_DIR}/.settings.json.tmp.XXXXXX")"
REMOTE_FILE="$(mktemp "${QORTAL_DIR}/.settings.remote.tmp.XXXXXX")"
# Single canonical remote file (default + patch)
DEFAULT_SETTINGS_URL="${AUTO_FIX_SETTINGS_URL:-https://raw.githubusercontent.com/crowetic/QORTector-scripts/refs/heads/main/settings.json}"
DEFAULT_SETTINGS_MIRROR="${AUTO_FIX_SETTINGS_MIRROR_URL:-https://gitea.qortal.link/crowetic/QORTector-scripts/raw/branch/main/settings.json}"
mkdir -p "${BACKUP_DIR}" 2>/dev/null || true
# Ensure jq (best-effort)
if ! command -v jq >/dev/null 2>&1; then
p "${YELLOW}jq not found. Attempting install (Debian/Ubuntu)...${NC}"
if command -v apt-get >/dev/null 2>&1; then
if [ "$(id -u)" -ne 0 ]; then
sudo apt-get update -y && sudo apt-get install -y jq || true
else
apt-get update -y && apt-get install -y jq || true
fi
fi
fi
# Backup current (even if invalid)
if [ -f "$SETTINGS_FILE" ]; then
cp -f -- "$SETTINGS_FILE" "$BACKUP_FILE" 2>/dev/null || true
if is_valid_json_file "$SETTINGS_FILE"; then
ln -sfn "$(basename "$BACKUP_FILE")" "$LATEST_GOOD_LINK" 2>/dev/null || true
fi
fi
# Fetch canonical remote
if ! fetch "$DEFAULT_SETTINGS_URL" "$REMOTE_FILE" "$DEFAULT_SETTINGS_MIRROR"; then
p "${RED}Failed to fetch remote settings (GitHub+Gitea). Aborting settings update safely.${NC}"
rm -f -- "$TMP_FILE" "$REMOTE_FILE"
return 1
fi
# If local invalid/missing: install remote as-is
if ! is_valid_json_file "$SETTINGS_FILE"; then
p "${YELLOW}settings.json missing/invalid. Installing remote settings as-is.${NC}"
atomic_write "$REMOTE_FILE" "$SETTINGS_FILE"
cp -f -- "$SETTINGS_FILE" "${BACKUP_DIR}/backup-settings-default-${TIMESTAMP}.json" 2>/dev/null || true
ln -sfn "backup-settings-default-${TIMESTAMP}.json" "$LATEST_GOOD_LINK" 2>/dev/null || true
p "${GREEN}settings.json created from remote.${NC}"
return 0
fi
# Valid local + valid remote -> merge
if command -v jq >/dev/null 2>&1; then
jq -S --slurpfile remote "$REMOTE_FILE" '
def merge_max($a;$b):
if ($a|type)=="object" and ($b|type)=="object" then
( (($a|keys_unsorted) + ($b|keys_unsorted)) | unique ) as $ks
| reduce $ks[] as $k
({}; .[$k] =
if ($a|has($k)) and ($b|has($k)) then merge_max($a[$k]; $b[$k])
elif ($a|has($k)) then $a[$k]
else $b[$k]
end)
elif ($a|type)=="number" and ($b|type)=="number" then
(if $a >= $b then $a else $b end)
else
# non-number leaves: prefer local if present; else remote
if ($a == null) then $b else $a end
end;
def to_map_array(a):
(a // [])
| map(select(has("messageType") and has("limit")))
| map({
key: .messageType,
# coerce numeric strings to numbers if possible, otherwise leave as-is
value: ( .limit | if type=="string" then (tonumber? // .) else . end )
})
| from_entries;
def merge_thread($l;$p):
(to_map_array($l)) as $lm
| (to_map_array($p)) as $pm
| ( (($lm|keys_unsorted) + ($pm|keys_unsorted)) | unique ) as $keys
| ( $keys
| map({
messageType: .,
limit:
( if (($lm[.]|type)=="number") and (($pm[.]|type)=="number") then
(if $lm[.] >= $pm[.] then $lm[.] else $pm[.] end)
elif (($lm[.]|type)=="number") then $lm[.]
else $pm[.]
end )
})
);
def force_from_remote($merged; $r; $keys):
reduce ($keys[]) as $k
($merged;
if ($r|has($k)) then
.[$k] = $r[$k] # force EXACT value from remote, type preserved
else
. # if remote lacks the key, leave merged value as-is
end);
. as $local
| ($remote[0] // {}) as $r
# 1) Merge everything except the special array (numeric max-merge)
| ( merge_max($local; ($r | del(.maxThreadsPerMessageType))) ) as $base
# 2) Special-case array: max per messageType, union of types
| ( $base
| .maxThreadsPerMessageType =
merge_thread($local.maxThreadsPerMessageType; $r.maxThreadsPerMessageType)
) as $withThreads
# 3) Force exact values for specific priority/latency keys from remote if present
| force_from_remote(
$withThreads;
$r;
[
"handshakeThreadPriority",
"dbCacheThreadPriority",
"networkThreadPriority",
"pruningThreadPriority",
"synchronizerThreadPriority",
"archivingPause"
]
)
' "$SETTINGS_FILE" > "$TMP_FILE" 2>/dev/null
if is_valid_json_file "$TMP_FILE"; then
atomic_write "$TMP_FILE" "$SETTINGS_FILE"
FINAL_BKP="${BACKUP_DIR}/backup-settings-postmerge-${TIMESTAMP}.json"
cp -f -- "$SETTINGS_FILE" "$FINAL_BKP" 2>/dev/null || true
ln -sfn "$(basename "$FINAL_BKP")" "$LATEST_GOOD_LINK" 2>/dev/null || true
p "${GREEN}settings.json merged successfully (max-merge + forced priorities from remote).${NC}"
else
p "${RED}Merged settings became invalid. Falling back to remote defaults.${NC}"
if is_valid_json_file "$REMOTE_FILE"; then
atomic_write "$REMOTE_FILE" "$SETTINGS_FILE"
DEFAULT_BKP="${BACKUP_DIR}/backup-settings-default-${TIMESTAMP}.json"
cp -f -- "$SETTINGS_FILE" "$DEFAULT_BKP" 2>/dev/null || true
ln -sfn "$(basename "$DEFAULT_BKP")" "$LATEST_GOOD_LINK" 2>/dev/null || true
p "${GREEN}settings.json restored from remote defaults.${NC}"
else
p "${RED}Remote defaults invalid as well. Keeping current settings.${NC}"
rm -f -- "$TMP_FILE" "$REMOTE_FILE" 2>/dev/null || true
return 1
fi
rm -f -- "$TMP_FILE" 2>/dev/null || true
fi
else
p "${YELLOW}jq unavailable; skipping merge. (Local file left unchanged.)${NC}"
rm -f -- "$REMOTE_FILE" 2>/dev/null || true
return 0
fi
rm -f -- "$REMOTE_FILE" 2>/dev/null || true
return 0
}
update_script() {
p "${YELLOW}Updating script to newest version and backing up old one...${NC}"
mkdir -p "${HOME}/qortal/new-scripts/backups" 2>/dev/null || true
if [ -f "${HOME}/qortal/new-scripts/auto-fix-qortal.sh" ]; then
cp -f -- "${HOME}/qortal/new-scripts/auto-fix-qortal.sh" "${HOME}/qortal/new-scripts/backups/auto-fix-$(date +%Y%m%d%H%M%S).sh" 2>/dev/null || true
fi
if [ -f "${HOME}/auto-fix-qortal.sh" ]; then
cp -f -- "${HOME}/auto-fix-qortal.sh" "${HOME}/qortal/new-scripts/backups/original.sh" 2>/dev/null || true
fi
dl="${HOME}/qortal/new-scripts/auto-fix-qortal.sh.download"
if fetch "$DEFAULT_SCRIPT_URL" "$dl" "$DEFAULT_SCRIPT_MIRROR"; then
chmod +x "$dl" 2>/dev/null || true
atomic_write "$dl" "${HOME}/qortal/new-scripts/auto-fix-qortal.sh"
cp -f -- "${HOME}/qortal/new-scripts/auto-fix-qortal.sh" "${HOME}/auto-fix-qortal.sh" 2>/dev/null || true
chmod +x "${HOME}/auto-fix-qortal.sh" 2>/dev/null || true
rm -f -- "${HOME}/auto_fix_updated"
else
p "${RED}Self-update fetch failed. Keeping current script. (Will try again next run.)${NC}"
fi
p "${YELLOW}Checking for any settings changes required...${NC}"
sleep 1
potentially_update_settings
rm -f -- "${HOME}/qortal.jar" "${HOME}/run.pid" "${HOME}/run.log" "${HOME}/remote.md5" "${HOME}/qortal/local.md5" 2>/dev/null || true
rm -f -- "${HOME}"/backups/backup-settings* 2>/dev/null || true
p "${YELLOW}Auto-fix script run complete.${NC}"
sleep 2
return 0
}
# ================= Entry =================
initial_update