diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7f6a1c3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM php:8.4-apache + +# SQLite support +RUN apt-get update && apt-get install -y --no-install-recommends \ + libsqlite3-dev \ + && docker-php-ext-install pdo_sqlite \ + && rm -rf /var/lib/apt/lists/* + +# Apache rewrite + .htaccess +RUN a2enmod rewrite \ + && sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf + +# App +COPY php/ /var/www/html/ + +# Permissions: +# 1) Make everything owned by www-data so Apache can write anywhere +# 2) Set sane default perms (dirs 755, files 644) +# 3) Lock down sensitive folders: storage/db and storage/sessions +RUN set -eux; \ + chown -R www-data:www-data /var/www/html; \ + find /var/www/html -type d -exec chmod 0755 {} \;; \ + find /var/www/html -type f -exec chmod 0644 {} \;; \ + mkdir -p /var/www/html/storage/db /var/www/html/storage/sessions; \ + chown -R www-data:www-data /var/www/html/storage; \ + chmod 0700 /var/www/html/storage/db /var/www/html/storage/sessions; \ + find /var/www/html/storage/db -type f -exec chmod 0600 {} \; || true; \ + find /var/www/html/storage/sessions -type f -exec chmod 0600 {} \; || true + +EXPOSE 80 diff --git a/README.md b/README.md index 1869fdf..129b1f7 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Upload all project files (from php or nodejs, ecc. directory) to your web server Open the installer in your browser: ``` -https://your-website-url/install +https://your-website-url/install/web/ ``` The installer will: @@ -83,8 +83,16 @@ Once the web interface is installed, you can start monitoring servers by install Run the following command on each server you want to monitor: +Linux ```bash -curl -fsSL https://your-website-url/install.sh | sudo bash -s -- https://your-website-url +curl -fsSLo servermonitor-install.sh "http://localhost/install/machine/?os=linux" +sudo bash servermonitor-install.sh +``` + +Windows +```bash +iwr -UseBasicParsing "http://localhost/install/machine/?os=windows" -OutFile servermonitor-install.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File .\servermonitor-install.ps1 ``` The agent will: diff --git a/php/app/Bootstrap.php b/php/app/Bootstrap.php index 957ecdf..c487109 100644 --- a/php/app/Bootstrap.php +++ b/php/app/Bootstrap.php @@ -34,6 +34,31 @@ $db->exec('PRAGMA synchronous = NORMAL;'); $db->exec('PRAGMA busy_timeout = 5000;'); +/** + * Redirect user to installer when DB is missing / schema not ready. + */ +$installerUrl = '/install/web/index.php'; + +// Avoid redirect loop if we're already in installer +$requestUri = $_SERVER['REQUEST_URI'] ?? ''; +if (str_starts_with($requestUri, '/install/')) { + // continue bootstrap without guarding +} else { + try { + // If table doesn't exist, this will throw (with ERRMODE_EXCEPTION) + $stmt = $db->query('SELECT 1 FROM servers LIMIT 1'); + + // Extra safety: some wrappers/drivers may still return false + if ($stmt === false) { + header('Location: ' . $installerUrl, true, 302); + exit; + } + } catch (Throwable) { + header('Location: ' . $installerUrl, true, 302); + exit; + } +} + // Enforce secure session behavior to reduce fixation and hijacking risk. ini_set('session.use_strict_mode', '1'); ini_set('session.use_only_cookies', '1'); diff --git a/php/public/index.php b/php/public/index.php index db567e9..10064d4 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -1,9 +1,7 @@ setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); +} catch (Throwable $e) { + http_response_code(500); + exit('Database connection failed: ' . $e->getMessage()); +} + +$db->exec('PRAGMA journal_mode = WAL;'); +$db->exec('PRAGMA foreign_keys = ON;'); +$db->exec('PRAGMA synchronous = NORMAL;'); +$db->exec('PRAGMA busy_timeout = 5000;'); if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(403); @@ -10,17 +37,75 @@ $action = $_POST['action'] ?? ''; +/** + * Applies all pending SQL migrations from SQL_UPDATES. + * + * @param \PDO $db + * @return array{applied: int, messages: array} + * @throws \Throwable + */ +function applyMigrations(\PDO $db): array +{ + $messages = []; + + // Ensure migrations table exists + $db->exec(" + CREATE TABLE IF NOT EXISTS db_migrations ( + version TEXT PRIMARY KEY, + applied_at TEXT NOT NULL + ) + "); + + // Already applied + $applied = $db->query("SELECT version FROM db_migrations") + ->fetchAll(\PDO::FETCH_COLUMN); + + $applied = array_flip($applied ?: []); + + $files = glob(SQL_UPDATES . '/*.sql') ?: []; + sort($files, SORT_NATURAL); + + $count = 0; + + if (!$files) { + $messages[] = "No updates found"; + return ['applied' => 0, 'messages' => $messages]; + } + + foreach ($files as $file) { + $version = basename($file, '.sql'); + + if (isset($applied[$version])) { + continue; + } + + $messages[] = "Applying {$version}..."; + + $sql = file_get_contents($file); + if ($sql === false || trim($sql) === '') { + throw new Exception("Empty migration: {$version}"); + } + + $db->exec($sql); + + $stmt = $db->prepare(" + INSERT INTO db_migrations (version, applied_at) + VALUES (?, datetime('now')) + "); + $stmt->execute([$version]); + + $messages[] = "{$version} OK"; + $count++; + } + + return ['applied' => $count, 'messages' => $messages]; +} + /* ------------------------------------------------- WRITE PASSWORD (config.local.php) ------------------------------------------------- */ if ($action === 'password') { $pass = $_POST['password'] ?? ''; - - /* if (strlen($pass) < 8) { - echo "Password too short\n"; - exit; - } */ - $hash = password_hash($pass, PASSWORD_DEFAULT); $file = __DIR__ . '/../../../config/config.local.php'; @@ -40,25 +125,29 @@ } /* ------------------------------------------------- - INSTALL SCHEMA (FIRST TIME) + INSTALL + AUTO-UPDATE ------------------------------------------------- */ if ($action === 'install') { try { $db->beginTransaction(); $schema = file_get_contents(SQL_SCHEMA); - if (!$schema) { - throw new Exception('schema.sql missing'); + if ($schema === false || trim($schema) === '') { + throw new Exception('schema.sql missing or empty'); } $db->exec($schema); + // Ensure migrations table exists even if schema.sql didn't include it $db->exec(" - CREATE TABLE IF NOT EXISTS db_migrations ( - version TEXT PRIMARY KEY, - applied_at TEXT NOT NULL - ) - "); + CREATE TABLE IF NOT EXISTS db_migrations ( + version TEXT PRIMARY KEY, + applied_at TEXT NOT NULL + ) + "); + + // Apply updates immediately (still inside same transaction) + $result = applyMigrations($db); $db->commit(); @@ -66,9 +155,14 @@ touch(__DIR__ . '/.installed'); echo "Install complete\n"; - + foreach ($result['messages'] as $m) { + echo $m . "\n"; + } + echo "Auto-updates applied: {$result['applied']}\n"; } catch (Throwable $e) { - $db->rollBack(); + if ($db->inTransaction()) { + $db->rollBack(); + } echo "FAILED: {$e->getMessage()}\n"; } @@ -82,58 +176,18 @@ try { $db->beginTransaction(); - // Ensure migrations table exists - $db->exec(" - CREATE TABLE IF NOT EXISTS db_migrations ( - version TEXT PRIMARY KEY, - applied_at TEXT NOT NULL - ) - "); - - // Get already applied migrations - $applied = $db->query(" - SELECT version FROM db_migrations - ")->fetchAll(PDO::FETCH_COLUMN); - - $applied = array_flip($applied); - - $files = glob(SQL_UPDATES . '/*.sql'); - sort($files, SORT_NATURAL); - - if (!$files) { - echo "No updates found\n"; - } - - foreach ($files as $file) { - $version = basename($file, '.sql'); - - if (isset($applied[$version])) { - continue; - } - - echo "Applying {$version}...\n"; - - $sql = file_get_contents($file); - if (!$sql) { - throw new Exception("Empty migration: {$version}"); - } - - $db->exec($sql); + $result = applyMigrations($db); - $stmt = $db->prepare(" - INSERT INTO db_migrations (version, applied_at) - VALUES (?, datetime('now')) - "); - $stmt->execute([$version]); + $db->commit(); - echo "{$version} OK\n"; + foreach ($result['messages'] as $m) { + echo $m . "\n"; } - - $db->commit(); echo "Update complete\n"; - } catch (Throwable $e) { - $db->rollBack(); + if ($db->inTransaction()) { + $db->rollBack(); + } echo "UPDATE FAILED: {$e->getMessage()}\n"; } diff --git a/php/public/pages/alerts/rules.php b/php/public/pages/alerts/rules.php index a48c1f9..06af53f 100644 --- a/php/public/pages/alerts/rules.php +++ b/php/public/pages/alerts/rules.php @@ -95,6 +95,8 @@