Self-hosted Telegram access-control bot for paid subscriptions. Gatekeeper observes Boosty and Tribute subscription sources, stores a durable local access ledger and manages entry to a private club chat and channel.
- Go 1.26+
- Task (
go-task) for canonical commands - SQLite is embedded through
modernc.org/sqlite; no cgo or system SQLite is required
task migrate:up
task build
./bin/gatekeeperThe serving binary never runs DDL. Run task migrate:up before the
first start and before serving after schema changes. If the database is
not migrated, startup fails with an instruction to run the migration
task.
Create an environment file or set variables directly. Keep .env files
at 0600.
Required baseline variables:
BOT_TOKENOWNER_TG_IDSBOOSTY_GROUP_IDTRIBUTE_CHANNEL_IDCLUB_CHAT_IDCLUB_CHANNEL_IDBOOSTY_SUBSCRIBE_URLTRIBUTE_SUBSCRIBE_URL_RUB
Common optional variables:
DB_PATH=./data/gatekeeper.dbINVITE_MODE=shared_join_requestTRIBUTE_MODE=observationTELEGRAM_MODE=pollingMETRICS_ENABLED=falseWEBHOOK_LISTEN_ADDR=:8080TRIBUTE_WEBHOOK_PATH=/webhooks/tributeTELEGRAM_WEBHOOK_PATH=/webhooks/telegram
Configure four different Telegram chats:
- Boosty group: observed source for Boosty membership.
- Tribute channel: observed source in
TRIBUTE_MODE=observationand a secondary verification signal inTRIBUTE_MODE=webhook. - Club chat: managed private chat for subscribers.
- Club channel: managed private channel for subscribers.
The bot must be a member of the source chats. In managed club resources
it must be an administrator with can_invite_users and
can_restrict_members; those rights are required to create join-request
links, approve requests and revoke access safely.
Use /here from an owner account inside a chat or supergroup to discover
its chat.id.
Polling is the default Telegram transport and does not open an HTTP listener.
HTTP starts only when at least one HTTP-facing mode is enabled:
TRIBUTE_MODE=webhookTELEGRAM_MODE=webhookMETRICS_ENABLED=true
Tribute webhook mode requires TRIBUTE_API_KEY. Requests are verified
with the trbt-signature HMAC over the raw request body. Telegram
webhook mode requires TELEGRAM_WEBHOOK_PUBLIC_URL and
TELEGRAM_WEBHOOK_SECRET; runtime registers the webhook explicitly with
Telegram.
TRIBUTE_CANCEL_IS_IMMEDIATE=false is the safe default: a Tribute
cancelled_subscription records cancellation but keeps access until
expires_at. Set it to true only as an explicit operator override.
User commands:
/start/status/help/boosty,/tribute
Owner commands:
/stats/alerts/chats/export/help_admin/whois,/grant,/revoke,/ban,/unban,/sync
/export is owner-only and private-chat-only. It exports users and
current subscription state, not raw provider payloads or secrets.
When HTTP is enabled:
GET /healthzis lightweight liveness.GET /readyzchecks SQLite/schema, startupgetMe, chat health, reconciliation freshness and Telegram webhook registration when used.GET /metricsis available only withMETRICS_ENABLED=true.
The canonical path is a published container image deployed with Docker
Compose; a systemd unit is provided as an alternative. All deployment
artifacts live in deploy/; the full runbook is deploy/DEPLOYMENT.md.
For Docker Compose (GHCR):
- Push a
v*tag. The Build & publish image workflow builds and pushesghcr.io/<owner>/gatekeeper:<tag>(and:latest). - Run the Deploy workflow (manual dispatch, optional
tag). It copies the compose file,config.envand scripts to the droplet and brings up thegatekeeper-migrate(one-shot) andgatekeeperservices. Non-secret configuration is committed indeploy/config.production.env;BOT_TOKENand the optional webhook secrets are injected from theProdGitHub Environment.
Metrics/health are bound to 127.0.0.1:8090 on the host. Do not bake
tokens or provider keys into the image.
For systemd:
- Build and install
gatekeeperto/usr/local/bin/gatekeeper. - Create a dedicated
gatekeeperuser and group. - Create
/var/lib/gatekeeperowned by that user with0700. - Put environment variables in
/etc/gatekeeper/gatekeeper.envwith0600. - Run migrations with the same environment before starting the service.
- Install
deploy/gatekeeper.serviceand enable it.
For Docker, pass runtime configuration as environment variables or a secret-mounted env file. Do not bake tokens or provider keys into the image.
Gatekeeper uses SQLite WAL mode. For consistent backups, either stop the
bot before copying the database or use VACUUM INTO from a controlled
maintenance session. If copying live files, include the main database,
-wal and -shm sidecars, or checkpoint before copying.
Database files and backups contain personal data and access state; treat them as secrets.
- Boosty membership signals can lag by up to about four weeks.
- Telegram does not provide a full member list, so Gatekeeper relies on events, local state and targeted membership checks.
- Kicking a user from a Telegram chat can remove their recent messages, depending on Telegram behavior and client settings.
- Tribute webhook mode is optional; polling plus observation mode remains the simplest deployment.
task build
task test
task lint
task tidyCurrent OpenSpec specs live in openspec/specs/; the product reference
document lives in docs/gatekeeper-product-spec.md.