Copy QuecManager beta to non-beta

QM BETA --> regular/non-beta
This commit is contained in:
Cameron Thompson
2025-08-31 02:17:49 -04:00
parent 6d6a3775c4
commit 02dafc73ad
391 changed files with 4494 additions and 740 deletions

View File

@@ -2,6 +2,7 @@
# AT Queue Client for OpenWRT
# Located in /www/cgi-bin/services/at_queue_client
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"
@@ -184,10 +185,36 @@ if [ "${SCRIPT_NAME}" != "" ]; then
# Output headers only once at the beginning
echo "Content-Type: application/json"
echo ""
# Get Token from Authorization Header
TOKEN="${HTTP_AUTHORIZATION}"
if [ ! -f $AUTH_FILE ]; then
output_json "{\"error\":\"Unauthenticated Request\"}" "0"
exit 1
fi
if [ -z "$TOKEN" ] || "${TOKEN}" = "" || [ $(grep "${TOKEN}" "${AUTH_FILE}" | wc -l) -eq 0 ]; then
output_json "{\"response\": { \"status\": \"error\", \"raw_output\": \"Not Authorized\" }, \"command\": {\"timestamp\": \"$(date +%Y%m%d'T'%H%M%S)\"}, \"error\":\"Not Authorized\"}" "0"
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
output_json "{ \"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\"}" "0"
# Cleanup/Remove token from file
sed -i -e "s/.*${TOKEN}.*//g" /tmp/auth_success 2>/dev/null
exit 1
fi
# Parse query string
eval $(echo "$QUERY_STRING" | sed 's/&/;/g')
# Handle different actions
if [ -n "$command_id" ]; then
# Get result for specific command ID
@@ -196,13 +223,13 @@ if [ "${SCRIPT_NAME}" != "" ]; then
# URL decode and normalize the command
command=$(urldecode "$command")
command=$(normalize_at_command "$command")
# Check if it's a valid AT command
if echo "$command" | grep -qi "^AT"; then
# Submit command and get response
response=$(submit_command "$command")
cmd_id=$(get_command_id "$response")
if [ "$wait" = "1" ]; then
if [ -n "$cmd_id" ]; then
wait_for_completion "$cmd_id" "${timeout:-180}" "0" # Don't show headers

View File

@@ -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

View File

@@ -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
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"

View File

@@ -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
}

View File

@@ -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"

View File

@@ -1,9 +1,16 @@
#!/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"
SPEEDTEST_SCRIPT="/www/cgi-bin/home/speedtest/speedtest.sh"
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() {
@@ -35,6 +42,49 @@ validate_interval() {
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
@@ -49,10 +99,10 @@ generate_cron_time() {
# 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 * * * $SPEEDTEST_SCRIPT"
echo "*/$INTERVAL 0-$((END_HOUR - 1)) * * * $SPEEDTEST_SCRIPT"
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)) * * * $SPEEDTEST_SCRIPT"
echo "*/$INTERVAL $START_HOUR-$((END_HOUR - 1)) * * * $KEEP_ALIVE_SCRIPT"
fi
}
@@ -75,7 +125,10 @@ disable_scheduling() {
sed -i 's/ENABLED=1/ENABLED=0/' "$CONFIG_FILE"
fi
# Remove any existing cron jobs
crontab -l | grep -v "$SPEEDTEST_SCRIPT" | crontab -
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
@@ -86,15 +139,21 @@ get_status() {
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}"
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}"
echo "{\"enabled\":0,\"start_time\":\"\",\"end_time\":\"\",\"interval\":0,\"last_activity\":\"\"}"
fi
}
@@ -110,7 +169,7 @@ if [ "$REQUEST_METHOD" = "POST" ]; then
echo "Status: 200 OK"
echo "Content-Type: application/json"
echo ""
echo "{\"status\":\"success\",\"message\":\"Scheduling disabled\"}"
echo "{\"status\":\"success\",\"message\":\"Keep-alive scheduling disabled\"}"
exit 0
fi
@@ -142,6 +201,15 @@ if [ "$REQUEST_METHOD" = "POST" ]; then
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"
@@ -151,11 +219,14 @@ if [ "$REQUEST_METHOD" = "POST" ]; then
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 "$SPEEDTEST_SCRIPT" >"$TEMP_CRON"
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"
@@ -167,10 +238,13 @@ if [ "$REQUEST_METHOD" = "POST" ]; then
# 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\"}"
echo "{\"status\":\"success\",\"message\":\"Keep-alive scheduling enabled with download method\"}"
exit 0
fi

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 || \

View File

@@ -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

View File

@@ -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}}"

View File

@@ -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

View File

@@ -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

View File

@@ -1,24 +0,0 @@
#!/bin/sh
# Set the content type to JSON
echo "Content-Type: application/json"
echo ""
# Ping 8.8.8.8 with 5 packets and capture the full output
ping_result=$(ping -c 5 8.8.8.8)
# Check if ping was successful
if [ $? -eq 0 ]; then
# Extract the average latency using awk
avg_latency=$(echo "$ping_result" | awk '/avg/ {split($4, a, "/"); print int(a[2])}')
# If average latency was extracted, return it
if [ ! -z "$avg_latency" ]; then
echo "{\"connection\": \"ACTIVE\", \"latency\": $avg_latency}"
else
echo '{"connection": "ACTIVE", "latency": 0}'
fi
else
# Ping failed
echo '{"connection": "INACTIVE", "latency": 0}'
fi

View File

@@ -19,7 +19,7 @@ chmod 644 $STATUS_FILE
# Run speedtest in background and pipe output to status file
(
export HOME=/tmp/home
/usr/bin/speedtest --accept-license -f json -p yes --progress-update-interval=100 | \
/usr/bin/speedtest --accept-license --accept-gdpr -f json -p yes --progress-update-interval=100 | \
while IFS= read -r line; do
# Update status file with latest JSON data
echo "$line" > $STATUS_FILE

View File

@@ -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"}'

View File

@@ -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 <<EOF
{

View File

@@ -34,35 +34,35 @@ fi
# Function to extract profiles from UCI config
get_profiles() {
log_message "Fetching profiles from UCI config"
# Check if UCI config exists
if [ ! -f /etc/config/quecprofiles ]; then
log_message "No profiles config found" "warn"
echo "{\"status\":\"success\",\"profiles\":[]}"
return 0
fi
# Start JSON output
local json_output=""
local first=1
local count=0
# Get all profile indices - make sure this succeeds
local indices=$(uci -q show quecprofiles | grep -o '@profile\[[0-9]*\]' | sort -u)
# Debug output
echo "Found indices: $indices" >>/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

View File

@@ -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}"

View File

@@ -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 <<EOF
delete quecprofiles.$profile_index
commit quecprofiles
EOF
# Check if the operation was successful
if [ $? -eq 0 ]; then
log_message "Successfully deleted profile '$profile_name'" "info"
@@ -80,14 +80,14 @@ iccid=""
if [ "$REQUEST_METHOD" = "POST" ]; then
# Get content length
CONTENT_LENGTH=$(echo "$CONTENT_LENGTH" | tr -cd '0-9')
if [ -n "$CONTENT_LENGTH" ]; then
# Read POST data
POST_DATA=$(dd bs=1 count=$CONTENT_LENGTH 2>/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

View File

@@ -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 <<EOF
@@ -183,6 +184,7 @@ set quecprofiles.$profile_index.sa_nr5g_bands='$sa_nr5g_bands'
set quecprofiles.$profile_index.nsa_nr5g_bands='$nsa_nr5g_bands'
set quecprofiles.$profile_index.network_type='$network_type'
set quecprofiles.$profile_index.ttl='$ttl'
set quecprofiles.$profile_index.mobile_provider='$mobile_provider'
commit quecprofiles
EOF
@@ -237,6 +239,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
@@ -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."}'

View File

@@ -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

View File

@@ -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\"}"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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