diff --git a/config/base_db.php b/config/base_db.php
index 58c4921..fad903c 100644
--- a/config/base_db.php
+++ b/config/base_db.php
@@ -3,12 +3,22 @@ declare(strict_types=1);
/**
* Base database for Nexus core (users, settings, modules).
- * Sync copies the correct file into /config.
+ * Prefer the deployed root config, but fall back to checked-in env defaults.
*/
-$path = __DIR__ . '/db_settings_basic.php';
-if (!file_exists($path)) {
- throw new RuntimeException('Missing base DB config: expected config/db_settings_basic.php');
+$candidates = [
+ __DIR__ . '/db_settings_basic.php',
+ __DIR__ . '/staging/db_settings_basic.php',
+ __DIR__ . '/prod/db_settings_basic.php',
+];
+
+foreach ($candidates as $path) {
+ if (is_file($path)) {
+ return require $path;
+ }
}
-return require $path;
+throw new RuntimeException(
+ 'Missing base DB config. Expected one of: '
+ . implode(', ', array_map(static fn (string $path): string => basename(dirname($path)) . '/' . basename($path), $candidates))
+);
diff --git a/config/prod/db_settings_basic.php b/config/prod/db_settings_basic.php
index df5a703..8bda9cf 100755
--- a/config/prod/db_settings_basic.php
+++ b/config/prod/db_settings_basic.php
@@ -26,6 +26,7 @@ $pgsql = [
'host' => 'db_nexus',
'port' => 5432,
'dbname' => 'nexus_live',
+ 'connect_timeout' => 5,
// optional: schema/search_path (commonly "public")
'schema' => 'public',
diff --git a/config/staging/db_settings_basic.php b/config/staging/db_settings_basic.php
index 042b74a..740e63a 100755
--- a/config/staging/db_settings_basic.php
+++ b/config/staging/db_settings_basic.php
@@ -26,6 +26,7 @@ $pgsql = [
'host' => 'staging_db_nexus',
'port' => 5432,
'dbname' => 'nexus_staging',
+ 'connect_timeout' => 5,
// optional: schema/search_path (commonly "public")
'schema' => 'public',
diff --git a/public/index.php b/public/index.php
index 962d3b8..f3b9569 100644
--- a/public/index.php
+++ b/public/index.php
@@ -1,100 +1,389 @@
-
-
-
-
-
- Nexus Wartungstest
-
-
-
-
- Wartungstest
- Nexus ist testweise im Wartungsmodus
- Diese Seite wird direkt aus public/index.php ausgeliefert und umgeht die normale App-Initialisierung.
- Wenn diese Seite stabil erscheint, liegt das Problem sehr wahrscheinlich in der PHP-Anwendung, in einem Modul oder in deren Abhängigkeiten und nicht in der grundlegenden Webserver-Auslieferung.
-
- Testzeit: = htmlspecialchars(date('Y-m-d H:i:s T'), ENT_QUOTES, 'UTF-8') ?>
- Datei: = htmlspecialchars(__FILE__, ENT_QUOTES, 'UTF-8') ?>
-
-
-
-
+ $target = $pagesBase . '/modules/setup.php';
+} elseif (str_starts_with($uriPath, 'modules/access/')) {
+ $_GET['module'] = trim(substr($uriPath, strlen('modules/access/')), '/');
+ $target = $pagesBase . '/modules/access.php';
+} elseif ($uriPath === 'modules/sql-import') {
+ $target = $pagesBase . '/modules/sql_import.php';
+} elseif ($uriPath === 'auth/login') {
+ $target = $pagesBase . '/auth/login.php';
+} elseif ($uriPath === 'auth/callback') {
+ $target = $pagesBase . '/auth/callback.php';
+} elseif ($uriPath === 'auth/logout') {
+ $target = $pagesBase . '/auth/logout.php';
+} elseif ($uriPath === 'settings') {
+ $target = $pagesBase . '/users/settings.php';
+} elseif ($uriPath === 'settings/widgets') {
+ $target = $pagesBase . '/users/settings_widgets.php';
+} elseif ($uriPath === 'settings/search-engines') {
+ $target = $pagesBase . '/users/settings_search_engines.php';
+} elseif ($uriPath === 'settings/apps') {
+ $target = $pagesBase . '/users/settings_apps.php';
+} elseif ($uriPath === 'users') {
+ $target = $pagesBase . '/users/index.php';
+} elseif ($uriPath === 'dashboard') {
+ $target = $pagesBase . '/dashboard.php';
+} elseif ($uriPath === 'dashboards') {
+ $target = $pagesBase . '/dashboards.php';
+} elseif ($uriPath === 'integrations') {
+ $target = $pagesBase . '/integrations.php';
+} elseif ($uriPath === 'page-modules') {
+ $target = $pagesBase . '/page_modules.php';
+} elseif (preg_match('~^page-modules/view/(\d+)$~', $uriPath, $pageModuleMatch)) {
+ $_GET['id'] = (string) $pageModuleMatch[1];
+ $target = $pagesBase . '/page_modules_view.php';
+} elseif ($uriPath === 'debug') {
+ $target = $pagesBase . '/retool/debug.php';
+} elseif (preg_match('~^module/([a-zA-Z0-9_-]+)(?:/(.+))?$~', $uriPath, $m)) {
+ $module = $m[1];
+ $page = isset($m[2]) && $m[2] !== '' ? trim($m[2], '/') : 'index';
+ $moduleMeta = app()->modules()->get($module);
+ if ($moduleMeta !== null) {
+ $auth->requireModuleAccess($moduleMeta);
+ }
+ $modulePage = app()->modules()->resolvePage($module, $page);
+ $moduleBootstrap = $projectRoot . '/modules/' . $module . '/bootstrap.php';
+ if (is_file($moduleBootstrap)) {
+ require_once $moduleBootstrap;
+ }
+ if ($modulePage) {
+ $target = $modulePage;
+ } else {
+ http_response_code(404);
+ $target = $page404;
+ }
+} elseif ($uriPath === '' || $uriPath === 'index' || $uriPath === 'index.php') {
+ $target = $pagesBase . '/index.php';
+} else {
+ $base = $pagesBase . '/' . $uriPath;
+ // 1) Verzeichnis mit index.php
+ if (is_dir($base) && is_file($base . '/index.php')) {
+ $target = $base . '/index.php';
+ }
+ // 2) Datei
+ elseif (is_file($base . '.php')) {
+
+ $target = $base . '.php';
+ }
+ // 3) 404
+ elseif (is_file($base)) {
+
+ $target = $base;
+ }
+ // 3) 404
+ else {
+ http_response_code(404);
+ $target = $page404;
+ }
+}
+// ------------------------------------
+// Layout-Regel
+// ------------------------------------
+$skipLayout = false;
+$targetReal = realpath($target);
+$retoolBase = realpath($pagesBase . '/retool/raw');
+
+// Beispiel: alles unter landingpages/retool/* ohne Layout
+if ($targetReal && $retoolBase && str_starts_with($targetReal, $retoolBase)) {
+ $skipLayout = true;
+}
+
+// ------------------------------------
+// Ausgabe
+// ------------------------------------
+// Erst Inhalt laden (ohne Ausgabe), damit Header/Redirects vor HTML funktionieren
+ob_start();
+try {
+ require $target;
+ $content = ob_get_clean();
+} catch (\App\ModuleConfigException $e) {
+ ob_end_clean();
+ http_response_code(412);
+ $moduleName = $e->module();
+ $module = app()->modules()->get($moduleName);
+ $title = $module['title'] ?? $moduleName;
+ $setupUrl = '/modules/setup/' . rawurlencode($moduleName);
+ $content = '' .
+ '
' . e($title) . '
' .
+ '
Setup erforderlich
' .
+ '
' . e($e->getMessage()) . '
' .
+ '
' .
+ '
';
+}
+
+// Wenn bereits Header gesendet wurden (z. B. eigener Redirect/Content-Type), Layout überspringen
+if (headers_sent()) {
+ $skipLayout = true;
+}
+
+if (!$skipLayout) {
+ tpl('layout_start', 'structure');
+}
+
+echo $content;
+
+if (!$skipLayout) {
+ tpl('layout_end', 'structure');
+}
diff --git a/src/App/Database.php b/src/App/Database.php
index a798b16..dfefab7 100755
--- a/src/App/Database.php
+++ b/src/App/Database.php
@@ -213,14 +213,21 @@ final class Database
$host = (string)($db['host'] ?? 'localhost');
$port = (int)($db['port'] ?? 5432);
+ $connectTimeout = isset($db['connect_timeout']) ? max(1, (int) $db['connect_timeout']) : null;
// Hinweis: charset gehört bei pgsql nicht in den DSN
- return sprintf(
+ $dsn = sprintf(
'pgsql:host=%s;port=%d;dbname=%s',
$host,
$port,
(string)$db['dbname']
);
+
+ if ($connectTimeout !== null) {
+ $dsn .= ';connect_timeout=' . $connectTimeout;
+ }
+
+ return $dsn;
}
private static function buildSqliteDsn(array $db): string