diff --git a/.env.example b/.env.example index c7e6ba3..a11b614 100644 --- a/.env.example +++ b/.env.example @@ -23,7 +23,9 @@ UVICORN_PORT = 8000 # DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/xxxxxxx" # CUSTOM_TEMPLATES_DIRECTORY="/var/lib/marzneshin/templates/" -# CLASH_SUBSCRIPTION_TEMPLATE="clash/my-custom-template.yml" +# CLASH_SUBSCRIPTION_TEMPLATE="/var/lib/marzneshin/templates/clash.yml" +# SINGBOX_SUBSCRIPTION_TEMPLATE="/var/lib/marzneshin/templates/sing-box.json" +# XRAY_SUBSCRIPTION_TEMPLATE="/var/lib/marzneshin/templates/xray.json" # SUBSCRIPTION_PAGE_TEMPLATE="subscription/index.html" # HOME_PAGE_TEMPLATE="home/index.html" diff --git a/app/templates/sing-box.json b/app/templates/sing-box.json new file mode 100644 index 0000000..dc0ff97 --- /dev/null +++ b/app/templates/sing-box.json @@ -0,0 +1,236 @@ +{ + "log": { + "level": "warn", + "timestamp": true + }, + "dns": { + "servers": [ + { + "tag": "dns-remote", + "address": "1.1.1.1", + "detour": "Green Zone" + }, + { + "tag": "dns-local", + "address": "https://free.shecan.ir/dns-query", + "address_resolver": "dns-resolver", + "detour": "direct" + }, + { + "tag": "dns-resolver", + "address": "178.22.122.100", + "detour": "direct" + }, + { + "tag": "dns-block", + "address": "rcode://success" + } + ], + "rules": [ + { + "outbound": "any", + "server": "dns-local" + }, + { + "domain_suffix": ".ir", + "server": "dns-local" + }, + { + "rule_set": "geosite-ir", + "server": "dns-local" + } + ], + "final": "dns-remote", + "reverse_mapping": true, + "independent_cache": true + }, + "ntp": { + "enabled": false, + "server": "pool.ntp.org" + }, + "inbounds": [ + { + "type": "tun", + "tag": "tun-in", + "interface_name": "tun0", + "mtu": 9000, + "inet4_address": "172.19.0.1/30", + "inet6_address": "fdfe:dcba:9876::1/126", + "auto_route": true, + "strict_route": true, + "stack": "mixed", + "sniff": true, + "sniff_override_destination": false + } + ], + "outbounds": [ + { + "type": "selector", + "tag": "Green Zone", + "interrupt_exist_connections": true, + "default": "\ud83c\udde9\ud83c\uddea - CD\u03a0 - MEDI\u039b", + "outbounds": null + }, + { + "type": "direct", + "tag": "direct" + }, + { + "type": "block", + "tag": "block" + }, + { + "type": "dns", + "tag": "dns-out" + } + ], + "route": { + "rules": [ + { + "type": "logical", + "mode": "or", + "rules": [ + { + "protocol": "dns" + }, + { + "port": 53 + } + ], + "outbound": "dns-out" + }, + { + "type": "logical", + "mode": "or", + "rules": [ + { + "port": 853 + }, + { + "network": "udp", + "port": 443 + } + ], + "outbound": "block" + }, + { + "domain_suffix": ".ir", + "outbound": "direct" + }, + { + "ip_is_private": true, + "outbound": "direct" + }, + { + "rule_set": [ + "geosite-category-ads-all", + "geosite-malware", + "geosite-phishing", + "geosite-cryptominers", + "geoip-malware", + "geoip-phishing" + ], + "outbound": "block" + }, + { + "rule_set": [ + "geosite-ir", + "geoip-ir" + ], + "outbound": "direct" + }, + { + "source_ip_cidr": [ + "224.0.0.0/3", + "ff00::/8" + ], + "ip_cidr": [ + "224.0.0.0/3", + "ff00::/8" + ], + "outbound": "block" + } + ], + "rule_set": [ + { + "type": "remote", + "tag": "geosite-ir", + "format": "binary", + "url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs", + "download_detour": "direct", + "update_interval": "1480h0m0s" + }, + { + "type": "remote", + "tag": "geosite-category-ads-all", + "format": "binary", + "url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ads-all.srs", + "download_detour": "direct", + "update_interval": "1480h0m0s" + }, + { + "type": "remote", + "tag": "geosite-malware", + "format": "binary", + "url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-malware.srs", + "download_detour": "direct", + "update_interval": "1480h0m0s" + }, + { + "type": "remote", + "tag": "geosite-phishing", + "format": "binary", + "url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-phishing.srs", + "download_detour": "direct", + "update_interval": "1480h0m0s" + }, + { + "type": "remote", + "tag": "geosite-cryptominers", + "format": "binary", + "url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-cryptominers.srs", + "download_detour": "direct", + "update_interval": "1480h0m0s" + }, + { + "type": "remote", + "tag": "geoip-ir", + "format": "binary", + "url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ir.srs", + "download_detour": "direct", + "update_interval": "1480h0m0s" + }, + { + "type": "remote", + "tag": "geoip-malware", + "format": "binary", + "url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-malware.srs", + "download_detour": "direct", + "update_interval": "1480h0m0s" + }, + { + "type": "remote", + "tag": "geoip-phishing", + "format": "binary", + "url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-phishing.srs", + "download_detour": "direct", + "update_interval": "1480h0m0s" + } + ], + "final": "Green Zone", + "auto_detect_interface": true, + "override_android_vpn": true + }, + "experimental": { + "cache_file": { + "enabled": true, + "store_fakeip": true + }, + "clash_api": { + "external_controller": "127.0.0.1:9090", + "external_ui": "metacubexd", + "external_ui_download_url": "https://github.com/MetaCubeX/metacubexd/archive/gh-pages.zip", + "external_ui_download_detour": "direct" + } + } +} \ No newline at end of file diff --git a/app/templates/xray.json b/app/templates/xray.json new file mode 100644 index 0000000..035a952 --- /dev/null +++ b/app/templates/xray.json @@ -0,0 +1,84 @@ +{ + "log": { + "loglevel": "warning" + }, + "dns": { + "queryStrategy": "UseIP", + "servers": [ + "1.1.1.1", + "8.8.8.8" + ], + "tag": "dns_out" + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "domain": [ + "geosite:category-ads-all" + ], + "outboundTag": "block" + }, + { + "domain": [ + "regexp:.*\\.ir", + "domain:ir", + "geosite:private" + ], + "outboundTag": "direct" + }, + { + "ip": [ + "geoip:ir", + "geoip:private" + ], + "outboundTag": "direct" + } + ] + }, + "policy": { + "system": { + "statsOutboundDownlink": true, + "statsOutboundUplink": true + } + }, + "inbounds": [ + { + "port": 10808, + "protocol": "socks", + "settings": { + "auth": "noauth", + "udp": true, + "userLevel": 8 + }, + "sniffing": { + "destOverride": [ + "http", + "tls", + "fakedns" + ], + "enabled": true + }, + "tag": "socks" + }, + { + "port": 10809, + "protocol": "http", + "settings": { + "userLevel": 8 + }, + "tag": "http" + } + ], + "outbounds": [ + { + "tag": "direct", + "protocol": "freedom" + }, + { + "tag": "block", + "protocol": "blackhole" + } + ], + "stats": {} +} diff --git a/app/utils/share.py b/app/utils/share.py index ef6d4ce..af8fc9a 100644 --- a/app/utils/share.py +++ b/app/utils/share.py @@ -4,6 +4,7 @@ import random import secrets from collections import defaultdict from datetime import datetime as dt, timedelta +from importlib import resources from typing import TYPE_CHECKING, Literal, Union, List from uuid import UUID @@ -18,6 +19,11 @@ from app.models.proxy import ( from app.models.user import UserStatus from app.utils.keygen import gen_uuid, gen_password from app.utils.system import get_public_ip, readable_size +from config import ( + XRAY_SUBSCRIPTION_TEMPLATE, + SINGBOX_SUBSCRIPTION_TEMPLATE, + CLASH_SUBSCRIPTION_TEMPLATE, +) if TYPE_CHECKING: from app.models.user import UserResponse @@ -40,6 +46,16 @@ subscription_handlers = { "sing-box": SingBoxConfig, } +handlers_templates = { + LinksConfig: None, + XrayConfig: XRAY_SUBSCRIPTION_TEMPLATE + or resources.files("app.templates") / "xray.json", + ClashConfig: CLASH_SUBSCRIPTION_TEMPLATE, + ClashMetaConfig: CLASH_SUBSCRIPTION_TEMPLATE, + SingBoxConfig: SINGBOX_SUBSCRIPTION_TEMPLATE + or resources.files("app.templates") / "sing-box.json", +} + def generate_subscription( user: "UserResponse", @@ -55,8 +71,14 @@ def generate_subscription( if config_format not in subscription_handlers.keys(): raise ValueError(f'Unsupported format "{config_format}"') + subscription_handler_class = subscription_handlers[config_format] + if template_path := handlers_templates[subscription_handler_class]: + subscription_handler = subscription_handler_class(template_path=template_path) + else: + subscription_handler = subscription_handler_class() + config = process_inbounds_and_tags( - inbounds, key, format_variables, conf=subscription_handlers[config_format]() + inbounds, key, format_variables, conf=subscription_handler ) return config if not as_base64 else base64.b64encode(config.encode()).decode() diff --git a/config.py b/config.py index 900df56..305fc1b 100755 --- a/config.py +++ b/config.py @@ -44,18 +44,15 @@ JWT_ACCESS_TOKEN_EXPIRE_MINUTES = config( ) CUSTOM_TEMPLATES_DIRECTORY = config("CUSTOM_TEMPLATES_DIRECTORY", default=None) -CLASH_SUBSCRIPTION_TEMPLATE = config( - "CLASH_SUBSCRIPTION_TEMPLATE", default="clash/default.yml" -) + SUBSCRIPTION_PAGE_TEMPLATE = config( "SUBSCRIPTION_PAGE_TEMPLATE", default="subscription/index.html" ) HOME_PAGE_TEMPLATE = config("HOME_PAGE_TEMPLATE", default="home/index.html") -SINGBOX_SUBSCRIPTION_TEMPLATE = config( - "SINGBOX_SUBSCRIPTION_TEMPLATE", default="singbox/default.json" -) - +SINGBOX_SUBSCRIPTION_TEMPLATE = config("SINGBOX_SUBSCRIPTION_TEMPLATE", default=None) +XRAY_SUBSCRIPTION_TEMPLATE = config("XRAY_SUBSCRIPTION_TEMPLATE", default=None) +CLASH_SUBSCRIPTION_TEMPLATE = config("CLASH_SUBSCRIPTION_TEMPLATE", default=None) WEBHOOK_ADDRESS = config("WEBHOOK_ADDRESS", default=None) WEBHOOK_SECRET = config("WEBHOOK_SECRET", default=None) diff --git a/requirements.txt b/requirements.txt index 20a3714..a3d1ba8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,4 +35,4 @@ python-multipart==0.0.7 xxhash==3.4.1 grpcio==1.60.0 psycopg==3.1.18 -v2share==0.1.0b6 +v2share==0.1.0b7