Docker: production-ready setup with immutable images, versioned migrations, env vars
- Dockerfile: PHP 8.3 FPM + composer install (no dev) - docker-compose.yml: app + nginx + MariaDB 10.11 - Versioned migrations (001-007) with schema_migrations tracking - DatabaseConfig.php: env vars with fallbacks - init.sh: wait-for-db + auto-migrate - nginx.conf: reverse proxy + gzip + security rules - .env.example: config template - .dockerignore: build optimization Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
99761ca5d8
commit
6d8bd99277
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Docker build ignore
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
node_modules
|
||||||
|
vendor
|
||||||
|
*.md
|
||||||
|
*.log
|
||||||
|
*.sql
|
||||||
|
!composer.lock
|
||||||
|
tests
|
||||||
|
.env
|
||||||
|
monitoring_system_dump.sql
|
||||||
|
*fixes*
|
||||||
|
*.svg
|
||||||
|
*.pyc
|
||||||
|
__pycache__
|
||||||
|
|
||||||
|
# Backups
|
||||||
|
*.bak.*
|
||||||
|
*.broken
|
||||||
|
|
@ -11,14 +11,20 @@ class DatabaseConfig
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
private $connection;
|
private $connection;
|
||||||
|
|
||||||
private $host = 'localhost';
|
private $host;
|
||||||
private $db_name = 'monitoring_system';
|
private $db_name;
|
||||||
private $username = "mon_user";
|
private $username;
|
||||||
private $password = 'mon_password_123';
|
private $password;
|
||||||
private $charset = 'utf8mb4';
|
private $charset = 'utf8mb4';
|
||||||
|
|
||||||
private function __construct()
|
private function __construct()
|
||||||
{
|
{
|
||||||
|
// Читаем из переменных окружения с фоллбэками для совместимости
|
||||||
|
$this->host = getenv('DB_HOST') ?: 'localhost';
|
||||||
|
$this->db_name = getenv('DB_NAME') ?: 'monitoring_system';
|
||||||
|
$this->username = getenv('DB_USERNAME') ?: 'mon_user';
|
||||||
|
$this->password = getenv('DB_PASSWORD') ?: 'mon_password_123';
|
||||||
|
|
||||||
$dsn = "mysql:host={$this->host};dbname={$this->db_name};charset={$this->charset}";
|
$dsn = "mysql:host={$this->host};dbname={$this->db_name};charset={$this->charset}";
|
||||||
$options = [
|
$options = [
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
// config/DatabaseConfig.php
|
||||||
|
|
||||||
|
namespace Config;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
|
||||||
|
class DatabaseConfig
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $connection;
|
||||||
|
|
||||||
|
private $host = 'localhost';
|
||||||
|
private $db_name = 'monitoring_system';
|
||||||
|
private $username = "mon_user";
|
||||||
|
private $password = 'mon_password_123';
|
||||||
|
private $charset = 'utf8mb4';
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$dsn = "mysql:host={$this->host};dbname={$this->db_name};charset={$this->charset}";
|
||||||
|
$options = [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->connection = new PDO($dsn, $this->username, $this->password, $options);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new PDOException($e->getMessage(), (int)$e->getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance->connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
# ==========================================
|
||||||
|
# MirvMon — Environment Configuration
|
||||||
|
# ==========================================
|
||||||
|
# Скопируйте этот файл в .env и заполните значения
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Приложение
|
||||||
|
# ------------------------------------------
|
||||||
|
APP_PORT=8082
|
||||||
|
APP_TIMEZONE=Asia/Irkutsk
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# База данных
|
||||||
|
# ------------------------------------------
|
||||||
|
DB_NAME=monitoring_system
|
||||||
|
DB_USERNAME=mon_user
|
||||||
|
DB_PASSWORD=mon_password_123
|
||||||
|
DB_ROOT_PASSWORD=mirvmon_db_root_2026
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Пользователь веб-интерфейса (первый запуск)
|
||||||
|
# ------------------------------------------
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=admin_change_me
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Уведомления — Email (опционально)
|
||||||
|
# ------------------------------------------
|
||||||
|
# SMTP_HOST=smtp.gmail.com
|
||||||
|
# SMTP_PORT=587
|
||||||
|
# SMTP_USERNAME=your@email.com
|
||||||
|
# SMTP_PASSWORD=your_app_password
|
||||||
|
# SMTP_ENCRYPTION=tls
|
||||||
|
# SMTP_FROM_EMAIL=your@email.com
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Уведомления — Telegram (опционально)
|
||||||
|
# ------------------------------------------
|
||||||
|
# TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
|
||||||
|
# TELEGRAM_CHAT_ID=-1001234567890
|
||||||
|
# TELEGRAM_PROXY=http://127.0.0.1:1081
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
# ==========================================
|
||||||
|
# MirvMon — Environment Configuration
|
||||||
|
# ==========================================
|
||||||
|
# Скопируйте этот файл в .env и заполните значения
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Приложение
|
||||||
|
# ------------------------------------------
|
||||||
|
APP_PORT=8080
|
||||||
|
APP_TIMEZONE=Asia/Irkutsk
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# База данных
|
||||||
|
# ------------------------------------------
|
||||||
|
DB_NAME=monitoring_system
|
||||||
|
DB_USERNAME=mon_user
|
||||||
|
DB_PASSWORD=mon_password_123
|
||||||
|
DB_ROOT_PASSWORD=root_password_change_me
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Пользователь веб-интерфейса (первый запуск)
|
||||||
|
# ------------------------------------------
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=admin_change_me
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Уведомления — Email (опционально)
|
||||||
|
# ------------------------------------------
|
||||||
|
# SMTP_HOST=smtp.gmail.com
|
||||||
|
# SMTP_PORT=587
|
||||||
|
# SMTP_USERNAME=your@email.com
|
||||||
|
# SMTP_PASSWORD=your_app_password
|
||||||
|
# SMTP_ENCRYPTION=tls
|
||||||
|
# SMTP_FROM_EMAIL=your@email.com
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Уведомления — Telegram (опционально)
|
||||||
|
# ------------------------------------------
|
||||||
|
# TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
|
||||||
|
# TELEGRAM_CHAT_ID=-1001234567890
|
||||||
|
# TELEGRAM_PROXY=http://127.0.0.1:1081
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
FROM php:8.3-fpm
|
||||||
|
|
||||||
|
# Устанавливаем системные зависимости
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libmariadb-dev \
|
||||||
|
zip \
|
||||||
|
unzip \
|
||||||
|
git \
|
||||||
|
default-mysql-client \
|
||||||
|
&& docker-php-ext-install pdo pdo_mysql \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Устанавливаем Composer
|
||||||
|
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
WORKDIR /var/www
|
||||||
|
|
||||||
|
# Копируем composer.json и ставим зависимости (кэш слой)
|
||||||
|
COPY composer.json composer.lock* ./
|
||||||
|
RUN composer install --no-dev --optimize-autoloader --no-interaction --no-progress 2>/dev/null \
|
||||||
|
|| composer install --no-dev --optimize-autoloader --no-interaction --no-progress
|
||||||
|
|
||||||
|
# Копируем всё приложение
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Создаём директории для кэша и логов
|
||||||
|
RUN mkdir -p /var/www/var/cache /var/www/var/log \
|
||||||
|
&& chown -R www-data:www-data /var/www
|
||||||
|
|
||||||
|
# Скрипт инициализации
|
||||||
|
COPY docker/init.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
# Скрипт миграций
|
||||||
|
COPY docker/migrate.sh /usr/local/bin/migrate.sh
|
||||||
|
RUN chmod +x /usr/local/bin/migrate.sh
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
|
CMD ["php-fpm"]
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
# MirvMon — Docker Setup
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Копируем конфиг
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# 2. Меняем пароли в .env
|
||||||
|
nano .env
|
||||||
|
|
||||||
|
# 3. Запускаем
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# 4. Открываем http://localhost:8080
|
||||||
|
# Логин: admin, Пароль: admin (сменить сразу!)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обновление
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Обновить код и пересобрать
|
||||||
|
git pull
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# Или если образы в registry:
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
docker/
|
||||||
|
├── Dockerfile # PHP 8.3 FPM + приложение
|
||||||
|
├── docker-compose.yml # app + nginx + db
|
||||||
|
├── nginx.conf # конфиг nginx
|
||||||
|
├── init.sh # entrypoint (ждёт БД + миграции)
|
||||||
|
├── migrate.sh # скрипт миграций
|
||||||
|
├── migrations/ # SQL миграции с версионированием
|
||||||
|
│ ├── 001_create_base_schema.sql
|
||||||
|
│ ├── 002_add_encrypted_token.sql
|
||||||
|
│ ├── ...
|
||||||
|
├── .env.example # шаблон конфига
|
||||||
|
└── README.md # этот файл
|
||||||
|
```
|
||||||
|
|
||||||
|
## Миграции
|
||||||
|
|
||||||
|
Каждая миграция — SQL файл с номером в имени. Система отслеживает применённые в таблице `schema_migrations`.
|
||||||
|
|
||||||
|
Добавить новую:
|
||||||
|
```bash
|
||||||
|
echo "ALTER TABLE servers ADD COLUMN foo VARCHAR(50);" > docker/migrations/009_add_foo.sql
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Переменные окружения (.env)
|
||||||
|
|
||||||
|
| Переменная | Описание | По умолчанию |
|
||||||
|
|---|---|---|
|
||||||
|
| `APP_PORT` | Порт веб-интерфейса | `8080` |
|
||||||
|
| `DB_HOST` | Хост БД | `db` |
|
||||||
|
| `DB_NAME` | Имя базы | `monitoring_system` |
|
||||||
|
| `DB_USERNAME` | Пользователь БД | `mon_user` |
|
||||||
|
| `DB_PASSWORD` | Пароль БД | `mon_password_123` |
|
||||||
|
| `DB_ROOT_PASSWORD` | Root пароль БД | (обязательно сменить!) |
|
||||||
|
|
||||||
|
## Тома (persistent data)
|
||||||
|
|
||||||
|
| Volume | Что хранит |
|
||||||
|
|---|---|
|
||||||
|
| `db_data` | База данных MariaDB |
|
||||||
|
| `app_var` | Кэш Twig и логи PHP |
|
||||||
|
|
||||||
|
Код приложения **не** монтируется — он внутри образа. Обновление = новый образ.
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: docker/Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- mon_net
|
||||||
|
volumes:
|
||||||
|
- app_var:/var/www/var
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${APP_PORT:-80}:80"
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
networks:
|
||||||
|
- mon_net
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: mariadb:10.11
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||||
|
MYSQL_DATABASE: ${DB_NAME}
|
||||||
|
MYSQL_USER: ${DB_USERNAME}
|
||||||
|
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||||
|
TZ: ${APP_TIMEZONE:-Asia/Irkutsk}
|
||||||
|
command: >
|
||||||
|
--character-set-server=utf8mb4
|
||||||
|
--collation-server=utf8mb4_unicode_ci
|
||||||
|
--max-connections=100
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
start_period: 10s
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- mon_net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
app_var:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
mon_net:
|
||||||
|
driver: bridge
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# docker/init.sh — Entry point script
|
||||||
|
# Ждёт БД, запускает миграции, потом стартует PHP-FPM
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DB_HOST="${DB_HOST:-db}"
|
||||||
|
DB_PORT="${DB_PORT:-3306}"
|
||||||
|
DB_NAME="${DB_NAME:-monitoring_system}"
|
||||||
|
DB_USER="${DB_USERNAME:-mon_user}"
|
||||||
|
DB_PASS="${DB_PASSWORD:-mon_password_123}"
|
||||||
|
|
||||||
|
# Флаг для отключения SSL (MariaDB в контейнере без SSL)
|
||||||
|
MYSQL_OPTS="--skip-ssl"
|
||||||
|
|
||||||
|
echo "🚀 MirvMon — Starting up..."
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# 1. Ожидание готовности БД
|
||||||
|
# ------------------------------------------
|
||||||
|
echo "⏳ Waiting for MariaDB at ${DB_HOST}:${DB_PORT}..."
|
||||||
|
MAX_RETRIES=30
|
||||||
|
RETRY=0
|
||||||
|
|
||||||
|
while ! mysql $MYSQL_OPTS -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -e "SELECT 1;" "$DB_NAME" >/dev/null 2>&1; do
|
||||||
|
RETRY=$((RETRY + 1))
|
||||||
|
if [ $RETRY -ge $MAX_RETRIES ]; then
|
||||||
|
echo "❌ Database not ready after $MAX_RETRIES attempts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Retry $RETRY/$MAX_RETRIES..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "✅ Database is ready"
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# 2. Запуск миграций
|
||||||
|
# ------------------------------------------
|
||||||
|
echo "📦 Running database migrations..."
|
||||||
|
|
||||||
|
# Экспортием переменные для migrate.sh
|
||||||
|
export DB_HOST DB_PORT DB_NAME DB_USERNAME DB_PASSWORD
|
||||||
|
|
||||||
|
migrate.sh
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# 3. Запуск PHP-FPM
|
||||||
|
# ------------------------------------------
|
||||||
|
echo "🟢 Starting PHP-FPM..."
|
||||||
|
exec "$@"
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# docker/migrate.sh — Run pending database migrations
|
||||||
|
# Использует таблицу migrations для отслеживания применённых миграций
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DB_HOST="${DB_HOST:-db}"
|
||||||
|
DB_PORT="${DB_PORT:-3306}"
|
||||||
|
DB_NAME="${DB_NAME:-monitoring_system}"
|
||||||
|
DB_USER="${DB_USERNAME:-mon_user}"
|
||||||
|
DB_PASS="${DB_PASSWORD:-mon_password_123}"
|
||||||
|
|
||||||
|
MIGRATIONS_DIR="/var/www/docker/migrations"
|
||||||
|
|
||||||
|
MYSQL_CMD="mysql --skip-ssl -h${DB_HOST} -P${DB_PORT} -u${DB_USER} -p${DB_PASS} ${DB_NAME}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 Checking migrations..."
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# 1. Создаём таблицу отслеживания миграций
|
||||||
|
# ------------------------------------------
|
||||||
|
$MYSQL_CMD -e "
|
||||||
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
filename VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
" 2>/dev/null
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# 2. Применяем миграции по порядку
|
||||||
|
# ------------------------------------------
|
||||||
|
APPLIED=0
|
||||||
|
SKIPPED=0
|
||||||
|
|
||||||
|
for migration_file in $(ls "$MIGRATIONS_DIR"/*.sql 2>/dev/null | sort); do
|
||||||
|
filename=$(basename "$migration_file")
|
||||||
|
|
||||||
|
# Проверяем была ли уже применена
|
||||||
|
EXISTS=$($MYSQL_CMD -sN -e "SELECT COUNT(*) FROM schema_migrations WHERE filename='${filename}';" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "$EXISTS" = "1" ]; then
|
||||||
|
echo " ⏭️ $filename (already applied)"
|
||||||
|
SKIPPED=$((SKIPPED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ▶️ $filename ..."
|
||||||
|
|
||||||
|
# Применяем миграцию
|
||||||
|
if $MYSQL_CMD < "$migration_file" 2>&1; then
|
||||||
|
# Записываем в tracking
|
||||||
|
$MYSQL_CMD -e "INSERT INTO schema_migrations (filename) VALUES ('${filename}');"
|
||||||
|
echo " ✅ $filename applied"
|
||||||
|
APPLIED=$((APPLIED + 1))
|
||||||
|
else
|
||||||
|
echo " ❌ Failed to apply $filename"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📊 Migrations: $APPLIED applied, $SKIPPED skipped"
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
-- 001: Базовая схема (пользователи, серверы, метрики, алерты)
|
||||||
|
|
||||||
|
-- Таблица пользователей
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(100),
|
||||||
|
role ENUM('admin', 'operator', 'user') DEFAULT 'user',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица настроек уведомлений пользователей
|
||||||
|
CREATE TABLE IF NOT EXISTS user_notification_settings (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
enabled_notifications TINYINT(1) DEFAULT 1,
|
||||||
|
notify_on_warning TINYINT(1) DEFAULT 1,
|
||||||
|
notify_on_critical TINYINT(1) DEFAULT 1,
|
||||||
|
telegram_chat_id VARCHAR(50),
|
||||||
|
email_for_alerts VARCHAR(100),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица групп серверов
|
||||||
|
CREATE TABLE IF NOT EXISTS server_groups (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
icon VARCHAR(50),
|
||||||
|
color VARCHAR(20)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица серверов
|
||||||
|
CREATE TABLE IF NOT EXISTS servers (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
address VARCHAR(255),
|
||||||
|
group_id INT,
|
||||||
|
description TEXT,
|
||||||
|
last_metrics_at TIMESTAMP NULL,
|
||||||
|
last_service_check_at TIMESTAMP NULL,
|
||||||
|
service_check_enabled TINYINT(1) DEFAULT 1,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (group_id) REFERENCES server_groups(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица названий метрик
|
||||||
|
CREATE TABLE IF NOT EXISTS metric_names (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
unit VARCHAR(20),
|
||||||
|
description TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Стандартные метрики
|
||||||
|
INSERT IGNORE INTO metric_names (name, unit, description) VALUES
|
||||||
|
('cpu_load', '%', 'Загрузка процессора'),
|
||||||
|
('ram_used', '%', 'Использование оперативной памяти'),
|
||||||
|
('disk_used', '%', 'Использование диска'),
|
||||||
|
('network_in', 'MB/s', 'Скорость приема сети'),
|
||||||
|
('network_out', 'MB/s', 'Скорость передачи сети');
|
||||||
|
|
||||||
|
-- Таблица пороговых значений метрик
|
||||||
|
CREATE TABLE IF NOT EXISTS metric_thresholds (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
metric_name_id INT NOT NULL,
|
||||||
|
warning_threshold DECIMAL(8,2),
|
||||||
|
critical_threshold DECIMAL(8,2),
|
||||||
|
duration INT DEFAULT 0,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (metric_name_id) REFERENCES metric_names(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица метрик серверов
|
||||||
|
CREATE TABLE IF NOT EXISTS server_metrics (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
metric_name_id INT NOT NULL,
|
||||||
|
value DECIMAL(8,2),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_server_metric_time (server_id, metric_name_id, created_at),
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (metric_name_id) REFERENCES metric_names(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица токенов агентов
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_tokens (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT UNIQUE NOT NULL,
|
||||||
|
token_hash VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_used_at TIMESTAMP NULL,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица алертов
|
||||||
|
CREATE TABLE IF NOT EXISTS alerts (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
metric_name VARCHAR(50) NOT NULL,
|
||||||
|
value DECIMAL(8,2),
|
||||||
|
severity ENUM('warning', 'critical') NOT NULL,
|
||||||
|
resolved BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
resolved_at TIMESTAMP NULL,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
-- 002: Добавляем encrypted_token в agent_tokens
|
||||||
|
|
||||||
|
ALTER TABLE agent_tokens ADD COLUMN IF NOT EXISTS encrypted_token TEXT AFTER token_hash;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- 003: Таблица конфигурации агентов
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_configs (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL UNIQUE,
|
||||||
|
interval_seconds INT DEFAULT 60,
|
||||||
|
monitor_services LONGTEXT COMMENT 'Массив сервисов для мониторинга' CHECK (json_valid(monitor_services)),
|
||||||
|
enabled TINYINT(1) DEFAULT 1 COMMENT 'Включен ли агент',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
-- 004: Глобальные настройки уведомлений
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS global_notification_settings (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
smtp_host VARCHAR(255) DEFAULT '',
|
||||||
|
smtp_port INT DEFAULT 587,
|
||||||
|
smtp_username VARCHAR(255) DEFAULT '',
|
||||||
|
smtp_password VARCHAR(255) DEFAULT '',
|
||||||
|
smtp_encryption ENUM('tls', 'ssl', 'none') DEFAULT 'tls',
|
||||||
|
smtp_from_email VARCHAR(255) DEFAULT '',
|
||||||
|
telegram_bot_token VARCHAR(255) DEFAULT '',
|
||||||
|
telegram_chat_id VARCHAR(100) DEFAULT '',
|
||||||
|
telegram_proxy VARCHAR(255) DEFAULT 'http://127.0.0.1:1081',
|
||||||
|
email_enabled TINYINT(1) DEFAULT 0,
|
||||||
|
telegram_enabled TINYINT(1) DEFAULT 0,
|
||||||
|
notify_on_warning TINYINT(1) DEFAULT 1,
|
||||||
|
notify_on_critical TINYINT(1) DEFAULT 1,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Создаём запись по умолчанию если нет
|
||||||
|
INSERT INTO global_notification_settings (id) SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM global_notification_settings WHERE id = 1);
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
-- 005: Таблицы мониторинга сервисов
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS service_status (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
service_name VARCHAR(255) NOT NULL,
|
||||||
|
status ENUM('running', 'stopped', 'unknown') NOT NULL,
|
||||||
|
load_state VARCHAR(50) DEFAULT NULL,
|
||||||
|
active_state VARCHAR(50) DEFAULT NULL,
|
||||||
|
sub_state VARCHAR(50) DEFAULT NULL,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_server_service (server_id, service_name),
|
||||||
|
KEY idx_server_updated (server_id, updated_at),
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS service_alerts (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
service_name VARCHAR(100) NOT NULL,
|
||||||
|
status ENUM('stopped', 'running', 'unknown') NOT NULL,
|
||||||
|
severity ENUM('warning', 'critical') DEFAULT 'warning' COMMENT 'Уровень важности',
|
||||||
|
resolved TINYINT(1) DEFAULT 0,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
resolved_at TIMESTAMP NULL,
|
||||||
|
KEY idx_server_service (server_id, service_name),
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
-- 006: Меняем тип value в server_metrics на TEXT (для JSON процессов и т.д.)
|
||||||
|
|
||||||
|
ALTER TABLE server_metrics MODIFY COLUMN value TEXT;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
-- 007: Создаём админа по умолчанию (если нет ни одного пользователя)
|
||||||
|
|
||||||
|
-- Пароль: admin (нужно сменить при первом входе!)
|
||||||
|
-- Хеш генерируется через password_hash('admin', PASSWORD_DEFAULT)
|
||||||
|
-- Это хеш от 'admin' — смените сразу после входа!
|
||||||
|
INSERT INTO users (username, password_hash, email, role)
|
||||||
|
SELECT 'admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin@localhost', 'admin'
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM users LIMIT 1);
|
||||||
|
|
||||||
|
-- Создаём настройки уведомлений для админа
|
||||||
|
INSERT INTO user_notification_settings (user_id, enabled_notifications, notify_on_warning, notify_on_critical)
|
||||||
|
SELECT id, 1, 1, 1 FROM users WHERE username = 'admin'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM user_notification_settings WHERE user_id = users.id)
|
||||||
|
LIMIT 1;
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /var/www/public;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
# Статика
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
# PHP-FPM
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass app:9000;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_read_timeout 300;
|
||||||
|
fastcgi_buffer_size 128k;
|
||||||
|
fastcgi_buffers 4 256k;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Запрет доступа к скрытым файлам
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Запрет доступа к composer.lock и другим служебным файлам
|
||||||
|
location ~* \.(sql|md|json|lock)$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gzip сжатие
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
|
||||||
|
gzip_min_length 256;
|
||||||
|
}
|
||||||
|
|
@ -191,7 +191,7 @@ class MetricsController extends Model
|
||||||
{
|
{
|
||||||
// Получаем пороговые значения для этой метрики на этом сервере
|
// Получаем пороговые значения для этой метрики на этом сервере
|
||||||
$stmt = $this->pdo->prepare("
|
$stmt = $this->pdo->prepare("
|
||||||
SELECT warning_threshold, critical_threshold
|
SELECT warning_threshold, critical_threshold, duration
|
||||||
FROM metric_thresholds
|
FROM metric_thresholds
|
||||||
WHERE server_id = :server_id AND metric_name_id = :metric_name_id
|
WHERE server_id = :server_id AND metric_name_id = :metric_name_id
|
||||||
");
|
");
|
||||||
|
|
@ -204,6 +204,7 @@ class MetricsController extends Model
|
||||||
if ($thresholds) {
|
if ($thresholds) {
|
||||||
$warningThreshold = $thresholds['warning_threshold'];
|
$warningThreshold = $thresholds['warning_threshold'];
|
||||||
$criticalThreshold = $thresholds['critical_threshold'];
|
$criticalThreshold = $thresholds['critical_threshold'];
|
||||||
|
$duration = (int)($thresholds['duration'] ?? 0);
|
||||||
|
|
||||||
$severity = null;
|
$severity = null;
|
||||||
$threshold = null;
|
$threshold = null;
|
||||||
|
|
@ -256,25 +257,61 @@ class MetricsController extends Model
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Нового алерта нет — создаём и отправляем уведомление
|
// Алерта нет — проверяем duration
|
||||||
$stmt = $this->pdo->prepare("
|
// Если duration = 0 — алертим сразу
|
||||||
INSERT INTO alerts (server_id, metric_name, value, severity)
|
// Если duration > 0 — проверяем что все метрики за последние N минут >= порога
|
||||||
VALUES (:server_id, :metric_name, :value, :severity)
|
$shouldAlert = true;
|
||||||
");
|
|
||||||
$stmt->execute([
|
|
||||||
':server_id' => $serverId,
|
|
||||||
':metric_name' => $metricName,
|
|
||||||
':value' => $value,
|
|
||||||
':severity' => $severity
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->notificationService->sendThresholdNotification(
|
if ($duration > 0) {
|
||||||
$serverName,
|
// Считаем сколько метрик за последние duration минут были >= порога
|
||||||
$metricName,
|
$thresholdToCheck = ($severity === 'critical') ? $criticalThreshold : $warningThreshold;
|
||||||
$value,
|
|
||||||
$severity,
|
$stmt = $this->pdo->prepare("
|
||||||
$threshold
|
SELECT COUNT(*) as total_count,
|
||||||
);
|
SUM(CASE WHEN CAST(value AS DECIMAL(10,2)) >= :threshold THEN 1 ELSE 0 END) as above_count
|
||||||
|
FROM server_metrics
|
||||||
|
WHERE server_id = :server_id
|
||||||
|
AND metric_name_id = :metric_name_id
|
||||||
|
AND created_at >= DATE_SUB(NOW(), INTERVAL :duration MINUTE)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':server_id' => $serverId,
|
||||||
|
':metric_name_id' => $metricId,
|
||||||
|
':threshold' => $thresholdToCheck,
|
||||||
|
':duration' => $duration
|
||||||
|
]);
|
||||||
|
$durationCheck = $stmt->fetch();
|
||||||
|
|
||||||
|
$totalCount = (int)$durationCheck['total_count'];
|
||||||
|
$aboveCount = (int)$durationCheck['above_count'];
|
||||||
|
|
||||||
|
// Если метрик за период нет или не все выше порога — не алертим
|
||||||
|
if ($totalCount === 0 || $aboveCount < $totalCount) {
|
||||||
|
$shouldAlert = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($shouldAlert) {
|
||||||
|
// Создаём алерт и отправляем уведомление
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
INSERT INTO alerts (server_id, metric_name, value, severity)
|
||||||
|
VALUES (:server_id, :metric_name, :value, :severity)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':server_id' => $serverId,
|
||||||
|
':metric_name' => $metricName,
|
||||||
|
':value' => $value,
|
||||||
|
':severity' => $severity
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->notificationService->sendThresholdNotification(
|
||||||
|
$serverName,
|
||||||
|
$metricName,
|
||||||
|
$value,
|
||||||
|
$severity,
|
||||||
|
$threshold
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue