Initialize Clash Web source and deployment documentation
This commit is contained in:
commit
8e0fa00a73
66 files changed
+8532
No files matched your search
@@ -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()
|
||||
Reference in new issue
Block a user