Initialize Clash Web source and deployment documentation

This commit is contained in:
lhc committed 2026-10-05 17:32:00 +08:00
commit 8e0fa00a73
66 files changed
+8532

No files matched your search

+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Run the browser against an isolated real backend/core and a local subscription fixture."""
import contextlib
import http.server
import json
import os
import shutil
import socket
import sqlite3
import subprocess
import tempfile
import threading
import time
import urllib.request
from pathlib import Path
root = Path(__file__).resolve().parent.parent
for port in (18080, 9090, 7890, 1053):
with socket.socket() as probe:
if probe.connect_ex(('127.0.0.1', port)) == 0:
raise SystemExit(f'Port {port} is occupied. E2E aborted to avoid changing another service.')
class Fixture(http.server.BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
def do_CONNECT(self):
self.send_response(200, 'Connection Established')
self.end_headers()
self.close_connection = False
self.handle_one_request()
def do_HEAD(self):
# Mihomo's unified-delay check repeats HEAD on the established tunnel.
# Supply a framed keep-alive response, rather than the default HTTP 501.
time.sleep(0.03)
self.send_response(200)
self.send_header('Content-Length', '0')
self.end_headers()
def do_GET(self):
if self.path == '/subscription':
data = 'proxies:\n'
for name in ['香港 · 测试节点', '日本 · 测试节点', '新加坡 · 测试节点']:
data += f' - name: {name}\n type: http\n server: 127.0.0.1\n port: {self.server.server_port}\n'
body = data.encode()
else:
# Keep successful local health checks above Mihomo's integer-ms zero.
time.sleep(0.03)
body = b'local-e2e-proxy'
self.send_response(200)
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
fixture = http.server.ThreadingHTTPServer(('127.0.0.1', 0), Fixture)
threading.Thread(target=fixture.serve_forever, daemon=True).start()
def wait_url(url, timeout=20):
until = time.monotonic() + timeout
while time.monotonic() < until:
try:
with urllib.request.urlopen(url, timeout=1) as response:
return response.status
except Exception:
time.sleep(0.1)
raise RuntimeError(f'Service failed to start: {url}')
root.joinpath('.cache').mkdir(exist_ok=True)
with tempfile.TemporaryDirectory(prefix='e2e-', dir=root / '.cache') as temporary:
work = Path(temporary)
core = work / 'mihomo'
shutil.copytree(root / 'data/mihomo/rules', core / 'rules')
command = [str(root / 'bin/clashweb'), '--data', str(work / 'app'), '--core-dir', str(core), '--mihomo', str(root / 'bin/mihomo')]
initialized = subprocess.run(command + ['--init'], text=True, capture_output=True)
if initialized.returncode:
raise SystemExit('Initialization failed: ' + initialized.stderr)
with contextlib.closing(sqlite3.connect(work / 'app/clashweb.sqlite')) as db:
token = db.execute("SELECT value FROM kv WHERE key='setup_token'").fetchone()[0]
processes = []
try:
with open(work / 'core.log', 'w') as log:
processes.append(subprocess.Popen([str(root / 'bin/mihomo'), '-d', str(core), '-f', str(core / 'config.yaml')], stdout=log, stderr=log))
with open(work / 'app.log', 'w') as log:
processes.append(subprocess.Popen(command + ['--listen', '127.0.0.1:18080'], stdout=log, stderr=log))
wait_url('http://127.0.0.1:18080/api/v1/auth/status')
environment = dict(os.environ, CW_E2E_URL='http://127.0.0.1:18080', CW_E2E_SETUP_TOKEN=token, CW_E2E_SUBSCRIPTION_URL=f'http://127.0.0.1:{fixture.server_port}/subscription')
result = subprocess.run(['npm', 'run', 'test:e2e'], cwd=root / 'web', env=environment)
if result.returncode:
# This harness contains only credential-free local proxy fixtures.
print('Local fixture core log:', (work / 'core.log').read_text()[-8000:])
raise SystemExit(result.returncode)
finally:
for process in reversed(processes):
process.terminate()
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
fixture.shutdown()