site: dawn.gg-style landing at root + port /skin/* for TG bot
- serve new launcher landing site (zern.cc root -> launcher server 1582)
- static assets mounted at /css /js /img
- port GET/HEAD /skin/{filename} from old site with identical headers
- exempt /skin/* + site assets from rate-limit and cache middleware
- Caddy: root domains -> 1582, old site preserved at legacy.zernmc.*
This commit is contained in:
+58
-4
@@ -12,7 +12,8 @@ import json
|
||||
import structlog
|
||||
from cachetools import TTLCache
|
||||
from fastapi import Depends, FastAPI, HTTPException, Request, Response
|
||||
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
||||
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, StreamingResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
|
||||
|
||||
# Disable httpx debug logging
|
||||
@@ -46,6 +47,12 @@ BUILDS_DIR = Path("builds")
|
||||
VERSIONS_DIR = BUILDS_DIR / "versions"
|
||||
WHITELIST_DIR = Path(__file__).parent / "whitelist"
|
||||
|
||||
# Website (landing) static files
|
||||
SITE_DIR = Path(__file__).parent / "site"
|
||||
|
||||
# SkinRestorer skins (shared with TG bot)
|
||||
SKINS_DIR = Path("/root/python/site/skins")
|
||||
|
||||
# Mirror configuration
|
||||
LAUNCHER_MIRRORS = {
|
||||
"main": "https://api.zern.cc", # primary
|
||||
@@ -838,7 +845,7 @@ class CacheControlMiddleware:
|
||||
path = scope.get("path", "")
|
||||
|
||||
# Skip caching for dynamic endpoints
|
||||
skip_cache = any(p in path for p in ["/api/", "/auth/", "/login", "/launch", "/install"])
|
||||
skip_cache = any(p in path for p in ["/api/", "/auth/", "/login", "/launch", "/install", "/skin/"])
|
||||
if skip_cache:
|
||||
await self.app(scope, receive, send)
|
||||
return
|
||||
@@ -953,6 +960,12 @@ app.include_router(admin_router)
|
||||
app.include_router(friends_router)
|
||||
app.include_router(playtime_router)
|
||||
|
||||
# Static assets for the landing site
|
||||
for sub in ("css", "js", "img"):
|
||||
mount_dir = SITE_DIR / sub
|
||||
mount_dir.mkdir(parents=True, exist_ok=True)
|
||||
app.mount(f"/{sub}", StaticFiles(directory=mount_dir), name=f"site-{sub}")
|
||||
|
||||
|
||||
# Monkey patch to catch invalid HTTP requests
|
||||
original_data_received = HttpToolsProtocol.data_received
|
||||
@@ -990,8 +1003,10 @@ HttpToolsProtocol.data_received = patched_data_received
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""Root endpoint"""
|
||||
logger.info("Root endpoint accessed")
|
||||
"""Root endpoint - serves the launcher landing site"""
|
||||
index_path = SITE_DIR / "index.html"
|
||||
if index_path.exists():
|
||||
return HTMLResponse(index_path.read_text(encoding="utf-8"))
|
||||
return {
|
||||
"status": "ok",
|
||||
"message": "ZernMC Launcher Server is running",
|
||||
@@ -1000,6 +1015,45 @@ async def root():
|
||||
}
|
||||
|
||||
|
||||
@app.get("/skin/{filename}")
|
||||
@app.head("/skin/{filename}")
|
||||
async def get_skin(filename: str, request: Request):
|
||||
"""Serve SkinRestorer skins (compatibility with TG bot direct links)"""
|
||||
if not filename or ".." in filename or "/" in filename or "\\" in filename:
|
||||
raise HTTPException(status_code=404, detail="Скин не найден")
|
||||
|
||||
filepath = SKINS_DIR / filename
|
||||
|
||||
if not filepath.is_file():
|
||||
raise HTTPException(status_code=404, detail="Скин не найден")
|
||||
|
||||
try:
|
||||
content = filepath.read_bytes()
|
||||
size = len(content)
|
||||
|
||||
headers = {
|
||||
"Content-Length": str(size),
|
||||
"Content-Type": "image/png",
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Content-Encoding": "identity",
|
||||
}
|
||||
|
||||
if request.method == "HEAD":
|
||||
return Response(status_code=200, headers=headers)
|
||||
|
||||
return Response(
|
||||
content=content,
|
||||
media_type="image/png",
|
||||
headers=headers
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Skin read error", error=str(e))
|
||||
raise HTTPException(status_code=500, detail="Ошибка чтения скина")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
"""Health check endpoint"""
|
||||
|
||||
Reference in New Issue
Block a user