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),
|
* Added **OBFS status API endpoint**
|
||||||
you must **re-activate NormalSUB** from the settings panel after upgrading to **v1.11.0**.
|
* Introduced `--check` flag to the `manage_obfs` CLI tool
|
||||||
|
* Enabled **status checking** functionality to monitor OBFS state
|
||||||
Failure to do so may result in broken subscription links.
|
* 🔧 **normalsub:** Improved `edit_subpath` with **better error handling** and **efficient Caddy reload**
|
||||||
|
* 📦 **Singbox:** Enhanced **Singbox template configuration** for better maintainability and compatibility
|
||||||
---
|
|
||||||
|
|
||||||
#### ✨ 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
|
|
||||||
|
|||||||
49
core/cli.py
49
core/cli.py
@ -250,19 +250,25 @@ def server_info():
|
|||||||
@cli.command('manage_obfs')
|
@cli.command('manage_obfs')
|
||||||
@click.option('--remove', '-r', is_flag=True, help="Remove 'obfs' from config.json.")
|
@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.")
|
@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:
|
try:
|
||||||
if not remove and not generate:
|
options_selected = sum([remove, generate, check])
|
||||||
raise click.UsageError('Error: You must use either --remove or --generate')
|
if options_selected == 0:
|
||||||
if remove and generate:
|
raise click.UsageError('Error: You must use either --remove, --generate, or --check.')
|
||||||
raise click.UsageError('Error: You cannot use both --remove and --generate at the same time')
|
if options_selected > 1:
|
||||||
|
raise click.UsageError('Error: You can only use one of --remove, --generate, or --check at a time.')
|
||||||
|
|
||||||
if generate:
|
if generate:
|
||||||
cli_api.enable_hysteria2_obfs()
|
cli_api.enable_hysteria2_obfs()
|
||||||
click.echo('Obfs enabled successfully.')
|
click.echo('OBFS enabled successfully.')
|
||||||
elif remove:
|
elif remove:
|
||||||
cli_api.disable_hysteria2_obfs()
|
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:
|
except Exception as e:
|
||||||
click.echo(f'{e}', err=True)
|
click.echo(f'{e}', err=True)
|
||||||
|
|
||||||
@ -361,16 +367,29 @@ def uninstall_warp():
|
|||||||
|
|
||||||
|
|
||||||
@cli.command('configure-warp')
|
@cli.command('configure-warp')
|
||||||
@click.option('--all', '-a', is_flag=True, help='Use WARP for all connections')
|
@click.option('--set-all', 'set_all_traffic', type=click.Choice(['on', 'off']), help='Set WARP for all connections (on/off)', required=False)
|
||||||
@click.option('--popular-sites', '-p', is_flag=True, help='Use WARP for popular sites like Google, OpenAI, etc')
|
@click.option('--set-popular-sites', type=click.Choice(['on', 'off']), help='Set WARP for popular sites (on/off)', required=False)
|
||||||
@click.option('--domestic-sites', '-d', is_flag=True, help='Use WARP for Iran domestic sites')
|
@click.option('--set-domestic-sites', type=click.Choice(['on', 'off']), help='Set behavior for domestic sites (on=WARP, off=REJECT)', required=False)
|
||||||
@click.option('--block-adult-sites', '-x', is_flag=True, help='Block adult content (porn)')
|
@click.option('--set-block-adult-sites', type=click.Choice(['on', 'off']), help='Set block adult content (on/off)', required=False)
|
||||||
def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool):
|
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:
|
try:
|
||||||
cli_api.configure_warp(all, popular_sites, domestic_sites, block_adult_sites)
|
cli_api.configure_warp(
|
||||||
click.echo('WARP configured successfully.')
|
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:
|
except Exception as e:
|
||||||
click.echo(f'{e}', err=True)
|
click.echo(f'Error configuring WARP: {e}', err=True)
|
||||||
|
|
||||||
@cli.command('warp-status')
|
@cli.command('warp-status')
|
||||||
def warp_status():
|
def warp_status():
|
||||||
|
|||||||
@ -218,6 +218,10 @@ def disable_hysteria2_obfs():
|
|||||||
'''Removes 'obfs' from Hysteria2 configuration.'''
|
'''Removes 'obfs' from Hysteria2 configuration.'''
|
||||||
run_cmd(['python3', Command.MANAGE_OBFS.value, '--remove'])
|
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):
|
def enable_hysteria2_masquerade(domain: str):
|
||||||
'''Enables masquerade for Hysteria2.'''
|
'''Enables masquerade for Hysteria2.'''
|
||||||
@ -458,21 +462,28 @@ def uninstall_warp():
|
|||||||
run_cmd(['python3', Command.UNINSTALL_WARP.value])
|
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 = [
|
cmd_args = [
|
||||||
'python3', Command.CONFIGURE_WARP.value
|
'python3', Command.CONFIGURE_WARP.value
|
||||||
]
|
]
|
||||||
if all:
|
if all_state:
|
||||||
cmd_args.append('--all')
|
cmd_args.extend(['--set-all', all_state])
|
||||||
if popular_sites:
|
if popular_sites_state:
|
||||||
cmd_args.append('--popular-sites')
|
cmd_args.extend(['--set-popular-sites', popular_sites_state])
|
||||||
if domestic_sites:
|
if domestic_sites_state:
|
||||||
cmd_args.append('--domestic-sites')
|
cmd_args.extend(['--set-domestic-sites', domestic_sites_state])
|
||||||
if block_adult_sites:
|
if block_adult_sites_state:
|
||||||
cmd_args.append('--block-adult')
|
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)
|
run_cmd(cmd_args)
|
||||||
|
|
||||||
|
|||||||
@ -9,16 +9,14 @@ from init_paths import *
|
|||||||
from paths import *
|
from paths import *
|
||||||
|
|
||||||
def restart_hysteria():
|
def restart_hysteria():
|
||||||
"""Restart the Hysteria2 service using the CLI script."""
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"],
|
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"],
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL)
|
stderr=subprocess.DEVNULL)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Failed to restart Hysteria2: {e}")
|
print(f"Failed to restart Hysteria2: {e}")
|
||||||
|
|
||||||
def remove_obfs():
|
def remove_obfs():
|
||||||
"""Remove the 'obfs' section from the config."""
|
|
||||||
try:
|
try:
|
||||||
with open(CONFIG_FILE, 'r') as f:
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
@ -27,9 +25,9 @@ def remove_obfs():
|
|||||||
del config['obfs']
|
del config['obfs']
|
||||||
with open(CONFIG_FILE, 'w') as f:
|
with open(CONFIG_FILE, 'w') as f:
|
||||||
json.dump(config, f, indent=2)
|
json.dump(config, f, indent=2)
|
||||||
print("✅ Successfully removed 'obfs' from config.json.")
|
print("Successfully removed 'obfs' from config.json.")
|
||||||
else:
|
else:
|
||||||
print("ℹ️ 'obfs' section not found in config.json.")
|
print("'obfs' section not found in config.json.")
|
||||||
|
|
||||||
restart_hysteria()
|
restart_hysteria()
|
||||||
|
|
||||||
@ -39,13 +37,12 @@ def remove_obfs():
|
|||||||
print(f"❌ Error removing 'obfs': {e}")
|
print(f"❌ Error removing 'obfs': {e}")
|
||||||
|
|
||||||
def generate_obfs():
|
def generate_obfs():
|
||||||
"""Generate and add an 'obfs' section with a random password."""
|
|
||||||
try:
|
try:
|
||||||
with open(CONFIG_FILE, 'r') as f:
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
||||||
if 'obfs' in config:
|
if 'obfs' in config:
|
||||||
print("ℹ️ 'obfs' section already exists. Replacing it.")
|
print("'obfs' section already exists. Replacing it.")
|
||||||
del config['obfs']
|
del config['obfs']
|
||||||
|
|
||||||
password = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32))
|
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:
|
with open(CONFIG_FILE, 'w') as f:
|
||||||
json.dump(config, f, indent=2)
|
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()
|
restart_hysteria()
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"❌ Config file not found: {CONFIG_FILE}")
|
print(f"Config file not found: {CONFIG_FILE}")
|
||||||
except Exception as e:
|
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():
|
def main():
|
||||||
if len(sys.argv) != 2:
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
option = sys.argv[1]
|
option = sys.argv[1]
|
||||||
if option in ("--remove", "-r"):
|
if option in ("--remove", "-r"):
|
||||||
print("Removing 'obfs' from config.json...")
|
# print("Removing 'obfs' from config.json...")
|
||||||
remove_obfs()
|
remove_obfs()
|
||||||
elif option in ("--generate", "-g"):
|
elif option in ("--generate", "-g"):
|
||||||
print("Generating 'obfs' in config.json...")
|
# print("Generating 'obfs' in config.json...")
|
||||||
generate_obfs()
|
generate_obfs()
|
||||||
|
elif option in ("--check", "-c"):
|
||||||
|
# print("Checking 'obfs' status in config.json...")
|
||||||
|
check_obfs()
|
||||||
else:
|
else:
|
||||||
print("Invalid option. Use --remove|-r or --generate|-g")
|
print("Invalid option. Use --remove|-r, --generate|-g, or --check|-c")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,171 +1,159 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
|
||||||
"level": "info",
|
|
||||||
"timestamp": true
|
|
||||||
},
|
|
||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"final": "local-dns",
|
||||||
{
|
|
||||||
"tag": "proxyDns",
|
|
||||||
"address": "tls://8.8.8.8",
|
|
||||||
"detour": "Proxy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "localDns",
|
|
||||||
"address": "https://223.5.5.5/dns-query",
|
|
||||||
"detour": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"outbound": "any",
|
"action": "route",
|
||||||
"server": "localDns"
|
"clash_mode": "Global",
|
||||||
|
"server": "proxy-dns",
|
||||||
|
"source_ip_cidr": [
|
||||||
|
"172.19.0.0/30",
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-ir",
|
"action": "route",
|
||||||
"server": "proxyDns"
|
"server": "proxy-dns",
|
||||||
},
|
"source_ip_cidr": [
|
||||||
{
|
"172.19.0.0/30",
|
||||||
"clash_mode": "direct",
|
"fdfe:dcba:9876::1/126"
|
||||||
"server": "localDns"
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"clash_mode": "global",
|
|
||||||
"server": "proxyDns"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"final": "localDns",
|
"servers": [
|
||||||
"strategy": "ipv4_only"
|
{
|
||||||
|
"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": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"tag": "tun-in",
|
|
||||||
"type": "tun",
|
|
||||||
"address": [
|
"address": [
|
||||||
"172.19.0.0/30"
|
"172.19.0.1/30",
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
],
|
],
|
||||||
"mtu": 9000,
|
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"strict_route": true,
|
"endpoint_independent_nat": false,
|
||||||
"stack": "system",
|
"mtu": 9000,
|
||||||
"platform": {
|
"platform": {
|
||||||
"http_proxy": {
|
"http_proxy": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 2080
|
"server_port": 2080
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"stack": "system",
|
||||||
|
"strict_route": false,
|
||||||
|
"type": "tun"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "mixed-in",
|
|
||||||
"type": "mixed",
|
|
||||||
"listen": "127.0.0.1",
|
"listen": "127.0.0.1",
|
||||||
"listen_port": 2080
|
"listen_port": 2080,
|
||||||
|
"type": "mixed",
|
||||||
|
"users": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"log": {
|
||||||
|
"level": "warn",
|
||||||
|
"timestamp": true
|
||||||
|
},
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"tag": "Proxy",
|
|
||||||
"type": "selector",
|
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
"auto",
|
"auto",
|
||||||
"direct"
|
"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",
|
"interval": "10m",
|
||||||
"tolerance": 50
|
"outbounds": [],
|
||||||
|
"tag": "auto",
|
||||||
|
"tolerance": 50,
|
||||||
|
"type": "urltest",
|
||||||
|
"url": "http://www.gstatic.com/generate_204"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "direct",
|
"tag": "direct",
|
||||||
"tag": "direct"
|
"type": "direct"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "direct",
|
|
||||||
"tag": "local"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"auto_detect_interface": true,
|
"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": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"inbound": [
|
|
||||||
"tun-in",
|
|
||||||
"mixed-in"
|
|
||||||
],
|
|
||||||
"action": "sniff"
|
"action": "sniff"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"action": "route",
|
||||||
"mode": "or",
|
"clash_mode": "Direct",
|
||||||
"rules": [
|
"outbound": "direct"
|
||||||
{
|
|
||||||
"port": 53
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"action": "route",
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"outbound": "proxy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "hijack-dns",
|
||||||
"protocol": "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",
|
"action": "route",
|
||||||
"rule_set": "geosite-ir",
|
"outbound": "direct",
|
||||||
"outbound": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "route",
|
|
||||||
"rule_set": "geoip-ir",
|
|
||||||
"outbound": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
{
|
"geosite-ir",
|
||||||
"tag": "geosite-category-ads-all",
|
"geoip-ir",
|
||||||
"type": "remote",
|
"geosite-private"
|
||||||
"format": "binary",
|
]
|
||||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ads-all.srs",
|
|
||||||
"download_detour": "direct"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "remote",
|
"action": "reject",
|
||||||
"tag": "geoip-ir",
|
"rule_set": [
|
||||||
"format": "binary",
|
"geoip-ads"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import json
|
|||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import argparse
|
||||||
|
|
||||||
core_scripts_dir = Path(__file__).resolve().parents[1]
|
core_scripts_dir = Path(__file__).resolve().parents[1]
|
||||||
if str(core_scripts_dir) not in sys.path:
|
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 *
|
from paths import *
|
||||||
|
|
||||||
def warp_configure_handler(all_traffic=False, popular_sites=False, domestic_sites=False, block_adult_sites=False):
|
def warp_configure_handler(
|
||||||
"""
|
set_all_traffic_state: str | None = None,
|
||||||
Configure WARP routing rules based on provided parameters
|
set_popular_sites_state: str | None = None,
|
||||||
|
set_domestic_sites_state: str | None = None,
|
||||||
Args:
|
set_block_adult_sites_state: str | None = None
|
||||||
all_traffic (bool): Toggle WARP for all traffic
|
):
|
||||||
popular_sites (bool): Toggle WARP for popular sites (Google, Netflix, etc.)
|
try:
|
||||||
domestic_sites (bool): Toggle between WARP and Reject for domestic sites
|
|
||||||
block_adult_sites (bool): Toggle blocking of adult content
|
|
||||||
"""
|
|
||||||
with open(CONFIG_FILE, 'r') as f:
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
config = json.load(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
|
modified = False
|
||||||
|
|
||||||
if all_traffic:
|
if 'acl' not in config:
|
||||||
warp_all_active = any(rule == "warps(all)" for rule in config.get('acl', {}).get('inline', []))
|
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:
|
if warp_all_active:
|
||||||
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule != "warps(all)"]
|
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule != warp_all_rule]
|
||||||
print("Traffic configuration changed to Direct.")
|
print("All traffic rule: Disabled.")
|
||||||
modified = True
|
modified = True
|
||||||
else:
|
else:
|
||||||
if 'acl' not in config:
|
print("All traffic rule: Already disabled.")
|
||||||
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
|
|
||||||
|
|
||||||
if popular_sites:
|
if set_popular_sites_state is not None:
|
||||||
popular_rules = [
|
popular_rules = [
|
||||||
"warps(geoip:google)",
|
"warps(geoip:google)", "warps(geosite:google)", "warps(geosite:netflix)",
|
||||||
"warps(geosite:google)",
|
"warps(geosite:spotify)", "warps(geosite:openai)", "warps(geoip:openai)"
|
||||||
"warps(geosite:netflix)",
|
|
||||||
"warps(geosite:spotify)",
|
|
||||||
"warps(geosite:openai)",
|
|
||||||
"warps(geoip:openai)"
|
|
||||||
]
|
]
|
||||||
|
if set_popular_sites_state == "on":
|
||||||
rule_exists = any(rule in config.get('acl', {}).get('inline', []) for rule in popular_rules)
|
added_any = False
|
||||||
|
for rule in popular_rules:
|
||||||
if rule_exists:
|
if rule not in config['acl']['inline']:
|
||||||
config['acl']['inline'] = [rule for rule in config['acl']['inline']
|
config['acl']['inline'].append(rule)
|
||||||
if rule not in popular_rules]
|
added_any = True
|
||||||
print("WARP configuration for Google, OpenAI, etc. removed.")
|
if added_any:
|
||||||
|
print("Popular sites rule: Enabled/Updated.")
|
||||||
modified = True
|
modified = True
|
||||||
else:
|
else:
|
||||||
if 'acl' not in config:
|
|
||||||
config['acl'] = {}
|
all_present = all(rule in config['acl']['inline'] for rule in popular_rules)
|
||||||
if 'inline' not in config['acl']:
|
if all_present:
|
||||||
config['acl']['inline'] = []
|
print("Popular sites rule: Already enabled.")
|
||||||
config['acl']['inline'].extend(popular_rules)
|
else:
|
||||||
print("WARP configured for Google, OpenAI, etc.")
|
print("Popular sites rule: Enabled/Updated.")
|
||||||
modified = True
|
modified = True
|
||||||
|
elif set_popular_sites_state == "off":
|
||||||
if domestic_sites:
|
removed_any = False
|
||||||
ir_site_rule = "warps(geosite:ir)"
|
initial_len = len(config['acl']['inline'])
|
||||||
ir_ip_rule = "warps(geoip:ir)"
|
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule not in popular_rules]
|
||||||
reject_site_rule = "reject(geosite:ir)"
|
if len(config['acl']['inline']) < initial_len:
|
||||||
reject_ip_rule = "reject(geoip:ir)"
|
removed_any = True
|
||||||
|
if removed_any:
|
||||||
using_warp = (ir_site_rule in config.get('acl', {}).get('inline', []) and
|
print("Popular sites rule: Disabled.")
|
||||||
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.")
|
|
||||||
modified = True
|
modified = True
|
||||||
else:
|
else:
|
||||||
config['acl']['inline'].extend([reject_site_rule, reject_ip_rule])
|
print("Popular sites rule: Already disabled.")
|
||||||
print("Added Reject configuration for geosite:ir and geoip:ir.")
|
|
||||||
modified = True
|
|
||||||
|
|
||||||
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)"
|
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:
|
desired_resolver = ""
|
||||||
config['acl']['inline'] = [rule for rule in config['acl']['inline']
|
if set_block_adult_sites_state == "on":
|
||||||
if rule != nsfw_rule]
|
desired_resolver = "1.1.1.3:853"
|
||||||
if 'resolver' not in config:
|
if not is_blocking_nsfw:
|
||||||
config['resolver'] = {}
|
config['acl']['inline'].append(nsfw_rule)
|
||||||
if 'tls' not in config['resolver']:
|
print("Adult content blocking: Enabled.")
|
||||||
config['resolver']['tls'] = {}
|
|
||||||
config['resolver']['tls']['addr'] = "1.1.1.1:853"
|
|
||||||
print("Adult content blocking removed and resolver updated.")
|
|
||||||
modified = True
|
modified = True
|
||||||
else:
|
else:
|
||||||
if 'acl' not in config:
|
print("Adult content blocking: Already enabled.")
|
||||||
config['acl'] = {}
|
elif set_block_adult_sites_state == "off":
|
||||||
if 'inline' not in config['acl']:
|
desired_resolver = "1.1.1.1:853"
|
||||||
config['acl']['inline'] = []
|
if is_blocking_nsfw:
|
||||||
config['acl']['inline'].append(nsfw_rule)
|
config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule != nsfw_rule]
|
||||||
if 'resolver' not in config:
|
print("Adult content blocking: Disabled.")
|
||||||
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.")
|
|
||||||
modified = True
|
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:
|
if modified:
|
||||||
with open(CONFIG_FILE, 'w') as f:
|
with open(CONFIG_FILE, 'w') as f:
|
||||||
json.dump(config, f, indent=2)
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
print("Configuration updated. Attempting to restart hysteria2 service...")
|
||||||
try:
|
try:
|
||||||
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"],
|
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"],
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=10)
|
||||||
except subprocess.CalledProcessError:
|
print("Hysteria2 service restarted successfully.")
|
||||||
print("Warning: Failed to restart hysteria2")
|
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__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
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 = argparse.ArgumentParser(description="Configure WARP settings")
|
parser.add_argument("--set-popular-sites", choices=['on', 'off'], help="Set WARP for popular sites (on/off)")
|
||||||
parser.add_argument("--all", action="store_true", help="Toggle WARP for all traffic")
|
parser.add_argument("--set-domestic-sites", choices=['on', 'off'], help="Set behavior for domestic sites (on=WARP, off=REJECT)")
|
||||||
parser.add_argument("--popular-sites", action="store_true", help="Toggle WARP for popular sites")
|
parser.add_argument("--set-block-adult", choices=['on', 'off'], help="Set blocking of adult content (on/off)")
|
||||||
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")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not any(vars(args).values()):
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
warp_configure_handler(
|
warp_configure_handler(
|
||||||
all_traffic=args.all,
|
set_all_traffic_state=args.set_all,
|
||||||
popular_sites=args.popular_sites,
|
set_popular_sites_state=args.set_popular_sites,
|
||||||
domestic_sites=args.domestic_sites,
|
set_domestic_sites_state=args.set_domestic_sites,
|
||||||
block_adult_sites=args.block_adult
|
set_block_adult_sites_state=args.set_block_adult
|
||||||
)
|
)
|
||||||
@ -1,5 +1,5 @@
|
|||||||
from fastapi import APIRouter, BackgroundTasks, HTTPException, UploadFile, File
|
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 ..schema.response import DetailResponse, IPLimitConfig, SetupDecoyRequest, DecoyStatusResponse, IPLimitConfigResponse
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
import shutil
|
import shutil
|
||||||
@ -223,6 +223,23 @@ async def disable_obfs():
|
|||||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
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')
|
@router.get('/enable-masquerade/{domain}', response_model=DetailResponse, summary='Enable Hysteria2 masquerade')
|
||||||
async def enable_masquerade(domain: str):
|
async def enable_masquerade(domain: str):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import cli_api
|
|||||||
router = APIRouter()
|
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():
|
async def install():
|
||||||
"""
|
"""
|
||||||
Installs WARP.
|
Installs WARP.
|
||||||
@ -27,7 +27,7 @@ async def install():
|
|||||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
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():
|
async def uninstall():
|
||||||
"""
|
"""
|
||||||
Uninstalls WARP.
|
Uninstalls WARP.
|
||||||
@ -45,7 +45,7 @@ async def uninstall():
|
|||||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
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):
|
async def configure(body: ConfigureInputBody):
|
||||||
"""
|
"""
|
||||||
Configures WARP with the given options.
|
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.
|
HTTPException: If an error occurs during configuration, an HTTP 400 error is raised with the error details.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cli_api.configure_warp(body.all, body.popular_sites, body.domestic_sites,
|
all_st = 'on' if body.all else 'off'
|
||||||
body.block_adult_sites)
|
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.')
|
return DetailResponse(detail='WARP configured successfully.')
|
||||||
except Exception as e:
|
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():
|
async def status():
|
||||||
try:
|
try:
|
||||||
status_json_str = cli_api.warp_status()
|
status_json_str = cli_api.warp_status()
|
||||||
|
|||||||
@ -18,3 +18,6 @@ class GetPortResponse(BaseModel):
|
|||||||
|
|
||||||
class GetSniResponse(BaseModel):
|
class GetSniResponse(BaseModel):
|
||||||
sni: str
|
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'
|
<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
|
aria-controls='sni' aria-selected='false'><i class="fas fa-shield-alt"></i> Change
|
||||||
SNI</a>
|
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>
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link' id='ip-tab' data-toggle='pill' href='#change_ip' role='tab'
|
<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>
|
aria-controls='decoy' aria-selected='false'><i class="fas fa-mask"></i>
|
||||||
Decoy Site</a>
|
Decoy Site</a>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class='card-body' style="margin-left: 25px;">
|
<div class='card-body' style="margin-left: 25px;">
|
||||||
@ -71,22 +80,21 @@
|
|||||||
<ul class='nav nav-tabs' id='subs-tabs' role='tablist'>
|
<ul class='nav nav-tabs' id='subs-tabs' role='tablist'>
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link active' id='normal-tab' data-toggle='tab' href='#normal' role='tab'
|
<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>
|
||||||
<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'
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class='tab-content' id='subs-tabs-content'>
|
<div class='tab-content' id='subs-tabs-content'>
|
||||||
<br>
|
<br>
|
||||||
<!-- Normal Sub Service Control Tab -->
|
|
||||||
<div class='tab-pane fade show active' id='normal' role='tabpanel' aria-labelledby='normal-tab'>
|
<div class='tab-pane fade show active' id='normal' role='tabpanel' aria-labelledby='normal-tab'>
|
||||||
<form id="normal_sub_service_form">
|
<form id="normal_sub_service_form">
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='normal_domain'>Domain:</label>
|
<label for='normal_domain'>Domain:</label>
|
||||||
<input type='text' class='form-control' id='normal_domain'
|
<input type='text' class='form-control' id='normal_domain'
|
||||||
placeholder='Enter Domain'>
|
placeholder='sub.example.com'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid domain (without http:// or https://).
|
Please enter a valid domain (without http:// or https://).
|
||||||
</div>
|
</div>
|
||||||
@ -94,26 +102,25 @@
|
|||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='normal_port'>Port:</label>
|
<label for='normal_port'>Port:</label>
|
||||||
<input type='text' class='form-control' id='normal_port'
|
<input type='text' class='form-control' id='normal_port'
|
||||||
placeholder='Enter Port'>
|
placeholder='e.g., 8080'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid port number.
|
Please enter a valid port number.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="normal_start" type='button' class='btn btn-success'>
|
<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>
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
|
||||||
Start
|
Start Service
|
||||||
</button>
|
</button>
|
||||||
<button id="normal_stop" type='button' class='btn btn-danger'
|
<button id="normal_stop" type='button' class='btn btn-danger'
|
||||||
style="display: none;">Stop</button>
|
style="display: none;">Stop Service</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- Normal Sub Configuration Tab -->
|
|
||||||
<div class='tab-pane fade' id='normal-sub-config-content' role='tabpanel' aria-labelledby='normal-sub-config-link-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">
|
<form id="normal_sub_config_form">
|
||||||
<div class='form-group'>
|
<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'
|
<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">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid subpath (alphanumeric characters only, e.g., mysub).
|
Please enter a valid subpath (alphanumeric characters only, e.g., mysub).
|
||||||
</div>
|
</div>
|
||||||
@ -183,6 +190,24 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</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 -->
|
<!-- Change IP Tab -->
|
||||||
<div class='tab-pane fade' id='change_ip' role='tabpanel' aria-labelledby='ip-tab'>
|
<div class='tab-pane fade' id='change_ip' role='tabpanel' aria-labelledby='ip-tab'>
|
||||||
<form id="change_ip_form">
|
<form id="change_ip_form">
|
||||||
@ -208,18 +233,41 @@
|
|||||||
|
|
||||||
<!-- Backup Tab -->
|
<!-- Backup Tab -->
|
||||||
<div class='tab-pane fade' id='backup' role='tabpanel' aria-labelledby='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">
|
<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">
|
<input type="file" class="form-control-file" id="backup_file" accept=".zip">
|
||||||
</div>
|
</div>
|
||||||
<button id="upload_backup" type='button' class='btn btn-success'>Upload</button>
|
<button id="upload_backup" type='button' class='btn btn-success btn-block'>
|
||||||
<button id="download_backup" type='button' class='btn btn-primary'>Download Backup</button>
|
<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 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>
|
||||||
|
|
||||||
<div id="backup_status" class="mt-2"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- IP Limit Tab -->
|
<!-- IP Limit Tab -->
|
||||||
@ -307,6 +355,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.card -->
|
<!-- /.card -->
|
||||||
@ -330,6 +435,7 @@
|
|||||||
|
|
||||||
initUI();
|
initUI();
|
||||||
fetchDecoyStatus();
|
fetchDecoyStatus();
|
||||||
|
fetchObfsStatus();
|
||||||
|
|
||||||
function isValidPath(path) {
|
function isValidPath(path) {
|
||||||
if (!path) return false;
|
if (!path) return false;
|
||||||
@ -519,7 +625,8 @@
|
|||||||
const servicesMap = {
|
const servicesMap = {
|
||||||
"hysteria_telegram_bot": "#telegram_form",
|
"hysteria_telegram_bot": "#telegram_form",
|
||||||
"hysteria_normal_sub": "#normal_sub_service_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 => {
|
Object.keys(servicesMap).forEach(service => {
|
||||||
@ -575,6 +682,17 @@
|
|||||||
$("#max_ips").val("");
|
$("#max_ips").val("");
|
||||||
$("#block_duration, #max_ips").removeClass('is-invalid');
|
$("#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 {
|
} else {
|
||||||
const $formSelector = $(targetSelector);
|
const $formSelector = $(targetSelector);
|
||||||
if (isRunning) {
|
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() {
|
function startTelegram() {
|
||||||
if (!validateForm('telegram_form')) return;
|
if (!validateForm('telegram_form')) return;
|
||||||
const apiToken = $("#telegram_api_token").val();
|
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);
|
$("#telegram_start").on("click", startTelegram);
|
||||||
@ -944,6 +1202,8 @@
|
|||||||
$("#ip_limit_change_config").on("click", configIPLimit);
|
$("#ip_limit_change_config").on("click", configIPLimit);
|
||||||
$("#decoy_setup").on("click", setupDecoy);
|
$("#decoy_setup").on("click", setupDecoy);
|
||||||
$("#decoy_stop").on("click", stopDecoy);
|
$("#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 () {
|
$('#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
|
if systemctl is-active --quiet "$service_name"; then
|
||||||
echo -e "${cyan}=== WARP Status ===${NC}"
|
echo -e "${cyan}=== WARP Status ===${NC}"
|
||||||
|
|
||||||
status_json=$(python3 $CLI_PATH warp-status)
|
status_json=$(python3 $CLI_PATH warp-status)
|
||||||
|
|
||||||
all_traffic=$(echo "$status_json" | grep -o '"all_traffic_via_warp": *[^,}]*' | cut -d':' -f2 | tr -d ' "')
|
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 ' "')
|
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 ' "')
|
block_adult=$(echo "$status_json" | grep -o '"block_adult_content": *[^,}]*' | cut -d':' -f2 | tr -d ' "')
|
||||||
|
|
||||||
display_status() {
|
display_status() {
|
||||||
local label="$1"
|
local label="$1"
|
||||||
local status="$2"
|
local status_val="$2"
|
||||||
if [ "$status" = "true" ]; then
|
if [ "$status_val" = "true" ]; then
|
||||||
echo -e " ${green}✓${NC} $label: ${green}Enabled${NC}"
|
echo -e " ${green}✓${NC} $label: ${green}Enabled${NC}"
|
||||||
else
|
else
|
||||||
echo -e " ${red}✗${NC} $label: ${red}Disabled${NC}"
|
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 "All Traffic via WARP" "$all_traffic"
|
||||||
display_status "Popular Sites via WARP" "$popular_sites"
|
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"
|
display_status "Block Adult Content" "$block_adult"
|
||||||
|
|
||||||
echo -e "${cyan}==================${NC}"
|
echo -e "${cyan}==================${NC}"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo "Configure WARP Options:"
|
echo "Configure WARP Options (Toggle):"
|
||||||
echo "1. Use WARP for all traffic"
|
echo "1. All traffic via WARP"
|
||||||
echo "2. Use WARP for popular sites"
|
echo "2. Popular sites via WARP"
|
||||||
echo "3. Use WARP for domestic sites"
|
echo "3. Domestic sites (WARP/Reject)"
|
||||||
echo "4. Block adult content"
|
echo "4. Block adult content"
|
||||||
echo "5. WARP Status Profile"
|
echo "5. WARP Status Profile (IP etc.)"
|
||||||
echo "6. Change IP address"
|
echo "6. Change WARP IP address"
|
||||||
echo "0. Cancel"
|
echo "0. Cancel"
|
||||||
|
|
||||||
read -p "Select an option: " option
|
read -p "Select an option to toggle: " option
|
||||||
|
|
||||||
case $option in
|
case $option in
|
||||||
1) python3 $CLI_PATH configure-warp --all ;;
|
1)
|
||||||
2) python3 $CLI_PATH configure-warp --popular-sites ;;
|
target_state=$([ "$all_traffic" = "true" ] && echo "off" || echo "on")
|
||||||
3) python3 $CLI_PATH configure-warp --domestic-sites ;;
|
python3 $CLI_PATH configure-warp --set-all "$target_state" ;;
|
||||||
4) python3 $CLI_PATH configure-warp --block-adult-sites ;;
|
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)
|
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
|
cd /etc/warp/ && wgcf status
|
||||||
echo
|
echo
|
||||||
echo -e "${yellow}Warp IP:${NC} ${cyan}$ip${NC}"
|
echo -e "${yellow}Warp IP:${NC} ${cyan}${current_ip}${NC}"
|
||||||
;;
|
;;
|
||||||
6)
|
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 -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"
|
systemctl restart "$service_name"
|
||||||
|
|
||||||
echo -n "Waiting for service to restart"
|
echo -n "Waiting for service to restart"
|
||||||
@ -445,18 +455,22 @@ warp_configure_handler() {
|
|||||||
done
|
done
|
||||||
echo
|
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}"
|
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}"
|
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}"
|
echo -e "${yellow}⚠ IP address remained the same${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${red}✗ Could not verify IP change.${NC}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
0) echo "WARP configuration canceled." ;;
|
0) echo "WARP configuration canceled." ;;
|
||||||
*) echo -e "${red}Invalid option. Please try again.${NC}" ;;
|
*) echo -e "${red}Invalid option. Please try again.${NC}" ;;
|
||||||
esac
|
esac
|
||||||
|
# echo "Command sent. Check status again to see changes."
|
||||||
|
|
||||||
else
|
else
|
||||||
echo -e "${red}$service_name is not active. Please start the service before configuring WARP.${NC}"
|
echo -e "${red}$service_name is not active. Please start the service before configuring WARP.${NC}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user