Merging 2.2.6 release candidate

This commit is contained in:
Russel Yasol
2025-08-07 10:17:34 +08:00
parent 384c055278
commit 619502a100
200 changed files with 1774 additions and 472 deletions

View File

@@ -54,13 +54,20 @@ 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
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}
# 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

View File

@@ -0,0 +1,269 @@
#!/bin/sh
# Keep-Alive Scheduling Script
# This script allows scheduling of keep-alive requests to prevent the connection from being closed.
# It supports setting a time interval during which the keep-alive requests will be made.
# It uses a worker script to perform the actual keep-alive requests by downloading a test file.
# Configuration
CONFIG_FILE="/etc/keep_alive_schedule.conf"
STATUS_FILE="/tmp/keep_alive_status"
KEEP_ALIVE_SCRIPT="/www/cgi-bin/quecmanager/experimental/keep_alive_worker.sh"
TEST_URL="https://ash-speed.hetzner.com/100MB.bin"
TEMP_FILE="/tmp/keep_alive_test.bin"
# Function to convert HH:MM to minutes since midnight
time_to_minutes() {
echo "$1" | awk -F: '{print $1 * 60 + $2}'
}
# Function to validate time interval
validate_interval() {
START_TIME=$1
END_TIME=$2
INTERVAL_MINUTES=$3
# Convert times to minutes
START_MINUTES=$(time_to_minutes "$START_TIME")
END_MINUTES=$(time_to_minutes "$END_TIME")
# Calculate duration between start and end time
if [ $END_MINUTES -lt $START_MINUTES ]; then
# Handle case where end time is on the next day
DURATION=$((1440 - START_MINUTES + END_MINUTES))
else
DURATION=$((END_MINUTES - START_MINUTES))
fi
# Check if interval is longer than duration
if [ $INTERVAL_MINUTES -gt $DURATION ]; then
return 1
fi
return 0
}
# Function to create the keep-alive worker script
create_worker_script() {
cat > "$KEEP_ALIVE_SCRIPT" << 'EOF'
#!/bin/sh
TEST_URL="https://ash-speed.hetzner.com/100MB.bin"
TEMP_FILE="/tmp/keep_alive_test.bin"
# Function to perform keep-alive test
perform_keep_alive() {
# Download the test file in background
wget -q -O "$TEMP_FILE" "$TEST_URL" &
WGET_PID=$!
# Wait for download to complete or timeout after 30 seconds
COUNTER=0
while [ $COUNTER -lt 30 ]; do
if ! kill -0 $WGET_PID 2>/dev/null; then
break
fi
sleep 1
COUNTER=$((COUNTER + 1))
done
# If download is still running, kill it
if kill -0 $WGET_PID 2>/dev/null; then
kill $WGET_PID 2>/dev/null
fi
# Wait 3 seconds then delete the file
sleep 3
#rm -f "$TEMP_FILE"
# Log the activity
echo "$(date): Keep-alive test performed" >> /tmp/keep_alive.log
}
# Execute the keep-alive test
perform_keep_alive
EOF
chmod +x "$KEEP_ALIVE_SCRIPT"
}
# Function to generate cron time expression
generate_cron_time() {
START_TIME=$1
END_TIME=$2
INTERVAL=$3
START_HOUR=$(echo "$START_TIME" | cut -d: -f1 | sed 's/^0//')
START_MIN=$(echo "$START_TIME" | cut -d: -f2)
END_HOUR=$(echo "$END_TIME" | cut -d: -f1 | sed 's/^0//')
END_MIN=$(echo "$END_TIME" | cut -d: -f2)
# If end time is less than start time, it means we cross midnight
if [ $(time_to_minutes "$END_TIME") -lt $(time_to_minutes "$START_TIME") ]; then
# Create two cron entries for before and after midnight
echo "*/$INTERVAL $START_HOUR-23 * * * $KEEP_ALIVE_SCRIPT"
echo "*/$INTERVAL 0-$((END_HOUR - 1)) * * * $KEEP_ALIVE_SCRIPT"
else
echo "*/$INTERVAL $START_HOUR-$((END_HOUR - 1)) * * * $KEEP_ALIVE_SCRIPT"
fi
}
# Function to urldecode
urldecode() {
echo -e "$(echo "$1" | sed 's/+/ /g;s/%\([0-9A-F][0-9A-F]\)/\\x\1/g')"
}
# Function to save configuration
save_config() {
echo "START_TIME=$1" >"$CONFIG_FILE"
echo "END_TIME=$2" >>"$CONFIG_FILE"
echo "INTERVAL=$3" >>"$CONFIG_FILE"
echo "ENABLED=1" >>"$CONFIG_FILE"
}
# Function to disable scheduling
disable_scheduling() {
if [ -f "$CONFIG_FILE" ]; then
sed -i 's/ENABLED=1/ENABLED=0/' "$CONFIG_FILE"
fi
# Remove any existing cron jobs
crontab -l | grep -v "$KEEP_ALIVE_SCRIPT" | crontab -
# Clean up temporary files
rm -f "$TEMP_FILE"
rm -f "$KEEP_ALIVE_SCRIPT"
}
# Function to get current status
get_status() {
if [ -f "$CONFIG_FILE" ]; then
ENABLED=$(grep "ENABLED=" "$CONFIG_FILE" | cut -d'=' -f2)
START_TIME=$(grep "START_TIME=" "$CONFIG_FILE" | cut -d'=' -f2)
END_TIME=$(grep "END_TIME=" "$CONFIG_FILE" | cut -d'=' -f2)
INTERVAL=$(grep "INTERVAL=" "$CONFIG_FILE" | cut -d'=' -f2)
# Check if log file exists and get last activity
LAST_ACTIVITY=""
if [ -f "/tmp/keep_alive.log" ]; then
LAST_ACTIVITY=$(tail -n 1 /tmp/keep_alive.log | cut -d: -f1-3)
fi
echo "Status: 200 OK"
echo "Content-Type: application/json"
echo ""
echo "{\"enabled\":$ENABLED,\"start_time\":\"$START_TIME\",\"end_time\":\"$END_TIME\",\"interval\":$INTERVAL,\"last_activity\":\"$LAST_ACTIVITY\"}"
else
echo "Status: 200 OK"
echo "Content-Type: application/json"
echo ""
echo "{\"enabled\":0,\"start_time\":\"\",\"end_time\":\"\",\"interval\":0,\"last_activity\":\"\"}"
fi
}
# Handle POST requests
if [ "$REQUEST_METHOD" = "POST" ]; then
# Read POST data
read -r POST_DATA
# Check if disabling is requested
echo "$POST_DATA" | grep -q "disable=true"
if [ $? -eq 0 ]; then
disable_scheduling
echo "Status: 200 OK"
echo "Content-Type: application/json"
echo ""
echo "{\"status\":\"success\",\"message\":\"Keep-alive scheduling disabled\"}"
exit 0
fi
# Extract times and interval
START_TIME=$(echo "$POST_DATA" | grep -o 'start_time=[^&]*' | cut -d'=' -f2)
END_TIME=$(echo "$POST_DATA" | grep -o 'end_time=[^&]*' | cut -d'=' -f2)
INTERVAL=$(echo "$POST_DATA" | grep -o 'interval=[^&]*' | cut -d'=' -f2)
# Decode times
START_TIME=$(urldecode "$START_TIME")
END_TIME=$(urldecode "$END_TIME")
INTERVAL=$(urldecode "$INTERVAL")
# Validate times
if [ -z "$START_TIME" ] || [ -z "$END_TIME" ] || [ -z "$INTERVAL" ]; then
echo "Status: 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo "{\"error\":\"Missing start time, end time, or interval\"}"
exit 1
fi
# Validate interval is a number
if ! echo "$INTERVAL" | grep -q '^[0-9]\+$'; then
echo "Status: 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo "{\"error\":\"Interval must be a number in minutes\"}"
exit 1
fi
# Validate interval (minimum 5 minutes to avoid too frequent requests)
if [ "$INTERVAL" -lt 5 ]; then
echo "Status: 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo "{\"error\":\"Interval must be at least 5 minutes\"}"
exit 1
fi
# Validate interval
if ! validate_interval "$START_TIME" "$END_TIME" "$INTERVAL"; then
echo "Status: 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo "{\"error\":\"Interval is longer than the time between start and end time\"}"
exit 1
fi
# Create the worker script
create_worker_script
# Create temporary file for new crontab
TEMP_CRON=$(mktemp)
# Get existing crontab entries (excluding our script)
crontab -l 2>/dev/null | grep -v "$KEEP_ALIVE_SCRIPT" >"$TEMP_CRON"
# Generate and add cron entries
generate_cron_time "$START_TIME" "$END_TIME" "$INTERVAL" >>"$TEMP_CRON"
# Install new crontab
crontab "$TEMP_CRON"
rm "$TEMP_CRON"
# Save configuration
save_config "$START_TIME" "$END_TIME" "$INTERVAL"
# Initialize log file
echo "$(date): Keep-alive scheduling enabled" > /tmp/keep_alive.log
echo "Status: 200 OK"
echo "Content-Type: application/json"
echo ""
echo "{\"status\":\"success\",\"message\":\"Keep-alive scheduling enabled with download method\"}"
exit 0
fi
# Parse query string for GET requests
if [ "$REQUEST_METHOD" = "GET" ]; then
QUERY_STRING=$(echo "$QUERY_STRING" | sed 's/&/\n/g')
for param in $QUERY_STRING; do
case "$param" in
status=*)
get_status
exit 0
;;
esac
done
fi
# If no valid request is made
echo "Status: 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo "{\"error\":\"Invalid request\"}"
exit 1

View File

@@ -1,9 +1,35 @@
#!/bin/sh
# Ping Latency Script with Enable/Disable Configuration
# Author: dr-dolomite
# Date: 2025-08-04
# Set the content type to JSON
echo "Content-Type: application/json"
echo ""
# Configuration
CONFIG_DIR="/etc/quecmanager/settings"
CONFIG_FILE="$CONFIG_DIR/ping_settings.conf"
# Check if ping is enabled (default: enabled if no config exists)
is_ping_enabled() {
# If config file exists, read the setting
if [ -f "$CONFIG_FILE" ]; then
ping_enabled=$(grep "^PING_ENABLED=" "$CONFIG_FILE" | cut -d'=' -f2)
if [ "$ping_enabled" = "false" ] || [ "$ping_enabled" = "0" ] || [ "$ping_enabled" = "off" ]; then
return 1 # Disabled
fi
fi
return 0 # Enabled (default)
}
# Check if ping is enabled before proceeding
if ! is_ping_enabled; then
echo '{"connection": "DISABLED", "latency": 0}'
exit 0
fi
# Ping 8.8.8.8 with 5 packets and capture the full output
ping_result=$(ping -c 5 8.8.8.8)

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,229 @@
#!/bin/sh
# Ping Settings Configuration Script
# Manages ping enable/disable preferences
# 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/ping_settings.conf"
LOG_FILE="/tmp/ping_settings.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/ping_settings.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
}
# Get current ping setting
get_ping_setting() {
# If config file exists, read from it
if [ -f "$CONFIG_FILE" ]; then
ping_enabled=$(grep "^PING_ENABLED=" "$CONFIG_FILE" | cut -d'=' -f2)
if [ -n "$ping_enabled" ]; then
if [ "$ping_enabled" = "true" ] || [ "$ping_enabled" = "1" ] || [ "$ping_enabled" = "on" ]; then
echo "true"
else
echo "false"
fi
return
fi
fi
# Default to enabled if no config exists
echo "true"
}
# Save ping setting to config file
save_ping_setting() {
local enabled="$1"
ensure_config_directory
# Create or update config file
if [ -f "$CONFIG_FILE" ]; then
# Update existing file
sed -i "s/^PING_ENABLED=.*$/PING_ENABLED=$enabled/" "$CONFIG_FILE"
if [ $? -ne 0 ]; then
# If sed fails (e.g., no match), append the setting
echo "PING_ENABLED=$enabled" >> "$CONFIG_FILE"
fi
else
# Create new file
echo "PING_ENABLED=$enabled" > "$CONFIG_FILE"
fi
chmod 644 "$CONFIG_FILE"
log_message "Saved ping setting: $enabled"
}
# Delete ping configuration (reset to default)
delete_ping_setting() {
if [ -f "$CONFIG_FILE" ]; then
# Remove the PING_ENABLED line
sed -i '/^PING_ENABLED=/d' "$CONFIG_FILE"
log_message "Deleted ping 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 ping setting
handle_get() {
log_message "GET request received"
# Get current setting (from config or default)
local enabled=$(get_ping_setting)
# Check if it's from config or default
local is_default=true
if [ -f "$CONFIG_FILE" ] && grep -q "^PING_ENABLED=" "$CONFIG_FILE"; then
is_default=false
fi
send_success "Ping setting retrieved" "{\"enabled\":$enabled,\"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 JSON to extract enabled value
local enabled=""
# Approach 1: Simple regex extraction for boolean
enabled=$(echo "$post_data" | sed -n 's/.*"enabled"[[:space:]]*:[[:space:]]*\([^,}]*\).*/\1/p' | tr -d ' ')
# Approach 2: grep + cut extraction
if [ -z "$enabled" ]; then
enabled=$(echo "$post_data" | grep -o '"enabled":[^,}]*' | cut -d':' -f2 | tr -d ' ')
fi
# Approach 3: Look for true/false in the payload
if [ -z "$enabled" ]; then
if echo "$post_data" | grep -q '"enabled"[[:space:]]*:[[:space:]]*true'; then
enabled="true"
elif echo "$post_data" | grep -q '"enabled"[[:space:]]*:[[:space:]]*false'; then
enabled="false"
fi
fi
# Clean up the value (remove quotes if present)
enabled=$(echo "$enabled" | sed 's/"//g')
log_message "Received enabled: $enabled"
# Validate setting
if [ "$enabled" = "true" ] || [ "$enabled" = "false" ]; then
save_ping_setting "$enabled"
send_success "Ping setting updated successfully" "{\"enabled\":$enabled}"
else
send_error "INVALID_SETTING" "Invalid setting provided. Must be 'true' or 'false'."
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_ping_setting; then
# Default is enabled
send_success "Ping setting reset to default" "{\"enabled\":true,\"isDefault\":true}"
else
send_error "NOT_FOUND" "Ping setting 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 "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
;;
OPTIONS)
handle_options
;;
*)
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