diff --git a/README.md b/README.md index 0781870..7838eba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- - logo + + logo

redViewer(rV)

tag @@ -17,15 +17,17 @@ 轻简风格的漫画阅读器。 +> [26/02/18] nyc.mn 旧域名已废弃,新域名后缀 xyz +

- - 主页 + + 主页 - - 体验 + + 体验 - 下载 + 下载

diff --git a/backend/api/__init__.py b/backend/api/__init__.py index 11a1392..9f9dc59 100644 --- a/backend/api/__init__.py +++ b/backend/api/__init__.py @@ -14,7 +14,7 @@ from core import lib_mgr from storage import StorageBackendFactory from api.routes.comic import index_router -from api.routes.root import root_router +from api.routes.root import root_router, api_config_router from utils.cbz_cache import close_cbz_cache staticFiles = None @@ -60,6 +60,7 @@ def register_router(app: FastAPI) -> None: if kemono_path and kemono_path.exists(): from api.routes.kemono import index_router as kemono_index_router app.include_router(kemono_index_router, prefix="", tags=['kemono']) + app.include_router(api_config_router, prefix="", tags=['api']) app.include_router(root_router, prefix="", tags=['root']) diff --git a/backend/api/routes/root.py b/backend/api/routes/root.py index e80410c..4dc4d25 100644 --- a/backend/api/routes/root.py +++ b/backend/api/routes/root.py @@ -3,8 +3,11 @@ """Root Router - 认证和配置管理 API""" import time +import httpx from functools import wraps +from urllib.parse import urlsplit from fastapi import APIRouter, HTTPException, Header +from fastapi.responses import JSONResponse from pydantic import BaseModel from typing import Optional @@ -12,6 +15,7 @@ from core.crypto import decrypt root_router = APIRouter(prefix='/root') +api_config_router = APIRouter(prefix='/api') # ===== 鉴权相关 ===== @@ -69,6 +73,12 @@ class WhitelistUpdate(BaseModel): whitelist: list[str] +class ApiConfigUpdate(BaseModel): + backendUrl: str | None = None + currentBackend: str | None = None + secret: str | None = None + + @root_router.get("/") async def root_status(): return {"status": "ok", "has_secret": is_auth_required()} @@ -130,4 +140,43 @@ async def update_whitelist(req: WhitelistUpdate, x_secret: Optional[str] = Heade if is_auth_required() and not verify_secret(x_secret or ''): raise HTTPException(401, "鉴权失败") backend.config.set('root_whitelist', req.whitelist) - return {"success": True} \ No newline at end of file + return {"success": True} + + +@api_config_router.get('/config') +async def get_api_config(): + return { + 'backendUrl': backend.config.get('frontend_backend_url'), + 'bgGif': None + } + + +@api_config_router.post('/config') +async def update_api_config(req: ApiConfigUpdate): + if not req.secret: + return JSONResponse({'error': '需要密钥'}, status_code=401) + if not is_auth_required(): + return JSONResponse({'error': '请先设置密钥'}, status_code=403) + if not verify_secret(req.secret): + return JSONResponse({'error': '密钥验证失败'}, status_code=401) + + target = (req.backendUrl or '').strip().rstrip('/') + if not target: + return JSONResponse({'error': '需要目标后端地址'}, status_code=400) + try: + parts = urlsplit(target) + except ValueError: + return JSONResponse({'error': '地址格式不正确'}, status_code=400) + if parts.scheme not in ('http', 'https') or not parts.netloc: + return JSONResponse({'error': '地址必须以 http:// 或 https:// 开头'}, status_code=400) + + try: + async with httpx.AsyncClient(timeout=5.0) as client: + r = await client.get(f'{target}/root/') + if r.status_code >= 400: + return JSONResponse({'error': '无法连接目标后端'}, status_code=502) + except httpx.HTTPError: + return JSONResponse({'error': '无法连接目标后端'}, status_code=502) + + backend.config.set('frontend_backend_url', target) + return {'success': True} diff --git a/deploy/tauri/Cargo.toml b/deploy/tauri/Cargo.toml index b05acca..51a131d 100644 --- a/deploy/tauri/Cargo.toml +++ b/deploy/tauri/Cargo.toml @@ -35,7 +35,7 @@ toml = "0.8" parking_lot = "0.12" # Tauri dependencies (src-tauri only) -tauri = { version = "2.9.5", features = ["tray-icon"] } +tauri = { version = "2.9.5", features = ["tray-icon", "macos-private-api"] } tauri-plugin-opener = "2" tauri-plugin-single-instance = "2" notify-rust = "4" diff --git a/deploy/tauri/lib/src/paths.rs b/deploy/tauri/lib/src/paths.rs index 6fd4e7f..2a26c26 100644 --- a/deploy/tauri/lib/src/paths.rs +++ b/deploy/tauri/lib/src/paths.rs @@ -43,12 +43,9 @@ pub fn resolve_uv() -> anyhow::Result { #[cfg(target_os = "macos")] { - // On macOS, uv is bundled in Contents/Resources alongside other resources - let uv_path = exe_dir - .parent() // Contents - .map(|p| p.join("Resources").join("uv")) - .ok_or_else(|| anyhow::anyhow!("cannot resolve macOS resources path"))?; - return Ok(uv_path); + // externalBin 打包后位于 Contents/MacOS/ 目录(与主程序同目录) + // 见: https://v2.tauri.app/distribute/macos-application-bundle + Ok(exe_dir.join("uv")) } #[cfg(target_os = "linux")] diff --git a/deploy/tauri/src-tauri/Cargo.toml b/deploy/tauri/src-tauri/Cargo.toml index c44d2f9..83858e6 100644 --- a/deploy/tauri/src-tauri/Cargo.toml +++ b/deploy/tauri/src-tauri/Cargo.toml @@ -15,7 +15,7 @@ tauri-build = { workspace = true } [dependencies] rv_lib = { path = "../lib", package = "lib" } -tauri = { workspace = true } +tauri = { workspace = true, features = ["tray-icon", "macos-private-api"] } tauri-plugin-opener = { workspace = true } tauri-plugin-single-instance = { workspace = true } notify-rust = { workspace = true } diff --git a/deploy/tauri/src-tauri/tauri.conf.json b/deploy/tauri/src-tauri/tauri.conf.json index 6e7e66c..a5f925e 100644 --- a/deploy/tauri/src-tauri/tauri.conf.json +++ b/deploy/tauri/src-tauri/tauri.conf.json @@ -10,6 +10,7 @@ "frontendDist": "../dist" }, "app": { + "macOSPrivateApi": true, "windows": [], "security": { "csp": null diff --git a/deploy/tauri/src-tauri/windows/hooks.nsh b/deploy/tauri/src-tauri/windows/hooks.nsh index 54a8362..850c705 100644 --- a/deploy/tauri/src-tauri/windows/hooks.nsh +++ b/deploy/tauri/src-tauri/windows/hooks.nsh @@ -197,6 +197,10 @@ FunctionEnd ; Handles config saving and dependency installation ;------------------------------------------------------------------------------ !macro NSIS_HOOK_POSTINSTALL + ; Add firewall rules for ports 8080 and 12345 + DetailPrint "Adding firewall rules..." + nsExec::ExecToLog 'netsh advfirewall firewall add rule name="rV" dir=in action=allow protocol=TCP localport=8080,12345' + ; Create config directory CreateDirectory "${CONFIG_DIR}" @@ -265,6 +269,9 @@ FunctionEnd ; POSTUNINSTALL Hook ;------------------------------------------------------------------------------ !macro NSIS_HOOK_POSTUNINSTALL + ; Remove firewall rule + nsExec::ExecToLog 'netsh advfirewall firewall delete rule name="rV"' + ; Clean up runtime-generated files only on full uninstall (not upgrade) ${If} $UpdateMode <> 1 Delete /REBOOTOK "$INSTDIR\res\src\uv.lock" diff --git a/docs/.vitepress/theme/MyLayout.vue b/docs/.vitepress/theme/MyLayout.vue index e5c8500..bc6099c 100644 --- a/docs/.vitepress/theme/MyLayout.vue +++ b/docs/.vitepress/theme/MyLayout.vue @@ -7,7 +7,7 @@ const { Layout } = DefaultTheme