# Magos - Схема базы данных

---

## Обзор

Magos работает с PostgreSQL базой данных `waf_info`, которая содержит конфигурации WAF платформы. Программа использует 5 основных таблиц для получения информации о хостах, клиентах, приложениях и backend серверах.

### Используемые таблицы

1. **instances** - информация о хостах/серверах
2. **client_info** - данные клиентов и контейнеров
3. **apps_settings** - настройки приложений и кастомные директивы
4. **origins** - backend серверы (получаются через REST API)
5. **aliases** - дополнительные домены (получаются через REST API)
6. **ips** - IP адреса для WAF и antibot сетей

---

## Подключение к БД

### Параметры подключения

```bash
DB_USER=login
DB_PASSWORD=password
DB_NAME=waf_info
DB_PORT=5432
PRIMARY_DB_HOST=10.10.10.8
SECONDARY_DB_HOST=10.10.10.5
```

### Failover логика

1. Попытка подключения к Primary DB (10.10.10.8)
2. Если неудача → подключение к Secondary DB (10.10.10.5)
3. Если обе недоступны → программа останавливается

**Функция:** `connectToDB()`

---

## Таблица: instances

### Назначение

Хранит информацию о хостах/серверах в кластере PTAF.

### Структура

| Поле | Тип | Описание | Пример |
|------|-----|----------|--------|
| `hostname` | TEXT | Hostname сервера | `host01.example.com` |
| `instance` | TEXT | Идентификатор инстанса | `waf-prod-01` |

### Используемые запросы

#### 1. Получение instance по hostname

```sql
SELECT instance 
FROM instances 
WHERE hostname = $1;
```

**Функция:** `getInstanceByHostname(hostname string)`

**Использование:** При старте программы для определения какой это инстанс.

**Пример:**
```go
hostname := "host01.example.com"
instance, err := getInstanceByHostname(db, hostname)
// instance = "waf-prod-01"
```

---

#### 2. Проверка принадлежности хоста к инстансу

```sql
SELECT COUNT(*) 
FROM instances 
WHERE hostname = $1 AND instance = $2;
```

**Функция:** `checkHostBelongsToInstance(hostname, instance string)`

**Использование:** Валидация что хост принадлежит данному инстансу.

**Пример:**
```go
hostname := "host01.example.com"
instance := "waf-prod-01"
err := checkHostBelongsToInstance(db, hostname, instance)
// err == nil если хост принадлежит инстансу
```

---

### Примеры данных

```sql
INSERT INTO instances (hostname, instance) VALUES
('host01.example.com', 'waf-prod-01'),
('host02.example.com', 'waf-prod-01'),
('host03.example.com', 'waf-prod-02');
```

---

## Таблица: client_info

### Назначение

Хранит информацию о клиентах: количество контейнеров, привязка к инстансу.

### Структура

| Поле | Тип | Описание | Пример |
|------|-----|----------|--------|
| `client_title` | TEXT | Идентификатор клиента | `CLIENT001` |
| `containers_count` | INTEGER | Количество контейнеров | `3` |
| `ptaf_config` | TEXT | Конфигурация PTAF (JSON) | `{"key": "value"}` |
| `fluent_bit_port` | INTEGER | Порт Fluent Bit | `24224` |
| `waf_instance` | TEXT | Instance для apps_settings | `waf-prod-01` |

### Используемые запросы

#### 1. Получение клиентов по instance

```sql
SELECT 
    containers_count, 
    ptaf_config, 
    client_title, 
    fluent_bit_port, 
    waf_instance
FROM client_info
WHERE waf_instance = $1;
```

**Функция:** `getClientInfoByInstance(instance string)`

**Использование:** Получение всех клиентов для данного инстанса.

**Пример:**
```go
instance := "waf-prod-01"
clients, err := getClientInfoByInstance(db, instance)
// clients = [{ContainersCount: 3, ClientTitle: "CLIENT001", ...}, ...]
```

---

#### 2. Получение клиента по client_title

```sql
SELECT 
    containers_count, 
    ptaf_config, 
    client_title, 
    fluent_bit_port, 
    waf_instance
FROM client_info
WHERE client_title = $1;
```

**Функция:** `getClientInfoByClientTitle(clientTitle string)`

**Использование:** Получение информации о конкретном клиенте.

---

### Примеры данных

```sql
INSERT INTO client_info 
(client_title, containers_count, ptaf_config, fluent_bit_port, waf_instance) 
VALUES
('CLIENT001', 3, '{"version": "1.0"}', 24224, 'waf-prod-01'),
('CLIENT002', 5, '{"version": "1.0"}', 24225, 'waf-prod-01'),
('CLIENT003', 2, '{"version": "1.0"}', 24226, 'waf-prod-02');
```

---

### Ключевое поле: containers_count

**Назначение:** Определяет сколько Docker контейнеров нужно создать для клиента.

**Логика:**
- `containers_count = 3` → создать ptaf_client_001, ptaf_client_002, ptaf_client_003
- При увеличении → создаются новые контейнеры
- При уменьшении → лишние контейнеры помечаются на graceful shutdown (90s)

**Пример изменения:**
```sql
-- Увеличить до 5 контейнеров
UPDATE client_info
SET containers_count = 5
WHERE client_title = 'CLIENT001';

-- Manager автоматически создаст ptaf_client_004 и ptaf_client_005
```

---

## Таблица: apps_settings

### Назначение

Основная таблица с настройками приложений и кастомными директивами Nginx/Angie.

### Структура

| Поле | Тип | Описание | Значение по умолчанию |
|------|-----|----------|----------------------|
| `l7resourceid` | INTEGER | ID ресурса | Первичный ключ |
| `client_title` | TEXT | Клиент (FK) | `CLIENT001` |
| `waf_enabled` | BOOLEAN | WAF включен | `true` |
| `sni` | INTEGER | SNI режим (0=keepalive on, 1=keepalive off) | `0` |
| `custom_input_http_ports` | INTEGER[] | HTTP порты для прослушивания | `{80, 8080}` |
| `custom_input_https_ports` | INTEGER[] | HTTPS порты для прослушивания | `{443, 8443}` |
| `custom_output_http_ports` | INTEGER[] | HTTP порты к backend | `{80}` |
| `custom_output_https_ports` | INTEGER[] | HTTPS порты к backend | `{443}` |
| `upstream_angie_custom` | TEXT | Кастомные upstream директивы (Angie) | `NULL` |
| `upstream_nginx_custom` | TEXT | Кастомные upstream директивы (Nginx) | `NULL` |
| `server_angie_custom` | TEXT | Полная замена server блока (Angie) | `NULL` |
| `server_nginx_custom` | TEXT | Полная замена server блока (Nginx) | `NULL` |
| `server_directives_angie_custom` | TEXT | Кастомные server директивы (Angie) | `NULL` |
| `server_directives_nginx_custom` | TEXT | Кастомные server директивы (Nginx) | `NULL` |
| `location_angie_custom` | TEXT | Кастомные location блоки (Angie) | `NULL` |
| `location_nginx_custom` | TEXT | Кастомные location блоки (Nginx) | `NULL` |

---

### Используемые запросы

#### Получение настроек по client_title

```sql
SELECT 
    l7resourceid, 
    waf_enabled,
    custom_input_http_ports, 
    custom_input_https_ports,
    custom_output_http_ports, 
    custom_output_https_ports,
    upstream_angie_custom, 
    server_nginx_custom,
    server_angie_custom, 
    client_title,
    upstream_nginx_custom,
    location_angie_custom, 
    location_nginx_custom,
    server_directives_angie_custom, 
    server_directives_nginx_custom,
    sni
FROM apps_settings
WHERE client_title = $1
ORDER BY l7resourceid ASC;
```

**Функция:** `getAppsSettingsByClientTitle(clientTitle string)`

**Использование:** Получение всех настроек приложений для клиента.

---

### Примеры данных

```sql
INSERT INTO apps_settings 
(l7resourceid, client_title, waf_enabled, sni, 
 custom_input_http_ports, custom_input_https_ports,
 custom_output_http_ports, custom_output_https_ports) 
VALUES
(12323, 'CLIENT001', true, 0, '{80}', '{443}', '{80}', '{443}'),
(12324, 'CLIENT001', true, 0, '{80,8080}', '{443,8443}', '{80}', '{443}'),
(15000, 'CLIENT002', true, 0, '{80}', '{443}', '{80}', '{443}');
```

---

### Кастомные поля (подробно)

#### upstream_angie_custom / upstream_nginx_custom

**Режим:** REPLACE (полная замена keepalive директив)

**NULL значение:**
```nginx
upstream backend {
    server 127.0.0.1:21000;
    
    # Default upstream directives
    keepalive 60;
    keepalive_timeout 70s;
}
```

**НЕ NULL значение:**
```nginx
upstream backend {
    server 127.0.0.1:21000;
    
    # Custom upstream directives (replace defaults)
    keepalive 200;
    keepalive_timeout 90s;
    least_conn;
}
```

**Пример SQL:**
```sql
-- Изменить балансировку на least_conn
UPDATE apps_settings
SET upstream_angie_custom = 'keepalive 60;
keepalive_timeout 70s;
least_conn;'
WHERE l7resourceid = 12323;

-- Вернуть дефолты
UPDATE apps_settings
SET upstream_angie_custom = NULL
WHERE l7resourceid = 12323;
```

---

#### server_directives_angie_custom / server_directives_nginx_custom

**Режим:** REPLACE (замена else блока в server)

**NULL значение:**
```nginx
server {
    listen 443 ssl;
    
    # Default directives
    keepalive_timeout 80s;
    send_timeout 60s;
    proxy_connect_timeout 60s;
    proxy_read_timeout 120s;
    proxy_send_timeout 120s;
    
    large_client_header_buffers 4 128k;
    proxy_buffers 4 32k;
    proxy_buffer_size 16k;
    proxy_busy_buffers_size 32k;
}
```

**НЕ NULL значение:**
```nginx
server {
    listen 443 ssl;
    
    # Custom directives (replace defaults)
    keepalive_timeout 120s;
    client_max_body_size 100m;
    # Нужно указать ВСЕ директивы, включая буферы!
}
```

**Пример SQL:**
```sql
-- Увеличить таймауты
UPDATE apps_settings
SET server_directives_angie_custom = 'keepalive_timeout 120s;
send_timeout 90s;
proxy_connect_timeout 90s;
proxy_read_timeout 180s;
proxy_send_timeout 180s;

large_client_header_buffers 4 128k;
proxy_buffers 4 32k;
proxy_buffer_size 16k;'
WHERE l7resourceid = 12323;
```

---

#### server_angie_custom / server_nginx_custom

**Режим:** SERVER_BLOCK (полная замена всего server блока)

**NULL значение:** Используется стандартный шаблон server блока

**НЕ NULL значение:** Полная замена server блока

**Пример SQL:**
```sql
-- Создать полностью кастомный server блок
UPDATE apps_settings
SET server_angie_custom = 'server {
    listen 9999;
    server_name special.example.com;
    
    location / {
        return 200 "Custom response";
    }
}'
WHERE l7resourceid = 12323;
```

---

#### location_angie_custom / location_nginx_custom

**Режим:** APPEND (добавление к дефолтному location)

**NULL значение:** Только стандартный `location / { proxy_pass }`

**НЕ NULL значение:** Добавляются дополнительные location блоки

**Пример SQL:**
```sql
-- Добавить location для API
UPDATE apps_settings
SET location_nginx_custom = 'location /api {
    proxy_pass http://api.backend.com;
    proxy_set_header X-API-Key secret;
}

location /static {
    alias /var/www/static;
}'
WHERE l7resourceid = 12323;
```

---

#### sni (Server Name Indication)

**Значения:**
- `0` - Keepalive ВКЛЮЧЕН (дефолт)
- `1` - Keepalive ОТКЛЮЧЕН

**Когда sni = 1:**
- Директивы `keepalive` и `keepalive_timeout` НЕ добавляются в upstream
- Используется для SNI passthrough (прямая передача без терминации SSL)

**Пример SQL:**
```sql
-- Отключить keepalive для SNI passthrough
UPDATE apps_settings
SET sni = 1
WHERE l7resourceid = 12323;
```

---

#### custom_input_*_ports и custom_output_*_ports

**custom_input_http_ports / custom_input_https_ports:**
- Порты на которых Angie и Nginx контейнеры будут слушать
- Angie слушает на этих портах (80, 443, 8080, 8443)
- Nginx контейнеры слушают на этих же портах

**custom_output_http_ports / custom_output_https_ports:**
- Порты к которым Nginx контейнеры будут проксировать на backend
- Обычно {80} для HTTP и {443} для HTTPS
- Могут быть нестандартные порты (8080, 8443)

**Пример:**
```sql
-- Приложение слушает на портах 80, 8080 (HTTP) и 443, 8443 (HTTPS)
-- Backend работает на порту 443
UPDATE apps_settings
SET 
    custom_input_http_ports = '{80, 8080}',
    custom_input_https_ports = '{443, 8443}',
    custom_output_http_ports = '{80}',
    custom_output_https_ports = '{443}'
WHERE l7resourceid = 12323;
```

**Результат:**
```nginx
# Angie
server {
    listen 80;     # custom_input_http_ports[0]
    listen 8080;   # custom_input_http_ports[1]
    listen 443 ssl;   # custom_input_https_ports[0]
    listen 8443 ssl;  # custom_input_https_ports[1]
}

# Nginx upstream
upstream backend {
    server 185.22.65.148:443;  # custom_output_https_ports[0]
}
```

---

## Таблица: ips

### Назначение

Хранит IP адреса для WAF и antibot сетей (белые списки).

### Структура

| Поле | Тип | Описание |
|------|-----|----------|
| `antibot_networks` | TEXT[] | IP сети для antibot |
| `waf_networks` | TEXT[] | IP сети для WAF |

### Используемые запросы

```sql
SELECT antibot_networks, waf_networks
FROM ips;
```

**Функция:** `loadIPsFromDB()`

**Использование:** При старте программы для загрузки IP сетей.

**Пример данных:**
```sql
INSERT INTO ips (antibot_networks, waf_networks) VALUES
(
    '{172.16.0.0/12, 10.0.0.0/8}',
    '{172.16.0.0/12, 10.100.0.0/16}'
);
```

**Использование в конфигах:**
```nginx
server {
    # Доверенные IP для получения реального IP клиента
    set_real_ip_from 172.16.0.0/12;
    set_real_ip_from 10.100.0.0/16;
    real_ip_header X-Forwarded-For;
}
```

---

## REST API (origins и aliases)

### Origins (Backend серверы)

**Не хранятся в БД**, получаются через REST API.

**Эндпоинт:** `/api/waf-proxy/get-apps-and-resourses`

**Функция:** `makeAPIRequest()`, `getResourceDataFromCache()`

**Пример ответа:**
```json
{
  "data": {
    "result": {
      "items": [
        {
          "id": 1,
          "ip": "185.22.65.148",
          "weight": 100,
          "mode": "active",
          "comment": "Primary backend"
        },
        {
          "id": 2,
          "ip": "185.22.65.149",
          "weight": 50,
          "mode": "backup",
          "comment": "Backup backend"
        }
      ]
    }
  }
}
```

**Использование в конфигах:**
```nginx
upstream backend {
    server 185.22.65.148:443 weight=100;
    server 185.22.65.149:443 weight=50 backup;
}
```

---

### Aliases (Дополнительные домены)

**Не хранятся в БД**, получаются через REST API.

**Эндпоинт:** `/api/waf-proxy/get-apps-and-resourses`

**Пример ответа:**
```json
{
  "data": {
    "result": {
      "items": [
        {
          "id": 1,
          "alias": "www.example.com"
        },
        {
          "id": 2,
          "alias": "app.example.com"
        }
      ]
    }
  }
}
```

**Использование в конфигах:**
```nginx
server {
    listen 443 ssl;
    server_name example.com www.example.com app.example.com;
}
```

---

## Связи между таблицами

```
instances
    ↓ (hostname → instance)
client_info (waf_instance)
    ↓ (client_title)
apps_settings (client_title)
    ↓ (l7resourceid через API)
Origins (от REST API)
Aliases (от REST API)
```

**Поток данных:**

1. Program запускается на хосте с hostname = `host01.example.com`
2. Запрос к `instances` → получаем `instance = "waf-prod-01"`
3. Запрос к `client_info` WHERE `waf_instance = "waf-prod-01"` → получаем клиентов
4. Для каждого клиента запрос к `apps_settings` WHERE `client_title = "CLIENT001"`
5. Для каждого ресурса API запрос → получаем origins и aliases
6. Генерируем конфиги на основе всех данных

---

## Примеры типичных запросов

### Получить все ресурсы клиента

```sql
SELECT 
    a.l7resourceid,
    a.custom_input_https_ports,
    a.upstream_angie_custom,
    c.containers_count
FROM apps_settings a
JOIN client_info c ON a.client_title = c.client_title
WHERE a.client_title = 'CLIENT001'
ORDER BY a.l7resourceid;
```

---

### Изменить количество контейнеров

```sql
-- Увеличить до 5
UPDATE client_info
SET containers_count = 5
WHERE client_title = 'CLIENT001';

-- Проверка
SELECT client_title, containers_count 
FROM client_info 
WHERE client_title = 'CLIENT001';
```

---

### Добавить кастомную балансировку

```sql
UPDATE apps_settings
SET upstream_nginx_custom = 'keepalive 60;
keepalive_timeout 70s;
least_conn;'
WHERE l7resourceid = 12323;

-- Проверка
SELECT l7resourceid, upstream_nginx_custom 
FROM apps_settings 
WHERE l7resourceid = 12323;
```

---

### Включить SNI passthrough

```sql
UPDATE apps_settings
SET sni = 1
WHERE l7resourceid = 12323;

-- Проверка
SELECT l7resourceid, sni 
FROM apps_settings 
WHERE sni = 1;
```

---

### Добавить дополнительные порты

```sql
UPDATE apps_settings
SET 
    custom_input_https_ports = '{443, 8443, 9443}',
    custom_output_https_ports = '{443, 8443, 9443}'
WHERE l7resourceid = 12323;

-- Проверка
SELECT l7resourceid, custom_input_https_ports, custom_output_https_ports
FROM apps_settings 
WHERE l7resourceid = 12323;
```

---

## Индексы (рекомендуемые)

```sql
-- Для быстрого поиска по hostname
CREATE INDEX idx_instances_hostname ON instances(hostname);

-- Для быстрого поиска клиентов по instance
CREATE INDEX idx_client_info_waf_instance ON client_info(waf_instance);
CREATE INDEX idx_client_info_client_title ON client_info(client_title);

-- Для быстрого поиска настроек по client_title
CREATE INDEX idx_apps_settings_client_title ON apps_settings(client_title);
CREATE INDEX idx_apps_settings_l7resourceid ON apps_settings(l7resourceid);
```

---

## Миграции и изменения схемы

### Добавление нового кастомного поля

```sql
-- Добавить новое поле для header манипуляций
ALTER TABLE apps_settings 
ADD COLUMN headers_custom TEXT DEFAULT NULL;

-- Обновить код для чтения этого поля
```

---

### Изменение типа поля

```sql
-- Изменить тип sni на boolean (если сейчас integer)
ALTER TABLE apps_settings 
ALTER COLUMN sni TYPE BOOLEAN 
USING (sni::integer = 0);
```

---

## Best Practices

### 1. NULL vs пустая строка

**Правильно:**
```sql
UPDATE apps_settings
SET upstream_angie_custom = NULL  -- NULL для дефолтов
WHERE l7resourceid = 12323;
```

**Неправильно:**
```sql
UPDATE apps_settings
SET upstream_angie_custom = ''  -- Пустая строка может вызвать ошибки
WHERE l7resourceid = 12323;
```

---

### 2. Массивы портов

**Правильно:**
```sql
UPDATE apps_settings
SET custom_input_https_ports = '{443, 8443}'  -- PostgreSQL array
WHERE l7resourceid = 12323;
```

**Неправильно:**
```sql
UPDATE apps_settings
SET custom_input_https_ports = '443,8443'  -- Строка, не массив
WHERE l7resourceid = 12323;
```

---

### 3. Многострочные кастомные директивы

**Правильно:**
```sql
UPDATE apps_settings
SET server_directives_angie_custom = 'keepalive_timeout 80s;
send_timeout 60s;
proxy_buffers 4 32k;'
WHERE l7resourceid = 12323;
```

**Также правильно (с экранированием):**
```sql
UPDATE apps_settings
SET server_directives_angie_custom = E'keepalive_timeout 80s;\nsend_timeout 60s;'
WHERE l7resourceid = 12323;
```

---

## Мониторинг БД

### Проверка соединения

```sql
-- Проверить активные соединения
SELECT 
    datname, 
    usename, 
    client_addr, 
    state, 
    query_start 
FROM pg_stat_activity 
WHERE datname = 'waf_info';
```

---

### Проверка данных

```sql
-- Количество клиентов на каждом instance
SELECT waf_instance, COUNT(*) as clients_count
FROM client_info
GROUP BY waf_instance;

-- Количество ресурсов на клиента
SELECT client_title, COUNT(*) as resources_count
FROM apps_settings
GROUP BY client_title;

-- Клиенты с кастомными upstream директивами
SELECT client_title, l7resourceid
FROM apps_settings
WHERE upstream_angie_custom IS NOT NULL 
   OR upstream_nginx_custom IS NOT NULL;
```

---

## Резервное копирование

```bash
# Дамп всей БД
pg_dump -h 10.100.10.8 -U install -d waf_info > waf_info_backup.sql

# Дамп только структуры
pg_dump -h 10.100.10.8 -U install -d waf_info --schema-only > schema.sql

# Дамп только данных
pg_dump -h 10.100.10.8 -U install -d waf_info --data-only > data.sql

# Восстановление
psql -h 10.100.10.8 -U install -d waf_info < waf_info_backup.sql
```

---

## Итого

### Основные таблицы

| Таблица | Записей (примерно) | Частота изменений |
|---------|-------------------|-------------------|
| instances | 10-50 | Редко (при добавлении хостов) |
| client_info | 50-500 | Редко (при добавлении клиентов) |
| apps_settings | 500-5000 | Часто (изменение настроек) |
| ips | 1 | Очень редко |

### Ключевые связи

- `instances.instance` → `client_info.waf_instance`
- `client_info.client_title` → `apps_settings.client_title`
- `apps_settings.l7resourceid` → REST API (origins, aliases)

### Кастомизация

Все кастомные поля в `apps_settings`:
- 8 полей для кастомных директив
- 4 режима: REPLACE (upstream, server_directives), SERVER_BLOCK, APPEND (location)
- NULL = дефолты, NOT NULL = кастом
