Files

395 lines
12 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
MAIN_URL="https://cloud.qortal.org/s/devnet_download/download/qortal-DevNet-MAIN.7z"
DB_URL="https://cloud.qortal.org/s/QortalDevNetDatabaseLatest/download/db-DevNet-LATEST.7z"
IP_SCRIPT_URL="https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/add-public-ip-to-settings-mac.sh"
DEFAULT_SETTINGS_URL="https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/default-settings.json"
START_VALIDATION_URL="https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/start-mac.sh"
QORT_SCRIPT_URL="https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/qort"
DEPENDENCIES_URL="https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/setup-dependencies-mac.sh"
STOP_SCRIPT_URL="https://gitea.qortal.link/crowetic/qortal-DevNet-scripts/raw/branch/main/stop-mac.sh"
PUBLISH_SHARE_TOKEN="PublishDevNetIPsHere"
PUBLISH_WEBDAV_URL="https://cloud.qortal.org/public.php/webdav"
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
HOME_DIR="${HOME}"
BASE_DIR="${HOME_DIR}"
DEVNET_DIR=""
STATE_FILE=""
DOWNLOAD_DIR=""
MAIN_ARCHIVE=""
DB_ARCHIVE=""
DEFAULT_SETTINGS_LOCAL=""
IP_SCRIPT_LOCAL=""
START_VALIDATION_LOCAL=""
QORT_SCRIPT_LOCAL=""
DEPENDENCIES_LOCAL=""
STOP_SCRIPT_LOCAL=""
PORT_START_DEFAULT=23391
PORT_START=""
LISTEN_PORT=""
log() { echo "[$(date +'%F %T')] $*"; }
die() { log "ERROR: $*"; exit 1; }
need_cmd() { command -v "$1" >/dev/null 2>&1; }
ensure_dependencies() {
local missing=()
if ! need_cmd 7z; then
missing+=("7z")
fi
if ! need_cmd python3; then
missing+=("python3")
fi
if ! need_cmd curl && ! need_cmd wget; then
missing+=("curl/wget")
fi
if ! need_cmd jq; then
missing+=("jq")
fi
if ! need_cmd java; then
missing+=("java")
fi
if [ "${#missing[@]}" -eq 0 ]; then
return 0
fi
log "Missing dependencies detected: ${missing[*]}"
mkdir -p "$DOWNLOAD_DIR"
if [ -x "${SCRIPT_DIR}/setup-dependencies-mac.sh" ]; then
DEP_SCRIPT="${SCRIPT_DIR}/setup-dependencies-mac.sh"
else
DEP_SCRIPT="${DEPENDENCIES_LOCAL}"
if need_cmd curl; then
curl -fL --retry 3 --retry-delay 5 -o "$DEP_SCRIPT" "$DEPENDENCIES_URL"
elif need_cmd wget; then
wget -qO "$DEP_SCRIPT" "$DEPENDENCIES_URL"
else
die "Neither curl nor wget found; cannot download setup-dependencies-mac.sh."
fi
fi
chmod +x "$DEP_SCRIPT"
"$DEP_SCRIPT"
if ! need_cmd 7z || ! need_cmd python3 || ( ! need_cmd curl && ! need_cmd wget ) || ! need_cmd jq || ! need_cmd java; then
die "Dependencies are still missing after setup. Please install manually."
fi
}
read -r -t 20 -p "Use default base path ${HOME_DIR}? [Y/n] (auto-continue in 20s): " USE_DEFAULT_BASE || USE_DEFAULT_BASE=""
case "$USE_DEFAULT_BASE" in
n|N|no|NO)
read -r -t 20 -p "Enter the base path to install into (auto-continue in 20s): " BASE_DIR_INPUT || BASE_DIR_INPUT=""
if [[ -z "${BASE_DIR_INPUT}" ]]; then
die "Base path cannot be empty."
fi
BASE_DIR="${BASE_DIR_INPUT}"
;;
*)
BASE_DIR="${HOME_DIR}"
;;
esac
DEVNET_DIR="${BASE_DIR}/qortal-DevNet"
STATE_FILE="${BASE_DIR}/qortal-DevNet.setup.state"
DOWNLOAD_DIR="${BASE_DIR}/qortal-DevNet-downloads"
MAIN_ARCHIVE="${DOWNLOAD_DIR}/qortal-DevNet-MAIN.7z"
DB_ARCHIVE="${DOWNLOAD_DIR}/db-DevNet-LATEST.7z"
DEFAULT_SETTINGS_LOCAL="${DOWNLOAD_DIR}/default-settings.json"
IP_SCRIPT_LOCAL="${DOWNLOAD_DIR}/add-public-ip-to-settings-mac.sh"
START_VALIDATION_LOCAL="${DOWNLOAD_DIR}/start-mac.sh"
QORT_SCRIPT_LOCAL="${DOWNLOAD_DIR}/qort"
DEPENDENCIES_LOCAL="${DOWNLOAD_DIR}/setup-dependencies-mac.sh"
STOP_SCRIPT_LOCAL="${DOWNLOAD_DIR}/stop-mac.sh"
ensure_dependencies
read -r -t 20 -p "Use default port range ${PORT_START_DEFAULT}-$((${PORT_START_DEFAULT} + 3))? [Y/n] (auto-continue in 20s): " USE_DEFAULT_PORTS || USE_DEFAULT_PORTS=""
case "$USE_DEFAULT_PORTS" in
n|N|no|NO)
read -r -t 20 -p "Enter the starting port for the range (auto-continue in 20s): " PORT_START_INPUT || PORT_START_INPUT=""
if [[ -z "${PORT_START_INPUT}" ]]; then
die "Port start cannot be empty."
fi
if ! [[ "$PORT_START_INPUT" =~ ^[0-9]+$ ]]; then
die "Port start must be a number."
fi
PORT_START="$PORT_START_INPUT"
;;
*)
PORT_START="$PORT_START_DEFAULT"
;;
esac
if (( PORT_START < 1024 || PORT_START > 65532 )); then
die "Port start must be between 1024 and 65532."
fi
LISTEN_PORT=$((PORT_START + 1))
step_done() {
grep -Fxq "$1" "$STATE_FILE" 2>/dev/null
}
mark_done() {
printf '%s\n' "$1" >> "$STATE_FILE"
}
download_file() {
local url="$1"
local dest="$2"
if need_cmd curl; then
curl -fL --retry 3 --retry-delay 5 -o "$dest" "$url"
elif need_cmd wget; then
wget -qO "$dest" "$url"
else
die "Neither curl nor wget found. Please install one before running."
fi
}
ensure_file() {
local url="$1"
local dest="$2"
if [[ -s "$dest" ]]; then
log "Using existing $(basename "$dest")"
else
download_file "$url" "$dest"
fi
}
port_in_use() {
local port="$1"
if need_cmd lsof; then
lsof -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1
elif need_cmd netstat; then
netstat -an | awk '{print $4}' | grep -Eq "[\.:]${port}\$"
else
return 2
fi
}
PORT_RANGE_IN_USE=false
PORT_CHECK_UNKNOWN=false
for p in "$PORT_START" "$((PORT_START + 1))" "$((PORT_START + 2))" "$((PORT_START + 3))"; do
if port_in_use "$p"; then
PORT_RANGE_IN_USE=true
break
elif [[ $? -eq 2 ]]; then
PORT_CHECK_UNKNOWN=true
fi
done
if [[ "$PORT_RANGE_IN_USE" = true ]]; then
log "Port range ${PORT_START}-$((${PORT_START} + 3)) appears in use; shifting by 10000"
PORT_START=$((PORT_START + 10000))
if (( PORT_START > 65532 )); then
die "Port range exceeds 65535 after shifting."
fi
LISTEN_PORT=$((PORT_START + 1))
elif [[ "$PORT_CHECK_UNKNOWN" = true ]]; then
log "Warning: unable to detect if ports are in use; continuing with selected range."
fi
get_public_ip() {
local ip=""
local url=""
local urls="https://api.ipify.org https://ipv4.icanhazip.com https://checkip.amazonaws.com https://ifconfig.me https://canhazip.com"
local curl_opts="-fsS --max-time 8 -A Mozilla/5.0"
for url in $urls; do
if need_cmd curl; then
ip="$(curl $curl_opts "$url" 2>/dev/null || true)"
elif need_cmd wget; then
ip="$(wget -qO- "$url" || true)"
else
return 1
fi
ip="$(echo "$ip" | tr -d ' \n\r')"
if echo "$ip" | grep -Eq '^[0-9]{1,3}(\.[0-9]{1,3}){3}$'; then
echo "$ip"
return 0
fi
done
if need_cmd dig; then
ip="$(dig +short myip.opendns.com @resolver1.opendns.com | head -n 1 | tr -d ' \n\r')"
if echo "$ip" | grep -Eq '^[0-9]{1,3}(\.[0-9]{1,3}){3}$'; then
echo "$ip"
return 0
fi
fi
return 1
}
publish_public_ip() {
local ip="$1"
local port="$2"
local host_name="$3"
local tmp_file="$4"
local remote_name="${host_name}.txt"
local url="${PUBLISH_WEBDAV_URL}/${remote_name}"
local alt_name=""
local alt_url=""
local http_code=""
printf '%s:%s\n' "$ip" "$port" > "$tmp_file"
if need_cmd curl; then
http_code="$(curl -sS -u "${PUBLISH_SHARE_TOKEN}:" -T "$tmp_file" -o /dev/null -w "%{http_code}" "$url" || true)"
if [[ "$http_code" != "200" && "$http_code" != "201" && "$http_code" != "204" ]]; then
alt_name="${host_name}-$(date -u +'%Y%m%d_%H%M%S').txt"
alt_url="${PUBLISH_WEBDAV_URL}/${alt_name}"
http_code="$(curl -sS -u "${PUBLISH_SHARE_TOKEN}:" -T "$tmp_file" -o /dev/null -w "%{http_code}" "$alt_url" || true)"
if [[ "$http_code" != "200" && "$http_code" != "201" && "$http_code" != "204" ]]; then
return 1
fi
fi
elif need_cmd wget; then
wget --method=PUT --body-file="$tmp_file" --user="$PUBLISH_SHARE_TOKEN" --password="" -qO- "$url" >/dev/null
else
return 1
fi
}
if ! need_cmd 7z; then
die "7z not found. Please install p7zip/7z before running."
fi
log "Step 1/7: Download files..."
mkdir -p "$DOWNLOAD_DIR"
ensure_file "$MAIN_URL" "$MAIN_ARCHIVE"
ensure_file "$DB_URL" "$DB_ARCHIVE"
ensure_file "$DEFAULT_SETTINGS_URL" "$DEFAULT_SETTINGS_LOCAL"
ensure_file "$IP_SCRIPT_URL" "$IP_SCRIPT_LOCAL"
ensure_file "$START_VALIDATION_URL" "$START_VALIDATION_LOCAL"
ensure_file "$QORT_SCRIPT_URL" "$QORT_SCRIPT_LOCAL"
ensure_file "$STOP_SCRIPT_URL" "$STOP_SCRIPT_LOCAL"
chmod +x "$IP_SCRIPT_LOCAL" "$START_VALIDATION_LOCAL" "$STOP_SCRIPT_LOCAL" "$QORT_SCRIPT_LOCAL"
if ! step_done "01-download-files"; then
mark_done "01-download-files"
fi
if ! step_done "02-prepare-devnet-dir"; then
log "Step 2/7: Prepare devnet directory..."
if [[ -d "$DEVNET_DIR" ]]; then
BACKUP_DIR="${HOME_DIR}/backup-qortal-DevNet"
if [[ -e "$BACKUP_DIR" ]]; then
BACKUP_DIR="${BACKUP_DIR}-$(date +%Y%m%d_%H%M%S)"
fi
log "Existing ${DEVNET_DIR} found; moving to ${BACKUP_DIR}"
mv "$DEVNET_DIR" "$BACKUP_DIR"
fi
log "Creating ${DEVNET_DIR}"
mkdir -p "$DEVNET_DIR"
mark_done "02-prepare-devnet-dir"
fi
if ! step_done "03-extract-main"; then
log "Step 3/7: Extract devnet main archive..."
7z x "$MAIN_ARCHIVE" -o"$DEVNET_DIR" >/dev/null
if [[ -d "${DEVNET_DIR}/temp" ]]; then
log "Normalizing devnet main files from temp/ into ${DEVNET_DIR}"
rsync -a "${DEVNET_DIR}/temp/" "${DEVNET_DIR}/"
rm -rf "${DEVNET_DIR}/temp"
fi
mark_done "03-extract-main"
fi
if ! step_done "04-extract-db"; then
log "Step 4/7: Extract devnet DB archive..."
if [[ -d "${DEVNET_DIR}/db" ]]; then
DB_BACKUP="${DEVNET_DIR}/db.bak-$(date +%Y%m%d_%H%M%S)"
log "Existing db folder found; moving to ${DB_BACKUP}"
mv "${DEVNET_DIR}/db" "$DB_BACKUP"
fi
7z x "$DB_ARCHIVE" -o"$DEVNET_DIR" >/dev/null
if [[ ! -d "${DEVNET_DIR}/db" ]]; then
die "Expected db/ folder in devnet DB archive, but it was not found."
fi
mark_done "04-extract-db"
fi
if ! step_done "05-configure-settings"; then
log "Step 5/7: Configure settings..."
SETTINGS_PATH="${DEVNET_DIR}/settings.json"
if [[ -f "$SETTINGS_PATH" ]]; then
SETTINGS_BACKUP="${DEVNET_DIR}/backup-settings.json"
if [[ -e "$SETTINGS_BACKUP" ]]; then
SETTINGS_BACKUP="${SETTINGS_BACKUP}-$(date +%Y%m%d_%H%M%S)"
fi
log "Existing settings.json found; moving to ${SETTINGS_BACKUP}"
mv "$SETTINGS_PATH" "$SETTINGS_BACKUP"
fi
cp -f "$DEFAULT_SETTINGS_LOCAL" "$SETTINGS_PATH"
IP_SCRIPT_PATH="${DEVNET_DIR}/add-public-ip-to-settings.sh"
cp -f "$IP_SCRIPT_LOCAL" "$IP_SCRIPT_PATH"
chmod +x "$IP_SCRIPT_PATH"
log "Running public IP script..."
( cd "$DEVNET_DIR" && QORTAL_PORT_START="$PORT_START" "$IP_SCRIPT_PATH" )
mark_done "05-configure-settings"
fi
if ! step_done "06-publish-public-ip"; then
log "Step 6/7: Publish public IP..."
HOST_NAME="$(hostname -s 2>/dev/null || hostname)"
PUBLISH_TMP="${DOWNLOAD_DIR}/devnet-public-ip.txt"
PUBLIC_IP="$(get_public_ip || true)"
if [[ -n "$PUBLIC_IP" ]]; then
if publish_public_ip "$PUBLIC_IP" "$LISTEN_PORT" "$HOST_NAME" "$PUBLISH_TMP"; then
log "Published ${PUBLIC_IP}:${LISTEN_PORT} for ${HOST_NAME}"
else
log "Warning: failed to publish public IP to Nextcloud share."
fi
else
log "Warning: could not determine public IP to publish."
fi
mark_done "06-publish-public-ip"
fi
if ! step_done "07-install-start-script"; then
log "Step 7/7: Install start/stop scripts..."
START_VALIDATION_PATH="${DEVNET_DIR}/start-mac.sh"
cp -f "$START_VALIDATION_LOCAL" "$START_VALIDATION_PATH"
chmod +x "$START_VALIDATION_PATH"
log "Replacing start.sh with start-mac.sh"
if [[ -f "${DEVNET_DIR}/start.sh" ]]; then
START_BACKUP="${DEVNET_DIR}/start.sh.bak-$(date +%Y%m%d_%H%M%S)"
mv "${DEVNET_DIR}/start.sh" "$START_BACKUP"
log "Backed up existing start.sh to ${START_BACKUP}"
fi
cp -f "$START_VALIDATION_PATH" "${DEVNET_DIR}/start.sh"
if [[ -f "${DEVNET_DIR}/stop-mac.sh" ]]; then
STOP_BACKUP="${DEVNET_DIR}/stop-mac.sh.bak-$(date +%Y%m%d_%H%M%S)"
mv "${DEVNET_DIR}/stop-mac.sh" "$STOP_BACKUP"
log "Backed up existing stop-mac.sh to ${STOP_BACKUP}"
fi
cp -f "$STOP_SCRIPT_LOCAL" "${DEVNET_DIR}/stop-mac.sh"
chmod +x "${DEVNET_DIR}/stop-mac.sh"
if [[ -f "${DEVNET_DIR}/stop.sh" ]]; then
STOP_ALIAS_BACKUP="${DEVNET_DIR}/stop.sh.bak-$(date +%Y%m%d_%H%M%S)"
mv "${DEVNET_DIR}/stop.sh" "$STOP_ALIAS_BACKUP"
log "Backed up existing stop.sh to ${STOP_ALIAS_BACKUP}"
fi
cp -f "$STOP_SCRIPT_LOCAL" "${DEVNET_DIR}/stop.sh"
chmod +x "${DEVNET_DIR}/stop.sh"
log "Installing qort helper..."
cp -f "$QORT_SCRIPT_LOCAL" "${DEVNET_DIR}/qort"
chmod +x "${DEVNET_DIR}/qort"
mark_done "07-install-start-script"
fi
log "Done."