mirror of
https://github.com/by-sonic/tglock.git
synced 2026-09-23 10:58:28 +03:00
docs: инструкция по Cloudflare Worker + скрипт, снять флаг пререлиза (#29)
Резервный маршрут через Worker был в коде с 2.0, но воспользоваться им никто не мог: в ARCHITECTURE_V2.md описан только контракт эндпоинта — это спецификация для того, кто будет писать воркер, а не руководство. Ни скрипта, ни шагов в репозитории не было. Поэтому люди, у которых легли все обычные маршруты, писали «не работает» вместо того, чтобы включить запасной выход. Добавлено: - worker/tglock-worker.js — готовый скрипт. Проверяет путь и upgrade, подтверждает подпротокол binary (без этого клиент рвёт рукопожатие), соединяется только с семью адресами Telegram, которые запрашивает TGLock, и поддерживает необязательный TGLOCK_TOKEN. Без списка адресов воркер стал бы открытым TCP-прокси для любого, кто узнает его адрес. - docs/CLOUDFLARE_WORKER.md — когда это нужно и когда нет (таблица «что видно в приложении → нужен ли Worker»), установка через веб-интерфейс, проверка живости, подключение в GUI и через --worker, ограничение доступа, контракт для своих реализаций. - Ссылки из README: в FAQ про блокировку web.telegram.org и в блок docs. Контракт закреплён тестами, чтобы документация не разошлась с кодом: - worker_path вынесен в функцию, из неё же строятся боевые маршруты; - documented_worker_contract_matches_the_requested_path сверяет формат пути; - worker_allowlist_covers_every_address_a_route_can_ask_for падает, если в маршрутах появится адрес, которого нет в скрипте воркера; - connects_through_the_documented_worker_contract поднимает сервер, ведущий себя ровно по документации, и проверяет что туннель работает в обе стороны и что запрошен именно документированный URI. Чего тесты не проверяют: развёрнутый воркер в самом Cloudflare. Это указано и в самой инструкции. Отдельно: снят флаг prerelease в release.yml. До правки /releases/latest отдавал v2.0.0-beta.1, то есть кнопка «Скачать» в README вела на сборку без CLI и без фикса рендера. Существующий релиз v2.0.0-beta.2 помечен как latest вручную. Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
// Резервный маршрут TGLock через Cloudflare Worker.
|
||||
//
|
||||
// Нужен в одном случае: провайдер заблокировал саму веб-инфраструктуру
|
||||
// Telegram, и все обычные маршруты TGLock перестали отвечать. Тогда соединение
|
||||
// идёт на твой домен *.workers.dev, а воркер доводит его до Telegram.
|
||||
//
|
||||
// Инструкция по установке: docs/CLOUDFLARE_WORKER.md
|
||||
//
|
||||
// Контракт, который ожидает клиент (src/transport.rs):
|
||||
// wss://<домен>/apiws?dst=<telegram-ip>&dc=<номер-dc>
|
||||
// заголовок Sec-WebSocket-Protocol: binary — его обязательно нужно
|
||||
// подтвердить в ответе, иначе клиент разорвёт рукопожатие;
|
||||
// бинарные frames в обе стороны, без обёрток.
|
||||
|
||||
import { connect } from "cloudflare:sockets";
|
||||
|
||||
// Только те адреса, которые запрашивает сам TGLock. Без этого списка любой,
|
||||
// кто узнает адрес воркера, получит через твой аккаунт произвольный
|
||||
// TCP-прокси.
|
||||
const ALLOWED_DESTINATIONS = new Set([
|
||||
"91.105.192.100",
|
||||
"149.154.167.51",
|
||||
"149.154.167.91",
|
||||
"149.154.167.220",
|
||||
"149.154.171.5",
|
||||
"149.154.175.50",
|
||||
"149.154.175.100",
|
||||
]);
|
||||
|
||||
const TELEGRAM_PORT = 443;
|
||||
|
||||
export default {
|
||||
async fetch(request, env) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname !== "/apiws") {
|
||||
return new Response("not found", { status: 404 });
|
||||
}
|
||||
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") {
|
||||
return new Response("expected a websocket upgrade", { status: 426 });
|
||||
}
|
||||
// Необязательный общий секрет: задай переменную TGLOCK_TOKEN в настройках
|
||||
// воркера, и посторонние подключиться не смогут.
|
||||
if (env.TGLOCK_TOKEN && url.searchParams.get("token") !== env.TGLOCK_TOKEN) {
|
||||
return new Response("forbidden", { status: 403 });
|
||||
}
|
||||
|
||||
const destination = url.searchParams.get("dst");
|
||||
if (!destination || !ALLOWED_DESTINATIONS.has(destination)) {
|
||||
return new Response("destination not allowed", { status: 403 });
|
||||
}
|
||||
|
||||
const [client, server] = Object.values(new WebSocketPair());
|
||||
server.accept();
|
||||
|
||||
const upstream = connect({ hostname: destination, port: TELEGRAM_PORT });
|
||||
const writer = upstream.writable.getWriter();
|
||||
let closed = false;
|
||||
|
||||
const shutdown = () => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
writer.close().catch(() => {});
|
||||
try {
|
||||
server.close();
|
||||
} catch {
|
||||
// соединение уже закрыто
|
||||
}
|
||||
};
|
||||
|
||||
server.addEventListener("message", (event) => {
|
||||
const chunk =
|
||||
event.data instanceof ArrayBuffer
|
||||
? new Uint8Array(event.data)
|
||||
: event.data;
|
||||
writer.write(chunk).catch(shutdown);
|
||||
});
|
||||
server.addEventListener("close", shutdown);
|
||||
server.addEventListener("error", shutdown);
|
||||
|
||||
// Обратное направление: всё, что приходит от Telegram, уходит клиенту.
|
||||
(async () => {
|
||||
const reader = upstream.readable.getReader();
|
||||
try {
|
||||
for (;;) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
server.send(value);
|
||||
}
|
||||
} catch {
|
||||
// разрыв соединения — обычная ситуация, не ошибка
|
||||
}
|
||||
shutdown();
|
||||
})();
|
||||
|
||||
return new Response(null, {
|
||||
status: 101,
|
||||
webSocket: client,
|
||||
// Обязательно: клиент запрашивает подпротокол binary и без
|
||||
// подтверждения рвёт рукопожатие.
|
||||
headers: { "Sec-WebSocket-Protocol": "binary" },
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user