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' => '无参数',
|
|
],
|
|
'reindex_opensearch' => [
|
|
'name' => 'reindex_opensearch',
|
|
'file' => 'reindex_opensearch.php',
|
|
'label' => '重建 OpenSearch 索引',
|
|
'description' => '把 PostgreSQL 中已向量化的数据重新写入 OpenSearch。',
|
|
'doc_name' => 'reindex_opensearch.md',
|
|
'args_hint' => '--archive_uid=01...',
|
|
],
|
|
'backfill_archive_content' => [
|
|
'name' => 'backfill_archive_content',
|
|
'file' => 'backfill_archive_content.php',
|
|
'label' => '回填 archive content',
|
|
'description' => '从 raw 或 chunks 回填 archives.content。',
|
|
'doc_name' => 'backfill_archive_content.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',
|
|
],
|
|
];
|
|
}
|
|
}
|