149 lines
5.1 KiB
PHP
149 lines
5.1 KiB
PHP
<?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',
|
||
],
|
||
];
|
||
}
|
||
}
|