mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
fbc445dad2
Panel no longer depends on the user-managed MariaDB service.
SQLite at /var/lib/novacpx/panel.db runs independently so the
control panel stays up even when MariaDB is stopped.
- DB.php: switch to sqlite: DSN, add SQL translator (ON DUPLICATE KEY,
DATE_ADD/DATE_SUB INTERVAL, NOW(), UNIX_TIMESTAMP(), IFNULL)
- Core.php: replace DB_HOST/NAME/USER/PASS with DB_PATH constant
- schema.sql: full SQLite syntax, add TOTP columns to users table
- _branding.php: use sqlite: PDO, datetime('now') for session check
- install.sh: apt install sqlite3, create SQLite DB instead of MySQL DB
- tools/migrate-to-sqlite.sh: one-shot migration script for existing installs
122 lines
4.6 KiB
PHP
122 lines
4.6 KiB
PHP
<?php
|
|
class DB {
|
|
private static ?DB $instance = null;
|
|
private PDO $pdo;
|
|
|
|
private function __construct() {
|
|
$path = defined('DB_PATH') ? DB_PATH : '/var/lib/novacpx/panel.db';
|
|
$dir = dirname($path);
|
|
if (!is_dir($dir)) mkdir($dir, 0750, true);
|
|
|
|
$this->pdo = new PDO(
|
|
"sqlite:{$path}",
|
|
null, null,
|
|
[
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
]
|
|
);
|
|
$this->pdo->exec("PRAGMA journal_mode = WAL");
|
|
$this->pdo->exec("PRAGMA foreign_keys = ON");
|
|
$this->pdo->exec("PRAGMA busy_timeout = 5000");
|
|
}
|
|
|
|
public static function getInstance(): self {
|
|
if (!self::$instance) self::$instance = new self();
|
|
return self::$instance;
|
|
}
|
|
|
|
// Translate MySQL-isms to SQLite equivalents
|
|
private function translate(string $sql): string {
|
|
// ON DUPLICATE KEY UPDATE col=VALUES(col) → ON CONFLICT DO UPDATE SET col=excluded.col
|
|
$sql = preg_replace_callback(
|
|
'/ON DUPLICATE KEY UPDATE\s+(.+?)(?=\s*(?:;|$))/is',
|
|
function (array $m): string {
|
|
$pairs = preg_split('/,\s*/', trim($m[1]));
|
|
$sets = array_map(function (string $pair): string {
|
|
if (preg_match('/(\w+)\s*=\s*VALUES\s*\(\s*(\w+)\s*\)/i', $pair, $pm)) {
|
|
return "{$pm[1]}=excluded.{$pm[2]}";
|
|
}
|
|
// col=? or col=expr — keep as-is
|
|
return $pair;
|
|
}, $pairs);
|
|
return 'ON CONFLICT DO UPDATE SET ' . implode(', ', $sets);
|
|
},
|
|
$sql
|
|
);
|
|
|
|
// NOW() → datetime('now')
|
|
$sql = preg_replace('/\bNOW\(\)/i', "datetime('now')", $sql);
|
|
|
|
// UNIX_TIMESTAMP() → strftime('%s','now')
|
|
$sql = preg_replace('/\bUNIX_TIMESTAMP\(\)/i', "strftime('%s','now')", $sql);
|
|
|
|
// DATE_ADD(expr, INTERVAL n UNIT) → datetime(expr, '+n unit')
|
|
$sql = preg_replace_callback(
|
|
"/DATE_ADD\s*\(\s*(NOW\(\)|datetime\('now'\))\s*,\s*INTERVAL\s+(\d+)\s+(\w+)\s*\)/i",
|
|
function (array $m): string {
|
|
$n = $m[2];
|
|
$unit = strtolower($m[3]);
|
|
// Map MySQL interval units to SQLite modifier strings
|
|
$map = [
|
|
'second' => 'second', 'seconds' => 'second',
|
|
'minute' => 'minute', 'minutes' => 'minute',
|
|
'hour' => 'hour', 'hours' => 'hour',
|
|
'day' => 'day', 'days' => 'day',
|
|
'month' => 'month', 'months' => 'month',
|
|
'year' => 'year', 'years' => 'year',
|
|
];
|
|
$mod = $map[$unit] ?? $unit;
|
|
return "datetime('now', '+{$n} {$mod}')";
|
|
},
|
|
$sql
|
|
);
|
|
|
|
// DATE_SUB(expr, INTERVAL n UNIT) → datetime(expr, '-n unit')
|
|
$sql = preg_replace_callback(
|
|
"/DATE_SUB\s*\(\s*(NOW\(\)|datetime\('now'\))\s*,\s*INTERVAL\s+(\d+)\s+(\w+)\s*\)/i",
|
|
function (array $m): string {
|
|
$n = $m[2];
|
|
$unit = strtolower($m[3]);
|
|
$map = [
|
|
'second' => 'second', 'seconds' => 'second',
|
|
'minute' => 'minute', 'minutes' => 'minute',
|
|
'hour' => 'hour', 'hours' => 'hour',
|
|
'day' => 'day', 'days' => 'day',
|
|
'month' => 'month', 'months' => 'month',
|
|
'year' => 'year', 'years' => 'year',
|
|
];
|
|
$mod = $map[$unit] ?? $unit;
|
|
return "datetime('now', '-{$n} {$mod}')";
|
|
},
|
|
$sql
|
|
);
|
|
|
|
// IFNULL → COALESCE (SQLite supports both but be safe)
|
|
$sql = preg_replace('/\bIFNULL\s*\(/i', 'COALESCE(', $sql);
|
|
|
|
return $sql;
|
|
}
|
|
|
|
public function execute(string $sql, array $params = []): PDOStatement {
|
|
$stmt = $this->pdo->prepare($this->translate($sql));
|
|
$stmt->execute($params);
|
|
return $stmt;
|
|
}
|
|
|
|
public function fetchOne(string $sql, array $params = []): ?array {
|
|
return $this->execute($sql, $params)->fetch() ?: null;
|
|
}
|
|
|
|
public function fetchAll(string $sql, array $params = []): array {
|
|
return $this->execute($sql, $params)->fetchAll();
|
|
}
|
|
|
|
public function insert(string $sql, array $params = []): string {
|
|
$this->execute($sql, $params);
|
|
return $this->pdo->lastInsertId();
|
|
}
|
|
|
|
public function pdo(): PDO { return $this->pdo; }
|
|
}
|