Android build v1.1.26

This commit is contained in:
Warren H
2026-07-05 23:31:04 -04:00
parent 62dc51b42a
commit de2f86e101
6 changed files with 149 additions and 41 deletions
Binary file not shown.
+1 -1
View File
@@ -7,7 +7,7 @@ android {
applicationId "com.warren.iwanttoheal"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 105
versionCode 106
versionName "1.1.26"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
+6
View File
@@ -96,6 +96,12 @@ curl -sS -X POST "$GITEA_URL/api/v1/repos/$GITEA_OWNER/$GITEA_REPO/releases/$REL
-F "attachment=@$APK"
```
If the CLI release fails after creating the Gitea release but before uploading the APK asset, retry only the release upload:
```sh
npm run release:cli -- --release-only 1.1.26
```
## Step 5: Update TrueNAS
```sh
Binary file not shown.
+44 -15
View File
@@ -5,6 +5,7 @@ import json
import os
import queue
import re
import shutil
import subprocess
import sys
import threading
@@ -153,6 +154,48 @@ def gitea_request(
except urllib.error.HTTPError as exc:
data = exc.read()
return exc.code, data
except urllib.error.URLError as exc:
raise ReleaseError(f"{method} {url} failed: {exc.reason}") from exc
def gitea_upload_asset(config: ReleaseConfig, path: str, apk_path: Path) -> tuple[int, bytes]:
curl = shutil.which("curl")
if curl is None:
raise ReleaseError("curl is required for Gitea asset uploads")
url = config.gitea_url.rstrip("/") + path
proc = subprocess.run(
[
curl,
"--silent",
"--show-error",
"--retry",
"3",
"--retry-all-errors",
"--retry-delay",
"2",
"--request",
"POST",
url,
"--header",
f"Authorization: token {config.token}",
"--form",
f"attachment=@{apk_path}",
"--write-out",
"\n%{http_code}",
],
check=False,
capture_output=True,
)
if proc.returncode != 0:
detail = (proc.stderr or proc.stdout).decode("utf-8", "replace").strip()
raise ReleaseError(f"Gitea asset upload failed: curl exited {proc.returncode}: {detail}")
try:
body, status_text = proc.stdout.rsplit(b"\n", 1)
return int(status_text), body
except ValueError as exc:
output = proc.stdout.decode("utf-8", "replace")
raise ReleaseError(f"Gitea asset upload returned malformed curl output: {output}") from exc
def parse_json_response(status: int, data: bytes, action: str) -> dict:
@@ -211,26 +254,12 @@ def upload_release_asset(config: ReleaseConfig, log: Callable[[str], None]) -> N
raise ReleaseError(f"APK not found for release upload: {apk_path}")
release_id = find_or_create_release(config, log)
boundary = f"----iwanttoheal{int(time.time() * 1000)}"
header = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="attachment"; filename="{apk_path.name}"\r\n'
"Content-Type: application/vnd.android.package-archive\r\n\r\n"
).encode("utf-8")
footer = f"\r\n--{boundary}--\r\n".encode("utf-8")
body = header + apk_path.read_bytes() + footer
asset_name = urllib.parse.quote(apk_path.name)
path = (
f"/api/v1/repos/{config.gitea_owner}/{config.gitea_repo}"
f"/releases/{release_id}/assets?name={asset_name}"
)
status, data = gitea_request(
config,
"POST",
path,
body=body,
content_type=f"multipart/form-data; boundary={boundary}",
)
status, data = gitea_upload_asset(config, path, apk_path)
if status == 409:
raise ReleaseError(f"Release asset already exists: {apk_path.name}")
parse_json_response(status, data, "Upload release asset")
+98 -25
View File
@@ -68,9 +68,16 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
action="store_true",
help="Skip the RESET confirmation when used with --reset-player-data.",
)
parser.add_argument(
"--release-only",
metavar="VERSION",
help="Create or reuse the Gitea release and upload an existing IWantToHeal-Thor-vVERSION.apk, without rebuilding.",
)
args = parser.parse_args(argv)
if args.yes_reset and not args.reset_player_data:
parser.error("--yes-reset requires --reset-player-data")
if args.release_only and args.reset_player_data:
parser.error("--release-only cannot be combined with --reset-player-data")
return args
@@ -131,44 +138,90 @@ def request_json(method: str, path: str, token: str, data: dict | None = None) -
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", "replace")
raise SystemExit(f"Gitea {method} failed ({exc.code}): {detail}") from exc
except urllib.error.URLError as exc:
raise SystemExit(f"Gitea {method} failed: {exc.reason}") from exc
def upload_asset(path: str, token: str, apk: Path) -> dict:
boundary = "----iwanttoheal-release-boundary"
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="attachment"; filename="{apk.name}"\r\n'
"Content-Type: application/vnd.android.package-archive\r\n\r\n"
).encode("utf-8")
body += apk.read_bytes()
body += f"\r\n--{boundary}--\r\n".encode("utf-8")
headers = {
"Authorization": f"token {token}",
"Content-Type": f"multipart/form-data; boundary={boundary}",
}
req = urllib.request.Request(GITEA_URL + path, data=body, headers=headers, method="POST")
url = GITEA_URL + path
curl = shutil.which("curl")
if curl is None:
raise SystemExit("curl is required for Gitea asset uploads")
proc = subprocess.run(
[
curl,
"--silent",
"--show-error",
"--retry",
"3",
"--retry-all-errors",
"--retry-delay",
"2",
"--request",
"POST",
url,
"--header",
f"Authorization: token {token}",
"--form",
f"attachment=@{apk}",
"--write-out",
"\n%{http_code}",
],
check=False,
capture_output=True,
text=True,
)
if proc.returncode != 0:
detail = proc.stderr.strip() or proc.stdout.strip()
raise SystemExit(f"Gitea asset upload failed: curl exited {proc.returncode}: {detail}")
try:
body, status_text = proc.stdout.rsplit("\n", 1)
status = int(status_text)
except ValueError as exc:
raise SystemExit(f"Gitea asset upload returned malformed curl output: {proc.stdout}") from exc
if status >= 400:
raise SystemExit(f"Gitea asset upload failed ({status}): {body}")
try:
parsed = json.loads(body or "{}")
except json.JSONDecodeError as exc:
raise SystemExit(f"Gitea asset upload returned invalid JSON: {body}") from exc
if not isinstance(parsed, dict):
raise SystemExit(f"Gitea asset upload returned unexpected JSON: {parsed}")
return parsed
def find_or_create_release(version: str, token: str) -> int:
repo_path = f"/api/v1/repos/{GITEA_OWNER}/{GITEA_REPO}"
tag = f"v{version}"
tag_path = repo_path + "/releases/tags/" + urllib.parse.quote(tag, safe="")
req = urllib.request.Request(GITEA_URL + tag_path, headers={"Authorization": f"token {token}"}, method="GET")
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode("utf-8"))
release = json.loads(resp.read().decode("utf-8"))
release_id = release.get("id")
if not release_id:
raise SystemExit(f"Gitea release missing id: {release}")
print(f"Gitea release exists: {tag} (id {release_id})")
return int(release_id)
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", "replace")
raise SystemExit(f"Gitea asset upload failed ({exc.code}): {detail}") from exc
if exc.code != 404:
detail = exc.read().decode("utf-8", "replace")
raise SystemExit(f"Gitea release lookup failed ({exc.code}): {detail}") from exc
except urllib.error.URLError as exc:
raise SystemExit(f"Gitea release lookup failed: {exc.reason}") from exc
def create_gitea_release(version: str, apk: Path) -> None:
token = GITEA_TOKEN.strip()
if not token or token == "PASTE_YOUR_GITEA_TOKEN_HERE":
raise SystemExit("Set GITEA_TOKEN near top of scripts/release_game_cli.py")
repo_path = f"/api/v1/repos/{GITEA_OWNER}/{GITEA_REPO}"
release = request_json(
"POST",
repo_path + "/releases",
token,
{
"tag_name": f"v{version}",
"tag_name": tag,
"target_commitish": BRANCH,
"name": f"v{version}",
"name": tag,
"body": f"I Want to Heal Android build v{version}",
"draft": False,
"prerelease": False,
@@ -177,7 +230,17 @@ def create_gitea_release(version: str, apk: Path) -> None:
release_id = release.get("id")
if not release_id:
raise SystemExit(f"Gitea release missing id: {release}")
print(f"Gitea release created: {tag} (id {release_id})")
return int(release_id)
def create_gitea_release(version: str, apk: Path) -> None:
token = GITEA_TOKEN.strip()
if not token or token == "PASTE_YOUR_GITEA_TOKEN_HERE":
raise SystemExit("Set GITEA_TOKEN near top of scripts/release_game_cli.py")
repo_path = f"/api/v1/repos/{GITEA_OWNER}/{GITEA_REPO}"
release_id = find_or_create_release(version, token)
asset_name = urllib.parse.quote(apk.name)
upload_asset(f"{repo_path}/releases/{release_id}/assets?name={asset_name}", token, apk)
print(f"Gitea release uploaded: v{version}")
@@ -289,6 +352,16 @@ def reset_player_data(database_path: Path, *, assume_yes: bool) -> None:
def main(argv: list[str] | None = None) -> int:
args = parse_args(sys.argv[1:] if argv is None else argv)
os.chdir(REPO_ROOT)
if args.release_only:
version = args.release_only.strip()
if not re.fullmatch(r"\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?", version):
raise SystemExit("Version must look like 1.1.2")
apk = REPO_ROOT / f"IWantToHeal-Thor-v{version}.apk"
if not apk.exists():
raise SystemExit(f"APK not found: {apk}")
create_gitea_release(version, apk)
return 0
version = prompt_version()
apk = build_apk(version)
commit_and_push(version)