feat: добавить аддон splitgw — прозрачный split-tunnel gateway (sing-box + Hysteria2 TPROXY)
- Роль: defaults, tasks (main/deploy-host/deploy-k8s), handlers, templates (sing-box config, iptables setup/teardown, systemd, K8s DaemonSet/ConfigMap/Secret) - Режимы: systemd (host) и K8s DaemonSet с hostNetwork + privileged init-container - Маршрутизация: YouTube/Google → Hysteria2, RU (geoip/geosite) → прямой, остальное → прямой - DNS без утечек: protocol=dns перехватывается TPROXY, per-domain DNS серверы - Интеграция: inventory [splitgw], addons.yml flag, Makefile target, playbooks/addons.yml - Документация: README.md, docs/addons.md, README.md (счётчик 36)
This commit is contained in:
6
Makefile
6
Makefile
@@ -58,7 +58,7 @@ DOCKER_RUN := docker run --rm -it \
|
||||
addon-harbor addon-gitea addon-owncloud addon-nextcloud \
|
||||
addon-csi-s3 addon-csi-ceph addon-csi-glusterfs addon-vaultwarden \
|
||||
addon-smtp-relay addon-vault addon-external-secrets \
|
||||
addon-jenkins addon-netbird addon-mediaserver addon-hysteria2-server \
|
||||
addon-jenkins addon-netbird addon-mediaserver addon-hysteria2-server addon-splitgw \
|
||||
add-node remove-node \
|
||||
add-etcd-node remove-etcd-node \
|
||||
etcd-backup etcd-restore etcd-list-snapshots \
|
||||
@@ -416,6 +416,10 @@ addon-hysteria2-server: _check_env _check_image ## Установить Hysteria
|
||||
@printf "$(CYAN)Устанавливаю Hysteria2 сервер на удалённый VPS...$(NC)\n"
|
||||
$(DOCKER_RUN) addon hysteria2-server $(ARGS)
|
||||
|
||||
addon-splitgw: _check_env _check_image ## Установить Split Gateway — sing-box+Hysteria2 TPROXY (группа [splitgw]; ARGS="-e splitgw_deploy_mode=k8s" для K8s DaemonSet)
|
||||
@printf "$(CYAN)Устанавливаю Split Gateway (sing-box + Hysteria2)...$(NC)\n"
|
||||
$(DOCKER_RUN) addon splitgw $(ARGS)
|
||||
|
||||
# Generic цель — любой аддон из addons/<name>/playbook.yml
|
||||
addon-%: _check_env _check_image
|
||||
@if [ ! -f "addons/$*/playbook.yml" ]; then \
|
||||
|
||||
@@ -38,7 +38,7 @@ HA-режим (embedded etcd): при отказе **любой одной** н
|
||||
|
||||
**CNI:** `flannel` (встроен) | `calico` (Network Policy, BGP) | `cilium` (eBPF, Hubble)
|
||||
|
||||
## Аддоны (35)
|
||||
## Аддоны (36)
|
||||
|
||||
| Категория | Аддоны |
|
||||
|---|---|
|
||||
@@ -51,6 +51,7 @@ HA-режим (embedded etcd): при отказе **любой одной** н
|
||||
| **Инфраструктура** | harbor, kubernetes-dashboard, velero, smtp-relay |
|
||||
| **Файловые хранилища** | nextcloud, owncloud |
|
||||
| **Медиасервер** | mediaserver — Plex, Sonarr, Radarr, Lidarr, Bazarr, Prowlarr + Hysteria2, Overseerr, Transmission, Samba |
|
||||
| **VPN / Прокси** | splitgw — прозрачный split-tunnel gateway (sing-box + Hysteria2 TPROXY, YouTube → прокси) |
|
||||
|
||||
Все аддоны включаются флагами в `group_vars/all/addons.yml`. Установка: `make addon-<name>`.
|
||||
|
||||
|
||||
528
addons/splitgw/README.md
Normal file
528
addons/splitgw/README.md
Normal file
@@ -0,0 +1,528 @@
|
||||
# Split Gateway
|
||||
|
||||
Прозрачный split-tunnel шлюз для домашней сети. Трафик Android TV (или любого другого устройства) автоматически разделяется на два потока — без какой-либо настройки на самом устройстве.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
Android TV
|
||||
│
|
||||
│ (любой трафик — TV ничего не знает о прокси)
|
||||
▼
|
||||
Keenetic (policy route: from TV_IP → gateway node)
|
||||
│
|
||||
▼
|
||||
K3S нода / Linux хост (gateway node)
|
||||
│
|
||||
│ iptables TPROXY → sing-box :7893
|
||||
▼
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ sing-box │
|
||||
│ │
|
||||
│ DNS: YouTube домены → 8.8.8.8 via Hysteria2 │
|
||||
│ RU домены → 192.168.1.1 (роутер) │
|
||||
│ │
|
||||
│ Routing: │
|
||||
│ geosite:youtube ──► Hysteria2 ──► VPS ──► 🌍 │
|
||||
│ geoip:ru + .ru ──► direct ──► 🇷🇺 │
|
||||
│ всё остальное ──► direct │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Ключевые свойства:**
|
||||
- Прозрачный TPROXY — TV ничего не настраивается
|
||||
- YouTube (TCP + UDP/QUIC) — через Hysteria2
|
||||
- RU сервисы (Plex, Кинопоиск, Яндекс, ВК) — прямой маршрут
|
||||
- DNS без утечек — раздельные DNS серверы по типу домена
|
||||
- geoip + geosite БД — автообновление каждые 7 дней
|
||||
|
||||
## Компоненты
|
||||
|
||||
| Компонент | Роль |
|
||||
|---|---|
|
||||
| **sing-box** | Routing engine: TPROXY inbound + split routing |
|
||||
| **Hysteria2 outbound** | Встроен в sing-box — отдельный процесс не нужен |
|
||||
| **iptables TPROXY** | Перехват трафика от TV на порт sing-box |
|
||||
| **splitgw-rules.service** | systemd: применяет iptables при старте |
|
||||
| **singbox.service** | systemd: запускает sing-box |
|
||||
|
||||
---
|
||||
|
||||
## Установка
|
||||
|
||||
### Шаг 1 — Добавить узел-шлюз в inventory
|
||||
|
||||
```ini
|
||||
# inventory/hosts.ini
|
||||
[splitgw]
|
||||
# Вариант А: отдельный Linux хост как шлюз
|
||||
gateway ansible_host=192.168.1.10 ansible_user=root
|
||||
|
||||
# Вариант Б: K3S нода как шлюз (та же нода что и master01)
|
||||
# gateway ansible_host=192.168.1.10 ansible_user=devops
|
||||
```
|
||||
|
||||
### Шаг 2 — Настроить переменные
|
||||
|
||||
```yaml
|
||||
# group_vars/all/main.yml или host_vars/gateway/main.yml
|
||||
splitgw_tv_sources:
|
||||
- "192.168.1.100/32" # IP Android TV (узнать: Keenetic → Устройства)
|
||||
|
||||
splitgw_router_ip: "192.168.1.1" # IP роутера Keenetic
|
||||
```
|
||||
|
||||
### Шаг 3 — Добавить Hysteria2 в vault
|
||||
|
||||
Если уже используешь mediaserver аддон — vault уже настроен, перейди к шагу 4.
|
||||
|
||||
```bash
|
||||
make vault-edit
|
||||
```
|
||||
|
||||
```yaml
|
||||
# group_vars/all/vault.yml
|
||||
# Способ 1 — URL из Shadowrocket/NekoBox:
|
||||
vault_hysteria2_url: "hysteria2://mypassword@vps.example.com:443?insecure=1"
|
||||
|
||||
# Способ 2 — по отдельности:
|
||||
vault_hysteria2_server: "vps.example.com:443"
|
||||
vault_hysteria2_auth: "mypassword"
|
||||
```
|
||||
|
||||
### Шаг 4 — Деплой
|
||||
|
||||
```bash
|
||||
# С SSH-ключом:
|
||||
make addon-splitgw
|
||||
|
||||
# С SSH-паролем:
|
||||
make addon-splitgw ARGS="-k"
|
||||
|
||||
# С SSH + sudo паролем:
|
||||
make addon-splitgw ARGS="-k -K"
|
||||
```
|
||||
|
||||
### Шаг 5 — Настроить Keenetic
|
||||
|
||||
Смотри раздел [Настройка Keenetic](#настройка-keenetic).
|
||||
|
||||
---
|
||||
|
||||
## Настройка Keenetic
|
||||
|
||||
Keenetic выполняет **policy-based routing**: трафик от TV направляется на узел-шлюз вместо стандартного маршрута.
|
||||
|
||||
### В веб-интерфейсе Keenetic
|
||||
|
||||
**1. Узнать IP Android TV**
|
||||
|
||||
`Мои сети и Wi-Fi` → список подключённых устройств → найти TV → скопировать IP.
|
||||
|
||||
Зафиксировать IP (чтобы не менялся):
|
||||
`Мои сети и Wi-Fi` → устройство → `Постоянный IP-адрес` → задать статический IP.
|
||||
|
||||
**2. Создать политику маршрутизации**
|
||||
|
||||
`Интернет` → `Другие подключения` → `Маршруты` → `Добавить маршрут`.
|
||||
|
||||
Или через `Управление` → `Политика маршрутизации` (зависит от версии прошивки).
|
||||
|
||||
В KeeneticOS 4.x: `Интернет` → `Доступ в интернет` → `Политика маршрутизации` → `+`.
|
||||
|
||||
**3. Параметры правила**
|
||||
|
||||
```
|
||||
Тип: Маршрут источника (source-based routing)
|
||||
Источник: 192.168.1.100 ← IP Android TV
|
||||
Через: 192.168.1.10 ← IP узла-шлюза (gateway node)
|
||||
Метрика: 10 (ниже = приоритетнее)
|
||||
```
|
||||
|
||||
**4. Через CLI Keenetic (точнее)**
|
||||
|
||||
Подключись к Keenetic по SSH:
|
||||
|
||||
```bash
|
||||
# Добавить статический маршрут для TV через шлюз:
|
||||
ip route 192.168.1.100/32 192.168.1.10 auto
|
||||
|
||||
# Или через policy routing (если поддерживается):
|
||||
ip policy-route source 192.168.1.100 nexthop 192.168.1.10
|
||||
```
|
||||
|
||||
Сохранить: `system configuration save`
|
||||
|
||||
**5. Альтернатива — DHCP опция Gateway**
|
||||
|
||||
Если TV получает IP по DHCP — настроить на роутере выдачу специфического шлюза для TV:
|
||||
|
||||
`Мои сети и Wi-Fi` → `Домашняя сеть` → `Настройки DHCP` → найти устройство → `Шлюз: 192.168.1.10`
|
||||
|
||||
Это заставит TV слать трафик через 192.168.1.10 (gateway node) вместо роутера.
|
||||
|
||||
---
|
||||
|
||||
## Полные конфигурационные файлы
|
||||
|
||||
### sing-box config (config.json)
|
||||
|
||||
Генерируется автоматически шаблоном. Полная структура:
|
||||
|
||||
```json
|
||||
{
|
||||
"log": { "level": "info", "timestamp": true },
|
||||
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "tag": "dns-proxy", "address": "tls://8.8.8.8", "detour": "proxy" },
|
||||
{ "tag": "dns-direct", "address": "udp://8.8.4.4", "detour": "direct" },
|
||||
{ "tag": "dns-ru", "address": "udp://192.168.1.1", "detour": "direct" }
|
||||
],
|
||||
"rules": [
|
||||
{ "outbound": "any", "server": "dns-direct" },
|
||||
{ "rule_set": ["geosite-youtube", "geosite-google"], "server": "dns-proxy" },
|
||||
{ "rule_set": ["geosite-category-ru"], "server": "dns-ru" },
|
||||
{ "domain_suffix": [".ru", ".рф", ".su"], "server": "dns-ru" }
|
||||
],
|
||||
"final": "dns-direct",
|
||||
"independent_cache": true,
|
||||
"reverse_mapping": true
|
||||
},
|
||||
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tproxy",
|
||||
"tag": "tproxy-in",
|
||||
"listen": "::",
|
||||
"listen_port": 7893,
|
||||
"tcp_fast_open": true,
|
||||
"udp_fragment": true,
|
||||
"sniff": true,
|
||||
"sniff_override_destination": true,
|
||||
"domain_strategy": "prefer_ipv4",
|
||||
"udp_timeout": "5m"
|
||||
}
|
||||
],
|
||||
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "hysteria2",
|
||||
"tag": "proxy",
|
||||
"server": "vps.example.com",
|
||||
"server_port": 443,
|
||||
"password": "YOUR_PASSWORD",
|
||||
"obfs": { "type": "salamander", "salamander": { "password": "OBFS" } },
|
||||
"tls": { "enabled": true, "insecure": false }
|
||||
},
|
||||
{ "type": "direct", "tag": "direct" },
|
||||
{ "type": "block", "tag": "block" },
|
||||
{ "type": "dns", "tag": "dns-out" }
|
||||
],
|
||||
|
||||
"route": {
|
||||
"rule_set": [
|
||||
{ "tag": "geosite-youtube", "type": "remote", "format": "binary",
|
||||
"url": "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite-youtube.srs" },
|
||||
{ "tag": "geosite-google", "type": "remote", "format": "binary",
|
||||
"url": "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite-google.srs" },
|
||||
{ "tag": "geosite-category-ru", "type": "remote", "format": "binary",
|
||||
"url": "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite-category-ru.srs" },
|
||||
{ "tag": "geoip-ru", "type": "remote", "format": "binary",
|
||||
"url": "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip-ru.srs" }
|
||||
],
|
||||
"rules": [
|
||||
{ "protocol": "dns", "outbound": "dns-out" },
|
||||
{ "ip_is_private": true, "outbound": "direct" },
|
||||
{
|
||||
"type": "logical", "mode": "or",
|
||||
"rules": [
|
||||
{ "rule_set": ["geosite-youtube"] },
|
||||
{ "domain_keyword": ["youtube", "googlevideo", "ytimg", "ggpht"] }
|
||||
],
|
||||
"outbound": "proxy"
|
||||
},
|
||||
{
|
||||
"type": "logical", "mode": "or",
|
||||
"rules": [
|
||||
{ "rule_set": ["geosite-category-ru", "geoip-ru"] },
|
||||
{ "domain_suffix": [".ru", ".рф", ".su"] },
|
||||
{ "domain_keyword": ["yandex", "kinopoisk", "vk.com"] }
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"final": "direct",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### iptables TPROXY правила
|
||||
|
||||
```bash
|
||||
# Таблица маршрутизации: помеченные пакеты → локальные
|
||||
ip rule add fwmark 0x1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
# Цепочка SPLITGW
|
||||
iptables -t mangle -N SPLITGW
|
||||
iptables -t mangle -A SPLITGW -i lo -j RETURN
|
||||
iptables -t mangle -A SPLITGW -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 127.0.0.0/8 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 10.0.0.0/8 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 172.16.0.0/12 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 192.168.0.0/16 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -p tcp -j TPROXY --on-port 7893 --tproxy-mark 0x1/0x1
|
||||
iptables -t mangle -A SPLITGW -p udp -j TPROXY --on-port 7893 --tproxy-mark 0x1/0x1
|
||||
|
||||
# Применить к трафику от TV
|
||||
iptables -t mangle -A PREROUTING -s 192.168.1.100/32 -j SPLITGW
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes DaemonSet (альтернатива systemd)
|
||||
|
||||
Используй когда gateway node — нода K3S кластера.
|
||||
|
||||
```bash
|
||||
# 1. Пометить ноду
|
||||
kubectl label node <gateway-node> splitgw=true
|
||||
|
||||
# 2. Деплой
|
||||
make addon-splitgw ARGS="-e splitgw_deploy_mode=k8s"
|
||||
```
|
||||
|
||||
DaemonSet запускает sing-box с `hostNetwork: true`. Init-контейнер (privileged) применяет iptables правила на хосте. Конфиг — в ConfigMap.
|
||||
|
||||
---
|
||||
|
||||
## Параметры
|
||||
|
||||
```yaml
|
||||
# group_vars/all/main.yml
|
||||
|
||||
# TV устройства (один или несколько IP/подсетей)
|
||||
splitgw_tv_sources:
|
||||
- "192.168.1.100/32" # Android TV
|
||||
- "192.168.1.101/32" # SmartTV на кухне
|
||||
|
||||
splitgw_router_ip: "192.168.1.1" # Keenetic (для RU DNS)
|
||||
splitgw_tproxy_port: 7893 # порт sing-box
|
||||
splitgw_singbox_log_level: "info" # trace|debug|info|warn|error
|
||||
|
||||
# Hysteria2 (из vault — те же что используются в mediaserver)
|
||||
# vault_hysteria2_url: "hysteria2://..." ← уже в vault
|
||||
|
||||
# Режим деплоя
|
||||
splitgw_deploy_mode: "host" # host | k8s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Проверка работы
|
||||
|
||||
### 1. Статус сервисов
|
||||
|
||||
```bash
|
||||
# На gateway node:
|
||||
systemctl status singbox
|
||||
systemctl status splitgw-rules
|
||||
|
||||
# Логи sing-box в реальном времени:
|
||||
journalctl -u singbox -f
|
||||
|
||||
# Проверить конфиг:
|
||||
sing-box check --config /etc/sing-box/config.json
|
||||
```
|
||||
|
||||
### 2. iptables правила
|
||||
|
||||
```bash
|
||||
# Посмотреть цепочку SPLITGW:
|
||||
iptables -t mangle -L SPLITGW -v -n
|
||||
|
||||
# Посмотреть rule для fwmark:
|
||||
ip rule list | grep 0x1
|
||||
|
||||
# Посмотреть таблицу 100:
|
||||
ip route show table 100
|
||||
```
|
||||
|
||||
### 3. Трафик с TV
|
||||
|
||||
```bash
|
||||
# На gateway node — смотреть трафик с TV:
|
||||
tcpdump -i any -nn "src host 192.168.1.100" -c 20
|
||||
|
||||
# Проверить что TPROXY работает (пакеты приходят в sing-box):
|
||||
ss -tnlp | grep 7893
|
||||
|
||||
# Реальный тест: с Android TV открыть YouTube, затем:
|
||||
conntrack -L -s 192.168.1.100 | grep ESTABLISHED | head -20
|
||||
```
|
||||
|
||||
### 4. Проверка маршрутов
|
||||
|
||||
```bash
|
||||
# Проверить что YouTube идёт через прокси:
|
||||
# Подключиться к TV по ADB (Android Debug Bridge):
|
||||
adb connect 192.168.1.100
|
||||
adb shell curl -s https://ifconfig.me # должен вернуть IP VPS
|
||||
|
||||
# Проверить что ВКонтакте идёт напрямую:
|
||||
adb shell curl -s https://api.vk.com # должен работать с RU IP
|
||||
```
|
||||
|
||||
### 5. DNS проверка
|
||||
|
||||
```bash
|
||||
# Проверить что DNS для YouTube резолвится через прокси:
|
||||
# На gateway node:
|
||||
dig youtube.com @8.8.8.8 +short
|
||||
# Должен вернуть зарубежный IP (не российский CDN)
|
||||
|
||||
# Лог DNS запросов в sing-box (включить debug):
|
||||
sing-box run --config /etc/sing-box/config.json 2>&1 | grep -i dns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Диагностика проблем
|
||||
|
||||
### YouTube не работает через прокси
|
||||
|
||||
```bash
|
||||
# 1. Проверить что sing-box запущен:
|
||||
systemctl status singbox
|
||||
|
||||
# 2. Проверить iptables:
|
||||
iptables -t mangle -L PREROUTING -v -n | grep SPLITGW
|
||||
|
||||
# 3. Проверить подключение к Hysteria2 серверу:
|
||||
sing-box run --config /etc/sing-box/config.json 2>&1 | grep -E "(hysteria|proxy|connect)"
|
||||
|
||||
# 4. Включить debug логи временно:
|
||||
sed -i 's/"level": "info"/"level": "debug"/' /etc/sing-box/config.json
|
||||
systemctl restart singbox
|
||||
journalctl -u singbox -f | grep -i youtube
|
||||
```
|
||||
|
||||
### RU сервисы идут через прокси (не должны)
|
||||
|
||||
```bash
|
||||
# Проверить что geoip-ru БД загружена:
|
||||
ls -la /var/lib/sing-box/*.srs
|
||||
|
||||
# Перезапустить sing-box (загружает БД при старте):
|
||||
systemctl restart singbox
|
||||
|
||||
# Проверить routing rule для yandex.ru:
|
||||
sing-box run --config /etc/sing-box/config.json 2>&1 | grep yandex
|
||||
```
|
||||
|
||||
### TV не видит интернет
|
||||
|
||||
```bash
|
||||
# 1. Включить IP forwarding на gateway:
|
||||
sysctl net.ipv4.ip_forward # должно быть 1
|
||||
|
||||
# 2. Проверить что TV трафик доходит:
|
||||
tcpdump -i any -nn "src host TV_IP"
|
||||
|
||||
# 3. Проверить маршрут на шлюзе:
|
||||
ip route get 8.8.8.8 from TV_IP # должен показать интерфейс
|
||||
|
||||
# 4. Проверить что sing-box отвечает:
|
||||
curl -x socks5h://127.0.0.1:1080 https://ifconfig.me # если включён SOCKS5
|
||||
```
|
||||
|
||||
### Keenetic не маршрутизирует трафик через шлюз
|
||||
|
||||
```bash
|
||||
# С самого TV (если есть ADB):
|
||||
adb shell ip route # проверить default gateway
|
||||
|
||||
# На роутере через CLI:
|
||||
show ip route
|
||||
# Должен быть маршрут: 192.168.1.100/32 via 192.168.1.10
|
||||
|
||||
# Попробовать ping от роутера до шлюза:
|
||||
ping 192.168.1.10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Мониторинг
|
||||
|
||||
### Логи (по умолчанию info)
|
||||
|
||||
```bash
|
||||
# Текущие логи:
|
||||
journalctl -u singbox -n 100
|
||||
|
||||
# Режим слежения:
|
||||
journalctl -u singbox -f
|
||||
|
||||
# Фильтр ошибок:
|
||||
journalctl -u singbox | grep -E "(ERR|WARN)"
|
||||
```
|
||||
|
||||
### Статистика трафика
|
||||
|
||||
```bash
|
||||
# Счётчики iptables цепочки SPLITGW:
|
||||
iptables -t mangle -L SPLITGW -v -n
|
||||
|
||||
# Активные соединения через sing-box:
|
||||
cat /proc/net/nf_conntrack | grep "src=192.168.1.100" | wc -l
|
||||
```
|
||||
|
||||
### Prometheus (если установлен prometheus-stack)
|
||||
|
||||
sing-box не экспортирует метрики нативно. Для мониторинга используй:
|
||||
|
||||
```yaml
|
||||
# Prometheus node-exporter метрики iptables (через iptables-exporter или вручную):
|
||||
# В ServiceMonitor добавить textfile collector с iptables счётчиками
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Обновление
|
||||
|
||||
```bash
|
||||
# Обновить sing-box до последней версии:
|
||||
make addon-splitgw
|
||||
|
||||
# Обновить только конфиг (изменил переменные):
|
||||
make addon-splitgw ARGS="--tags config"
|
||||
|
||||
# Добавить новый TV:
|
||||
# group_vars/all/main.yml:
|
||||
# splitgw_tv_sources:
|
||||
# - "192.168.1.100/32"
|
||||
# - "192.168.1.105/32" ← новый TV
|
||||
make addon-splitgw
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Деинсталляция
|
||||
|
||||
```bash
|
||||
# На gateway node:
|
||||
systemctl disable --now singbox splitgw-rules
|
||||
rm -f /etc/systemd/system/{singbox,splitgw-rules}.service
|
||||
rm -rf /etc/sing-box /var/lib/sing-box /var/log/sing-box
|
||||
rm -f /usr/local/bin/sing-box
|
||||
systemctl daemon-reload
|
||||
|
||||
# Удалить iptables правила:
|
||||
iptables -t mangle -D PREROUTING -s 192.168.1.100/32 -j SPLITGW
|
||||
iptables -t mangle -F SPLITGW
|
||||
iptables -t mangle -X SPLITGW
|
||||
ip rule del fwmark 0x1 lookup 100
|
||||
ip route flush table 100
|
||||
```
|
||||
7
addons/splitgw/playbook.yml
Normal file
7
addons/splitgw/playbook.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Install Split Gateway (sing-box + Hysteria2 TPROXY)
|
||||
hosts: splitgw
|
||||
gather_facts: true
|
||||
become: true
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/role"
|
||||
77
addons/splitgw/role/defaults/main.yml
Normal file
77
addons/splitgw/role/defaults/main.yml
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
# ─── Split Gateway — прозрачный split-tunnel через Hysteria2 + sing-box ───────
|
||||
# YouTube → Hysteria2 (зарубежный IP)
|
||||
# RU сервисы → direct
|
||||
# Всё остальное → direct
|
||||
|
||||
# ─── IP устройств, трафик которых нужно проксировать ─────────────────────────
|
||||
# Можно указать один IP или несколько подсетей
|
||||
splitgw_tv_sources:
|
||||
- "192.168.1.100/32" # Android TV (ЗАМЕНИ на реальный IP)
|
||||
|
||||
# ─── IP роутера Keenetic (для RU DNS) ─────────────────────────────────────────
|
||||
splitgw_router_ip: "192.168.1.1"
|
||||
|
||||
# ─── Hysteria2 — берётся из vault (те же переменные что и в mediaserver) ──────
|
||||
# Приоритет: URL → отдельные поля
|
||||
splitgw_hysteria2_url: "{{ vault_hysteria2_url | default('') }}"
|
||||
splitgw_hysteria2_server: "{{ vault_hysteria2_server | default('') }}"
|
||||
splitgw_hysteria2_auth: "{{ vault_hysteria2_auth | default('') }}"
|
||||
splitgw_hysteria2_insecure: false
|
||||
splitgw_hysteria2_obfs_type: ""
|
||||
splitgw_hysteria2_obfs_password: ""
|
||||
|
||||
# ─── Sing-box ─────────────────────────────────────────────────────────────────
|
||||
splitgw_singbox_version: "" # пусто = автоопределение последней
|
||||
splitgw_singbox_config_dir: /etc/sing-box
|
||||
splitgw_singbox_data_dir: /var/lib/sing-box # geoip/geosite БД
|
||||
splitgw_singbox_log_dir: /var/log/sing-box
|
||||
splitgw_singbox_log_level: "info" # trace|debug|info|warn|error
|
||||
|
||||
# ─── TPROXY параметры ─────────────────────────────────────────────────────────
|
||||
splitgw_tproxy_port: 7893
|
||||
splitgw_tproxy_mark: "0x1"
|
||||
splitgw_tproxy_table: 100
|
||||
|
||||
# ─── Маршрутизация ────────────────────────────────────────────────────────────
|
||||
# Дополнительные домены для YouTube (кроме geosite-youtube)
|
||||
splitgw_youtube_extra_keywords:
|
||||
- "youtube"
|
||||
- "googlevideo"
|
||||
- "ytimg"
|
||||
- "ggpht"
|
||||
- "googleusercontent"
|
||||
- "youtu.be"
|
||||
- "gvt1.com"
|
||||
- "youtube-nocookie"
|
||||
|
||||
# Дополнительные RU домены/ключевые слова (кроме geosite-category-ru + geoip-ru)
|
||||
splitgw_ru_extra_suffixes:
|
||||
- ".ru"
|
||||
- ".рф"
|
||||
- ".su"
|
||||
|
||||
splitgw_ru_extra_keywords:
|
||||
- "yandex"
|
||||
- "kinopoisk"
|
||||
- "mail.ru"
|
||||
- "ok.ru"
|
||||
- "vk.com"
|
||||
- "sberbank"
|
||||
- "tinkoff"
|
||||
- "gosuslugi"
|
||||
- "mos.ru"
|
||||
- "avito"
|
||||
- "hh.ru"
|
||||
- "wildberries"
|
||||
- "ozon"
|
||||
|
||||
# ─── Режим деплоя ─────────────────────────────────────────────────────────────
|
||||
# host — systemd сервис прямо на хосте (рекомендуется)
|
||||
# k8s — DaemonSet в K3S кластере
|
||||
splitgw_deploy_mode: "host"
|
||||
|
||||
# ─── K8s режим (splitgw_deploy_mode: k8s) ────────────────────────────────────
|
||||
splitgw_k8s_namespace: "splitgw"
|
||||
# Метка на ноде-шлюзе: kubectl label node <node> splitgw=true
|
||||
splitgw_k8s_node_label: "splitgw=true"
|
||||
17
addons/splitgw/role/handlers/main.yml
Normal file
17
addons/splitgw/role/handlers/main.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
become: true
|
||||
|
||||
- name: Restart splitgw-rules
|
||||
ansible.builtin.systemd:
|
||||
name: splitgw-rules
|
||||
state: restarted
|
||||
become: true
|
||||
|
||||
- name: Restart singbox
|
||||
ansible.builtin.systemd:
|
||||
name: singbox
|
||||
state: restarted
|
||||
become: true
|
||||
217
addons/splitgw/role/tasks/deploy-host.yml
Normal file
217
addons/splitgw/role/tasks/deploy-host.yml
Normal file
@@ -0,0 +1,217 @@
|
||||
---
|
||||
# ── Определить архитектуру и install_dir ─────────────────────────────────────
|
||||
|
||||
- name: Set sing-box install dir
|
||||
ansible.builtin.set_fact:
|
||||
splitgw_singbox_install_dir: /usr/local/bin
|
||||
_splitgw_arch: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}"
|
||||
|
||||
# ── Получить последнюю версию sing-box ───────────────────────────────────────
|
||||
|
||||
- name: Get latest sing-box release from GitHub
|
||||
ansible.builtin.uri:
|
||||
url: https://api.github.com/repos/SagerNet/sing-box/releases/latest
|
||||
return_content: true
|
||||
headers:
|
||||
Accept: "application/vnd.github.v3+json"
|
||||
register: _singbox_release
|
||||
when: splitgw_singbox_version == ""
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
run_once: true
|
||||
|
||||
- name: Set sing-box version fact
|
||||
ansible.builtin.set_fact:
|
||||
_singbox_version: >-
|
||||
{{ (splitgw_singbox_version != '')
|
||||
| ternary(splitgw_singbox_version,
|
||||
_singbox_release.json.tag_name | regex_replace('^v', '')) }}
|
||||
run_once: true
|
||||
|
||||
- name: Show sing-box version
|
||||
ansible.builtin.debug:
|
||||
msg: "Installing sing-box v{{ _singbox_version }} ({{ _splitgw_arch }})"
|
||||
|
||||
# ── Зависимости ───────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Install dependencies
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- iptables
|
||||
- iproute2
|
||||
- ca-certificates
|
||||
- curl
|
||||
state: present
|
||||
become: true
|
||||
|
||||
# ── Скачать sing-box ──────────────────────────────────────────────────────────
|
||||
|
||||
- name: Set sing-box download URL
|
||||
ansible.builtin.set_fact:
|
||||
_singbox_url: >-
|
||||
https://github.com/SagerNet/sing-box/releases/download/v{{ _singbox_version
|
||||
}}/sing-box-{{ _singbox_version }}-linux-{{ _splitgw_arch }}.tar.gz
|
||||
|
||||
- name: Download sing-box archive
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ _singbox_url }}"
|
||||
dest: /tmp/sing-box.tar.gz
|
||||
mode: "0644"
|
||||
become: true
|
||||
register: _singbox_downloaded
|
||||
|
||||
- name: Extract sing-box binary
|
||||
ansible.builtin.unarchive:
|
||||
src: /tmp/sing-box.tar.gz
|
||||
dest: /tmp/
|
||||
remote_src: true
|
||||
creates: "/tmp/sing-box-{{ _singbox_version }}-linux-{{ _splitgw_arch }}/sing-box"
|
||||
become: true
|
||||
when: _singbox_downloaded.changed
|
||||
|
||||
- name: Install sing-box binary
|
||||
ansible.builtin.copy:
|
||||
src: "/tmp/sing-box-{{ _singbox_version }}-linux-{{ _splitgw_arch }}/sing-box"
|
||||
dest: "{{ splitgw_singbox_install_dir }}/sing-box"
|
||||
mode: "0755"
|
||||
owner: root
|
||||
group: root
|
||||
remote_src: true
|
||||
become: true
|
||||
notify: Restart singbox
|
||||
|
||||
# ── Директории ────────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Create sing-box directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ splitgw_singbox_config_dir }}"
|
||||
- "{{ splitgw_singbox_data_dir }}"
|
||||
- "{{ splitgw_singbox_log_dir }}"
|
||||
become: true
|
||||
|
||||
# ── Конфиги ───────────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Template sing-box config
|
||||
ansible.builtin.template:
|
||||
src: singbox-config.json.j2
|
||||
dest: "{{ splitgw_singbox_config_dir }}/config.json"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
become: true
|
||||
notify: Restart singbox
|
||||
|
||||
- name: Template iptables setup script
|
||||
ansible.builtin.template:
|
||||
src: setup-iptables.sh.j2
|
||||
dest: "{{ splitgw_singbox_config_dir }}/setup-iptables.sh"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
become: true
|
||||
|
||||
- name: Template iptables teardown script
|
||||
ansible.builtin.template:
|
||||
src: teardown-iptables.sh.j2
|
||||
dest: "{{ splitgw_singbox_config_dir }}/teardown-iptables.sh"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
become: true
|
||||
|
||||
# ── sysctl persist ────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Persist IP forwarding in sysctl
|
||||
ansible.posix.sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: "1"
|
||||
state: present
|
||||
sysctl_set: true
|
||||
reload: true
|
||||
become: true
|
||||
|
||||
# ── Systemd сервисы ───────────────────────────────────────────────────────────
|
||||
|
||||
- name: Template splitgw-rules systemd service
|
||||
ansible.builtin.template:
|
||||
src: splitgw-rules.service.j2
|
||||
dest: /etc/systemd/system/splitgw-rules.service
|
||||
mode: "0644"
|
||||
become: true
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Restart splitgw-rules
|
||||
|
||||
- name: Template singbox systemd service
|
||||
ansible.builtin.template:
|
||||
src: singbox.service.j2
|
||||
dest: /etc/systemd/system/singbox.service
|
||||
mode: "0644"
|
||||
become: true
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Restart singbox
|
||||
|
||||
- name: Enable and start splitgw-rules
|
||||
ansible.builtin.systemd:
|
||||
name: splitgw-rules
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
become: true
|
||||
|
||||
- name: Enable and start sing-box
|
||||
ansible.builtin.systemd:
|
||||
name: singbox
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
become: true
|
||||
|
||||
# ── Проверка ─────────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Wait for sing-box to start (TPROXY port)
|
||||
ansible.builtin.wait_for:
|
||||
port: "{{ splitgw_tproxy_port }}"
|
||||
host: "0.0.0.0"
|
||||
timeout: 30
|
||||
|
||||
- name: Verify sing-box config is valid
|
||||
ansible.builtin.command: >
|
||||
sing-box check --config {{ splitgw_singbox_config_dir }}/config.json
|
||||
become: true
|
||||
changed_when: false
|
||||
register: _singbox_check
|
||||
failed_when: _singbox_check.rc != 0
|
||||
|
||||
- name: Show iptables SPLITGW rules
|
||||
ansible.builtin.command: iptables -t mangle -L SPLITGW -v -n
|
||||
become: true
|
||||
changed_when: false
|
||||
register: _iptables_rules
|
||||
|
||||
- name: "=== Split Gateway Ready ==="
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "╔══════════════════════════════════════════════════════════════╗"
|
||||
- "║ Split Gateway (sing-box) установлен ║"
|
||||
- "╚══════════════════════════════════════════════════════════════╝"
|
||||
- ""
|
||||
- " Режим: systemd (host)"
|
||||
- " Sing-box: v{{ _singbox_version }}"
|
||||
- " TPROXY порт: {{ splitgw_tproxy_port }}"
|
||||
- " TV источники: {{ splitgw_tv_sources | join(', ') }}"
|
||||
- " Прокси: {{ _splitgw_hy2_host }}:{{ _splitgw_hy2_port }}"
|
||||
- ""
|
||||
- " YouTube → Hysteria2 ({{ _splitgw_hy2_host }})"
|
||||
- " RU + geoip:ru → прямой маршрут"
|
||||
- " Всё остальное → прямой маршрут"
|
||||
- ""
|
||||
- " Логи: journalctl -u singbox -f"
|
||||
- " Статус: systemctl status singbox splitgw-rules"
|
||||
74
addons/splitgw/role/tasks/deploy-k8s.yml
Normal file
74
addons/splitgw/role/tasks/deploy-k8s.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
- name: Create splitgw namespace
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl create namespace {{ splitgw_k8s_namespace }}
|
||||
--dry-run=client -o yaml | k3s kubectl apply -f -
|
||||
become: true
|
||||
delegate_to: "{{ groups['k3s_master'][0] }}"
|
||||
run_once: true
|
||||
changed_when: false
|
||||
|
||||
- name: Template K8s ConfigMap
|
||||
ansible.builtin.template:
|
||||
src: k8s/configmap.yaml.j2
|
||||
dest: /tmp/splitgw-configmap.yaml
|
||||
mode: "0644"
|
||||
delegate_to: "{{ groups['k3s_master'][0] }}"
|
||||
run_once: true
|
||||
|
||||
- name: Template K8s Secret
|
||||
ansible.builtin.template:
|
||||
src: k8s/secret.yaml.j2
|
||||
dest: /tmp/splitgw-secret.yaml
|
||||
mode: "0600"
|
||||
delegate_to: "{{ groups['k3s_master'][0] }}"
|
||||
run_once: true
|
||||
|
||||
- name: Template K8s DaemonSet
|
||||
ansible.builtin.template:
|
||||
src: k8s/daemonset.yaml.j2
|
||||
dest: /tmp/splitgw-daemonset.yaml
|
||||
mode: "0644"
|
||||
delegate_to: "{{ groups['k3s_master'][0] }}"
|
||||
run_once: true
|
||||
|
||||
- name: Apply ConfigMap, Secret, DaemonSet
|
||||
kubernetes.core.k8s:
|
||||
src: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- /tmp/splitgw-configmap.yaml
|
||||
- /tmp/splitgw-secret.yaml
|
||||
- /tmp/splitgw-daemonset.yaml
|
||||
become: true
|
||||
delegate_to: "{{ groups['k3s_master'][0] }}"
|
||||
run_once: true
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Wait for DaemonSet pods to be ready
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl -n {{ splitgw_k8s_namespace }}
|
||||
rollout status daemonset/splitgw --timeout=120s
|
||||
become: true
|
||||
delegate_to: "{{ groups['k3s_master'][0] }}"
|
||||
run_once: true
|
||||
changed_when: false
|
||||
|
||||
- name: Show pod status
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl -n {{ splitgw_k8s_namespace }} get pods -o wide
|
||||
become: true
|
||||
delegate_to: "{{ groups['k3s_master'][0] }}"
|
||||
run_once: true
|
||||
register: _splitgw_pods
|
||||
changed_when: false
|
||||
|
||||
- name: "=== Split Gateway K8s Ready ==="
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Split Gateway DaemonSet deployed to namespace: {{ splitgw_k8s_namespace }}"
|
||||
- "Убедись что нода помечена: kubectl label node <node> splitgw=true"
|
||||
- ""
|
||||
- "{{ _splitgw_pods.stdout_lines }}"
|
||||
run_once: true
|
||||
75
addons/splitgw/role/tasks/main.yml
Normal file
75
addons/splitgw/role/tasks/main.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
# ── Парсинг Hysteria2 URL ─────────────────────────────────────────────────────
|
||||
|
||||
- name: Parse Hysteria2 URL into components
|
||||
ansible.builtin.command: >-
|
||||
python3 -c "
|
||||
import sys, json
|
||||
from urllib.parse import urlparse, parse_qs, unquote
|
||||
url = sys.argv[1]
|
||||
p = urlparse(url)
|
||||
qs = parse_qs(p.query, keep_blank_values=True)
|
||||
host = p.hostname or ''
|
||||
port = p.port or 443
|
||||
print(json.dumps({
|
||||
'host': host,
|
||||
'port': port,
|
||||
'password': unquote(p.username or ''),
|
||||
'insecure': qs.get('insecure', ['0'])[0] == '1',
|
||||
'obfs_type': qs.get('obfs', [''])[0],
|
||||
'obfs_password': qs.get('obfs-password', [''])[0],
|
||||
}))
|
||||
" "{{ splitgw_hysteria2_url }}"
|
||||
register: _splitgw_url_parsed
|
||||
changed_when: false
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
run_once: true
|
||||
when: splitgw_hysteria2_url != ""
|
||||
|
||||
- name: Set Hysteria2 facts from URL
|
||||
ansible.builtin.set_fact:
|
||||
_splitgw_hy2_host: "{{ (_splitgw_url_parsed.stdout | from_json).host }}"
|
||||
_splitgw_hy2_port: "{{ (_splitgw_url_parsed.stdout | from_json).port }}"
|
||||
_splitgw_hy2_password: "{{ (_splitgw_url_parsed.stdout | from_json).password }}"
|
||||
_splitgw_hy2_insecure: "{{ (_splitgw_url_parsed.stdout | from_json).insecure }}"
|
||||
_splitgw_hy2_obfs_type: "{{ (_splitgw_url_parsed.stdout | from_json).obfs_type }}"
|
||||
_splitgw_hy2_obfs_password: "{{ (_splitgw_url_parsed.stdout | from_json).obfs_password }}"
|
||||
when: splitgw_hysteria2_url != "" and _splitgw_url_parsed.rc == 0
|
||||
|
||||
- name: Set Hysteria2 facts from individual vars (fallback)
|
||||
ansible.builtin.set_fact:
|
||||
_splitgw_hy2_host: "{{ splitgw_hysteria2_server | regex_replace(':.*', '') }}"
|
||||
_splitgw_hy2_port: "{{ splitgw_hysteria2_server | regex_replace('.*:', '') | default('443') }}"
|
||||
_splitgw_hy2_password: "{{ splitgw_hysteria2_auth }}"
|
||||
_splitgw_hy2_insecure: "{{ splitgw_hysteria2_insecure }}"
|
||||
_splitgw_hy2_obfs_type: "{{ splitgw_hysteria2_obfs_type }}"
|
||||
_splitgw_hy2_obfs_password: "{{ splitgw_hysteria2_obfs_password }}"
|
||||
when: splitgw_hysteria2_url == ""
|
||||
|
||||
- name: Assert Hysteria2 credentials are set
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _splitgw_hy2_host != ""
|
||||
- _splitgw_hy2_password != ""
|
||||
fail_msg: |
|
||||
Задай Hysteria2 credentials — в vault.yml:
|
||||
vault_hysteria2_url: "hysteria2://password@host:port?insecure=1"
|
||||
ИЛИ:
|
||||
vault_hysteria2_server: "host:443"
|
||||
vault_hysteria2_auth: "password"
|
||||
run_once: true
|
||||
|
||||
- name: Show Hysteria2 connection target
|
||||
ansible.builtin.debug:
|
||||
msg: "sing-box → Hysteria2: {{ _splitgw_hy2_host }}:{{ _splitgw_hy2_port }} (insecure={{ _splitgw_hy2_insecure }}, obfs={{ _splitgw_hy2_obfs_type | default('none') }})"
|
||||
|
||||
# ── Деплой ───────────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Deploy to host (systemd mode)
|
||||
ansible.builtin.include_tasks: deploy-host.yml
|
||||
when: splitgw_deploy_mode == "host"
|
||||
|
||||
- name: Deploy to K3S (DaemonSet mode)
|
||||
ansible.builtin.include_tasks: deploy-k8s.yml
|
||||
when: splitgw_deploy_mode == "k8s"
|
||||
15
addons/splitgw/role/templates/k8s/configmap.yaml.j2
Normal file
15
addons/splitgw/role/templates/k8s/configmap.yaml.j2
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: splitgw-config
|
||||
namespace: {{ splitgw_k8s_namespace }}
|
||||
data:
|
||||
config.json: |
|
||||
{{ lookup('template', '../singbox-config.json.j2') | indent(4, True) }}
|
||||
|
||||
setup-iptables.sh: |
|
||||
{{ lookup('template', '../setup-iptables.sh.j2') | indent(4, True) }}
|
||||
|
||||
teardown-iptables.sh: |
|
||||
{{ lookup('template', '../teardown-iptables.sh.j2') | indent(4, True) }}
|
||||
104
addons/splitgw/role/templates/k8s/daemonset.yaml.j2
Normal file
104
addons/splitgw/role/templates/k8s/daemonset.yaml.j2
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: splitgw
|
||||
namespace: {{ splitgw_k8s_namespace }}
|
||||
labels:
|
||||
app: splitgw
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: splitgw
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: splitgw
|
||||
spec:
|
||||
hostNetwork: true # Необходим для перехвата хостового трафика
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
nodeSelector:
|
||||
splitgw: "true" # kubectl label node <node> splitgw=true
|
||||
|
||||
# Приоритет — чтобы pod не вытесняли
|
||||
priorityClassName: system-node-critical
|
||||
|
||||
tolerations:
|
||||
- operator: Exists # Запускать на любых нодах с меткой (включая tainted)
|
||||
|
||||
initContainers:
|
||||
# Init: установить iptables TPROXY правила на хосте
|
||||
- name: init-iptables
|
||||
image: alpine:latest
|
||||
securityContext:
|
||||
privileged: true # Нужен для изменения iptables и ip route хоста
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
apk add -q iptables ip6tables iproute2
|
||||
chmod +x /scripts/setup-iptables.sh
|
||||
/scripts/setup-iptables.sh
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /scripts/setup-iptables.sh
|
||||
subPath: setup-iptables.sh
|
||||
|
||||
containers:
|
||||
- name: singbox
|
||||
image: ghcr.io/sagernet/sing-box:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- run
|
||||
- --config
|
||||
- /etc/sing-box/config.json
|
||||
- --directory
|
||||
- /var/lib/sing-box
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
- NET_BIND_SERVICE
|
||||
ports:
|
||||
- name: tproxy
|
||||
containerPort: {{ splitgw_tproxy_port }}
|
||||
protocol: TCP
|
||||
- name: tproxy-udp
|
||||
containerPort: {{ splitgw_tproxy_port }}
|
||||
protocol: UDP
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/sing-box/config.json
|
||||
subPath: config.json
|
||||
- name: data
|
||||
mountPath: /var/lib/sing-box
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "sing-box check --config /etc/sing-box/config.json"
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
|
||||
# Очистка правил при удалении пода
|
||||
terminationGracePeriodSeconds: 10
|
||||
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: splitgw-config
|
||||
defaultMode: 0755
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
7
addons/splitgw/role/templates/k8s/namespace.yaml.j2
Normal file
7
addons/splitgw/role/templates/k8s/namespace.yaml.j2
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{ splitgw_k8s_namespace }}
|
||||
labels:
|
||||
name: {{ splitgw_k8s_namespace }}
|
||||
10
addons/splitgw/role/templates/k8s/secret.yaml.j2
Normal file
10
addons/splitgw/role/templates/k8s/secret.yaml.j2
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: splitgw-hy2-credentials
|
||||
namespace: {{ splitgw_k8s_namespace }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
hy2-server: "{{ _splitgw_hy2_host }}:{{ _splitgw_hy2_port }}"
|
||||
hy2-password: "{{ _splitgw_hy2_password }}"
|
||||
77
addons/splitgw/role/templates/setup-iptables.sh.j2
Normal file
77
addons/splitgw/role/templates/setup-iptables.sh.j2
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/bin/sh
|
||||
# ─── Split Gateway — iptables TPROXY setup ────────────────────────────────────
|
||||
# Перехватывает TCP+UDP от TV через TPROXY → sing-box (порт {{ splitgw_tproxy_port }})
|
||||
# DNS (порт 53) перехватывается тоже — sing-box обрабатывает как dns-out
|
||||
#
|
||||
# Источники трафика: {{ splitgw_tv_sources | join(', ') }}
|
||||
# TPROXY порт: {{ splitgw_tproxy_port }}
|
||||
# Метка пакетов: {{ splitgw_tproxy_mark }}
|
||||
# Таблица маршрутизации: {{ splitgw_tproxy_table }}
|
||||
|
||||
set -e
|
||||
|
||||
TPROXY_PORT="{{ splitgw_tproxy_port }}"
|
||||
TPROXY_MARK="{{ splitgw_tproxy_mark }}"
|
||||
TPROXY_TABLE="{{ splitgw_tproxy_table }}"
|
||||
|
||||
log() { echo "[splitgw] $*"; }
|
||||
|
||||
# ── Включить IP forwarding ────────────────────────────────────────────────────
|
||||
log "Enabling IP forwarding..."
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
sysctl -w net.ipv6.conf.all.forwarding=1
|
||||
|
||||
# Сохранить в sysctl.conf для перезагрузки
|
||||
grep -q "net.ipv4.ip_forward" /etc/sysctl.conf 2>/dev/null \
|
||||
&& sed -i 's/^#\?net\.ipv4\.ip_forward.*/net.ipv4.ip_forward = 1/' /etc/sysctl.conf \
|
||||
|| echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
|
||||
|
||||
# ── Таблица маршрутизации для помеченных пакетов ──────────────────────────────
|
||||
log "Setting up policy routing table ${TPROXY_TABLE}..."
|
||||
|
||||
# Удалить старые правила если есть
|
||||
ip rule del fwmark "$TPROXY_MARK" lookup "$TPROXY_TABLE" 2>/dev/null || true
|
||||
|
||||
# Добавить: помеченные пакеты → таблица 100
|
||||
ip rule add fwmark "$TPROXY_MARK" lookup "$TPROXY_TABLE" prio 100
|
||||
|
||||
# В таблице 100 все адреса = локальные (чтобы ядро не отбросило пакет)
|
||||
ip route flush table "$TPROXY_TABLE" 2>/dev/null || true
|
||||
ip route add local 0.0.0.0/0 dev lo table "$TPROXY_TABLE"
|
||||
|
||||
# ── Создать цепочку SPLITGW в таблице mangle ─────────────────────────────────
|
||||
log "Creating SPLITGW iptables chain..."
|
||||
|
||||
iptables -t mangle -N SPLITGW 2>/dev/null || iptables -t mangle -F SPLITGW
|
||||
|
||||
# Пропускаем loopback (не трогаем системный трафик)
|
||||
iptables -t mangle -A SPLITGW -i lo -j RETURN
|
||||
|
||||
# Пропускаем уже установленные соединения (TPROXY только для новых)
|
||||
iptables -t mangle -A SPLITGW -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
|
||||
|
||||
# Пропускаем RFC 1918 приватные диапазоны (LAN трафик идёт напрямую)
|
||||
iptables -t mangle -A SPLITGW -d 127.0.0.0/8 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 10.0.0.0/8 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 172.16.0.0/12 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 192.168.0.0/16 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 224.0.0.0/4 -j RETURN
|
||||
iptables -t mangle -A SPLITGW -d 240.0.0.0/4 -j RETURN
|
||||
|
||||
# TPROXY TCP → sing-box
|
||||
iptables -t mangle -A SPLITGW -p tcp \
|
||||
-j TPROXY --on-port "$TPROXY_PORT" --tproxy-mark "$TPROXY_MARK/$TPROXY_MARK"
|
||||
|
||||
# TPROXY UDP → sing-box (включая YouTube QUIC / DNS)
|
||||
iptables -t mangle -A SPLITGW -p udp \
|
||||
-j TPROXY --on-port "$TPROXY_PORT" --tproxy-mark "$TPROXY_MARK/$TPROXY_MARK"
|
||||
|
||||
# ── Применить цепочку к трафику от TV ─────────────────────────────────────────
|
||||
log "Applying SPLITGW to TV sources..."
|
||||
|
||||
{% for source in splitgw_tv_sources %}
|
||||
iptables -t mangle -A PREROUTING -s {{ source }} -j SPLITGW
|
||||
log " + source {{ source }}"
|
||||
{% endfor %}
|
||||
|
||||
log "TPROXY rules applied. sing-box listens on port ${TPROXY_PORT}."
|
||||
166
addons/splitgw/role/templates/singbox-config.json.j2
Normal file
166
addons/splitgw/role/templates/singbox-config.json.j2
Normal file
@@ -0,0 +1,166 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "{{ splitgw_singbox_log_level }}",
|
||||
"timestamp": true
|
||||
},
|
||||
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "dns-proxy",
|
||||
"address": "tls://8.8.8.8",
|
||||
"address_resolver": "dns-direct",
|
||||
"detour": "proxy"
|
||||
},
|
||||
{
|
||||
"tag": "dns-direct",
|
||||
"address": "udp://8.8.4.4",
|
||||
"detour": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "dns-ru",
|
||||
"address": "udp://{{ splitgw_router_ip }}",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "dns-direct"
|
||||
},
|
||||
{
|
||||
"rule_set": ["geosite-youtube", "geosite-google"],
|
||||
"server": "dns-proxy"
|
||||
},
|
||||
{
|
||||
"rule_set": ["geosite-category-ru"],
|
||||
"server": "dns-ru"
|
||||
},
|
||||
{
|
||||
"domain_suffix": {{ splitgw_ru_extra_suffixes | to_json }},
|
||||
"server": "dns-ru"
|
||||
}
|
||||
],
|
||||
"final": "dns-direct",
|
||||
"independent_cache": true,
|
||||
"reverse_mapping": true
|
||||
},
|
||||
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tproxy",
|
||||
"tag": "tproxy-in",
|
||||
"listen": "::",
|
||||
"listen_port": {{ splitgw_tproxy_port }},
|
||||
"tcp_fast_open": true,
|
||||
"udp_fragment": true,
|
||||
"sniff": true,
|
||||
"sniff_override_destination": true,
|
||||
"domain_strategy": "prefer_ipv4",
|
||||
"udp_timeout": "5m"
|
||||
}
|
||||
],
|
||||
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "hysteria2",
|
||||
"tag": "proxy",
|
||||
"server": "{{ _splitgw_hy2_host }}",
|
||||
"server_port": {{ _splitgw_hy2_port }},
|
||||
"password": "{{ _splitgw_hy2_password }}",
|
||||
{% if _splitgw_hy2_obfs_type != "" %}
|
||||
"obfs": {
|
||||
"type": "{{ _splitgw_hy2_obfs_type }}",
|
||||
"{{ _splitgw_hy2_obfs_type }}": {
|
||||
"password": "{{ _splitgw_hy2_obfs_password }}"
|
||||
}
|
||||
},
|
||||
{% endif %}
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"insecure": {{ _splitgw_hy2_insecure | lower }},
|
||||
"server_name": "{{ _splitgw_hy2_host }}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
|
||||
"route": {
|
||||
"rule_set": [
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-youtube",
|
||||
"format": "binary",
|
||||
"url": "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite-youtube.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "7d"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-google",
|
||||
"format": "binary",
|
||||
"url": "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite-google.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "7d"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-category-ru",
|
||||
"format": "binary",
|
||||
"url": "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite-category-ru.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "7d"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-ru",
|
||||
"format": "binary",
|
||||
"url": "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip-ru.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "7d"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{ "rule_set": ["geosite-youtube"] },
|
||||
{ "domain_keyword": {{ splitgw_youtube_extra_keywords | to_json }} }
|
||||
],
|
||||
"outbound": "proxy"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{ "rule_set": ["geosite-category-ru", "geoip-ru"] },
|
||||
{ "domain_suffix": {{ splitgw_ru_extra_suffixes | to_json }} },
|
||||
{ "domain_keyword": {{ splitgw_ru_extra_keywords | to_json }} }
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"final": "direct",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
27
addons/splitgw/role/templates/singbox.service.j2
Normal file
27
addons/splitgw/role/templates/singbox.service.j2
Normal file
@@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=sing-box transparent proxy (split gateway)
|
||||
Documentation=https://sing-box.sagernet.org/
|
||||
After=network.target network-online.target splitgw-rules.service
|
||||
Wants=network-online.target
|
||||
Requires=splitgw-rules.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart={{ splitgw_singbox_install_dir }}/sing-box run \
|
||||
--config {{ splitgw_singbox_config_dir }}/config.json \
|
||||
--directory {{ splitgw_singbox_data_dir }}
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
LimitNOFILE=1048576
|
||||
|
||||
# CAP_NET_ADMIN нужен для TPROXY
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
||||
|
||||
StandardOutput=append:{{ splitgw_singbox_log_dir }}/singbox.log
|
||||
StandardError=append:{{ splitgw_singbox_log_dir }}/singbox.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
13
addons/splitgw/role/templates/splitgw-rules.service.j2
Normal file
13
addons/splitgw/role/templates/splitgw-rules.service.j2
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Split Gateway iptables TPROXY rules
|
||||
After=network.target
|
||||
Before=singbox.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart={{ splitgw_singbox_config_dir }}/setup-iptables.sh
|
||||
ExecStop={{ splitgw_singbox_config_dir }}/teardown-iptables.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
23
addons/splitgw/role/templates/teardown-iptables.sh.j2
Normal file
23
addons/splitgw/role/templates/teardown-iptables.sh.j2
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
# ─── Split Gateway — iptables TPROXY teardown ────────────────────────────────
|
||||
# Удаляет правила SPLITGW и таблицу маршрутизации
|
||||
|
||||
TPROXY_MARK="{{ splitgw_tproxy_mark }}"
|
||||
TPROXY_TABLE="{{ splitgw_tproxy_table }}"
|
||||
|
||||
log() { echo "[splitgw] $*"; }
|
||||
|
||||
log "Removing SPLITGW iptables rules..."
|
||||
|
||||
{% for source in splitgw_tv_sources %}
|
||||
iptables -t mangle -D PREROUTING -s {{ source }} -j SPLITGW 2>/dev/null || true
|
||||
{% endfor %}
|
||||
|
||||
iptables -t mangle -F SPLITGW 2>/dev/null || true
|
||||
iptables -t mangle -X SPLITGW 2>/dev/null || true
|
||||
|
||||
log "Removing policy routing..."
|
||||
ip rule del fwmark "$TPROXY_MARK" lookup "$TPROXY_TABLE" 2>/dev/null || true
|
||||
ip route flush table "$TPROXY_TABLE" 2>/dev/null || true
|
||||
|
||||
log "Teardown complete."
|
||||
@@ -65,6 +65,8 @@ make addon-netbird
|
||||
| owncloud | `addon_owncloud` | ownCloud OCIS | [→](../addons/owncloud/README.md) |
|
||||
| **Медиасервер** | | | |
|
||||
| mediaserver | `addon_mediaserver` | Plex, Sonarr, Radarr, Lidarr, Bazarr, Prowlarr + Hysteria2 sidecar, Overseerr, Transmission, Samba | [→](../addons/mediaserver/README.md) |
|
||||
| **Сеть / VPN** | | | |
|
||||
| splitgw | `addon_splitgw` | Прозрачный split-tunnel gateway: sing-box + Hysteria2 TPROXY, YouTube→прокси, RU→прямой | [→](../addons/splitgw/README.md) |
|
||||
|
||||
## Конфигурация addons.yml
|
||||
|
||||
@@ -111,6 +113,9 @@ addon_smtp_relay: false
|
||||
|
||||
# ── Медиасервер ───────────────────────────────────────────────────────────────
|
||||
addon_mediaserver: false # Plex + *arr + Transmission + Prowlarr/Hysteria2 + Samba
|
||||
|
||||
# ── Split Gateway ─────────────────────────────────────────────────────────────
|
||||
addon_splitgw: false # sing-box + Hysteria2 TPROXY (host или k8s DaemonSet)
|
||||
```
|
||||
|
||||
## Зависимости между аддонами
|
||||
@@ -130,6 +135,7 @@ addon_mediaserver: false # Plex + *arr + Transmission + Prowlarr/Hyste
|
||||
| `istio` | `prometheus-stack` (опционально) | Kiali ↔ Prometheus/Grafana авто |
|
||||
| `crowdsec` | `ingress-nginx` | Bouncer интеграция при addon_crowdsec |
|
||||
| `mediaserver` | `csi-nfs` (рекомендуется) | Shared PVC требует RWX StorageClass |
|
||||
| `splitgw` | Hysteria2 сервер (vault_hysteria2_url) | URL из Shadowrocket / NekoBox |
|
||||
|
||||
## MediaServer
|
||||
|
||||
@@ -170,6 +176,42 @@ Samba получает IP от kube-vip (`LoadBalancer`) — подключен
|
||||
|
||||
Подробнее: [addons/mediaserver/README.md](../addons/mediaserver/README.md)
|
||||
|
||||
## Split Gateway
|
||||
|
||||
Прозрачный split-tunnel proxy на базе sing-box с Hysteria2 как outbound. Перехватывает трафик с TV/устройств через TPROXY и маршрутизирует по правилам: YouTube → Hysteria2, RU-сервисы и частные сети → прямой маршрут.
|
||||
|
||||
```bash
|
||||
# Режим systemd (установка на хост из группы [splitgw]):
|
||||
make addon-splitgw
|
||||
|
||||
# Режим K8s DaemonSet:
|
||||
make addon-splitgw ARGS="-e splitgw_deploy_mode=k8s"
|
||||
```
|
||||
|
||||
Настройки в `group_vars/all/addons.yml`:
|
||||
|
||||
```yaml
|
||||
# IP-адреса устройств, трафик которых перехватывается:
|
||||
splitgw_tv_sources:
|
||||
- "192.168.1.100/32" # Smart TV
|
||||
|
||||
# IP роутера (для разворота DNS):
|
||||
splitgw_router_ip: "192.168.1.1"
|
||||
|
||||
# URL из Shadowrocket / NekoBox (разбирается автоматически):
|
||||
# vault_hysteria2_url задаётся в vault.yml
|
||||
```
|
||||
|
||||
Vault секрет (тот же что для mediaserver):
|
||||
|
||||
```yaml
|
||||
vault_hysteria2_url: "hysteria2://password@vps.example.com:443?obfs=salamander&obfs-password=secret"
|
||||
```
|
||||
|
||||
Keenetic: создай политику маршрутизации для TV с шлюзом на ноду splitgw.
|
||||
|
||||
Подробнее: [addons/splitgw/README.md](../addons/splitgw/README.md)
|
||||
|
||||
---
|
||||
|
||||
## Prometheus метрики
|
||||
@@ -209,7 +251,7 @@ vault_netbird_coturn_password: ""
|
||||
vault_netbird_router_setup_key: ""
|
||||
vault_netbird_exit_node_setup_key: ""
|
||||
vault_plex_claim_token: ""
|
||||
vault_hysteria2_url: "" # URL целиком из Shadowrocket/NekoBox (или задать server+auth ниже)
|
||||
vault_hysteria2_url: "" # URL целиком из Shadowrocket/NekoBox — для mediaserver и splitgw
|
||||
vault_hysteria2_server: ""
|
||||
vault_hysteria2_auth: ""
|
||||
vault_samba_password: ""
|
||||
|
||||
@@ -40,6 +40,7 @@ addon_jenkins: false # Jenkins CI/CD (Helm, dynamic k8s agents, JC
|
||||
addon_netbird: false # NetBird VPN (управляющий сервер + subnet router + exit node)
|
||||
addon_mediaserver: false # MediaServer — Plex, *arr, Transmission, Prowlarr+Hysteria2, Samba
|
||||
addon_hysteria2_server: false # Hysteria2 VPN сервер на удалённый VPS (группа [hysteria2_server] в inventory)
|
||||
addon_splitgw: false # Split Gateway — прозрачный прокси sing-box+Hysteria2 (группа [splitgw] в inventory)
|
||||
|
||||
# ─── NFS Server ───────────────────────────────────────────────────────────────
|
||||
nfs_exports:
|
||||
|
||||
@@ -59,3 +59,12 @@ master01
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
[hysteria2_server]
|
||||
# myvps ansible_host=1.2.3.4 ansible_user=root
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Split Gateway — прозрачный прокси на основе sing-box + Hysteria2 (TPROXY)
|
||||
# YouTube → Hysteria2, RU-сервисы → прямой маршрут, всё остальное → прямой
|
||||
# Используется аддоном: make addon-splitgw
|
||||
# Пометить ноду для k8s-режима: kubectl label node <node> splitgw=true
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
[splitgw]
|
||||
# gateway01 ansible_host=192.168.1.50
|
||||
|
||||
@@ -287,3 +287,11 @@
|
||||
when: addon_hysteria2_server | default(false) | bool
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/hysteria2-server/role"
|
||||
|
||||
- name: Install Split Gateway (sing-box + Hysteria2 TPROXY)
|
||||
hosts: splitgw
|
||||
gather_facts: true
|
||||
become: true
|
||||
when: addon_splitgw | default(false) | bool
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/splitgw/role"
|
||||
|
||||
Reference in New Issue
Block a user