diff --git a/api/index.php b/api/index.php index 052403a..69ccf59 100644 --- a/api/index.php +++ b/api/index.php @@ -50,6 +50,7 @@ $routes = [ 'backup' => __DIR__ . '/../handlers/backup.php', 'dev' => __DIR__ . '/../handlers/dev.php', 'dev/import' => __DIR__ . '/../handlers/dev.php', + 'health' => __DIR__ . '/../handlers/health.php', ]; try { diff --git a/common.php b/common.php index a43d9cc..61f73ca 100644 --- a/common.php +++ b/common.php @@ -6,17 +6,67 @@ require_once __DIR__ . '/lib/text_sanitize.php'; /** * Values loaded from the project .env file (see qdb_load_dotenv). - * Checked before getenv so a stale/wrong FPM pool variable cannot override .env. * * @var array */ $GLOBALS['qdb_dotenv'] = []; +function qdb_env_file_path(): string { + return __DIR__ . '/.env'; +} + +/** + * Parse a dotenv file (cached per path). Does not use getenv/putenv. + * + * @return array + */ +function qdb_parse_env_file(string $path): array { + static $cache = []; + if (isset($cache[$path])) { + return $cache[$path]; + } + $out = []; + if (!is_readable($path)) { + return $cache[$path] = $out; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + return $cache[$path] = $out; + } + foreach ($lines as $line) { + $line = trim($line); + if ($line === '' || $line[0] === '#') { + continue; + } + $eq = strpos($line, '='); + if ($eq === false) { + continue; + } + $name = trim(substr($line, 0, $eq)); + $value = trim(substr($line, $eq + 1)); + if ($name === '') { + continue; + } + $len = strlen($value); + if ($len >= 2) { + $q = $value[0]; + if (($q === '"' && $value[$len - 1] === '"') || ($q === "'" && $value[$len - 1] === "'")) { + $value = substr($value, 1, -1); + } + } + $out[$name] = $value; + } + return $cache[$path] = $out; +} + /** * Read an environment variable (.env file first, then getenv / $_ENV / $_SERVER). - * PHP-FPM often disables putenv(); .env is applied via qdb_dotenv + $_ENV. */ function qdb_env_get(string $name): ?string { + $fileVars = qdb_parse_env_file(qdb_env_file_path()); + if (isset($fileVars[$name]) && $fileVars[$name] !== '') { + return $fileVars[$name]; + } if (isset($GLOBALS['qdb_dotenv'][$name]) && $GLOBALS['qdb_dotenv'][$name] !== '') { return $GLOBALS['qdb_dotenv'][$name]; } @@ -42,52 +92,24 @@ function qdb_env_set(string $name, string $value): void { } } -/** - * Load KEY=value pairs from .env in the project root. - * Values from the file take precedence over PHP-FPM / getenv for the same keys. - */ +/** Load KEY=value pairs from .env into $GLOBALS['qdb_dotenv'] and $_ENV. */ function qdb_load_dotenv(string $path): void { - if (!is_readable($path)) { - return; - } - $lines = file($path, FILE_IGNORE_NEW_LINES); - if ($lines === false) { - return; - } - foreach ($lines as $line) { - $line = trim($line); - if ($line === '' || $line[0] === '#') { - continue; - } - $eq = strpos($line, '='); - if ($eq === false) { - continue; - } - $name = trim(substr($line, 0, $eq)); - $value = trim(substr($line, $eq + 1)); - if ($name === '') { - continue; - } - $len = strlen($value); - if ($len >= 2) { - $q = $value[0]; - if (($q === '"' && $value[$len - 1] === '"') || ($q === "'" && $value[$len - 1] === "'")) { - $value = substr($value, 1, -1); - } - } + foreach (qdb_parse_env_file($path) as $name => $value) { $GLOBALS['qdb_dotenv'][$name] = $value; qdb_env_set($name, $value); } } -qdb_load_dotenv(__DIR__ . '/.env'); +qdb_load_dotenv(qdb_env_file_path()); // --- MASTER-KEY (Server-seitig zum Speichern der DB) --- -// Per ENV 'QDB_MASTER_KEY' (Base64- oder ASCII-String) oder Fallback auf bisherigen Wert. +// Per ENV 'QDB_MASTER_KEY' (Base64- oder ASCII-String). function get_master_key_bytes(): string { $env = qdb_env_get('QDB_MASTER_KEY'); if ($env === null) { - throw new RuntimeException('QDB_MASTER_KEY environment variable is not set. Cannot proceed without a master key.'); + $hint = is_readable(qdb_env_file_path()) ? 'missing QDB_MASTER_KEY in .env' + : '.env not readable by PHP (' . qdb_env_file_path() . ')'; + throw new RuntimeException('QDB_MASTER_KEY is not set. ' . $hint); } $b = base64_decode($env, true); if ($b !== false && strlen($b) > 0) { diff --git a/db_init.php b/db_init.php index 1742902..c3ab68e 100644 --- a/db_init.php +++ b/db_init.php @@ -194,7 +194,21 @@ function qdb_open(bool $writable = false): array { if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); } throw new Exception("Could not read stored DB"); } - $decrypted = aes256_cbc_decrypt_bytes($storedEnc, $masterKey); + try { + $decrypted = aes256_cbc_decrypt_bytes($storedEnc, $masterKey); + } catch (Throwable $e) { + error_log(sprintf( + 'qdb_open decrypt failed: path=%s realpath=%s enc_bytes=%d key_sha=%s sapi=%s env_readable=%s common=%s', + $dbPath, + realpath($dbPath) ?: 'none', + strlen($storedEnc), + hash('sha256', $masterKey), + PHP_SAPI, + is_readable(__DIR__ . '/.env') ? 'yes' : 'no', + realpath(__DIR__ . '/common.php') ?: __DIR__ . '/common.php' + )); + throw $e; + } if (file_put_contents($tmpDb, $decrypted) === false) { if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); } throw new Exception("Could not write temp DB"); diff --git a/handlers/health.php b/handlers/health.php new file mode 100644 index 0000000..1b31be2 --- /dev/null +++ b/handlers/health.php @@ -0,0 +1,42 @@ +getMessage(); +} + +$keySha = null; +$keyError = null; +try { + $keySha = hash('sha256', get_master_key_bytes()); +} catch (Throwable $e) { + $keyError = $e->getMessage(); +} + +json_success([ + 'sapi' => PHP_SAPI, + 'projectRoot' => realpath(__DIR__ . '/..') ?: (__DIR__ . '/..'), + 'commonFile' => realpath(__DIR__ . '/../common.php') ?: (__DIR__ . '/../common.php'), + 'commonMtime' => @filemtime(__DIR__ . '/../common.php') ?: 0, + 'envFile' => qdb_env_file_path(), + 'envReadable' => is_readable(qdb_env_file_path()), + 'keySha256' => $keySha, + 'keyError' => $keyError, + 'dbPath' => QDB_PATH, + 'dbRealpath' => realpath(QDB_PATH) ?: null, + 'dbBytes' => is_file(QDB_PATH) ? filesize(QDB_PATH) : 0, + 'dbOpenOk' => $dbOpenOk, + 'dbError' => $dbError, + 'expectedKeySha'=> 'e584e16eb81fbb4f1e3aac965b5225c5fefabc2a67ea20e26e55671c7d9f1ae7', +]);