Add optional Mieru (mita v3.28.0) install per server with mierus:// share links.

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-28 13:47:59 +03:00
co-authored by Cursor
parent 32a0e90e19
commit 9890e8cc3f
14 changed files with 632 additions and 14 deletions
+85 -5
View File
@@ -103,7 +103,7 @@ else:
application_path = os.path.dirname(__file__)
DATA_FILE = os.path.join(application_path, 'data.json') # legacy JSON; used only for one-shot import / export
CURRENT_VERSION = "v2.6.3"
CURRENT_VERSION = "v2.6.4"
RELEASES_REPO_URL = repo_url()
RELEASES_API_LATEST = api_latest_url()
BIN_DIR = os.environ.get('TUNNEL_BIN_DIR', os.path.join(application_path, 'bin'))
@@ -1111,8 +1111,9 @@ async def wait_for_tunnel_url(provider: str, seconds: int = 20):
return get_tunnel_status(provider)
BASE_PROTOCOLS = ['awg', 'awg2', 'awg_legacy', 'xray', 'telemt', 'hysteria', 'naiveproxy', 'dns', 'wireguard', 'socks5', 'adguard', 'nginx']
BASE_PROTOCOLS = ['awg', 'awg2', 'awg_legacy', 'xray', 'telemt', 'hysteria', 'naiveproxy', 'mieru', 'dns', 'wireguard', 'socks5', 'adguard', 'nginx']
MULTI_INSTANCE_PROTOCOLS = {'awg', 'awg2', 'awg_legacy', 'xray', 'telemt', 'hysteria', 'socks5'}
NON_DOCKER_PROTOCOLS = frozenset({'mieru'})
def protocol_base(protocol: str) -> str:
@@ -1154,6 +1155,7 @@ def protocol_display_name(protocol: str) -> str:
'telemt': 'Telemt',
'hysteria': 'Hysteria 2',
'naiveproxy': 'NaiveProxy',
'mieru': 'Mieru',
'dns': 'AmneziaDNS',
'wireguard': 'WireGuard',
'socks5': 'SOCKS5',
@@ -1175,6 +1177,7 @@ def protocol_container_name(protocol: str) -> Optional[str]:
'telemt': 'telemt',
'hysteria': 'amnezia-hysteria',
'naiveproxy': 'amnezia-naiveproxy',
'mieru': 'mita',
'dns': 'amnezia-dns',
'wireguard': 'amnezia-wireguard',
'socks5': 'amnezia-socks5proxy',
@@ -1220,6 +1223,9 @@ def get_protocol_manager(ssh, protocol: str):
elif base == 'naiveproxy':
from managers.naiveproxy_manager import NaiveProxyManager
return NaiveProxyManager(ssh, protocol)
elif base == 'mieru':
from managers.mieru_manager import MieruManager
return MieruManager(ssh, protocol)
from managers.awg_manager import AWGManager
return AWGManager(ssh)
@@ -1281,7 +1287,7 @@ def _manager_call(manager, method, protocol, *args, **kwargs):
# Protocols that own VPN clients (can list/link connections)
CLIENT_VPN_BASES = {'awg', 'awg2', 'awg_legacy', 'xray', 'telemt', 'wireguard', 'hysteria', 'naiveproxy', 'xui'}
CLIENT_VPN_BASES = {'awg', 'awg2', 'awg_legacy', 'xray', 'telemt', 'wireguard', 'hysteria', 'naiveproxy', 'mieru', 'xui'}
def generate_vpn_link(config_text):
@@ -3466,6 +3472,12 @@ async def api_check_server(request: Request, server_id: int):
merged[key] = db_proto[key]
if db_proto.get('port') and not merged.get('port'):
merged['port'] = db_proto['port']
if protocol_base(proto) == 'mieru':
for key in ('release',):
if db_proto.get(key) not in (None, '') and not merged.get(key):
merged[key] = db_proto[key]
if db_proto.get('port') and not merged.get('port'):
merged['port'] = db_proto['port']
return merged
def should_preserve_saved_protocol(proto, result=None, err=None):
@@ -3480,8 +3492,9 @@ async def api_check_server(request: Request, server_id: int):
def check_proto(proto):
cname = protocol_container_name(proto)
base = protocol_base(proto)
# Fast path: skip deep manager probes when container is absent
if cname and cname not in inventory:
if cname and cname not in inventory and base not in NON_DOCKER_PROTOCOLS:
return proto, merge_saved_protocol_status(proto, {
'container_exists': False,
'container_running': False,
@@ -3574,7 +3587,9 @@ async def api_install_protocol(request: Request, server_id: int, req: InstallPro
ssh = get_ssh(server)
ssh.connect()
docker_install_log = ensure_docker_installed(ssh)
docker_install_log = ''
if install_base not in NON_DOCKER_PROTOCOLS:
docker_install_log = ensure_docker_installed(ssh)
manager = get_protocol_manager(ssh, install_protocol)
# Pass parameters to installer
@@ -3631,6 +3646,11 @@ async def api_install_protocol(request: Request, server_id: int, req: InstallPro
domain=req.naiveproxy_domain,
email=req.naiveproxy_email,
)
elif install_base == 'mieru':
result = manager.install_protocol(
protocol_type=install_protocol,
port=req.port,
)
else:
result = manager.install_protocol(install_protocol, port=req.port)
@@ -3680,6 +3700,11 @@ async def api_install_protocol(request: Request, server_id: int, req: InstallPro
proto_record['email'] = result.get('email')
if result.get('port'):
proto_record['port'] = str(result['port'])
if install_base == 'mieru':
if result.get('port'):
proto_record['port'] = str(result['port'])
if result.get('release'):
proto_record['release'] = result.get('release')
proto_record['base_protocol'] = install_base
proto_record['instance'] = protocol_instance(install_protocol)
proto_record['display_name'] = protocol_display_name(install_protocol)
@@ -3867,6 +3892,7 @@ CONTAINER_NAMES = {
'telemt': 'telemt',
'hysteria': 'amnezia-hysteria',
'naiveproxy': 'amnezia-naiveproxy',
'mieru': 'mita',
'dns': 'amnezia-dns',
'wireguard': 'amnezia-wireguard',
'socks5': 'amnezia-socks5proxy',
@@ -4164,6 +4190,35 @@ async def api_container_toggle(request: Request, server_id: int, req: ProtocolRe
server = data['servers'][server_id]
ssh = get_ssh(server)
ssh.connect()
base = protocol_base(req.protocol)
if base in NON_DOCKER_PROTOCOLS:
manager = get_protocol_manager(ssh, req.protocol)
is_running = manager.check_container_running(req.protocol)
if is_running:
manager.stop_service()
action = 'stopped'
else:
manager.start_service()
action = 'started'
diag = manager.get_container_diagnostics(req.protocol)
error = diag.get('error_summary') or ''
recent_logs = diag.get('recent_logs') or ''
if action == 'started' and not diag.get('running'):
ssh.disconnect()
return JSONResponse({
'error': error or 'Mieru failed to start',
'action': action,
'container': container,
'recent_logs': recent_logs,
}, status_code=400)
ssh.disconnect()
return {
'status': 'success',
'action': action,
'container': container,
'error': error,
'recent_logs': recent_logs,
}
# Check current state
out, _, _ = ssh.run_sudo_command(
f"docker inspect -f '{{{{.State.Running}}}}' {container} 2>/dev/null"
@@ -4227,6 +4282,20 @@ async def api_container_logs(request: Request, server_id: int, req: ContainerLog
container = protocol_container_name(req.protocol)
ssh = get_ssh(server)
ssh.connect()
if protocol_base(req.protocol) in NON_DOCKER_PROTOCOLS:
mgr = get_protocol_manager(ssh, req.protocol)
logs = mgr.get_logs(req.protocol, tail=req.tail or 200)
diag = mgr.get_container_diagnostics(req.protocol)
ssh.disconnect()
return {
'status': 'success',
'protocol': req.protocol,
'container': container,
'logs': logs,
'running': bool(diag.get('running')),
'error': diag.get('error_summary') or '',
'container_status': diag.get('status') or '',
}
if protocol_base(req.protocol) == 'hysteria':
from managers.hysteria_manager import HysteriaManager
mgr = HysteriaManager(ssh, req.protocol)
@@ -4292,6 +4361,17 @@ async def api_container_logs_stream(request: Request, server_id: int, protocol:
ssh = get_ssh(server)
ssh.connect()
try:
if protocol_base(protocol) in NON_DOCKER_PROTOCOLS:
mgr = get_protocol_manager(ssh, protocol)
logs = mgr.get_logs(protocol, tail=tail)
diag = mgr.get_container_diagnostics(protocol)
state = '|'.join([
diag.get('status') or '',
'true' if diag.get('running') else 'false',
'',
diag.get('error_summary') or '',
])
return logs, state
out, err, _ = ssh.run_sudo_command(
f"docker logs --tail {tail} --timestamps {shlex.quote(container)} 2>&1",
timeout=25,