sb
This commit is contained in:
@@ -6,12 +6,18 @@ namespace App;
|
|||||||
class Config
|
class Config
|
||||||
{
|
{
|
||||||
public string $assetVersion;
|
public string $assetVersion;
|
||||||
|
public bool $dbAutoInit;
|
||||||
|
public ?string $dbInitScript;
|
||||||
|
public ?string $dbInitCmd;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public array $db,
|
public array $db,
|
||||||
public bool $dbEnabled = true
|
public bool $dbEnabled = true
|
||||||
) {
|
) {
|
||||||
$this->assetVersion = defined('ASSET_VERSION') ? ASSET_VERSION : '';
|
$this->assetVersion = defined('ASSET_VERSION') ? ASSET_VERSION : '';
|
||||||
|
$this->dbAutoInit = defined('APP_DB_AUTO_INIT') ? (bool)APP_DB_AUTO_INIT : false;
|
||||||
|
$this->dbInitScript = defined('APP_DB_INIT_SCRIPT') ? (string)APP_DB_INIT_SCRIPT : null;
|
||||||
|
$this->dbInitCmd = defined('APP_DB_INIT_CMD') ? (string)APP_DB_INIT_CMD : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function primaryUrl(): string
|
public function primaryUrl(): string
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ final class Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self::ensureSchema($pdo, $config);
|
||||||
|
|
||||||
return $pdo;
|
return $pdo;
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
@@ -61,6 +63,126 @@ final class Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function ensureSchema(\PDO $pdo, Config $config): void
|
||||||
|
{
|
||||||
|
$driver = (string)$pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||||
|
if ($driver !== 'pgsql') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self::tableExists($pdo, 'hosts')) {
|
||||||
|
if ($config->dbAutoInit) {
|
||||||
|
self::initKeaSchema($pdo, $config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After init, ensure our metadata table exists (non-invasive)
|
||||||
|
if (self::tableExists($pdo, 'hosts')) {
|
||||||
|
self::ensureNexusTables($pdo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function tableExists(\PDO $pdo, string $table): bool
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = current_schema()
|
||||||
|
AND table_name = :table
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute(['table' => $table]);
|
||||||
|
return (bool)$stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function initKeaSchema(\PDO $pdo, Config $config): void
|
||||||
|
{
|
||||||
|
if ($config->dbInitCmd) {
|
||||||
|
self::runInitCommand($config->dbInitCmd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($config->dbInitScript) {
|
||||||
|
self::execSqlFile($pdo, $config->dbInitScript);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException(
|
||||||
|
'DB auto-init enabled but no APP_DB_INIT_CMD or APP_DB_INIT_SCRIPT configured.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function runInitCommand(string $cmd): void
|
||||||
|
{
|
||||||
|
$descriptor = [
|
||||||
|
1 => ['pipe', 'w'],
|
||||||
|
2 => ['pipe', 'w'],
|
||||||
|
];
|
||||||
|
$process = proc_open($cmd, $descriptor, $pipes);
|
||||||
|
if (!is_resource($process)) {
|
||||||
|
throw new \RuntimeException('Failed to start DB init command.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stdout = stream_get_contents($pipes[1]);
|
||||||
|
$stderr = stream_get_contents($pipes[2]);
|
||||||
|
foreach ($pipes as $pipe) {
|
||||||
|
fclose($pipe);
|
||||||
|
}
|
||||||
|
$code = proc_close($process);
|
||||||
|
|
||||||
|
if ($code !== 0) {
|
||||||
|
throw new \RuntimeException('DB init command failed: ' . trim($stderr ?: $stdout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function execSqlFile(\PDO $pdo, string $path): void
|
||||||
|
{
|
||||||
|
if (!is_readable($path)) {
|
||||||
|
throw new \RuntimeException('DB init script not readable: ' . $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = file_get_contents($path);
|
||||||
|
if ($sql === false) {
|
||||||
|
throw new \RuntimeException('Failed to read DB init script: ' . $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip psql meta-commands (lines starting with backslash)
|
||||||
|
$sql = preg_replace('/^\\\\.+$/m', '', $sql);
|
||||||
|
|
||||||
|
$pdo->exec($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function ensureNexusTables(\PDO $pdo): void
|
||||||
|
{
|
||||||
|
$pdo->exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS nexus_host_meta (
|
||||||
|
host_id BIGINT PRIMARY KEY,
|
||||||
|
location TEXT,
|
||||||
|
device_type TEXT,
|
||||||
|
owner TEXT,
|
||||||
|
tags JSONB,
|
||||||
|
notes TEXT,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$pdo->exec(
|
||||||
|
"DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.table_constraints
|
||||||
|
WHERE constraint_name = 'fk_nexus_host_meta_host'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE nexus_host_meta
|
||||||
|
ADD CONSTRAINT fk_nexus_host_meta_host
|
||||||
|
FOREIGN KEY (host_id)
|
||||||
|
REFERENCES hosts(host_id)
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static function buildMysqlDsn(array $db): string
|
private static function buildMysqlDsn(array $db): string
|
||||||
{
|
{
|
||||||
if (empty($db['dbname'])) {
|
if (empty($db['dbname'])) {
|
||||||
|
|||||||
Reference in New Issue
Block a user