Add prebuilt frontend fetch script. Closes #110.

This commit is contained in:
jkingsman
2026-03-24 16:42:49 -07:00
parent caf4bf4eff
commit 4ead2ffcde
2 changed files with 108 additions and 2 deletions

View File

@@ -41,8 +41,6 @@ If you plan to contribute, read [CONTRIBUTING.md](CONTRIBUTING.md).
- [UV](https://astral.sh/uv) package manager: `curl -LsSf https://astral.sh/uv/install.sh | sh`
- MeshCore radio connected via USB serial, TCP, or BLE
If you are on a low-resource system and do not want to build the frontend locally, download the release zip named `remoteterm-prebuilt-frontend-vX.X.X-<short hash>.zip`. That bundle includes `frontend/prebuilt`, so you can run the app without doing a frontend build from source.
<details>
<summary>Finding your serial port</summary>
@@ -111,6 +109,8 @@ uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
The release bundle includes `frontend/prebuilt`, so it does not require a local frontend build.
Alternatively, if you have already cloned the repo, you can fetch just the prebuilt frontend into your working tree without downloading the full release zip via `python3 scripts/fetch_prebuilt_frontend.py`.
## Path 2: Docker
> **Warning:** Docker has had reports intermittent issues with serial event subscriptions. The native method above is more reliable.

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""
fetch_prebuilt_frontend.py
Downloads the latest prebuilt frontend artifact from the GitHub releases page
and installs it into frontend/prebuilt/ so the backend can serve it directly.
No GitHub CLI or authentication required — uses only the public releases API
and browser_download_url. Requires only the Python standard library.
"""
import json
import shutil
import sys
import urllib.request
import zipfile
from pathlib import Path
REPO = "jkingsman/Remote-Terminal-for-MeshCore"
API_URL = f"https://api.github.com/repos/{REPO}/releases/latest"
PREBUILT_PREFIX = "Remote-Terminal-for-MeshCore/frontend/prebuilt/"
SCRIPT_DIR = Path(__file__).resolve().parent
PREBUILT_DIR = SCRIPT_DIR.parent / "frontend" / "prebuilt"
def fetch_json(url: str) -> dict:
req = urllib.request.Request(url, headers={"Accept": "application/vnd.github+json"})
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())
def find_prebuilt_asset(release: dict) -> tuple[str, str, str]:
"""Return (tag_name, asset_name, download_url) for the prebuilt zip."""
tag = release.get("tag_name", "")
for asset in release.get("assets", []):
name = asset.get("name", "")
if name.startswith("remoteterm-prebuilt-frontend-") and name.endswith(".zip"):
return tag, name, asset["browser_download_url"]
raise SystemExit(
f"No prebuilt frontend artifact found in the latest release.\n"
f"Check https://github.com/{REPO}/releases for available assets."
)
def download(url: str, dest: Path) -> None:
with urllib.request.urlopen(url) as resp, open(dest, "wb") as f:
shutil.copyfileobj(resp, f)
def extract_prebuilt(zip_path: Path, dest: Path) -> int:
with zipfile.ZipFile(zip_path) as zf:
members = [m for m in zf.namelist() if m.startswith(PREBUILT_PREFIX)]
if not members:
raise SystemExit(f"'{PREBUILT_PREFIX}' not found inside zip.")
if dest.exists():
shutil.rmtree(dest)
dest.mkdir(parents=True)
for member in members:
rel = member[len(PREBUILT_PREFIX):]
if not rel:
continue
target = dest / rel
if member.endswith("/"):
target.mkdir(parents=True, exist_ok=True)
else:
target.parent.mkdir(parents=True, exist_ok=True)
with zf.open(member) as src, open(target, "wb") as dst:
shutil.copyfileobj(src, dst)
return len(members)
def main() -> None:
print("Fetching latest release info...")
release = fetch_json(API_URL)
tag, asset_name, download_url = find_prebuilt_asset(release)
print(f" Release : {tag}")
print(f" Asset : {asset_name}")
print()
zip_path = PREBUILT_DIR.parent / asset_name
try:
print(f"Downloading {asset_name}...")
download(download_url, zip_path)
print("Extracting prebuilt frontend...")
count = extract_prebuilt(zip_path, PREBUILT_DIR)
print(f"Extracted {count} entries.")
finally:
zip_path.unlink(missing_ok=True)
print()
print(f"Done! Prebuilt frontend ({tag}) installed to frontend/prebuilt/")
print("Start the server with:")
print(" uv run uvicorn app.main:app --host 0.0.0.0 --port 8000")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nAborted.", file=sys.stderr)
sys.exit(1)