feat: Add per-user IP limit exemption

Introduces a new 'unlimited_user' flag for users. When set to true, the user is exempt from the concurrent IP limit enforcement by `limit.sh`.

- `core/scripts/hysteria2/add_user.py`:
  - Adds an optional `unlimited_user` argument (defaults to false).
  - Stores the `unlimited_user` boolean in the `users.json` file when adding a user.

- `core/scripts/hysteria2/limit.sh`:
  - Modifies `check_ip_limit` function to first check the `unlimited_user` flag for the connecting user in `users.json`.
  - If the flag is true, the script bypasses the IP limit check for that user.
  - Uses `jq` for reliable parsing and includes a `grep` fallback for systems without `jq`.
  - Existing users without the flag are treated as limited (default behavior).
This commit is contained in:
Whispering Wind
2025-08-11 23:21:36 +03:30
committed by GitHub
parent 0a377d221e
commit eac13764e2
2 changed files with 29 additions and 7 deletions

View File

@ -9,7 +9,7 @@ from datetime import datetime
from init_paths import *
from paths import *
def add_user(username, traffic_gb, expiration_days, password=None, creation_date=None):
def add_user(username, traffic_gb, expiration_days, password=None, creation_date=None, unlimited_user=False):
"""
Adds a new user to the USERS_FILE.
@ -19,12 +19,13 @@ def add_user(username, traffic_gb, expiration_days, password=None, creation_date
expiration_days (str): The number of days until the account expires.
password (str, optional): The user's password. If None, a random one is generated.
creation_date (str, optional): The account creation date in YYYY-MM-DD format. If None, the current date is used.
unlimited_user (bool, optional): If True, user is exempt from IP limits. Defaults to False.
Returns:
int: 0 on success, 1 on failure.
"""
if not username or not traffic_gb or not expiration_days:
print(f"Usage: {sys.argv[0]} <username> <traffic_limit_GB> <expiration_days> [password] [creation_date]")
print(f"Usage: {sys.argv[0]} <username> <traffic_limit_GB> <expiration_days> [password] [creation_date] [unlimited_user (true/false)]")
return 1
try:
@ -45,6 +46,7 @@ def add_user(username, traffic_gb, expiration_days, password=None, creation_date
password = subprocess.check_output(['cat', '/proc/sys/kernel/random/uuid'], text=True).strip()
except Exception:
print("Error: Failed to generate password. Please install 'pwgen' or ensure /proc access.")
return 1
if not creation_date:
creation_date = datetime.now().strftime("%Y-%m-%d")
@ -88,7 +90,8 @@ def add_user(username, traffic_gb, expiration_days, password=None, creation_date
"max_download_bytes": traffic_bytes,
"expiration_days": expiration_days,
"account_creation_date": creation_date,
"blocked": False
"blocked": False,
"unlimited_user": unlimited_user
}
f.seek(0)
@ -103,8 +106,8 @@ def add_user(username, traffic_gb, expiration_days, password=None, creation_date
return 1
if __name__ == "__main__":
if len(sys.argv) not in [4, 6]:
print(f"Usage: {sys.argv[0]} <username> <traffic_limit_GB> <expiration_days> [password] [creation_date]")
if len(sys.argv) < 4 or len(sys.argv) > 7:
print(f"Usage: {sys.argv[0]} <username> <traffic_limit_GB> <expiration_days> [password] [creation_date] [unlimited_user (true/false)]")
sys.exit(1)
username = sys.argv[1]
@ -112,6 +115,8 @@ if __name__ == "__main__":
expiration_days = sys.argv[3]
password = sys.argv[4] if len(sys.argv) > 4 else None
creation_date = sys.argv[5] if len(sys.argv) > 5 else None
unlimited_user_str = sys.argv[6] if len(sys.argv) > 6 else "false"
unlimited_user = unlimited_user_str.lower() == 'true'
exit_code = add_user(username, traffic_gb, expiration_days, password, creation_date)
exit_code = add_user(username, traffic_gb, expiration_days, password, creation_date, unlimited_user)
sys.exit(exit_code)

View File

@ -183,6 +183,23 @@ check_ip_limit() {
local username="$1"
local ips=()
local is_unlimited="false"
if [ -f "$USERS_FILE" ]; then
if command -v jq &>/dev/null; then
is_unlimited=$(jq -r --arg user "$username" '.[$user].unlimited_user // "false"' "$USERS_FILE" 2>/dev/null)
else
if grep -q "\"$username\"" "$USERS_FILE" && \
grep -A 5 "\"$username\"" "$USERS_FILE" | grep -q '"unlimited_user": true'; then
is_unlimited="true"
fi
fi
fi
if [ "$is_unlimited" = "true" ]; then
log_message "INFO" "User $username is exempt from IP limit. Skipping check."
return
fi
# Get all IPs for this user
if command -v jq &>/dev/null; then
readarray -t ips < <(jq -r --arg user "$username" '.[$user][]' "$CONNECTIONS_FILE" 2>/dev/null)
@ -364,4 +381,4 @@ case "$1" in
;;
esac
exit 0
exit 0