Merge pull request #185 from ReturnFI/beta
OBFS & WARP management, UI improvements, and better normalsub config
This commit is contained in:
25
changelog
25
changelog
@ -1,18 +1,13 @@
|
||||
# [1.11.0] - 2025-05-28
|
||||
# [1.12.0] - 2025-06-03
|
||||
|
||||
#### ✨ Features & Enhancements
|
||||
|
||||
#### ⚠️ Attention Required
|
||||
* 🎨 **UI/UX:** Some **enhancements to the Settings page** with cleaner design and improved interactivity
|
||||
* 🌐 **New Tabs:** Added **WARP** and **OBFS** management tabs to the settings panel for easier control
|
||||
* 🔍 **OBFS:**
|
||||
|
||||
🚨 **Important:** Due to changes in the `normalsub` system (now using passwords in links and routing through Caddy),
|
||||
you must **re-activate NormalSUB** from the settings panel after upgrading to **v1.11.0**.
|
||||
|
||||
Failure to do so may result in broken subscription links.
|
||||
|
||||
---
|
||||
|
||||
#### ✨ Features & Improvements
|
||||
|
||||
* 🔐 **normalsub:** Use **user password** instead of username in subscription path for improved privacy (harder to enumerate users)
|
||||
* 🔁 **normalsub:** Now uses **Caddy** as a reverse proxy for better performance and flexibility
|
||||
* 🧩 **WARP API:** Adapted backend to support **JSON-based status output**
|
||||
* 🛠️ **Script Update:** WARP status script now outputs clean **JSON**, allowing easier parsing and integration
|
||||
* Added **OBFS status API endpoint**
|
||||
* Introduced `--check` flag to the `manage_obfs` CLI tool
|
||||
* Enabled **status checking** functionality to monitor OBFS state
|
||||
* 🔧 **normalsub:** Improved `edit_subpath` with **better error handling** and **efficient Caddy reload**
|
||||
* 📦 **Singbox:** Enhanced **Singbox template configuration** for better maintainability and compatibility
|
||||
|
||||
49
core/cli.py
49
core/cli.py
@ -250,19 +250,25 @@ def server_info():
|
||||
@cli.command('manage_obfs')
|
||||
@click.option('--remove', '-r', is_flag=True, help="Remove 'obfs' from config.json.")
|
||||
@click.option('--generate', '-g', is_flag=True, help="Generate new 'obfs' in config.json.")
|
||||
def manage_obfs(remove: bool, generate: bool):
|
||||
@click.option('--check', '-c', is_flag=True, help="Check 'obfs' status in config.json.")
|
||||
def manage_obfs(remove: bool, generate: bool, check: bool):
|
||||
try:
|
||||
if not remove and not generate:
|
||||
raise click.UsageError('Error: You must use either --remove or --generate')
|
||||
if remove and generate:
|
||||
raise click.UsageError('Error: You cannot use both --remove and --generate at the same time')
|
||||
options_selected = sum([remove, generate, check])
|
||||
if options_selected == 0:
|
||||
raise click.UsageError('Error: You must use either --remove, --generate, or --check.')
|
||||
if options_selected > 1:
|
||||
raise click.UsageError('Error: You can only use one of --remove, --generate, or --check at a time.')
|
||||
|
||||
if generate:
|
||||
cli_api.enable_hysteria2_obfs()
|
||||
click.echo('Obfs enabled successfully.')
|
||||
click.echo('OBFS enabled successfully.')
|
||||
elif remove:
|
||||
cli_api.disable_hysteria2_obfs()
|
||||
click.echo('Obfs disabled successfully.')
|
||||
click.echo('OBFS disabled successfully.')
|
||||
elif check:
|
||||
status_output = cli_api.check_hysteria2_obfs()
|
||||
click.echo(status_output)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f'{e}', err=True)
|
||||
|
||||
@ -361,16 +367,29 @@ def uninstall_warp():
|
||||
|
||||
|
||||
@cli.command('configure-warp')
|
||||
@click.option('--all', '-a', is_flag=True, help='Use WARP for all connections')
|
||||
@click.option('--popular-sites', '-p', is_flag=True, help='Use WARP for popular sites like Google, OpenAI, etc')
|
||||
@click.option('--domestic-sites', '-d', is_flag=True, help='Use WARP for Iran domestic sites')
|
||||
@click.option('--block-adult-sites', '-x', is_flag=True, help='Block adult content (porn)')
|
||||
def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool):
|
||||
@click.option('--set-all', 'set_all_traffic', type=click.Choice(['on', 'off']), help='Set WARP for all connections (on/off)', required=False)
|
||||
@click.option('--set-popular-sites', type=click.Choice(['on', 'off']), help='Set WARP for popular sites (on/off)', required=False)
|
||||
@click.option('--set-domestic-sites', type=click.Choice(['on', 'off']), help='Set behavior for domestic sites (on=WARP, off=REJECT)', required=False)
|
||||
@click.option('--set-block-adult-sites', type=click.Choice(['on', 'off']), help='Set block adult content (on/off)', required=False)
|
||||
def configure_warp_cmd(set_all_traffic: str | None,
|
||||
set_popular_sites: str | None,
|
||||
set_domestic_sites: str | None,
|
||||
set_block_adult_sites: str | None):
|
||||
if not any([set_all_traffic, set_popular_sites, set_domestic_sites, set_block_adult_sites]):
|
||||
click.echo("Error: At least one configuration option must be provided to configure-warp.", err=True)
|
||||
click.echo("Use --help for more information.")
|
||||
return
|
||||
|
||||
try:
|
||||
cli_api.configure_warp(all, popular_sites, domestic_sites, block_adult_sites)
|
||||
click.echo('WARP configured successfully.')
|
||||
cli_api.configure_warp(
|
||||
all_state=set_all_traffic,
|
||||
popular_sites_state=set_popular_sites,
|
||||
domestic_sites_state=set_domestic_sites,
|
||||
block_adult_sites_state=set_block_adult_sites
|
||||
)
|
||||
click.echo('WARP configuration update process initiated.')
|
||||
except Exception as e:
|
||||
click.echo(f'{e}', err=True)
|
||||
click.echo(f'Error configuring WARP: {e}', err=True)
|
||||
|
||||
@cli.command('warp-status')
|
||||
def warp_status():
|
||||
|
||||
@ -218,6 +218,10 @@ def disable_hysteria2_obfs():
|
||||
'''Removes 'obfs' from Hysteria2 configuration.'''
|
||||
run_cmd(['python3', Command.MANAGE_OBFS.value, '--remove'])
|
||||
|
||||
def check_hysteria2_obfs():
|
||||
'''Removes 'obfs' from Hysteria2 configuration.'''
|
||||
result = subprocess.run(["python3", Command.MANAGE_OBFS.value, "--check"], check=True, capture_output=True, text=True)
|
||||
return result.stdout.strip()
|
||||
|
||||
def enable_hysteria2_masquerade(domain: str):
|
||||
'''Enables masquerade for Hysteria2.'''
|
||||
@ -458,21 +462,28 @@ def uninstall_warp():
|
||||
run_cmd(['python3', Command.UNINSTALL_WARP.value])
|
||||
|
||||
|
||||
def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool):
|
||||
def configure_warp(all_state: str | None = None,
|
||||
popular_sites_state: str | None = None,
|
||||
domestic_sites_state: str | None = None,
|
||||
block_adult_sites_state: str | None = None):
|
||||
'''
|
||||
Configures WARP with various options.
|
||||
Configures WARP with various options. States are 'on' or 'off'.
|
||||
'''
|
||||
cmd_args = [
|
||||
'python3', Command.CONFIGURE_WARP.value
|
||||
]
|
||||
if all:
|
||||
cmd_args.append('--all')
|
||||
if popular_sites:
|
||||
cmd_args.append('--popular-sites')
|
||||
if domestic_sites:
|
||||
cmd_args.append('--domestic-sites')
|
||||
if block_adult_sites:
|
||||
cmd_args.append('--block-adult')
|
||||
if all_state:
|
||||
cmd_args.extend(['--set-all', all_state])
|
||||
if popular_sites_state:
|
||||
cmd_args.extend(['--set-popular-sites', popular_sites_state])
|
||||
if domestic_sites_state:
|
||||
cmd_args.extend(['--set-domestic-sites', domestic_sites_state])
|
||||
if block_adult_sites_state:
|
||||
cmd_args.extend(['--set-block-adult', block_adult_sites_state])
|
||||
|
||||
if len(cmd_args) == 2:
|
||||
print("No WARP configuration options provided to cli_api.configure_warp.")
|
||||
return
|
||||
|
||||
run_cmd(cmd_args)
|
||||
|
||||
|
||||
@ -9,16 +9,14 @@ from init_paths import *
|
||||
from paths import *
|
||||
|
||||
def restart_hysteria():
|
||||
"""Restart the Hysteria2 service using the CLI script."""
|
||||
try:
|
||||
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to restart Hysteria2: {e}")
|
||||
print(f"Failed to restart Hysteria2: {e}")
|
||||
|
||||
def remove_obfs():
|
||||
"""Remove the 'obfs' section from the config."""
|
||||
try:
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
config = json.load(f)
|
||||
@ -27,9 +25,9 @@ def remove_obfs():
|
||||
del config['obfs']
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
print("✅ Successfully removed 'obfs' from config.json.")
|
||||
print("Successfully removed 'obfs' from config.json.")
|
||||
else:
|
||||
print("ℹ️ 'obfs' section not found in config.json.")
|
||||
print("'obfs' section not found in config.json.")
|
||||
|
||||
restart_hysteria()
|
||||
|
||||
@ -39,13 +37,12 @@ def remove_obfs():
|
||||
print(f"❌ Error removing 'obfs': {e}")
|
||||
|
||||
def generate_obfs():
|
||||
"""Generate and add an 'obfs' section with a random password."""
|
||||
try:
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
if 'obfs' in config:
|
||||
print("ℹ️ 'obfs' section already exists. Replacing it.")
|
||||
print("'obfs' section already exists. Replacing it.")
|
||||
del config['obfs']
|
||||
|
||||
password = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32))
|
||||
@ -60,29 +57,47 @@ def generate_obfs():
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
print(f"✅ Successfully added 'obfs' to config.json with password: {password}")
|
||||
print(f"Successfully added 'obfs' to config.json with password: {password}")
|
||||
|
||||
restart_hysteria()
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"❌ Config file not found: {CONFIG_FILE}")
|
||||
print(f"Config file not found: {CONFIG_FILE}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error generating 'obfs': {e}")
|
||||
print(f"Error generating 'obfs': {e}")
|
||||
|
||||
def check_obfs():
|
||||
try:
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
if 'obfs' in config:
|
||||
print("OBFS is active.")
|
||||
else:
|
||||
print("OBFS is not active.")
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Config file not found: {CONFIG_FILE}")
|
||||
except Exception as e:
|
||||
print(f"Error checking 'obfs' status: {e}")
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: python3 obfs_manager.py --remove|-r | --generate|-g")
|
||||
print("Usage: python3 obfs_manager.py --remove|-r | --generate|-g | --check|-c")
|
||||
sys.exit(1)
|
||||
|
||||
option = sys.argv[1]
|
||||
if option in ("--remove", "-r"):
|
||||
print("Removing 'obfs' from config.json...")
|
||||
# print("Removing 'obfs' from config.json...")
|
||||
remove_obfs()
|
||||
elif option in ("--generate", "-g"):
|
||||
print("Generating 'obfs' in config.json...")
|
||||
# print("Generating 'obfs' in config.json...")
|
||||
generate_obfs()
|
||||
elif option in ("--check", "-c"):
|
||||
# print("Checking 'obfs' status in config.json...")
|
||||
check_obfs()
|
||||
else:
|
||||
print("Invalid option. Use --remove|-r or --generate|-g")
|
||||
print("Invalid option. Use --remove|-r, --generate|-g, or --check|-c")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -1,171 +1,159 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info",
|
||||
"timestamp": true
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "proxyDns",
|
||||
"address": "tls://8.8.8.8",
|
||||
"detour": "Proxy"
|
||||
},
|
||||
{
|
||||
"tag": "localDns",
|
||||
"address": "https://223.5.5.5/dns-query",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"final": "local-dns",
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "localDns"
|
||||
"action": "route",
|
||||
"clash_mode": "Global",
|
||||
"server": "proxy-dns",
|
||||
"source_ip_cidr": [
|
||||
"172.19.0.0/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-ir",
|
||||
"server": "proxyDns"
|
||||
},
|
||||
{
|
||||
"clash_mode": "direct",
|
||||
"server": "localDns"
|
||||
},
|
||||
{
|
||||
"clash_mode": "global",
|
||||
"server": "proxyDns"
|
||||
"action": "route",
|
||||
"server": "proxy-dns",
|
||||
"source_ip_cidr": [
|
||||
"172.19.0.0/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
]
|
||||
}
|
||||
],
|
||||
"final": "localDns",
|
||||
"strategy": "ipv4_only"
|
||||
"servers": [
|
||||
{
|
||||
"type": "https",
|
||||
"server": "1.1.1.1",
|
||||
"address_resolver": "local-dns",
|
||||
"detour": "proxy",
|
||||
"tag": "proxy-dns"
|
||||
},
|
||||
{
|
||||
"address": "local",
|
||||
"detour": "direct",
|
||||
"tag": "local-dns"
|
||||
}
|
||||
],
|
||||
"strategy": "prefer_ipv4"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "tun-in",
|
||||
"type": "tun",
|
||||
"address": [
|
||||
"172.19.0.0/30"
|
||||
"172.19.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"stack": "system",
|
||||
"endpoint_independent_nat": false,
|
||||
"mtu": 9000,
|
||||
"platform": {
|
||||
"http_proxy": {
|
||||
"enabled": true,
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 2080
|
||||
}
|
||||
}
|
||||
},
|
||||
"stack": "system",
|
||||
"strict_route": false,
|
||||
"type": "tun"
|
||||
},
|
||||
{
|
||||
"tag": "mixed-in",
|
||||
"type": "mixed",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 2080
|
||||
"listen_port": 2080,
|
||||
"type": "mixed",
|
||||
"users": []
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"level": "warn",
|
||||
"timestamp": true
|
||||
},
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "Proxy",
|
||||
"type": "selector",
|
||||
"outbounds": [
|
||||
"auto",
|
||||
"direct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "Global",
|
||||
"type": "selector",
|
||||
"outbounds": [
|
||||
"direct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "auto",
|
||||
"type": "urltest",
|
||||
"outbounds": [
|
||||
"Proxy"
|
||||
],
|
||||
"url": "http://www.gstatic.com/generate_204",
|
||||
"tag": "proxy",
|
||||
"type": "selector"
|
||||
},
|
||||
{
|
||||
"interval": "10m",
|
||||
"tolerance": 50
|
||||
"outbounds": [],
|
||||
"tag": "auto",
|
||||
"tolerance": 50,
|
||||
"type": "urltest",
|
||||
"url": "http://www.gstatic.com/generate_204"
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "local"
|
||||
"tag": "direct",
|
||||
"type": "direct"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"auto_detect_interface": true,
|
||||
"final": "Proxy",
|
||||
"final": "proxy",
|
||||
"rule_set": [
|
||||
{
|
||||
"download_detour": "direct",
|
||||
"format": "binary",
|
||||
"tag": "geosite-ads",
|
||||
"type": "remote",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ads-all.srs"
|
||||
},
|
||||
{
|
||||
"download_detour": "direct",
|
||||
"format": "binary",
|
||||
"tag": "geoip-private",
|
||||
"type": "remote",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-private.srs"
|
||||
},
|
||||
{
|
||||
"download_detour": "direct",
|
||||
"format": "binary",
|
||||
"tag": "geosite-ir",
|
||||
"type": "remote",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs"
|
||||
},
|
||||
{
|
||||
"download_detour": "direct",
|
||||
"format": "binary",
|
||||
"tag": "geoip-ir",
|
||||
"type": "remote",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ir.srs"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"inbound": [
|
||||
"tun-in",
|
||||
"mixed-in"
|
||||
],
|
||||
"action": "sniff"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"port": 53
|
||||
"action": "route",
|
||||
"clash_mode": "Direct",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"action": "route",
|
||||
"clash_mode": "Global",
|
||||
"outbound": "proxy"
|
||||
},
|
||||
{
|
||||
"action": "hijack-dns",
|
||||
"protocol": "dns"
|
||||
}
|
||||
],
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-category-ads-all",
|
||||
"action": "reject"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-category-ads-all",
|
||||
"outbound": "Proxy"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"action": "route",
|
||||
"rule_set": "geosite-ir",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"action": "route",
|
||||
"rule_set": "geoip-ir",
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"outbound": "direct",
|
||||
"rule_set": [
|
||||
{
|
||||
"tag": "geosite-category-ads-all",
|
||||
"type": "remote",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ads-all.srs",
|
||||
"download_detour": "direct"
|
||||
"geosite-ir",
|
||||
"geoip-ir",
|
||||
"geosite-private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-ir",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ir.srs",
|
||||
"update_interval": "120h0m0s"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-ir",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs",
|
||||
"update_interval": "120h0m0s"
|
||||
"action": "reject",
|
||||
"rule_set": [
|
||||
"geoip-ads"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import json
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
|
||||
core_scripts_dir = Path(__file__).resolve().parents[1]
|
||||
if str(core_scripts_dir) not in sys.path:
|
||||
@ -11,149 +12,184 @@ if str(core_scripts_dir) not in sys.path:
|
||||
|
||||
from paths import *
|
||||
|
||||
def warp_configure_handler(all_traffic=False, popular_sites=False, domestic_sites=False, block_adult_sites=False):
|
||||
"""
|
||||
Configure WARP routing rules based on provided parameters
|
||||
|
||||
Args:
|
||||
all_traffic (bool): Toggle WARP for all traffic
|
||||
popular_sites (bool): Toggle WARP for popular sites (Google, Netflix, etc.)
|
||||
domestic_sites (bool): Toggle between WARP and Reject for domestic sites
|
||||
block_adult_sites (bool): Toggle blocking of adult content
|
||||
"""
|
||||
def warp_configure_handler(
|
||||
set_all_traffic_state: str | None = None,
|
||||
set_popular_sites_state: str | None = None,
|
||||
set_domestic_sites_state: str | None = None,
|
||||
set_block_adult_sites_state: str | None = None
|
||||
):
|
||||
try:
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
config = json.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"Error: Configuration file {CONFIG_FILE} not found.")
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError:
|
||||
print(f"Error: Could not decode JSON from {CONFIG_FILE}.")
|
||||
sys.exit(1)
|
||||
|
||||
modified = False
|
||||
|
||||
if all_traffic:
|
||||
warp_all_active = any(rule == "warps(all)" for rule in config.get('acl', {}).get('inline', []))
|
||||
if 'acl' not in config:
|
||||
config['acl'] = {}
|
||||
if 'inline' not in config['acl']:
|
||||
config['acl']['inline'] = []
|
||||
|
||||
if set_all_traffic_state is not None:
|
||||
warp_all_rule = "warps(all)"
|
||||
warp_all_active = warp_all_rule in config['acl']['inline']
|
||||
if set_all_traffic_state == "on":
|
||||
if not warp_all_active:
|
||||
config['acl']['inline'].append(warp_all_rule)
|
||||
print("All traffic rule: Enabled.")
|
||||
modified = True
|
||||
else:
|
||||
print("All traffic rule: Already enabled.")
|
||||
elif set_all_traffic_state == "off":
|
||||
if warp_all_active:
|
||||
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule != "warps(all)"]
|
||||
print("Traffic configuration changed to Direct.")
|
||||
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule != warp_all_rule]
|
||||
print("All traffic rule: Disabled.")
|
||||
modified = True
|
||||
else:
|
||||
if 'acl' not in config:
|
||||
config['acl'] = {}
|
||||
if 'inline' not in config['acl']:
|
||||
config['acl']['inline'] = []
|
||||
config['acl']['inline'].append("warps(all)")
|
||||
print("Traffic configuration changed to WARP.")
|
||||
modified = True
|
||||
print("All traffic rule: Already disabled.")
|
||||
|
||||
if popular_sites:
|
||||
if set_popular_sites_state is not None:
|
||||
popular_rules = [
|
||||
"warps(geoip:google)",
|
||||
"warps(geosite:google)",
|
||||
"warps(geosite:netflix)",
|
||||
"warps(geosite:spotify)",
|
||||
"warps(geosite:openai)",
|
||||
"warps(geoip:openai)"
|
||||
"warps(geoip:google)", "warps(geosite:google)", "warps(geosite:netflix)",
|
||||
"warps(geosite:spotify)", "warps(geosite:openai)", "warps(geoip:openai)"
|
||||
]
|
||||
|
||||
rule_exists = any(rule in config.get('acl', {}).get('inline', []) for rule in popular_rules)
|
||||
|
||||
if rule_exists:
|
||||
config['acl']['inline'] = [rule for rule in config['acl']['inline']
|
||||
if rule not in popular_rules]
|
||||
print("WARP configuration for Google, OpenAI, etc. removed.")
|
||||
if set_popular_sites_state == "on":
|
||||
added_any = False
|
||||
for rule in popular_rules:
|
||||
if rule not in config['acl']['inline']:
|
||||
config['acl']['inline'].append(rule)
|
||||
added_any = True
|
||||
if added_any:
|
||||
print("Popular sites rule: Enabled/Updated.")
|
||||
modified = True
|
||||
else:
|
||||
if 'acl' not in config:
|
||||
config['acl'] = {}
|
||||
if 'inline' not in config['acl']:
|
||||
config['acl']['inline'] = []
|
||||
config['acl']['inline'].extend(popular_rules)
|
||||
print("WARP configured for Google, OpenAI, etc.")
|
||||
|
||||
all_present = all(rule in config['acl']['inline'] for rule in popular_rules)
|
||||
if all_present:
|
||||
print("Popular sites rule: Already enabled.")
|
||||
else:
|
||||
print("Popular sites rule: Enabled/Updated.")
|
||||
modified = True
|
||||
|
||||
if domestic_sites:
|
||||
ir_site_rule = "warps(geosite:ir)"
|
||||
ir_ip_rule = "warps(geoip:ir)"
|
||||
reject_site_rule = "reject(geosite:ir)"
|
||||
reject_ip_rule = "reject(geoip:ir)"
|
||||
|
||||
using_warp = (ir_site_rule in config.get('acl', {}).get('inline', []) and
|
||||
ir_ip_rule in config.get('acl', {}).get('inline', []))
|
||||
using_reject = (reject_site_rule in config.get('acl', {}).get('inline', []) and
|
||||
reject_ip_rule in config.get('acl', {}).get('inline', []))
|
||||
|
||||
if 'acl' not in config:
|
||||
config['acl'] = {}
|
||||
if 'inline' not in config['acl']:
|
||||
config['acl']['inline'] = []
|
||||
|
||||
if using_warp:
|
||||
config['acl']['inline'] = [reject_site_rule if rule == ir_site_rule else
|
||||
reject_ip_rule if rule == ir_ip_rule else
|
||||
rule for rule in config['acl']['inline']]
|
||||
print("Configuration changed to Reject for geosite:ir and geoip:ir.")
|
||||
modified = True
|
||||
elif using_reject:
|
||||
config['acl']['inline'] = [ir_site_rule if rule == reject_site_rule else
|
||||
ir_ip_rule if rule == reject_ip_rule else
|
||||
rule for rule in config['acl']['inline']]
|
||||
print("Configuration changed to Use WARP for geosite:ir and geoip:ir.")
|
||||
elif set_popular_sites_state == "off":
|
||||
removed_any = False
|
||||
initial_len = len(config['acl']['inline'])
|
||||
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule not in popular_rules]
|
||||
if len(config['acl']['inline']) < initial_len:
|
||||
removed_any = True
|
||||
if removed_any:
|
||||
print("Popular sites rule: Disabled.")
|
||||
modified = True
|
||||
else:
|
||||
config['acl']['inline'].extend([reject_site_rule, reject_ip_rule])
|
||||
print("Added Reject configuration for geosite:ir and geoip:ir.")
|
||||
modified = True
|
||||
print("Popular sites rule: Already disabled.")
|
||||
|
||||
if block_adult_sites:
|
||||
if set_domestic_sites_state is not None:
|
||||
ir_site_warp_rule = "warps(geosite:ir)"
|
||||
ir_ip_warp_rule = "warps(geoip:ir)"
|
||||
ir_site_reject_rule = "reject(geosite:ir)"
|
||||
ir_ip_reject_rule = "reject(geoip:ir)"
|
||||
|
||||
if set_domestic_sites_state == "on":
|
||||
changed_to_warp = False
|
||||
if ir_site_reject_rule in config['acl']['inline'] or ir_ip_reject_rule in config['acl']['inline']:
|
||||
config['acl']['inline'] = [r for r in config['acl']['inline'] if r not in [ir_site_reject_rule, ir_ip_reject_rule]]
|
||||
changed_to_warp = True
|
||||
if ir_site_warp_rule not in config['acl']['inline']:
|
||||
config['acl']['inline'].append(ir_site_warp_rule)
|
||||
changed_to_warp = True
|
||||
if ir_ip_warp_rule not in config['acl']['inline']:
|
||||
config['acl']['inline'].append(ir_ip_warp_rule)
|
||||
changed_to_warp = True
|
||||
if changed_to_warp:
|
||||
print("Domestic sites: Configured to use WARP.")
|
||||
modified = True
|
||||
else:
|
||||
print("Domestic sites: Already configured to use WARP.")
|
||||
elif set_domestic_sites_state == "off":
|
||||
changed_to_reject = False
|
||||
if ir_site_warp_rule in config['acl']['inline'] or ir_ip_warp_rule in config['acl']['inline']:
|
||||
config['acl']['inline'] = [r for r in config['acl']['inline'] if r not in [ir_site_warp_rule, ir_ip_warp_rule]]
|
||||
changed_to_reject = True
|
||||
if ir_site_reject_rule not in config['acl']['inline']:
|
||||
config['acl']['inline'].append(ir_site_reject_rule)
|
||||
changed_to_reject = True
|
||||
if ir_ip_reject_rule not in config['acl']['inline']:
|
||||
config['acl']['inline'].append(ir_ip_reject_rule)
|
||||
changed_to_reject = True
|
||||
if changed_to_reject:
|
||||
print("Domestic sites: Configured to REJECT.")
|
||||
modified = True
|
||||
else:
|
||||
print("Domestic sites: Already configured to REJECT.")
|
||||
|
||||
if set_block_adult_sites_state is not None:
|
||||
nsfw_rule = "reject(geosite:nsfw)"
|
||||
is_blocking_nsfw = nsfw_rule in config['acl']['inline']
|
||||
|
||||
blocked = nsfw_rule in config.get('acl', {}).get('inline', [])
|
||||
if 'resolver' not in config: config['resolver'] = {}
|
||||
if 'tls' not in config['resolver']: config['resolver']['tls'] = {}
|
||||
|
||||
if blocked:
|
||||
config['acl']['inline'] = [rule for rule in config['acl']['inline']
|
||||
if rule != nsfw_rule]
|
||||
if 'resolver' not in config:
|
||||
config['resolver'] = {}
|
||||
if 'tls' not in config['resolver']:
|
||||
config['resolver']['tls'] = {}
|
||||
config['resolver']['tls']['addr'] = "1.1.1.1:853"
|
||||
print("Adult content blocking removed and resolver updated.")
|
||||
desired_resolver = ""
|
||||
if set_block_adult_sites_state == "on":
|
||||
desired_resolver = "1.1.1.3:853"
|
||||
if not is_blocking_nsfw:
|
||||
config['acl']['inline'].append(nsfw_rule)
|
||||
print("Adult content blocking: Enabled.")
|
||||
modified = True
|
||||
else:
|
||||
if 'acl' not in config:
|
||||
config['acl'] = {}
|
||||
if 'inline' not in config['acl']:
|
||||
config['acl']['inline'] = []
|
||||
config['acl']['inline'].append(nsfw_rule)
|
||||
if 'resolver' not in config:
|
||||
config['resolver'] = {}
|
||||
if 'tls' not in config['resolver']:
|
||||
config['resolver']['tls'] = {}
|
||||
config['resolver']['tls']['addr'] = "1.1.1.3:853"
|
||||
print("Adult content blocked and resolver updated.")
|
||||
print("Adult content blocking: Already enabled.")
|
||||
elif set_block_adult_sites_state == "off":
|
||||
desired_resolver = "1.1.1.1:853"
|
||||
if is_blocking_nsfw:
|
||||
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule != nsfw_rule]
|
||||
print("Adult content blocking: Disabled.")
|
||||
modified = True
|
||||
else:
|
||||
print("Adult content blocking: Already disabled.")
|
||||
|
||||
if config['resolver']['tls'].get('addr') != desired_resolver:
|
||||
config['resolver']['tls']['addr'] = desired_resolver
|
||||
print(f"Resolver: Updated to {desired_resolver}.")
|
||||
modified = True
|
||||
|
||||
if 'acl' in config and 'inline' in config['acl']:
|
||||
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule]
|
||||
|
||||
|
||||
if modified:
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
print("Configuration updated. Attempting to restart hysteria2 service...")
|
||||
try:
|
||||
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
print("Warning: Failed to restart hysteria2")
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=10)
|
||||
print("Hysteria2 service restarted successfully.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Warning: Failed to restart hysteria2. STDERR: {e.stderr.decode().strip()}")
|
||||
except subprocess.TimeoutExpired:
|
||||
print("Warning: Timeout expired while trying to restart hysteria2 service.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Configure WARP settings")
|
||||
parser.add_argument("--all", action="store_true", help="Toggle WARP for all traffic")
|
||||
parser.add_argument("--popular-sites", action="store_true", help="Toggle WARP for popular sites")
|
||||
parser.add_argument("--domestic-sites", action="store_true", help="Toggle between WARP and Reject for domestic sites")
|
||||
parser.add_argument("--block-adult", action="store_true", help="Toggle blocking of adult content")
|
||||
parser = argparse.ArgumentParser(description="Configure WARP settings. At least one option must be provided.")
|
||||
parser.add_argument("--set-all", choices=['on', 'off'], help="Set WARP for all traffic (on/off)")
|
||||
parser.add_argument("--set-popular-sites", choices=['on', 'off'], help="Set WARP for popular sites (on/off)")
|
||||
parser.add_argument("--set-domestic-sites", choices=['on', 'off'], help="Set behavior for domestic sites (on=WARP, off=REJECT)")
|
||||
parser.add_argument("--set-block-adult", choices=['on', 'off'], help="Set blocking of adult content (on/off)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not any(vars(args).values()):
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
warp_configure_handler(
|
||||
all_traffic=args.all,
|
||||
popular_sites=args.popular_sites,
|
||||
domestic_sites=args.domestic_sites,
|
||||
block_adult_sites=args.block_adult
|
||||
set_all_traffic_state=args.set_all,
|
||||
set_popular_sites_state=args.set_popular_sites,
|
||||
set_domestic_sites_state=args.set_domestic_sites,
|
||||
set_block_adult_sites_state=args.set_block_adult
|
||||
)
|
||||
@ -1,5 +1,5 @@
|
||||
from fastapi import APIRouter, BackgroundTasks, HTTPException, UploadFile, File
|
||||
from ..schema.config.hysteria import ConfigFile, GetPortResponse, GetSniResponse
|
||||
from ..schema.config.hysteria import ConfigFile, GetPortResponse, GetSniResponse, GetObfsResponse
|
||||
from ..schema.response import DetailResponse, IPLimitConfig, SetupDecoyRequest, DecoyStatusResponse, IPLimitConfigResponse
|
||||
from fastapi.responses import FileResponse
|
||||
import shutil
|
||||
@ -223,6 +223,23 @@ async def disable_obfs():
|
||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
||||
|
||||
|
||||
@router.get('/check-obfs', response_model=GetObfsResponse, summary='Check Hysteria2 OBFS Status')
|
||||
async def check_obfs():
|
||||
"""
|
||||
Checks the current status of Hysteria2 OBFS.
|
||||
|
||||
Returns:
|
||||
A GetObfsResponse containing the Hysteria2 OBFS status message (e.g., 'OBFS is active.').
|
||||
|
||||
Raises:
|
||||
HTTPException: if an error occurs while checking the Hysteria2 OBFS status.
|
||||
"""
|
||||
try:
|
||||
obfs_status_message = cli_api.check_hysteria2_obfs()
|
||||
return GetObfsResponse(obfs=obfs_status_message)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f'Error checking OBFS status: {str(e)}')
|
||||
|
||||
@router.get('/enable-masquerade/{domain}', response_model=DetailResponse, summary='Enable Hysteria2 masquerade')
|
||||
async def enable_masquerade(domain: str):
|
||||
"""
|
||||
|
||||
@ -8,7 +8,7 @@ import cli_api
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post('/install', response_model=DetailResponse, summary='Install WARP')
|
||||
@router.post('/install', response_model=DetailResponse, summary='Install WARP', name="install_warp")
|
||||
async def install():
|
||||
"""
|
||||
Installs WARP.
|
||||
@ -27,7 +27,7 @@ async def install():
|
||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
||||
|
||||
|
||||
@router.delete('/uninstall', response_model=DetailResponse, summary='Uninstall WARP')
|
||||
@router.delete('/uninstall', response_model=DetailResponse, summary='Uninstall WARP', name="uninstall_warp")
|
||||
async def uninstall():
|
||||
"""
|
||||
Uninstalls WARP.
|
||||
@ -45,7 +45,7 @@ async def uninstall():
|
||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
||||
|
||||
|
||||
@router.post('/configure', response_model=DetailResponse, summary='Configure WARP')
|
||||
@router.post('/configure', response_model=DetailResponse, summary='Configure WARP', name="configure_warp")
|
||||
async def configure(body: ConfigureInputBody):
|
||||
"""
|
||||
Configures WARP with the given options.
|
||||
@ -60,14 +60,23 @@ async def configure(body: ConfigureInputBody):
|
||||
HTTPException: If an error occurs during configuration, an HTTP 400 error is raised with the error details.
|
||||
"""
|
||||
try:
|
||||
cli_api.configure_warp(body.all, body.popular_sites, body.domestic_sites,
|
||||
body.block_adult_sites)
|
||||
all_st = 'on' if body.all else 'off'
|
||||
pop_sites_st = 'on' if body.popular_sites else 'off'
|
||||
dom_sites_st = 'on' if body.domestic_sites else 'off'
|
||||
block_adult_st = 'on' if body.block_adult_sites else 'off'
|
||||
|
||||
cli_api.configure_warp(
|
||||
all_state=all_st,
|
||||
popular_sites_state=pop_sites_st,
|
||||
domestic_sites_state=dom_sites_st,
|
||||
block_adult_sites_state=block_adult_st
|
||||
)
|
||||
return DetailResponse(detail='WARP configured successfully.')
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
||||
raise HTTPException(status_code=404, detail=f'Error configuring WARP: {str(e)}')
|
||||
|
||||
|
||||
@router.get('/status', response_model=StatusResponse, summary='Get WARP Status')
|
||||
@router.get('/status', response_model=StatusResponse, summary='Get WARP Status', name="status_warp")
|
||||
async def status():
|
||||
try:
|
||||
status_json_str = cli_api.warp_status()
|
||||
|
||||
@ -18,3 +18,6 @@ class GetPortResponse(BaseModel):
|
||||
|
||||
class GetSniResponse(BaseModel):
|
||||
sni: str
|
||||
|
||||
class GetObfsResponse(BaseModel):
|
||||
obfs: str
|
||||
@ -40,6 +40,11 @@
|
||||
<a class='nav-link' id='sni-tab' data-toggle='pill' href='#sni' role='tab'
|
||||
aria-controls='sni' aria-selected='false'><i class="fas fa-shield-alt"></i> Change
|
||||
SNI</a>
|
||||
</li>
|
||||
<li class='nav-item'>
|
||||
<a class='nav-link' id='obfs-tab' data-toggle='pill' href='#obfs' role='tab'
|
||||
aria-controls='obfs' aria-selected='false'><i class="fas fa-user-secret"></i>
|
||||
OBFS</a>
|
||||
</li>
|
||||
<li class='nav-item'>
|
||||
<a class='nav-link' id='ip-tab' data-toggle='pill' href='#change_ip' role='tab'
|
||||
@ -61,6 +66,10 @@
|
||||
aria-controls='decoy' aria-selected='false'><i class="fas fa-mask"></i>
|
||||
Decoy Site</a>
|
||||
</li>
|
||||
<li class='nav-item'>
|
||||
<a class='nav-link' id='warp-tab-link' data-toggle='pill' href='#warp-content' role='tab'
|
||||
aria-controls='warp-content' aria-selected='false'><i class="fas fa-cloud"></i> WARP</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='card-body' style="margin-left: 25px;">
|
||||
@ -71,22 +80,21 @@
|
||||
<ul class='nav nav-tabs' id='subs-tabs' role='tablist'>
|
||||
<li class='nav-item'>
|
||||
<a class='nav-link active' id='normal-tab' data-toggle='tab' href='#normal' role='tab'
|
||||
aria-controls='normal' aria-selected='true'><strong>Normal</strong></a>
|
||||
aria-controls='normal' aria-selected='true'><strong>Service Control</strong></a>
|
||||
</li>
|
||||
<li class='nav-item normal-sub-config-tab-li' style="display: none;"> <!-- Initially hidden -->
|
||||
<li class='nav-item normal-sub-config-tab-li' style="display: none;">
|
||||
<a class='nav-link' id='normal-sub-config-link-tab' data-toggle='tab' href='#normal-sub-config-content' role='tab'
|
||||
aria-controls='normal-sub-config-content' aria-selected='false'><strong>Configure</strong></a>
|
||||
aria-controls='normal-sub-config-content' aria-selected='false'><strong>Configure Link</strong></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class='tab-content' id='subs-tabs-content'>
|
||||
<br>
|
||||
<!-- Normal Sub Service Control Tab -->
|
||||
<div class='tab-pane fade show active' id='normal' role='tabpanel' aria-labelledby='normal-tab'>
|
||||
<form id="normal_sub_service_form">
|
||||
<div class='form-group'>
|
||||
<label for='normal_domain'>Domain:</label>
|
||||
<input type='text' class='form-control' id='normal_domain'
|
||||
placeholder='Enter Domain'>
|
||||
placeholder='sub.example.com'>
|
||||
<div class="invalid-feedback">
|
||||
Please enter a valid domain (without http:// or https://).
|
||||
</div>
|
||||
@ -94,26 +102,25 @@
|
||||
<div class='form-group'>
|
||||
<label for='normal_port'>Port:</label>
|
||||
<input type='text' class='form-control' id='normal_port'
|
||||
placeholder='Enter Port'>
|
||||
placeholder='e.g., 8080'>
|
||||
<div class="invalid-feedback">
|
||||
Please enter a valid port number.
|
||||
</div>
|
||||
</div>
|
||||
<button id="normal_start" type='button' class='btn btn-success'>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||
Start
|
||||
Start Service
|
||||
</button>
|
||||
<button id="normal_stop" type='button' class='btn btn-danger'
|
||||
style="display: none;">Stop</button>
|
||||
style="display: none;">Stop Service</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Normal Sub Configuration Tab -->
|
||||
<div class='tab-pane fade' id='normal-sub-config-content' role='tabpanel' aria-labelledby='normal-sub-config-link-tab'>
|
||||
<form id="normal_sub_config_form">
|
||||
<div class='form-group'>
|
||||
<label for='normal_subpath_input'>Subpath:</label>
|
||||
<label for='normal_subpath_input'>Subscription Path Segment:</label>
|
||||
<input type='text' class='form-control' id='normal_subpath_input'
|
||||
placeholder='Enter subpath (e.g., mysub)'>
|
||||
placeholder='e.g., mysub (becomes /mysub/...)'>
|
||||
<div class="invalid-feedback">
|
||||
Please enter a valid subpath (alphanumeric characters only, e.g., mysub).
|
||||
</div>
|
||||
@ -183,6 +190,24 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- OBFS Tab -->
|
||||
<div class='tab-pane fade' id='obfs' role='tabpanel' aria-labelledby='obfs-tab'>
|
||||
<div class="mb-3">
|
||||
<h5>OBFS Status</h5>
|
||||
<div id="obfs_status_container" class="p-3 border rounded">
|
||||
<span id="obfs_status_message">Loading OBFS status...</span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="obfs_enable_btn" type='button' class='btn btn-success' style="display: none;">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||
Enable OBFS
|
||||
</button>
|
||||
<button id="obfs_disable_btn" type='button' class='btn btn-danger' style="display: none;">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||
Disable OBFS
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Change IP Tab -->
|
||||
<div class='tab-pane fade' id='change_ip' role='tabpanel' aria-labelledby='ip-tab'>
|
||||
<form id="change_ip_form">
|
||||
@ -208,18 +233,41 @@
|
||||
|
||||
<!-- Backup Tab -->
|
||||
<div class='tab-pane fade' id='backup' role='tabpanel' aria-labelledby='backup-tab'>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card card-outline card-success h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-upload"></i> Restore from Backup</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Upload a previously downloaded .zip backup file to restore your panel settings and Hysteria configuration.</p>
|
||||
<div class="form-group">
|
||||
<label for="backup_file">Upload Backup:</label>
|
||||
<label for="backup_file">Select Backup File (.zip):</label>
|
||||
<input type="file" class="form-control-file" id="backup_file" accept=".zip">
|
||||
</div>
|
||||
<button id="upload_backup" type='button' class='btn btn-success'>Upload</button>
|
||||
<button id="download_backup" type='button' class='btn btn-primary'>Download Backup</button>
|
||||
|
||||
<button id="upload_backup" type='button' class='btn btn-success btn-block'>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||
Upload and Restore
|
||||
</button>
|
||||
<div class="progress mt-3" style="display: none;">
|
||||
<div id="backup_progress_bar" class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div id="backup_progress_bar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
|
||||
</div>
|
||||
<div id="backup_status" class="mt-2 small"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card card-outline card-primary h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-download"></i> Create New Backup</h3>
|
||||
</div>
|
||||
<div class="card-body d-flex flex-column justify-content-center">
|
||||
<p>Download a .zip file containing a full backup of your panel settings and Hysteria configuration.</p>
|
||||
<button id="download_backup" type='button' class='btn btn-primary btn-block mt-auto'>Download Backup</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="backup_status" class="mt-2"></div>
|
||||
</div>
|
||||
|
||||
<!-- IP Limit Tab -->
|
||||
@ -307,6 +355,63 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WARP Tab -->
|
||||
<div class='tab-pane fade' id='warp-content' role='tabpanel' aria-labelledby='warp-tab-link'>
|
||||
<div id="warp_initial_controls">
|
||||
<div class='alert alert-info'>WARP service is not active.</div>
|
||||
<button id="warp_start_btn" type='button' class='btn btn-success mt-3'>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||
Install & Start WARP
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="warp_active_controls" style="display: none;">
|
||||
<div class='alert alert-success mb-3'>WARP service is active.</div>
|
||||
|
||||
<div class="card card-outline card-secondary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><i class="fas fa-cogs"></i> WARP Routing Configuration</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="warp_config_form">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="warp_all_traffic">
|
||||
<label class="custom-control-label" for="warp_all_traffic">Route All Traffic through WARP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="warp_popular_sites">
|
||||
<label class="custom-control-label" for="warp_popular_sites">Route Popular Sites through WARP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="warp_domestic_sites">
|
||||
<label class="custom-control-label" for="warp_domestic_sites">Route Domestic Sites through WARP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="warp_block_adult_sites">
|
||||
<label class="custom-control-label" for="warp_block_adult_sites">Block Adult Sites (WARP Family DNS)</label>
|
||||
</div>
|
||||
</div>
|
||||
<button id="warp_save_config_btn" type='button' class='btn btn-primary'>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||
Save Configuration
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<button id="warp_stop_btn" type='button' class='btn btn-danger mt-3'>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||
Stop & Uninstall WARP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
@ -330,6 +435,7 @@
|
||||
|
||||
initUI();
|
||||
fetchDecoyStatus();
|
||||
fetchObfsStatus();
|
||||
|
||||
function isValidPath(path) {
|
||||
if (!path) return false;
|
||||
@ -519,7 +625,8 @@
|
||||
const servicesMap = {
|
||||
"hysteria_telegram_bot": "#telegram_form",
|
||||
"hysteria_normal_sub": "#normal_sub_service_form",
|
||||
"hysteria_iplimit": "#ip-limit-service"
|
||||
"hysteria_iplimit": "#ip-limit-service",
|
||||
"hysteria_warp": "warp_service"
|
||||
};
|
||||
|
||||
Object.keys(servicesMap).forEach(service => {
|
||||
@ -575,6 +682,17 @@
|
||||
$("#max_ips").val("");
|
||||
$("#block_duration, #max_ips").removeClass('is-invalid');
|
||||
}
|
||||
} else if (service === "hysteria_warp") {
|
||||
const isWarpServiceRunning = data[service];
|
||||
if (isWarpServiceRunning) {
|
||||
$("#warp_initial_controls").hide();
|
||||
$("#warp_active_controls").show();
|
||||
fetchWarpFullStatusAndConfig();
|
||||
} else {
|
||||
$("#warp_initial_controls").show();
|
||||
$("#warp_active_controls").hide();
|
||||
$("#warp_config_form")[0].reset();
|
||||
}
|
||||
} else {
|
||||
const $formSelector = $(targetSelector);
|
||||
if (isRunning) {
|
||||
@ -722,6 +840,68 @@
|
||||
}
|
||||
|
||||
|
||||
function fetchObfsStatus() {
|
||||
$.ajax({
|
||||
url: "{{ url_for('check_obfs') }}",
|
||||
type: "GET",
|
||||
success: function (data) {
|
||||
updateObfsUI(data.obfs);
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$("#obfs_status_message").html('<span class="text-danger">Failed to fetch OBFS status.</span>');
|
||||
console.error("Failed to fetch OBFS status:", error, xhr.responseText);
|
||||
$("#obfs_enable_btn").hide();
|
||||
$("#obfs_disable_btn").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateObfsUI(statusMessage) {
|
||||
$("#obfs_status_message").text(statusMessage);
|
||||
if (statusMessage === "OBFS is active.") {
|
||||
$("#obfs_enable_btn").hide();
|
||||
$("#obfs_disable_btn").show();
|
||||
$("#obfs_status_container").removeClass("border-danger border-warning alert-danger alert-warning").addClass("border-success alert-success");
|
||||
} else if (statusMessage === "OBFS is not active.") {
|
||||
$("#obfs_enable_btn").show();
|
||||
$("#obfs_disable_btn").hide();
|
||||
$("#obfs_status_container").removeClass("border-success border-danger alert-success alert-danger").addClass("border-warning alert-warning");
|
||||
} else {
|
||||
$("#obfs_enable_btn").hide();
|
||||
$("#obfs_disable_btn").hide();
|
||||
$("#obfs_status_container").removeClass("border-success border-warning alert-success alert-warning").addClass("border-danger alert-danger");
|
||||
}
|
||||
}
|
||||
|
||||
function enableObfs() {
|
||||
confirmAction("enable OBFS", function () {
|
||||
sendRequest(
|
||||
"{{ url_for('enable_obfs') }}",
|
||||
"GET",
|
||||
null,
|
||||
"OBFS enabled successfully!",
|
||||
"#obfs_enable_btn",
|
||||
false,
|
||||
fetchObfsStatus
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function disableObfs() {
|
||||
confirmAction("disable OBFS", function () {
|
||||
sendRequest(
|
||||
"{{ url_for('disable_obfs') }}",
|
||||
"GET",
|
||||
null,
|
||||
"OBFS disabled successfully!",
|
||||
"#obfs_disable_btn",
|
||||
false,
|
||||
fetchObfsStatus
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function startTelegram() {
|
||||
if (!validateForm('telegram_form')) return;
|
||||
const apiToken = $("#telegram_api_token").val();
|
||||
@ -927,6 +1107,84 @@
|
||||
});
|
||||
}
|
||||
|
||||
function fetchWarpFullStatusAndConfig() {
|
||||
$.ajax({
|
||||
url: "{{ url_for('status_warp') }}",
|
||||
type: "GET",
|
||||
success: function (data) {
|
||||
$("#warp_all_traffic").prop('checked', data.all_traffic_via_warp || false);
|
||||
$("#warp_popular_sites").prop('checked', data.popular_sites_via_warp || false);
|
||||
$("#warp_domestic_sites").prop('checked', data.domestic_sites_via_warp || false);
|
||||
$("#warp_block_adult_sites").prop('checked', data.block_adult_content || false);
|
||||
|
||||
$("#warp_initial_controls").hide();
|
||||
$("#warp_active_controls").show();
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
let errorMsg = "Failed to fetch WARP configuration.";
|
||||
if (xhr.responseJSON && xhr.responseJSON.detail) {
|
||||
errorMsg = xhr.responseJSON.detail;
|
||||
}
|
||||
console.error("Error fetching WARP config:", errorMsg, xhr.responseText);
|
||||
|
||||
if (xhr.status === 404) {
|
||||
$("#warp_initial_controls").show();
|
||||
$("#warp_active_controls").hide();
|
||||
$("#warp_config_form")[0].reset();
|
||||
Swal.fire("Info", "WARP service might not be fully configured. Please try reinstalling if issues persist.", "info");
|
||||
} else {
|
||||
$("#warp_config_form")[0].reset();
|
||||
Swal.fire("Warning", "Could not load current WARP configuration values. Please check manually or re-save.", "warning");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#warp_start_btn").on("click", function() {
|
||||
confirmAction("install and start WARP", function () {
|
||||
sendRequest(
|
||||
"{{ url_for('install_warp') }}",
|
||||
"POST",
|
||||
null,
|
||||
"WARP installation request sent. The page will reload.",
|
||||
"#warp_start_btn",
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
$("#warp_stop_btn").on("click", function() {
|
||||
confirmAction("stop and uninstall WARP", function () {
|
||||
sendRequest(
|
||||
"{{ url_for('uninstall_warp') }}",
|
||||
"DELETE",
|
||||
null,
|
||||
"WARP uninstallation request sent. The page will reload.",
|
||||
"#warp_stop_btn",
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
$("#warp_save_config_btn").on("click", function() {
|
||||
const configData = {
|
||||
all: $("#warp_all_traffic").is(":checked"),
|
||||
popular_sites: $("#warp_popular_sites").is(":checked"),
|
||||
domestic_sites: $("#warp_domestic_sites").is(":checked"),
|
||||
block_adult_sites: $("#warp_block_adult_sites").is(":checked")
|
||||
};
|
||||
confirmAction("save WARP configuration", function () {
|
||||
sendRequest(
|
||||
"{{ url_for('configure_warp') }}",
|
||||
"POST",
|
||||
configData,
|
||||
"WARP configuration saved successfully!",
|
||||
"#warp_save_config_btn",
|
||||
false,
|
||||
fetchWarpFullStatusAndConfig
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#telegram_start").on("click", startTelegram);
|
||||
@ -944,6 +1202,8 @@
|
||||
$("#ip_limit_change_config").on("click", configIPLimit);
|
||||
$("#decoy_setup").on("click", setupDecoy);
|
||||
$("#decoy_stop").on("click", stopDecoy);
|
||||
$("#obfs_enable_btn").on("click", enableObfs);
|
||||
$("#obfs_disable_btn").on("click", disableObfs);
|
||||
|
||||
|
||||
$('#normal_domain, #sni_domain, #decoy_domain').on('input', function () {
|
||||
|
||||
60
menu.sh
60
menu.sh
@ -384,18 +384,17 @@ warp_configure_handler() {
|
||||
|
||||
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=$(echo "$status_json" | grep -o '"domestic_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="$2"
|
||||
if [ "$status" = "true" ]; then
|
||||
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}"
|
||||
@ -404,38 +403,49 @@ warp_configure_handler() {
|
||||
|
||||
display_status "All Traffic via WARP" "$all_traffic"
|
||||
display_status "Popular Sites via WARP" "$popular_sites"
|
||||
display_status "Domestic Sites via WARP" "$domestic_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:"
|
||||
echo "1. Use WARP for all traffic"
|
||||
echo "2. Use WARP for popular sites"
|
||||
echo "3. Use WARP for domestic sites"
|
||||
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"
|
||||
echo "6. Change IP address"
|
||||
echo "5. WARP Status Profile (IP etc.)"
|
||||
echo "6. Change WARP IP address"
|
||||
echo "0. Cancel"
|
||||
|
||||
read -p "Select an option: " option
|
||||
read -p "Select an option to toggle: " option
|
||||
|
||||
case $option in
|
||||
1) python3 $CLI_PATH configure-warp --all ;;
|
||||
2) python3 $CLI_PATH configure-warp --popular-sites ;;
|
||||
3) python3 $CLI_PATH configure-warp --domestic-sites ;;
|
||||
4) python3 $CLI_PATH configure-warp --block-adult-sites ;;
|
||||
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)
|
||||
ip=$(curl -s --interface wgcf --connect-timeout 0.5 http://v4.ident.me)
|
||||
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}$ip${NC}"
|
||||
echo -e "${yellow}Warp IP:${NC} ${cyan}${current_ip}${NC}"
|
||||
;;
|
||||
6)
|
||||
old_ip=$(curl -s --interface wgcf --connect-timeout 0.5 http://v4.ident.me)
|
||||
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..."
|
||||
echo "Restarting $service_name to attempt IP change..."
|
||||
systemctl restart "$service_name"
|
||||
|
||||
echo -n "Waiting for service to restart"
|
||||
@ -445,18 +455,22 @@ warp_configure_handler() {
|
||||
done
|
||||
echo
|
||||
|
||||
new_ip=$(curl -s --interface wgcf --connect-timeout 0.5 http://v4.ident.me)
|
||||
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" != "$new_ip" ]; then
|
||||
if [ "$old_ip" != "N/A" ] && [ "$new_ip" != "N/A" ] && [ "$old_ip" != "$new_ip" ]; then
|
||||
echo -e "${green}✓ IP address changed successfully${NC}"
|
||||
else
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user