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 требует две записи:
panel_type='XUI'— для fill_configs (добавление клиентов на панель)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