|
@@ -77,7 +77,7 @@ DESEC_DOMAIN="manager.firewallfalcon.qzz.io"
|
|
|
|
|
|
|
|
SELECTED_USER=""
|
|
SELECTED_USER=""
|
|
|
UNINSTALL_MODE="interactive"
|
|
UNINSTALL_MODE="interactive"
|
|
|
-BANNER_CACHE_TTL=5
|
|
|
|
|
|
|
+BANNER_CACHE_TTL=15
|
|
|
BANNER_CACHE_TS=0
|
|
BANNER_CACHE_TS=0
|
|
|
BANNER_CACHE_OS_NAME=""
|
|
BANNER_CACHE_OS_NAME=""
|
|
|
BANNER_CACHE_UP_TIME=""
|
|
BANNER_CACHE_UP_TIME=""
|
|
@@ -85,6 +85,12 @@ BANNER_CACHE_RAM_USAGE=""
|
|
|
BANNER_CACHE_CPU_LOAD=""
|
|
BANNER_CACHE_CPU_LOAD=""
|
|
|
BANNER_CACHE_ONLINE_USERS=0
|
|
BANNER_CACHE_ONLINE_USERS=0
|
|
|
BANNER_CACHE_TOTAL_USERS=0
|
|
BANNER_CACHE_TOTAL_USERS=0
|
|
|
|
|
+SSH_SESSION_CACHE_TTL=10
|
|
|
|
|
+SSH_SESSION_CACHE_TS=0
|
|
|
|
|
+SSH_SESSION_CACHE_DB_MTIME=0
|
|
|
|
|
+SSH_SESSION_TOTAL=0
|
|
|
|
|
+declare -A SSH_SESSION_COUNTS=()
|
|
|
|
|
+declare -A SSH_SESSION_PIDS=()
|
|
|
|
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
if [[ $EUID -ne 0 ]]; then
|
|
|
echo -e "${C_RED}❌ Error: This script requires root privileges to run.${C_RESET}"
|
|
echo -e "${C_RED}❌ Error: This script requires root privileges to run.${C_RESET}"
|
|
@@ -110,6 +116,13 @@ ensure_firewallfalcon_dirs() {
|
|
|
touch "$DB_FILE"
|
|
touch "$DB_FILE"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+require_interactive_terminal() {
|
|
|
|
|
+ if [[ ! -t 0 || ! -t 1 ]]; then
|
|
|
|
|
+ echo -e "${C_RED}❌ Error: The FirewallFalcon menu must be run from an interactive terminal.${C_RESET}"
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
initial_setup() {
|
|
initial_setup() {
|
|
|
echo -e "${C_BLUE}⚙️ Initializing FirewallFalcon Manager setup...${C_RESET}"
|
|
echo -e "${C_BLUE}⚙️ Initializing FirewallFalcon Manager setup...${C_RESET}"
|
|
|
check_environment
|
|
check_environment
|
|
@@ -237,192 +250,220 @@ setup_limiter_service() {
|
|
|
# Combined limiter + bandwidth monitoring
|
|
# Combined limiter + bandwidth monitoring
|
|
|
cat > "$LIMITER_SCRIPT" << 'EOF'
|
|
cat > "$LIMITER_SCRIPT" << 'EOF'
|
|
|
#!/bin/bash
|
|
#!/bin/bash
|
|
|
|
|
+# FirewallFalcon limiter version 2026-03-20.1
|
|
|
DB_FILE="/etc/firewallfalcon/users.db"
|
|
DB_FILE="/etc/firewallfalcon/users.db"
|
|
|
BW_DIR="/etc/firewallfalcon/bandwidth"
|
|
BW_DIR="/etc/firewallfalcon/bandwidth"
|
|
|
PID_DIR="$BW_DIR/pidtrack"
|
|
PID_DIR="$BW_DIR/pidtrack"
|
|
|
|
|
+BANNER_DIR="/etc/firewallfalcon/banners"
|
|
|
|
|
+SCAN_INTERVAL=30
|
|
|
|
|
|
|
|
mkdir -p "$BW_DIR" "$PID_DIR"
|
|
mkdir -p "$BW_DIR" "$PID_DIR"
|
|
|
|
|
+shopt -s nullglob
|
|
|
|
|
+
|
|
|
|
|
+write_banner_if_changed() {
|
|
|
|
|
+ local user="$1"
|
|
|
|
|
+ local content="$2"
|
|
|
|
|
+ local banner_file="$BANNER_DIR/${user}.txt"
|
|
|
|
|
+ local tmp_file="${banner_file}.tmp"
|
|
|
|
|
+
|
|
|
|
|
+ printf "%s" "$content" > "$tmp_file"
|
|
|
|
|
+ if ! cmp -s "$tmp_file" "$banner_file" 2>/dev/null; then
|
|
|
|
|
+ mv "$tmp_file" "$banner_file"
|
|
|
|
|
+ else
|
|
|
|
|
+ rm -f "$tmp_file"
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
while true; do
|
|
while true; do
|
|
|
- if [[ ! -f "$DB_FILE" ]]; then
|
|
|
|
|
- sleep 30
|
|
|
|
|
|
|
+ if [[ ! -s "$DB_FILE" ]]; then
|
|
|
|
|
+ sleep "$SCAN_INTERVAL"
|
|
|
continue
|
|
continue
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
current_ts=$(date +%s)
|
|
current_ts=$(date +%s)
|
|
|
-
|
|
|
|
|
|
|
+ banners_enabled=false
|
|
|
|
|
+ declare -A session_counts=()
|
|
|
|
|
+ declare -A session_pids=()
|
|
|
|
|
+ declare -A locked_users=()
|
|
|
|
|
+ declare -A uid_to_user=()
|
|
|
|
|
+ declare -A seen_sessions=()
|
|
|
|
|
+
|
|
|
|
|
+ while IFS=: read -r username _ uid _rest; do
|
|
|
|
|
+ [[ -n "$username" && "$uid" =~ ^[0-9]+$ ]] && uid_to_user["$uid"]="$username"
|
|
|
|
|
+ done < /etc/passwd
|
|
|
|
|
+
|
|
|
|
|
+ while read -r ssh_pid ssh_owner; do
|
|
|
|
|
+ [[ "$ssh_pid" =~ ^[0-9]+$ ]] || continue
|
|
|
|
|
+
|
|
|
|
|
+ session_user=""
|
|
|
|
|
+ if [[ -n "$ssh_owner" && "$ssh_owner" != "root" && "$ssh_owner" != "sshd" ]]; then
|
|
|
|
|
+ session_user="$ssh_owner"
|
|
|
|
|
+ elif [[ -r "/proc/$ssh_pid/loginuid" ]]; then
|
|
|
|
|
+ login_uid=""
|
|
|
|
|
+ read -r login_uid < "/proc/$ssh_pid/loginuid" || login_uid=""
|
|
|
|
|
+ if [[ "$login_uid" =~ ^[0-9]+$ && "$login_uid" != "4294967295" ]]; then
|
|
|
|
|
+ session_user="${uid_to_user[$login_uid]}"
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ [[ -n "$session_user" ]] || continue
|
|
|
|
|
+ session_key="${session_user}:$ssh_pid"
|
|
|
|
|
+ [[ -z "${seen_sessions[$session_key]+x}" ]] || continue
|
|
|
|
|
+
|
|
|
|
|
+ seen_sessions["$session_key"]=1
|
|
|
|
|
+ ((session_counts["$session_user"]++))
|
|
|
|
|
+ session_pids["$session_user"]+="$ssh_pid "
|
|
|
|
|
+ done < <(ps -C sshd -o pid=,user= 2>/dev/null)
|
|
|
|
|
+
|
|
|
|
|
+ while read -r passwd_user _ passwd_status _rest; do
|
|
|
|
|
+ [[ "$passwd_status" == "L" ]] && locked_users["$passwd_user"]=1
|
|
|
|
|
+ done < <(passwd -Sa 2>/dev/null)
|
|
|
|
|
+
|
|
|
|
|
+ if [[ -f "/etc/firewallfalcon/banners_enabled" ]]; then
|
|
|
|
|
+ mkdir -p "$BANNER_DIR"
|
|
|
|
|
+ banners_enabled=true
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
|
|
while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
|
|
|
[[ -z "$user" || "$user" == \#* ]] && continue
|
|
[[ -z "$user" || "$user" == \#* ]] && continue
|
|
|
-
|
|
|
|
|
- # --- Expiry Check ---
|
|
|
|
|
- if [[ "$expiry" != "Never" && "$expiry" != "" ]]; then
|
|
|
|
|
- expiry_ts=$(date -d "$expiry" +%s 2>/dev/null || echo 0)
|
|
|
|
|
- if [[ $expiry_ts -lt $current_ts && $expiry_ts -ne 0 ]]; then
|
|
|
|
|
- if ! passwd -S "$user" | grep -q " L "; then
|
|
|
|
|
|
|
+
|
|
|
|
|
+ online_count=${session_counts["$user"]:-0}
|
|
|
|
|
+ user_locked=false
|
|
|
|
|
+ if [[ -n "${locked_users[$user]+x}" ]]; then
|
|
|
|
|
+ user_locked=true
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ expiry_ts=0
|
|
|
|
|
+ if [[ "$expiry" != "Never" && -n "$expiry" ]]; then
|
|
|
|
|
+ expiry_ts=$(date -d "$expiry" +%s 2>/dev/null || echo 0)
|
|
|
|
|
+ if [[ "$expiry_ts" =~ ^[0-9]+$ ]] && (( expiry_ts > 0 && expiry_ts < current_ts )); then
|
|
|
|
|
+ if ! $user_locked; then
|
|
|
usermod -L "$user" &>/dev/null
|
|
usermod -L "$user" &>/dev/null
|
|
|
killall -u "$user" -9 &>/dev/null
|
|
killall -u "$user" -9 &>/dev/null
|
|
|
|
|
+ locked_users["$user"]=1
|
|
|
fi
|
|
fi
|
|
|
continue
|
|
continue
|
|
|
- fi
|
|
|
|
|
|
|
+ fi
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
- # --- Connection Limit Check ---
|
|
|
|
|
- online_count=$(pgrep -c -u "$user" sshd)
|
|
|
|
|
- if ! [[ "$limit" =~ ^[0-9]+$ ]]; then limit=1; fi
|
|
|
|
|
-
|
|
|
|
|
- if [[ "$online_count" -gt "$limit" ]]; then
|
|
|
|
|
- if ! passwd -S "$user" | grep -q " L "; then
|
|
|
|
|
|
|
+
|
|
|
|
|
+ [[ "$limit" =~ ^[0-9]+$ ]] || limit=1
|
|
|
|
|
+ if (( online_count > limit )); then
|
|
|
|
|
+ if ! $user_locked; then
|
|
|
usermod -L "$user" &>/dev/null
|
|
usermod -L "$user" &>/dev/null
|
|
|
killall -u "$user" -9 &>/dev/null
|
|
killall -u "$user" -9 &>/dev/null
|
|
|
- (sleep 120; usermod -U "$user" &>/dev/null) &
|
|
|
|
|
|
|
+ (sleep 120; usermod -U "$user" &>/dev/null) &
|
|
|
|
|
+ locked_users["$user"]=1
|
|
|
|
|
+ user_locked=true
|
|
|
else
|
|
else
|
|
|
killall -u "$user" -9 &>/dev/null
|
|
killall -u "$user" -9 &>/dev/null
|
|
|
fi
|
|
fi
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
- # --- SSH Banner Generation (Delay of 1 cycle for BW stats is fine) ---
|
|
|
|
|
- if [[ -f "/etc/firewallfalcon/banners_enabled" ]]; then
|
|
|
|
|
- mkdir -p "/etc/firewallfalcon/banners"
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if $banners_enabled; then
|
|
|
days_left="N/A"
|
|
days_left="N/A"
|
|
|
- if [[ "$expiry" != "Never" && -n "$expiry" ]]; then
|
|
|
|
|
- if [[ $expiry_ts -gt 0 ]]; then
|
|
|
|
|
- diff_secs=$((expiry_ts - current_ts))
|
|
|
|
|
- if [[ $diff_secs -le 0 ]]; then
|
|
|
|
|
- days_left="EXPIRED"
|
|
|
|
|
|
|
+ if [[ "$expiry" != "Never" && -n "$expiry" && "$expiry_ts" =~ ^[0-9]+$ && $expiry_ts -gt 0 ]]; then
|
|
|
|
|
+ diff_secs=$((expiry_ts - current_ts))
|
|
|
|
|
+ if (( diff_secs <= 0 )); then
|
|
|
|
|
+ days_left="EXPIRED"
|
|
|
|
|
+ else
|
|
|
|
|
+ d_l=$(( diff_secs / 86400 ))
|
|
|
|
|
+ h_l=$(( (diff_secs % 86400) / 3600 ))
|
|
|
|
|
+ if (( d_l == 0 )); then
|
|
|
|
|
+ days_left="${h_l}h left"
|
|
|
else
|
|
else
|
|
|
- d_l=$(( diff_secs / 86400 ))
|
|
|
|
|
- h_l=$(( (diff_secs % 86400) / 3600 ))
|
|
|
|
|
- if [[ $d_l -eq 0 ]]; then days_left="${h_l}h left"
|
|
|
|
|
- else days_left="${d_l}d ${h_l}h"; fi
|
|
|
|
|
|
|
+ days_left="${d_l}d ${h_l}h"
|
|
|
fi
|
|
fi
|
|
|
fi
|
|
fi
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
bw_info="Unlimited"
|
|
bw_info="Unlimited"
|
|
|
if [[ "$bandwidth_gb" != "0" && -n "$bandwidth_gb" ]]; then
|
|
if [[ "$bandwidth_gb" != "0" && -n "$bandwidth_gb" ]]; then
|
|
|
usagefile="$BW_DIR/${user}.usage"
|
|
usagefile="$BW_DIR/${user}.usage"
|
|
|
accum_disp=0
|
|
accum_disp=0
|
|
|
- [[ -f "$usagefile" ]] && accum_disp=$(cat "$usagefile" 2>/dev/null)
|
|
|
|
|
|
|
+ if [[ -f "$usagefile" ]]; then
|
|
|
|
|
+ read -r accum_disp < "$usagefile"
|
|
|
|
|
+ [[ "$accum_disp" =~ ^[0-9]+$ ]] || accum_disp=0
|
|
|
|
|
+ fi
|
|
|
used_gb=$(awk "BEGIN {printf \"%.2f\", $accum_disp / 1073741824}")
|
|
used_gb=$(awk "BEGIN {printf \"%.2f\", $accum_disp / 1073741824}")
|
|
|
remain_gb=$(awk "BEGIN {r=$bandwidth_gb - $used_gb; if(r<0) r=0; printf \"%.2f\", r}")
|
|
remain_gb=$(awk "BEGIN {r=$bandwidth_gb - $used_gb; if(r<0) r=0; printf \"%.2f\", r}")
|
|
|
bw_info="${used_gb}/${bandwidth_gb} GB used | ${remain_gb} GB left"
|
|
bw_info="${used_gb}/${bandwidth_gb} GB used | ${remain_gb} GB left"
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
- # Format the output with HTML tags since clients like HTTP Custom render Server Messages using Html.fromHtml()
|
|
|
|
|
- # Crucial: Use echo -e instead of heredoc to prevent DOS CRLF syntax errors when moving script to Linux
|
|
|
|
|
- echo -e "<br><font color=\"yellow\"><b> ✨ ACCOUNT STATUS ✨ </b></font><br><br>" > "/etc/firewallfalcon/banners/${user}.txt"
|
|
|
|
|
- echo -e "<font color=\"white\">👤 <b>Username :</b> $user</font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
|
|
|
|
|
- echo -e "<font color=\"white\">📅 <b>Expiration :</b> $expiry ($days_left)</font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
|
|
|
|
|
- echo -e "<font color=\"white\">📊 <b>Bandwidth :</b> $bw_info</font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
|
|
|
|
|
- echo -e "<font color=\"white\">🔌 <b>Sessions :</b> $online_count/$limit</font><br><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
|
|
|
|
|
|
|
+
|
|
|
|
|
+ banner_content="<br><font color=\"yellow\"><b> ✨ ACCOUNT STATUS ✨ </b></font><br><br>"
|
|
|
|
|
+ banner_content+="<font color=\"white\">👤 <b>Username :</b> $user</font><br>"
|
|
|
|
|
+ banner_content+="<font color=\"white\">📅 <b>Expiration :</b> $expiry ($days_left)</font><br>"
|
|
|
|
|
+ banner_content+="<font color=\"white\">📊 <b>Bandwidth :</b> $bw_info</font><br>"
|
|
|
|
|
+ banner_content+="<font color=\"white\">🔌 <b>Sessions :</b> $online_count/$limit</font><br><br>"
|
|
|
|
|
+ write_banner_if_changed "$user" "$banner_content"
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- # --- Bandwidth Check ---
|
|
|
|
|
[[ -z "$bandwidth_gb" || "$bandwidth_gb" == "0" ]] && continue
|
|
[[ -z "$bandwidth_gb" || "$bandwidth_gb" == "0" ]] && continue
|
|
|
-
|
|
|
|
|
- # Get user UID
|
|
|
|
|
- user_uid=$(id -u "$user" 2>/dev/null)
|
|
|
|
|
- [[ -z "$user_uid" ]] && continue
|
|
|
|
|
-
|
|
|
|
|
- # Find sshd PIDs for this user via loginuid
|
|
|
|
|
- pids=""
|
|
|
|
|
-
|
|
|
|
|
- # Method 1: pgrep
|
|
|
|
|
- m1=$(pgrep -u "$user" sshd 2>/dev/null | tr '\n' ' ')
|
|
|
|
|
- pids="$m1"
|
|
|
|
|
-
|
|
|
|
|
- # Method 2: loginuid scan
|
|
|
|
|
- for p in /proc/[0-9]*/loginuid; do
|
|
|
|
|
- [[ ! -f "$p" ]] && continue
|
|
|
|
|
- luid=$(cat "$p" 2>/dev/null)
|
|
|
|
|
- [[ -z "$luid" || "$luid" == "4294967295" ]] && continue
|
|
|
|
|
- [[ "$luid" != "$user_uid" ]] && continue
|
|
|
|
|
-
|
|
|
|
|
- pid_dir=$(dirname "$p")
|
|
|
|
|
- pid_num=$(basename "$pid_dir")
|
|
|
|
|
-
|
|
|
|
|
- cname=$(cat "$pid_dir/comm" 2>/dev/null)
|
|
|
|
|
- [[ "$cname" != "sshd" ]] && continue
|
|
|
|
|
-
|
|
|
|
|
- ppid_val=$(awk '/^PPid:/{print $2}' "$pid_dir/status" 2>/dev/null)
|
|
|
|
|
- [[ "$ppid_val" == "1" ]] && continue
|
|
|
|
|
-
|
|
|
|
|
- pids="$pids $pid_num"
|
|
|
|
|
- done
|
|
|
|
|
-
|
|
|
|
|
- # Deduplicate
|
|
|
|
|
- pids=$(echo "$pids" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ')
|
|
|
|
|
-
|
|
|
|
|
- # Read accumulated usage
|
|
|
|
|
|
|
+
|
|
|
usagefile="$BW_DIR/${user}.usage"
|
|
usagefile="$BW_DIR/${user}.usage"
|
|
|
accumulated=0
|
|
accumulated=0
|
|
|
if [[ -f "$usagefile" ]]; then
|
|
if [[ -f "$usagefile" ]]; then
|
|
|
- accumulated=$(cat "$usagefile" 2>/dev/null)
|
|
|
|
|
- if ! [[ "$accumulated" =~ ^[0-9]+$ ]]; then accumulated=0; fi
|
|
|
|
|
|
|
+ read -r accumulated < "$usagefile"
|
|
|
|
|
+ [[ "$accumulated" =~ ^[0-9]+$ ]] || accumulated=0
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ pids="${session_pids["$user"]}"
|
|
|
if [[ -z "$pids" ]]; then
|
|
if [[ -z "$pids" ]]; then
|
|
|
rm -f "$PID_DIR/${user}__"*.last 2>/dev/null
|
|
rm -f "$PID_DIR/${user}__"*.last 2>/dev/null
|
|
|
continue
|
|
continue
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
delta_total=0
|
|
delta_total=0
|
|
|
-
|
|
|
|
|
for pid in $pids; do
|
|
for pid in $pids; do
|
|
|
- [[ -z "$pid" ]] && continue
|
|
|
|
|
|
|
+ [[ "$pid" =~ ^[0-9]+$ ]] || continue
|
|
|
io_file="/proc/$pid/io"
|
|
io_file="/proc/$pid/io"
|
|
|
|
|
+ cur=0
|
|
|
if [[ -r "$io_file" ]]; then
|
|
if [[ -r "$io_file" ]]; then
|
|
|
- rchar=$(awk '/^rchar:/{print $2}' "$io_file" 2>/dev/null)
|
|
|
|
|
- wchar=$(awk '/^wchar:/{print $2}' "$io_file" 2>/dev/null)
|
|
|
|
|
- [[ -z "$rchar" ]] && rchar=0
|
|
|
|
|
- [[ -z "$wchar" ]] && wchar=0
|
|
|
|
|
|
|
+ rchar=0
|
|
|
|
|
+ wchar=0
|
|
|
|
|
+ while read -r key value; do
|
|
|
|
|
+ case "$key" in
|
|
|
|
|
+ rchar:) rchar=${value:-0} ;;
|
|
|
|
|
+ wchar:) wchar=${value:-0} ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+ done < "$io_file"
|
|
|
cur=$((rchar + wchar))
|
|
cur=$((rchar + wchar))
|
|
|
- else
|
|
|
|
|
- cur=0
|
|
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
pidfile="$PID_DIR/${user}__${pid}.last"
|
|
pidfile="$PID_DIR/${user}__${pid}.last"
|
|
|
-
|
|
|
|
|
if [[ -f "$pidfile" ]]; then
|
|
if [[ -f "$pidfile" ]]; then
|
|
|
- prev=$(cat "$pidfile" 2>/dev/null)
|
|
|
|
|
- if ! [[ "$prev" =~ ^[0-9]+$ ]]; then prev=0; fi
|
|
|
|
|
-
|
|
|
|
|
- if [[ "$cur" -ge "$prev" ]]; then
|
|
|
|
|
|
|
+ read -r prev < "$pidfile"
|
|
|
|
|
+ [[ "$prev" =~ ^[0-9]+$ ]] || prev=0
|
|
|
|
|
+ if (( cur >= prev )); then
|
|
|
d=$((cur - prev))
|
|
d=$((cur - prev))
|
|
|
else
|
|
else
|
|
|
d=$cur
|
|
d=$cur
|
|
|
fi
|
|
fi
|
|
|
delta_total=$((delta_total + d))
|
|
delta_total=$((delta_total + d))
|
|
|
fi
|
|
fi
|
|
|
- echo "$cur" > "$pidfile"
|
|
|
|
|
|
|
+ printf "%s\n" "$cur" > "$pidfile"
|
|
|
done
|
|
done
|
|
|
-
|
|
|
|
|
- # Clean up dead PID files
|
|
|
|
|
|
|
+
|
|
|
for f in "$PID_DIR/${user}__"*.last; do
|
|
for f in "$PID_DIR/${user}__"*.last; do
|
|
|
- [[ ! -f "$f" ]] && continue
|
|
|
|
|
- fpid=$(basename "$f" .last)
|
|
|
|
|
- fpid=${fpid#${user}__}
|
|
|
|
|
- [[ ! -d "/proc/$fpid" ]] && rm -f "$f"
|
|
|
|
|
|
|
+ [[ -f "$f" ]] || continue
|
|
|
|
|
+ fpid=${f##*__}
|
|
|
|
|
+ fpid=${fpid%.last}
|
|
|
|
|
+ [[ -d "/proc/$fpid" ]] || rm -f "$f"
|
|
|
done
|
|
done
|
|
|
-
|
|
|
|
|
- # Update total
|
|
|
|
|
|
|
+
|
|
|
new_total=$((accumulated + delta_total))
|
|
new_total=$((accumulated + delta_total))
|
|
|
- echo "$new_total" > "$usagefile"
|
|
|
|
|
-
|
|
|
|
|
- # Check quota
|
|
|
|
|
|
|
+ printf "%s\n" "$new_total" > "$usagefile"
|
|
|
|
|
+
|
|
|
quota_bytes=$(awk "BEGIN {printf \"%.0f\", $bandwidth_gb * 1073741824}")
|
|
quota_bytes=$(awk "BEGIN {printf \"%.0f\", $bandwidth_gb * 1073741824}")
|
|
|
-
|
|
|
|
|
- if [[ "$new_total" -ge "$quota_bytes" ]]; then
|
|
|
|
|
- if ! passwd -S "$user" 2>/dev/null | grep -q " L "; then
|
|
|
|
|
|
|
+ if [[ "$quota_bytes" =~ ^[0-9]+$ ]] && (( new_total >= quota_bytes )); then
|
|
|
|
|
+ if ! $user_locked; then
|
|
|
usermod -L "$user" &>/dev/null
|
|
usermod -L "$user" &>/dev/null
|
|
|
killall -u "$user" -9 &>/dev/null
|
|
killall -u "$user" -9 &>/dev/null
|
|
|
|
|
+ locked_users["$user"]=1
|
|
|
fi
|
|
fi
|
|
|
fi
|
|
fi
|
|
|
-
|
|
|
|
|
done < "$DB_FILE"
|
|
done < "$DB_FILE"
|
|
|
-
|
|
|
|
|
- sleep 15
|
|
|
|
|
|
|
+
|
|
|
|
|
+ sleep "$SCAN_INTERVAL"
|
|
|
done
|
|
done
|
|
|
EOF
|
|
EOF
|
|
|
chmod +x "$LIMITER_SCRIPT"
|
|
chmod +x "$LIMITER_SCRIPT"
|
|
@@ -438,7 +479,10 @@ After=network.target
|
|
|
Type=simple
|
|
Type=simple
|
|
|
ExecStart=$LIMITER_SCRIPT
|
|
ExecStart=$LIMITER_SCRIPT
|
|
|
Restart=always
|
|
Restart=always
|
|
|
-RestartSec=5
|
|
|
|
|
|
|
+RestartSec=10
|
|
|
|
|
+Nice=10
|
|
|
|
|
+IOSchedulingClass=best-effort
|
|
|
|
|
+IOSchedulingPriority=7
|
|
|
|
|
|
|
|
[Install]
|
|
[Install]
|
|
|
WantedBy=multi-user.target
|
|
WantedBy=multi-user.target
|
|
@@ -458,6 +502,13 @@ EOF
|
|
|
fi
|
|
fi
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+sync_runtime_components_if_needed() {
|
|
|
|
|
+ local limiter_marker="# FirewallFalcon limiter version 2026-03-20.1"
|
|
|
|
|
+ if [[ ! -f "$LIMITER_SCRIPT" ]] || ! grep -Fqx "$limiter_marker" "$LIMITER_SCRIPT" 2>/dev/null; then
|
|
|
|
|
+ setup_limiter_service >/dev/null 2>&1
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
setup_bandwidth_service() {
|
|
setup_bandwidth_service() {
|
|
|
mkdir -p "$BANDWIDTH_DIR"
|
|
mkdir -p "$BANDWIDTH_DIR"
|
|
|
# Bandwidth monitoring is now integrated into the limiter service above.
|
|
# Bandwidth monitoring is now integrated into the limiter service above.
|
|
@@ -695,7 +746,11 @@ _select_user_interface() {
|
|
|
echo
|
|
echo
|
|
|
local choice
|
|
local choice
|
|
|
while true; do
|
|
while true; do
|
|
|
- read -p "👉 Enter the number or exact username: " choice
|
|
|
|
|
|
|
+ if ! read -r -p "👉 Enter the number or exact username: " choice; then
|
|
|
|
|
+ echo
|
|
|
|
|
+ SELECTED_USER=""
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 0 ] && [ "$choice" -le "${#users[@]}" ]; then
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 0 ] && [ "$choice" -le "${#users[@]}" ]; then
|
|
|
if [ "$choice" -eq 0 ]; then
|
|
if [ "$choice" -eq 0 ]; then
|
|
|
SELECTED_USER=""; return
|
|
SELECTED_USER=""; return
|
|
@@ -753,7 +808,11 @@ _select_multi_user_interface() {
|
|
|
echo
|
|
echo
|
|
|
local choice
|
|
local choice
|
|
|
while true; do
|
|
while true; do
|
|
|
- read -p "👉 Enter user numbers or usernames: " choice
|
|
|
|
|
|
|
+ if ! read -r -p "👉 Enter user numbers or usernames: " choice; then
|
|
|
|
|
+ echo
|
|
|
|
|
+ SELECTED_USERS=()
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
choice=$(echo "$choice" | tr ',' ' ') # Replace commas with spaces
|
|
choice=$(echo "$choice" | tr ',' ' ') # Replace commas with spaces
|
|
|
|
|
|
|
|
if [[ -z "$choice" ]]; then
|
|
if [[ -z "$choice" ]]; then
|
|
@@ -970,7 +1029,12 @@ edit_user() {
|
|
|
printf " ${C_GREEN}[ 3]${C_RESET} %-35s\n" "📶 Change Connection Limit"
|
|
printf " ${C_GREEN}[ 3]${C_RESET} %-35s\n" "📶 Change Connection Limit"
|
|
|
printf " ${C_GREEN}[ 4]${C_RESET} %-35s\n" "📦 Change Bandwidth Limit"
|
|
printf " ${C_GREEN}[ 4]${C_RESET} %-35s\n" "📦 Change Bandwidth Limit"
|
|
|
printf " ${C_GREEN}[ 5]${C_RESET} %-35s\n" "🔄 Reset Bandwidth Counter"
|
|
printf " ${C_GREEN}[ 5]${C_RESET} %-35s\n" "🔄 Reset Bandwidth Counter"
|
|
|
- echo -e "\n ${C_RED}[ 0]${C_RESET} ✅ Finish Editing"; echo; read -p "👉 Enter your choice: " edit_choice
|
|
|
|
|
|
|
+ echo -e "\n ${C_RED}[ 0]${C_RESET} ✅ Finish Editing"
|
|
|
|
|
+ echo
|
|
|
|
|
+ if ! read -r -p "👉 Enter your choice: " edit_choice; then
|
|
|
|
|
+ echo
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
case $edit_choice in
|
|
case $edit_choice in
|
|
|
1)
|
|
1)
|
|
|
local new_pass=""
|
|
local new_pass=""
|
|
@@ -1017,7 +1081,7 @@ edit_user() {
|
|
|
0) return ;;
|
|
0) return ;;
|
|
|
*) echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" ;;
|
|
*) echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" ;;
|
|
|
esac
|
|
esac
|
|
|
- echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to continue editing..." && read -r
|
|
|
|
|
|
|
+ echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to continue editing..." && read -r || return
|
|
|
done
|
|
done
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1077,7 +1141,6 @@ list_users() {
|
|
|
current_ts=$(date +%s)
|
|
current_ts=$(date +%s)
|
|
|
local -A system_user_lookup=()
|
|
local -A system_user_lookup=()
|
|
|
local -A locked_user_lookup=()
|
|
local -A locked_user_lookup=()
|
|
|
- local -A session_counts=()
|
|
|
|
|
|
|
|
|
|
while IFS=: read -r system_user _rest; do
|
|
while IFS=: read -r system_user _rest; do
|
|
|
[[ -n "$system_user" ]] && system_user_lookup["$system_user"]=1
|
|
[[ -n "$system_user" ]] && system_user_lookup["$system_user"]=1
|
|
@@ -1089,13 +1152,10 @@ list_users() {
|
|
|
locked_user_lookup["$passwd_user"]=1
|
|
locked_user_lookup["$passwd_user"]=1
|
|
|
fi
|
|
fi
|
|
|
done < <(passwd -Sa 2>/dev/null)
|
|
done < <(passwd -Sa 2>/dev/null)
|
|
|
-
|
|
|
|
|
- while read -r ssh_user ssh_count; do
|
|
|
|
|
- [[ -n "$ssh_user" && -n "$ssh_count" ]] && session_counts["$ssh_user"]="$ssh_count"
|
|
|
|
|
- done < <(ps -C sshd -o user= 2>/dev/null | awk 'NF {count[$1]++} END {for (user in count) print user, count[user]}')
|
|
|
|
|
|
|
+ refresh_ssh_session_cache
|
|
|
|
|
|
|
|
while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
|
|
while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
|
|
|
- local online_count="${session_counts[$user]:-0}"
|
|
|
|
|
|
|
+ local online_count="${SSH_SESSION_COUNTS[$user]:-0}"
|
|
|
local connection_string="$online_count / $limit"
|
|
local connection_string="$online_count / $limit"
|
|
|
local plain_status="Active"
|
|
local plain_status="Active"
|
|
|
local status="${C_GREEN}🟢 Active${C_RESET}"
|
|
local status="${C_GREEN}🟢 Active${C_RESET}"
|
|
@@ -2513,7 +2573,10 @@ install_falcon_proxy() {
|
|
|
|
|
|
|
|
local choice
|
|
local choice
|
|
|
while true; do
|
|
while true; do
|
|
|
- read -p "👉 Enter version number [1]: " choice
|
|
|
|
|
|
|
+ if ! read -r -p "👉 Enter version number [1]: " choice; then
|
|
|
|
|
+ echo
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
choice=${choice:-1}
|
|
choice=${choice:-1}
|
|
|
if [[ "$choice" == "0" ]]; then return; fi
|
|
if [[ "$choice" == "0" ]]; then return; fi
|
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -le "${#versions[@]}" ]; then
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -le "${#versions[@]}" ]; then
|
|
@@ -3080,20 +3143,72 @@ uninstall_xui_panel() {
|
|
|
fi
|
|
fi
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-count_managed_online_sessions() {
|
|
|
|
|
|
|
+refresh_ssh_session_cache() {
|
|
|
|
|
+ local now db_mtime
|
|
|
|
|
+ now=$(date +%s)
|
|
|
|
|
+ db_mtime=$(stat -c %Y "$DB_FILE" 2>/dev/null || echo 0)
|
|
|
|
|
+
|
|
|
|
|
+ if (( SSH_SESSION_CACHE_TS > 0 && now - SSH_SESSION_CACHE_TS < SSH_SESSION_CACHE_TTL && db_mtime == SSH_SESSION_CACHE_DB_MTIME )); then
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ SSH_SESSION_COUNTS=()
|
|
|
|
|
+ SSH_SESSION_PIDS=()
|
|
|
|
|
+ SSH_SESSION_TOTAL=0
|
|
|
|
|
+ SSH_SESSION_CACHE_DB_MTIME=$db_mtime
|
|
|
|
|
+
|
|
|
if [[ ! -s "$DB_FILE" ]]; then
|
|
if [[ ! -s "$DB_FILE" ]]; then
|
|
|
- echo 0
|
|
|
|
|
|
|
+ SSH_SESSION_CACHE_TS=$now
|
|
|
return
|
|
return
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
- local session_count
|
|
|
|
|
- session_count=$(awk -F: 'NR==FNR { users[$1]=1; next } ($1 in users) { count++ } END { print count+0 }' "$DB_FILE" <(ps -C sshd -o user= 2>/dev/null))
|
|
|
|
|
- [[ "$session_count" =~ ^[0-9]+$ ]] || session_count=0
|
|
|
|
|
- echo "$session_count"
|
|
|
|
|
|
|
+ local -A managed_user_lookup=()
|
|
|
|
|
+ local -A uid_user_lookup=()
|
|
|
|
|
+ local -A seen_sessions=()
|
|
|
|
|
+ local managed_user system_user system_uid ssh_pid ssh_owner candidate_user login_uid
|
|
|
|
|
+
|
|
|
|
|
+ while IFS=: read -r managed_user _rest; do
|
|
|
|
|
+ [[ -n "$managed_user" && "$managed_user" != \#* ]] && managed_user_lookup["$managed_user"]=1
|
|
|
|
|
+ done < "$DB_FILE"
|
|
|
|
|
+
|
|
|
|
|
+ while IFS=: read -r system_user _ system_uid _rest; do
|
|
|
|
|
+ [[ -n "$system_user" && "$system_uid" =~ ^[0-9]+$ ]] && uid_user_lookup["$system_uid"]="$system_user"
|
|
|
|
|
+ done < /etc/passwd
|
|
|
|
|
+
|
|
|
|
|
+ while read -r ssh_pid ssh_owner; do
|
|
|
|
|
+ [[ "$ssh_pid" =~ ^[0-9]+$ ]] || continue
|
|
|
|
|
+
|
|
|
|
|
+ candidate_user=""
|
|
|
|
|
+ if [[ -n "$ssh_owner" && "$ssh_owner" != "root" && "$ssh_owner" != "sshd" && -n "${managed_user_lookup[$ssh_owner]+x}" ]]; then
|
|
|
|
|
+ candidate_user="$ssh_owner"
|
|
|
|
|
+ elif [[ -r "/proc/$ssh_pid/loginuid" ]]; then
|
|
|
|
|
+ login_uid=""
|
|
|
|
|
+ read -r login_uid < "/proc/$ssh_pid/loginuid" || login_uid=""
|
|
|
|
|
+ if [[ "$login_uid" =~ ^[0-9]+$ && "$login_uid" != "4294967295" ]]; then
|
|
|
|
|
+ candidate_user="${uid_user_lookup[$login_uid]}"
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ [[ -n "$candidate_user" && -n "${managed_user_lookup[$candidate_user]+x}" ]] || continue
|
|
|
|
|
+ [[ -z "${seen_sessions[$candidate_user:$ssh_pid]+x}" ]] || continue
|
|
|
|
|
+
|
|
|
|
|
+ seen_sessions["$candidate_user:$ssh_pid"]=1
|
|
|
|
|
+ ((SSH_SESSION_COUNTS["$candidate_user"]++))
|
|
|
|
|
+ SSH_SESSION_PIDS["$candidate_user"]+="$ssh_pid "
|
|
|
|
|
+ ((SSH_SESSION_TOTAL++))
|
|
|
|
|
+ done < <(ps -C sshd -o pid=,user= 2>/dev/null)
|
|
|
|
|
+
|
|
|
|
|
+ SSH_SESSION_CACHE_TS=$now
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+count_managed_online_sessions() {
|
|
|
|
|
+ refresh_ssh_session_cache
|
|
|
|
|
+ echo "$SSH_SESSION_TOTAL"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
invalidate_banner_cache() {
|
|
invalidate_banner_cache() {
|
|
|
BANNER_CACHE_TS=0
|
|
BANNER_CACHE_TS=0
|
|
|
|
|
+ SSH_SESSION_CACHE_TS=0
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
refresh_banner_cache() {
|
|
refresh_banner_cache() {
|
|
@@ -3103,7 +3218,9 @@ refresh_banner_cache() {
|
|
|
return
|
|
return
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
- BANNER_CACHE_OS_NAME=$(grep -oP 'PRETTY_NAME="\K[^"]+' /etc/os-release 2>/dev/null || echo "Linux")
|
|
|
|
|
|
|
+ if [[ -z "$BANNER_CACHE_OS_NAME" ]]; then
|
|
|
|
|
+ BANNER_CACHE_OS_NAME=$(grep -oP 'PRETTY_NAME="\K[^"]+' /etc/os-release 2>/dev/null || echo "Linux")
|
|
|
|
|
+ fi
|
|
|
BANNER_CACHE_UP_TIME=$(uptime -p 2>/dev/null | sed 's/up //' || echo "unknown")
|
|
BANNER_CACHE_UP_TIME=$(uptime -p 2>/dev/null | sed 's/up //' || echo "unknown")
|
|
|
BANNER_CACHE_RAM_USAGE=$(free -m | awk '/^Mem:/{if($2>0){printf "%.2f", $3*100/$2}else{print "0.00"}}')
|
|
BANNER_CACHE_RAM_USAGE=$(free -m | awk '/^Mem:/{if($2>0){printf "%.2f", $3*100/$2}else{print "0.00"}}')
|
|
|
BANNER_CACHE_CPU_LOAD=$(awk '{print $1}' /proc/loadavg 2>/dev/null)
|
|
BANNER_CACHE_CPU_LOAD=$(awk '{print $1}' /proc/loadavg 2>/dev/null)
|
|
@@ -3118,7 +3235,7 @@ refresh_banner_cache() {
|
|
|
|
|
|
|
|
show_banner() {
|
|
show_banner() {
|
|
|
refresh_banner_cache
|
|
refresh_banner_cache
|
|
|
- clear
|
|
|
|
|
|
|
+ [[ -t 1 ]] && clear
|
|
|
echo
|
|
echo
|
|
|
echo -e "${C_TITLE} FirewallFalcon Manager ${C_RESET}${C_DIM}| v4.0.0 Premium Edition${C_RESET}"
|
|
echo -e "${C_TITLE} FirewallFalcon Manager ${C_RESET}${C_DIM}| v4.0.0 Premium Edition${C_RESET}"
|
|
|
echo -e "${C_BLUE} ─────────────────────────────────────────────────────────${C_RESET}"
|
|
echo -e "${C_BLUE} ─────────────────────────────────────────────────────────${C_RESET}"
|
|
@@ -3177,7 +3294,10 @@ protocol_menu() {
|
|
|
echo -e " ${C_DIM}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~${C_RESET}"
|
|
echo -e " ${C_DIM}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~${C_RESET}"
|
|
|
echo -e " ${C_WARN}[ 0]${C_RESET} ↩️ Return to Main Menu"
|
|
echo -e " ${C_WARN}[ 0]${C_RESET} ↩️ Return to Main Menu"
|
|
|
echo
|
|
echo
|
|
|
- read -p "$(echo -e ${C_PROMPT}"👉 Select an option: "${C_RESET})" choice
|
|
|
|
|
|
|
+ if ! read -r -p "$(echo -e ${C_PROMPT}"👉 Select an option: "${C_RESET})" choice; then
|
|
|
|
|
+ echo
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
case $choice in
|
|
case $choice in
|
|
|
1) install_badvpn; press_enter ;; 2) uninstall_badvpn; press_enter ;;
|
|
1) install_badvpn; press_enter ;; 2) uninstall_badvpn; press_enter ;;
|
|
|
3) install_udp_custom; press_enter ;; 4) uninstall_udp_custom; press_enter ;;
|
|
3) install_udp_custom; press_enter ;; 4) uninstall_udp_custom; press_enter ;;
|
|
@@ -3583,42 +3703,61 @@ client_config_menu() {
|
|
|
generate_client_config "$u" "$pass"
|
|
generate_client_config "$u" "$pass"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+format_rate_from_kbps() {
|
|
|
|
|
+ local kbps=${1:-0}
|
|
|
|
|
+ if (( kbps >= 1024 )); then
|
|
|
|
|
+ printf "%d.%02d MB/s" $((kbps / 1024)) $((((kbps % 1024) * 100) / 1024))
|
|
|
|
|
+ else
|
|
|
|
|
+ printf "%d KB/s" "$kbps"
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
# Lightweight Bash Monitor (No vnStat required)
|
|
# Lightweight Bash Monitor (No vnStat required)
|
|
|
simple_live_monitor() {
|
|
simple_live_monitor() {
|
|
|
local iface=$1
|
|
local iface=$1
|
|
|
|
|
+ local rx_file="/sys/class/net/$iface/statistics/rx_bytes"
|
|
|
|
|
+ local tx_file="/sys/class/net/$iface/statistics/tx_bytes"
|
|
|
|
|
+ local interval=2
|
|
|
|
|
+ local stop_monitor=0
|
|
|
|
|
+ local rx1 tx1 rx2 tx2 rx_diff tx_diff rx_kbs tx_kbs rx_fmt tx_fmt
|
|
|
|
|
+
|
|
|
|
|
+ if [[ -z "$iface" || ! -r "$rx_file" || ! -r "$tx_file" ]]; then
|
|
|
|
|
+ echo -e "\n${C_RED}❌ Could not read interface statistics for '${iface:-unknown}'.${C_RESET}"
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
echo -e "\n${C_BLUE}⚡ Starting Lightweight Traffic Monitor for $iface...${C_RESET}"
|
|
echo -e "\n${C_BLUE}⚡ Starting Lightweight Traffic Monitor for $iface...${C_RESET}"
|
|
|
echo -e "${C_DIM}Press [Ctrl+C] to stop.${C_RESET}\n"
|
|
echo -e "${C_DIM}Press [Ctrl+C] to stop.${C_RESET}\n"
|
|
|
-
|
|
|
|
|
- # Get initial values
|
|
|
|
|
- local rx1=$(cat /sys/class/net/$iface/statistics/rx_bytes)
|
|
|
|
|
- local tx1=$(cat /sys/class/net/$iface/statistics/tx_bytes)
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ read -r rx1 < "$rx_file"
|
|
|
|
|
+ read -r tx1 < "$tx_file"
|
|
|
|
|
+
|
|
|
printf "%-15s | %-15s\n" "⬇️ Download" "⬆️ Upload"
|
|
printf "%-15s | %-15s\n" "⬇️ Download" "⬆️ Upload"
|
|
|
echo "-----------------------------------"
|
|
echo "-----------------------------------"
|
|
|
-
|
|
|
|
|
- while true; do
|
|
|
|
|
- sleep 1
|
|
|
|
|
- local rx2=$(cat /sys/class/net/$iface/statistics/rx_bytes)
|
|
|
|
|
- local tx2=$(cat /sys/class/net/$iface/statistics/tx_bytes)
|
|
|
|
|
-
|
|
|
|
|
- # Calculate diffs
|
|
|
|
|
- local rx_diff=$((rx2 - rx1))
|
|
|
|
|
- local tx_diff=$((tx2 - tx1))
|
|
|
|
|
-
|
|
|
|
|
- # Convert to KB/s
|
|
|
|
|
- local rx_kbs=$((rx_diff / 1024))
|
|
|
|
|
- local tx_kbs=$((tx_diff / 1024))
|
|
|
|
|
-
|
|
|
|
|
- # Formatting for MB/s if > 1024 KB
|
|
|
|
|
- if [ $rx_kbs -gt 1024 ]; then rx_fmt=$(awk "BEGIN {printf \"%.2f MB/s\", $rx_kbs/1024}"); else rx_fmt="${rx_kbs} KB/s"; fi
|
|
|
|
|
- if [ $tx_kbs -gt 1024 ]; then tx_fmt=$(awk "BEGIN {printf \"%.2f MB/s\", $tx_kbs/1024}"); else tx_fmt="${tx_kbs} KB/s"; fi
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ trap 'stop_monitor=1' INT TERM
|
|
|
|
|
+ while (( ! stop_monitor )); do
|
|
|
|
|
+ sleep "$interval"
|
|
|
|
|
+ read -r rx2 < "$rx_file" || break
|
|
|
|
|
+ read -r tx2 < "$tx_file" || break
|
|
|
|
|
+
|
|
|
|
|
+ rx_diff=$((rx2 - rx1))
|
|
|
|
|
+ tx_diff=$((tx2 - tx1))
|
|
|
|
|
+ (( rx_diff < 0 )) && rx_diff=0
|
|
|
|
|
+ (( tx_diff < 0 )) && tx_diff=0
|
|
|
|
|
+
|
|
|
|
|
+ rx_kbs=$((rx_diff / 1024 / interval))
|
|
|
|
|
+ tx_kbs=$((tx_diff / 1024 / interval))
|
|
|
|
|
+ rx_fmt=$(format_rate_from_kbps "$rx_kbs")
|
|
|
|
|
+ tx_fmt=$(format_rate_from_kbps "$tx_kbs")
|
|
|
|
|
+
|
|
|
printf "\r%-15s | %-15s" "$rx_fmt" "$tx_fmt"
|
|
printf "\r%-15s | %-15s" "$rx_fmt" "$tx_fmt"
|
|
|
-
|
|
|
|
|
- # Reset for next loop
|
|
|
|
|
|
|
+
|
|
|
rx1=$rx2
|
|
rx1=$rx2
|
|
|
tx1=$tx2
|
|
tx1=$tx2
|
|
|
done
|
|
done
|
|
|
|
|
+ trap - INT TERM
|
|
|
|
|
+ echo
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
traffic_monitor_menu() {
|
|
traffic_monitor_menu() {
|
|
@@ -3894,7 +4033,7 @@ auto_reboot_menu() {
|
|
|
|
|
|
|
|
|
|
|
|
|
press_enter() {
|
|
press_enter() {
|
|
|
- echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return to the menu..." && read -r
|
|
|
|
|
|
|
+ echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return to the menu..." && read -r || true
|
|
|
}
|
|
}
|
|
|
invalid_option() {
|
|
invalid_option() {
|
|
|
echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" && sleep 1
|
|
echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" && sleep 1
|
|
@@ -3929,7 +4068,10 @@ main_menu() {
|
|
|
echo -e " ${C_DANGER}═══════════════════[ ${C_BOLD}🔥 DANGER ZONE ${C_RESET}${C_DANGER}]═══════════════════${C_RESET}"
|
|
echo -e " ${C_DANGER}═══════════════════[ ${C_BOLD}🔥 DANGER ZONE ${C_RESET}${C_DANGER}]═══════════════════${C_RESET}"
|
|
|
echo -e " ${C_DANGER}[99]${C_RESET} Uninstall Script ${C_WARN}[ 0]${C_RESET} Exit"
|
|
echo -e " ${C_DANGER}[99]${C_RESET} Uninstall Script ${C_WARN}[ 0]${C_RESET} Exit"
|
|
|
echo
|
|
echo
|
|
|
- read -p "$(echo -e ${C_PROMPT}"👉 Select an option: "${C_RESET})" choice
|
|
|
|
|
|
|
+ if ! read -r -p "$(echo -e ${C_PROMPT}"👉 Select an option: "${C_RESET})" choice; then
|
|
|
|
|
+ echo
|
|
|
|
|
+ exit 0
|
|
|
|
|
+ fi
|
|
|
case $choice in
|
|
case $choice in
|
|
|
1) create_user; press_enter ;;
|
|
1) create_user; press_enter ;;
|
|
|
2) delete_user; press_enter ;;
|
|
2) delete_user; press_enter ;;
|
|
@@ -3966,4 +4108,6 @@ if [[ "$1" == "--install-setup" ]]; then
|
|
|
exit 0
|
|
exit 0
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
|
|
+require_interactive_terminal
|
|
|
|
|
+sync_runtime_components_if_needed
|
|
|
main_menu
|
|
main_menu
|