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

+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Package already-built Linux amd64 resources; never include runtime state."""
import argparse
import datetime
import hashlib
import json
import subprocess
import tarfile
import tempfile
from pathlib import Path
import shutil
root = Path(__file__).resolve().parent.parent
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output-dir', type=Path, default=root)
args = parser.parse_args()
args.output_dir.mkdir(parents=True, exist_ok=True)
(root / '.cache').mkdir(exist_ok=True)
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
for name in ('clashweb', 'mihomo'):
header = (root / 'bin' / name).read_bytes()[:64]
if header[:5] != b'\x7fELF\x02' or int.from_bytes(header[18:20], 'little') != 62:
raise SystemExit(f'bin/{name} must be a Linux amd64 ELF binary')
update_readme = '''# Clash Web 管理程序更新(Linux amd64)
本包更新 Clash Web 管理服务及其嵌入的 Web UI。
无需更新 Mihomo 或规则,也不修改数据库、管理员密码、订阅和 VM 网络设置。
在已部署的目标 VM 上解压并进入本目录,然后执行:
```bash
sha256sum -c SHA256SUMS
sudo install -m 0755 -o root -g root /usr/local/bin/clashweb /usr/local/bin/clashweb.previous
sudo install -m 0755 -o root -g root bin/clashweb /usr/local/bin/clashweb.next
sudo mv /usr/local/bin/clashweb.next /usr/local/bin/clashweb
sudo systemctl restart clashweb
sudo systemctl status clashweb --no-pager
```
重启后刷新浏览器,确认管理服务正常。独立 Mihomo 内核继续运行;若页面仍显示旧内容,可用 Ctrl+Shift+R 刷新。
需要回退时执行:
```bash
sudo install -m 0755 -o root -g root /usr/local/bin/clashweb.previous /usr/local/bin/clashweb.rollback
sudo mv /usr/local/bin/clashweb.rollback /usr/local/bin/clashweb
sudo systemctl restart clashweb
```
测试只检查 HTTPS 主页,不承诺流媒体解锁、AI 账号或镜像拉取。
完整说明见 docs/upgrading.md。
'''
deploy_readme = '''# Clash Web Linux amd64 部署包
包含静态 Go 管理程序与 Web UI、Mihomo v1.19.32、已有大陆规则、安装脚本和文档。
在正式 PVE VM 上部署;不包含任何订阅、管理员密码或开发机运行数据。
解压后进入目录,先运行 `sha256sum -c SHA256SUMS`。
首次安装见 docs/deployment.md;Ubuntu/Debian 需要 sudo、python3、iproute2、nftables、ca-certificates。
运行 `sudo scripts/install.sh --target-vm --bind VM_IP:8080`。
VM 固定 IP、转发、防火墙与客户端网关/DNS 按部署文档配置,VM 不运行 DHCP。
已经部署的 VM 推荐使用 clashweb-update-linux-amd64.tar.gz,只替换管理程序。
不要用包内空数据覆盖 /var/lib/clashweb 或 /var/lib/clashweb-core。
'''
scripts = ['install.sh', 'clashweb-control', 'network-vm.py', 'fetch-mihomo.py',
'fetch-rules.py', 'update-rules.sh', 'diagnose-subscription.py']
with tempfile.TemporaryDirectory(prefix='package-', dir=root / '.cache') as temp:
for name, full in [('clashweb-linux-amd64', True), ('clashweb-update-linux-amd64', False)]:
stage = Path(temp) / name
files = ['bin/clashweb']
if full:
files += ['bin/mihomo', 'data/mihomo/rules/cn-domain.mrs', 'data/mihomo/rules/cn-ip.mrs']
files += ['scripts/' + script for script in scripts]
files += [p.relative_to(root).as_posix() for p in (root / 'deploy').glob('*.service')]
files += ['README.md']
files += ['docs/' + document for document in ['api.md', 'development.md', 'deployment.md', 'upgrading.md', 'third-party.md']]
else:
files += ['docs/upgrading.md']
for relative in files:
destination = stage / relative
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(root / relative, destination)
(stage / 'README-DEPLOY.md' if full else stage / 'README-UPDATE.md').write_text(deploy_readme if full else update_readme)
(stage / 'BUILD-INFO.json').write_text(json.dumps({
'platform': 'linux/amd64', 'goamd64': 'v1', 'cgoEnabled': False,
'createdAt': datetime.datetime.now(datetime.timezone.utc).isoformat(),
'clashwebSha256': digest(root / 'bin/clashweb'),
'mihomoVersion': 'v1.19.32' if full else None,
}, indent=2) + '\n')
payload = sorted(p for p in stage.rglob('*') if p.is_file())
(stage / 'SHA256SUMS').write_text(''.join(f'{digest(p)} {p.relative_to(stage).as_posix()}\n' for p in payload))
archive = args.output_dir / f'{name}.tar.gz'
with tarfile.open(archive, 'w:gz') as tar:
for p in sorted(stage.rglob('*')):
if p.is_file():
tar.add(p, arcname=name + '/' + p.relative_to(stage).as_posix(), recursive=False)
archive.with_suffix(archive.suffix + '.sha256').write_text(f'{digest(archive)} {archive.name}\n')
# Verify the actual archive, not just its staging directory.
with tarfile.open(archive) as tar:
extracted = Path(temp) / (name + '-verify')
tar.extractall(extracted, filter='data')
checked = subprocess.run(['sha256sum', '-c', 'SHA256SUMS'], cwd=extracted / name, capture_output=True)
if checked.returncode:
raise SystemExit('Package payload checksum failed')
if full:
clean = Path(temp) / 'clean-init'
initialized = subprocess.run([str(extracted / name / 'bin/clashweb'), '--init',
'--data', str(clean / 'app'), '--core-dir', str(extracted / name / 'data/mihomo'),
'--mihomo', str(extracted / name / 'bin/mihomo')], capture_output=True)
if initialized.returncode:
raise SystemExit('Clean package initialization/core validation failed')
print(f'{archive}: {archive.stat().st_size:,} bytes, payload checksums verified')