proofdb/app/service/AdminConsole/MaintenanceScriptService.php
2026-05-11 15:23:34 +08:00

149 lines
5.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\service\AdminConsole;
use RuntimeException;
class MaintenanceScriptService
{
private const ARG_PATTERN = '/^--[A-Za-z0-9_]+(?:=[A-Za-z0-9._:@\/-]+)?$/';
public function list(): array
{
$docs = new AdminDocService();
$items = [];
foreach ($this->definitions() as $definition) {
$item = $definition;
try {
$doc = $docs->readScriptDoc($definition['doc_name']);
$item['doc_title'] = $doc['title'];
$item['doc_html'] = $doc['html'];
$item['doc_content'] = $doc['content'];
} catch (RuntimeException) {
$item['doc_title'] = null;
$item['doc_html'] = null;
$item['doc_content'] = null;
}
$items[] = $item;
}
return $items;
}
public function describe(string $name): array
{
$definitions = $this->definitions();
if (!isset($definitions[$name])) {
throw new RuntimeException('Script is not allowed.');
}
foreach ($this->list() as $item) {
if ($item['name'] === $name) {
return $item;
}
}
throw new RuntimeException('Script metadata not found.');
}
public function run(string $name, array $args = []): array
{
$definitions = $this->definitions();
if (!isset($definitions[$name])) {
throw new RuntimeException('Script is not allowed.');
}
$script = $definitions[$name];
$scriptPath = base_path('scripts/' . $script['file']);
if (!is_file($scriptPath)) {
throw new RuntimeException('Script file not found.');
}
$safeArgs = [];
foreach ($args as $arg) {
$arg = trim((string) $arg);
if ($arg === '') {
continue;
}
if (!preg_match(self::ARG_PATTERN, $arg)) {
throw new RuntimeException('Only --key=value style arguments are allowed.');
}
$safeArgs[] = $arg;
}
$command = array_merge([PHP_BINARY, $scriptPath], $safeArgs);
$descriptors = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($command, $descriptors, $pipes, base_path());
if (!is_resource($process)) {
throw new RuntimeException('Failed to start script process.');
}
fclose($pipes[0]);
$stdout = (string) stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = (string) stream_get_contents($pipes[2]);
fclose($pipes[2]);
$exitCode = proc_close($process);
return [
'script_name' => $name,
'command' => array_merge(['php', 'scripts/' . $script['file']], $safeArgs),
'exit_code' => $exitCode,
'stdout' => $stdout,
'stderr' => $stderr,
'ok' => $exitCode === 0,
];
}
private function definitions(): array
{
return [
'setup_database' => [
'name' => 'setup_database',
'file' => 'setup_database.php',
'label' => '初始化数据库',
'description' => '创建或补齐 archives、chunks 相关表结构与索引。',
'doc_name' => 'setup_database.md',
'args_hint' => '无参数',
],
'setup_opensearch' => [
'name' => 'setup_opensearch',
'file' => 'setup_opensearch.php',
'label' => '初始化 OpenSearch',
'description' => '创建或补齐 proofdb_chunks 索引与 mapping。',
'doc_name' => 'setup_opensearch.md',
'args_hint' => '无参数',
],
'reembed_chunks' => [
'name' => 'reembed_chunks',
'file' => 'reembed_chunks.php',
'label' => '重新生成向量',
'description' => '对 chunks 重新执行 embedding支持 resume 与 --reset。',
'doc_name' => 'reembed_chunks.md',
'args_hint' => '--archive_uid=01... 或 --reset',
],
'reindex_opensearch' => [
'name' => 'reindex_opensearch',
'file' => 'reindex_opensearch.php',
'label' => '重建 OpenSearch 索引',
'description' => '把 PostgreSQL 中已向量化的数据重新写入 OpenSearch。',
'doc_name' => 'reindex_opensearch.md',
'args_hint' => '--archive_uid=01...',
],
'setup_admin_users' => [
'name' => 'setup_admin_users',
'file' => 'setup_admin_users.php',
'label' => '初始化管理员用户',
'description' => '创建 admin_users 表并写入或更新管理员账号。',
'doc_name' => 'setup_admin_users.md',
'args_hint' => '--username=admin --password=secret',
],
];
}
}