diff --git a/docs/development-log.md b/docs/development-log.md index 80b3e3f..287f905 100644 --- a/docs/development-log.md +++ b/docs/development-log.md @@ -1542,3 +1542,13 @@ This is the append-only implementation and handoff record shared by Codex and Cl `apk` branch has the same SHA-256 as the one built here. - Caution: published without having been seen on a device, at the product owner's decision. The APK is the debug build signed with this PC's key. + +## 2026-10-12 (continued) — TASK-070 follow-up: the old site is out of the repository + +- Request: the product owner will not use the old GitHub site any more and asked for the version + check to live on Gitea too. +- What changed: the version check already reads Gitea since 0.2.2, so nothing in the app changes. + `tools/publish-download.py`, the publisher for the old page, is removed; TASK-066 is marked + superseded. Publishing is the three steps in `tasks/070-update-from-gitea.md`. +- Caution: the page at `choimy0204.github.io/ubgitea/` still exists (password, 0.2.0). Copies of + the app older than 0.2.2 keep reading it and see "up to date". diff --git a/tasks/066-download-page-and-update-check.md b/tasks/066-download-page-and-update-check.md index 9626b07..9f24e8b 100644 --- a/tasks/066-download-page-and-update-check.md +++ b/tasks/066-download-page-and-update-check.md @@ -1,6 +1,6 @@ # TASK-066: download page and in-app update check -Status: done +Status: done; superseded by TASK-070 (the page and its publishing script are no longer used) Owner: Claude Reviewer: human product owner diff --git a/tasks/070-update-from-gitea.md b/tasks/070-update-from-gitea.md index cb193dd..e9b362b 100644 --- a/tasks/070-update-from-gitea.md +++ b/tasks/070-update-from-gitea.md @@ -19,14 +19,19 @@ owner no longer uses that page (2026-10-12) and asked for the app to use the Git ## Scope - Included: the two addresses, the wording, `version.json` on the `apk` branch. -- Excluded: `tools/publish-download.py` and the `choimy0204.github.io/ubgitea/` page. Both are left - as they are and are no longer part of publishing. +- Follow-up (product owner, 2026-10-12: the old site is not used any more): + `tools/publish-download.py`, which built the old page, is removed. Nothing in the repository + points at the old site now. +- Excluded: the `choimy0204.github.io/ubgitea/` page itself. It is in another repository and is + left as it is. ## Writable paths - `app/src/main/java/com/ubisam/ubgitea/core/update/AppUpdate.kt` - `app/src/main/java/com/ubisam/ubgitea/MainActivity.kt` - `app/src/main/res/values/strings_update.xml` +- `tools/publish-download.py` (removed) +- `tasks/066-download-page-and-update-check.md` - `tasks/070-update-from-gitea.md` - `docs/development-log.md` diff --git a/tools/publish-download.py b/tools/publish-download.py deleted file mode 100644 index 3585e26..0000000 --- a/tools/publish-download.py +++ /dev/null @@ -1,187 +0,0 @@ -"""Builds the password-protected download page for one APK (TASK-066). - -The APK is encrypted with AES-256-GCM under a key derived from the password with PBKDF2-SHA256, -so the files can sit on a public static host: the page asks for the password and decrypts in the -browser with WebCrypto. version.json beside it is plain; the app reads it to know a newer build -is out. - - python tools/publish-download.py --notes "what changed" - -The password is read from UBGITEA_DOWNLOAD_PASSWORD, or asked for. It is never written anywhere. -versionCode and versionName are read from app/build.gradle.kts. -""" -import argparse -import base64 -import getpass -import hashlib -import html -import json -import os -import re -import sys -from pathlib import Path - -from cryptography.hazmat.primitives.ciphers.aead import AESGCM - -ITERATIONS = 310_000 -ENCRYPTED_NAME = "ubgitea.apk.enc" -CHECK_TEXT = b"UBGitea" - - -def gradle_version(root: Path): - text = (root / "app" / "build.gradle.kts").read_text(encoding="utf-8") - code = re.search(r"versionCode\s*=\s*(\d+)", text) - name = re.search(r'versionName\s*=\s*"([^"]+)"', text) - if not code or not name: - sys.exit("versionCode / versionName not found in app/build.gradle.kts") - return int(code.group(1)), name.group(1) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("apk", type=Path) - parser.add_argument("out", type=Path) - parser.add_argument("--notes", default="") - args = parser.parse_args() - - password = os.environ.get("UBGITEA_DOWNLOAD_PASSWORD") or getpass.getpass("Download password: ") - if not password: - sys.exit("A password is required.") - code, name = gradle_version(Path(__file__).resolve().parent.parent) - - data = args.apk.read_bytes() - salt = os.urandom(16) - iv = os.urandom(12) - key = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, ITERATIONS, 32) - sealed = AESGCM(key).encrypt(iv, data, None) - # A few sealed bytes the page opens first: a wrong password is refused before the APK is - # fetched at all, so nothing is downloaded with it. - check_iv = os.urandom(12) - check = AESGCM(key).encrypt(check_iv, CHECK_TEXT, None) - - args.out.mkdir(parents=True, exist_ok=True) - (args.out / ENCRYPTED_NAME).write_bytes(sealed) - release = {"versionCode": code, "versionName": name, "notes": args.notes} - (args.out / "version.json").write_text(json.dumps(release, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - meta = { - "salt": base64.b64encode(salt).decode(), - "iv": base64.b64encode(iv).decode(), - "iterations": ITERATIONS, - "check": base64.b64encode(check).decode(), - "checkIv": base64.b64encode(check_iv).decode(), - "file": ENCRYPTED_NAME, - "fileName": f"UBGitea-{name}.apk", - "size": len(data), - } - page = PAGE.replace("{{META}}", json.dumps(meta)) \ - .replace("{{VERSION}}", html.escape(name)) \ - .replace("{{NOTES}}", html.escape(args.notes)) \ - .replace("{{SIZE}}", f"{len(data) / 1024 / 1024:.1f} MB") - (args.out / "index.html").write_text(page, encoding="utf-8") - print(f"UBGitea {name} (versionCode {code}) -> {args.out}") - - -PAGE = """ - - - - - -UBGitea 다운로드 - - - -
-

UBGitea

-
버전 {{VERSION}} · {{SIZE}}
-
{{NOTES}}
-
- - - -
-

-
    -
  1. 받은 APK 파일을 엽니다.
  2. -
  3. "출처를 알 수 없는 앱" 설치를 이 브라우저에 허용합니다.
  4. -
  5. 설치(업데이트)를 누릅니다. 로그인과 설정은 그대로 남습니다.
  6. -
-
- - - -""" - -if __name__ == "__main__": - main()