up two levels -> api/ $this->baseDir = dirname(__DIR__, 2); } public function lockFile(): string { return $this->baseDir . '/system/.installed'; } public function isInstalled(): bool { return is_file($this->lockFile()); } public function testConnection(array $db): bool { try { new PDO( "mysql:host={$db['host']};dbname={$db['name']};charset=utf8mb4", $db['user'], $db['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); return true; } catch (PDOException $e) { return false; } } public function importSchema(array $db): void { $sqlFile = $this->baseDir . '/install/dump.sql'; if (!is_file($sqlFile)) { throw new RuntimeException("Schema dump not found: {$sqlFile}"); } $mysqli = new \mysqli($db['host'], $db['user'], $db['pass'], $db['name']); if ($mysqli->connect_errno) { throw new RuntimeException('DB connect failed: ' . $mysqli->connect_error); } if (!$mysqli->multi_query((string) file_get_contents($sqlFile))) { throw new RuntimeException('Schema import failed: ' . $mysqli->error); } // Drain all result sets so the connection finishes cleanly. while ($mysqli->more_results() && $mysqli->next_result()) { /* noop */ } if ($mysqli->errno) { throw new RuntimeException('Schema import error: ' . $mysqli->error); } $mysqli->close(); } public function generateKey(int $bytes = 32): string { return bin2hex(random_bytes($bytes)); // hex only: safe inside single-quoted PHP } public function writeConfig(array $c): void { $e = fn($v) => addslashes((string) $v); // escape operator-provided values $tpl = "baseDir . '/config.php', $tpl); } public function lock(): void { file_put_contents($this->lockFile(), date('c') . "\n"); } /** * Full install. $cfg = ['url','name','db'=>['host','name','user','pass']]. */ public function run(array $cfg): void { if (!$this->testConnection($cfg['db'])) { throw new RuntimeException('Database connection failed. Check credentials.'); } $this->importSchema($cfg['db']); $cfg['admin_token'] = $this->generateKey(24); $cfg['hash_password_key'] = $this->generateKey(32); $cfg['hash_api_key'] = $this->generateKey(32); $this->writeConfig($cfg); $this->lock(); // Apply migrations on top of the freshly imported baseline. $pdo = new PDO( "mysql:host={$cfg['db']['host']};dbname={$cfg['db']['name']};charset=utf8mb4", $cfg['db']['user'], $cfg['db']['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); (new Migrator($pdo, $this->baseDir . '/db/migrations'))->migrate(); } }