fix: guard desktop-managed core restart (#9098)

This commit is contained in:
エイカク
2026-07-02 11:21:02 +09:00
committed by GitHub
parent 4cf210e503
commit b673cb375f
6 changed files with 116 additions and 1 deletions

View 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"

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -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,