Integrates WARP (install, uninstall, configure, status) functionality into the web panel's settings page. Users can now manage WARP directly from the UI.
1139 lines
45 KiB
Bash
1139 lines
45 KiB
Bash
#!/bin/bash
|
|
|
|
source /etc/hysteria/core/scripts/utils.sh
|
|
source /etc/hysteria/core/scripts/path.sh
|
|
source /etc/hysteria/core/scripts/services_status.sh >/dev/null 2>&1
|
|
|
|
check_services() {
|
|
for service in "${services[@]}"; do
|
|
service_base_name=$(basename "$service" .service)
|
|
|
|
display_name=$(echo "$service_base_name" | sed -E 's/([^-]+)-?/\u\1/g')
|
|
|
|
if systemctl is-active --quiet "$service"; then
|
|
echo -e "${NC}${display_name}:${green} Active${NC}"
|
|
else
|
|
echo -e "${NC}${display_name}:${red} Inactive${NC}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# OPTION HANDLERS (ONLY NEEDED ONE)
|
|
hysteria2_install_handler() {
|
|
if systemctl is-active --quiet hysteria-server.service; then
|
|
echo "The hysteria-server.service is currently active."
|
|
echo "If you need to update the core, please use the 'Update Core' option."
|
|
return
|
|
fi
|
|
|
|
while true; do
|
|
read -p "Enter the SNI (default: bts.com): " sni
|
|
sni=${sni:-bts.com}
|
|
|
|
read -p "Enter the port number you want to use: " port
|
|
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
|
|
echo "Invalid port number. Please enter a number between 1 and 65535."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
|
|
python3 $CLI_PATH install-hysteria2 --port "$port" --sni "$sni"
|
|
|
|
cat <<EOF > /etc/hysteria/.configs.env
|
|
SNI=$sni
|
|
EOF
|
|
python3 $CLI_PATH ip-address
|
|
}
|
|
|
|
hysteria2_add_user_handler() {
|
|
while true; do
|
|
read -p "Enter the username: " username
|
|
|
|
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
|
|
if [[ -n $(python3 $CLI_PATH get-user -u "$username") ]]; then
|
|
echo -e "${red}Error:${NC} Username already exists. Please choose another username."
|
|
else
|
|
break
|
|
fi
|
|
else
|
|
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
|
|
fi
|
|
done
|
|
|
|
read -p "Enter the traffic limit (in GB): " traffic_limit_GB
|
|
|
|
read -p "Enter the expiration days: " expiration_days
|
|
password=$(pwgen -s 32 1)
|
|
creation_date=$(date +%Y-%m-%d)
|
|
|
|
python3 $CLI_PATH add-user --username "$username" --traffic-limit "$traffic_limit_GB" --expiration-days "$expiration_days" --password "$password" --creation-date "$creation_date"
|
|
}
|
|
|
|
hysteria2_edit_user_handler() {
|
|
# Function to prompt for user input with validation
|
|
prompt_for_input() {
|
|
local prompt_message="$1"
|
|
local validation_regex="$2"
|
|
local default_value="$3"
|
|
local input_variable_name="$4"
|
|
|
|
while true; do
|
|
read -p "$prompt_message" input
|
|
if [[ -z "$input" ]]; then
|
|
input="$default_value"
|
|
fi
|
|
if [[ "$input" =~ $validation_regex ]]; then
|
|
eval "$input_variable_name='$input'"
|
|
break
|
|
else
|
|
echo -e "${red}Error:${NC} Invalid input. Please try again."
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Prompt for username
|
|
prompt_for_input "Enter the username you want to edit: " '^[a-zA-Z0-9]+$' '' username
|
|
|
|
# Check if user exists
|
|
user_exists_output=$(python3 $CLI_PATH get-user -u "$username" 2>&1)
|
|
if [[ -z "$user_exists_output" ]]; then
|
|
echo -e "${red}Error:${NC} User '$username' not found or an error occurred."
|
|
return 1
|
|
fi
|
|
|
|
# Prompt for new username
|
|
prompt_for_input "Enter the new username (leave empty to keep the current username): " '^[a-zA-Z0-9]*$' '' new_username
|
|
|
|
# Prompt for new traffic limit
|
|
prompt_for_input "Enter the new traffic limit (in GB) (leave empty to keep the current limit): " '^[0-9]*$' '' new_traffic_limit_GB
|
|
|
|
# Prompt for new expiration days
|
|
prompt_for_input "Enter the new expiration days (leave empty to keep the current expiration days): " '^[0-9]*$' '' new_expiration_days
|
|
|
|
# Determine if we need to renew password
|
|
while true; do
|
|
read -p "Do you want to generate a new password? (y/n): " renew_password
|
|
case "$renew_password" in
|
|
y|Y) renew_password=true; break ;;
|
|
n|N) renew_password=false; break ;;
|
|
*) echo -e "${red}Error:${NC} Please answer 'y' or 'n'." ;;
|
|
esac
|
|
done
|
|
|
|
# Determine if we need to renew creation date
|
|
while true; do
|
|
read -p "Do you want to generate a new creation date? (y/n): " renew_creation_date
|
|
case "$renew_creation_date" in
|
|
y|Y) renew_creation_date=true; break ;;
|
|
n|N) renew_creation_date=false; break ;;
|
|
*) echo -e "${red}Error:${NC} Please answer 'y' or 'n'." ;;
|
|
esac
|
|
done
|
|
|
|
# Determine if user should be blocked
|
|
while true; do
|
|
read -p "Do you want to block the user? (y/n): " block_user
|
|
case "$block_user" in
|
|
y|Y) blocked=true; break ;;
|
|
n|N) blocked=false; break ;;
|
|
*) echo -e "${red}Error:${NC} Please answer 'y' or 'n'." ;;
|
|
esac
|
|
done
|
|
|
|
# Construct the arguments for the edit-user command
|
|
args=()
|
|
if [[ -n "$new_username" ]]; then args+=("--new-username" "$new_username"); fi
|
|
if [[ -n "$new_traffic_limit_GB" ]]; then args+=("--new-traffic-limit" "$new_traffic_limit_GB"); fi
|
|
if [[ -n "$new_expiration_days" ]]; then args+=("--new-expiration-days" "$new_expiration_days"); fi
|
|
if [[ "$renew_password" == "true" ]]; then args+=("--renew-password"); fi
|
|
if [[ "$renew_creation_date" == "true" ]]; then args+=("--renew-creation-date"); fi
|
|
if [[ "$blocked" == "true" ]]; then args+=("--blocked"); fi
|
|
|
|
# Call the edit-user script with the constructed arguments
|
|
python3 $CLI_PATH edit-user --username "$username" "${args[@]}"
|
|
}
|
|
|
|
hysteria2_remove_user_handler() {
|
|
while true; do
|
|
read -p "Enter the username: " username
|
|
|
|
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
|
|
break
|
|
else
|
|
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
|
|
fi
|
|
done
|
|
python3 $CLI_PATH remove-user --username "$username"
|
|
}
|
|
|
|
hysteria2_get_user_handler() {
|
|
while true; do
|
|
read -p "Enter the username: " username
|
|
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
|
|
break
|
|
else
|
|
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
|
|
fi
|
|
done
|
|
|
|
user_data=$(python3 "$CLI_PATH" get-user --username "$username" 2>/dev/null)
|
|
|
|
if [[ $exit_code -ne 0 || -z "$user_data" ]]; then
|
|
echo -e "${red}Error:${NC} User '$username' not found or invalid response."
|
|
return 1
|
|
fi
|
|
|
|
password=$(echo "$user_data" | jq -r '.password // "N/A"')
|
|
max_download_bytes=$(echo "$user_data" | jq -r '.max_download_bytes // 0')
|
|
upload_bytes=$(echo "$user_data" | jq -r '.upload_bytes // 0')
|
|
download_bytes=$(echo "$user_data" | jq -r '.download_bytes // 0')
|
|
account_creation_date=$(echo "$user_data" | jq -r '.account_creation_date // "N/A"')
|
|
expiration_days=$(echo "$user_data" | jq -r '.expiration_days // 0')
|
|
blocked=$(echo "$user_data" | jq -r '.blocked // false')
|
|
status=$(echo "$user_data" | jq -r '.status // "N/A"')
|
|
total_usage=$((upload_bytes + download_bytes))
|
|
max_download_gb=$(echo "scale=2; $max_download_bytes / 1073741824" | bc)
|
|
upload_gb=$(echo "scale=2; $upload_bytes / 1073741824" | bc)
|
|
download_gb=$(echo "scale=2; $download_bytes / 1073741824" | bc)
|
|
total_usage_gb=$(echo "scale=2; $total_usage / 1073741824" | bc)
|
|
expiration_date=$(date -d "$account_creation_date + $expiration_days days" +"%Y-%m-%d")
|
|
current_date=$(date +"%Y-%m-%d")
|
|
used_days=$(( ( $(date -d "$current_date" +%s) - $(date -d "$account_creation_date" +%s) ) / 86400 ))
|
|
|
|
if [[ $used_days -gt $expiration_days ]]; then
|
|
used_days=$expiration_days
|
|
fi
|
|
|
|
echo -e "${green}User Details:${NC}"
|
|
echo -e "Username: $username"
|
|
echo -e "Password: $password"
|
|
echo -e "Total Traffic: $max_download_gb GB"
|
|
echo -e "Total Usage: $total_usage_gb GB"
|
|
echo -e "Time Expiration: $expiration_date ($used_days/$expiration_days Days)"
|
|
echo -e "Blocked: $blocked"
|
|
echo -e "Status: $status"
|
|
}
|
|
|
|
hysteria2_list_users_handler() {
|
|
users_json=$(python3 $CLI_PATH list-users 2>/dev/null)
|
|
if [ $? -ne 0 ] || [ -z "$users_json" ]; then
|
|
echo -e "${red}Error:${NC} Failed to list users."
|
|
return 1
|
|
fi
|
|
|
|
# Extract keys (usernames) from JSON
|
|
users_keys=$(echo "$users_json" | jq -r 'keys[]')
|
|
|
|
if [ -z "$users_keys" ]; then
|
|
echo -e "${red}Error:${NC} No users found."
|
|
return 1
|
|
fi
|
|
|
|
# Print headers
|
|
printf "%-20s %-20s %-15s %-20s %-30s %-10s\n" "Username" "Traffic Limit (GB)" "Expiration (Days)" "Creation Date" "Password" "Blocked"
|
|
|
|
# Print user details
|
|
for key in $users_keys; do
|
|
echo "$users_json" | jq -r --arg key "$key" '
|
|
"\($key) \(.[$key].max_download_bytes / 1073741824) \(.[$key].expiration_days) \(.[$key].account_creation_date) \(.[$key].password) \(.[$key].blocked)"' | \
|
|
while IFS= read -r line; do
|
|
IFS=' ' read -r username traffic_limit expiration_date creation_date password blocked <<< "$line"
|
|
printf "%-20s %-20s %-15s %-20s %-30s %-10s\n" "$username" "$traffic_limit" "$expiration_date" "$creation_date" "$password" "$blocked"
|
|
done
|
|
done
|
|
}
|
|
|
|
hysteria2_reset_user_handler() {
|
|
while true; do
|
|
read -p "Enter the username: " username
|
|
|
|
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
|
|
break
|
|
else
|
|
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
|
|
fi
|
|
done
|
|
python3 $CLI_PATH reset-user --username "$username"
|
|
}
|
|
|
|
hysteria2_show_user_uri_handler() {
|
|
check_service_active() {
|
|
systemctl is-active --quiet "$1"
|
|
}
|
|
|
|
while true; do
|
|
read -p "Enter the username: " username
|
|
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
|
|
break
|
|
else
|
|
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
|
|
fi
|
|
done
|
|
|
|
flags=""
|
|
|
|
if check_service_active "hysteria-singbox.service"; then
|
|
flags+=" -s"
|
|
fi
|
|
|
|
if check_service_active "hysteria-normal-sub.service"; then
|
|
flags+=" -n"
|
|
fi
|
|
|
|
if [[ -n "$flags" ]]; then
|
|
python3 $CLI_PATH show-user-uri -u "$username" -a -qr $flags
|
|
else
|
|
python3 $CLI_PATH show-user-uri -u "$username" -a -qr
|
|
fi
|
|
}
|
|
|
|
|
|
hysteria2_change_port_handler() {
|
|
while true; do
|
|
read -p "Enter the new port number you want to use: " port
|
|
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
|
|
echo "Invalid port number. Please enter a number between 1 and 65535."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
python3 $CLI_PATH change-hysteria2-port --port "$port"
|
|
}
|
|
|
|
hysteria2_change_sni_handler() {
|
|
while true; do
|
|
read -p "Enter the new SNI (e.g., example.com): " sni
|
|
|
|
if [[ "$sni" =~ ^[a-zA-Z0-9.]+$ ]]; then
|
|
break
|
|
else
|
|
echo -e "${red}Error:${NC} SNI can only contain letters, numbers, and dots."
|
|
fi
|
|
done
|
|
|
|
python3 $CLI_PATH change-hysteria2-sni --sni "$sni"
|
|
|
|
if systemctl is-active --quiet hysteria-singbox.service; then
|
|
systemctl restart hysteria-singbox.service
|
|
fi
|
|
}
|
|
|
|
edit_ips() {
|
|
while true; do
|
|
echo "======================================"
|
|
echo " IP/Domain Address Manager "
|
|
echo "======================================"
|
|
echo "1. Change IPv4 or Domain"
|
|
echo "2. Change IPv6 or Domain"
|
|
echo "0. Back"
|
|
echo "======================================"
|
|
read -p "Enter your choice [0-2]: " choice
|
|
|
|
case $choice in
|
|
1)
|
|
read -p "Enter the new IPv4 address or domain: " new_ip4_or_domain
|
|
if [[ $new_ip4_or_domain =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
|
if [[ $(echo "$new_ip4_or_domain" | awk -F. '{for (i=1;i<=NF;i++) if ($i>255) exit 1}') ]]; then
|
|
echo "Error: Invalid IPv4 address. Values must be between 0 and 255."
|
|
else
|
|
python3 "$CLI_PATH" ip-address --edit -4 "$new_ip4_or_domain"
|
|
echo "IPv4 address has been updated to $new_ip4_or_domain."
|
|
fi
|
|
elif [[ $new_ip4_or_domain =~ ^[a-zA-Z0-9.-]+$ ]] && [[ ! $new_ip4_or_domain =~ [/:] ]]; then
|
|
python3 "$CLI_PATH" ip-address --edit -4 "$new_ip4_or_domain"
|
|
echo "Domain has been updated to $new_ip4_or_domain."
|
|
else
|
|
echo "Error: Invalid IPv4 or domain format."
|
|
fi
|
|
break
|
|
;;
|
|
2)
|
|
read -p "Enter the new IPv6 address or domain: " new_ip6_or_domain
|
|
if [[ $new_ip6_or_domain =~ ^(([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$ ]]; then
|
|
python3 "$CLI_PATH" ip-address --edit -6 "$new_ip6_or_domain"
|
|
echo "IPv6 address has been updated to $new_ip6_or_domain."
|
|
elif [[ $new_ip6_or_domain =~ ^[a-zA-Z0-9.-]+$ ]] && [[ ! $new_ip6_or_domain =~ [/:] ]]; then
|
|
python3 "$CLI_PATH" ip-address --edit -6 "$new_ip6_or_domain"
|
|
echo "Domain has been updated to $new_ip6_or_domain."
|
|
else
|
|
echo "Error: Invalid IPv6 or domain format."
|
|
fi
|
|
break
|
|
;;
|
|
0)
|
|
break
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
break
|
|
;;
|
|
esac
|
|
echo "======================================"
|
|
read -p "Press Enter to continue..."
|
|
done
|
|
}
|
|
|
|
hysteria_upgrade(){
|
|
bash <(curl https://raw.githubusercontent.com/ReturnFI/Blitz/main/upgrade.sh)
|
|
}
|
|
|
|
warp_configure_handler() {
|
|
local service_name="wg-quick@wgcf.service"
|
|
|
|
if systemctl is-active --quiet "$service_name"; then
|
|
echo -e "${cyan}=== WARP Status ===${NC}"
|
|
status_json=$(python3 $CLI_PATH warp-status)
|
|
|
|
all_traffic=$(echo "$status_json" | grep -o '"all_traffic_via_warp": *[^,}]*' | cut -d':' -f2 | tr -d ' "')
|
|
popular_sites=$(echo "$status_json" | grep -o '"popular_sites_via_warp": *[^,}]*' | cut -d':' -f2 | tr -d ' "')
|
|
domestic_sites_via_warp=$(echo "$status_json" | grep -o '"domestic_sites_via_warp": *[^,}]*' | cut -d':' -f2 | tr -d ' "')
|
|
block_adult=$(echo "$status_json" | grep -o '"block_adult_content": *[^,}]*' | cut -d':' -f2 | tr -d ' "')
|
|
|
|
display_status() {
|
|
local label="$1"
|
|
local status_val="$2"
|
|
if [ "$status_val" = "true" ]; then
|
|
echo -e " ${green}✓${NC} $label: ${green}Enabled${NC}"
|
|
else
|
|
echo -e " ${red}✗${NC} $label: ${red}Disabled${NC}"
|
|
fi
|
|
}
|
|
|
|
display_status "All Traffic via WARP" "$all_traffic"
|
|
display_status "Popular Sites via WARP" "$popular_sites"
|
|
display_status "Domestic Sites via WARP" "$domestic_sites_via_warp"
|
|
display_status "Block Adult Content" "$block_adult"
|
|
|
|
echo -e "${cyan}==================${NC}"
|
|
echo
|
|
|
|
echo "Configure WARP Options (Toggle):"
|
|
echo "1. All traffic via WARP"
|
|
echo "2. Popular sites via WARP"
|
|
echo "3. Domestic sites (WARP/Reject)"
|
|
echo "4. Block adult content"
|
|
echo "5. WARP Status Profile (IP etc.)"
|
|
echo "6. Change WARP IP address"
|
|
echo "0. Cancel"
|
|
|
|
read -p "Select an option to toggle: " option
|
|
|
|
case $option in
|
|
1)
|
|
target_state=$([ "$all_traffic" = "true" ] && echo "off" || echo "on")
|
|
python3 $CLI_PATH configure-warp --set-all "$target_state" ;;
|
|
2)
|
|
target_state=$([ "$popular_sites" = "true" ] && echo "off" || echo "on")
|
|
python3 $CLI_PATH configure-warp --set-popular-sites "$target_state" ;;
|
|
3)
|
|
target_state=$([ "$domestic_sites_via_warp" = "true" ] && echo "off" || echo "on")
|
|
python3 $CLI_PATH configure-warp --set-domestic-sites "$target_state" ;;
|
|
4)
|
|
target_state=$([ "$block_adult" = "true" ] && echo "off" || echo "on")
|
|
python3 $CLI_PATH configure-warp --set-block-adult-sites "$target_state" ;;
|
|
5)
|
|
current_ip=$(python3 $CLI_PATH warp-status | grep -o '"ip": *"[^"]*"' | cut -d':' -f2- | tr -d '" ')
|
|
if [ -z "$current_ip" ]; then
|
|
current_ip=$(curl -s --interface wgcf --connect-timeout 1 http://v4.ident.me || echo "N/A")
|
|
fi
|
|
cd /etc/warp/ && wgcf status
|
|
echo
|
|
echo -e "${yellow}Warp IP:${NC} ${cyan}${current_ip}${NC}"
|
|
;;
|
|
6)
|
|
old_ip=$(curl -s --interface wgcf --connect-timeout 1 http://v4.ident.me || echo "N/A")
|
|
echo -e "${yellow}Current IP:${NC} ${cyan}$old_ip${NC}"
|
|
echo "Restarting $service_name to attempt IP change..."
|
|
systemctl restart "$service_name"
|
|
|
|
echo -n "Waiting for service to restart"
|
|
for i in {1..5}; do
|
|
echo -n "."
|
|
sleep 1
|
|
done
|
|
echo
|
|
|
|
new_ip=$(curl -s --interface wgcf --connect-timeout 1 http://v4.ident.me || echo "N/A")
|
|
echo -e "${yellow}New IP:${NC} ${green}$new_ip${NC}"
|
|
|
|
if [ "$old_ip" != "N/A" ] && [ "$new_ip" != "N/A" ] && [ "$old_ip" != "$new_ip" ]; then
|
|
echo -e "${green}✓ IP address changed successfully${NC}"
|
|
elif [ "$old_ip" = "$new_ip" ] && [ "$old_ip" != "N/A" ]; then
|
|
echo -e "${yellow}⚠ IP address remained the same${NC}"
|
|
else
|
|
echo -e "${red}✗ Could not verify IP change.${NC}"
|
|
fi
|
|
;;
|
|
0) echo "WARP configuration canceled." ;;
|
|
*) echo -e "${red}Invalid option. Please try again.${NC}" ;;
|
|
esac
|
|
# echo "Command sent. Check status again to see changes."
|
|
|
|
else
|
|
echo -e "${red}$service_name is not active. Please start the service before configuring WARP.${NC}"
|
|
fi
|
|
}
|
|
|
|
telegram_bot_handler() {
|
|
while true; do
|
|
echo -e "${cyan}1.${NC} Start Telegram bot service"
|
|
echo -e "${red}2.${NC} Stop Telegram bot service"
|
|
echo "0. Back"
|
|
read -p "Choose an option: " option
|
|
|
|
case $option in
|
|
1)
|
|
if systemctl is-active --quiet hysteria-telegram-bot.service; then
|
|
echo "The hysteria-telegram-bot.service is already active."
|
|
else
|
|
while true; do
|
|
read -e -p "Enter the Telegram bot token: " token
|
|
if [ -z "$token" ]; then
|
|
echo "Token cannot be empty. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
while true; do
|
|
read -e -p "Enter the admin IDs (comma-separated): " admin_ids
|
|
if [[ ! "$admin_ids" =~ ^[0-9,]+$ ]]; then
|
|
echo "Admin IDs can only contain numbers and commas. Please try again."
|
|
elif [ -z "$admin_ids" ]; then
|
|
echo "Admin IDs cannot be empty. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
python3 $CLI_PATH telegram -a start -t "$token" -aid "$admin_ids"
|
|
fi
|
|
;;
|
|
2)
|
|
python3 $CLI_PATH telegram -a stop
|
|
;;
|
|
0)
|
|
break
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
singbox_handler() {
|
|
while true; do
|
|
echo -e "${cyan}Merged with Normal-Sub sublink.${NC}"
|
|
# echo -e "${cyan}1.${NC} Start Singbox service"
|
|
echo -e "${red}2.${NC} Stop Singbox service"
|
|
echo "0. Back"
|
|
read -p "Choose an option: " option
|
|
|
|
case $option in
|
|
# 1)
|
|
# if systemctl is-active --quiet hysteria-singbox.service; then
|
|
# echo "The hysteria-singbox.service is already active."
|
|
# else
|
|
# while true; do
|
|
# read -e -p "Enter the domain name for the SSL certificate: " domain
|
|
# if [ -z "$domain" ]; then
|
|
# echo "Domain name cannot be empty. Please try again."
|
|
# else
|
|
# break
|
|
# fi
|
|
# done
|
|
|
|
# while true; do
|
|
# read -e -p "Enter the port number for the service: " port
|
|
# if [ -z "$port" ]; then
|
|
# echo "Port number cannot be empty. Please try again."
|
|
# elif ! [[ "$port" =~ ^[0-9]+$ ]]; then
|
|
# echo "Port must be a number. Please try again."
|
|
# else
|
|
# break
|
|
# fi
|
|
# done
|
|
|
|
# python3 $CLI_PATH singbox -a start -d "$domain" -p "$port"
|
|
# fi
|
|
# ;;
|
|
2)
|
|
if ! systemctl is-active --quiet hysteria-singbox.service; then
|
|
echo "The hysteria-singbox.service is already inactive."
|
|
else
|
|
python3 $CLI_PATH singbox -a stop
|
|
fi
|
|
;;
|
|
0)
|
|
break
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
normalsub_handler() {
|
|
while true; do
|
|
echo -e "${cyan}1.${NC} Start Normal-Sub service"
|
|
echo -e "${red}2.${NC} Stop Normal-Sub service"
|
|
echo -e "${yellow}3.${NC} Change SUBPATH"
|
|
echo "0. Back"
|
|
read -p "Choose an option: " option
|
|
|
|
case $option in
|
|
1)
|
|
if systemctl is-active --quiet hysteria-normal-sub.service; then
|
|
echo "The hysteria-normal-sub.service is already active."
|
|
else
|
|
while true; do
|
|
read -e -p "Enter the domain name for the SSL certificate: " domain
|
|
if [ -z "$domain" ]; then
|
|
echo "Domain name cannot be empty. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
while true; do
|
|
read -e -p "Enter the port number for the service: " port
|
|
if [ -z "$port" ]; then
|
|
echo "Port number cannot be empty. Please try again."
|
|
elif ! [[ "$port" =~ ^[0-9]+$ ]]; then
|
|
echo "Port must be a number. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
python3 $CLI_PATH normal-sub -a start -d "$domain" -p "$port"
|
|
fi
|
|
;;
|
|
2)
|
|
if ! systemctl is-active --quiet hysteria-normal-sub.service; then
|
|
echo "The hysteria-normal-sub.service is already inactive."
|
|
else
|
|
python3 $CLI_PATH normal-sub -a stop
|
|
fi
|
|
;;
|
|
3)
|
|
if ! systemctl is-active --quiet hysteria-normal-sub.service; then
|
|
echo "Error: The hysteria-normal-sub.service is not active. Start the service first."
|
|
continue
|
|
fi
|
|
|
|
while true; do
|
|
read -e -p "Enter new SUBPATH (Must include Uppercase, Lowercase, and Numbers): " subpath
|
|
if [[ -z "$subpath" ]]; then
|
|
echo "Error: SUBPATH cannot be empty. Please try again."
|
|
elif ! [[ "$subpath" =~ [A-Z] ]] || ! [[ "$subpath" =~ [a-z] ]] || ! [[ "$subpath" =~ [0-9] ]]; then
|
|
echo "Error: SUBPATH must include at least one uppercase letter, one lowercase letter, and one number."
|
|
else
|
|
python3 $CLI_PATH normal-sub -a edit_subpath -sp "$subpath"
|
|
# echo "SUBPATH updated successfully!"
|
|
break
|
|
fi
|
|
done
|
|
;;
|
|
0)
|
|
break
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
webpanel_handler() {
|
|
service_status=$(python3 "$CLI_PATH" get-webpanel-services-status)
|
|
echo -e "${cyan}Services Status:${NC}"
|
|
echo "$service_status"
|
|
echo ""
|
|
|
|
while true; do
|
|
echo -e "${cyan}1.${NC} Start WebPanel service"
|
|
echo -e "${red}2.${NC} Stop WebPanel service"
|
|
echo -e "${cyan}3.${NC} Get WebPanel URL"
|
|
echo -e "${cyan}4.${NC} Show API Token"
|
|
echo -e "${yellow}5.${NC} Reset WebPanel Credentials"
|
|
echo "0. Back"
|
|
read -p "Choose an option: " option
|
|
|
|
case $option in
|
|
1)
|
|
if systemctl is-active --quiet hysteria-webpanel.service; then
|
|
echo "The hysteria-webpanel.service is already active."
|
|
else
|
|
while true; do
|
|
read -e -p "Enter the domain name for the SSL certificate: " domain
|
|
if [ -z "$domain" ]; then
|
|
echo "Domain name cannot be empty. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
while true; do
|
|
read -e -p "Enter the port number for the service: " port
|
|
if [ -z "$port" ]; then
|
|
echo "Port number cannot be empty. Please try again."
|
|
elif ! [[ "$port" =~ ^[0-9]+$ ]]; then
|
|
echo "Port must be a number. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
while true; do
|
|
read -e -p "Enter the admin username: " admin_username
|
|
if [ -z "$admin_username" ]; then
|
|
echo "Admin username cannot be empty. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
while true; do
|
|
read -e -p "Enter the admin password: " admin_password
|
|
if [ -z "$admin_password" ]; then
|
|
echo "Admin password cannot be empty. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
python3 $CLI_PATH webpanel -a start -d "$domain" -p "$port" -au "$admin_username" -ap "$admin_password"
|
|
fi
|
|
;;
|
|
2)
|
|
if ! systemctl is-active --quiet hysteria-webpanel.service; then
|
|
echo "The hysteria-webpanel.service is already inactive."
|
|
else
|
|
python3 $CLI_PATH webpanel -a stop
|
|
fi
|
|
;;
|
|
3)
|
|
url=$(python3 $CLI_PATH get-webpanel-url)
|
|
echo "-------------------------------"
|
|
echo "$url"
|
|
echo "-------------------------------"
|
|
;;
|
|
4)
|
|
api_token=$(python3 $CLI_PATH get-webpanel-api-token)
|
|
echo "-------------------------------"
|
|
echo "$api_token"
|
|
echo "-------------------------------"
|
|
;;
|
|
5)
|
|
if ! systemctl is-active --quiet hysteria-webpanel.service; then
|
|
echo -e "${red}WebPanel service is not running. Cannot reset credentials.${NC}"
|
|
else
|
|
read -e -p "Enter new admin username (leave blank to keep current): " new_username
|
|
read -e -p "Enter new admin password (leave blank to keep current): " new_password
|
|
echo
|
|
|
|
if [ -z "$new_username" ] && [ -z "$new_password" ]; then
|
|
echo -e "${yellow}No changes specified. Aborting.${NC}"
|
|
else
|
|
local cmd_args=("-u" "$new_username")
|
|
if [ -n "$new_password" ]; then
|
|
cmd_args+=("-p" "$new_password")
|
|
fi
|
|
|
|
if [ -z "$new_username" ]; then
|
|
cmd_args=()
|
|
if [ -n "$new_password" ]; then
|
|
cmd_args+=("-p" "$new_password")
|
|
fi
|
|
fi
|
|
|
|
echo "Attempting to reset credentials..."
|
|
python3 "$CLI_PATH" reset-webpanel-creds "${cmd_args[@]}"
|
|
fi
|
|
fi
|
|
;;
|
|
0)
|
|
break
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
|
|
obfs_handler() {
|
|
while true; do
|
|
echo -e "${cyan}1.${NC} Remove Obfs"
|
|
echo -e "${red}2.${NC} Generating new Obfs"
|
|
echo "0. Back"
|
|
read -p "Choose an option: " option
|
|
|
|
case $option in
|
|
1)
|
|
python3 $CLI_PATH manage_obfs -r
|
|
;;
|
|
2)
|
|
python3 $CLI_PATH manage_obfs -g
|
|
;;
|
|
0)
|
|
break
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
geo_update_handler() {
|
|
echo "Configure Geo Update Options:"
|
|
echo "1. Update Iran Geo Files"
|
|
echo "2. Update China Geo Files"
|
|
echo "3. Update Russia Geo Files"
|
|
echo "4. Check Current Geo Files"
|
|
echo "0. Cancel"
|
|
|
|
read -p "Select an option: " option
|
|
|
|
case $option in
|
|
1)
|
|
echo "Updating Iran Geo Files..."
|
|
python3 $CLI_PATH update-geo --country iran
|
|
;;
|
|
2)
|
|
echo "Updating China Geo Files..."
|
|
python3 $CLI_PATH update-geo --country china
|
|
;;
|
|
3)
|
|
echo "Updating Russia Geo Files..."
|
|
python3 $CLI_PATH update-geo --country russia
|
|
;;
|
|
4)
|
|
echo "Current Geo Files Information:"
|
|
echo "--------------------------"
|
|
if [ -f "/etc/hysteria/geosite.dat" ]; then
|
|
echo "GeoSite File:"
|
|
ls -lh /etc/hysteria/geosite.dat
|
|
echo "Last modified: $(stat -c %y /etc/hysteria/geosite.dat)"
|
|
else
|
|
echo "GeoSite file not found!"
|
|
fi
|
|
echo
|
|
if [ -f "/etc/hysteria/geoip.dat" ]; then
|
|
echo "GeoIP File:"
|
|
ls -lh /etc/hysteria/geoip.dat
|
|
echo "Last modified: $(stat -c %y /etc/hysteria/geoip.dat)"
|
|
else
|
|
echo "GeoIP file not found!"
|
|
fi
|
|
;;
|
|
0)
|
|
echo "Geo update configuration canceled."
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
masquerade_handler() {
|
|
while true; do
|
|
echo -e "${cyan}1.${NC} Enable Masquerade"
|
|
echo -e "${red}2.${NC} Remove Masquerade"
|
|
echo "0. Back"
|
|
read -p "Choose an option: " option
|
|
|
|
case $option in
|
|
1)
|
|
if systemctl is-active --quiet hysteria-webpanel.service; then
|
|
echo -e "${red}Error:${NC} Masquerade cannot be enabled because hysteria-webpanel.service is running."
|
|
else
|
|
read -p "Enter the URL for rewriteHost: " url
|
|
if [ -z "$url" ]; then
|
|
echo "Error: URL cannot be empty. Please try again."
|
|
else
|
|
python3 $CLI_PATH masquerade -e "$url"
|
|
fi
|
|
fi
|
|
;;
|
|
2)
|
|
python3 $CLI_PATH masquerade -r
|
|
;;
|
|
0)
|
|
break
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
ip_limit_handler() {
|
|
while true; do
|
|
echo -e "${cyan}1.${NC} Start IP Limiter Service"
|
|
echo -e "${red}2.${NC} Stop IP Limiter Service"
|
|
echo -e "${yellow}3.${NC} Change IP Limiter Configuration"
|
|
echo "0. Back"
|
|
read -p "Choose an option: " option
|
|
|
|
case $option in
|
|
1)
|
|
if systemctl is-active --quiet hysteria-ip-limit.service; then
|
|
echo "The hysteria-ip-limit.service is already active."
|
|
else
|
|
while true; do
|
|
read -e -p "Enter Block Duration (seconds, default: 60): " block_duration
|
|
block_duration=${block_duration:-60} # Default to 60 if empty
|
|
if ! [[ "$block_duration" =~ ^[0-9]+$ ]]; then
|
|
echo "Invalid Block Duration. Please enter a number."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
while true; do
|
|
read -e -p "Enter Max IPs per User (default: 1): " max_ips
|
|
max_ips=${max_ips:-1} # Default to 1 if empty
|
|
if ! [[ "$max_ips" =~ ^[0-9]+$ ]]; then
|
|
echo "Invalid Max IPs. Please enter a number."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
python3 $CLI_PATH config-ip-limit --block-duration "$block_duration" --max-ips "$max_ips"
|
|
python3 $CLI_PATH start-ip-limit
|
|
fi
|
|
;;
|
|
2)
|
|
if ! systemctl is-active --quiet hysteria-ip-limit.service; then
|
|
echo "The hysteria-ip-limit.service is already inactive."
|
|
else
|
|
python3 $CLI_PATH stop-ip-limit
|
|
fi
|
|
;;
|
|
3)
|
|
block_duration=""
|
|
max_ips=""
|
|
updated=false
|
|
|
|
while true; do
|
|
read -e -p "Enter New Block Duration (seconds, current: $(grep '^BLOCK_DURATION=' /etc/hysteria/.configs.env | cut -d'=' -f2), leave empty to keep current): " input_block_duration
|
|
if [[ -n "$input_block_duration" ]] && ! [[ "$input_block_duration" =~ ^[0-9]+$ ]]; then
|
|
echo "Invalid Block Duration. Please enter a number or leave empty."
|
|
else
|
|
if [[ -n "$input_block_duration" ]]; then
|
|
block_duration="$input_block_duration"
|
|
updated=true
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
|
|
while true; do
|
|
read -e -p "Enter New Max IPs per User (current: $(grep '^MAX_IPS=' /etc/hysteria/.configs.env | cut -d'=' -f2), leave empty to keep current): " input_max_ips
|
|
if [[ -n "$input_max_ips" ]] && ! [[ "$input_max_ips" =~ ^[0-9]+$ ]]; then
|
|
echo "Invalid Max IPs. Please enter a number or leave empty."
|
|
else
|
|
if [[ -n "$input_max_ips" ]]; then
|
|
max_ips="$input_max_ips"
|
|
updated=true
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ "$updated" == "true" ]]; then
|
|
python3 $CLI_PATH config-ip-limit --block-duration "$block_duration" --max-ips "$max_ips"
|
|
else
|
|
echo "No changes to IP Limiter configuration were provided."
|
|
fi
|
|
;;
|
|
0)
|
|
break
|
|
;;
|
|
*)
|
|
echo "Invalid option. Please try again."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Function to display the main menu
|
|
display_main_menu() {
|
|
clear
|
|
tput setaf 7 ; tput setab 4 ; tput bold
|
|
echo -e "◇────────────────🚀 Welcome To Blitz Panel 🚀─────────────────◇"
|
|
tput sgr0
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
|
|
printf "\033[0;32m• OS: \033[0m%-25s \033[0;32m• ARCH: \033[0m%-25s\n" "$OS" "$ARCH"
|
|
printf "\033[0;32m• ISP: \033[0m%-25s \033[0;32m• CPU: \033[0m%-25s\n" "$ISP" "$CPU"
|
|
printf "\033[0;32m• IP: \033[0m%-25s \033[0;32m• RAM: \033[0m%-25s\n" "$IP" "$RAM"
|
|
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
check_core_version
|
|
check_version
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
echo -e "${yellow} ☼ Services Status ☼ ${NC}"
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
|
|
check_services
|
|
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
echo -e "${yellow} ☼ Main Menu ☼ ${NC}"
|
|
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
echo -e "${green}[1] ${NC}↝ Hysteria2 Menu"
|
|
echo -e "${cyan}[2] ${NC}↝ Advance Menu"
|
|
echo -e "${cyan}[3] ${NC}↝ Update Panel"
|
|
echo -e "${red}[0] ${NC}↝ Exit"
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
echo -ne "${yellow}➜ Enter your option: ${NC}"
|
|
}
|
|
|
|
# Function to handle main menu options
|
|
main_menu() {
|
|
clear
|
|
local choice
|
|
while true; do
|
|
get_system_info
|
|
display_main_menu
|
|
read -r choice
|
|
case $choice in
|
|
1) hysteria2_menu ;;
|
|
2) advance_menu ;;
|
|
3) hysteria_upgrade ;;
|
|
0) exit 0 ;;
|
|
*) echo "Invalid option. Please try again." ;;
|
|
esac
|
|
echo
|
|
read -rp "Press Enter to continue..."
|
|
done
|
|
}
|
|
|
|
# Function to display the Blitz menu
|
|
display_hysteria2_menu() {
|
|
clear
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
|
|
echo -e "${yellow} ☼ Blitz Menu ☼ ${NC}"
|
|
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
|
|
echo -e "${green}[1] ${NC}↝ Install and Configure Hysteria2"
|
|
echo -e "${cyan}[2] ${NC}↝ Add User"
|
|
echo -e "${cyan}[3] ${NC}↝ Edit User"
|
|
echo -e "${cyan}[4] ${NC}↝ Reset User"
|
|
echo -e "${cyan}[5] ${NC}↝ Remove User"
|
|
echo -e "${cyan}[6] ${NC}↝ Get User"
|
|
echo -e "${cyan}[7] ${NC}↝ List Users"
|
|
echo -e "${cyan}[8] ${NC}↝ Check Traffic Status"
|
|
echo -e "${cyan}[9] ${NC}↝ Show User URI"
|
|
|
|
echo -e "${red}[0] ${NC}↝ Back to Main Menu"
|
|
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
|
|
echo -ne "${yellow}➜ Enter your option: ${NC}"
|
|
}
|
|
|
|
# Function to handle Hysteria2 menu options
|
|
hysteria2_menu() {
|
|
clear
|
|
local choice
|
|
while true; do
|
|
get_system_info
|
|
display_hysteria2_menu
|
|
read -r choice
|
|
case $choice in
|
|
1) hysteria2_install_handler ;;
|
|
2) hysteria2_add_user_handler ;;
|
|
3) hysteria2_edit_user_handler ;;
|
|
4) hysteria2_reset_user_handler ;;
|
|
5) hysteria2_remove_user_handler ;;
|
|
6) hysteria2_get_user_handler ;;
|
|
7) hysteria2_list_users_handler ;;
|
|
8) python3 $CLI_PATH traffic-status ;;
|
|
9) hysteria2_show_user_uri_handler ;;
|
|
0) return ;;
|
|
*) echo "Invalid option. Please try again." ;;
|
|
esac
|
|
echo
|
|
read -rp "Press Enter to continue..."
|
|
done
|
|
}
|
|
|
|
# Function to get Advance menu
|
|
display_advance_menu() {
|
|
clear
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
echo -e "${yellow} ☼ Advance Menu ☼ ${NC}"
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
echo -e "${green}[1] ${NC}↝ Install TCP Brutal"
|
|
echo -e "${green}[2] ${NC}↝ Install WARP"
|
|
echo -e "${cyan}[3] ${NC}↝ Configure WARP"
|
|
echo -e "${red}[4] ${NC}↝ Uninstall WARP"
|
|
echo -e "${green}[5] ${NC}↝ Telegram Bot"
|
|
echo -e "${green}[6] ${NC}↝ SingBox SubLink(${red}Deprecated${NC})"
|
|
echo -e "${green}[7] ${NC}↝ Normal-SUB SubLink"
|
|
echo -e "${green}[8] ${NC}↝ Web Panel"
|
|
echo -e "${cyan}[9] ${NC}↝ Change Port Hysteria2"
|
|
echo -e "${cyan}[10] ${NC}↝ Change SNI Hysteria2"
|
|
echo -e "${cyan}[11] ${NC}↝ Manage OBFS"
|
|
echo -e "${cyan}[12] ${NC}↝ Change IPs(4-6)"
|
|
echo -e "${cyan}[13] ${NC}↝ Update geo Files"
|
|
echo -e "${cyan}[14] ${NC}↝ Manage Masquerade"
|
|
echo -e "${cyan}[15] ${NC}↝ Restart Hysteria2"
|
|
echo -e "${cyan}[16] ${NC}↝ Update Core Hysteria2"
|
|
echo -e "${cyan}[17] ${NC}↝ IP Limiter Menu"
|
|
echo -e "${red}[18] ${NC}↝ Uninstall Hysteria2"
|
|
echo -e "${red}[0] ${NC}↝ Back to Main Menu"
|
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
|
echo -ne "${yellow}➜ Enter your option: ${NC}"
|
|
}
|
|
|
|
# Function to handle Advance menu options
|
|
advance_menu() {
|
|
clear
|
|
local choice
|
|
while true; do
|
|
display_advance_menu
|
|
read -r choice
|
|
case $choice in
|
|
1) python3 $CLI_PATH install-tcp-brutal ;;
|
|
2) python3 $CLI_PATH install-warp ;;
|
|
3) warp_configure_handler ;;
|
|
4) python3 $CLI_PATH uninstall-warp ;;
|
|
5) telegram_bot_handler ;;
|
|
6) singbox_handler ;;
|
|
7) normalsub_handler ;;
|
|
8) webpanel_handler ;;
|
|
9) hysteria2_change_port_handler ;;
|
|
10) hysteria2_change_sni_handler ;;
|
|
11) obfs_handler ;;
|
|
12) edit_ips ;;
|
|
13) geo_update_handler ;;
|
|
14) masquerade_handler ;;
|
|
15) python3 $CLI_PATH restart-hysteria2 ;;
|
|
16) python3 $CLI_PATH update-hysteria2 ;;
|
|
17) ip_limit_handler ;;
|
|
18) python3 $CLI_PATH uninstall-hysteria2 ;;
|
|
0) return ;;
|
|
*) echo "Invalid option. Please try again." ;;
|
|
esac
|
|
echo
|
|
read -rp "Press Enter to continue..."
|
|
done
|
|
}
|
|
# Main function to run the script
|
|
define_colors
|
|
main_menu
|