Перейти к содержанию

Whitelist — структура базы данных

База данных: PROD MySQL, 10.99.87.62, схема vpn Доступ: ./scripts/ssh-internal.sh 10.99.87.62


Схема таблиц

country

Поле Тип Описание
id binary(16) UUID, PK
code varchar(255) UNIQUE. Коды WL-стран начинаются с W: WA, WBR, WD...
name_key varchar(255) UNIQUE. i18n ключ: whitelist_austria, whitelist_brazil...
picture longtext Опционально

city

Поле Тип Описание
id binary(16) UUID, PK
name_key varchar(255) UNIQUE. Паттерн: moscow_wl<код>moscow_wlat, moscow_wlbr...
country_id binary(16) FK → country.id

server (WL-специфичные поля)

Поле Тип Описание
id binary(16) UUID, PK
name varchar(255) UNIQUE. Паттерн: vk-03-wlat-2096
ip varchar(255) IP upstream VPN-сервера (тот, куда идёт трафик)
port int Порт прослушивания на proxy
change_ip varchar(255) IP российского proxy (клиент подключается сюда)
change_ip_enabled tinyint(1) Должно быть 1 для активных WL
panel_type enum Должно быть WHITELIST
is_active tinyint(1) 0/1
city_id binary(16) FK → city.id
transport_params json Reality-параметры (может быть NULL для WL)

Поток подключения: клиент → change_ip:port → nftables DNAT → ip (upstream XRAY)


Proxy IPs (VK Cloud)

10 активных IP в VK Cloud для WL-проксирования:

IP Сервер
212.233.123.88 vk-01
90.156.214.48 vk-02
90.156.213.143 vk-03
83.166.239.199 vk-04
83.166.238.168 vk-05
83.166.235.99 vk-06
90.156.218.106 vk-07
90.156.219.233 vk-08
90.156.218.103 vk-09
37.139.35.61 vk-10

Стандартные порты: 443, 2096, 2083, 8443, 51443, 52443, 57443, 59443.


Создание новой WL-страны

Порядок строгий — нарушать нельзя (FK-зависимости).

Шаг 1: country

INSERT INTO country (id, code, name_key, picture) VALUES (
  UNHEX('XXXXXXXX06A311F1968A0242AC110002'),
  'WDN',
  'whitelist_denmark',
  NULL
);

Шаг 2: city

INSERT INTO city (id, name_key, country_id) VALUES (
  UNHEX('YYYYYYYY06A311F1968A0242AC110002'),
  'moscow_wldn',
  UNHEX('XXXXXXXX06A311F1968A0242AC110002')
);

Шаг 3: server (по одному на порт/proxy)

INSERT INTO server (id, name, ip, port, city_id, is_active, change_ip,
                   change_ip_enabled, panel_type, latitude, longitude)
VALUES (
  UNHEX('ZZZZZZZZ06A311F1968A0242AC110002'),
  'vk-03-wldn-2096',
  '10.99.X.X',         -- upstream VPN IP
  2096,                -- proxy listen port
  UNHEX('YYYYYYYY06A311F1968A0242AC110002'),
  1,
  '90.156.218.106',   -- proxy IP
  1,
  'WHITELIST',
  0.0, 0.0
);

Шаг 4: добавить код в Java backend

В CountryServiceImpl.java добавить WDN в WHITELIST_CODES — иначе страна не появится во вкладке Whitelist в приложении.

Stealth-серверы (WLS, inbound_id=3)

Каждый stealth-inbound требует две записи:

  1. panel_type='XUI' — для fill_configs (добавление клиентов на панель)
  2. panel_type='WHITELIST' — проксированный маршрут через RU proxy

Без XUI-записи fill_configs не создаст клиентов → подключения будут отклоняться.

Порт в MySQL = upstream-порт: для NL/DE = 9443, для AT/CH/IT/PL = 10443.


Как генерируются конфиги для WL

Java backend (NodeSyncService.java), не Python VPN Config Service.

  • Быстрый путь (~127-134): нет SSH, нет X-UI API — только DB
  • Строит VLESS-ссылку из server.ip, server.port, transport_params
  • Триггеры: AccountCreatedEvent, ServerStatusChangedEvent, cron 6h, on-demand sync

Python VCS для WL: mysql_sync.py синхронизирует в PostgreSQL, health_check.py пытается SSH к upstream (может не работать для WL-серверов), fill_configs.py не трогает WL-серверы.


Диагностические запросы

# Подключение к MySQL
./scripts/ssh-internal.sh 10.99.87.62 'docker exec vpn-db mysql -uvpn -p'$(см. Vaultwarden  Databases)' vpn -e "QUERY"'

Инвентаризация

-- Все WL-страны
SELECT HEX(id), code, name_key FROM country WHERE code LIKE 'W%' ORDER BY code;

-- Серверов по стране
SELECT c.code, COUNT(s.id) as cnt,
  GROUP_CONCAT(DISTINCT s.change_ip ORDER BY s.change_ip SEPARATOR ', ') as proxies
FROM country c
JOIN city ct ON ct.country_id = c.id
JOIN server s ON s.city_id = ct.id
WHERE c.code LIKE 'W%'
GROUP BY c.code ORDER BY c.code;

-- Порты на proxy
SELECT change_ip, GROUP_CONCAT(DISTINCT port ORDER BY port SEPARATOR ', ') as ports,
       COUNT(*) as total_servers
FROM server WHERE panel_type='WHITELIST'
GROUP BY change_ip ORDER BY change_ip;

Поиск

-- Серверы конкретной страны
SELECT HEX(s.id), s.name, s.ip, s.port, s.change_ip, s.is_active
FROM server s
JOIN city ct ON s.city_id = ct.id
JOIN country c ON ct.country_id = c.id
WHERE c.code='WA' ORDER BY s.change_ip, s.port;

-- Все серверы на конкретном proxy
SELECT HEX(s.id), s.name, s.ip, s.port, ct.name_key
FROM server s JOIN city ct ON s.city_id = ct.id
WHERE s.change_ip='90.156.218.106' ORDER BY s.port;

-- Дублирующиеся порты (должно быть 0 строк)
SELECT change_ip, port, COUNT(*) as count
FROM server WHERE panel_type='WHITELIST'
GROUP BY change_ip, port HAVING count > 1;

Валидация

-- FK: города без страны
SELECT HEX(id), name_key FROM city
WHERE country_id NOT IN (SELECT id FROM country);

-- FK: серверы без города
SELECT HEX(id), name FROM server WHERE city_id NOT IN (SELECT id FROM city);

-- Дубли country.code
SELECT code, COUNT(*) as cnt FROM country GROUP BY code HAVING cnt > 1;

-- Дубли city.name_key
SELECT name_key, COUNT(*) as cnt FROM city GROUP BY name_key HAVING cnt > 1;

-- Дубли server.name (WL)
SELECT name, COUNT(*) as cnt FROM server WHERE panel_type='WHITELIST' GROUP BY name HAVING cnt > 1;

-- Уникальность кодов стран
SELECT COUNT(*) as total_codes, COUNT(DISTINCT code) as unique_codes FROM country;
-- Если total_codes != unique_codes — есть дубли

-- Неверные panel_type (должно быть 0 строк)
SELECT COUNT(*) FROM server
WHERE panel_type NOT IN ('XUI', 'HIDDIFY', 'REMNAWAVE', 'WHITELIST');

-- Неактивные WL-серверы
SELECT name, change_ip, port FROM server
WHERE panel_type='WHITELIST' AND is_active=0;

Полная конфигурация страны

-- Полный конфиг WL для конкретной страны
SELECT
  c.code as country_code,
  c.name_key as country_name,
  ct.name_key as city_name,
  s.name as server_name,
  s.ip as upstream_ip,
  s.port as proxy_port,
  s.change_ip as proxy_ip,
  s.is_active,
  s.change_ip_enabled
FROM country c
JOIN city ct ON ct.country_id = c.id
JOIN server s ON s.city_id = ct.id
WHERE c.code='WA'
ORDER BY s.change_ip, s.port;

-- Экспорт всех WL серверов (формат для скриптов)
SELECT CONCAT_WS(',', s.change_ip, s.port, s.ip) as proxy_config
FROM server s
WHERE s.panel_type='WHITELIST' AND s.is_active=1
ORDER BY s.change_ip, s.port;
-- Формат: proxy_ip,listen_port,upstream_ip

Изменение

-- Деактивировать все серверы страны
UPDATE server SET is_active=0
WHERE city_id IN (
  SELECT ct.id FROM city ct
  WHERE ct.country_id=(SELECT id FROM country WHERE code='WDN')
);

-- Активировать страну
UPDATE server SET is_active=1
WHERE city_id IN (
  SELECT ct.id FROM city ct
  WHERE ct.country_id=(SELECT id FROM country WHERE code='WDN')
);

-- Сменить proxy IP для всех серверов страны
UPDATE server SET change_ip='90.156.219.233'  -- новый proxy IP
WHERE city_id IN (
  SELECT ct.id FROM city ct
  WHERE ct.country_id=(SELECT id FROM country WHERE code='WDN')
) AND change_ip='90.156.218.106';  -- старый proxy IP

-- Проверить после смены
SELECT name, change_ip FROM server WHERE city_id IN (
  SELECT ct.id FROM city ct
  WHERE ct.country_id=(SELECT id FROM country WHERE code='WDN')
);

Удаление (с проверками)

-- Посмотреть сколько серверов будет удалено
SELECT COUNT(*) as servers_to_delete FROM server s
WHERE s.city_id IN (
  SELECT ct.id FROM city ct
  WHERE ct.country_id=(SELECT id FROM country WHERE code='WDN')
);

-- Удалить серверы страны
DELETE FROM server
WHERE city_id IN (
  SELECT ct.id FROM city ct
  WHERE ct.country_id=(SELECT id FROM country WHERE code='WDN')
);

-- Удалить город (если серверов нет)
DELETE FROM city WHERE id NOT IN (SELECT DISTINCT city_id FROM server)
AND country_id IN (SELECT id FROM country WHERE code='WDN');

-- Удалить страну (если городов нет)
DELETE FROM country WHERE code='WDN' AND id NOT IN (SELECT DISTINCT country_id FROM city);

Сводная статистика (аналитика)

-- Общая сводка по WL-инфраструктуре
SELECT
  'Total WL Countries' as metric, COUNT(DISTINCT c.id) as value
FROM country c WHERE c.code LIKE 'W%'
UNION ALL
SELECT 'Total WL Cities', COUNT(DISTINCT ct.id)
FROM city ct WHERE ct.name_key LIKE '%_wl%'
UNION ALL
SELECT 'Total WL Servers', COUNT(DISTINCT s.id)
FROM server s WHERE s.panel_type='WHITELIST'
UNION ALL
SELECT 'Active WL Servers', COUNT(DISTINCT s.id)
FROM server s WHERE s.panel_type='WHITELIST' AND s.is_active=1
UNION ALL
SELECT 'WL Proxy IPs', COUNT(DISTINCT change_ip)
FROM server WHERE panel_type='WHITELIST' AND change_ip IS NOT NULL;

-- Серверов по proxy (с деталями)
SELECT
  change_ip as proxy_ip,
  COUNT(*) as total_servers,
  SUM(CASE WHEN is_active=1 THEN 1 ELSE 0 END) as active_servers,
  COUNT(DISTINCT port) as ports_in_use,
  COUNT(DISTINCT ip) as upstream_ips
FROM server
WHERE panel_type='WHITELIST'
GROUP BY change_ip
ORDER BY total_servers DESC;

Bulk insert (шаблон с переменными)

SET @country_id = UUID();
SET @city_id = UUID();

INSERT INTO country (id, code, name_key)
  VALUES (UNHEX(REPLACE(@country_id, '-', '')), 'WDN', 'whitelist_denmark');

INSERT INTO city (id, name_key, country_id)
  VALUES (UNHEX(REPLACE(@city_id, '-', '')), 'moscow_wldn',
          UNHEX(REPLACE(@country_id, '-', '')));

INSERT INTO server (id, name, ip, port, city_id, is_active,
                   change_ip, change_ip_enabled, panel_type, latitude, longitude)
VALUES
  (UNHEX(REPLACE(UUID(), '-', '')), 'vk-03-wldn-443',
   '10.99.87.X', 443, UNHEX(REPLACE(@city_id, '-', '')),
   1, '90.156.218.106', 1, 'WHITELIST', 0, 0),
  (UNHEX(REPLACE(UUID(), '-', '')), 'vk-03-wldn-2096',
   '10.99.87.X', 2096, UNHEX(REPLACE(@city_id, '-', '')),
   1, '90.156.218.106', 1, 'WHITELIST', 0, 0);

Индексы (производительность)

-- Добавить если запросы работают медленно
CREATE INDEX idx_server_panel_type ON server(panel_type);
CREATE INDEX idx_server_change_ip ON server(change_ip);
CREATE INDEX idx_city_country_id ON city(country_id);
CREATE INDEX idx_server_city_id ON server(city_id);

-- Проверить наличие
SHOW INDEXES FROM server WHERE Key_name LIKE 'idx%';

Типовые ошибки

Ошибка Причина Решение
Foreign key constraint fails city_id не существует Создать city перед server
Duplicate entry for key 'name_key' Уже есть city/country с таким именем Проверить и использовать другой ключ
Сервер создан, но не виден в API is_active=0 или change_ip_enabled=0 или неверный panel_type Проверить все три поля
Страна не отображается во вкладке Код не добавлен в WHITELIST_CODES в Java Добавить код в CountryServiceImpl.java

Диагностика по ошибке

"Foreign key constraint fails"

-- Проверить, существует ли city
SELECT HEX(id) FROM city WHERE name_key='moscow_wldn';
-- Если пусто — сначала создать city

"Duplicate entry for key 'name_key'"

-- Проверить что уже есть
SELECT name_key FROM city WHERE name_key='moscow_wldn';
SELECT code FROM country WHERE code='WDN';
-- Использовать другое имя или удалить старое

"Unknown column 'panel_type'"

-- Проверить схему — возможно старая версия БД
DESCRIBE server;
SHOW COLUMNS FROM server WHERE Field='panel_type';

Серверы созданы, но нет подключений

-- Проверить все три условия одновременно
SELECT name, is_active, change_ip_enabled, panel_type
FROM server WHERE panel_type='WHITELIST'
ORDER BY name LIMIT 10;
-- Все три поля должны быть: is_active=1, change_ip_enabled=1, panel_type='WHITELIST'


Ссылки

  • Архитектура WL-инфраструктуры: docs/infrastructure/whitelist-architecture.md
  • Добавление WL-сервера: docs/administration/whitelist-server-setup.md
  • Добавление страны в БД: docs/administration/add-new-country-to-database.md

См. также: Добавление WL-сервера · Whitelist архитектура · DevOps скрипты → audit_wl_proxies · VPN Config Service