diff --git a/server/main.py b/server/main.py index 7c133d1..910615b 100644 --- a/server/main.py +++ b/server/main.py @@ -1310,6 +1310,32 @@ async def get_pack_manifest(pack_name: str, request: Request, current_user: dict ) +@app.get("/pack/{pack_name}/zip") +async def download_pack_zip(pack_name: str, request: Request, current_user: dict = Depends(get_current_user)): + """Download whole pack as zip for manual installation (requires pass)""" + if not has_permission(current_user["role"], Permissions.DOWNLOAD_PACK): + raise HTTPException(403, "Requires active pass") + if is_pack_disabled(pack_name): + raise HTTPException(403, "Pack is disabled") + pack_path = PACKS_DIR / pack_name + if not pack_path.exists() or not pack_path.is_dir(): + raise HTTPException(404, "Pack not found") + import tempfile, zipfile + tmp = Path(tempfile.gettempdir()) / f"{pack_name}.zip" + # re-use if fresh (<5 min) + if tmp.exists() and (datetime.utcnow().timestamp() - tmp.stat().st_mtime) < 300: + return await send_file_async(tmp, request, content_type="application/zip", cache=False) + # create zip + def _zip(): + with zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as zf: + for f in pack_path.rglob("*"): + if f.is_file(): + zf.write(f, f.relative_to(pack_path)) + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, _zip) + return await send_file_async(tmp, request, content_type="application/zip", cache=False) + + @app.get("/pack/{pack_name}/file/{file_path:path}") async def get_pack_file(pack_name: str, file_path: str, request: Request, current_user: dict = Depends(get_current_user), preset: Optional[str] = None): """Get a file from a pack. If preset is specified, try preset dir first, fall back to base.""" @@ -1364,32 +1390,6 @@ async def get_pack_file(pack_name: str, file_path: str, request: Request, curren return await send_file_async(full_path, request, cache=True) -@app.get("/pack/{pack_name}/zip") -async def download_pack_zip(pack_name: str, request: Request, current_user: dict = Depends(get_current_user)): - """Download whole pack as zip for manual installation (requires pass)""" - if not has_permission(current_user["role"], Permissions.DOWNLOAD_PACK): - raise HTTPException(403, "Requires active pass") - if is_pack_disabled(pack_name): - raise HTTPException(403, "Pack is disabled") - pack_path = PACKS_DIR / pack_name - if not pack_path.exists() or not pack_path.is_dir(): - raise HTTPException(404, "Pack not found") - import tempfile, zipfile - tmp = Path(tempfile.gettempdir()) / f"{pack_name}.zip" - # re-use if fresh (<5 min) - if tmp.exists() and (datetime.utcnow().timestamp() - tmp.stat().st_mtime) < 300: - return await send_file_async(tmp, request, content_type="application/zip", cache=False) - # create zip - def _zip(): - with zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as zf: - for f in pack_path.rglob("*"): - if f.is_file(): - zf.write(f, f.relative_to(pack_path)) - loop = asyncio.get_running_loop() - await loop.run_in_executor(None, _zip) - return await send_file_async(tmp, request, content_type="application/zip", cache=False) - - # ====================== ЭНДПОИНТЫ ДЛЯ ЛАУНЧЕРА ====================== def get_current_launcher_version() -> str: diff --git a/server/site/index.html b/server/site/index.html index 8cd48dd..fb68d37 100644 --- a/server/site/index.html +++ b/server/site/index.html @@ -447,11 +447,12 @@

Zern Prologue — ручная установка

-

1.21.1 NeoForge · Create: Aeronautics · без лаунчера

+

1.21.1 NeoForge 21.1.228 · Create: Aeronautics · без лаунчера

v4 · 1.8 ГБ

Скачай архив модпака и распакуй вручную. Подходит если лаунчер не качает или хочешь поставить на Prism / MultiMC / ATLauncher.

+
⚠️ Важно: используй только NeoForge 21.1.228 для 1.21.1 — на новейшей (21.5+) сборка крашится, мир не загрузится.
%USERPROFILE%\.zernmc\instances\ZernPrologue · ~/.zernmc/instances/ZernPrologue на Linux