Files
clash-web/scripts/package.py
T

120 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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')