Skip to content

Commit 0300d56

Browse files
committed
refactor: simplify desktop runtime build and cleanup flow
1 parent 8a1f58a commit 0300d56

8 files changed

Lines changed: 226 additions & 157 deletions

File tree

astrbot/core/updator.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from astrbot.core.config.default import VERSION
99
from astrbot.core.utils.astrbot_path import get_astrbot_path
1010
from astrbot.core.utils.io import download_file
11-
from astrbot.core.utils.runtime_env import is_packaged_electron_runtime
11+
from astrbot.core.utils.update_guard import ensure_packaged_update_allowed
1212

1313
from .zip_updator import ReleaseInfo, RepoZipUpdator
1414

@@ -90,10 +90,7 @@ async def get_releases(self) -> list:
9090
async def update(self, reboot=False, latest=True, version=None, proxy="") -> None:
9191
if os.environ.get("ASTRBOT_CLI") or os.environ.get("ASTRBOT_LAUNCHER"):
9292
raise Exception("不支持更新此方式启动的AstrBot") # 避免版本管理混乱
93-
if is_packaged_electron_runtime():
94-
raise Exception(
95-
"桌面打包版不支持在线更新,请下载最新安装包并替换当前应用。"
96-
)
93+
ensure_packaged_update_allowed()
9794

9895
update_data = await self.fetch_release_info(self.ASTRBOT_RELEASE_API, latest)
9996
file_url = None

astrbot/core/utils/update_guard.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from astrbot.core.utils.runtime_env import is_packaged_electron_runtime
2+
3+
DESKTOP_PACKAGED_UPDATE_BLOCK_MESSAGE = (
4+
"桌面打包版不支持在线更新。请下载最新安装包并替换当前应用。"
5+
)
6+
7+
8+
def should_block_packaged_update() -> bool:
9+
return is_packaged_electron_runtime()
10+
11+
12+
def get_packaged_update_block_message() -> str:
13+
return DESKTOP_PACKAGED_UPDATE_BLOCK_MESSAGE
14+
15+
16+
def ensure_packaged_update_allowed() -> None:
17+
if should_block_packaged_update():
18+
raise RuntimeError(get_packaged_update_block_message())

astrbot/dashboard/routes/update.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
from astrbot.core.db.migration.helper import check_migration_needed_v4, do_migration_v4
99
from astrbot.core.updator import AstrBotUpdator
1010
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
11-
from astrbot.core.utils.runtime_env import is_packaged_electron_runtime
11+
from astrbot.core.utils.update_guard import (
12+
get_packaged_update_block_message,
13+
should_block_packaged_update,
14+
)
1215

1316
from .route import Response, Route, RouteContext
1417

1518
CLEAR_SITE_DATA_HEADERS = {"Clear-Site-Data": '"cache"'}
16-
DESKTOP_PACKAGED_UPDATE_BLOCK_MESSAGE = (
17-
"桌面打包版不支持在线更新。请下载最新安装包并替换当前应用。"
18-
)
1919

2020

2121
class UpdateRoute(Route):
@@ -90,8 +90,8 @@ async def get_releases(self):
9090
return Response().error(e.__str__()).__dict__
9191

9292
async def update_project(self):
93-
if is_packaged_electron_runtime():
94-
return Response().error(DESKTOP_PACKAGED_UPDATE_BLOCK_MESSAGE).__dict__
93+
if should_block_packaged_update():
94+
return Response().error(get_packaged_update_block_message()).__dict__
9595

9696
data = await request.json
9797
version = data.get("version", "")
@@ -144,8 +144,8 @@ async def update_project(self):
144144
return Response().error(e.__str__()).__dict__
145145

146146
async def update_dashboard(self):
147-
if is_packaged_electron_runtime():
148-
return Response().error(DESKTOP_PACKAGED_UPDATE_BLOCK_MESSAGE).__dict__
147+
if should_block_packaged_update():
148+
return Response().error(get_packaged_update_block_message()).__dict__
149149

150150
try:
151151
try:

desktop/lib/backend-manager.js

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -162,41 +162,56 @@ class BackendManager {
162162
};
163163
}
164164

165-
resolveBackendConfig() {
166-
const webuiDir = this.resolveWebuiDir();
165+
resolveLaunchCommand(webuiDir) {
167166
const customCmd = process.env.ASTRBOT_BACKEND_CMD;
168-
let launch = null;
169-
let failureReason = null;
170-
171167
if (customCmd) {
172-
launch = {
173-
cmd: customCmd,
174-
args: [],
175-
shell: true,
168+
return {
169+
launch: {
170+
cmd: customCmd,
171+
args: [],
172+
shell: true,
173+
},
174+
failureReason: null,
176175
};
177-
} else if (this.app.isPackaged) {
176+
}
177+
178+
if (this.app.isPackaged) {
178179
const packagedBackendState = this.getPackagedBackendState();
179180
if (packagedBackendState?.ok && packagedBackendState.config) {
180-
launch = {
181-
cmd: packagedBackendState.config.runtimePythonPath,
182-
args: [packagedBackendState.config.launchScriptPath, ...(webuiDir ? ['--webui-dir', webuiDir] : [])],
183-
shell: false,
181+
return {
182+
launch: {
183+
cmd: packagedBackendState.config.runtimePythonPath,
184+
args: [packagedBackendState.config.launchScriptPath, ...(webuiDir ? ['--webui-dir', webuiDir] : [])],
185+
shell: false,
186+
},
187+
failureReason: null,
184188
};
185-
} else {
186-
failureReason =
187-
packagedBackendState?.failureReason || 'Backend command is not configured.';
188-
this.log(failureReason);
189189
}
190-
} else {
191-
launch = this.buildDefaultBackendLaunch(webuiDir);
190+
return {
191+
launch: null,
192+
failureReason: packagedBackendState?.failureReason || 'Backend command is not configured.',
193+
};
192194
}
193195

196+
return {
197+
launch: this.buildDefaultBackendLaunch(webuiDir),
198+
failureReason: null,
199+
};
200+
}
201+
202+
resolveBackendConfig() {
203+
const webuiDir = this.resolveWebuiDir();
204+
const { launch, failureReason } = this.resolveLaunchCommand(webuiDir);
205+
194206
const cwd = process.env.ASTRBOT_BACKEND_CWD || this.resolveBackendCwd();
195207
const rootDir = process.env.ASTRBOT_ROOT || this.resolveBackendRoot();
196208
ensureDir(cwd);
197209
if (rootDir) {
198210
ensureDir(rootDir);
199211
}
212+
if (failureReason) {
213+
this.log(failureReason);
214+
}
200215
this.backendConfig = {
201216
cmd: launch ? launch.cmd : null,
202217
args: launch ? launch.args : [],
@@ -641,6 +656,25 @@ class BackendManager {
641656
return { imageName, pid: parsedPid };
642657
}
643658

659+
shouldKillUnmanagedProcess({
660+
pid,
661+
processInfo,
662+
backendConfig,
663+
commandLineCache,
664+
}) {
665+
const hasBackendConfig = backendConfig && typeof backendConfig === 'object';
666+
return shouldKillUnmanagedBackendProcess({
667+
pid,
668+
processInfo,
669+
backendConfig,
670+
allowImageOnlyMatch: !hasBackendConfig,
671+
commandLineCache,
672+
spawnSync,
673+
log: (message) => this.log(message),
674+
fallbackCmdRaw: process.env.ASTRBOT_BACKEND_CMD || 'python.exe',
675+
});
676+
}
677+
644678
async stopUnmanagedBackendByPort() {
645679
if (!this.app.isPackaged || process.platform !== 'win32') {
646680
return false;
@@ -684,15 +718,11 @@ class BackendManager {
684718
this.log(`Skip unmanaged cleanup for pid=${pid}: unable to resolve process info.`);
685719
continue;
686720
}
687-
const shouldKill = shouldKillUnmanagedBackendProcess({
721+
const shouldKill = this.shouldKillUnmanagedProcess({
688722
pid,
689723
processInfo,
690724
backendConfig,
691-
allowImageOnlyMatch: !hasBackendConfig,
692725
commandLineCache,
693-
spawnSync,
694-
log: (message) => this.log(message),
695-
fallbackCmdRaw: process.env.ASTRBOT_BACKEND_CMD || 'python.exe',
696726
});
697727
if (!shouldKill) {
698728
continue;

desktop/lib/windows-backend-cleanup.js

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const path = require('path');
44

55
const WINDOWS_PROCESS_QUERY_TIMEOUT_MS = 2000;
6+
const COMMAND_LINE_QUERY_UNAVAILABLE_KEY = '__command_line_query_unavailable__';
7+
const COMMAND_LINE_FALLBACK_LOGGED_KEY = '__command_line_fallback_logged__';
68

79
function normalizeWindowsPathForMatch(value) {
810
return String(value || '')
@@ -15,56 +17,38 @@ function isGenericWindowsPythonImage(imageName) {
1517
return normalized === 'python.exe' || normalized === 'pythonw.exe' || normalized === 'py.exe';
1618
}
1719

18-
function queryWindowsProcessCommandLine({ pid, shellName, spawnSync, timeoutMs }) {
19-
const query = `$p = Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}"; if ($null -ne $p) { $p.CommandLine }`;
20-
const args = ['-NoProfile', '-NonInteractive', '-Command', query];
21-
const options = {
22-
stdio: ['ignore', 'pipe', 'ignore'],
23-
encoding: 'utf8',
24-
windowsHide: true,
25-
timeout: timeoutMs,
26-
};
27-
if (shellName === 'powershell') {
28-
return spawnSync('powershell', args, options);
29-
}
30-
if (shellName === 'pwsh') {
31-
return spawnSync('pwsh', args, options);
32-
}
33-
throw new Error(`Unsupported shell for process command line query: ${shellName}`);
34-
}
35-
36-
function parseWindowsProcessCommandLine(result) {
37-
if (!result || !result.stdout) {
38-
return null;
39-
}
40-
return (
41-
result.stdout
42-
.split(/\r?\n/)
43-
.map((item) => item.trim())
44-
.find((item) => item.length > 0) || null
45-
);
46-
}
47-
4820
function getWindowsProcessCommandLine({ pid, commandLineCache, spawnSync, log, timeoutMs }) {
4921
const numericPid = Number.parseInt(`${pid}`, 10);
5022
if (!Number.isInteger(numericPid)) {
51-
return null;
23+
return { commandLine: null, commandLineQueryUnavailable: false };
24+
}
25+
26+
if (commandLineCache && commandLineCache.get(COMMAND_LINE_QUERY_UNAVAILABLE_KEY) === true) {
27+
return { commandLine: null, commandLineQueryUnavailable: true };
5228
}
5329

5430
if (commandLineCache && commandLineCache.has(numericPid)) {
55-
return commandLineCache.get(numericPid);
31+
return {
32+
commandLine: commandLineCache.get(numericPid),
33+
commandLineQueryUnavailable: false,
34+
};
5635
}
5736

37+
const query = `$p = Get-CimInstance Win32_Process -Filter "ProcessId = ${numericPid}"; if ($null -ne $p) { $p.CommandLine }`;
38+
const args = ['-NoProfile', '-NonInteractive', '-Command', query];
39+
const options = {
40+
stdio: ['ignore', 'pipe', 'ignore'],
41+
encoding: 'utf8',
42+
windowsHide: true,
43+
timeout: timeoutMs,
44+
};
45+
5846
const queryAttempts = ['powershell', 'pwsh'];
47+
let hasAvailableShell = false;
5948
for (const shellName of queryAttempts) {
6049
let result = null;
6150
try {
62-
result = queryWindowsProcessCommandLine({
63-
pid: numericPid,
64-
shellName,
65-
spawnSync,
66-
timeoutMs,
67-
});
51+
result = spawnSync(shellName, args, options);
6852
} catch (error) {
6953
if (error instanceof Error && error.message) {
7054
log(
@@ -77,41 +61,48 @@ function getWindowsProcessCommandLine({ pid, commandLineCache, spawnSync, log, t
7761
if (result.error && result.error.code === 'ENOENT') {
7862
continue;
7963
}
64+
hasAvailableShell = true;
8065
if (result.error && result.error.code === 'ETIMEDOUT') {
8166
log(
8267
`Timed out (${timeoutMs}ms) querying process command line by ${shellName} for pid=${numericPid}.`,
8368
);
8469
continue;
8570
}
71+
if (result.error) {
72+
if (result.error.message) {
73+
log(
74+
`Failed to query process command line by ${shellName} for pid=${numericPid}: ${result.error.message}`,
75+
);
76+
}
77+
continue;
78+
}
8679

8780
if (result.status === 0) {
88-
const commandLine = parseWindowsProcessCommandLine(result);
81+
const commandLine =
82+
result.stdout
83+
.split(/\r?\n/)
84+
.map((item) => item.trim())
85+
.find((item) => item.length > 0) || null;
8986
if (commandLineCache) {
9087
commandLineCache.set(numericPid, commandLine);
88+
commandLineCache.set(COMMAND_LINE_QUERY_UNAVAILABLE_KEY, false);
9189
}
92-
return commandLine;
90+
return { commandLine, commandLineQueryUnavailable: false };
9391
}
9492
}
9593

94+
if (!hasAvailableShell) {
95+
if (commandLineCache) {
96+
commandLineCache.set(COMMAND_LINE_QUERY_UNAVAILABLE_KEY, true);
97+
}
98+
return { commandLine: null, commandLineQueryUnavailable: true };
99+
}
100+
96101
if (commandLineCache) {
97102
commandLineCache.set(numericPid, null);
103+
commandLineCache.set(COMMAND_LINE_QUERY_UNAVAILABLE_KEY, false);
98104
}
99-
return null;
100-
}
101-
102-
function getFallbackWindowsBackendImageName(fallbackCmdRaw) {
103-
const fallbackCmd = String(fallbackCmdRaw || 'python.exe')
104-
.trim()
105-
.split(/\s+/, 1)[0];
106-
return path.basename(fallbackCmd || 'python.exe').toLowerCase();
107-
}
108-
109-
function getExpectedWindowsBackendImageName(backendConfig, fallbackCmdRaw) {
110-
const safeBackendConfig =
111-
backendConfig && typeof backendConfig === 'object' ? backendConfig : {};
112-
return path
113-
.basename(safeBackendConfig.cmd || getFallbackWindowsBackendImageName(fallbackCmdRaw))
114-
.toLowerCase();
105+
return { commandLine: null, commandLineQueryUnavailable: false };
115106
}
116107

117108
function buildBackendCommandLineMarkers(backendConfig) {
@@ -145,7 +136,14 @@ function shouldKillUnmanagedBackendProcess({
145136
log,
146137
fallbackCmdRaw,
147138
}) {
148-
const expectedImageName = getExpectedWindowsBackendImageName(backendConfig, fallbackCmdRaw);
139+
const safeBackendConfig =
140+
backendConfig && typeof backendConfig === 'object' ? backendConfig : {};
141+
const fallbackCmd = String(fallbackCmdRaw || 'python.exe')
142+
.trim()
143+
.split(/\s+/, 1)[0];
144+
const expectedImageName = path
145+
.basename(safeBackendConfig.cmd || fallbackCmd || 'python.exe')
146+
.toLowerCase();
149147
const actualImageName = processInfo.imageName.toLowerCase();
150148
if (actualImageName !== expectedImageName) {
151149
log(
@@ -164,14 +162,24 @@ function shouldKillUnmanagedBackendProcess({
164162
return false;
165163
}
166164

167-
const commandLine = getWindowsProcessCommandLine({
165+
const { commandLine, commandLineQueryUnavailable } = getWindowsProcessCommandLine({
168166
pid,
169167
commandLineCache,
170168
spawnSync,
171169
log,
172170
timeoutMs: WINDOWS_PROCESS_QUERY_TIMEOUT_MS,
173171
});
174172
if (!commandLine) {
173+
if (commandLineQueryUnavailable) {
174+
if (commandLineCache && !commandLineCache.get(COMMAND_LINE_FALLBACK_LOGGED_KEY)) {
175+
commandLineCache.set(COMMAND_LINE_FALLBACK_LOGGED_KEY, true);
176+
log(
177+
'Neither powershell nor pwsh is available. ' +
178+
'Falling back to image-name-only matching for generic Python backend cleanup.',
179+
);
180+
}
181+
return true;
182+
}
175183
log(`Skip unmanaged cleanup for pid=${pid}: unable to resolve process command line.`);
176184
return false;
177185
}

0 commit comments

Comments
 (0)