6aeb529f13
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 53s
CI / gate (pull_request) Successful in 1s
CI / deploy (pull_request) Failing after 2m6s
Move all Telegram egress off the main host. The single connector held the bot token, long-polled Telegram and answered the gateway/backend over the trusted internal network, so the whole component (including login validation) shared fate with its VPN sidecar. Split it into two binaries that share the token: - cmd/validator (home, no VPN): Mini App initData + Login Widget HMAC only, never calls the Bot API. The gateway dials it for Telegram auth, so game login is now independent of Telegram reachability. - cmd/bot (remote): Bot API long-poll + sendMessage, the only component reaching Telegram. It holds no inbound port — it dials the gateway over a new reverse mTLS bot-link (pkg/proto/botlink/v1) and executes the send commands the gateway pushes. The gateway funnels sends to the bot-link: out-of-app push is fire-and-forget (at-most-once, dropped if no bot is connected); the backend admin broadcasts reach a gateway-served relay that forwards them and awaits the bot's ack (SendToUser/SendToGameChannel contract preserved). mTLS (pkg/mtls) is the one inter-service link that leaves the trusted segment; validator<->gateway and the relay stay plaintext internal. The bot is Telegram-rate-limited. One bot now; the gateway bot registry, an owns_updates flag and per-command ids leave seams for N later. Webhook rejected (one URL per token, adds inbound + a static address). The unified test contour runs the split (the bot keeps its VPN sidecar and dials the gateway by its internal name; bot-link certs from deploy/gen-certs.sh, generated in CI). The prod wiring — the bot on a separate host (no VPN), the gateway bot-link port published, PROD_ certs with scheduled rotation, an SSH deploy of both hosts together — is the deferred final stage (PRERELEASE.md TX, Stage 18). Docs: ARCHITECTURE, PRERELEASE (phase TX), platform/telegram + gateway + backend + deploy READMEs, FUNCTIONAL(+ru), CLAUDE.md, .env.example.
68 lines
2.6 KiB
Protocol Buffer
68 lines
2.6 KiB
Protocol Buffer
syntax = "proto3";
|
|
|
|
// Package scrabble.botlink.v1 is the reverse control channel between a remote
|
|
// Telegram bot and the gateway. The bot dials the gateway and opens a single
|
|
// long-lived Link stream (mTLS); once the stream is open the gateway pushes send
|
|
// Commands down it and the bot returns one Ack per command. This keeps the bot
|
|
// egress (the Bot API token, getUpdates long-poll and sendMessage) off the main
|
|
// host with no inbound port on the bot. See docs/ARCHITECTURE.md.
|
|
package scrabble.botlink.v1;
|
|
|
|
option go_package = "scrabble/pkg/proto/botlink/v1;botlinkv1";
|
|
|
|
import "telegram/v1/telegram.proto";
|
|
|
|
// BotLink is the reverse (bot-dials-gateway) control channel. The bot is the gRPC
|
|
// client; once the stream is open the gateway (server) sends Commands at will and
|
|
// the bot returns one Ack per command. Delivery is best-effort, at-most-once: a
|
|
// command lost across a reconnect is not replayed.
|
|
service BotLink {
|
|
// Link opens the single bot <-> gateway stream. The first client message is
|
|
// Hello; thereafter the client sends one Ack per received Command.
|
|
rpc Link(stream FromBot) returns (stream ToBot);
|
|
}
|
|
|
|
// FromBot is a message the bot sends to the gateway: the opening Hello, then one
|
|
// Ack per Command.
|
|
message FromBot {
|
|
oneof msg {
|
|
Hello hello = 1;
|
|
Ack ack = 2;
|
|
}
|
|
}
|
|
|
|
// ToBot is a message the gateway sends to the bot. Only Command is carried today.
|
|
message ToBot {
|
|
Command command = 1;
|
|
}
|
|
|
|
// Hello registers the bot on connect. instance_id identifies the bot process for
|
|
// gateway-side logging and metrics; owns_updates reports whether this bot runs the
|
|
// exclusive getUpdates long-poll (exactly one bot must, else Telegram returns 409).
|
|
message Hello {
|
|
string instance_id = 1;
|
|
bool owns_updates = 2;
|
|
}
|
|
|
|
// Command is one send instruction addressed by command_id, which the bot echoes in
|
|
// its Ack. Exactly one payload is set; the payloads reuse the connector request
|
|
// shapes from scrabble.telegram.v1.
|
|
message Command {
|
|
string command_id = 1;
|
|
oneof payload {
|
|
scrabble.telegram.v1.NotifyRequest notify = 2;
|
|
scrabble.telegram.v1.SendToUserRequest send_to_user = 3;
|
|
scrabble.telegram.v1.SendToGameChannelRequest send_to_channel = 4;
|
|
}
|
|
}
|
|
|
|
// Ack reports the outcome of the Command with command_id. delivered mirrors the
|
|
// connector delivery semantics (false when the kind is not rendered out-of-app, the
|
|
// user never started the bot, or no channel is configured); error carries an
|
|
// unexpected transport/render failure, distinct from a clean not-delivered.
|
|
message Ack {
|
|
string command_id = 1;
|
|
bool delivered = 2;
|
|
string error = 3;
|
|
}
|