"""把构建好的 APK 发布成 OTA 版本:算 sha256、写 latest.json。 用法(在仓库根目录): python backend/scripts/publish_apk.py android/app/build/outputs/apk/release/app-release.apk \ --version-code 2 --version-name 0.2.0 --notes "第一版安装包:知识库支持分类与附件" 发布目录默认 `backend/static/apk/`,可用 `--dir` 或环境变量 `APK_DIR` 改。 这个目录是产物、不入库;部署到服务器时把 `latest.json` 和 apk 一起 scp 过去即可。 """ from __future__ import annotations import argparse import hashlib import json import os import shutil import sys from datetime import datetime, timezone from pathlib import Path REPO = Path(__file__).resolve().parents[2] DEFAULT_DIR = REPO / "backend" / "static" / "apk" def sha256_of(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="发布安卓安装包(OTA)") parser.add_argument("apk", type=Path, help="构建产物路径,如 app-release.apk") parser.add_argument( "--version-code", type=int, required=True, help="与 build.gradle 的 versionCode 一致" ) parser.add_argument("--version-name", required=True, help="展示用版本号,如 0.2.0") parser.add_argument("--notes", default="", help="更新说明,App 里会原样显示") parser.add_argument("--min-sdk", type=int, default=26) parser.add_argument( "--dir", type=Path, default=Path(os.environ.get("APK_DIR") or DEFAULT_DIR), help="发布目录(默认 backend/static/apk)", ) args = parser.parse_args(argv) if not args.apk.is_file(): print(f"APK 不存在:{args.apk}", file=sys.stderr) return 1 target_dir = args.dir target_dir.mkdir(parents=True, exist_ok=True) name = f"dadaotask-{args.version_name}.apk" target = target_dir / name shutil.copyfile(args.apk, target) manifest = { "platform": "android", "version_code": args.version_code, "version_name": args.version_name, "notes": args.notes.strip(), "file": name, "size": target.stat().st_size, "sha256": sha256_of(target), "min_sdk": args.min_sdk, "published_at": datetime.now(timezone.utc).isoformat(timespec="seconds"), } (target_dir / "latest.json").write_text( json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8" ) print(f"已发布 {name}") print(f" versionCode={manifest['version_code']} versionName={manifest['version_name']}") print(f" size={manifest['size'] / 1024 / 1024:.1f}MB sha256={manifest['sha256'][:16]}…") print(f" 清单:{target_dir / 'latest.json'}") print("提示:服务器上把这两个文件放到 /backend/static/apk/ 即可,无需重启服务。") return 0 if __name__ == "__main__": raise SystemExit(main())