mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
fix: guard desktop-managed core restart (#9098)
This commit is contained in:
10
astrbot/core/desktop_runtime.py
Normal file
10
astrbot/core/desktop_runtime.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import os
|
||||
|
||||
DESKTOP_MANAGED_RESTART_MESSAGE = (
|
||||
"AstrBot Desktop manages this backend process. Please restart or update from "
|
||||
"the desktop app instead of the core WebUI."
|
||||
)
|
||||
|
||||
|
||||
def is_desktop_managed_backend() -> bool:
|
||||
return os.environ.get("ASTRBOT_DESKTOP_MANAGED") == "1"
|
||||
@@ -8,6 +8,10 @@ import psutil
|
||||
|
||||
from astrbot.core import logger
|
||||
from astrbot.core.config.default import VERSION
|
||||
from astrbot.core.desktop_runtime import (
|
||||
DESKTOP_MANAGED_RESTART_MESSAGE,
|
||||
is_desktop_managed_backend,
|
||||
)
|
||||
from astrbot.core.utils.astrbot_path import get_astrbot_path
|
||||
from astrbot.core.utils.io import ensure_dir
|
||||
|
||||
@@ -142,6 +146,10 @@ class AstrBotUpdator(RepoZipUpdator):
|
||||
在指定的延迟后,终止所有子进程并重新启动程序
|
||||
这里只能使用 os.exec* 来重启程序
|
||||
"""
|
||||
if is_desktop_managed_backend():
|
||||
logger.error(DESKTOP_MANAGED_RESTART_MESSAGE)
|
||||
raise RuntimeError(DESKTOP_MANAGED_RESTART_MESSAGE)
|
||||
|
||||
time.sleep(delay)
|
||||
self.terminate_child_processes()
|
||||
executable = sys.executable
|
||||
|
||||
@@ -4,6 +4,7 @@ from fastapi import APIRouter, Depends, Query, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from astrbot.core import logger
|
||||
from astrbot.core.desktop_runtime import DESKTOP_MANAGED_RESTART_MESSAGE
|
||||
from astrbot.dashboard.async_utils import run_maybe_async
|
||||
from astrbot.dashboard.schemas import PipInstallRequest, UpdateRequest
|
||||
from astrbot.dashboard.services.update_service import (
|
||||
@@ -58,6 +59,15 @@ def _service_response(result: UpdateServiceResult) -> JSONResponse:
|
||||
|
||||
def _service_error(exc: UpdateServiceError) -> JSONResponse:
|
||||
logger.error(f"Dashboard update operation failed: {exc}", exc_info=True)
|
||||
if exc.code == "desktop_managed":
|
||||
return JSONResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": DESKTOP_MANAGED_RESTART_MESSAGE,
|
||||
"data": None,
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
return JSONResponse(
|
||||
{"status": "error", "message": "An internal error has occurred.", "data": None},
|
||||
status_code=200,
|
||||
|
||||
@@ -21,6 +21,10 @@ from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
||||
from astrbot.core.db import BaseDatabase
|
||||
from astrbot.core.db.po import ProviderStat
|
||||
from astrbot.core.desktop_runtime import (
|
||||
DESKTOP_MANAGED_RESTART_MESSAGE,
|
||||
is_desktop_managed_backend,
|
||||
)
|
||||
from astrbot.core.utils.astrbot_path import get_astrbot_path
|
||||
from astrbot.core.utils.auth_password import (
|
||||
is_default_dashboard_password,
|
||||
@@ -57,6 +61,9 @@ class StatService:
|
||||
raise StatServiceError(
|
||||
"You are not permitted to do this operation in demo mode"
|
||||
)
|
||||
if is_desktop_managed_backend():
|
||||
raise StatServiceError(DESKTOP_MANAGED_RESTART_MESSAGE)
|
||||
|
||||
await self.core_lifecycle.restart()
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -15,6 +15,10 @@ from astrbot.core import logger
|
||||
from astrbot.core import pip_installer as _pip_installer
|
||||
from astrbot.core.config.default import VERSION
|
||||
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
||||
from astrbot.core.desktop_runtime import (
|
||||
DESKTOP_MANAGED_RESTART_MESSAGE,
|
||||
is_desktop_managed_backend,
|
||||
)
|
||||
from astrbot.core.updator import AstrBotUpdator
|
||||
from astrbot.core.utils.astrbot_path import (
|
||||
get_astrbot_data_path,
|
||||
@@ -67,7 +71,9 @@ class UpdateServiceResult:
|
||||
|
||||
|
||||
class UpdateServiceError(Exception):
|
||||
pass
|
||||
def __init__(self, message: str, *, code: str | None = None) -> None:
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
class UpdateService:
|
||||
@@ -143,6 +149,12 @@ class UpdateService:
|
||||
raise UpdateServiceError(exc.__str__()) from exc
|
||||
|
||||
async def update_project(self, data: object) -> UpdateServiceResult:
|
||||
if is_desktop_managed_backend():
|
||||
raise UpdateServiceError(
|
||||
DESKTOP_MANAGED_RESTART_MESSAGE,
|
||||
code="desktop_managed",
|
||||
)
|
||||
|
||||
payload = data if isinstance(data, dict) else {}
|
||||
version = payload.get("version", "")
|
||||
reboot = payload.get("reboot", True)
|
||||
|
||||
@@ -20,6 +20,7 @@ from werkzeug.datastructures import FileStorage
|
||||
from astrbot.core import LogBroker
|
||||
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
||||
from astrbot.core.db.sqlite import SQLiteDatabase
|
||||
from astrbot.core.desktop_runtime import DESKTOP_MANAGED_RESTART_MESSAGE
|
||||
from astrbot.core.star.star import StarMetadata, star_registry
|
||||
from astrbot.core.star.star_handler import star_handlers_registry
|
||||
from astrbot.core.utils.auth_password import (
|
||||
@@ -2662,6 +2663,35 @@ async def test_check_update(
|
||||
assert data["data"]["has_new_version"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_restart_core_rejects_desktop_managed_backend(
|
||||
app: FastAPIAppAdapter,
|
||||
authenticated_header: dict,
|
||||
core_lifecycle_td: AstrBotCoreLifecycle,
|
||||
monkeypatch,
|
||||
):
|
||||
test_client = app.test_client()
|
||||
restart_called = False
|
||||
|
||||
async def mock_restart():
|
||||
nonlocal restart_called
|
||||
restart_called = True
|
||||
|
||||
monkeypatch.setenv("ASTRBOT_DESKTOP_MANAGED", "1")
|
||||
monkeypatch.setattr(core_lifecycle_td, "restart", mock_restart)
|
||||
|
||||
response = await test_client.post(
|
||||
"/api/stat/restart-core",
|
||||
headers=authenticated_header,
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
data = await response.get_json()
|
||||
assert data["status"] == "error"
|
||||
assert data["message"] == DESKTOP_MANAGED_RESTART_MESSAGE
|
||||
assert restart_called is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_do_update(
|
||||
app: FastAPIAppAdapter,
|
||||
@@ -2826,6 +2856,44 @@ async def test_do_update_does_not_apply_files_when_core_download_fails(
|
||||
assert calls == ["download-dashboard", "download-core"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_do_update_rejects_desktop_managed_backend(
|
||||
app: FastAPIAppAdapter,
|
||||
authenticated_header: dict,
|
||||
core_lifecycle_td: AstrBotCoreLifecycle,
|
||||
monkeypatch,
|
||||
):
|
||||
test_client = app.test_client()
|
||||
calls = []
|
||||
|
||||
async def mock_download_core(*args, **kwargs):
|
||||
del args, kwargs
|
||||
calls.append("download-core")
|
||||
|
||||
async def mock_restart():
|
||||
calls.append("restart")
|
||||
|
||||
monkeypatch.setenv("ASTRBOT_DESKTOP_MANAGED", "1")
|
||||
monkeypatch.setattr(
|
||||
core_lifecycle_td.astrbot_updator,
|
||||
"download_update_package",
|
||||
mock_download_core,
|
||||
)
|
||||
monkeypatch.setattr(core_lifecycle_td, "restart", mock_restart)
|
||||
|
||||
response = await test_client.post(
|
||||
"/api/update/do",
|
||||
headers=authenticated_header,
|
||||
json={"version": "v3.4.0", "progress_id": "desktop-progress"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = await response.get_json()
|
||||
assert data["status"] == "error"
|
||||
assert data["message"] == DESKTOP_MANAGED_RESTART_MESSAGE
|
||||
assert calls == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_do_update_does_not_apply_files_when_package_verification_fails(
|
||||
app: FastAPIAppAdapter,
|
||||
|
||||
Reference in New Issue
Block a user