From 6bd2c7ea527bd2028ba3594022766fe8b1358c8a Mon Sep 17 00:00:00 2001 From: Russel Yasol Date: Wed, 27 Aug 2025 11:31:00 +0800 Subject: [PATCH] Official Hot Fix for QuecManager 2.3.0 --- .../quecmanager/at_cmd/at_queue_client.sh | 60 ++- .../cgi-bin/quecmanager/at_cmd/fetch_data.sh | 95 +++-- .../root/www/cgi-bin/quecmanager/auth.sh | 40 +- .../experimental/cell_scanner/clear_scan.sh | 99 +++++ .../experimental/fetch_interpretations.sh | 20 + .../experimental/keep_alive_reworked.sh | 269 +++++++++++++ .../experimental/logs/fetch_logs.sh | 220 ++++++++++ .../experimental/scheduled_reboot.sh | 251 ++++++++++++ .../quecmanager/home/fetch_hw_details.sh | 86 ++-- .../quecmanager/home/fetch_public_ip.sh | 9 + .../quecmanager/home/memory/fetch_memory.sh | 59 +++ .../quecmanager/home/memory/memory_service.sh | 78 ++++ .../quecmanager/home/ping/fetch_ping.sh | 55 +++ .../quecmanager/home/ping/ping_service.sh | 62 +++ .../root/www/cgi-bin/quecmanager/logout.sh | 15 + .../quecmanager/profiles/check_status.sh | 10 +- .../quecmanager/profiles/list_profiles.sh | 35 +- .../profiles/quec_profile_create.sh | 13 +- .../profiles/quec_profile_delete.sh | 20 +- .../quecmanager/profiles/quec_profile_edit.sh | 15 +- .../quecmanager/profiles/toggle_pause.sh | 4 +- .../cgi-bin/quecmanager/reset-at-bridge.sh | 25 ++ .../quecmanager/settings/change-password.sh | 110 +++++ .../quecmanager/settings/force-reboot.sh | 34 ++ .../quecmanager/settings/measurement_units.sh | 375 ++++++++++++++++++ .../quecmanager/settings/memory_settings.sh | 301 ++++++++++++++ .../quecmanager/settings/ping_settings.sh | 330 +++++++++++++++ .../quecmanager/settings/profile_picture.sh | 193 +++++++++ .../www/cgi-bin/services/at_queue_manager.sh | 135 ++++--- .../root/www/cgi-bin/services/cleanup_logs.sh | 110 +++++ .../www/cgi-bin/services/interpret_qcainfo.sh | 227 +++++++++++ .../cgi-bin/services/log_signal_metrics.sh | 18 +- .../www/cgi-bin/services/memory_daemon.sh | 201 ++++++++++ .../services/network_insights_interpreter.sh | 372 +++++++++++++++++ .../root/www/cgi-bin/services/ping_daemon.sh | 137 +++++++ .../cgi-bin/services/quecmanager_logger.sh | 119 ++++++ .../root/www/cgi-bin/services/quecprofile.sh | 105 ++++- .../root/www/cgi-bin/services/quecwatch.sh | 28 +- 38 files changed, 4133 insertions(+), 202 deletions(-) create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/cell_scanner/clear_scan.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/fetch_interpretations.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/keep_alive_reworked.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/logs/fetch_logs.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/scheduled_reboot.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/memory/fetch_memory.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/memory/memory_service.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/ping/fetch_ping.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/ping/ping_service.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/logout.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/reset-at-bridge.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/change-password.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/force-reboot.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/measurement_units.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/memory_settings.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/ping_settings.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/profile_picture.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/cleanup_logs.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/interpret_qcainfo.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/memory_daemon.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/network_insights_interpreter.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/ping_daemon.sh create mode 100644 ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecmanager_logger.sh diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/at_cmd/at_queue_client.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/at_cmd/at_queue_client.sh index 3ae94a4..c879c7f 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/at_cmd/at_queue_client.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/at_cmd/at_queue_client.sh @@ -2,11 +2,40 @@ # AT Queue Client for OpenWRT # Located in /www/cgi-bin/services/at_queue_client +# Load centralized logging +. /www/cgi-bin/services/quecmanager_logger.sh + AUTH_FILE="/tmp/auth_success" QUEUE_DIR="/tmp/at_queue" RESULTS_DIR="$QUEUE_DIR/results" QUEUE_MANAGER="/www/cgi-bin/services/at_queue_manager.sh" POLL_INTERVAL=0.01 +SCRIPT_NAME_LOG="at_queue_client" + +# Logging function - uses both centralized and system logging +log_at_queue_client() { + local level="$1" + local message="$2" + + # Use centralized logging + case "$level" in + "error") + qm_log_error "service" "$SCRIPT_NAME_LOG" "$message" + ;; + "warn") + qm_log_warn "service" "$SCRIPT_NAME_LOG" "$message" + ;; + "debug") + qm_log_debug "service" "$SCRIPT_NAME_LOG" "$message" + ;; + *) + qm_log_info "service" "$SCRIPT_NAME_LOG" "$message" + ;; + esac + + # Also maintain system logging for compatibility + logger -t at_queue -p "daemon.$level" "$message" +} usage() { echo "Usage: $0 [options] " @@ -20,14 +49,14 @@ usage() { # Output JSON response output_json() { local content="$1" - local headers="${2:-1}" # Default to showing headers + local headers="${2:-1}" # Default to showing headers echo "$content" } # URL decode function urldecode() { local encoded="$1" - logger -t at_queue -p daemon.debug "urldecode: input='$encoded'" + log_at_queue_client "debug" "urldecode: input='$encoded'" # Handle %2B -> + and %22 -> " conversions local decoded="${encoded//%2B/+}" @@ -35,10 +64,23 @@ urldecode() { # Then handle other encoded characters decoded=$(printf '%b' "${decoded//%/\\x}") - logger -t at_queue -p daemon.debug "urldecode: output='$decoded'" + log_at_queue_client "debug" "urldecode: output='$decoded'" echo "$decoded" } +# URL encode function (simplified for AT commands) +urlencode() { + local string="$1" + # Simple encoding for common AT command characters + string="${string// /%20}" + string="${string//+/%2B}" + string="${string//\"/%22}" + string="${string//=/%3D}" + string="${string//&/%26}" + string="${string//?/%3F}" + echo "$string" +} + # Extract command ID from response with improved error handling get_command_id() { local response="$1" @@ -72,19 +114,19 @@ get_command_id() { # Normalize AT command normalize_at_command() { local cmd="$1" - logger -t at_queue -p daemon.debug "normalize: input='$cmd'" + log_at_queue_client "debug" "normalize: input='$cmd'" # URL decode the command cmd=$(urldecode "$cmd") - logger -t at_queue -p daemon.debug "normalize: after urldecode='$cmd'" + log_at_queue_client "debug" "normalize: after urldecode='$cmd'" # Remove any carriage returns or newlines cmd=$(echo "$cmd" | tr -d '\r\n') - logger -t at_queue -p daemon.debug "normalize: after cleanup='$cmd'" + log_at_queue_client "debug" "normalize: after cleanup='$cmd'" # Trim leading/trailing whitespace while preserving quotes cmd=$(echo "$cmd" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - logger -t at_queue -p daemon.debug "normalize: final output='$cmd'" + log_at_queue_client "debug" "normalize: final output='$cmd'" echo "$cmd" } @@ -101,7 +143,7 @@ submit_command() { # Submit using appropriate method if [ "${SCRIPT_NAME}" != "" ]; then - # CGI mode - direct execution + # CGI mode - direct execution like the original working version local escaped_cmd=$(echo "$cmd" | sed 's/"/\\"/g') QUERY_STRING="action=enqueue&command=${escaped_cmd}&priority=$priority" "$QUEUE_MANAGER" else @@ -118,7 +160,7 @@ check_result() { if [ -f "$RESULTS_DIR/$cmd_id.json" ]; then local result_content=$(cat "$RESULTS_DIR/$cmd_id.json") if [ -z "$result_content" ]; then - logger -t at_queue -p daemon.error "Empty result file for command ID: $cmd_id" + log_at_queue_client "error" "Empty result file for command ID: $cmd_id" local error_json="{\"error\":\"Empty result file\",\"command_id\":\"$cmd_id\"}" output_json "$error_json" "$show_headers" return 1 diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/at_cmd/fetch_data.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/at_cmd/fetch_data.sh index a44270d..8d5ef18 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/at_cmd/fetch_data.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/at_cmd/fetch_data.sh @@ -1,8 +1,9 @@ #!/bin/sh - +# On SDXPINN and (assumed) SDXLEMUR with OpenWRT Overlay, the environment NEEDS to be /bin/sh, +# whereas QTI environment on SDXLEMUR uses /bin/bash. This assumption requires verification. # Set content-type for JSON response -echo "Content-type: application/json" -echo "" +printf "Content-type: application/json\r\n" +printf "\r\n" # Define paths and constants to match queue system QUEUE_DIR="/tmp/at_queue" @@ -13,11 +14,11 @@ TOKEN_FILE="$QUEUE_DIR/token" # Logging function (minimized) log_message() { # Only log errors and critical info - if [ "$1" = "error" ] || [ "$1" = "crit" ]; then + if [ "$1" = "error" ] || [ "$1" = "crit" ]; then logger -t at_queue -p "daemon.$1" "$2" - fi + fi } - +mkdir -m755 -p ${QUEUE_DIR} # Enhanced JSON string escaping function escape_json() { printf '%s' "$1" | awk ' @@ -36,39 +37,46 @@ escape_json() { # Acquire token directly (avoid CGI overhead) acquire_token() { - local priority="${1:-10}" - local max_attempts=10 - local attempt=0 - + priority="${1:-10}" + max_attempts=10 + attempt=0 + log_message "debug" "Acquiring token" while [ $attempt -lt $max_attempts ]; do # Check if token file exists if [ -f "$TOKEN_FILE" ]; then - local current_holder=$(cat "$TOKEN_FILE" | jsonfilter -e '@.id' 2>/dev/null) - local current_priority=$(cat "$TOKEN_FILE" | jsonfilter -e '@.priority' 2>/dev/null) - local timestamp=$(cat "$TOKEN_FILE" | jsonfilter -e '@.timestamp' 2>/dev/null) - local current_time=$(date +%s) - + current_holder=$(cat "$TOKEN_FILE" | jsonfilter -e '@.id' 2>/dev/null) + current_priority=$(cat "$TOKEN_FILE" | jsonfilter -e '@.priority' 2>/dev/null) + timestamp=$(cat "$TOKEN_FILE" | jsonfilter -e '@.timestamp' 2>/dev/null) + current_time=$(date +%s) + log_message "info" "current_holder: ${current_holder}" + log_message "info" "current_priority: ${current_priority}" + log_message "info" "timestamp: ${timestamp}" + log_message "info" "current_time: ${current_time}" # Check for expired token (> 30 seconds old) if [ $((current_time - timestamp)) -gt 30 ] || [ -z "$current_holder" ]; then # Remove expired token + log_message "debug" "Removing token, cur time minus timestamp gt 30 or current-holder not set" rm -f "$TOKEN_FILE" 2>/dev/null elif [ $priority -lt $current_priority ]; then # Preempt lower priority token + log_message "debug" "Current priority lower priority than other task" rm -f "$TOKEN_FILE" 2>/dev/null else # Try again sleep 0.1 attempt=$((attempt + 1)) + log_message "debug" "Trying again $attempt" continue fi + else + log_message "debug" "No token file" fi - # Try to create token file - echo "{\"id\":\"$LOCK_ID\",\"priority\":$priority,\"timestamp\":$(date +%s)}" >"$TOKEN_FILE" 2>/dev/null + printf "{\"id\":\"$LOCK_ID\",\"priority\":$priority,\"timestamp\":$(date +%s)}" >"$TOKEN_FILE" 2>/dev/null chmod 644 "$TOKEN_FILE" 2>/dev/null # Verify we got the token - local holder=$(cat "$TOKEN_FILE" 2>/dev/null | jsonfilter -e '@.id' 2>/dev/null) + holder=$(cat "$TOKEN_FILE" 2>/dev/null | jsonfilter -e '@.id' 2>/dev/null) if [ "$holder" = "$LOCK_ID" ]; then return 0 fi @@ -79,13 +87,16 @@ acquire_token() { return 1 } - # Release token directly release_token() { + log_message "debug" "Release Token" # Only remove if it's our token if [ -f "$TOKEN_FILE" ]; then - local current_holder=$(cat "$TOKEN_FILE" | jsonfilter -e '@.id' 2>/dev/null) + log_message "debug" "Has Token file" + current_holder=$(cat "$TOKEN_FILE" | jsonfilter -e '@.id' 2>/dev/null) + log_message "debug" "Release Token, Current Holder: ${current_holder}" if [ "$current_holder" = "$LOCK_ID" ]; then + log_message "debug" "Release Token, Current Holder: ${current_holder}, removing token" rm -f "$TOKEN_FILE" 2>/dev/null fi fi @@ -93,18 +104,21 @@ release_token() { # Direct AT command execution with minimal overhead execute_at_command() { - local CMD="$1" + CMD="$1" sms_tool at "$CMD" -t 3 2>/dev/null } # Batch process all commands with a single token process_all_commands() { - local commands="$1" - local priority="${2:-10}" - local first=1 - + commands="$1" + priority="${2:-10}" + first=1 + log_message "info" "Before acquire_token check" + acquire_token "$priority" + trying=$? + log_message "debug" "trying: ${trying}" # Acquire a single token for all commands - if ! acquire_token "$priority"; then + if [ $trying -ne 0 ]; then log_message "error" "Failed to acquire token for batch processing" # Return all failed responses printf '[' @@ -115,7 +129,7 @@ process_all_commands() { ESCAPED_CMD=$(escape_json "$cmd") printf '{"command":"%s","response":"Failed to acquire token","status":"error"}' "${ESCAPED_CMD}" done - printf ']\n' + printf ']\r\n' return 1 fi @@ -124,10 +138,9 @@ process_all_commands() { for cmd in $commands; do [ $first -eq 0 ] && printf ',' first=0 - OUTPUT=$(execute_at_command "$cmd") - local CMD_STATUS=$? - + CMD_STATUS=$? + log_message "debug" "CMD: ${cmd}, OUTPUT: ${OUTPUT}, CMD_STAT: ${CMD_STATUS}" ESCAPED_CMD=$(escape_json "$cmd") ESCAPED_OUTPUT=$(escape_json "$OUTPUT") @@ -140,8 +153,7 @@ process_all_commands() { "${ESCAPED_CMD}" fi done - printf ']\n' - + printf ']\r\n' # Release token after all commands are done release_token return 0 @@ -184,15 +196,14 @@ if echo "$COMMANDS" | grep -qi "AT+QSCAN"; then PRIORITY=1 fi -# Process commands with timeout protection -( - sleep 60 - kill -TERM $$ 2>/dev/null -) & -TIMEOUT_PID=$! +# ( +# sleep 60 +# kill -TERM $$ +# ) & +# TIMEOUT_PID=$! -process_all_commands "$COMMANDS" "$PRIORITY" + process_all_commands "$COMMANDS" "$PRIORITY" + +# kill $TIMEOUT_PID 2>/dev/null + release_token -# Clean up -kill $TIMEOUT_PID 2>/dev/null -release_token diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/auth.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/auth.sh index dc53c38..a481bf0 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/auth.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/auth.sh @@ -9,7 +9,7 @@ read -r POST_DATA # Debug log for generated hash DEBUG_LOG="/tmp/auth.log" - +AUTH_FILE="/tmp/auth_success" # Extract the password from POST data (URL encoded) USER="root" INPUT_PASSWORD=$(echo "$POST_DATA" | grep -o 'password=[^&]*' | cut -d= -f2-) @@ -54,9 +54,43 @@ GENERATED_HASH=$(printf '%s' "$INPUT_PASSWORD" | openssl passwd -1 -salt "$SALT" # Log generated hash for debugging printf "Generated hash: %s\n" "$GENERATED_HASH" >> "$DEBUG_LOG" +# Check if the request for AUTH contains the Authorization Header so as to assure we're not at an initial login +SUPPLIED_TOKEN="${HTTP_AUTHORIZATION}" # Compare the generated hash with the one in the shadow file if [ "$GENERATED_HASH" = "$USER_HASH" ]; then - echo '{"state":"success"}' + # If the token is supplied, use it; otherwise, generate a new one and store it in the auth file + if [ "$SUPPLIED_TOKEN" != "" ]; then + TOKEN="$SUPPLIED_TOKEN" + else + TOKEN=$(head -c 16 /dev/urandom | hexdump -v -e '/1 "%02x"') + CREATED_DATE=$(date +"%Y-%m-%dT%H:%M:%S") + touch ${AUTH_FILE} + echo "${CREATED_DATE} ${TOKEN}" >> ${AUTH_FILE} + echo "" >> ${AUTH_FILE} + fi + echo "{\"state\":\"success\",\"token\":\"${TOKEN}\"}" else + # Remove token from file + if [ -n ${TOKEN} ]; then + sed -i -e "s/.*${TOKEN}.*//g" ${AUTH_FILE} 2>/dev/null + fi echo '{"state":"failed", "message":"Authentication failed"}' -fi \ No newline at end of file +fi + +# AUTH_FILE cleanup process, Remove any token lines older than 2 hours from AUTH_FILE +MAX_AGE=$((2 * 3600)) # 2 hours in seconds +NOW_TIME=$(date +%s) +TMP_FILE=$(mktemp) +while read -r line; do + if [ -n "$(echo "$line" | tr -d '[:space:]')" ]; then + # Extract the date from the line and convert it to a timestamp + TOKEN_DATE=$(echo "$line" | awk '{print $1}' | sed 's/T/ /') + TOKEN_TIME=$(date -d "$TOKEN_DATE" +%s 2>/dev/null) + # If date is valid and not older than MAX_AGE, keep the line + if [ -n "$TOKEN_TIME" ] && [ $((NOW_TIME - TOKEN_TIME)) -le $MAX_AGE ]; then + echo "$line" >> "$TMP_FILE" + fi + fi +done < "$AUTH_FILE" + +mv "$TMP_FILE" "$AUTH_FILE" \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/cell_scanner/clear_scan.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/cell_scanner/clear_scan.sh new file mode 100644 index 0000000..72ebc64 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/cell_scanner/clear_scan.sh @@ -0,0 +1,99 @@ +#!/bin/sh + +# Set content type to JSON +echo "Content-type: application/json" +echo "" + +# Configuration +QUEUE_DIR="/tmp/at_queue" +RESULTS_DIR="$QUEUE_DIR/results" +RESULT_FILE="/tmp/qscan_result.json" +PID_FILE="/tmp/cell_scan.pid" +TOKEN_FILE="$QUEUE_DIR/token" + +# Function to log messages +log_message() { + local level="${2:-info}" + logger -t at_queue -p "daemon.$level" "check_scan: $1" +} + +# Function to output JSON response +output_json() { + local status="$1" + local message="$2" + + if [ "$status" = "success" ] && [ -f "$RESULT_FILE" ]; then + # Return the contents of the result file + cat "$RESULT_FILE" + else + printf '{"status":"%s","message":"%s","timestamp":"","output":""}\n' "$status" "$message" + fi +} + +# Check for scan token holder +check_token_holder() { + if [ -f "$TOKEN_FILE" ]; then + local current_holder=$(cat "$TOKEN_FILE" | jsonfilter -e '@.id' 2>/dev/null) + if [ -n "$current_holder" ] && echo "$current_holder" | grep -q "CELL_SCAN"; then + log_message "Cell scan token is active: $current_holder" "debug" + return 0 + fi + fi + return 1 +} + +# Check if a scan is already in progress +check_scan_progress() { + # First check PID file + if [ -f "$PID_FILE" ]; then + pid=$(cat "$PID_FILE") + if kill -0 "$pid" 2>/dev/null; then + log_message "Scan in progress (PID: $pid)" "info" + output_json "running" "Scan in progress" + exit 0 + else + log_message "Removing stale PID file" "warn" + rm -f "$PID_FILE" + fi + fi + + # Also check token holder + if check_token_holder; then + log_message "Scan in progress (Token active)" "info" + output_json "running" "Scan in progress (Token active)" + exit 0 + fi +} + +# Check for existing results +check_results() { + if [ -f "$RESULT_FILE" ]; then + rm -f "$RESULT_FILE" # Remove the result file if it exists + log_message "Result file removed" "info" + output_json "success" "Scan results removed" + exit 0 + else + log_message "No result file found to clear" "info" + output_json "success" "No result file to clear" + exit 0 + fi +} + +# Main execution +{ + # First check if a scan is in progress + check_scan_progress + + # Then check for existing results + check_results + + # If no results and no running scan, indicate idle state + log_message "No active scan or recent results" "info" + output_json "success" "No active scan" + exit 0 +} || { + # Error handler + log_message "Failed to remove scan results" "error" + output_json "error" "Failed to remove scan results" + exit 1 +} \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/fetch_interpretations.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/fetch_interpretations.sh new file mode 100644 index 0000000..727c38b --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/fetch_interpretations.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Simple script to fetch interpreted QCAINFO results + +INTERPRETED_FILE="/tmp/interpreted_result.json" + +# Set content type for JSON +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, POST, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Check if file exists +if [ ! -f "$INTERPRETED_FILE" ]; then + echo "[]" + exit 0 +fi + +# Return the JSON content +cat "$INTERPRETED_FILE" diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/keep_alive_reworked.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/keep_alive_reworked.sh new file mode 100644 index 0000000..9b91bb6 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/keep_alive_reworked.sh @@ -0,0 +1,269 @@ +#!/bin/sh + +# Keep-Alive Scheduling Script +# This script allows scheduling of keep-alive requests to prevent the connection from being closed. +# It supports setting a time interval during which the keep-alive requests will be made. +# It uses a worker script to perform the actual keep-alive requests by downloading a test file. + +# Configuration +CONFIG_FILE="/etc/keep_alive_schedule.conf" +STATUS_FILE="/tmp/keep_alive_status" +KEEP_ALIVE_SCRIPT="/www/cgi-bin/quecmanager/experimental/keep_alive_worker.sh" +TEST_URL="https://ash-speed.hetzner.com/100MB.bin" +TEMP_FILE="/tmp/keep_alive_test.bin" + +# Function to convert HH:MM to minutes since midnight +time_to_minutes() { + echo "$1" | awk -F: '{print $1 * 60 + $2}' +} + +# Function to validate time interval +validate_interval() { + START_TIME=$1 + END_TIME=$2 + INTERVAL_MINUTES=$3 + + # Convert times to minutes + START_MINUTES=$(time_to_minutes "$START_TIME") + END_MINUTES=$(time_to_minutes "$END_TIME") + + # Calculate duration between start and end time + if [ $END_MINUTES -lt $START_MINUTES ]; then + # Handle case where end time is on the next day + DURATION=$((1440 - START_MINUTES + END_MINUTES)) + else + DURATION=$((END_MINUTES - START_MINUTES)) + fi + + # Check if interval is longer than duration + if [ $INTERVAL_MINUTES -gt $DURATION ]; then + return 1 + fi + return 0 +} + +# Function to create the keep-alive worker script +create_worker_script() { + cat > "$KEEP_ALIVE_SCRIPT" << 'EOF' +#!/bin/sh + +TEST_URL="https://ash-speed.hetzner.com/100MB.bin" +TEMP_FILE="/tmp/keep_alive_test.bin" + +# Function to perform keep-alive test +perform_keep_alive() { + # Download the test file in background + wget -q -O "$TEMP_FILE" "$TEST_URL" & + WGET_PID=$! + + # Wait for download to complete or timeout after 30 seconds + COUNTER=0 + while [ $COUNTER -lt 30 ]; do + if ! kill -0 $WGET_PID 2>/dev/null; then + break + fi + sleep 1 + COUNTER=$((COUNTER + 1)) + done + + # If download is still running, kill it + if kill -0 $WGET_PID 2>/dev/null; then + kill $WGET_PID 2>/dev/null + fi + + # Wait 3 seconds then delete the file + sleep 3 + #rm -f "$TEMP_FILE" + + # Log the activity + echo "$(date): Keep-alive test performed" >> /tmp/keep_alive.log +} + +# Execute the keep-alive test +perform_keep_alive +EOF + chmod +x "$KEEP_ALIVE_SCRIPT" +} + +# Function to generate cron time expression +generate_cron_time() { + START_TIME=$1 + END_TIME=$2 + INTERVAL=$3 + + START_HOUR=$(echo "$START_TIME" | cut -d: -f1 | sed 's/^0//') + START_MIN=$(echo "$START_TIME" | cut -d: -f2) + END_HOUR=$(echo "$END_TIME" | cut -d: -f1 | sed 's/^0//') + END_MIN=$(echo "$END_TIME" | cut -d: -f2) + + # If end time is less than start time, it means we cross midnight + if [ $(time_to_minutes "$END_TIME") -lt $(time_to_minutes "$START_TIME") ]; then + # Create two cron entries for before and after midnight + echo "*/$INTERVAL $START_HOUR-23 * * * $KEEP_ALIVE_SCRIPT" + echo "*/$INTERVAL 0-$((END_HOUR - 1)) * * * $KEEP_ALIVE_SCRIPT" + else + echo "*/$INTERVAL $START_HOUR-$((END_HOUR - 1)) * * * $KEEP_ALIVE_SCRIPT" + fi +} + +# Function to urldecode +urldecode() { + echo -e "$(echo "$1" | sed 's/+/ /g;s/%\([0-9A-F][0-9A-F]\)/\\x\1/g')" +} + +# Function to save configuration +save_config() { + echo "START_TIME=$1" >"$CONFIG_FILE" + echo "END_TIME=$2" >>"$CONFIG_FILE" + echo "INTERVAL=$3" >>"$CONFIG_FILE" + echo "ENABLED=1" >>"$CONFIG_FILE" +} + +# Function to disable scheduling +disable_scheduling() { + if [ -f "$CONFIG_FILE" ]; then + sed -i 's/ENABLED=1/ENABLED=0/' "$CONFIG_FILE" + fi + # Remove any existing cron jobs + crontab -l | grep -v "$KEEP_ALIVE_SCRIPT" | crontab - + # Clean up temporary files + rm -f "$TEMP_FILE" + rm -f "$KEEP_ALIVE_SCRIPT" +} + +# Function to get current status +get_status() { + if [ -f "$CONFIG_FILE" ]; then + ENABLED=$(grep "ENABLED=" "$CONFIG_FILE" | cut -d'=' -f2) + START_TIME=$(grep "START_TIME=" "$CONFIG_FILE" | cut -d'=' -f2) + END_TIME=$(grep "END_TIME=" "$CONFIG_FILE" | cut -d'=' -f2) + INTERVAL=$(grep "INTERVAL=" "$CONFIG_FILE" | cut -d'=' -f2) + + # Check if log file exists and get last activity + LAST_ACTIVITY="" + if [ -f "/tmp/keep_alive.log" ]; then + LAST_ACTIVITY=$(tail -n 1 /tmp/keep_alive.log | cut -d: -f1-3) + fi + + echo "Status: 200 OK" + echo "Content-Type: application/json" + echo "" + echo "{\"enabled\":$ENABLED,\"start_time\":\"$START_TIME\",\"end_time\":\"$END_TIME\",\"interval\":$INTERVAL,\"last_activity\":\"$LAST_ACTIVITY\"}" + else + echo "Status: 200 OK" + echo "Content-Type: application/json" + echo "" + echo "{\"enabled\":0,\"start_time\":\"\",\"end_time\":\"\",\"interval\":0,\"last_activity\":\"\"}" + fi +} + +# Handle POST requests +if [ "$REQUEST_METHOD" = "POST" ]; then + # Read POST data + read -r POST_DATA + + # Check if disabling is requested + echo "$POST_DATA" | grep -q "disable=true" + if [ $? -eq 0 ]; then + disable_scheduling + echo "Status: 200 OK" + echo "Content-Type: application/json" + echo "" + echo "{\"status\":\"success\",\"message\":\"Keep-alive scheduling disabled\"}" + exit 0 + fi + + # Extract times and interval + START_TIME=$(echo "$POST_DATA" | grep -o 'start_time=[^&]*' | cut -d'=' -f2) + END_TIME=$(echo "$POST_DATA" | grep -o 'end_time=[^&]*' | cut -d'=' -f2) + INTERVAL=$(echo "$POST_DATA" | grep -o 'interval=[^&]*' | cut -d'=' -f2) + + # Decode times + START_TIME=$(urldecode "$START_TIME") + END_TIME=$(urldecode "$END_TIME") + INTERVAL=$(urldecode "$INTERVAL") + + # Validate times + if [ -z "$START_TIME" ] || [ -z "$END_TIME" ] || [ -z "$INTERVAL" ]; then + echo "Status: 400 Bad Request" + echo "Content-Type: application/json" + echo "" + echo "{\"error\":\"Missing start time, end time, or interval\"}" + exit 1 + fi + + # Validate interval is a number + if ! echo "$INTERVAL" | grep -q '^[0-9]\+$'; then + echo "Status: 400 Bad Request" + echo "Content-Type: application/json" + echo "" + echo "{\"error\":\"Interval must be a number in minutes\"}" + exit 1 + fi + + # Validate interval (minimum 5 minutes to avoid too frequent requests) + if [ "$INTERVAL" -lt 5 ]; then + echo "Status: 400 Bad Request" + echo "Content-Type: application/json" + echo "" + echo "{\"error\":\"Interval must be at least 5 minutes\"}" + exit 1 + fi + + # Validate interval + if ! validate_interval "$START_TIME" "$END_TIME" "$INTERVAL"; then + echo "Status: 400 Bad Request" + echo "Content-Type: application/json" + echo "" + echo "{\"error\":\"Interval is longer than the time between start and end time\"}" + exit 1 + fi + + # Create the worker script + create_worker_script + + # Create temporary file for new crontab + TEMP_CRON=$(mktemp) + + # Get existing crontab entries (excluding our script) + crontab -l 2>/dev/null | grep -v "$KEEP_ALIVE_SCRIPT" >"$TEMP_CRON" + + # Generate and add cron entries + generate_cron_time "$START_TIME" "$END_TIME" "$INTERVAL" >>"$TEMP_CRON" + + # Install new crontab + crontab "$TEMP_CRON" + rm "$TEMP_CRON" + + # Save configuration + save_config "$START_TIME" "$END_TIME" "$INTERVAL" + + # Initialize log file + echo "$(date): Keep-alive scheduling enabled" > /tmp/keep_alive.log + + echo "Status: 200 OK" + echo "Content-Type: application/json" + echo "" + echo "{\"status\":\"success\",\"message\":\"Keep-alive scheduling enabled with download method\"}" + exit 0 +fi + +# Parse query string for GET requests +if [ "$REQUEST_METHOD" = "GET" ]; then + QUERY_STRING=$(echo "$QUERY_STRING" | sed 's/&/\n/g') + for param in $QUERY_STRING; do + case "$param" in + status=*) + get_status + exit 0 + ;; + esac + done +fi + +# If no valid request is made +echo "Status: 400 Bad Request" +echo "Content-Type: application/json" +echo "" +echo "{\"error\":\"Invalid request\"}" +exit 1 \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/logs/fetch_logs.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/logs/fetch_logs.sh new file mode 100644 index 0000000..ce54cca --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/logs/fetch_logs.sh @@ -0,0 +1,220 @@ +#!/bin/sh + +# QuecManager Log Viewer API +# Provides centralized log access for the web interface + +. /www/cgi-bin/services/quecmanager_logger.sh + +# CGI Headers +printf "Content-Type: application/json\r\n" +printf "Access-Control-Allow-Origin: *\r\n" +printf "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n" +printf "Access-Control-Allow-Headers: Content-Type\r\n" +printf "\r\n" + +# Initialize logs if needed +qm_init_logs + +# Parse query parameters +QUERY_STRING="${QUERY_STRING:-}" +CATEGORY="" +SCRIPT="" +LEVEL="" +LINES="50" +SINCE="" + +# Simple parameter parsing +if [ -n "$QUERY_STRING" ]; then + for param in $(echo "$QUERY_STRING" | tr '&' ' '); do + case "$param" in + category=*) + CATEGORY=$(echo "$param" | cut -d'=' -f2 | sed 's/%20/ /g' | tr -d '"') + ;; + script=*) + SCRIPT=$(echo "$param" | cut -d'=' -f2 | sed 's/%20/ /g' | tr -d '"') + ;; + level=*) + LEVEL=$(echo "$param" | cut -d'=' -f2 | sed 's/%20/ /g' | tr -d '"') + ;; + lines=*) + LINES=$(echo "$param" | cut -d'=' -f2 | tr -d '"') + ;; + since=*) + SINCE=$(echo "$param" | cut -d'=' -f2 | sed 's/%20/ /g' | tr -d '"') + ;; + esac + done +fi + +# Validate lines parameter +if ! echo "$LINES" | grep -qE '^[0-9]+$' || [ "$LINES" -gt 1000 ]; then + LINES="50" +fi + +# Function to get available categories +get_categories() { + printf '{\n' + printf ' "categories": [\n' + if [ -d "$QM_LOG_DAEMONS" ]; then + printf ' "daemons"' + [ -d "$QM_LOG_SERVICES" ] || [ -d "$QM_LOG_SETTINGS" ] || [ -d "$QM_LOG_SYSTEM" ] && printf ',' + printf '\n' + fi + if [ -d "$QM_LOG_SERVICES" ]; then + printf ' "services"' + [ -d "$QM_LOG_SETTINGS" ] || [ -d "$QM_LOG_SYSTEM" ] && printf ',' + printf '\n' + fi + if [ -d "$QM_LOG_SETTINGS" ]; then + printf ' "settings"' + [ -d "$QM_LOG_SYSTEM" ] && printf ',' + printf '\n' + fi + if [ -d "$QM_LOG_SYSTEM" ]; then + printf ' "system"\n' + fi + printf ' ]\n' + printf '}\n' +} + +# Function to get available scripts for a category +get_scripts() { + local cat_dir="" + case "$CATEGORY" in + "daemons") cat_dir="$QM_LOG_DAEMONS" ;; + "services") cat_dir="$QM_LOG_SERVICES" ;; + "settings") cat_dir="$QM_LOG_SETTINGS" ;; + "system") cat_dir="$QM_LOG_SYSTEM" ;; + *) + printf '{"error": "Invalid category"}\n' + return 1 + ;; + esac + + if [ ! -d "$cat_dir" ]; then + printf '{"scripts": []}\n' + return 0 + fi + + printf '{\n' + printf ' "scripts": [\n' + + first=true + for logfile in "$cat_dir"/*.log; do + if [ -f "$logfile" ]; then + if [ "$first" = "false" ]; then + printf ',\n' + fi + script_name=$(basename "$logfile" .log) + printf ' "%s"' "$script_name" + first=false + fi + done + + printf '\n ]\n' + printf '}\n' +} + +# Function to get log entries +get_logs() { + local logfile="" + + if [ -n "$CATEGORY" ] && [ -n "$SCRIPT" ]; then + logfile=$(qm_get_logfile "$CATEGORY" "$SCRIPT") + else + printf '{"error": "Category and script parameters required"}\n' + return 1 + fi + + if [ ! -f "$logfile" ]; then + printf '{"entries": [], "total": 0}\n' + return 0 + fi + + # Get log entries with optional filtering + local temp_file="/tmp/quecmanager_log_view.$$" + + # Start with all entries + cat "$logfile" > "$temp_file" 2>/dev/null + + # Filter by level if specified + if [ -n "$LEVEL" ]; then + grep "\[$LEVEL\]" "$temp_file" > "${temp_file}.filtered" 2>/dev/null || touch "${temp_file}.filtered" + mv "${temp_file}.filtered" "$temp_file" + fi + + # Filter by time if specified (simple grep for now) + if [ -n "$SINCE" ]; then + grep "$SINCE" "$temp_file" > "${temp_file}.filtered" 2>/dev/null || touch "${temp_file}.filtered" + mv "${temp_file}.filtered" "$temp_file" + fi + + # Get total count + local total_count=$(wc -l < "$temp_file" 2>/dev/null || echo "0") + + # Get last N lines + tail -n "$LINES" "$temp_file" > "${temp_file}.final" 2>/dev/null || touch "${temp_file}.final" + + printf '{\n' + printf ' "entries": [\n' + + first=true + while IFS= read -r line; do + if [ -n "$line" ]; then + if [ "$first" = "false" ]; then + printf ',\n' + fi + + # Parse log line (format: [timestamp] [level] [script] [pid] message) + timestamp=$(echo "$line" | sed -n 's/^\[\([^]]*\)\].*/\1/p') + level=$(echo "$line" | sed -n 's/^[^]]*\] \[\([^]]*\)\].*/\1/p') + script=$(echo "$line" | sed -n 's/^[^]]*\] [^]]*\] \[\([^]]*\)\].*/\1/p') + pid=$(echo "$line" | sed -n 's/^[^]]*\] [^]]*\] [^]]*\] \[PID:\([^]]*\)\].*/\1/p') + message=$(echo "$line" | sed 's/^[^]]*\] [^]]*\] [^]]*\] [^]]*\] //') + + # Escape quotes in message + message=$(echo "$message" | sed 's/"/\\"/g') + + printf ' {\n' + printf ' "timestamp": "%s",\n' "$timestamp" + printf ' "level": "%s",\n' "$level" + printf ' "script": "%s",\n' "$script" + printf ' "pid": "%s",\n' "$pid" + printf ' "message": "%s"\n' "$message" + printf ' }' + + first=false + fi + done < "${temp_file}.final" + + printf '\n ],\n' + printf ' "total": %s,\n' "$total_count" + printf ' "showing": %s\n' "$LINES" + printf '}\n' + + # Cleanup temp files + rm -f "$temp_file" "${temp_file}.filtered" "${temp_file}.final" 2>/dev/null || true +} + +# Main logic +case "$REQUEST_METHOD" in + "GET") + if [ -z "$CATEGORY" ]; then + # Return available categories + get_categories + elif [ -z "$SCRIPT" ]; then + # Return available scripts for category + get_scripts + else + # Return log entries + get_logs + fi + ;; + "OPTIONS") + # Handle CORS preflight + exit 0 + ;; + *) + printf '{"error": "Method not allowed"}\n' + ;; +esac diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/scheduled_reboot.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/scheduled_reboot.sh new file mode 100644 index 0000000..267594e --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/experimental/scheduled_reboot.sh @@ -0,0 +1,251 @@ +#!/bin/sh + +# Scheduled Reboot Configuration Script +# Manages device reboot scheduling using cron +# Author: dr-dolomite +# Date: 2025-08-10 + +# Set content type and CORS headers +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Configuration +CONFIG_DIR="/etc/quecmanager/settings" +CONFIG_FILE="$CONFIG_DIR/scheduled_reboot.conf" +LOG_FILE="/tmp/scheduled_reboot.log" +CRON_FILE="/etc/crontabs/root" + +# Logging function +log_message() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" +} + +# Error response function +send_error() { + local error_code="$1" + local error_message="$2" + log_message "ERROR: $error_message" + echo "{\"status\":\"error\",\"code\":\"$error_code\",\"message\":\"$error_message\"}" + exit 1 +} + +# Success response function +send_success() { + local message="$1" + local data="$2" + log_message "SUCCESS: $message" + if [ -n "$data" ]; then + echo "{\"status\":\"success\",\"message\":\"$message\",\"data\":$data}" + else + echo "{\"status\":\"success\",\"message\":\"$message\"}" + fi +} + +# Ensure configuration directory exists +ensure_config_directory() { + if [ ! -d "$CONFIG_DIR" ]; then + mkdir -p "$CONFIG_DIR" + if [ $? -ne 0 ]; then + CONFIG_DIR="/tmp/quecmanager/settings" + CONFIG_FILE="$CONFIG_DIR/scheduled_reboot.conf" + mkdir -p "$CONFIG_DIR" + if [ $? -ne 0 ]; then + send_error "DIRECTORY_ERROR" "Failed to create configuration directory" + fi + fi + chmod 755 "$CONFIG_DIR" + fi +} + +# Update cron entry +update_cron() { + local enabled="$1" + local time="$2" + local days="$3" + + # Create a temporary file for the new crontab + local temp_cron=$(mktemp) + + # If crontab exists, copy all non-QuecManager reboot entries + if [ -f "$CRON_FILE" ]; then + grep -v "# QuecManager scheduled reboot$" "$CRON_FILE" > "$temp_cron" + fi + + if [ "$enabled" = "true" ]; then + # Extract hours and minutes from time (HH:MM format) + local minutes=$(echo "$time" | cut -d':' -f2) + local hours=$(echo "$time" | cut -d':' -f1) + + # Convert days array to cron format (0-6, where 0 is Sunday) + local cron_days="" + echo "$days" | grep -q '"sunday"' && cron_days="${cron_days}0," + echo "$days" | grep -q '"monday"' && cron_days="${cron_days}1," + echo "$days" | grep -q '"tuesday"' && cron_days="${cron_days}2," + echo "$days" | grep -q '"wednesday"' && cron_days="${cron_days}3," + echo "$days" | grep -q '"thursday"' && cron_days="${cron_days}4," + echo "$days" | grep -q '"friday"' && cron_days="${cron_days}5," + echo "$days" | grep -q '"saturday"' && cron_days="${cron_days}6," + + # Remove trailing comma + cron_days=$(echo "$cron_days" | sed 's/,$//') + + if [ -n "$cron_days" ]; then + # Add new cron entry to our temporary file + echo "$minutes $hours * * $cron_days /sbin/reboot # QuecManager scheduled reboot" >> "$temp_cron" + fi + fi + + # Ensure the crontabs directory exists + if [ ! -d "/etc/crontabs" ]; then + mkdir -p /etc/crontabs + chmod 755 /etc/crontabs + fi + + # Move the temporary file to the actual crontab and set permissions + mv "$temp_cron" "$CRON_FILE" + chmod 600 "$CRON_FILE" + + # Always restart cron to ensure changes take effect + /etc/init.d/cron restart +} + +# Save reboot configuration +save_config() { + local enabled="$1" + local time="$2" + local days="$3" + + ensure_config_directory + + # Validate days is a proper JSON array + if ! echo "$days" | grep -q '^\[.*\]$'; then + days='["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]' + fi + + # Create or update config file with proper JSON handling + cat > "$CONFIG_FILE" << EOF +REBOOT_ENABLED=$enabled +REBOOT_TIME=$time +REBOOT_DAYS=$days +EOF + + chmod 644 "$CONFIG_FILE" + + # Update cron entry + update_cron "$enabled" "$time" "$days" +} + +# Get current configuration +get_config() { + local enabled="false" + local time="03:00" + local days='["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]' + + if [ -f "$CONFIG_FILE" ]; then + # Read the config file line by line to handle JSON properly + while IFS='=' read -r key value; do + case "$key" in + REBOOT_ENABLED) + enabled="$value" + ;; + REBOOT_TIME) + time="$value" + ;; + REBOOT_DAYS) + # Only update days if the value is a valid JSON array + if echo "$value" | grep -q '^\[.*\]$'; then + days="$value" + fi + ;; + esac + done < "$CONFIG_FILE" + fi + + # Ensure proper JSON formatting + echo "{\"enabled\":$enabled,\"time\":\"$time\",\"days\":$days}" +} + +# Handle GET request +handle_get() { + local config=$(get_config) + send_success "Configuration retrieved" "$config" +} + +# Handle POST request +handle_post() { + # Read POST data + local content_length=${CONTENT_LENGTH:-0} + if [ "$content_length" -gt 0 ]; then + local post_data=$(dd bs=$content_length count=1 2>/dev/null) + + # Extract values using grep and sed + local enabled=$(echo "$post_data" | grep -o '"enabled":\s*\(true\|false\)' | cut -d':' -f2 | tr -d ' ') + local time=$(echo "$post_data" | grep -o '"time":"[^"]*"' | cut -d'"' -f4) + local days=$(echo "$post_data" | grep -o '"days":\s*\[[^]]*\]' | cut -d':' -f2 | tr -d ' ') + + # Validate input + if [ -z "$enabled" ] || [ -z "$time" ] || [ -z "$days" ]; then + send_error "INVALID_INPUT" "Missing required fields" + return + fi + + # Validate time format (HH:MM) + if ! echo "$time" | grep -qE '^([01]?[0-9]|2[0-3]):[0-5][0-9]$'; then + send_error "INVALID_TIME" "Invalid time format. Use HH:MM (24-hour)" + return + fi + + # Save configuration + save_config "$enabled" "$time" "$days" + send_success "Configuration updated successfully" "$(get_config)" + + else + send_error "NO_DATA" "No data provided" + fi +} + +# Handle DELETE request +handle_delete() { + if [ -f "$CONFIG_FILE" ]; then + # Remove cron entry first + update_cron "false" "00:00" "[]" + + # Remove config file + rm -f "$CONFIG_FILE" + send_success "Configuration reset to default" "$(get_config)" + else + send_error "NOT_FOUND" "Configuration not found" + fi +} + +# Handle OPTIONS request +handle_options() { + echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" + echo "Access-Control-Allow-Headers: Content-Type" + echo "Access-Control-Max-Age: 86400" + exit 0 +} + +# Main execution +log_message "Scheduled reboot script called with method: ${REQUEST_METHOD:-GET}" + +case "${REQUEST_METHOD:-GET}" in + GET) + handle_get + ;; + POST) + handle_post + ;; + DELETE) + handle_delete + ;; + OPTIONS) + handle_options + ;; + *) + send_error "METHOD_NOT_ALLOWED" "HTTP method ${REQUEST_METHOD} not supported" + ;; +esac \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/fetch_hw_details.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/fetch_hw_details.sh index 103cade..5947449 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/fetch_hw_details.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/fetch_hw_details.sh @@ -1,5 +1,8 @@ #!/bin/sh +# Ethernet Hardware Details Fetch Script +# Provides ethernet interface information using ethtool + # Set common headers echo "Content-Type: application/json" echo "Access-Control-Allow-Origin: *" @@ -8,7 +11,7 @@ echo "" # Lock file path LOCK_FILE="/tmp/hw_details.lock" -LOCK_TIMEOUT=10 # Maximum wait time in seconds +LOCK_TIMEOUT=10 # Maximum wait time in seconds # Function to acquire lock acquire_lock() { @@ -57,63 +60,72 @@ cleanup() { # Set trap for cleanup trap cleanup EXIT INT TERM -# Function to get memory information -get_memory_info() { - free_output=$(free -b) - memory_info=$(echo "$free_output" | awk '/Mem:/ {print "{\"total\": " $2 ", \"used\": " $3 ", \"available\": " $7 "}"}') - echo "$memory_info" -} - # Function to get ethernet information get_ethernet_info() { interface=${1:-eth0} - # Check if ethtool is installed - if ! which ethtool >/dev/null 2>&1; then - error_response "ethtool not found" + + # First check if interface exists at all + if ! ip link show "$interface" >/dev/null 2>&1; then + # Interface doesn't exist - return not connected state + echo "{\"link_speed\":\"Not Connected\",\"link_status\":\"no\",\"auto_negotiation\":\"off\",\"connected\":false}" + return 0 fi - # Check if interface exists - if ! ip link show "$interface" >/dev/null 2>&1; then - error_response "Interface $interface not found" + # Check if interface is up (administratively) + interface_state=$(ip link show "$interface" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2) + if [ "$interface_state" = "DOWN" ]; then + # Interface exists but is down - return not connected state + echo "{\"link_speed\":\"Not Connected\",\"link_status\":\"no\",\"auto_negotiation\":\"off\",\"connected\":false}" + return 0 + fi + + # Check if ethtool is available + if ! which ethtool >/dev/null 2>&1; then + # Fallback: basic interface info without ethtool + echo "{\"link_speed\":\"Unknown\",\"link_status\":\"unknown\",\"auto_negotiation\":\"unknown\",\"connected\":true}" + return 0 fi # Run ethtool and capture output - ethtool_output=$(ethtool "$interface" 2>/dev/null) || error_response "Failed to get ethernet information" + ethtool_output=$(ethtool "$interface" 2>/dev/null) + if [ $? -ne 0 ]; then + # ethtool failed - likely no physical connection + echo "{\"link_speed\":\"Not Connected\",\"link_status\":\"no\",\"auto_negotiation\":\"off\",\"connected\":false}" + return 0 + fi # Extract values using sed instead of grep -P - speed=$(echo "$ethtool_output" | sed -n 's/.*Speed: \([^[:space:]]*\).*/\1/p' || echo "Unknown") - link_status=$(echo "$ethtool_output" | sed -n 's/.*Link detected: \(yes\|no\).*/\1/p' || echo "unknown") - auto_negotiation=$(echo "$ethtool_output" | sed -n 's/.*Auto-negotiation: \(on\|off\).*/\1/p' || echo "unknown") + speed=$(echo "$ethtool_output" | sed -n 's/.*Speed: \([^[:space:]]*\).*/\1/p') + link_status=$(echo "$ethtool_output" | sed -n 's/.*Link detected: \(yes\|no\).*/\1/p') + auto_negotiation=$(echo "$ethtool_output" | sed -n 's/.*Auto-negotiation: \(on\|off\).*/\1/p') - # Output JSON - echo "{\"link_speed\":\"$speed\",\"link_status\":\"$link_status\",\"auto_negotiation\":\"$auto_negotiation\"}" + # Set defaults if extraction failed + [ -z "$speed" ] && speed="Unknown" + [ -z "$link_status" ] && link_status="unknown" + [ -z "$auto_negotiation" ] && auto_negotiation="unknown" + + # Check if link is actually detected + if [ "$link_status" = "no" ]; then + # Physical link not detected - return not connected state + echo "{\"link_speed\":\"Not Connected\",\"link_status\":\"no\",\"auto_negotiation\":\"$auto_negotiation\",\"connected\":false}" + return 0 + fi + + # Link is detected and active - return connected state + echo "{\"link_speed\":\"$speed\",\"link_status\":\"$link_status\",\"auto_negotiation\":\"$auto_negotiation\",\"connected\":true}" } # Main execution # Acquire lock before proceeding acquire_lock -# Parse query string for type and interface -type=$(echo "$QUERY_STRING" | sed -n 's/.*type=\([^&]*\).*/\1/p') +# Parse query string for interface parameter interface=$(echo "$QUERY_STRING" | sed -n 's/.*interface=\([^&]*\).*/\1/p') # Default interface if not specified [ -z "$interface" ] && interface="eth0" -# Convert type to lowercase using tr -type=$(echo "$type" | tr '[:upper:]' '[:lower:]') - -# Check type parameter and call appropriate function -case "$type" in - "memory") - get_memory_info - ;; - "eth") - get_ethernet_info "$interface" - ;; - *) - error_response "Invalid type. Use 'memory' or 'eth'" - ;; -esac +# Get ethernet information for the specified interface +get_ethernet_info "$interface" # Lock will be automatically released by the cleanup trap \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/fetch_public_ip.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/fetch_public_ip.sh index d20d275..b9ecc36 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/fetch_public_ip.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/fetch_public_ip.sh @@ -6,6 +6,15 @@ echo "Content-Type: application/json" echo "" +# Check for internet connectivity by pinging 8.8.8.8 twice +ping -c 2 8.8.8.8 >/dev/null 2>&1 + +# If ping fails, return error immediately +if [ $? -ne 0 ]; then + echo '{"error": "Failed to fetch public IP"}' + exit 1 +fi + # Fetch public IP using multiple fallback methods PUBLIC_IP=$( curl -s https://api.ipify.org 2>/dev/null || \ diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/memory/fetch_memory.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/memory/fetch_memory.sh new file mode 100644 index 0000000..179295f --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/memory/fetch_memory.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +# Memory Data Fetch Script - Simplified and robust + +# Always set CORS headers first (no conditional OPTIONS handling) +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Handle OPTIONS request and exit early +if [ "${REQUEST_METHOD:-GET}" = "OPTIONS" ]; then + echo "{\"status\":\"success\"}" + exit 0 +fi + +# Only handle GET requests +if [ "${REQUEST_METHOD:-GET}" != "GET" ]; then + echo "{\"status\":\"error\",\"message\":\"Method not allowed\"}" + exit 0 +fi + +# Paths +MEMORY_JSON="/tmp/quecmanager/memory.json" +CONFIG_FILE="/etc/quecmanager/settings/memory_settings.conf" + +# Check if memory data file exists +if [ -f "$MEMORY_JSON" ] && [ -r "$MEMORY_JSON" ]; then + # Read the file content + memory_data=$(cat "$MEMORY_JSON" 2>/dev/null) + + # Check if we got content and it looks like JSON + if [ -n "$memory_data" ] && echo "$memory_data" | grep -q '"total"'; then + # File exists and has content, return it as-is if it's valid JSON + if echo "$memory_data" | grep -q '"used"' && echo "$memory_data" | grep -q '"available"'; then + echo "{\"status\":\"success\",\"data\":$memory_data}" + else + echo "{\"status\":\"error\",\"message\":\"Invalid memory data format\"}" + fi + else + echo "{\"status\":\"error\",\"message\":\"Memory data file is empty or corrupted\"}" + fi +else + # No memory file exists - check configuration + if [ -f "$CONFIG_FILE" ] && [ -r "$CONFIG_FILE" ]; then + # Check if memory monitoring is enabled + if grep -q "^MEMORY_ENABLED=true" "$CONFIG_FILE" 2>/dev/null; then + echo "{\"status\":\"error\",\"message\":\"Memory daemon starting up\"}" + else + echo "{\"status\":\"error\",\"message\":\"Memory monitoring disabled\"}" + fi + else + echo "{\"status\":\"error\",\"message\":\"Memory monitoring not configured\"}" + fi +fi + +# Always exit cleanly +exit 0 \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/memory/memory_service.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/memory/memory_service.sh new file mode 100644 index 0000000..ca0f46a --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/memory/memory_service.sh @@ -0,0 +1,78 @@ +#!/bin/sh + +# Memory Service Fetch Script +# Returns current memory configuration and status + +# Handle OPTIONS request first +if [ "${REQUEST_METHOD:-GET}" = "OPTIONS" ]; then + echo "Content-Type: text/plain" + echo "Access-Control-Allow-Origin: *" + echo "Access-Control-Allow-Methods: GET, OPTIONS" + echo "Access-Control-Allow-Headers: Content-Type" + echo "Access-Control-Max-Age: 86400" + echo "" + exit 0 +fi + +# Set content type and CORS headers +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Configuration paths +CONFIG_FILE="/etc/quecmanager/settings/memory_settings.conf" +FALLBACK_CONFIG_FILE="/tmp/quecmanager/settings/memory_settings.conf" + +# Get current configuration +get_config() { + # Defaults + ENABLED="false" + INTERVAL="1" + + # Try primary config first, then fallback + local config_to_read="" + if [ -f "$CONFIG_FILE" ]; then + config_to_read="$CONFIG_FILE" + elif [ -f "$FALLBACK_CONFIG_FILE" ]; then + config_to_read="$FALLBACK_CONFIG_FILE" + fi + + if [ -n "$config_to_read" ]; then + local enabled_val=$(grep "^MEMORY_ENABLED=" "$config_to_read" 2>/dev/null | tail -n1 | cut -d'=' -f2 | tr -d '"') + local interval_val=$(grep "^MEMORY_INTERVAL=" "$config_to_read" 2>/dev/null | tail -n1 | cut -d'=' -f2) + + case "$enabled_val" in + true|1|on|yes|enabled) ENABLED="true" ;; + *) ENABLED="false" ;; + esac + + if echo "$interval_val" | grep -qE '^[0-9]+$' && [ "$interval_val" -ge 1 ] && [ "$interval_val" -le 10 ]; then + INTERVAL="$interval_val" + fi + fi +} + +# Check if memory daemon is running +is_memory_daemon_running() { + pgrep -f "memory_daemon.sh" >/dev/null 2>&1 +} + +# Handle GET request only +if [ "${REQUEST_METHOD:-GET}" != "GET" ]; then + echo "{\"status\":\"error\",\"code\":\"METHOD_NOT_ALLOWED\",\"message\":\"Only GET method is supported\"}" + exit 1 +fi + +# Get current configuration +get_config + +# Check daemon status +running="false" +if is_memory_daemon_running; then + running="true" +fi + +# Return configuration and status +echo "{\"status\":\"success\",\"data\":{\"enabled\":$ENABLED,\"interval\":$INTERVAL,\"running\":$running}}" \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/ping/fetch_ping.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/ping/fetch_ping.sh new file mode 100644 index 0000000..0bde8e2 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/ping/fetch_ping.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +# Ping Data Fetch Script - Simplified and OpenWrt compatible + +# Always set CORS headers first +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Handle OPTIONS request and exit early +if [ "${REQUEST_METHOD:-GET}" = "OPTIONS" ]; then + echo "{\"status\":\"success\"}" + exit 0 +fi + +# Only handle GET requests +if [ "${REQUEST_METHOD:-GET}" != "GET" ]; then + echo "{\"status\":\"error\",\"message\":\"Method not allowed\"}" + exit 0 +fi + +# Paths +PING_JSON="/tmp/quecmanager/ping_latency.json" +CONFIG_FILE="/etc/quecmanager/settings/ping_settings.conf" + +# Check if ping data file exists +if [ -f "$PING_JSON" ] && [ -r "$PING_JSON" ]; then + # Read the file content + ping_data=$(cat "$PING_JSON" 2>/dev/null) + + # Check if we got content and it looks like JSON + if [ -n "$ping_data" ] && echo "$ping_data" | grep -q '"timestamp"'; then + # File exists and has content, return it wrapped in success + echo "{\"status\":\"success\",\"data\":$ping_data}" + else + echo "{\"status\":\"error\",\"message\":\"Ping data file is empty or corrupted\"}" + fi +else + # No ping file exists - check configuration + if [ -f "$CONFIG_FILE" ] && [ -r "$CONFIG_FILE" ]; then + # Check if ping monitoring is enabled + if grep -q "^PING_ENABLED=true" "$CONFIG_FILE" 2>/dev/null; then + echo "{\"status\":\"error\",\"message\":\"Ping daemon starting up\"}" + else + echo "{\"status\":\"error\",\"message\":\"Ping monitoring disabled\"}" + fi + else + echo "{\"status\":\"error\",\"message\":\"Ping monitoring not configured\"}" + fi +fi + +# Always exit cleanly +exit 0 diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/ping/ping_service.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/ping/ping_service.sh new file mode 100644 index 0000000..313d754 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/home/ping/ping_service.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Ping Service Configuration Script - Simple OpenWrt compatible version + +# Always set CORS headers first +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Handle OPTIONS request and exit early +if [ "${REQUEST_METHOD:-GET}" = "OPTIONS" ]; then + echo "{\"status\":\"success\"}" + exit 0 +fi + +# Only handle GET requests +if [ "${REQUEST_METHOD:-GET}" != "GET" ]; then + echo "{\"status\":\"error\",\"message\":\"Method not allowed\"}" + exit 0 +fi + +# Configuration path +CONFIG_FILE="/etc/quecmanager/settings/ping_settings.conf" + +# Get current configuration +ENABLED="false" +INTERVAL="5" +HOST="8.8.8.8" + +if [ -f "$CONFIG_FILE" ] && [ -r "$CONFIG_FILE" ]; then + # Parse config using awk (more reliable in BusyBox) + enabled_val=$(awk -F'=' '/^PING_ENABLED=/ {print $2}' "$CONFIG_FILE" 2>/dev/null | tr -d '"') + interval_val=$(awk -F'=' '/^PING_INTERVAL=/ {print $2}' "$CONFIG_FILE" 2>/dev/null) + host_val=$(awk -F'=' '/^PING_HOST=/ {print $2}' "$CONFIG_FILE" 2>/dev/null | tr -d '"') + + case "$enabled_val" in + true|1|on|yes|enabled) ENABLED="true" ;; + *) ENABLED="false" ;; + esac + + if echo "$interval_val" | grep -qE '^[0-9]+$' && [ "$interval_val" -ge 1 ] && [ "$interval_val" -le 3600 ]; then + INTERVAL="$interval_val" + fi + + if [ -n "$host_val" ]; then + HOST="$host_val" + fi +fi + +# Check if ping daemon is running +RUNNING="false" +if pgrep -f "ping_daemon.sh" >/dev/null 2>&1; then + RUNNING="true" +fi + +# Return configuration and status +echo "{\"status\":\"success\",\"data\":{\"enabled\":$ENABLED,\"interval\":$INTERVAL,\"host\":\"$HOST\",\"running\":$RUNNING}}" + +# Always exit cleanly +exit 0 diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/logout.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/logout.sh new file mode 100644 index 0000000..ad4e041 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/logout.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Get token from Request Header Authorization +USER_TOKEN="${HTTP_AUTHORIZATION}" +# Remove token from file +sed -i -e "s/.*${USER_TOKEN}.*//g" /tmp/auth_success 2>/dev/null + +echo "Content-Type: application/json" +echo "Cache-Control: no-cache, no-store, must-revalidate" +echo "Pragma: no-cache" +echo "Expires: 0" +echo "" + + + +echo '{"state":"success", "message":"Logged out successfully"}' \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/check_status.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/check_status.sh index 409b011..41299ef 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/check_status.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/check_status.sh @@ -35,15 +35,15 @@ if [ -f "$STATUS_FILE" ]; then if [ -s "$STATUS_FILE" ]; then # Cat the entire file content (more reliable than grep) status_content=$(cat "$STATUS_FILE") - + # Log content for debugging log_message "Status file content: $status_content" "debug" - + # Check if it looks like valid JSON if echo "$status_content" | grep -q "status"; then # Output the status file content cat "$STATUS_FILE" - + # Extract status for logging only status=$(echo "$status_content" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p') log_message "Status from file: $status" "info" @@ -63,7 +63,7 @@ if [ -f "$TRACK_FILE" ]; then status=$(echo "$status_info" | cut -d':' -f1) profile=$(echo "$status_info" | cut -d':' -f2) progress=$(echo "$status_info" | cut -d':' -f3) - + # Make sure the message reflects the actual status if [ "$status" = "success" ]; then message="Profile successfully applied" @@ -76,7 +76,7 @@ if [ -f "$TRACK_FILE" ]; then else message="Profile operation status: $status" fi - + # Output JSON based on track file cat <>/tmp/list_profiles_error.log - + if [ -z "$indices" ]; then log_message "No profile indices found" "warn" echo "{\"status\":\"success\",\"profiles\":[]}" return 0 fi - + # Process each profile for idx in $indices; do log_message "Processing profile index: $idx" - + # Try different UCI get approaches local name name=$(uci -q get "quecprofiles.$idx.name" 2>/dev/null) @@ -72,7 +72,7 @@ get_profiles() { section=${section%]} name=$(uci -q get "quecprofiles.@profile[$section].name" 2>/dev/null) fi - + # Get profile details local iccid=$(uci -q get "quecprofiles.$idx.iccid" 2>/dev/null) local imei=$(uci -q get "quecprofiles.$idx.imei" 2>/dev/null) @@ -83,8 +83,9 @@ get_profiles() { local nsa_nr5g_bands=$(uci -q get "quecprofiles.$idx.nsa_nr5g_bands" 2>/dev/null) local network_type=$(uci -q get "quecprofiles.$idx.network_type" 2>/dev/null) local ttl=$(uci -q get "quecprofiles.$idx.ttl" 2>/dev/null) + local mobile_provider=$(uci -q get "quecprofiles.$idx.mobile_provider" 2>/dev/null) local paused=$(uci -q get "quecprofiles.$idx.paused" 2>/dev/null) - + # Debug output log_message "Retrieved for $idx: name=$name, iccid=$iccid, apn=$apn, paused=$paused" @@ -93,7 +94,7 @@ get_profiles() { log_message "Skipping invalid profile: $idx (missing required fields)" "warn" continue fi - + # Sanitize all values to ensure valid JSON name=$(sanitize_for_json "$name") iccid=$(sanitize_for_json "$iccid") @@ -105,8 +106,9 @@ get_profiles() { nsa_nr5g_bands=$(sanitize_for_json "${nsa_nr5g_bands:-""}") network_type=$(sanitize_for_json "${network_type:-"LTE"}") ttl=$(sanitize_for_json "${ttl:-0}") + mobile_provider=$(sanitize_for_json "${mobile_provider:-""}") paused=$(sanitize_for_json "${paused:-0}") - + # Create profile JSON local profile_json="{" profile_json="${profile_json}\"name\":\"${name}\"," @@ -119,27 +121,28 @@ get_profiles() { profile_json="${profile_json}\"nsa_nr5g_bands\":\"${nsa_nr5g_bands}\"," profile_json="${profile_json}\"network_type\":\"${network_type}\"," profile_json="${profile_json}\"ttl\":\"${ttl}\"," + profile_json="${profile_json}\"mobile_provider\":\"${mobile_provider}\"," profile_json="${profile_json}\"paused\":\"${paused}\"" profile_json="${profile_json}}" - + # Add comma if not first if [ $first -eq 0 ]; then json_output="${json_output}," else first=0 fi - + # Add profile to output json_output="${json_output}${profile_json}" count=$((count+1)) done - + # Complete the JSON response local response="{\"status\":\"success\",\"profiles\":[${json_output}]}" - + # Save the response for debugging echo "$response" > /tmp/list_profiles_response.json - + echo "$response" log_message "Found and returned $count profiles" return 0 diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_create.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_create.sh index 76a420b..826531e 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_create.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_create.sh @@ -136,6 +136,7 @@ create_profile() { local nsa_nr5g_bands="$8" local network_type="$9" local ttl="${10}" + local mobile_provider="${11}" # Generate a unique ID for the profile local profile_id="profile_$(date +%s)_$(head -c 4 /dev/urandom | hexdump -e '"%x"')" @@ -154,6 +155,7 @@ set quecprofiles.@profile[-1].nsa_nr5g_bands='$nsa_nr5g_bands' set quecprofiles.@profile[-1].network_type='$network_type' set quecprofiles.@profile[-1].ttl='$ttl' set quecprofiles.@profile[-1].paused='0' +set quecprofiles.@profile[-1].mobile_provider='$mobile_provider' commit quecprofiles EOF @@ -206,6 +208,7 @@ if [ "$REQUEST_METHOD" = "POST" ]; then nsa_nr5g_bands=$(echo "$POST_DATA" | jsonfilter -e '@.nsa_nr5g_bands' 2>/dev/null) network_type=$(echo "$POST_DATA" | jsonfilter -e '@.network_type' 2>/dev/null) ttl=$(echo "$POST_DATA" | jsonfilter -e '@.ttl' 2>/dev/null) + mobile_provider=$(echo "$POST_DATA" | jsonfilter -e '@.mobile_provider' 2>/dev/null) log_message "Parsed JSON data for profile: $name" "debug" else @@ -221,6 +224,7 @@ if [ "$REQUEST_METHOD" = "POST" ]; then nsa_nr5g_bands=$(echo "$POST_DATA" | grep -o '"nsa_nr5g_bands":"[^"]*"' | head -1 | cut -d':' -f2 | tr -d '"') network_type=$(echo "$POST_DATA" | grep -o '"network_type":"[^"]*"' | head -1 | cut -d':' -f2 | tr -d '"') ttl=$(echo "$POST_DATA" | grep -o '"ttl":"[^"]*"' | head -1 | cut -d':' -f2 | tr -d '"') + mobile_provider=$(echo "$POST_DATA" | grep -o '"mobile_provider":"[^"]*"' | head -1 | cut -d':' -f2 | tr -d '"') log_message "Basic parsing for profile: $name" "warn" fi @@ -240,6 +244,7 @@ else nsa_nr5g_bands=$(echo "$QUERY_STRING" | grep -o 'nsa_nr5g_bands=[^&]*' | cut -d'=' -f2) network_type=$(echo "$QUERY_STRING" | grep -o 'network_type=[^&]*' | cut -d'=' -f2) ttl=$(echo "$QUERY_STRING" | grep -o 'ttl=[^&]*' | cut -d'=' -f2) + mobile_provider=$(echo "$QUERY_STRING" | grep -o 'mobile_provider=[^&]*' | cut -d'=' -f2) # URL decode values name=$(echo "$name" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") @@ -252,6 +257,7 @@ else nsa_nr5g_bands=$(echo "$nsa_nr5g_bands" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") network_type=$(echo "$network_type" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") ttl=$(echo "$ttl" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") + mobile_provider=$(echo "$mobile_provider" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") log_message "Using URL parameters" "warn" fi @@ -267,6 +273,7 @@ sa_nr5g_bands=$(sanitize "${sa_nr5g_bands:-}") nsa_nr5g_bands=$(sanitize "${nsa_nr5g_bands:-}") network_type=$(sanitize "${network_type:-LTE}") ttl=$(sanitize "${ttl:-0}") # Default to 0 (disabled) +mobile_provider=$(sanitize "${mobile_provider:-Other}") # Output debug info log_message "Creating profile: $name, ICCID: $iccid, IMEI: $imei, APN: $apn" "debug" @@ -340,14 +347,14 @@ elif [ $dup_status -eq 2 ]; then fi # Create the profile -if create_profile "$name" "$iccid" "$imei" "$apn" "$pdp_type" "$lte_bands" "$sa_nr5g_bands" "$nsa_nr5g_bands" "$network_type" "$ttl"; then +if create_profile "$name" "$iccid" "$imei" "$apn" "$pdp_type" "$lte_bands" "$sa_nr5g_bands" "$nsa_nr5g_bands" "$network_type" "$ttl" "$mobile_provider"; then # Trigger immediate profile application touch "/tmp/quecprofiles_check" chmod 644 "/tmp/quecprofiles_check" log_message "Triggered immediate profile check after creation" "info" - + # Create profile data JSON for return - WITHOUT outer curly braces - profile_data="\"name\":\"$name\",\"iccid\":\"$iccid\",\"imei\":\"$imei\",\"apn\":\"$apn\",\"pdp_type\":\"$pdp_type\",\"lte_bands\":\"$lte_bands\",\"sa_nr5g_bands\":\"$sa_nr5g_bands\",\"nsa_nr5g_bands\":\"$nsa_nr5g_bands\",\"network_type\":\"$network_type\",\"ttl\":\"$ttl\"" + profile_data="\"name\":\"$name\",\"iccid\":\"$iccid\",\"imei\":\"$imei\",\"apn\":\"$apn\",\"pdp_type\":\"$pdp_type\",\"lte_bands\":\"$lte_bands\",\"sa_nr5g_bands\":\"$sa_nr5g_bands\",\"nsa_nr5g_bands\":\"$nsa_nr5g_bands\",\"network_type\":\"$network_type\",\"ttl\":\"$ttl\",\"mobile_provider\":\"$mobile_provider\"" # Wrap the data field in curly braces inside output_json output_json "success" "Profile created successfully" "{$profile_data}" diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_delete.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_delete.sh index 95c6e6c..fe48a73 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_delete.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_delete.sh @@ -17,7 +17,7 @@ output_json() { local status="$1" local message="$2" local data="${3:-{}}" - + printf '{"status":"%s","message":"%s","data":%s}\n' "$status" "$message" "$data" exit 0 } @@ -32,7 +32,7 @@ find_profile_by_iccid() { local iccid="$1" # Get all profile indices local profile_indices=$(uci show quecprofiles | grep -o '@profile\[[0-9]\+\]' | sort -u) - + for profile_index in $profile_indices; do local current_iccid=$(uci -q get quecprofiles.$profile_index.iccid) if [ "$current_iccid" = "$iccid" ]; then @@ -40,7 +40,7 @@ find_profile_by_iccid() { return 0 fi done - + return 1 } @@ -48,13 +48,13 @@ find_profile_by_iccid() { delete_profile() { local profile_index="$1" local profile_name=$(uci -q get quecprofiles.$profile_index.name) - + # Delete the profile from UCI config uci -q batch </dev/null) - + # Debug log log_message "Received POST data: $POST_DATA" "debug" - + # Parse JSON with jsonfilter if available if command -v jsonfilter >/dev/null 2>&1; then iccid=$(echo "$POST_DATA" | jsonfilter -e '@.iccid' 2>/dev/null) @@ -102,10 +102,10 @@ if [ "$REQUEST_METHOD" = "POST" ]; then elif [ -n "$QUERY_STRING" ]; then # URL parameters for GET or DELETE requests iccid=$(echo "$QUERY_STRING" | grep -o 'iccid=[^&]*' | cut -d'=' -f2) - + # URL decode value iccid=$(echo "$iccid" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") - + log_message "Using URL parameter: iccid=$iccid" "debug" fi diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_edit.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_edit.sh index c56b85f..2c0eb79 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_edit.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/quec_profile_edit.sh @@ -171,6 +171,7 @@ update_profile() { local nsa_nr5g_bands="$8" local network_type="$9" local ttl="${10}" + local mobile_provider="${11}" # Update the profile in UCI config uci -q batch </dev/null) network_type=$(echo "$POST_DATA" | jsonfilter -e '@.network_type' 2>/dev/null) ttl=$(echo "$POST_DATA" | jsonfilter -e '@.ttl' 2>/dev/null) + mobile_provider=$(echo "$POST_DATA" | jsonfilter -e '@.mobile_provider' 2>/dev/null) log_message "Parsed JSON data for profile: $name" "debug" else @@ -252,6 +255,7 @@ if [ "$REQUEST_METHOD" = "POST" ]; then nsa_nr5g_bands=$(echo "$POST_DATA" | grep -o '"nsa_nr5g_bands":"[^"]*"' | head -1 | cut -d':' -f2 | tr -d '"') network_type=$(echo "$POST_DATA" | grep -o '"network_type":"[^"]*"' | head -1 | cut -d':' -f2 | tr -d '"') ttl=$(echo "$POST_DATA" | grep -o '"ttl":"[^"]*"' | head -1 | cut -d':' -f2 | tr -d '"') + mobile_provider=$(echo "$POST_DATA" | grep -o '"mobile_provider":"[^"]*"' | head -1 | cut -d':' -f2 | tr -d '"') log_message "Basic parsing for profile: $name" "warn" fi @@ -271,6 +275,7 @@ else nsa_nr5g_bands=$(echo "$QUERY_STRING" | grep -o 'nsa_nr5g_bands=[^&]*' | cut -d'=' -f2) network_type=$(echo "$QUERY_STRING" | grep -o 'network_type=[^&]*' | cut -d'=' -f2) ttl=$(echo "$QUERY_STRING" | grep -o 'ttl=[^&]*' | cut -d'=' -f2) + mobile_provider=$(echo "$QUERY_STRING" | grep -o 'mobile_provider=[^&]*' | cut -d'=' -f2) # URL decode values iccid=$(echo "$iccid" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") @@ -283,6 +288,7 @@ else nsa_nr5g_bands=$(echo "$nsa_nr5g_bands" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") network_type=$(echo "$network_type" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") ttl=$(echo "$ttl" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") + mobile_provider=$(echo "$mobile_provider" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") log_message "Using URL parameters" "warn" fi @@ -298,6 +304,7 @@ sa_nr5g_bands=$(sanitize "${sa_nr5g_bands:-}") nsa_nr5g_bands=$(sanitize "${nsa_nr5g_bands:-}") network_type=$(sanitize "${network_type:-LTE}") ttl=$(sanitize "${ttl:-0}") # Default to 0 (disabled) +mobile_provider=$(sanitize "${mobile_provider:-Other}") # Output debug info log_message "Editing profile: $name, ICCID: $iccid, IMEI: $imei, APN: $apn" "debug" @@ -373,18 +380,18 @@ if check_duplicate_name "$name" "$iccid"; then fi # Update profile -if update_profile "$profile_index" "$name" "$imei" "$apn" "$pdp_type" "$lte_bands" "$nr5g_bands" "$network_type"; then +if update_profile "$profile_index" "$name" "$imei" "$apn" "$pdp_type" "$lte_bands" "$sa_nr5g_bands" "$nsa_nr5g_bands" "$network_type" "$ttl" "$mobile_provider"; then # Trigger immediate profile application touch "/tmp/quecprofiles_check" chmod 644 "/tmp/quecprofiles_check" log_message "Triggered immediate profile check after update" "info" - + # Create a clean JSON response with properly escaped quotes printf '{"status":"success","message":"Profile updated successfully","data":{"name":"%s","iccid":"%s","imei":"%s","apn":"%s","pdp_type":"%s","lte_bands":"%s","nr5g_bands":"%s","network_type":"%s"}}' \ "$name" "$iccid" "$imei" "$apn" "$pdp_type" "$lte_bands" "$nr5g_bands" "$network_type" - + log_message "Profile updated successfully: $name" "info" - + # Note: The conditional trigger is replaced with the direct trigger above else printf '{"status":"error","message":"Failed to update profile. Please check system logs."}' diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/toggle_pause.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/toggle_pause.sh index 8ae0845..26e8f32 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/toggle_pause.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/profiles/toggle_pause.sh @@ -145,10 +145,10 @@ elif [ -n "$QUERY_STRING" ]; then # URL parameters for GET requests (for testing) iccid=$(echo "$QUERY_STRING" | grep -o 'iccid=[^&]*' | cut -d'=' -f2) paused=$(echo "$QUERY_STRING" | grep -o 'paused=[^&]*' | cut -d'=' -f2) - + # URL decode values iccid=$(echo "$iccid" | sed 's/+/ /g;s/%\(..\)/\\x\1/g;' | xargs -0 printf "%b") - + log_message "Using URL parameters: iccid=$iccid, paused=$paused" "debug" fi diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/reset-at-bridge.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/reset-at-bridge.sh new file mode 100644 index 0000000..ec4fe95 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/reset-at-bridge.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +DEBUG_LOG="/tmp/socat-at-bridge-reset.log" + +echo "Content-Type: application/json" +echo "Cache-Control: no-cache, no-store, must-revalidate" +echo "Pragma: no-cache" +echo "Expires: 0" +echo "" + + + +service socat-at-bridge restart &>/dev/null +SOCAT_RESET_STATUS=$? + +touch $DEBUG_LOG +# Log the reset status +if [ $SOCAT_RESET_STATUS -eq 0 ]; then + echo "$(date) - socat-at-bridge service restarted successfully." >> $DEBUG_LOG +else + echo "$(date) - Failed to restart socat-at-bridge service. Status: $SOCAT_RESET_STATUS" >> $DEBUG_LOG +fi + +# Basic response indicating the server is up +echo "{\"status\": \"$SOCAT_RESET_STATUS\"}" \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/change-password.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/change-password.sh new file mode 100644 index 0000000..e199a7f --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/change-password.sh @@ -0,0 +1,110 @@ +#!/bin/sh + +# Set Content-Type for CGI script +echo "Content-type: application/json" +echo "" + +TOKEN="${HTTP_AUTHORIZATION}" + +# Read POST data +read -r POST_DATA + +# Debug log for generated hash +DEBUG_LOG="/tmp/password_change.log" +AUTH_FILE="/tmp/auth_success" + +# Get Token from Authorization Header on Request +if [ ! -f $AUTH_FILE ]; then + echo "{\"error\":\"Unauthenticated Request\"}" + exit 1 +fi + +if [ -z "$TOKEN" ] || "${TOKEN}" = "" || [ $(grep "${TOKEN}" "${AUTH_FILE}" | wc -l) -eq 0 ]; then + echo "{\"response\": { \"status\": \"error\", \"raw_output\": \"Not Authorized\" }, \"command\": {\"timestamp\": \"$(date +%Y%m%d'T'%H%M%S)\"}, \"error\":\"Not Authorized\"}" + exit 1 +fi + +# Check if token is within 2 hours +TOKEN_LINE=$(grep "${TOKEN}" "${AUTH_FILE}") +TOKEN_DATE=$(echo "$TOKEN_LINE" | awk '{print $1}' | sed 's/T/ /') +TOKEN_TIME=$(date -d "$TOKEN_DATE" +%s 2>/dev/null) +NOW_TIME=$(date +%s) +MAX_AGE=$((2 * 3600)) # 2 hours in seconds + +if [ -z "$TOKEN_TIME" ] || [ $((NOW_TIME - TOKEN_TIME)) -gt $MAX_AGE ]; then + echo "{ \"response\": { \"status\": \"error\", \"raw_output\": \"Token expired. Reauthenticate to get new token.\" }, \"command\": {\"timestamp\": \"$(date +%Y%m%d'T'%H%M%S)\"}, \"error\":\"Token expired\"}" + # Cleanup/Remove token from file + sed -i -e "s/.*${TOKEN}.*//g" /tmp/auth_success 2>/dev/null + exit 1 +fi + + +# Extract the passwords from POST data (URL encoded) +USER="root" +OLD_PASSWORD=$(echo "$POST_DATA" | grep -o 'oldPassword=[^&]*' | cut -d= -f2-) +NEW_PASSWORD=$(echo "$POST_DATA" | grep -o 'newPassword=[^&]*' | cut -d= -f2-) + +# URL-decode the passwords (replace + with space and decode %XX) +urldecode() { + local encoded="${1//+/ }" + printf '%b' "${encoded//%/\\x}" +} + +OLD_PASSWORD=$(urldecode "$OLD_PASSWORD") +NEW_PASSWORD=$(urldecode "$NEW_PASSWORD") + +# Basic validation to reject & and $ characters +if echo "$OLD_PASSWORD$NEW_PASSWORD" | grep -q '[&$]'; then + echo '{"state":"failed","message":"Password contains forbidden characters (& or $)"}' + exit 1 +fi + +# Extract the hashed password from /etc/shadow for the specified user +USER_SHADOW_ENTRY=$(grep "^$USER:" /etc/shadow) + +if [ -z "$USER_SHADOW_ENTRY" ]; then + echo '{"state":"failed","message":"User not found"}' + exit 1 +fi + +# Extract the password hash (second field, colon-separated) +USER_HASH=$(echo "$USER_SHADOW_ENTRY" | cut -d: -f2) + +# Extract the salt (MD5 uses the $1$ prefix followed by the salt) +SALT=$(echo "$USER_HASH" | cut -d'$' -f3) + +# Generate hash from old password using the same salt +OLD_GENERATED_HASH=$(printf '%s' "$OLD_PASSWORD" | openssl passwd -1 -salt "$SALT" -stdin) + +# Verify old password +if [ "$OLD_GENERATED_HASH" != "$USER_HASH" ]; then + echo '{"state":"failed","message":"Current password is incorrect"}' + exit 1 +fi + +# Create a temporary file for the new password +PASS_FILE=$(mktemp) +chmod 600 "$PASS_FILE" + +# Write the new password twice (for confirmation) +printf '%s\n%s\n' "$NEW_PASSWORD" "$NEW_PASSWORD" > "$PASS_FILE" + +# Change password using passwd command +ERROR_OUTPUT=$(passwd "$USER" < "$PASS_FILE" 2>&1) +RESULT=$? + +# Log the operation +echo "Password change attempt. Result: $RESULT. Time: $(date)" >> "$DEBUG_LOG" +if [ $RESULT -ne 0 ]; then + echo "Error output: $ERROR_OUTPUT" >> "$DEBUG_LOG" +fi + +# Clean up +rm -f "$PASS_FILE" + +# Return result +if [ $RESULT -eq 0 ]; then + echo '{"state":"success","message":"Password changed successfully"}' +else + echo '{"state":"failed","message":"Failed to change password"}' +fi \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/force-reboot.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/force-reboot.sh new file mode 100644 index 0000000..cdd475d --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/force-reboot.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +# Send CGI headers first +echo "Content-Type: application/json" +echo "Cache-Control: no-cache" +echo + +# Simple script to force a reboot of the system +output_json() { + local status="$1" + local message="$2" + echo "{\"status\": \"$status\", \"message\": \"$message\"}" +} + +# Function to force reboot +force_reboot() { + if command -v reboot >/dev/null 2>&1; then + reboot + return 0 + else + return 1 + fi +} + +# Main execution +main() { + if force_reboot; then + output_json "success" "System is rebooting" + else + output_json "error" "Reboot command not found or failed" + fi +} + +main \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/measurement_units.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/measurement_units.sh new file mode 100644 index 0000000..0e362da --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/measurement_units.sh @@ -0,0 +1,375 @@ +#!/bin/sh + +# Smart Measurement Units Configuration Script +# Manages distance unit preferences (km/mi) with automatic timezone-based defaults +# Author: dr-dolomite +# Date: 2025-08-04 + +# Set content type and CORS headers +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Configuration +CONFIG_DIR="/etc/quecmanager/settings" +CONFIG_FILE="$CONFIG_DIR/measurement_units.conf" +LOG_FILE="/tmp/measurement_units.log" + +# Logging function +log_message() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" +} + +# Error response function +send_error() { + local error_code="$1" + local error_message="$2" + log_message "ERROR: $error_message" + echo "{\"status\":\"error\",\"code\":\"$error_code\",\"message\":\"$error_message\"}" + exit 1 +} + +# Success response function +send_success() { + local message="$1" + local data="$2" + log_message "SUCCESS: $message" + if [ -n "$data" ]; then + echo "{\"status\":\"success\",\"message\":\"$message\",\"data\":$data}" + else + echo "{\"status\":\"success\",\"message\":\"$message\"}" + fi +} + +# Ensure configuration directory exists +ensure_config_directory() { + if [ ! -d "$CONFIG_DIR" ]; then + log_message "Creating directory: $CONFIG_DIR" + mkdir -p "$CONFIG_DIR" + if [ $? -ne 0 ]; then + # Try to use a fallback location in /tmp + CONFIG_DIR="/tmp/quecmanager/settings" + CONFIG_FILE="$CONFIG_DIR/measurement_units.conf" + log_message "Fallback to alternative location: $CONFIG_DIR" + mkdir -p "$CONFIG_DIR" + if [ $? -ne 0 ]; then + send_error "DIRECTORY_ERROR" "Failed to create configuration directory" + fi + fi + chmod 755 "$CONFIG_DIR" + log_message "Created configuration directory: $CONFIG_DIR" + fi +} + +# Check if the country uses imperial or metric system based on timezone +get_default_unit() { + # Get timezone from OpenWrt system - use uci as primary method + local timezone="" + + # Primary method: Use uci command (standard OpenWrt way) + if command -v uci >/dev/null 2>&1; then + timezone=$(uci -q get system.@system[0].zonename) + if [ -z "$timezone" ]; then + timezone=$(uci -q get system.@system[0].timezone) + fi + log_message "Detected timezone using uci command: $timezone" + fi + + # Fallback method: Parse OpenWrt config file directly + if [ -z "$timezone" ] && [ -f "/etc/config/system" ]; then + timezone=$(grep -o "option zonename '[^']*'" /etc/config/system | sed "s/option zonename '//;s/'//") + + if [ -z "$timezone" ]; then + timezone=$(grep -o "option timezone '[^']*'" /etc/config/system | sed "s/option timezone '//;s/'//") + fi + log_message "Detected timezone from OpenWrt config file: $timezone" + fi + + # Additional fallback methods + if [ -z "$timezone" ]; then + # Try TZ environment variable + if [ -n "$TZ" ]; then + timezone="$TZ" + log_message "Detected timezone from TZ environment variable: $timezone" + # Try /etc/TZ file + elif [ -f "/etc/TZ" ]; then + timezone=$(cat /etc/TZ) + log_message "Detected timezone from /etc/TZ file: $timezone" + fi + fi + + # If still no timezone, use a default + if [ -z "$timezone" ]; then + timezone="Unknown" + log_message "Warning: Could not detect timezone, using default (km)" + fi + + # Countries and territories that primarily use imperial system (miles) + # Based on current usage as of 2025: + # - United States (including territories) + # - Liberia + # - Myanmar/Burma (mixed usage, but officially imperial for distances) + # - UK uses miles for road distances (though metric for most other measurements) + # - Some British territories and dependencies + case "$timezone" in + # United States and territories - comprehensive timezone coverage + *America/New_York*|*America/Chicago*|*America/Denver*|*America/Los_Angeles*|*America/Phoenix*|*America/Anchorage*|*America/Honolulu*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (US major cities)" + ;; + # All Americas timezones that are US-based + *America/Adak*|*America/Juneau*|*America/Metlakatla*|*America/Nome*|*America/Sitka*|*America/Yakutat*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (US Alaska)" + ;; + # US territories in Pacific + *Pacific/Honolulu*|*Pacific/Johnston*|*Pacific/Midway*|*Pacific/Wake*|*HST*|*Pacific/Samoa*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (US Pacific territories)" + ;; + # US territories in other regions + *America/Puerto_Rico*|*America/Virgin*|*Atlantic/Bermuda*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (US territories)" + ;; + # General US timezone patterns + *America/*EDT*|*America/*EST*|*America/*CDT*|*America/*CST*|*America/*MDT*|*America/*MST*|*America/*PDT*|*America/*PST*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (US timezone abbreviations)" + ;; + # Simple timezone abbreviations commonly used in US systems + *EST*|*CST*|*MST*|*PST*|*EDT*|*CDT*|*MDT*|*PDT*|*AKST*|*AKDT*|*HST*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (US timezone codes)" + ;; + # United Kingdom - uses miles for road distances + *Europe/London*|*GMT*|*BST*|*Europe/Belfast*|*Europe/Edinburgh*|*Europe/Cardiff*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (UK)" + ;; + # British territories and dependencies that use miles + *Atlantic/Stanley*|*Indian/Chagos*|*Europe/Gibraltar*|*Atlantic/South_Georgia*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (British territories)" + ;; + # Liberia + *Africa/Monrovia*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (Liberia)" + ;; + # Myanmar/Burma (mixed usage but officially uses imperial for some measurements) + *Asia/Yangon*|*Asia/Rangoon*) + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (Myanmar)" + ;; + # OpenWrt config format with spaces (common in some router configurations) + "America/New York"|"America/Los Angeles"|"America/Chicago"|"America/Denver"|"America/Phoenix"|"America/Anchorage"|"Europe/London") + echo "mi" + log_message "Default unit based on timezone ($timezone): miles (space-separated format)" + ;; + # Default to metric for all other countries/territories + *) + echo "km" + log_message "Default unit based on timezone ($timezone): kilometers (metric country)" + ;; + esac +} + +# Get current measurement unit +get_measurement_unit() { + # If config file exists, read from it + if [ -f "$CONFIG_FILE" ]; then + unit=$(grep "^DISTANCE_UNIT=" "$CONFIG_FILE" | cut -d'=' -f2) + if [ -n "$unit" ]; then + echo "$unit" + return + fi + fi + + # If no config or empty config, determine default based on timezone + get_default_unit +} + +# Save measurement unit to config file +save_measurement_unit() { + local unit="$1" + ensure_config_directory + + # Create or update config file + if [ -f "$CONFIG_FILE" ]; then + # Update existing file + sed -i "s/^DISTANCE_UNIT=.*$/DISTANCE_UNIT=$unit/" "$CONFIG_FILE" + if [ $? -ne 0 ]; then + # If sed fails (e.g., no match), append the setting + echo "DISTANCE_UNIT=$unit" >> "$CONFIG_FILE" + fi + else + # Create new file + echo "DISTANCE_UNIT=$unit" > "$CONFIG_FILE" + fi + + chmod 644 "$CONFIG_FILE" + log_message "Saved distance unit: $unit" +} + +# Delete measurement unit configuration +delete_measurement_unit() { + if [ -f "$CONFIG_FILE" ]; then + # Remove the DISTANCE_UNIT line + sed -i '/^DISTANCE_UNIT=/d' "$CONFIG_FILE" + log_message "Deleted distance unit configuration" + + # If file is empty after deletion, remove it + if [ ! -s "$CONFIG_FILE" ]; then + rm -f "$CONFIG_FILE" + log_message "Removed empty config file" + fi + return 0 + else + return 1 + fi +} + +# Handle GET request - Retrieve measurement unit preference +handle_get() { + log_message "GET request received" + + # Check if this is a debug request + if echo "$QUERY_STRING" | grep -q "debug=1"; then + # Return diagnostic information + local timezone_info="" + + if command -v uci >/dev/null 2>&1; then + timezone_info="$timezone_info\"uci_system_zonename\": \"$(uci -q get system.@system[0].zonename || echo 'Not found')\"," + timezone_info="$timezone_info\"uci_system_timezone\": \"$(uci -q get system.@system[0].timezone || echo 'Not found')\"," + else + timezone_info="$timezone_info\"uci\": \"Command not found\"," + fi + + if [ -f "/etc/config/system" ]; then + timezone_info="$timezone_info\"openwrt_config\": \"$(cat /etc/config/system | grep -E 'zonename|timezone' | tr '\n' ' ' | sed 's/"/\\"/g')\"," + else + timezone_info="$timezone_info\"openwrt_config\": \"Not found\"," + fi + + if [ -n "$TZ" ]; then + timezone_info="$timezone_info\"TZ_env\": \"$TZ\"," + else + timezone_info="$timezone_info\"TZ_env\": \"Not set\"," + fi + + if [ -f "/etc/TZ" ]; then + timezone_info="$timezone_info\"etc_TZ\": \"$(cat /etc/TZ)\"," + else + timezone_info="$timezone_info\"etc_TZ\": \"Not found\"," + fi + + # Get default unit + local default_unit=$(get_default_unit) + + # Remove trailing comma + timezone_info=$(echo "$timezone_info" | sed 's/,$//') + + send_success "Debug information" "{$timezone_info, \"default_unit\": \"$default_unit\"}" + return + fi + + # Get current unit (from config or default) + local unit=$(get_measurement_unit) + + # Check if it's from config or default + local is_default=true + if [ -f "$CONFIG_FILE" ] && grep -q "^DISTANCE_UNIT=" "$CONFIG_FILE"; then + is_default=false + fi + + send_success "Measurement unit retrieved" "{\"unit\":\"$unit\",\"isDefault\":$is_default}" +} + +# Handle POST request - Update measurement unit preference +handle_post() { + log_message "POST request received" + + # Read POST data + local content_length=${CONTENT_LENGTH:-0} + if [ "$content_length" -gt 0 ]; then + local post_data=$(dd bs=$content_length count=1 2>/dev/null) + log_message "Received POST data: $post_data" + + # Multiple approaches to parse JSON, for robustness across various OpenWrt versions + # Approach 1: Simple regex extraction + local unit=$(echo "$post_data" | sed -n 's/.*"unit"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') + + # Approach 2: grep + cut extraction + if [ -z "$unit" ]; then + unit=$(echo "$post_data" | grep -o '"unit":"[^"]*"' | cut -d'"' -f4) + fi + + # Approach 3: Very basic extraction - look for km or mi in the payload + if [ -z "$unit" ]; then + if echo "$post_data" | grep -q '"km"'; then + unit="km" + elif echo "$post_data" | grep -q '"mi"'; then + unit="mi" + fi + fi + + log_message "Received unit: $unit" + + # Validate unit + if [ "$unit" = "km" ] || [ "$unit" = "mi" ]; then + save_measurement_unit "$unit" + send_success "Measurement unit updated successfully" "{\"unit\":\"$unit\"}" + else + send_error "INVALID_UNIT" "Invalid unit provided. Must be 'km' or 'mi'." + fi + else + send_error "NO_DATA" "No data provided" + fi +} + +# Handle DELETE request - Reset to default (delete configuration) +handle_delete() { + log_message "DELETE request received" + + if delete_measurement_unit; then + # Get the default unit that will be used + local default_unit=$(get_default_unit) + send_success "Measurement unit reset to default" "{\"unit\":\"$default_unit\",\"isDefault\":true}" + else + send_error "NOT_FOUND" "Measurement unit configuration not found" + fi +} + +# Handle OPTIONS request for CORS preflight +handle_options() { + log_message "OPTIONS request received" + echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" + echo "Access-Control-Allow-Headers: Content-Type" + echo "Access-Control-Max-Age: 86400" + exit 0 +} + +# Main execution +log_message "Measurement units script called with method: ${REQUEST_METHOD:-GET}" + +# Handle different HTTP methods +case "${REQUEST_METHOD:-GET}" in + GET) + handle_get + ;; + POST) + handle_post + ;; + DELETE) + handle_delete + ;; + OPTIONS) + handle_options + ;; + *) + send_error "METHOD_NOT_ALLOWED" "HTTP method ${REQUEST_METHOD} not supported" + ;; +esac diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/memory_settings.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/memory_settings.sh new file mode 100644 index 0000000..4b4b3bd --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/memory_settings.sh @@ -0,0 +1,301 @@ +#!/bin/sh + +# Memory Settings Configuration Script +# Manages memory service (enable/disable) and daemon settings with dynamic service management + +# Handle OPTIONS request first +if [ "${REQUEST_METHOD:-GET}" = "OPTIONS" ]; then + echo "Content-Type: text/plain" + echo "Access-Control-Allow-Origin: *" + echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" + echo "Access-Control-Allow-Headers: Content-Type" + echo "Access-Control-Max-Age: 86400" + echo "" + exit 0 +fi + +# Set content type and CORS headers +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Configuration paths +CONFIG_DIR="/etc/quecmanager/settings" +CONFIG_FILE="$CONFIG_DIR/memory_settings.conf" +FALLBACK_CONFIG_DIR="/tmp/quecmanager/settings" +FALLBACK_CONFIG_FILE="$FALLBACK_CONFIG_DIR/memory_settings.conf" +LOG_FILE="/tmp/memory_settings.log" +SERVICES_INIT="/etc/init.d/quecmanager_services" + +# Logging function +log_message() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" +} + +# Error response function +send_error() { + local error_code="$1" + local error_message="$2" + log_message "ERROR: $error_message" + echo "{\"status\":\"error\",\"code\":\"$error_code\",\"message\":\"$error_message\"}" + exit 1 +} + +# Success response function +send_success() { + local message="$1" + local data="$2" + log_message "SUCCESS: $message" + if [ -n "$data" ]; then + echo "{\"status\":\"success\",\"message\":\"$message\",\"data\":$data}" + else + echo "{\"status\":\"success\",\"message\":\"$message\"}" + fi +} + +# Get current configuration +get_config() { + # Defaults + ENABLED="false" + INTERVAL="1" + + # Try primary config first, then fallback + local config_to_read="" + if [ -f "$CONFIG_FILE" ]; then + config_to_read="$CONFIG_FILE" + elif [ -f "$FALLBACK_CONFIG_FILE" ]; then + config_to_read="$FALLBACK_CONFIG_FILE" + fi + + if [ -n "$config_to_read" ]; then + local enabled_val=$(grep "^MEMORY_ENABLED=" "$config_to_read" 2>/dev/null | tail -n1 | cut -d'=' -f2) + local interval_val=$(grep "^MEMORY_INTERVAL=" "$config_to_read" 2>/dev/null | tail -n1 | cut -d'=' -f2) + + case "$enabled_val" in + true|1|on|yes|enabled) ENABLED="true" ;; + *) ENABLED="false" ;; + esac + + if echo "$interval_val" | grep -qE '^[0-9]+$' && [ "$interval_val" -ge 1 ] && [ "$interval_val" -le 10 ]; then + INTERVAL="$interval_val" + fi + fi +} + +# Save configuration +save_config() { + local enabled="$1" + local interval="$2" + + # Try primary location first + if mkdir -p "$CONFIG_DIR" 2>/dev/null && [ -w "$CONFIG_DIR" ]; then + { + echo "MEMORY_ENABLED=$enabled" + echo "MEMORY_INTERVAL=$interval" + } > "$CONFIG_FILE" && chmod 644 "$CONFIG_FILE" 2>/dev/null + log_message "Saved config to primary location: enabled=$enabled, interval=$interval" + return 0 + fi + + # Fallback to tmp + mkdir -p "$FALLBACK_CONFIG_DIR" 2>/dev/null + { + echo "MEMORY_ENABLED=$enabled" + echo "MEMORY_INTERVAL=$interval" + } > "$FALLBACK_CONFIG_FILE" && chmod 644 "$FALLBACK_CONFIG_FILE" 2>/dev/null + log_message "Saved config to fallback location: enabled=$enabled, interval=$interval" +} + +# Add memory daemon to services init script +add_memory_daemon_to_services() { + if [ ! -f "$SERVICES_INIT" ]; then + log_message "Services init file not found: $SERVICES_INIT" + return 1 + fi + + # Check if memory daemon is already present + if grep -q "memory_daemon.sh" "$SERVICES_INIT" 2>/dev/null; then + log_message "Memory daemon already present in services" + return 0 + fi + + # Create a temporary file with the memory daemon block + local temp_file="/tmp/services_temp_$$" + + # Find the line before "echo \"All QuecManager services Started\"" and insert memory daemon + awk ' + /echo "All QuecManager services Started"/ { + print " # Start memory daemon" + print " echo \"Starting Memory Daemon...\"" + print " procd_open_instance" + print " procd_set_param command /www/cgi-bin/services/memory_daemon.sh" + print " procd_set_param respawn" + print " procd_set_param stdout 1" + print " procd_set_param stderr 1" + print " procd_close_instance" + print " echo \"Memory Daemon started\"" + print "" + } + { print } + ' "$SERVICES_INIT" > "$temp_file" + + if [ -s "$temp_file" ]; then + mv "$temp_file" "$SERVICES_INIT" + chmod +x "$SERVICES_INIT" + log_message "Added memory daemon to services init script" + return 0 + else + rm -f "$temp_file" + log_message "Failed to add memory daemon to services" + return 1 + fi +} + +# Remove memory daemon from services init script +remove_memory_daemon_from_services() { + if [ ! -f "$SERVICES_INIT" ]; then + log_message "Services init file not found: $SERVICES_INIT" + return 1 + fi + + # Check if memory daemon is present + if ! grep -q "memory_daemon.sh" "$SERVICES_INIT" 2>/dev/null; then + log_message "Memory daemon not present in services" + return 0 + fi + + # Remove the memory daemon block (from "# Start memory daemon" to the empty line after) + local temp_file="/tmp/services_temp_$$" + + awk ' + /# Start memory daemon/ { skip=1; next } + skip && /^$/ { skip=0; next } + !skip { print } + ' "$SERVICES_INIT" > "$temp_file" + + if [ -s "$temp_file" ]; then + mv "$temp_file" "$SERVICES_INIT" + chmod +x "$SERVICES_INIT" + log_message "Removed memory daemon from services init script" + return 0 + else + rm -f "$temp_file" + log_message "Failed to remove memory daemon from services" + return 1 + fi +} + +# Restart QuecManager services +restart_services() { + log_message "Restarting QuecManager services..." + + # Stop services + if [ -x "$SERVICES_INIT" ]; then + "$SERVICES_INIT" stop >/dev/null 2>&1 + sleep 2 + "$SERVICES_INIT" start >/dev/null 2>&1 + log_message "Services restarted successfully" + return 0 + else + log_message "Cannot restart services - init script not found or not executable" + return 1 + fi +} + +# Check if memory daemon is running +is_memory_daemon_running() { + pgrep -f "memory_daemon.sh" >/dev/null 2>&1 +} + +# Handle POST request - Update memory setting +handle_post() { + log_message "POST request received" + + local content_length=${CONTENT_LENGTH:-0} + if [ "$content_length" -eq 0 ]; then + send_error "NO_DATA" "No data provided" + fi + + # Read POST data + local post_data=$(dd bs=$content_length count=1 2>/dev/null) + log_message "Received POST data: $post_data" + + # Parse enabled and interval from JSON + local enabled=$(echo "$post_data" | sed -n 's/.*"enabled"[[:space:]]*:[[:space:]]*\([^,}]*\).*/\1/p' | tr -d ' "') + local interval=$(echo "$post_data" | sed -n 's/.*"interval"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p') + + # Set defaults if not provided + [ -z "$enabled" ] && enabled="false" + [ -z "$interval" ] && interval="1" + + # Validate input + case "$enabled" in + true|false) ;; + *) send_error "INVALID_SETTING" "Invalid enabled value. Must be true or false." ;; + esac + + if ! echo "$interval" | grep -qE '^[0-9]+$' || [ "$interval" -lt 1 ] || [ "$interval" -gt 10 ]; then + send_error "INVALID_INTERVAL" "Interval must be a number between 1 and 10 seconds." + fi + + # Get current config to compare + get_config + local prev_enabled="$ENABLED" + local prev_interval="$INTERVAL" + + # Save new configuration + save_config "$enabled" "$interval" + + # Handle service changes + if [ "$enabled" = "true" ]; then + # Enable memory daemon + add_memory_daemon_to_services + if [ "$prev_enabled" != "true" ] || [ "$prev_interval" != "$interval" ]; then + restart_services + fi + else + # Disable memory daemon + remove_memory_daemon_from_services + restart_services + fi + + # Return current status + sleep 1 # Give services time to start/stop + local running="false" + if is_memory_daemon_running; then + running="true" + fi + + send_success "Memory setting updated successfully" "{\"enabled\":$enabled,\"interval\":$interval,\"running\":$running}" +} + +# Handle DELETE request - Reset to default +handle_delete() { + log_message "DELETE request received" + + # Remove memory daemon from services and restart + remove_memory_daemon_from_services + restart_services + + # Remove config files + rm -f "$CONFIG_FILE" "$FALLBACK_CONFIG_FILE" 2>/dev/null + + send_success "Memory setting reset to default (disabled)" "{\"enabled\":false,\"interval\":1,\"running\":false,\"isDefault\":true}" +} + +# Main execution +log_message "Memory settings script called with method: ${REQUEST_METHOD:-GET}" + +case "${REQUEST_METHOD:-GET}" in + POST) + handle_post + ;; + DELETE) + handle_delete + ;; + *) + send_error "METHOD_NOT_ALLOWED" "HTTP method ${REQUEST_METHOD} not supported." + ;; +esac diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/ping_settings.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/ping_settings.sh new file mode 100644 index 0000000..388b868 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/ping_settings.sh @@ -0,0 +1,330 @@ +#!/bin/sh + +# Ping Settings Configuration Script +# Manages ping service (enable/disable) and daemon settings +# Author: dr-dolomite +# Date: 2025-08-04 + +# Handle OPTIONS request first (before any headers) +if [ "${REQUEST_METHOD:-GET}" = "OPTIONS" ]; then + echo "Content-Type: text/plain" + echo "Access-Control-Allow-Origin: *" + echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" + echo "Access-Control-Allow-Headers: Content-Type" + echo "Access-Control-Max-Age: 86400" + echo "" + exit 0 +fi + +# Set content type and CORS headers for other requests +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type" +echo "" + +# Configuration +CONFIG_DIR="/etc/quecmanager/settings" +CONFIG_FILE="$CONFIG_DIR/ping_settings.conf" +FALLBACK_CONFIG_DIR="/tmp/quecmanager/settings" +FALLBACK_CONFIG_FILE="$FALLBACK_CONFIG_DIR/ping_settings.conf" +LOG_FILE="/tmp/ping_settings.log" +PID_FILE="/tmp/quecmanager/ping_daemon.pid" +# Prefer the new services location, fall back to the legacy path for compatibility +DAEMON_RELATIVE_PATHS="/cgi-bin/services/ping_daemon.sh" + +# Logging function +log_message() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" +} + +# Error response function +send_error() { + local error_code="$1" + local error_message="$2" + log_message "ERROR: $error_message" + echo "{\"status\":\"error\",\"code\":\"$error_code\",\"message\":\"$error_message\"}" + exit 1 +} + +# Success response function +send_success() { + local message="$1" + local data="$2" + log_message "SUCCESS: $message" + if [ -n "$data" ]; then + echo "{\"status\":\"success\",\"message\":\"$message\",\"data\":$data}" + else + echo "{\"status\":\"success\",\"message\":\"$message\"}" + fi +} + +# Resolve config file for reading: prefer primary, then fallback +resolve_config_for_read() { + if [ -f "$CONFIG_FILE" ]; then + return 0 + elif [ -f "$FALLBACK_CONFIG_FILE" ]; then + CONFIG_FILE="$FALLBACK_CONFIG_FILE" + CONFIG_DIR="$FALLBACK_CONFIG_DIR" + return 0 + fi + # Default to primary path if none exist + return 0 +} + +# Determine daemon path (absolute) based on typical web root layouts +resolve_daemon_path() { + # Common locations where CGI/WWW is mounted + for rel in $DAEMON_RELATIVE_PATHS; do + for base in \ + /www \ + /; do + if [ -x "$base$rel" ]; then + echo "$base$rel" + return 0 + fi + done + # Also try as-is if busybox httpd cwd matches web root + if [ -x "$rel" ]; then + echo "$rel" + return 0 + fi + done + # Nothing found; return first candidate as a best-effort path + set -- $DAEMON_RELATIVE_PATHS + echo "$1" +} + +daemon_running() { + if [ -f "$PID_FILE" ]; then + pid="$(cat "$PID_FILE" 2>/dev/null || true)" + if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then + return 0 + fi + fi + return 1 +} + +start_daemon() { + # Ensure /tmp/quecmanager exists for PID + [ -d "/tmp/quecmanager" ] || mkdir -p "/tmp/quecmanager" + + if daemon_running; then + log_message "Daemon already running" + return 0 + fi + + local daemon_path + daemon_path="$(resolve_daemon_path)" + if [ ! -x "$daemon_path" ]; then + # Try to make it executable if present + if [ -f "$daemon_path" ]; then + chmod +x "$daemon_path" 2>/dev/null || true + fi + fi + + if [ -x "$daemon_path" ]; then + nohup "$daemon_path" >/dev/null 2>&1 & + log_message "Started ping daemon: $daemon_path (pid $!)" + return 0 + else + log_message "Daemon script not found or not executable: $daemon_path" + return 1 + fi +} + +stop_daemon() { + if daemon_running; then + pid="$(cat "$PID_FILE" 2>/dev/null || true)" + if [ -n "${pid:-}" ]; then + kill "$pid" 2>/dev/null || true + sleep 0.2 + kill -9 "$pid" 2>/dev/null || true + fi + fi + rm -f "$PID_FILE" 2>/dev/null || true +} + +# Get current ping setting +get_config_values() { + # defaults + ENABLED="true" + HOST="8.8.8.8" + INTERVAL="5" + + resolve_config_for_read + if [ -f "$CONFIG_FILE" ]; then + val=$(grep -E "^PING_ENABLED=" "$CONFIG_FILE" | tail -n1 | cut -d'=' -f2) + if [ -n "${val:-}" ]; then + case "$val" in + true|1|on|yes|enabled) ENABLED="true" ;; + *) ENABLED="false" ;; + esac + fi + val=$(grep -E "^PING_HOST=" "$CONFIG_FILE" | tail -n1 | cut -d'=' -f2) + [ -n "${val:-}" ] && HOST="$val" + val=$(grep -E "^PING_INTERVAL=" "$CONFIG_FILE" | tail -n1 | cut -d'=' -f2) + if echo "${val:-}" | grep -qE '^[0-9]+$'; then + INTERVAL="$val" + fi + fi +} + +# Save ping setting to config file +save_config() { + local enabled="$1" + local host="$2" + local interval="$3" + + # Try primary directory first + if mkdir -p "$CONFIG_DIR" 2>/dev/null; then + local tmp="$CONFIG_FILE.tmp.$$" + echo "PING_ENABLED=$enabled" > "$tmp" || rm -f "$tmp" || return 1 + echo "PING_HOST=$host" >> "$tmp" || rm -f "$tmp" || return 1 + echo "PING_INTERVAL=$interval" >> "$tmp" || rm -f "$tmp" || return 1 + if mv -f "$tmp" "$CONFIG_FILE" 2>/dev/null; then + chmod 644 "$CONFIG_FILE" 2>/dev/null || true + log_message "Saved ping config (primary): enabled=$enabled host=$host interval=$interval" + return 0 + fi + fi + + # Fallback to /tmp + mkdir -p "$FALLBACK_CONFIG_DIR" 2>/dev/null || true + local tmp2="$FALLBACK_CONFIG_FILE.tmp.$$" + echo "PING_ENABLED=$enabled" > "$tmp2" || rm -f "$tmp2" || return 1 + echo "PING_HOST=$host" >> "$tmp2" || rm -f "$tmp2" || return 1 + echo "PING_INTERVAL=$interval" >> "$tmp2" || rm -f "$tmp2" || return 1 + mv -f "$tmp2" "$FALLBACK_CONFIG_FILE" 2>/dev/null || return 1 + chmod 644 "$FALLBACK_CONFIG_FILE" 2>/dev/null || true + # Point CONFIG_FILE to fallback for subsequent reads in this request + CONFIG_FILE="$FALLBACK_CONFIG_FILE"; CONFIG_DIR="$FALLBACK_CONFIG_DIR" + log_message "Saved ping config (fallback): enabled=$enabled host=$host interval=$interval" +} + +# Delete ping configuration (reset to default) +delete_ping_setting() { + local removed=1 + for f in "$CONFIG_FILE" "$FALLBACK_CONFIG_FILE"; do + if [ -f "$f" ]; then + sed -i '/^PING_ENABLED=/d' "$f" 2>/dev/null || true + sed -i '/^PING_HOST=/d' "$f" 2>/dev/null || true + sed -i '/^PING_INTERVAL=/d' "$f" 2>/dev/null || true + log_message "Deleted ping configuration entries in $f" + [ -s "$f" ] || { rm -f "$f" 2>/dev/null || true; log_message "Removed empty config file $f"; } + removed=0 + fi + done + return $removed +} + +# Handle GET request - Retrieve ping setting +handle_get() { + log_message "GET request received" + get_config_values + local running=false + if daemon_running; then running=true; fi + local is_default=true + if [ -f "$CONFIG_FILE" ] && grep -q "^PING_ENABLED=" "$CONFIG_FILE"; then + is_default=false + fi + send_success "Ping configuration retrieved" "{\"enabled\":$ENABLED,\"host\":\"$HOST\",\"interval\":$INTERVAL,\"running\":$running,\"isDefault\":$is_default}" +} + +# Handle POST request - Update ping setting +handle_post() { + log_message "POST request received" + + # Read POST data + local content_length=${CONTENT_LENGTH:-0} + if [ "$content_length" -gt 0 ]; then + local post_data=$(dd bs=$content_length count=1 2>/dev/null) + log_message "Received POST data: $post_data" + + # Parse fields + local enabled host interval + enabled=$(echo "$post_data" | sed -n 's/.*"enabled"[[:space:]]*:[[:space:]]*\([^,}]*\).*/\1/p' | tr -d ' ' | sed 's/"//g') + host=$(echo "$post_data" | sed -n 's/.*"host"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') + interval=$(echo "$post_data" | sed -n 's/.*"interval"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p') + + # Defaults when missing + [ -z "$enabled" ] && enabled="true" + [ -z "$host" ] && host="8.8.8.8" + [ -z "$interval" ] && interval="5" + + # Validate + case "$enabled" in + true|false) : ;; + *) send_error "INVALID_SETTING" "Invalid enabled value. Must be true or false." ;; + esac + if ! echo "$interval" | grep -qE '^[0-9]+$'; then + send_error "INVALID_INTERVAL" "Interval must be a number (seconds)." + fi + if [ "$interval" -lt 1 ] || [ "$interval" -gt 3600 ]; then + send_error "INVALID_INTERVAL" "Interval must be between 1 and 3600 seconds." + fi + + # Capture previous values to decide on restart + get_config_values + local prev_enabled="$ENABLED" + local prev_host="$HOST" + local prev_interval="$INTERVAL" + + save_config "$enabled" "$host" "$interval" || send_error "WRITE_FAILED" "Failed to save configuration" + + if [ "$enabled" = "true" ]; then + if daemon_running; then + # Restart only if effective parameters changed + if [ "$prev_host" != "$host" ] || [ "$prev_interval" != "$interval" ] || [ "$prev_enabled" != "$enabled" ]; then + log_message "Config change detected (host/interval/enabled). Restarting daemon." + stop_daemon + start_daemon || log_message "Failed to restart daemon" + else + log_message "No change requiring restart; daemon remains running" + fi + else + start_daemon || log_message "Failed to start daemon" + fi + else + stop_daemon + fi + + get_config_values + local running=false + if daemon_running; then running=true; fi + send_success "Ping setting updated successfully" "{\"enabled\":$ENABLED,\"host\":\"$HOST\",\"interval\":$INTERVAL,\"running\":$running}" + else + send_error "NO_DATA" "No data provided" + fi +} + +# Handle DELETE request - Reset to default (delete configuration) +handle_delete() { + log_message "DELETE request received" + stop_daemon + if delete_ping_setting; then + # Default is enabled + send_success "Ping setting reset to default" "{\"enabled\":true,\"isDefault\":true,\"running\":false}" + else + send_error "NOT_FOUND" "Ping setting configuration not found" + fi +} + +# Main execution +log_message "Ping settings script called with method: ${REQUEST_METHOD:-GET}" + +# Handle different HTTP methods +case "${REQUEST_METHOD:-GET}" in + GET) + handle_get + ;; + POST) + handle_post + ;; + DELETE) + handle_delete + ;; + *) + send_error "METHOD_NOT_ALLOWED" "HTTP method ${REQUEST_METHOD} not supported" + ;; +esac diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/profile_picture.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/profile_picture.sh new file mode 100644 index 0000000..3acf5f4 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/quecmanager/settings/profile_picture.sh @@ -0,0 +1,193 @@ +#!/bin/sh + +# Ultra-Simple Profile Picture Management Script +# Handles direct file uploads without base64 encoding +# Author: dr-dolomite +# Date: 2025-08-04 + +# Set content type and CORS headers +echo "Content-Type: application/json" +echo "Access-Control-Allow-Origin: *" +echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" +echo "Access-Control-Allow-Headers: Content-Type, Authorization" +echo "" + +# Configuration +PROFILE_DIR="/www/assets/profile" +PROFILE_IMAGE="$PROFILE_DIR/profile.jpg" +TEMP_DIR="/tmp" +LOG_FILE="/tmp/profile_picture.log" + +# Logging function +log_message() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" +} + +# Error response function +send_error() { + local error_code="$1" + local error_message="$2" + log_message "ERROR: $error_message" + echo "{\"status\":\"error\",\"code\":\"$error_code\",\"message\":\"$error_message\"}" + exit 1 +} + +# Success response function +send_success() { + local message="$1" + local data="$2" + log_message "SUCCESS: $message" + if [ -n "$data" ]; then + echo "{\"status\":\"success\",\"message\":\"$message\",\"data\":$data}" + else + echo "{\"status\":\"success\",\"message\":\"$message\"}" + fi +} + +# Get file size +get_file_size() { + local file="$1" + if [ -f "$file" ]; then + stat -c%s "$file" 2>/dev/null || wc -c < "$file" + else + echo 0 + fi +} + +# Create profile directory if it doesn't exist +ensure_profile_directory() { + if [ ! -d "$PROFILE_DIR" ]; then + mkdir -p "$PROFILE_DIR" + if [ $? -ne 0 ]; then + send_error "DIRECTORY_ERROR" "Failed to create profile directory" + fi + chmod 755 "$PROFILE_DIR" + log_message "Created profile directory: $PROFILE_DIR" + fi +} + +# Handle GET request - Fetch profile picture +handle_get() { + log_message "GET request received" + + if [ -f "$PROFILE_IMAGE" ]; then + # Get file information + local file_size=$(get_file_size "$PROFILE_IMAGE") + local file_modified=$(stat -c %Y "$PROFILE_IMAGE" 2>/dev/null || echo "0") + + # Return file information and base64 encoded image + local base64_image="" + if command -v base64 >/dev/null 2>&1; then + base64_image=$(base64 -w 0 "$PROFILE_IMAGE" 2>/dev/null) + elif command -v openssl >/dev/null 2>&1; then + base64_image=$(openssl base64 -in "$PROFILE_IMAGE" | tr -d '\n' 2>/dev/null) + elif command -v python3 >/dev/null 2>&1; then + base64_image=$(python3 -c " +import base64 +try: + with open('$PROFILE_IMAGE', 'rb') as f: + data = f.read() + encoded = base64.b64encode(data).decode('ascii') + print(encoded) +except Exception as e: + pass +" 2>/dev/null) + elif command -v busybox >/dev/null 2>&1; then + base64_image=$(busybox base64 "$PROFILE_IMAGE" | tr -d '\n' 2>/dev/null) + fi + + if [ -n "$base64_image" ]; then + local file_type=$(file -b --mime-type "$PROFILE_IMAGE" 2>/dev/null || echo "image/jpeg") + send_success "Profile picture found" "{\"exists\":true,\"size\":$file_size,\"modified\":$file_modified,\"type\":\"$file_type\",\"data\":\"data:$file_type;base64,$base64_image\"}" + else + send_success "Profile picture found but could not encode" "{\"exists\":true,\"size\":$file_size,\"modified\":$file_modified,\"data\":null}" + fi + else + log_message "No profile picture found" + echo "{\"status\":\"error\",\"code\":\"NO_IMAGE_FOUND\",\"message\":\"No profile picture found\"}" + fi +} + +# Handle POST request - Direct file upload (no base64) +handle_post() { + log_message "POST request received" + ensure_profile_directory + + # Create temporary file with unique name + local temp_file="$TEMP_DIR/profile_upload_$$" + + log_message "Content-Type: ${CONTENT_TYPE:-unknown}" + log_message "Content-Length: ${CONTENT_LENGTH:-unknown}" + + # Read the raw uploaded file data directly to temp file + cat > "$temp_file" + + # Check if file was created and has content + if [ ! -f "$temp_file" ]; then + send_error "UPLOAD_ERROR" "Failed to receive uploaded file" + fi + + local temp_size=$(get_file_size "$temp_file") + log_message "Received file size: $temp_size bytes" + + if [ "$temp_size" -eq 0 ]; then + rm -f "$temp_file" + send_error "UPLOAD_ERROR" "Received empty file" + fi + + # Simply move the uploaded file to profile location (rename operation) + if mv "$temp_file" "$PROFILE_IMAGE"; then + chmod 644 "$PROFILE_IMAGE" + local file_size=$(get_file_size "$PROFILE_IMAGE") + log_message "Profile picture saved successfully, size: $file_size bytes" + send_success "Profile picture uploaded successfully" "{\"size\":$file_size,\"path\":\"$PROFILE_IMAGE\"}" + else + rm -f "$temp_file" + send_error "SAVE_ERROR" "Failed to save profile picture" + fi +} + +# Handle DELETE request - Remove profile picture +handle_delete() { + log_message "DELETE request received" + + if [ -f "$PROFILE_IMAGE" ]; then + if rm "$PROFILE_IMAGE"; then + send_success "Profile picture deleted successfully" + else + send_error "DELETE_ERROR" "Failed to delete profile picture" + fi + else + send_error "NO_IMAGE_FOUND" "No profile picture found to delete" + fi +} + +# Handle OPTIONS request for CORS preflight +handle_options() { + echo "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS" + echo "Access-Control-Allow-Headers: Content-Type, Authorization" + echo "Access-Control-Max-Age: 86400" + exit 0 +} + +# Main execution +log_message "Profile picture script called with method: ${REQUEST_METHOD:-GET}" + +# Handle different HTTP methods +case "${REQUEST_METHOD:-GET}" in + GET) + handle_get + ;; + POST) + handle_post + ;; + DELETE) + handle_delete + ;; + OPTIONS) + handle_options + ;; + *) + send_error "METHOD_NOT_ALLOWED" "HTTP method ${REQUEST_METHOD} not supported" + ;; +esac diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/at_queue_manager.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/at_queue_manager.sh index aa9aee3..98ad9a3 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/at_queue_manager.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/at_queue_manager.sh @@ -2,6 +2,9 @@ # AT Queue Manager for OpenWRT with Preemption Support and Token System # Located in /www/cgi-bin/services/at_queue_manager +# Load centralized logging +. /www/cgi-bin/services/quecmanager_logger.sh + # Constants QUEUE_DIR="/tmp/at_queue" QUEUE_FILE="$QUEUE_DIR/queue" @@ -15,6 +18,32 @@ RESULTS_MAX_AGE=3600 # 1 hour in seconds POLL_INTERVAL=0.01 PREEMPTION_THRESHOLD=2 # 3 seconds threshold for preemption TOKEN_TIMEOUT=30 # seconds before token expires +SCRIPT_NAME_LOG="at_queue_manager" + +# Logging function - uses both centralized and system logging +log_at_queue() { + local level="$1" + local message="$2" + + # Use centralized logging + case "$level" in + "error") + qm_log_error "service" "$SCRIPT_NAME_LOG" "$message" + ;; + "warn") + qm_log_warn "service" "$SCRIPT_NAME_LOG" "$message" + ;; + "debug") + qm_log_debug "service" "$SCRIPT_NAME_LOG" "$message" + ;; + *) + qm_log_info "service" "$SCRIPT_NAME_LOG" "$message" + ;; + esac + + # Also maintain system logging for compatibility + logger -t at_queue -p "daemon.$level" "$message" +} # Utility function for JSON escaping escape_json() { @@ -39,7 +68,7 @@ acquire_lock() { while [ $attempt -lt $timeout ]; do if mkdir "$LOCK_DIR" 2>/dev/null; then - logger -t at_queue -p daemon.debug "Lock acquired" + log_at_queue "debug" "Lock acquired" return 0 fi @@ -47,18 +76,18 @@ acquire_lock() { attempt=$((attempt + 1)) done - logger -t at_queue -p daemon.error "Failed to acquire lock after $timeout attempts" + log_at_queue "error" "Failed to acquire lock after $timeout attempts" return 1 } release_lock() { if [ -d "$LOCK_DIR" ]; then rmdir "$LOCK_DIR" 2>/dev/null - logger -t at_queue -p daemon.debug "Lock released" + log_at_queue "debug" "Lock released" return 0 fi - logger -t at_queue -p daemon.error "Lock directory doesn't exist" + log_at_queue "error" "Lock directory doesn't exist" return 1 } @@ -69,7 +98,7 @@ init_queue_system() { chmod 755 "$QUEUE_DIR" chmod 644 "$QUEUE_FILE" chmod 755 "$RESULTS_DIR" - logger -t at_queue -p daemon.info "Queue system initialized" + log_at_queue "info" "Queue system initialized" } # Cleanup old results and tracking files @@ -80,7 +109,7 @@ cleanup_old_results() { find "$QUEUE_DIR" -name "pid.*" -type f -mmin +60 -delete 2>/dev/null find "$QUEUE_DIR" -name "*.exit" -type f -mmin +60 -delete 2>/dev/null find "$QUEUE_DIR" -name "start_time.*" -type f -mmin +60 -delete 2>/dev/null - logger -t at_queue -p daemon.debug "Cleaned up old tracking files" + log_at_queue "debug" "Cleaned up old tracking files" # Use find with -delete and basic timestamp check for OpenWRT find "$RESULTS_DIR" -name "*.json" -type f -mmin +60 -delete 2>/dev/null || { @@ -99,12 +128,12 @@ cleanup_old_results() { local token_time=$(cat "$TOKEN_FILE" | jsonfilter -e '@.timestamp') if [ $((current_time - token_time)) -gt $TOKEN_TIMEOUT ]; then local token_holder=$(cat "$TOKEN_FILE" | jsonfilter -e '@.id') - logger -t at_queue -p daemon.warn "Removing expired token from $token_holder" + log_at_queue "warn" "Removing expired token from $token_holder" rm -f "$TOKEN_FILE" fi fi - logger -t at_queue -p daemon.info "Cleanup: Removed files older than 1 hour" + log_at_queue "info" "Cleanup: Removed files older than 1 hour" } # Generate unique command ID @@ -122,7 +151,7 @@ start_execution_tracking() { echo "$pid" > "$QUEUE_DIR/pid.$cmd_id" chmod 644 "$QUEUE_DIR/start_time.$cmd_id" chmod 644 "$QUEUE_DIR/pid.$cmd_id" - logger -t at_queue -p daemon.debug "Started tracking command $cmd_id (PID: $pid)" + log_at_queue "debug" "Started tracking command $cmd_id (PID: $pid)" } # Check if running command should be preempted @@ -131,7 +160,7 @@ should_preempt() { local new_priority="$2" if [ ! -f "$QUEUE_DIR/start_time.$current_cmd_id" ]; then - logger -t at_queue -p daemon.debug "No start time found for $current_cmd_id" + log_at_queue "debug" "No start time found for $current_cmd_id" return 1 fi @@ -144,16 +173,16 @@ should_preempt() { if [ -f "$ACTIVE_FILE" ]; then current_priority=$(cat "$ACTIVE_FILE" | jsonfilter -e '@.priority') else - logger -t at_queue -p daemon.debug "No active command found" + log_at_queue "debug" "No active command found" return 1 fi if [ $execution_time -gt $PREEMPTION_THRESHOLD ] && [ $new_priority -lt $current_priority ]; then - logger -t at_queue -p daemon.info "Command $current_cmd_id (priority $current_priority) running for ${execution_time}s is eligible for preemption by priority $new_priority" + log_at_queue "info" "Command $current_cmd_id (priority $current_priority) running for ${execution_time}s is eligible for preemption by priority $new_priority" return 0 fi - logger -t at_queue -p daemon.debug "Command $current_cmd_id not eligible for preemption (time: ${execution_time}s, current priority: $current_priority, new priority: $new_priority)" + log_at_queue "debug" "Command $current_cmd_id not eligible for preemption (time: ${execution_time}s, current priority: $current_priority, new priority: $new_priority)" return 1 } @@ -164,7 +193,7 @@ preempt_command() { if [ -f "$pid_file" ]; then local pid=$(cat "$pid_file") - logger -t at_queue -p daemon.info "Preempting command $cmd_id (PID: $pid)" + log_at_queue "info" "Preempting command $cmd_id (PID: $pid)" # Send SIGTERM first kill -TERM $pid 2>/dev/null @@ -175,7 +204,7 @@ preempt_command() { # Force kill if still running if kill -0 $pid 2>/dev/null; then kill -KILL $pid 2>/dev/null - logger -t at_queue -p daemon.warn "Forced termination of command $cmd_id" + log_at_queue "warn" "Forced termination of command $cmd_id" fi # Record preemption result @@ -185,11 +214,11 @@ preempt_command() { rm -f "$pid_file" "$QUEUE_DIR/start_time.$cmd_id" "$QUEUE_DIR/$cmd_id.exit" [ -f "$ACTIVE_FILE" ] && rm -f "$ACTIVE_FILE" - logger -t at_queue -p daemon.info "Command $cmd_id preemption complete" + log_at_queue "info" "Command $cmd_id preemption complete" return 0 fi - logger -t at_queue -p daemon.warn "No PID file found for command $cmd_id" + log_at_queue "warn" "No PID file found for command $cmd_id" return 1 } @@ -227,7 +256,7 @@ EOF printf "%s" "$response" > "$RESULTS_DIR/$cmd_id.json" chmod 644 "$RESULTS_DIR/$cmd_id.json" - logger -t at_queue -p daemon.info "Recorded preemption result for command $cmd_id (duration: ${duration}ms)" + log_at_queue "info" "Recorded preemption result for command $cmd_id (duration: ${duration}ms)" } # Request a token for direct sms_tool execution @@ -238,7 +267,7 @@ request_token() { # Acquire lock first if ! acquire_lock; then - logger -t at_queue -p daemon.error "Failed to acquire lock for token request" + log_at_queue "error" "Failed to acquire lock for token request" echo "{\"error\":\"Could not acquire lock\",\"status\":\"denied\"}" return 1 fi @@ -252,11 +281,11 @@ request_token() { # Check for expired token (> TOKEN_TIMEOUT seconds old) if [ $((current_time - timestamp)) -gt $TOKEN_TIMEOUT ]; then - logger -t at_queue -p daemon.warn "Found expired token from $current_holder, releasing" + log_at_queue "warn" "Found expired token from $current_holder, releasing" rm -f "$TOKEN_FILE" # Check for priority preemption elif [ $priority -lt $current_priority ]; then - logger -t at_queue -p daemon.info "Preempting token from $current_holder (priority: $current_priority) for $requestor_id (priority: $priority)" + log_at_queue "info" "Preempting token from $current_holder (priority: $current_priority) for $requestor_id (priority: $priority)" rm -f "$TOKEN_FILE" else # Token in use and cannot be preempted @@ -278,7 +307,7 @@ request_token() { return 1 fi - logger -t at_queue -p daemon.info "Direct execution with higher priority than active queue command" + log_at_queue "info" "Direct execution with higher priority than active queue command" fi # Grant token @@ -296,7 +325,7 @@ release_token() { local requestor_id="$1" if ! acquire_lock; then - logger -t at_queue -p daemon.error "Failed to acquire lock for token release" + log_at_queue "error" "Failed to acquire lock for token release" return 1 fi @@ -305,15 +334,15 @@ release_token() { if [ "$current_holder" = "$requestor_id" ]; then rm -f "$TOKEN_FILE" - logger -t at_queue -p daemon.debug "Token released by $requestor_id" + log_at_queue "debug" "Token released by $requestor_id" release_lock echo "{\"status\":\"released\"}" return 0 else - logger -t at_queue -p daemon.warn "Token release attempted by $requestor_id but held by $current_holder" + log_at_queue "warn" "Token release attempted by $requestor_id but held by $current_holder" fi else - logger -t at_queue -p daemon.warn "Token release attempted but no token exists" + log_at_queue "warn" "Token release attempted but no token exists" fi release_lock @@ -331,11 +360,11 @@ enqueue_command() { # Ensure queue directory exists [ ! -d "$QUEUE_DIR" ] && init_queue_system - logger -t at_queue -p daemon.info "Enqueuing command: $cmd (priority: $priority, id: $cmd_id)" + log_at_queue "info" "Enqueuing command: $cmd (priority: $priority, id: $cmd_id)" # Acquire lock for queue modification if ! acquire_lock; then - logger -t at_queue -p daemon.error "Failed to acquire lock for enqueuing command" + log_at_queue "error" "Failed to acquire lock for enqueuing command" echo "{\"error\":\"Queue lock acquisition failed\",\"command\":\"$cmd\"}" return 1 fi @@ -358,11 +387,11 @@ enqueue_command() { cat "$QUEUE_FILE" >> "$temp_file" mv "$temp_file" "$QUEUE_FILE" chmod 644 "$QUEUE_FILE" - logger -t at_queue -p daemon.info "Added high priority command to front of queue" + log_at_queue "info" "Added high priority command to front of queue" else # Normal priority - append to queue echo "$entry" >> "$QUEUE_FILE" - logger -t at_queue -p daemon.info "Added normal priority command to end of queue" + log_at_queue "info" "Added normal priority command to end of queue" fi # Release lock @@ -379,7 +408,7 @@ dequeue_command() { # Acquire lock if ! acquire_lock; then - logger -t at_queue -p daemon.error "Failed to acquire lock for dequeuing command" + log_at_queue "error" "Failed to acquire lock for dequeuing command" return 1 fi @@ -395,7 +424,7 @@ dequeue_command() { # Release lock release_lock - logger -t at_queue -p daemon.debug "Dequeued command: $(echo "$cmd_entry" | jsonfilter -e '@.command')" + log_at_queue "debug" "Dequeued command: $(echo "$cmd_entry" | jsonfilter -e '@.command')" echo "$cmd_entry" } @@ -433,7 +462,7 @@ execute_with_timeout() { # Start execution tracking start_execution_tracking "$cmd_id" "$pid" - logger -t at_queue -p daemon.debug "Started command execution: $command (PID: $pid)" + log_at_queue "debug" "Started command execution: $command (PID: $pid)" # Wait for completion with shorter polling interval local start_time=$(date +%s) @@ -447,7 +476,7 @@ execute_with_timeout() { # Cleanup rm -f "$QUEUE_DIR/pid.$cmd_id" "$QUEUE_DIR/$cmd_id.exit" "$output_file" "$QUEUE_DIR/start_time.$cmd_id" - logger -t at_queue -p daemon.debug "Command completed with exit code $exit_code" + log_at_queue "debug" "Command completed with exit code $exit_code" echo "$output" return $exit_code fi @@ -471,7 +500,7 @@ execute_with_timeout() { # Cleanup rm -f "$QUEUE_DIR/pid.$cmd_id" "$QUEUE_DIR/$cmd_id.exit" "$output_file" "$QUEUE_DIR/start_time.$cmd_id" - logger -t at_queue -p daemon.warn "Command timed out after $timeout seconds" + log_at_queue "warn" "Command timed out after $timeout seconds" echo "${partial_output:-Command timed out after $timeout seconds}" fi @@ -487,7 +516,7 @@ execute_command() { local start_time=$(date +%s%3N) - logger -t at_queue -p daemon.info "Executing command $cmd_id: $cmd_text (priority: $priority)" + log_at_queue "info" "Executing command $cmd_id: $cmd_text (priority: $priority)" # Execute command with timeout local result=$(execute_with_timeout "$cmd_text" $MAX_TIMEOUT "$cmd_id") @@ -501,16 +530,16 @@ execute_command() { if [ $exit_code -eq 124 ]; then status="timeout" - logger -t at_queue -p daemon.error "Command $cmd_id timed out after ${duration}ms" + log_at_queue "error" "Command $cmd_id timed out after ${duration}ms" elif echo "$result" | grep -q "OK"; then status="success" log_level="info" - logger -t at_queue -p daemon.info "Command $cmd_id completed successfully in ${duration}ms" + log_at_queue "info" "Command $cmd_id completed successfully in ${duration}ms" elif echo "$result" | grep -q "CME ERROR"; then status="cme_error" - logger -t at_queue -p daemon.error "Command $cmd_id failed with CME ERROR in ${duration}ms" + log_at_queue "error" "Command $cmd_id failed with CME ERROR in ${duration}ms" else - logger -t at_queue -p daemon.error "Command $cmd_id failed with general error in ${duration}ms" + log_at_queue "error" "Command $cmd_id failed with general error in ${duration}ms" fi # Clean and escape the output @@ -536,7 +565,7 @@ EOF # Acquire lock for writing result if ! acquire_lock; then - logger -t at_queue -p daemon.error "Failed to acquire lock for writing result" + log_at_queue "error" "Failed to acquire lock for writing result" else # Save response printf "%s" "$response" > "$RESULTS_DIR/$cmd_id.json" @@ -561,7 +590,7 @@ process_queue() { # Make sure the lock directory doesn't exist at startup [ -d "$LOCK_DIR" ] && rmdir "$LOCK_DIR" 2>/dev/null - logger -t at_queue -p daemon.info "Started queue processing daemon" + log_at_queue "info" "Started queue processing daemon" while true; do # Quick cleanup check @@ -579,12 +608,12 @@ process_queue() { # Check for expired token if [ $((current_time - token_time)) -gt $TOKEN_TIMEOUT ]; then - logger -t at_queue -p daemon.warn "Removing expired token from $token_holder" + log_at_queue "warn" "Removing expired token from $token_holder" rm -f "$TOKEN_FILE" else # Log pause status only every 5 seconds to reduce log spam if [ $((current_time - last_log)) -ge 5 ]; then - logger -t at_queue -p daemon.debug "Queue processing paused, token held by $token_holder" + log_at_queue "debug" "Queue processing paused, token held by $token_holder" last_log=$current_time fi sleep $POLL_INTERVAL @@ -618,42 +647,42 @@ if [ "${SCRIPT_NAME}" != "" ]; then case "$action" in "enqueue") if [ -n "$command" ]; then - logger -t at_queue -p daemon.info "CGI: Received enqueue request for command: $command" + log_at_queue "info" "CGI: Received enqueue request for command: $command" enqueue_command "$command" "$priority" else - logger -t at_queue -p daemon.error "CGI: Empty command received" + log_at_queue "error" "CGI: Empty command received" echo "{\"error\":\"No command specified\"}" fi ;; "status") if [ -f "$ACTIVE_FILE" ]; then - logger -t at_queue -p daemon.debug "CGI: Status request - queue active" + log_at_queue "debug" "CGI: Status request - queue active" cat "$ACTIVE_FILE" else - logger -t at_queue -p daemon.debug "CGI: Status request - queue idle" + log_at_queue "debug" "CGI: Status request - queue idle" echo "{\"status\":\"idle\"}" fi ;; "request_token") if [ -n "$id" ]; then - logger -t at_queue -p daemon.info "Token request from $id (priority: ${priority:-10})" + log_at_queue "info" "Token request from $id (priority: ${priority:-10})" request_token "$id" "${priority:-10}" "${timeout:-10}" else - logger -t at_queue -p daemon.error "Token request missing ID" + log_at_queue "error" "Token request missing ID" echo "{\"error\":\"No requestor ID specified\",\"status\":\"denied\"}" fi ;; "release_token") if [ -n "$id" ]; then - logger -t at_queue -p daemon.info "Token release from $id" + log_at_queue "info" "Token release from $id" release_token "$id" else - logger -t at_queue -p daemon.error "Token release missing ID" + log_at_queue "error" "Token release missing ID" echo "{\"error\":\"No requestor ID specified\",\"status\":\"denied\"}" fi ;; *) - logger -t at_queue -p daemon.error "CGI: Invalid action received: $action" + log_at_queue "error" "CGI: Invalid action received: $action" echo "{\"error\":\"Invalid action\"}" ;; esac diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/cleanup_logs.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/cleanup_logs.sh new file mode 100644 index 0000000..8920904 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/cleanup_logs.sh @@ -0,0 +1,110 @@ +#!/bin/sh + +# QuecManager Log Cleanup Script +# Periodically clean up old log files to prevent /tmp from filling up + +. /www/cgi-bin/services/quecmanager_logger.sh + +# Configuration +MAX_LOG_AGE_DAYS=7 # Delete logs older than 7 days +MAX_BACKUP_FILES=2 # Keep maximum 2 backup files (.1, .2) +CLEANUP_LOG_SIZE=1000 # Run cleanup if any log exceeds 1MB + +# Function to log cleanup activities +log_cleanup() { + qm_log_info "system" "log_cleanup" "$1" +} + +# Initialize +qm_init_logs +log_cleanup "Starting log cleanup process" + +# Cleanup function +perform_cleanup() { + local files_cleaned=0 + local space_freed=0 + + # Clean up old backup files + if [ -d "$QM_LOG_BASE" ]; then + # Remove backup files older than specified days + old_backups=$(find "$QM_LOG_BASE" -name "*.1" -o -name "*.2" -type f -mtime +$MAX_LOG_AGE_DAYS 2>/dev/null) + for backup_file in $old_backups; do + if [ -f "$backup_file" ]; then + file_size=$(du -k "$backup_file" 2>/dev/null | cut -f1) + rm -f "$backup_file" 2>/dev/null + if [ $? -eq 0 ]; then + files_cleaned=$((files_cleaned + 1)) + space_freed=$((space_freed + ${file_size:-0})) + log_cleanup "Removed old backup file: $(basename "$backup_file")" + fi + fi + done + + # Force rotation for large log files + for category_dir in "$QM_LOG_DAEMONS" "$QM_LOG_SERVICES" "$QM_LOG_SETTINGS" "$QM_LOG_SYSTEM"; do + if [ -d "$category_dir" ]; then + for logfile in "$category_dir"/*.log; do + if [ -f "$logfile" ]; then + # Check file size in KB + file_size_kb=$(du -k "$logfile" 2>/dev/null | cut -f1) + + if [ "${file_size_kb:-0}" -gt $CLEANUP_LOG_SIZE ]; then + log_cleanup "Rotating large log file: $(basename "$logfile") (${file_size_kb}KB)" + qm_rotate_log "$logfile" + files_cleaned=$((files_cleaned + 1)) + fi + fi + done + fi + done + + # Additional cleanup: remove empty log files + empty_logs=$(find "$QM_LOG_BASE" -name "*.log" -type f -size 0 2>/dev/null) + for empty_log in $empty_logs; do + rm -f "$empty_log" 2>/dev/null + if [ $? -eq 0 ]; then + files_cleaned=$((files_cleaned + 1)) + log_cleanup "Removed empty log file: $(basename "$empty_log")" + fi + done + fi + + # Log cleanup summary + if [ $files_cleaned -gt 0 ]; then + log_cleanup "Cleanup completed: $files_cleaned files processed, ${space_freed}KB freed" + else + log_cleanup "Cleanup completed: no files needed cleaning" + fi +} + +# Check if we should run cleanup based on disk usage +check_disk_usage() { + # Check /tmp usage (OpenWrt compatible) + local tmp_usage="" + + # Try df first (most common) + if command -v df >/dev/null 2>&1; then + tmp_usage=$(df /tmp 2>/dev/null | awk 'NR==2 {print $5}' | tr -d '%') + fi + + # If we got a valid percentage and it's high, force cleanup + if [ -n "$tmp_usage" ] && [ "$tmp_usage" -gt 80 ]; then + log_cleanup "High /tmp usage detected (${tmp_usage}%), forcing cleanup" + return 0 + fi + + # Always run periodic cleanup + return 0 +} + +# Main execution +if check_disk_usage; then + perform_cleanup +else + log_cleanup "Disk usage check passed, skipping cleanup" +fi + +# Clean up centralized log helper's old logs too +qm_cleanup_logs + +log_cleanup "Log cleanup process completed" diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/interpret_qcainfo.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/interpret_qcainfo.sh new file mode 100644 index 0000000..1bca528 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/interpret_qcainfo.sh @@ -0,0 +1,227 @@ +#!/bin/sh +# Simple QCAINFO Interpreter + +# Configuration +QCAINFO_FILE="/www/signal_graphs/qcainfo.json" +INTERPRETED_FILE="/tmp/interpreted_result.json" +DEBUG_LOG="/tmp/qcainfo_interpreter.log" +INTERVAL=15 + +# Simple logging function +log() { + echo "$(date): $1" >> "$DEBUG_LOG" +} + +# Parse QCAINFO output to extract band and EARFCN +parse_entry() { + local output="$1" + local datetime="$2" + + # Extract band and EARFCN using simple grep + local band=$(echo "$output" | grep -o 'LTE BAND [0-9]*' | head -1) + local earfcn=$(echo "$output" | grep -o '+QCAINFO: "PCC",[0-9]*' | grep -o '[0-9]*' | head -1) + local pci=$(echo "$output" | grep -o '+QCAINFO: "PCC",[0-9]*,[0-9]*' | grep -o ',[0-9]*,' | tr -d ',' | head -1) + + # Check for SCC (carrier aggregation) + local has_scc="" + if echo "$output" | grep -q '+QCAINFO: "SCC"'; then + has_scc="yes" + else + has_scc="no" + fi + + echo "${datetime}|${band}|${earfcn}|${pci}|${has_scc}" +} + +# Compare two entries and generate interpretation +generate_interpretation() { + local old_entry="$1" + local new_entry="$2" + + # Parse entries + local old_datetime=$(echo "$old_entry" | cut -d'|' -f1) + local old_band=$(echo "$old_entry" | cut -d'|' -f2) + local old_earfcn=$(echo "$old_entry" | cut -d'|' -f3) + local old_pci=$(echo "$old_entry" | cut -d'|' -f4) + local old_scc=$(echo "$old_entry" | cut -d'|' -f5) + + local new_datetime=$(echo "$new_entry" | cut -d'|' -f1) + local new_band=$(echo "$new_entry" | cut -d'|' -f2) + local new_earfcn=$(echo "$new_entry" | cut -d'|' -f3) + local new_pci=$(echo "$new_entry" | cut -d'|' -f4) + local new_scc=$(echo "$new_entry" | cut -d'|' -f5) + + local time_only=$(echo "$new_datetime" | awk '{print $2}' | cut -d: -f1,2) + local interpretation="" + + # Check for band change + if [ "$old_band" != "$new_band" ]; then + interpretation="${interpretation}At ${time_only}, your modem changed primary band from ${old_band} to ${new_band}. " + fi + + # Check for EARFCN change + if [ "$old_earfcn" != "$new_earfcn" ]; then + interpretation="${interpretation}At ${time_only}, your modem changed primary EARFCN from ${old_earfcn} to ${new_earfcn}. " + fi + + # Check for PCI change + if [ "$old_pci" != "$new_pci" ]; then + interpretation="${interpretation}At ${time_only}, your modem changed primary PCI from ${old_pci} to ${new_pci}. " + fi + + # Check for carrier aggregation changes + if [ "$old_scc" = "no" ] && [ "$new_scc" = "yes" ]; then + interpretation="${interpretation}At ${time_only}, your modem activated carrier aggregation. " + elif [ "$old_scc" = "yes" ] && [ "$new_scc" = "no" ]; then + interpretation="${interpretation}At ${time_only}, your modem deactivated carrier aggregation. " + fi + + echo "$interpretation" +} + +# Add interpretation to JSON file without jq +add_interpretation() { + local interpretation="$1" + local datetime="$2" + + if [ -z "$interpretation" ]; then + return + fi + + # Initialize file if it doesn't exist + if [ ! -f "$INTERPRETED_FILE" ]; then + echo "[]" > "$INTERPRETED_FILE" + fi + + # Read existing content + local existing_content=$(cat "$INTERPRETED_FILE") + + # Escape quotes in interpretation + local escaped_interpretation=$(echo "$interpretation" | sed 's/"/\\"/g') + + # Create new entry + local new_entry="{\"datetime\":\"$datetime\",\"interpretation\":\"$escaped_interpretation\"}" + + # Add to array + if [ "$existing_content" = "[]" ]; then + echo "[$new_entry]" > "$INTERPRETED_FILE" + else + # Remove closing bracket, add comma and new entry + echo "$existing_content" | sed 's/]$//' > "$INTERPRETED_FILE.tmp" + echo ",$new_entry]" >> "$INTERPRETED_FILE.tmp" + mv "$INTERPRETED_FILE.tmp" "$INTERPRETED_FILE" + fi + + log "Added interpretation: $interpretation" +} + +# Main processing function +process_qcainfo() { + if [ ! -f "$QCAINFO_FILE" ]; then + log "QCAINFO file not found: $QCAINFO_FILE" + return + fi + + # Get total entries + local total_entries=$(jq 'length' "$QCAINFO_FILE" 2>/dev/null) + if [ -z "$total_entries" ] || [ "$total_entries" = "null" ] || [ "$total_entries" -lt 2 ]; then + log "Not enough entries to compare (need at least 2, found: $total_entries)" + return + fi + + log "Found $total_entries entries in QCAINFO file" + + # Get last two entries + local last_entry=$(jq -r '.[-1]' "$QCAINFO_FILE" 2>/dev/null) + local second_last_entry=$(jq -r '.[-2]' "$QCAINFO_FILE" 2>/dev/null) + + if [ "$last_entry" = "null" ] || [ "$second_last_entry" = "null" ]; then + log "Failed to get last two entries" + return + fi + + # Extract data from JSON entries + local last_datetime=$(echo "$last_entry" | jq -r '.datetime') + local last_output=$(echo "$last_entry" | jq -r '.output') + local second_datetime=$(echo "$second_last_entry" | jq -r '.datetime') + local second_output=$(echo "$second_last_entry" | jq -r '.output') + + log "Comparing entries: $second_datetime vs $last_datetime" + + # Parse entries + local parsed_second=$(parse_entry "$second_output" "$second_datetime") + local parsed_last=$(parse_entry "$last_output" "$last_datetime") + + log "Parsed second: $parsed_second" + log "Parsed last: $parsed_last" + + # Generate interpretation + local interpretation=$(generate_interpretation "$parsed_second" "$parsed_last") + + if [ -n "$interpretation" ]; then + add_interpretation "$interpretation" "$last_datetime" + log "Generated interpretation for $last_datetime" + else + log "No changes detected between $second_datetime and $last_datetime" + fi +} + +# Initialize +log "QCAINFO Interpreter started (PID: $$)" + +# Initialize interpreted results file +if [ ! -f "$INTERPRETED_FILE" ]; then + echo "[]" > "$INTERPRETED_FILE" + log "Initialized interpreted results file" +fi + +# Process all existing data once at startup +log "Processing all existing QCAINFO data..." +if [ -f "$QCAINFO_FILE" ]; then + total=$(jq 'length' "$QCAINFO_FILE" 2>/dev/null) + if [ "$total" -gt 1 ]; then + # Process all consecutive pairs + i=1 + while [ $i -lt $total ]; do + prev_entry=$(jq -r ".[$((i-1))]" "$QCAINFO_FILE" 2>/dev/null) + curr_entry=$(jq -r ".[$i]" "$QCAINFO_FILE" 2>/dev/null) + + if [ "$prev_entry" != "null" ] && [ "$curr_entry" != "null" ]; then + prev_datetime=$(echo "$prev_entry" | jq -r '.datetime') + prev_output=$(echo "$prev_entry" | jq -r '.output') + curr_datetime=$(echo "$curr_entry" | jq -r '.datetime') + curr_output=$(echo "$curr_entry" | jq -r '.output') + + parsed_prev=$(parse_entry "$prev_output" "$prev_datetime") + parsed_curr=$(parse_entry "$curr_output" "$curr_datetime") + + interpretation=$(generate_interpretation "$parsed_prev" "$parsed_curr") + + if [ -n "$interpretation" ]; then + add_interpretation "$interpretation" "$curr_datetime" + fi + fi + i=$((i + 1)) + done + log "Completed processing all existing data ($total entries)" + else + log "Not enough existing data to process" + fi +fi + +# Remember last processed entry count +last_count=$(jq 'length' "$QCAINFO_FILE" 2>/dev/null) + +# Main monitoring loop +log "Starting continuous monitoring (checking every $INTERVAL seconds)" +while true; do + sleep "$INTERVAL" + + current_count=$(jq 'length' "$QCAINFO_FILE" 2>/dev/null) + + if [ "$current_count" -gt "$last_count" ]; then + log "New entries detected: $last_count -> $current_count" + process_qcainfo + last_count="$current_count" + fi +done \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/log_signal_metrics.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/log_signal_metrics.sh index 6646134..70ee8a0 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/log_signal_metrics.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/log_signal_metrics.sh @@ -164,7 +164,23 @@ process_all_metrics() { "$logfile" > "$temp_file" 2>/dev/null && mv "$temp_file" "$logfile" chmod 644 "$logfile" fi - + + sleep 0.5 + + # QCAINFO with time stamp + local usage_output=$(execute_at_command "AT+QCAINFO") + if [ -n "$usage_output" ] && echo "$usage_output" | grep -q "QCAINFO"; then + local logfile="$LOGDIR/qcainfo.json" + [ ! -s "$logfile" ] && echo "[]" > "$logfile" + + local temp_file="${logfile}.tmp.$$" + jq --arg dt "$timestamp" \ + --arg out "$usage_output" \ + '. + [{"datetime": $dt, "output": $out}] | .[-'"$MAX_ENTRIES"':]' \ + "$logfile" > "$temp_file" 2>/dev/null && mv "$temp_file" "$logfile" + chmod 644 "$logfile" + fi + # Release token release_token "$metrics_id" logger -t at_queue -p daemon.info "Metrics processing completed" diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/memory_daemon.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/memory_daemon.sh new file mode 100644 index 0000000..6bc2278 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/memory_daemon.sh @@ -0,0 +1,201 @@ +#!/bin/sh + +# Memory Daemon - Monitors system memory usage and writes to JSON file +# This daemon only runs when memory monitoring is enabled via settings + +set -eu + +# Ensure PATH for OpenWrt/BusyBox +export PATH="/usr/sbin:/usr/bin:/sbin:/bin:$PATH" + +# Load centralized logging +. /www/cgi-bin/services/quecmanager_logger.sh + +# Configuration +TMP_DIR="/tmp/quecmanager" +OUT_JSON="$TMP_DIR/memory.json" +PID_FILE="$TMP_DIR/memory_daemon.pid" +CONFIG_FILE="/etc/quecmanager/settings/memory_settings.conf" +[ -f "$CONFIG_FILE" ] || CONFIG_FILE="/tmp/quecmanager/settings/memory_settings.conf" +DEFAULT_INTERVAL=1 +SCRIPT_NAME="memory_daemon" + +# Ensure temp directory exists +ensure_tmp_dir() { + [ -d "$TMP_DIR" ] || mkdir -p "$TMP_DIR" || exit 1 +} + +# Logging function +log() { + qm_log_info "daemon" "$SCRIPT_NAME" "$1" +} + +# Check if this daemon instance is already running +daemon_is_running() { + if [ -f "$PID_FILE" ]; then + pid="$(cat "$PID_FILE" 2>/dev/null || true)" + if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then + # Verify it's actually our daemon by checking process cmdline + if [ -r "/proc/$pid/cmdline" ] && grep -q "memory_daemon.sh" "/proc/$pid/cmdline" 2>/dev/null; then + return 0 + else + # PID file is stale, remove it + rm -f "$PID_FILE" 2>/dev/null || true + fi + fi + fi + return 1 +} + +# Write our PID to file +write_pid() { + echo "$$" > "$PID_FILE" +} + +# Cleanup function +cleanup() { + rm -f "$PID_FILE" 2>/dev/null || true + log "Memory daemon stopped" +} + +# Create default config if none exists +create_default_config() { + local primary_config="/etc/quecmanager/settings/memory_settings.conf" + local fallback_config="/tmp/quecmanager/settings/memory_settings.conf" + + if [ ! -f "$primary_config" ] && [ ! -f "$fallback_config" ]; then + log "No config file found, creating default configuration" + + # Try primary location first + if mkdir -p "/etc/quecmanager/settings" 2>/dev/null; then + { + echo "MEMORY_ENABLED=false" + echo "MEMORY_INTERVAL=1" + } > "$primary_config" 2>/dev/null && { + chmod 644 "$primary_config" 2>/dev/null || true + CONFIG_FILE="$primary_config" + log "Created default config at $primary_config" + return 0 + } + fi + + # Fallback to tmp location + mkdir -p "/tmp/quecmanager/settings" 2>/dev/null || true + { + echo "MEMORY_ENABLED=false" + echo "MEMORY_INTERVAL=1" + } > "$fallback_config" && { + chmod 644 "$fallback_config" 2>/dev/null || true + CONFIG_FILE="$fallback_config" + log "Created default config at $fallback_config" + return 0 + } + + log "Failed to create default config file" + return 1 + fi +} + +# Read configuration from file +read_config() { + ENABLED="false" + INTERVAL="$DEFAULT_INTERVAL" + + if [ -f "$CONFIG_FILE" ]; then + MEMORY_ENABLED=$(grep -E "^MEMORY_ENABLED=" "$CONFIG_FILE" 2>/dev/null | tail -n1 | cut -d'=' -f2 | tr -d '\r' | tr -d '"') + MEMORY_INTERVAL=$(grep -E "^MEMORY_INTERVAL=" "$CONFIG_FILE" 2>/dev/null | tail -n1 | cut -d'=' -f2 | tr -d '\r') + + case "${MEMORY_ENABLED:-}" in + true|1|on|yes|enabled) ENABLED="true" ;; + *) ENABLED="false" ;; + esac + + if echo "${MEMORY_INTERVAL:-}" | grep -qE '^[0-9]+$'; then + if [ "$MEMORY_INTERVAL" -ge 1 ] && [ "$MEMORY_INTERVAL" -le 10 ]; then + INTERVAL="$MEMORY_INTERVAL" + fi + fi + fi +} + +# Write JSON data atomically +write_json_atomic() { + local json_data="$1" + local tmpfile="$(mktemp "$TMP_DIR/memory.XXXXXX" 2>/dev/null || echo "$TMP_DIR/memory.tmp.$$")" + + if [ -n "$tmpfile" ] && printf '%s' "$json_data" > "$tmpfile" 2>/dev/null; then + mv "$tmpfile" "$OUT_JSON" 2>/dev/null || { + # Fallback if move fails + printf '%s' "$json_data" > "$OUT_JSON" 2>/dev/null || true + rm -f "$tmpfile" 2>/dev/null || true + } + else + # Direct write fallback + printf '%s' "$json_data" > "$OUT_JSON" 2>/dev/null || true + rm -f "$tmpfile" 2>/dev/null || true + fi +} + +# Main execution starts here +ensure_tmp_dir +log "Starting memory daemon (PID: $$)" + +# Check if already running +if daemon_is_running; then + log "Memory daemon already running, exiting" + exit 0 +fi + +# Create default config if needed +create_default_config + +# Set up signal handlers +trap cleanup EXIT INT TERM +write_pid + +# Main monitoring loop +while true; do + read_config + + # Exit if disabled + if [ "$ENABLED" != "true" ]; then + log "Memory monitoring disabled in config, exiting" + exit 0 + fi + + # Get current timestamp + ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + + # Get memory information using /proc/meminfo (most reliable method) + if [ -r "/proc/meminfo" ]; then + # Extract values from /proc/meminfo (values are in kB) + TOTAL_KB=$(grep "^MemTotal:" /proc/meminfo 2>/dev/null | awk '{print $2}' || echo "0") + AVAIL_KB=$(grep "^MemAvailable:" /proc/meminfo 2>/dev/null | awk '{print $2}' || echo "0") + FREE_KB=$(grep "^MemFree:" /proc/meminfo 2>/dev/null | awk '{print $2}' || echo "0") + + # If MemAvailable is not available (older kernels), estimate it + if [ "$AVAIL_KB" = "0" ]; then + CACHED_KB=$(grep "^Cached:" /proc/meminfo 2>/dev/null | awk '{print $2}' || echo "0") + BUFFERS_KB=$(grep "^Buffers:" /proc/meminfo 2>/dev/null | awk '{print $2}' || echo "0") + AVAIL_KB=$((FREE_KB + CACHED_KB + BUFFERS_KB)) + fi + + # Convert to bytes (multiply by 1024) + TOTAL_BYTES=$((TOTAL_KB * 1024)) + AVAIL_BYTES=$((AVAIL_KB * 1024)) + USED_BYTES=$((TOTAL_BYTES - AVAIL_BYTES)) + + json="{\"total\": $TOTAL_BYTES, \"used\": $USED_BYTES, \"available\": $AVAIL_BYTES, \"timestamp\": \"$ts\"}" + else + # Fallback if /proc/meminfo is not available + log "Warning: /proc/meminfo not readable, using error response" + json="{\"total\": 0, \"used\": 0, \"available\": 0, \"timestamp\": \"$ts\", \"error\": \"meminfo_unavailable\"}" + fi + + # Write the JSON data + write_json_atomic "$json" + log "Updated memory data: total=${TOTAL_KB:-0}KB, used=${USED_BYTES:-0}B, available=${AVAIL_KB:-0}KB" + + # Sleep for the configured interval + sleep "$INTERVAL" +done \ No newline at end of file diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/network_insights_interpreter.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/network_insights_interpreter.sh new file mode 100644 index 0000000..2ddd6e6 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/network_insights_interpreter.sh @@ -0,0 +1,372 @@ +#!/bin/sh +# Network Insights Interpreter Service +# Monitors qcainfo.json and generates network event interpretations +# OpenWrt/BusyBox compatible version + +# Configuration +QCAINFO_FILE="/www/signal_graphs/qcainfo.json" +INTERPRETED_FILE="/tmp/interpreted_result.json" +LAST_ENTRY_FILE="/tmp/last_qcainfo_entry.json" +LOCKFILE="/tmp/network_interpreter.lock" +MAX_INTERPRETATIONS=50 + +# Logging function (OpenWrt compatible) +log_message() { + if command -v logger >/dev/null 2>&1; then + logger -t network_interpreter -p daemon.info "$1" + else + # Use simpler date format for BusyBox + echo "$(date) [network_interpreter] $1" >&2 + fi +} + +# Convert datetime to timestamp (OpenWrt/BusyBox compatible) +datetime_to_timestamp() { + local datetime="$1" + # Try GNU date first, fallback to string comparison for BusyBox + if date -d "$datetime" +%s >/dev/null 2>&1; then + date -d "$datetime" +%s + else + # For BusyBox, just return the datetime string for string comparison + # This is less precise but works for sequential comparison + echo "$datetime" + fi +} + +# Compare timestamps/datetime strings (OpenWrt compatible) +is_datetime_newer() { + local datetime1="$1" + local datetime2="$2" + + local ts1=$(datetime_to_timestamp "$datetime1") + local ts2=$(datetime_to_timestamp "$datetime2") + + # If we got numeric timestamps, compare numerically + if [ "$ts1" -eq "$ts1" ] 2>/dev/null && [ "$ts2" -eq "$ts2" ] 2>/dev/null; then + [ "$ts1" -gt "$ts2" ] + else + # Fall back to string comparison (works for ISO format) + [ "$datetime1" \> "$datetime2" ] + fi +} + +# Parse QCAINFO output to extract band information +parse_qcainfo_bands() { + local output="$1" + + # Clean up the output - remove escape sequences and extra characters + local clean_output=$(echo "$output" | tr -d '\r' | sed 's/\\r//g; s/\\n/\n/g') + + # Extract all band information from QCAINFO lines + echo "$clean_output" | grep "+QCAINFO:" | while IFS= read -r line; do + if echo "$line" | grep -q "LTE BAND"; then + band=$(echo "$line" | sed -n 's/.*"LTE BAND \([0-9][0-9]*\)".*/B\1/p') + if [ -n "$band" ]; then + echo "LTE:$band" + fi + elif echo "$line" | grep -q "NR5G BAND"; then + band=$(echo "$line" | sed -n 's/.*"NR5G BAND \([0-9][0-9]*\)".*/N\1/p') + if [ -n "$band" ]; then + echo "NR5G:$band" + fi + fi + done +} + +# Get network mode from bands +get_network_mode() { + local bands="$1" + local has_lte=false + local has_nr5g=false + + if echo "$bands" | grep -q "LTE:"; then + has_lte=true + fi + if echo "$bands" | grep -q "NR5G:"; then + has_nr5g=true + fi + + if [ "$has_lte" = true ] && [ "$has_nr5g" = true ]; then + echo "NSA" + elif [ "$has_lte" = true ]; then + echo "LTE" + elif [ "$has_nr5g" = true ]; then + echo "SA" + else + echo "NO_SIGNAL" + fi +} + +# Get band list from parsed bands +get_band_list() { + local bands="$1" + if [ -z "$bands" ]; then + echo "" + return + fi + echo "$bands" | sed 's/LTE://g; s/NR5G://g' | sort -u | tr '\n' ',' | sed 's/,$//' +} + +# Get carrier count +get_carrier_count() { + local bands="$1" + if [ -z "$bands" ]; then + echo "0" + return + fi + echo "$bands" | wc -l +} + +# Compare two band configurations and generate interpretation +compare_configurations() { + local base_output="$1" + local new_output="$2" + local base_datetime="$3" + local new_datetime="$4" + + # Parse both configurations + local base_bands=$(parse_qcainfo_bands "$base_output") + local new_bands=$(parse_qcainfo_bands "$new_output") + + local base_mode=$(get_network_mode "$base_bands") + local new_mode=$(get_network_mode "$new_bands") + + local base_band_list=$(get_band_list "$base_bands") + local new_band_list=$(get_band_list "$new_bands") + + local base_carrier_count=$(get_carrier_count "$base_bands") + local new_carrier_count=$(get_carrier_count "$new_bands") + + local interpretations="" + + # Check for no signal condition + if [ "$new_mode" = "NO_SIGNAL" ]; then + if [ "$base_mode" != "NO_SIGNAL" ]; then + interpretations="Signal lost - No cellular connection detected" + fi + # Check if signal was restored + elif [ "$base_mode" = "NO_SIGNAL" ] && [ "$new_mode" != "NO_SIGNAL" ]; then + interpretations="Signal restored - Connected to $new_mode network" + if [ -n "$new_band_list" ]; then + interpretations="$interpretations ($new_band_list)" + fi + # Check if CA was activated immediately upon signal restoration + if [ "$new_carrier_count" -gt 1 ]; then + interpretations="$interpretations; Carrier Aggregation activated - Now using $new_carrier_count carriers" + fi + else + # Network mode changes + if [ "$base_mode" != "$new_mode" ]; then + case "$new_mode" in + "LTE") + if [ "$base_mode" = "NSA" ]; then + interpretations="Network mode changed from NSA to LTE-only" + elif [ "$base_mode" = "SA" ]; then + interpretations="Network mode changed from 5G SA to LTE" + fi + ;; + "SA") + if [ "$base_mode" = "LTE" ]; then + interpretations="Network mode changed from LTE to 5G SA" + elif [ "$base_mode" = "NSA" ]; then + interpretations="Network mode changed from NSA to 5G SA" + fi + ;; + "NSA") + if [ "$base_mode" = "LTE" ]; then + interpretations="Network mode changed from LTE to NSA" + elif [ "$base_mode" = "SA" ]; then + interpretations="Network mode changed from 5G SA to NSA" + fi + ;; + esac + fi + + # Band changes + if [ "$base_band_list" != "$new_band_list" ]; then + if [ -n "$interpretations" ]; then + interpretations="$interpretations; " + fi + + # Find added and removed bands + local added_bands="" + local removed_bands="" + + # Check for new bands + for band in $(echo "$new_band_list" | tr ',' ' '); do + if [ -n "$band" ] && ! echo "$base_band_list" | grep -q "$band"; then + if [ -n "$added_bands" ]; then + added_bands="$added_bands, $band" + else + added_bands="$band" + fi + fi + done + + # Check for removed bands + for band in $(echo "$base_band_list" | tr ',' ' '); do + if [ -n "$band" ] && ! echo "$new_band_list" | grep -q "$band"; then + if [ -n "$removed_bands" ]; then + removed_bands="$removed_bands, $band" + else + removed_bands="$band" + fi + fi + done + + if [ -n "$added_bands" ] && [ -n "$removed_bands" ]; then + interpretations="${interpretations}Band configuration changed - Added: $added_bands, Removed: $removed_bands" + elif [ -n "$added_bands" ]; then + interpretations="${interpretations}New bands added: $added_bands" + elif [ -n "$removed_bands" ]; then + interpretations="${interpretations}Bands removed: $removed_bands" + else + interpretations="${interpretations}Band sequence changed from ($base_band_list) to ($new_band_list)" + fi + fi + + # Carrier Aggregation changes + if [ "$base_carrier_count" != "$new_carrier_count" ]; then + if [ -n "$interpretations" ]; then + interpretations="$interpretations; " + fi + + if [ "$new_carrier_count" -gt 1 ] && [ "$base_carrier_count" -le 1 ]; then + interpretations="${interpretations}Carrier Aggregation activated - Now using $new_carrier_count carriers" + elif [ "$new_carrier_count" -le 1 ] && [ "$base_carrier_count" -gt 1 ]; then + interpretations="${interpretations}Carrier Aggregation deactivated - Single carrier mode" + elif [ "$new_carrier_count" -gt "$base_carrier_count" ]; then + interpretations="${interpretations}Additional carriers aggregated - Carriers increased from $base_carrier_count to $new_carrier_count" + elif [ "$new_carrier_count" -lt "$base_carrier_count" ]; then + interpretations="${interpretations}Carriers reduced from $base_carrier_count to $new_carrier_count" + fi + fi + fi + + # Return interpretation if any changes detected + if [ -n "$interpretations" ]; then + echo "$interpretations" + fi +} + +# Add interpretation to JSON file +add_interpretation() { + local datetime="$1" + local interpretation="$2" + + # Initialize file if it doesn't exist + if [ ! -f "$INTERPRETED_FILE" ]; then + echo "[]" > "$INTERPRETED_FILE" + fi + + # Add new interpretation using jq + local temp_file="${INTERPRETED_FILE}.tmp.$$" + jq --arg dt "$datetime" \ + --arg interp "$interpretation" \ + '. + [{"datetime": $dt, "interpretation": $interp}] | .[-'"$MAX_INTERPRETATIONS"':]' \ + "$INTERPRETED_FILE" > "$temp_file" 2>/dev/null && mv "$temp_file" "$INTERPRETED_FILE" + + chmod 644 "$INTERPRETED_FILE" + log_message "Added interpretation: $interpretation" +} + +# Process QCAINFO entries and generate interpretations +process_qcainfo_data() { + if [ ! -f "$QCAINFO_FILE" ]; then + log_message "QCAINFO file not found: $QCAINFO_FILE" + return 1 + fi + + # Get total number of entries + local total_entries=$(jq 'length' "$QCAINFO_FILE" 2>/dev/null || echo "0") + + if [ "$total_entries" -lt 2 ]; then + log_message "Not enough entries to compare ($total_entries)" + return 0 + fi + + # Get the last processed entry timestamp + local last_processed="" + if [ -f "$LAST_ENTRY_FILE" ]; then + last_processed=$(cat "$LAST_ENTRY_FILE" 2>/dev/null) + fi + + # Process entries sequentially + local i=0 + while [ "$i" -lt $((total_entries - 1)) ]; do + local base_entry=$(jq -r ".[$i]" "$QCAINFO_FILE" 2>/dev/null) + local next_entry=$(jq -r ".[$(($i + 1))]" "$QCAINFO_FILE" 2>/dev/null) + + local base_datetime=$(echo "$base_entry" | jq -r '.datetime' 2>/dev/null) + local next_datetime=$(echo "$next_entry" | jq -r '.datetime' 2>/dev/null) + local base_output=$(echo "$base_entry" | jq -r '.output' 2>/dev/null) + local next_output=$(echo "$next_entry" | jq -r '.output' 2>/dev/null) + + # Skip if this entry was already processed + if [ -n "$last_processed" ] && [ "$next_datetime" = "$last_processed" ]; then + i=$((i + 1)) + continue + fi + + # Only process entries after the last processed one + if [ -n "$last_processed" ]; then + if ! is_datetime_newer "$next_datetime" "$last_processed"; then + i=$((i + 1)) + continue + fi + fi + + # Compare configurations and generate interpretation + local interpretation=$(compare_configurations "$base_output" "$next_output" "$base_datetime" "$next_datetime") + + if [ -n "$interpretation" ]; then + add_interpretation "$next_datetime" "$interpretation" + fi + + i=$((i + 1)) + done + + # Update last processed entry + if [ "$total_entries" -gt 0 ]; then + local last_datetime=$(jq -r '.[-1].datetime' "$QCAINFO_FILE" 2>/dev/null) + echo "$last_datetime" > "$LAST_ENTRY_FILE" + fi +} + +# Check for new entries every 61 seconds +monitor_qcainfo() { + log_message "Starting network insights interpreter monitoring" + + while true; do + # Acquire lock (OpenWrt compatible) + if (set -C; echo $$ > "$LOCKFILE") 2>/dev/null; then + trap 'rm -f "$LOCKFILE"; exit' INT TERM EXIT + + process_qcainfo_data + + # Release lock + rm -f "$LOCKFILE" + trap - INT TERM EXIT + else + log_message "Another instance is running, skipping this cycle" + fi + + sleep 61 + done +} + +# Main execution +case "${1:-monitor}" in + "monitor") + monitor_qcainfo + ;; + "process") + process_qcainfo_data + ;; + *) + echo "Usage: $0 {monitor|process}" + echo " monitor - Run continuous monitoring (default)" + echo " process - Process current data once" + exit 1 + ;; +esac diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/ping_daemon.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/ping_daemon.sh new file mode 100644 index 0000000..fb8e9e3 --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/ping_daemon.sh @@ -0,0 +1,137 @@ +#!/bin/sh + +set -eu + +# Ensure PATH for OpenWrt/BusyBox +export PATH="/usr/sbin:/usr/bin:/sbin:/bin:$PATH" + +# Load centralized logging +. /www/cgi-bin/services/quecmanager_logger.sh + +TMP_DIR="/tmp/quecmanager" +OUT_JSON="$TMP_DIR/ping_latency.json" +PID_FILE="$TMP_DIR/ping_daemon.pid" +CONFIG_FILE="/etc/quecmanager/settings/ping_settings.conf" +[ -f "$CONFIG_FILE" ] || CONFIG_FILE="/tmp/quecmanager/settings/ping_settings.conf" +DEFAULT_HOST="8.8.8.8" +DEFAULT_INTERVAL=5 +SCRIPT_NAME="ping_daemon" + +ensure_tmp_dir() { [ -d "$TMP_DIR" ] || mkdir -p "$TMP_DIR" || exit 1; } + +log() { + qm_log_info "daemon" "$SCRIPT_NAME" "$1" +} + +daemon_is_running() { + if [ -f "$PID_FILE" ]; then + pid="$(cat "$PID_FILE" 2>/dev/null || true)" + if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then + # Avoid false positive if PID reused + if [ -r "/proc/$pid/cmdline" ] && grep -q "ping_daemon.sh" "/proc/$pid/cmdline" 2>/dev/null; then + return 0 + else + rm -f "$PID_FILE" 2>/dev/null || true + fi + fi + fi + return 1 +} + +write_pid() { echo "$$" > "$PID_FILE"; } + +cleanup() { rm -f "$PID_FILE" 2>/dev/null || true; } + +read_config() { + ENABLED="true"; HOST="$DEFAULT_HOST"; INTERVAL="$DEFAULT_INTERVAL" + if [ -f "$CONFIG_FILE" ]; then + PING_ENABLED=$(grep -E "^PING_ENABLED=" "$CONFIG_FILE" | tail -n1 | cut -d'=' -f2 | tr -d '\r') || true + PING_HOST=$(grep -E "^PING_HOST=" "$CONFIG_FILE" | tail -n1 | cut -d'=' -f2 | tr -d '\r') || true + PING_INTERVAL=$(grep -E "^PING_INTERVAL=" "$CONFIG_FILE" | tail -n1 | cut -d'=' -f2 | tr -d '\r') || true + case "${PING_ENABLED:-}" in true|1|on|yes|enabled) ENABLED=true ;; *) ENABLED=false ;; esac + [ -n "${PING_HOST:-}" ] && HOST="$PING_HOST" + if echo "${PING_INTERVAL:-}" | grep -qE '^[0-9]+$'; then + if [ "$PING_INTERVAL" -ge 1 ] && [ "$PING_INTERVAL" -le 3600 ]; then + INTERVAL="$PING_INTERVAL" + fi + fi + fi +} + +# Create default config if none exists +create_default_config() { + local primary_config="/etc/quecmanager/settings/ping_settings.conf" + local fallback_config="/tmp/quecmanager/settings/ping_settings.conf" + + # Check if either config exists + if [ -f "$primary_config" ] || [ -f "$fallback_config" ]; then + return 0 + fi + + # Try to create in primary location first + if mkdir -p "/etc/quecmanager/settings" 2>/dev/null; then + { + echo "PING_ENABLED=true" + echo "PING_HOST=$DEFAULT_HOST" + echo "PING_INTERVAL=$DEFAULT_INTERVAL" + } > "$primary_config" 2>/dev/null && { + chmod 644 "$primary_config" 2>/dev/null || true + CONFIG_FILE="$primary_config" + log "Created default config at $primary_config" + return 0 + } + fi + + # Fallback to tmp location + mkdir -p "/tmp/quecmanager/settings" 2>/dev/null || true + { + echo "PING_ENABLED=true" + echo "PING_HOST=$DEFAULT_HOST" + echo "PING_INTERVAL=$DEFAULT_INTERVAL" + } > "$fallback_config" && { + chmod 644 "$fallback_config" 2>/dev/null || true + CONFIG_FILE="$fallback_config" + log "Created default config at $fallback_config" + return 0 + } + + log "Failed to create default config file" + return 1 +} + +write_json_atomic() { + tmpfile="$(mktemp "$TMP_DIR/ping_latency.XXXXXX" 2>/dev/null || true)" + if [ -n "${tmpfile:-}" ] && [ -w "$TMP_DIR" ]; then + printf '%s' "$1" > "$tmpfile" 2>/dev/null || true + mv -f "$tmpfile" "$OUT_JSON" 2>/dev/null || printf '%s' "$1" > "$OUT_JSON" + else + printf '%s' "$1" > "$OUT_JSON" + fi +} + +ensure_tmp_dir +log "Starting ping daemon" +if daemon_is_running; then log "Already running"; exit 0; fi + +# Create default config if none exists +create_default_config + +trap cleanup EXIT INT TERM +write_pid + +while true; do + read_config + if [ "$ENABLED" != "true" ]; then log "Disabled in config"; exit 0; fi + ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + PING_BIN="$(command -v ping || echo /bin/ping)" + output="$("$PING_BIN" -c 1 -w 2 "$HOST" 2>/dev/null || true)" + if echo "$output" | grep -q "time="; then + latency_ms="$(echo "$output" | grep -o 'time=[0-9.]*' | head -n1 | cut -d'=' -f2 | cut -d'.' -f1)"; [ -z "$latency_ms" ] && latency_ms=0 + json="{\"timestamp\":\"$ts\",\"host\":\"$HOST\",\"latency\":$latency_ms,\"ok\":true}" + else + json="{\"timestamp\":\"$ts\",\"host\":\"$HOST\",\"latency\":null,\"ok\":false}" + fi + write_json_atomic "$json" + log "Wrote: $json" + sleep "$INTERVAL" +done diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecmanager_logger.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecmanager_logger.sh new file mode 100644 index 0000000..2f3f27f --- /dev/null +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecmanager_logger.sh @@ -0,0 +1,119 @@ +#!/bin/sh + +# QuecManager Centralized Logging Helper +# OpenWrt/BusyBox compatible logging system +# Usage: source this file and use qm_log function + +set -e + +# Base log directory +QM_LOG_BASE="/tmp/quecmanager/logs" + +# Log categories +QM_LOG_DAEMONS="$QM_LOG_BASE/daemons" +QM_LOG_SERVICES="$QM_LOG_BASE/services" +QM_LOG_SETTINGS="$QM_LOG_BASE/settings" +QM_LOG_SYSTEM="$QM_LOG_BASE/system" + +# Log levels +QM_LOG_ERROR="ERROR" +QM_LOG_WARN="WARN" +QM_LOG_INFO="INFO" +QM_LOG_DEBUG="DEBUG" + +# Maximum log file size (in KB) - keep small for OpenWrt +QM_LOG_MAX_SIZE=500 + +# Initialize log directories +qm_init_logs() { + mkdir -p "$QM_LOG_DAEMONS" "$QM_LOG_SERVICES" "$QM_LOG_SETTINGS" "$QM_LOG_SYSTEM" 2>/dev/null || true +} + +# Get log file path based on category and script name +qm_get_logfile() { + local category="$1" + local script_name="$2" + + case "$category" in + "daemon"|"daemons") + echo "$QM_LOG_DAEMONS/${script_name}.log" + ;; + "service"|"services") + echo "$QM_LOG_SERVICES/${script_name}.log" + ;; + "setting"|"settings") + echo "$QM_LOG_SETTINGS/${script_name}.log" + ;; + "system") + echo "$QM_LOG_SYSTEM/${script_name}.log" + ;; + *) + echo "$QM_LOG_SYSTEM/unknown.log" + ;; + esac +} + +# Simple log rotation - keep it OpenWrt compatible +qm_rotate_log() { + local logfile="$1" + + if [ -f "$logfile" ]; then + # Get file size in KB (use du for BusyBox compatibility) + local size_kb=$(du -k "$logfile" 2>/dev/null | cut -f1) + + if [ "${size_kb:-0}" -gt "$QM_LOG_MAX_SIZE" ]; then + # Simple rotation: keep last 2 versions + [ -f "${logfile}.1" ] && mv "${logfile}.1" "${logfile}.2" 2>/dev/null || true + mv "$logfile" "${logfile}.1" 2>/dev/null || true + touch "$logfile" 2>/dev/null || true + fi + fi +} + +# Main logging function +# Usage: qm_log "category" "script_name" "level" "message" +qm_log() { + local category="$1" + local script_name="$2" + local level="$3" + local message="$4" + + # Initialize if needed + qm_init_logs + + # Get log file path + local logfile=$(qm_get_logfile "$category" "$script_name") + + # Rotate if needed + qm_rotate_log "$logfile" + + # Create log entry with OpenWrt compatible date + local timestamp=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date) + local pid="$$" + + # Write log entry + printf '[%s] [%s] [%s] [PID:%s] %s\n' "$timestamp" "$level" "$script_name" "$pid" "$message" >> "$logfile" 2>/dev/null || true +} + +# Convenience functions for different log levels +qm_log_error() { + qm_log "$1" "$2" "$QM_LOG_ERROR" "$3" +} + +qm_log_warn() { + qm_log "$1" "$2" "$QM_LOG_WARN" "$3" +} + +qm_log_info() { + qm_log "$1" "$2" "$QM_LOG_INFO" "$3" +} + +qm_log_debug() { + qm_log "$1" "$2" "$QM_LOG_DEBUG" "$3" +} + +# Cleanup old logs (called periodically) +qm_cleanup_logs() { + # Remove .2 backup files older than 1 day to save space + find "$QM_LOG_BASE" -name "*.2" -type f -mtime +1 -delete 2>/dev/null || true +} diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecprofile.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecprofile.sh index bb6e69b..eac608d 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecprofile.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecprofile.sh @@ -2,6 +2,9 @@ # Updated QuecProfiles daemon with enhanced SA/NSA NR5G band management and TTL support # Including profile application functions and fixed comparison logic +# Load centralized logging +. /www/cgi-bin/services/quecmanager_logger.sh + # Configuration QUEUE_DIR="/tmp/at_queue" TOKEN_FILE="$QUEUE_DIR/token" @@ -15,25 +18,49 @@ DEFAULT_CHECK_INTERVAL=60 # Default check interval in seconds COMMAND_TIMEOUT=10 # Default timeout for AT commands in seconds QUEUE_PRIORITY=3 # Medium-high priority (1 is highest for cell scan) MAX_TOKEN_WAIT=15 # Maximum seconds to wait for token acquisition +SCRIPT_NAME_LOG="quecprofiles_daemon" -# Initialize log file -echo "$(date) - Starting QuecProfiles daemon with SA/NSA NR5G and TTL support (PID: $$)" >"$DEBUG_LOG" -echo "$(date) - Starting QuecProfiles daemon with SA/NSA NR5G and TTL support (PID: $$)" >"$DETAILED_LOG" +# Initialize log files and use centralized logging +mkdir -p "$(dirname "$DEBUG_LOG")" "$(dirname "$DETAILED_LOG")" +touch "$DEBUG_LOG" "$DETAILED_LOG" chmod 644 "$DEBUG_LOG" "$DETAILED_LOG" -# Function to log messages +# Log startup message using centralized logging +qm_log_info "service" "$SCRIPT_NAME_LOG" "Starting QuecProfiles daemon with SA/NSA NR5G and TTL support (PID: $$)" + +# Also maintain file logging for compatibility +echo "$(date) - Starting QuecProfiles daemon with SA/NSA NR5G and TTL support (PID: $$)" >"$DEBUG_LOG" +echo "$(date) - Starting QuecProfiles daemon with SA/NSA NR5G and TTL support (PID: $$)" >"$DETAILED_LOG" + +# Function to log messages - now uses centralized logging log_message() { local message="$1" local level="${2:-info}" local timestamp=$(date "+%Y-%m-%d %H:%M:%S") - # Log to system log + # Use centralized logging + case "$level" in + "error") + qm_log_error "service" "$SCRIPT_NAME_LOG" "$message" + ;; + "warn") + qm_log_warn "service" "$SCRIPT_NAME_LOG" "$message" + ;; + "debug") + qm_log_debug "service" "$SCRIPT_NAME_LOG" "$message" + ;; + *) + qm_log_info "service" "$SCRIPT_NAME_LOG" "$message" + ;; + esac + + # Also maintain system logging for compatibility logger -t quecprofiles_daemon -p "daemon.$level" "$message" - # Log to debug file + # Log to debug file (maintain existing behavior) echo "[$timestamp] [$level] $message" >>"$DEBUG_LOG" - # For detailed logs or errors + # For detailed logs or errors (maintain existing behavior) if [ "$level" = "error" ] || [ "$level" = "debug" ]; then echo "[$timestamp] [$level] $message" >>"$DETAILED_LOG" fi @@ -607,6 +634,7 @@ apply_profile_settings() { local current_nsa_nr5g_bands="${14}" local current_imei="${15}" local iccid="${16}" + local mobile_provider="${17}" # Set TTL to 0 (disabled) if not specified ttl="${ttl:-0}" @@ -619,6 +647,7 @@ apply_profile_settings() { log_message "- APN: $apn ($pdp_type)" "info" log_message "- IMEI: $imei" "info" log_message "- TTL: $ttl" "info" + log_message "- Mobile Provider: $mobile_provider" "info" # Check if any changes are needed using improved comparison local needs_apn_change=0 @@ -630,6 +659,7 @@ apply_profile_settings() { local needs_ttl_change=0 local changes_needed=0 local requires_reboot=0 + local change_for_reboot="" # Use normalized comparison compare_values "$current_apn" "$apn" "apn" && needs_apn_change=1 && changes_needed=1 @@ -804,6 +834,7 @@ apply_profile_settings() { if [ $? -eq 0 ]; then changes_made=1 requires_reboot=1 + change_for_reboot="IMEI" log_message "IMEI changed successfully to $imei (device will reboot)" "info" update_track "rebooting" "IMEI changed, device will reboot" "$profile_name" "95" else @@ -813,9 +844,56 @@ apply_profile_settings() { fi fi + # Apply unique rule setup for Verizon, but also handle "Other" Mobile Providers because of MPDN_rule shenanigans + # Probably requires reboot + output_check=$(execute_at_command "AT+QMAP=\"mpdn_rule\"") + sleep 1 # Short delay to ensure command is processed + qmap_rule0=$(echo "$output_check" | grep '+QMAP: "MPDN_rule",0,') + qmap_ippt_rule0=$(echo "$qmap_rule0" | cut -d',' -f5) + if [ $apply_success -eq 1 ] && [ -n "$mobile_provider" ]; then + if [ "$mobile_provider" = "Verizon" ]; then + # If Verizon, data call should be set to rule 3, AT+QMAP="mpdn_rule",0,3,0,0,1 + if echo "$qmap_rule0" | awk -F',' '{exit !($2==0 && $3==3 && $6==1)}'; then + log_message "Verizon rule already set correctly, no changes needed" "info" + else + log_message "Setting Verizon data call mpdn_rule to 3" "info" + update_track "applying" "Setting Verizon data call rule to 3" "$profile_name" "100" + verizon_cmd="AT+QMAP=\"mpdn_rule\",0,3,0,$qmap_ippt_rule0,1" + execute_at_command "$verizon_cmd" 10 "$token_id" >/dev/null + sleep 1 # Short delay to ensure command is processed + fi + elif [ "$mobile_provider" = "Other" ]; then + # Check if MPDN_rule 0 is already set to all zeros + if echo "$qmap_rule0" | awk -F',' '{exit !($2==0 && $3==0 && $6==0)}'; then + log_message "Default rule already set correctly, no changes needed" "info" + else + log_message "Setting to default mpdn_rule and releasing" "info" + update_track "applying" "Setting Default data call mpdn_rule to 0" "$profile_name" "100" + def_cmd1="AT+QMAP=\"mpdn_rule\",0" + execute_at_command "$def_cmd1" 10 "$token_id" + sleep 1 # Short delay to ensure command is processed + def_cmd2="AT+QMAP=\"mpdn_rule\",0,1,0,$qmap_ippt_rule0,1" + execute_at_command "$def_cmd2" 10 "$token_id" + sleep 1 # Short delay to ensure command is processed + if [ "$qmap_ippt_rule0" = "0" ]; then + log_message "IPPT is disabled for rule, release the MPDN_rule" "info" + def_cmd3="AT+QMAP=\"mpdn_rule\",0" + execute_at_command "$def_cmd3" 10 "$token_id" + sleep 1 # Short delay to ensure command is processed + if [ "$(cat /sys/devices/soc0/machine)" = "SDXPINN" ]; then + requires_reboot=1 + change_for_reboot="MPDN_rule" + update_track "rebooting" "MPDN_rule released, device will reboot" "$profile_name" "105" + fi + else + log_message "IPPT is enabled for rule0 not releasing MPDN_rule, no reboot needed: IPPT Value $qmap_ippt_rule0" "info" + fi + fi + fi + fi + # Release token release_token "$token_id" - # Mark profile as applied if changes were made if [ $changes_made -eq 1 ]; then mark_profile_applied "$iccid" "$profile_name" @@ -824,7 +902,7 @@ apply_profile_settings() { # If IMEI was changed, need to reboot if [ $requires_reboot -eq 1 ]; then log_message "IMEI change requires reboot, scheduling reboot..." "info" - update_track "rebooting" "Device is rebooting to apply IMEI change" "$profile_name" "100" + update_track "rebooting" "Device is rebooting to apply $change_for_reboot change" "$profile_name" "100" sleep 2 reboot & return 0 @@ -913,11 +991,12 @@ check_profile() { local pdp_type=$(uci -q get quecprofiles.$profile_index.pdp_type) local imei=$(uci -q get quecprofiles.$profile_index.imei) local ttl=$(uci -q get quecprofiles.$profile_index.ttl) - + local mobile_provider=$(uci -q get quecprofiles.$profile_index.mobile_provider) + # Check if profile is paused local paused=$(uci -q get quecprofiles.$profile_index.paused) paused="${paused:-0}" # Default to not paused if not set - + # Skip applying paused profiles if [ "$paused" = "1" ]; then log_message "Profile '$profile_name' is paused, skipping application" "info" @@ -982,7 +1061,7 @@ check_profile() { # Apply profile settings with the new parameters apply_profile_settings "$profile_name" "$network_type" "$lte_bands" "$sa_nr5g_bands" "$nsa_nr5g_bands" \ "$apn" "$pdp_type" "$imei" "$ttl" "$current_apn" "$current_mode" "$current_lte_bands" \ - "$current_sa_nr5g_bands" "$current_nsa_nr5g_bands" "$current_imei" "$current_iccid" + "$current_sa_nr5g_bands" "$current_nsa_nr5g_bands" "$current_imei" "$current_iccid" "$mobile_provider" return $? else log_message "Automatic profile switching is disabled, not applying profile" "info" @@ -1038,7 +1117,7 @@ main() { while [ $sleep_counter -lt $check_interval ]; do sleep 5 sleep_counter=$((sleep_counter + 5)) - + # Check for manual trigger during sleep if [ -f "$CHECK_TRIGGER" ]; then log_message "Manual check triggered during sleep" "info" diff --git a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecwatch.sh b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecwatch.sh index 8f4c912..df12391 100755 --- a/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecwatch.sh +++ b/ipk-source/sdxpinn-quecmanager/root/www/cgi-bin/services/quecwatch.sh @@ -3,6 +3,9 @@ # QuecWatch Daemon # Monitors cellular connectivity and performs recovery actions +# Load centralized logging +. /www/cgi-bin/services/quecmanager_logger.sh + # Load UCI configuration functions . /lib/functions.sh @@ -17,6 +20,7 @@ RETRY_COUNT_FILE="/tmp/quecwatch_retry_count" UCI_CONFIG="quecmanager" MAX_TOKEN_WAIT=10 # Maximum seconds to wait for token acquisition TOKEN_PRIORITY=15 # Medium priority (between profiles and metrics) +SCRIPT_NAME_LOG="quecwatch" # Ensure directories exist mkdir -p "$LOG_DIR" "$QUEUE_DIR" @@ -25,17 +29,33 @@ mkdir -p "$LOG_DIR" "$QUEUE_DIR" echo "$$" > "$PID_FILE" chmod 644 "$PID_FILE" -# Function to log messages +# Function to log messages - now uses centralized logging log_message() { local level="${2:-info}" local message="$1" local timestamp=$(date "+%Y-%m-%d %H:%M:%S") - # Log to file - echo "[$timestamp] [$level] $message" >> "$LOG_FILE" + # Use centralized logging + case "$level" in + "error") + qm_log_error "service" "$SCRIPT_NAME_LOG" "$message" + ;; + "warn") + qm_log_warn "service" "$SCRIPT_NAME_LOG" "$message" + ;; + "debug") + qm_log_debug "service" "$SCRIPT_NAME_LOG" "$message" + ;; + *) + qm_log_info "service" "$SCRIPT_NAME_LOG" "$message" + ;; + esac - # Log to system log + # Also maintain system logging for compatibility logger -t quecwatch -p "daemon.$level" "$message" + + # Log to file (maintain existing behavior) + echo "[$timestamp] [$level] $message" >> "$LOG_FILE" } # Function to update status