From b342be862543aaebd52750a9cfc589acb545a8f6 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Tue, 25 Nov 2025 13:45:02 +0330 Subject: [PATCH 1/9] feat: implement Bash REST API with TLS and API key protection --- node-service.sh | 188 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100755 node-service.sh diff --git a/node-service.sh b/node-service.sh new file mode 100755 index 0000000..5676957 --- /dev/null +++ b/node-service.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { + printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2 +} + +load_env_file() { + while IFS= read -r line || [[ -n "$line" ]]; do + line="${line%%#*}" + line="${line%%$'\r'*}" + [[ -z "${line//[[:space:]]/}" ]] && continue + if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=[[:space:]]*(.*)$ ]]; then + local key=${BASH_REMATCH[1]} + local val=${BASH_REMATCH[2]} + val="${val#"${val%%[![:space:]]*}"}" # trim leading ws + val="${val%"${val##*[![:space:]]}"}" # trim trailing ws + if [[ "$val" =~ ^\".*\"$ || "$val" =~ ^\'.*\'$ ]]; then + val=${val:1:${#val}-2} + fi + export "$key=$val" + fi + done < "$ENV_FILE" +} + +APP_NAME="pg-node" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEFAULT_ENV_FILE="/opt/$APP_NAME/.env" +LOCAL_ENV_FILE="$SCRIPT_DIR/.env" +ENV_FILE="${ENV_FILE:-$DEFAULT_ENV_FILE}" + +if [[ ! -f "$ENV_FILE" && -f "$LOCAL_ENV_FILE" ]]; then + ENV_FILE="$LOCAL_ENV_FILE" +fi + +if [[ -f "$ENV_FILE" ]]; then + load_env_file + log "Loaded env file: $ENV_FILE" +else + log "Env file not found, using defaults: $ENV_FILE" +fi + +PORT="${PORT:-3000}" +MAX_BODY=1048576 +API_KEY="${API_KEY:-}" + +if [[ -z "$API_KEY" ]]; then + log "API_KEY must be set in the env file" + exit 1 +fi + +if [[ -z "${SSL_CERT_FILE:-}" || -z "${SSL_KEY_FILE:-}" ]]; then + log "TLS required: set SSL_CERT_FILE and SSL_KEY_FILE in the env file" + exit 1 +fi +if [[ ! -r "$SSL_CERT_FILE" ]]; then + log "Cannot read SSL_CERT_FILE: $SSL_CERT_FILE" + exit 1 +fi +if [[ ! -r "$SSL_KEY_FILE" ]]; then + log "Cannot read SSL_KEY_FILE: $SSL_KEY_FILE" + exit 1 +fi +if ! command -v openssl >/dev/null 2>&1; then + log "openssl is required for TLS mode" + exit 1 +fi +log "TLS enforced with cert=$SSL_CERT_FILE key=$SSL_KEY_FILE on port $PORT" +log "API key protection enabled" + +json_escape() { + local s=$1 + s=${s//\\/\\\\} + s=${s//\"/\\\"} + s=${s//$'\n'/\\n} + s=${s//$'\r'/\\r} + echo -n "$s" +} + +status_text() { + case "$1" in + 200) echo -n "OK" ;; + 401) echo -n "Unauthorized" ;; + 400) echo -n "Bad Request" ;; + 404) echo -n "Not Found" ;; + *) echo -n "Internal Server Error" ;; + esac +} + + +respond() { + local code=$1 + local body=$2 + local text body_len + LAST_STATUS=$code + text=$(status_text "$code") + body_len=${#body} + printf 'HTTP/1.1 %s %s\r\n' "$code" "$text" + printf 'Content-Type: application/json\r\n' + printf 'Content-Length: %s\r\n' "$body_len" + printf 'Connection: close\r\n\r\n' + printf '%s' "$body" +} +handle_node_update(){ + log "Executing $APP_NAME update" + $APP_NAME update + respond 200 "{\"detaile\":\"node updated successfully\"}" +} + +handle_node_core_update(){ + log "Executing $APP_NAME core-update" + $APP_NAME core-update + respond 200 "{\"detaile\":\"node core updated successfully\"}" +} + +handle_connection() { + local request_line method path version + if ! IFS= read -r request_line; then + return 0 + fi + request_line=${request_line%$'\r'} + read -r method path version <<<"$request_line" + log "Request line: $request_line" + + local header_line content_length=0 x_api_key="" header_name header_value + while IFS= read -r header_line; do + header_line=${header_line%$'\r'} + [[ -z "$header_line" ]] && break + log "Header: $header_line" + if [[ "$header_line" =~ ^[Cc]ontent-[Ll]ength:\ ([0-9]+) ]]; then + content_length=${BASH_REMATCH[1]} + fi + header_name=${header_line%%:*} + header_value=${header_line#*:} + header_name=${header_name,,} + header_value=${header_value# } + if [[ "$header_name" == "x-api-key" ]]; then + x_api_key="$header_value" + fi + done + + if [[ -z "$x_api_key" ]]; then + respond 401 '{"error":"missing api key"}' + log "Unauthorized: missing x-api-key for $method $path" + return 0 + fi + if [[ "$x_api_key" != "$API_KEY" ]]; then + respond 401 '{"error":"invalid api key"}' + log "Unauthorized: invalid x-api-key for $method $path" + return 0 + fi + + local body="" + if (( content_length > 0 )); then + if (( content_length > MAX_BODY )); then + respond 400 '{"error":"Payload too large"}' + log "Body rejected: $content_length bytes (too large)" + return 0 + fi + IFS= read -r -N "$content_length" body || true + log "Body received: ${#body} bytes" + fi + + case "$method $path" in + "GET /") + respond 200 '{"status":"ok"}' + ;; + "POST /node/update") + handle_node_update + ;; + "POST /node/core_update") + handle_node_core_update + ;; + *) + respond 404 '{"error":"Not found"}' + ;; + esac + log "Responded $LAST_STATUS to $method $path" +} + +log "Bash REST API listening (TLS only) on https://localhost:${PORT}" +while true; do + coproc OPENSSL { openssl s_server -quiet -accept "$PORT" -cert "$SSL_CERT_FILE" -key "$SSL_KEY_FILE" -naccept 1; } + handle_connection <&"${OPENSSL[0]}" >&"${OPENSSL[1]}" || true + exec {OPENSSL[0]}>&- + exec {OPENSSL[1]}>&- + wait "$OPENSSL_PID" 2>/dev/null || true +done From e08b7dab95de5c58bd431b2453bc57465a6208df Mon Sep 17 00:00:00 2001 From: Mohammad Date: Tue, 25 Nov 2025 20:18:26 +0330 Subject: [PATCH 2/9] rename node-service to pg-node-service --- node-service.sh => pg-node-service.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename node-service.sh => pg-node-service.sh (100%) diff --git a/node-service.sh b/pg-node-service.sh similarity index 100% rename from node-service.sh rename to pg-node-service.sh From 18ee97f55611c1ea369e471afa1d6419b488ead0 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Tue, 25 Nov 2025 20:59:06 +0330 Subject: [PATCH 3/9] feat: Add core version for updating xray dirctly --- pg-node-service.sh | 40 +++++++++++++++++++++++++++++--------- pg-node.sh | 48 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/pg-node-service.sh b/pg-node-service.sh index 5676957..48d8f4f 100755 --- a/pg-node-service.sh +++ b/pg-node-service.sh @@ -104,13 +104,36 @@ respond() { handle_node_update(){ log "Executing $APP_NAME update" $APP_NAME update - respond 200 "{\"detaile\":\"node updated successfully\"}" + respond 200 "{\"detail\":\"node updated successfully\"}" } handle_node_core_update(){ - log "Executing $APP_NAME core-update" - $APP_NAME core-update - respond 200 "{\"detaile\":\"node core updated successfully\"}" + local body="${1:-}" + local core_version="" + + if ! command -v jq >/dev/null 2>&1; then + log "jq is required to parse core_version from request body" + respond 500 '{"detail":"jq not installed on server"}' + return + fi + + if [[ -n "$body" ]]; then + if ! core_version=$(printf '%s' "$body" | jq -r '."core_version" // ""' 2>/dev/null); then + log "Failed to parse JSON body for core_version" + respond 400 '{"detail":"Invalid JSON body"}' + return + fi + fi + + if [[ -n "$core_version" ]]; then + log "Executing $APP_NAME core-update with version: $core_version" + if $APP_NAME core-update --version "$core_version"; then + respond 200 "{\"detail\":\"node core updated successfully\"}" + else + log "core-update failed for version: $core_version" + respond 404 "{\"detail\":\"core-update failed for version $(json_escape "$core_version")\"}" + fi + fi } handle_connection() { @@ -126,7 +149,6 @@ handle_connection() { while IFS= read -r header_line; do header_line=${header_line%$'\r'} [[ -z "$header_line" ]] && break - log "Header: $header_line" if [[ "$header_line" =~ ^[Cc]ontent-[Ll]ength:\ ([0-9]+) ]]; then content_length=${BASH_REMATCH[1]} fi @@ -161,7 +183,7 @@ handle_connection() { log "Body received: ${#body} bytes" fi - case "$method $path" in + case "$method $path" in "GET /") respond 200 '{"status":"ok"}' ;; @@ -169,16 +191,16 @@ handle_connection() { handle_node_update ;; "POST /node/core_update") - handle_node_core_update + handle_node_core_update "$body" ;; *) - respond 404 '{"error":"Not found"}' + respond 404 '{"detail":"Not found"}' ;; esac log "Responded $LAST_STATUS to $method $path" } -log "Bash REST API listening (TLS only) on https://localhost:${PORT}" +log "Bash REST API listening on https://localhost:${PORT}" while true; do coproc OPENSSL { openssl s_server -quiet -accept "$PORT" -cert "$SSL_CERT_FILE" -key "$SSL_KEY_FILE" -naccept 1; } handle_connection <&"${OPENSSL[0]}" >&"${OPENSSL[1]}" || true diff --git a/pg-node.sh b/pg-node.sh index 5293c91..74e63f8 100755 --- a/pg-node.sh +++ b/pg-node.sh @@ -1007,6 +1007,7 @@ identify_the_operating_system_and_architecture() { # Function to update the Xray core get_xray_core() { + local requested_version="${1:-}" identify_the_operating_system_and_architecture clear @@ -1043,7 +1044,21 @@ get_xray_core() { versions=($(echo "$latest_releases" | grep -oP '"tag_name": "\K(.*?)(?=")')) - if [ "$AUTO_CONFIRM" = true ]; then + if [ ${#versions[@]} -eq 0 ]; then + echo -e "\033[1;31mNo Xray-core releases found.\033[0m" + exit 1 + fi + + if [[ -n "$requested_version" ]]; then + if [[ "$requested_version" == "latest" ]]; then + selected_version=${versions[0]} + elif [ "$(validate_version "$requested_version")" == "valid" ]; then + selected_version="$requested_version" + else + echo -e "\033[1;31mInvalid version or version does not exist. Please try again.\033[0m" + exit 1 + fi + elif [ "$AUTO_CONFIRM" = true ]; then selected_version=${versions[0]} else while true; do @@ -1204,7 +1219,31 @@ install_yq() { update_core_command() { check_running_as_root - get_xray_core + local core_version_arg="" + + while [[ $# -gt 0 ]]; do + case "$1" in + -v | --version) + if [[ -z "${2:-}" ]]; then + colorized_echo red "Error: --version requires a value." + exit 1 + fi + core_version_arg="$2" + shift 2 + ;; + -h | --help) + colorized_echo red "Usage: node core-update [--version VERSION]" + echo " --version VERSION Install a specific Xray-core version (use 'latest' for newest release)" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac + done + + get_xray_core "$core_version_arg" # Ensure volumes match DATA_DIR when custom name is used service_name="node" @@ -1336,6 +1375,8 @@ usage() { colorized_echo yellow " -v, --version VERSION $(tput sgr0)– Install specific version" colorized_echo yellow " --pre-release $(tput sgr0)– Install pre-release version" colorized_echo yellow " --name NAME $(tput sgr0)– Install with custom name" + colorized_echo cyan "Core-update Options:" + colorized_echo yellow " --version VERSION $(tput sgr0)– Update Xray-core to specific version (use 'latest' for newest)" echo colorized_echo cyan "Node Information:" @@ -1463,7 +1504,8 @@ logs) logs_command "$@" ;; core-update) - update_core_command + shift + update_core_command "$@" ;; geofiles) shift From 3dd0284f7484e56cb668971c67b0adf16c958057 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Wed, 26 Nov 2025 08:59:00 +0330 Subject: [PATCH 4/9] feat: Add systemd service management for pg-node --- pg-node-service.sh | 8 +- pg-node.sh | 226 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 229 insertions(+), 5 deletions(-) diff --git a/pg-node-service.sh b/pg-node-service.sh index 48d8f4f..0796655 100755 --- a/pg-node-service.sh +++ b/pg-node-service.sh @@ -40,7 +40,7 @@ else log "Env file not found, using defaults: $ENV_FILE" fi -PORT="${PORT:-3000}" +API_PORT="${API_PORT:-3000}" MAX_BODY=1048576 API_KEY="${API_KEY:-}" @@ -65,7 +65,7 @@ if ! command -v openssl >/dev/null 2>&1; then log "openssl is required for TLS mode" exit 1 fi -log "TLS enforced with cert=$SSL_CERT_FILE key=$SSL_KEY_FILE on port $PORT" +log "TLS enforced with cert=$SSL_CERT_FILE key=$SSL_KEY_FILE on port $API_PORT" log "API key protection enabled" json_escape() { @@ -200,9 +200,9 @@ handle_connection() { log "Responded $LAST_STATUS to $method $path" } -log "Bash REST API listening on https://localhost:${PORT}" +log "Bash REST API listening on https://localhost:${API_PORT}" while true; do - coproc OPENSSL { openssl s_server -quiet -accept "$PORT" -cert "$SSL_CERT_FILE" -key "$SSL_KEY_FILE" -naccept 1; } + coproc OPENSSL { openssl s_server -quiet -accept "$API_PORT" -cert "$SSL_CERT_FILE" -key "$SSL_KEY_FILE" -naccept 1; } handle_connection <&"${OPENSSL[0]}" >&"${OPENSSL[1]}" || true exec {OPENSSL[0]}>&- exec {OPENSSL[1]}>&- diff --git a/pg-node.sh b/pg-node.sh index 74e63f8..19acc7c 100755 --- a/pg-node.sh +++ b/pg-node.sh @@ -77,6 +77,7 @@ SSL_KEY_FILE="$DATA_DIR/certs/ssl_key.pem" LAST_XRAY_CORES=5 FETCH_REPO="PasarGuard/scripts" SCRIPT_URL="https://github.com/$FETCH_REPO/raw/main/pg-node.sh" +SERVICE_SCRIPT_URL="https://github.com/$FETCH_REPO/raw/main/pg-node-service.sh" colorized_echo() { local color=$1 @@ -115,6 +116,56 @@ check_running_as_root() { fi } +set_service_paths() { + SERVICE_NAME="${APP_NAME}-service" + SERVICE_SCRIPT_PATH="/usr/local/bin/${SERVICE_NAME}.sh" + SERVICE_UNIT="/etc/systemd/system/${SERVICE_NAME}.service" +} + +require_systemd() { + if ! command -v systemctl >/dev/null 2>&1; then + colorized_echo red "systemd is required to manage the service (systemctl not found)." + exit 1 + fi +} + +service_installed() { + if ! command -v systemctl >/dev/null 2>&1; then + return 1 + fi + set_service_paths + if [ -f "$SERVICE_UNIT" ] || systemctl list-unit-files | grep -q "^${SERVICE_NAME}.service"; then + return 0 + fi + return 1 +} + +restart_service_if_installed() { + if ! service_installed; then + return + fi + if [ "$(id -u)" != "0" ]; then + colorized_echo yellow "$SERVICE_NAME is installed; run as root to restart it." + return + fi + systemctl restart "$SERVICE_NAME" + colorized_echo blue "$SERVICE_NAME service restarted." +} + +update_service_if_installed() { + if ! service_installed; then + return + fi + if [ "$(id -u)" != "0" ]; then + colorized_echo yellow "$SERVICE_NAME is installed; run as root to update/restart it." + return + fi + install_node_service_script + systemctl daemon-reload + systemctl restart "$SERVICE_NAME" + colorized_echo blue "$SERVICE_NAME service updated and restarted." +} + detect_os() { # Detect the operating system if [ -f /etc/lsb-release ]; then @@ -209,6 +260,15 @@ install_node_script() { colorized_echo green "node script installed successfully at $TARGET_PATH" } +install_node_service_script() { + set_service_paths + colorized_echo blue "Installing node service script" + curl -sSL $SERVICE_SCRIPT_URL -o "$SERVICE_SCRIPT_PATH" + sed -i "s/^APP_NAME=.*/APP_NAME=\"$APP_NAME\"/" "$SERVICE_SCRIPT_PATH" + chmod 755 "$SERVICE_SCRIPT_PATH" + colorized_echo green "node service script installed successfully at $SERVICE_SCRIPT_PATH" +} + # Get a list of occupied ports get_occupied_ports() { if command -v ss &>/dev/null; then @@ -493,6 +553,14 @@ uninstall_node_script() { fi } +uninstall_node_service_script() { + set_service_paths + if [ -f "$SERVICE_SCRIPT_PATH" ]; then + colorized_echo yellow "Removing node service script" + rm "$SERVICE_SCRIPT_PATH" + fi +} + uninstall_node() { if [ -d "$APP_DIR" ]; then colorized_echo yellow "Removing directory: $APP_DIR" @@ -554,6 +622,13 @@ is_node_installed() { fi } +ensure_env_exists() { + if [ ! -f "$ENV_FILE" ]; then + colorized_echo red "Environment file not found at $ENV_FILE. Please install the node first." + exit 1 + fi +} + is_node_up() { if [ -z "$($COMPOSE -f $COMPOSE_FILE ps -q -a)" ]; then return 1 @@ -686,6 +761,18 @@ install_command() { up_node show_node_logs + local install_service_choice="" + if [ "$AUTO_CONFIRM" = true ]; then + install_service_choice="y" + else + read -p "Do you want to install and start the systemd service for $APP_NAME? (Y/n): " install_service_choice + fi + if [[ -z "$install_service_choice" || "$install_service_choice" =~ ^[Yy]$ ]]; then + install_service_command + else + colorized_echo yellow "Skipped installing systemd service for $APP_NAME." + fi + colorized_echo blue "================================" colorized_echo magenta " node is set up with the following IP: $NODE_IP and Port: $SERVICE_PORT." colorized_echo magenta "Please use the following Certificate in pasarguard Panel (it's located in ${DATA_DIR}/certs):" @@ -717,6 +804,9 @@ uninstall_command() { if is_node_up; then down_node fi + if service_installed; then + uninstall_service_command + fi uninstall_completion uninstall_node_script uninstall_node @@ -837,9 +927,126 @@ restart_command() { down_node up_node + restart_service_if_installed } +install_service_command() { + check_running_as_root + require_systemd + set_service_paths + + detect_os + if ! command -v jq >/dev/null 2>&1; then + install_package jq + fi + + if ! is_node_installed; then + colorized_echo red "node not installed! Install it before setting up the service." + exit 1 + fi + + ensure_env_exists + + get_occupied_ports + local random_api_port existing_api_port="" + if existing_api_port=$(grep -E '^API_PORT[[:space:]]*=' "$ENV_FILE" | head -n1 | sed 's/^API_PORT[[:space:]]*=[[:space:]]*//'); then + existing_api_port=$(echo "$existing_api_port" | tr -d '"'\') + fi + + if [[ "$existing_api_port" =~ ^[0-9]+$ ]] && [ "$existing_api_port" -ge 1 ] && [ "$existing_api_port" -le 65535 ]; then + if is_port_occupied "$existing_api_port"; then + colorized_echo yellow "Existing API_PORT $existing_api_port is already in use. Selecting a new random port." + else + random_api_port="$existing_api_port" + colorized_echo blue "Keeping existing API_PORT: $random_api_port (available)." + fi + fi + + if [ -z "$random_api_port" ]; then + while true; do + random_api_port=$(shuf -i 20000-65000 -n1) + if ! is_port_occupied "$random_api_port"; then + break + fi + done + colorized_echo blue "API_PORT set to random available port: $random_api_port" + fi + + if grep -q '^API_PORT[[:space:]]*=' "$ENV_FILE"; then + sed -i "s/^API_PORT[[:space:]]*=.*/API_PORT= ${random_api_port}/" "$ENV_FILE" + else + echo "API_PORT= ${random_api_port}" >>"$ENV_FILE" + fi + + install_node_service_script + + colorized_echo blue "Creating systemd unit at $SERVICE_UNIT" + cat >"$SERVICE_UNIT" </dev/null 2>&1 || true + systemctl disable "$SERVICE_NAME" >/dev/null 2>&1 || true + + if [ -f "$SERVICE_UNIT" ]; then + colorized_echo yellow "Removing systemd unit $SERVICE_UNIT" + rm "$SERVICE_UNIT" + fi + + uninstall_node_service_script + + systemctl daemon-reload + colorized_echo green "$SERVICE_NAME service uninstalled." +} + +restart_service_command() { + check_running_as_root + require_systemd + if ! service_installed; then + colorized_echo red "Service not installed. Run service-install first." + exit 1 + fi + restart_service_if_installed +} + +status_service_command() { + require_systemd + if ! service_installed; then + colorized_echo red "Service not installed. Run service-install first." + exit 1 + fi + + systemctl status --no-pager "$SERVICE_NAME" +} + status_command() { # Check if node is installed if ! is_node_installed; then @@ -942,6 +1149,7 @@ update_command() { colorized_echo blue "Restarting node services" down_node up_node + update_service_if_installed colorized_echo blue "node updated successfully" } @@ -1317,7 +1525,7 @@ _node_completions() local cur cmds COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" - cmds="up down restart status logs install update uninstall install-script uninstall-script core-update geofiles edit edit-env completion" + cmds="up down restart status logs install update uninstall install-script uninstall-script core-update geofiles edit edit-env completion service-install service-uninstall service-restart service-status" COMPREPLY=( $(compgen -W "$cmds" -- "$cur") ) return 0 } @@ -1366,6 +1574,10 @@ usage() { colorized_echo yellow " uninstall $(tput sgr0)– Uninstall node" colorized_echo yellow " install-script $(tput sgr0)– Install node script" colorized_echo yellow " uninstall-script $(tput sgr0)– Uninstall node script" + colorized_echo yellow " service-install $(tput sgr0)– Install and start pg-node-service (systemd)" + colorized_echo yellow " service-uninstall $(tput sgr0)– Remove pg-node-service (systemd)" + colorized_echo yellow " service-restart $(tput sgr0)– Restart pg-node-service (systemd)" + colorized_echo yellow " service-status $(tput sgr0)– Show pg-node-service status" colorized_echo yellow " edit $(tput sgr0)– Edit docker-compose.yml (via nano or vi)" colorized_echo yellow " edit-env $(tput sgr0)– Edit .env file (via nano or vi)" colorized_echo yellow " core-update $(tput sgr0)– Update/Change Xray core" @@ -1517,6 +1729,18 @@ install-script) uninstall-script) uninstall_node_script ;; +service-install) + install_service_command + ;; +service-uninstall) + uninstall_service_command + ;; +service-restart) + restart_service_command + ;; +service-status) + status_service_command + ;; edit) edit_command ;; From 75ce4c6055890755f8349d9e86721a0e77b927b5 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Wed, 26 Nov 2025 14:52:28 +0330 Subject: [PATCH 5/9] feat: Add firewall configuration hint and log selected API port in pg-node script --- pg-node.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pg-node.sh b/pg-node.sh index 19acc7c..8408ed8 100755 --- a/pg-node.sh +++ b/pg-node.sh @@ -166,6 +166,13 @@ update_service_if_installed() { colorized_echo blue "$SERVICE_NAME service updated and restarted." } +configure_firewall_for_port() { + local port="$1" + local proto="${2:-tcp}" + local hint="If a firewall is enabled (e.g., UFW or firewalld), allow ${port}/${proto}." + colorized_echo yellow "$hint" +} + detect_os() { # Detect the operating system if [ -f /etc/lsb-release ]; then @@ -749,7 +756,7 @@ install_command() { install_node "$node_version" echo "Installing $node_version version" else - echo "Version $node_version does not exist. Please enter a valid version (e.g. v0.5.2)" + echo "Version $node_version does not exist. Please enter a valid version (e.g. v0.1.2)" exit 1 fi else @@ -978,6 +985,8 @@ install_service_command() { else echo "API_PORT= ${random_api_port}" >>"$ENV_FILE" fi + colorized_echo magenta "API_PORT selected: ${random_api_port}" + configure_firewall_for_port "$random_api_port" "tcp" install_node_service_script From be2530611722c264d6a4fd551f7f9bc2c4f04d4c Mon Sep 17 00:00:00 2001 From: Mohammad Date: Wed, 26 Nov 2025 20:37:51 +0330 Subject: [PATCH 6/9] fix: update code and do some code rabit suggests --- pg-node-service.sh | 9 ++++++++- pg-node.sh | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pg-node-service.sh b/pg-node-service.sh index 0796655..d8fa3cc 100755 --- a/pg-node-service.sh +++ b/pg-node-service.sh @@ -103,7 +103,11 @@ respond() { } handle_node_update(){ log "Executing $APP_NAME update" - $APP_NAME update + if ! $APP_NAME update 2>&1; then + log "update failed with exit code: $?" + respond 500 '{"detail":"update failed on server"}' + return + fi respond 200 "{\"detail\":\"node updated successfully\"}" } @@ -207,4 +211,7 @@ while true; do exec {OPENSSL[0]}>&- exec {OPENSSL[1]}>&- wait "$OPENSSL_PID" 2>/dev/null || true + # Ensure cleanup even if wait fails + kill "$OPENSSL_PID" 2>/dev/null || true + sleep 0.1 # Brief pause to avoid rapid respawn storms done diff --git a/pg-node.sh b/pg-node.sh index 8408ed8..4e11b12 100755 --- a/pg-node.sh +++ b/pg-node.sh @@ -1003,6 +1003,10 @@ ExecStart=$SERVICE_SCRIPT_PATH WorkingDirectory=$APP_DIR Restart=on-failure RestartSec=5 +StartLimitInterval=600 +StartLimitBurst=3 +TimeoutStartSec=30 +TimeoutStopSec=10 Environment="ENV_FILE=$ENV_FILE" [Install] From a32ac859f251450b9e3bca813025640b1e483a4b Mon Sep 17 00:00:00 2001 From: Mohammad Date: Thu, 27 Nov 2025 10:58:41 +0330 Subject: [PATCH 7/9] feat: Enhance API port selection and validation in pg-node script --- pg-node.sh | 53 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/pg-node.sh b/pg-node.sh index 4e11b12..b284813 100755 --- a/pg-node.sh +++ b/pg-node.sh @@ -956,37 +956,56 @@ install_service_command() { ensure_env_exists get_occupied_ports - local random_api_port existing_api_port="" + local api_port existing_api_port="" + local default_api_port=62051 if existing_api_port=$(grep -E '^API_PORT[[:space:]]*=' "$ENV_FILE" | head -n1 | sed 's/^API_PORT[[:space:]]*=[[:space:]]*//'); then existing_api_port=$(echo "$existing_api_port" | tr -d '"'\') fi if [[ "$existing_api_port" =~ ^[0-9]+$ ]] && [ "$existing_api_port" -ge 1 ] && [ "$existing_api_port" -le 65535 ]; then - if is_port_occupied "$existing_api_port"; then - colorized_echo yellow "Existing API_PORT $existing_api_port is already in use. Selecting a new random port." - else - random_api_port="$existing_api_port" - colorized_echo blue "Keeping existing API_PORT: $random_api_port (available)." - fi + colorized_echo blue "Existing API_PORT found in $ENV_FILE: $existing_api_port" + default_api_port="$existing_api_port" fi - if [ -z "$random_api_port" ]; then + if [ "$AUTO_CONFIRM" = true ]; then + api_port="$default_api_port" + if is_port_occupied "$api_port"; then + colorized_echo red "Port $api_port is already in use. Run without -y to choose another port." + exit 1 + fi + else while true; do - random_api_port=$(shuf -i 20000-65000 -n1) - if ! is_port_occupied "$random_api_port"; then - break + read -p "Enter the API_PORT for node service (default ${default_api_port}): " -r api_port + if [[ -z "$api_port" ]]; then + api_port="$default_api_port" + fi + if [[ "$api_port" =~ ^[0-9]+$ && "$api_port" -ge 1 && "$api_port" -le 65535 ]]; then + if is_port_occupied "$api_port"; then + colorized_echo red "Port $api_port is already in use. Please enter another port." + else + break + fi + else + colorized_echo red "Invalid port. Please enter a port between 1 and 65535." fi done - colorized_echo blue "API_PORT set to random available port: $random_api_port" fi + local api_port_comment="# API_PORT is used by the node service API (pg-node-service)" if grep -q '^API_PORT[[:space:]]*=' "$ENV_FILE"; then - sed -i "s/^API_PORT[[:space:]]*=.*/API_PORT= ${random_api_port}/" "$ENV_FILE" + sed -i "s/^API_PORT[[:space:]]*=.*/API_PORT= ${api_port}/" "$ENV_FILE" + if ! grep -q '^# *API_PORT' "$ENV_FILE"; then + sed -i "/^API_PORT[[:space:]]*=.*/i ${api_port_comment}" "$ENV_FILE" + fi else - echo "API_PORT= ${random_api_port}" >>"$ENV_FILE" - fi - colorized_echo magenta "API_PORT selected: ${random_api_port}" - configure_firewall_for_port "$random_api_port" "tcp" + { + echo "" + echo "$api_port_comment" + echo "API_PORT= ${api_port}" + } >>"$ENV_FILE" + fi + colorized_echo magenta "API_PORT selected: ${api_port}" + configure_firewall_for_port "$api_port" "tcp" install_node_service_script From e90dd7283142a2f2ef696b7fa7a57b169b61c748 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Thu, 27 Nov 2025 17:18:55 +0330 Subject: [PATCH 8/9] feat: Add geofiles update handling with region validation in pg-node service --- pg-node-service.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/pg-node-service.sh b/pg-node-service.sh index d8fa3cc..072aec1 100755 --- a/pg-node-service.sh +++ b/pg-node-service.sh @@ -140,6 +140,49 @@ handle_node_core_update(){ fi } +handle_geofiles_update(){ + local body="${1:-}" + local region="" flag="" + + if [[ -n "$body" ]]; then + if ! command -v jq >/dev/null 2>&1; then + log "jq is required to parse region from request body" + respond 500 '{"detail":"jq not installed on server"}' + return + fi + + if ! region=$(printf '%s' "$body" | jq -r '.region // empty' 2>/dev/null); then + log "Failed to parse JSON body for region" + respond 400 '{"detail":"Invalid JSON body"}' + return + fi + fi + + if [[ -z "$region" ]]; then + respond 400 '{"detail":"region is required (iran, russia, china)"}' + return + fi + + case "${region,,}" in + iran) flag="--iran" ;; + russia) flag="--russia" ;; + china) flag="--china" ;; + *) + log "Invalid region provided: $region" + respond 400 "{\"detail\":\"Unsupported region $(json_escape "$region")\"}" + return + ;; + esac + + log "Executing $APP_NAME geofiles $flag" + if $APP_NAME geofiles "$flag"; then + respond 200 '{"detail":"geofiles updated successfully"}' + else + log "geofiles update failed" + respond 500 '{"detail":"geofiles update failed on server"}' + fi +} + handle_connection() { local request_line method path version if ! IFS= read -r request_line; then @@ -166,12 +209,12 @@ handle_connection() { done if [[ -z "$x_api_key" ]]; then - respond 401 '{"error":"missing api key"}' + respond 401 '{"detail":"missing api key"}' log "Unauthorized: missing x-api-key for $method $path" return 0 fi if [[ "$x_api_key" != "$API_KEY" ]]; then - respond 401 '{"error":"invalid api key"}' + respond 401 '{"detail":"invalid api key"}' log "Unauthorized: invalid x-api-key for $method $path" return 0 fi @@ -197,6 +240,9 @@ handle_connection() { "POST /node/core_update") handle_node_core_update "$body" ;; + "POST /node/geofiles") + handle_geofiles_update "$body" + ;; *) respond 404 '{"detail":"Not found"}' ;; From b00c5d1d61ecac5272e6b4e9379c42bdf49e31bc Mon Sep 17 00:00:00 2001 From: Mohammad Date: Fri, 28 Nov 2025 07:45:36 +0330 Subject: [PATCH 9/9] chore: update response --- pg-node-service.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg-node-service.sh b/pg-node-service.sh index 072aec1..f92caaf 100755 --- a/pg-node-service.sh +++ b/pg-node-service.sh @@ -222,7 +222,7 @@ handle_connection() { local body="" if (( content_length > 0 )); then if (( content_length > MAX_BODY )); then - respond 400 '{"error":"Payload too large"}' + respond 400 '{"default":"Payload too large"}' log "Body rejected: $content_length bytes (too large)" return 0 fi