Files
DiscordBot-CloudflareWarp/README.md
T
2026-09-08 01:20:55 +02:00

135 lines
6.3 KiB
Markdown

# DiscordBot-CloudflareWarp
A small [discord.js](https://discord.js.org/) bot that runs **entirely inside Docker** and sends all of its
traffic through a **Cloudflare WARP** (Cloudflare One / Zero Trust client) tunnel. Every container runs its
own WARP client, so every bot gets its own VPN tunnel and its own Cloudflare egress IP. The bot proves it is
still on Cloudflare by logging the egress IP and `warp=on` flag on startup, on a timer and on demand.
Nothing runs on the host except Docker: `npm install` happens in the image build, and the lockfile was
generated inside a `node:22-bookworm-slim` container.
## How it works
```
+-------------------------------------------------------------+
| container: discordbot-warp |
| |
| dbus -> warp-svc (Cloudflare WARP daemon, TUN device) |
| ^ |
| entrypoint.sh: register -> mode warp -> connect -> verify |
| | |
| node src/index.js ---- all traffic ----> WARP tunnel ---> Cloudflare ---> Discord
+-------------------------------------------------------------+
```
1. `docker/entrypoint.sh` starts `dbus` and `warp-svc`, creates (or reuses) a WARP registration, sets
`mode warp`, connects and waits until `warp-cli status` says `Connected`.
2. It then calls `https://www.cloudflare.com/cdn-cgi/trace` and **refuses to start the bot** unless
Cloudflare reports `warp=on` (or `warp=plus`). The egress IP and colo are logged.
3. `bot/src/index.js` logs into Discord and every `WARP_CHECK_INTERVAL` seconds (default 60) asks
Cloudflare's edge over IPv4 (`1.1.1.1`) and IPv6 (`2606:4700:4700::1111`) what it sees, logging a line like:
```
[2026-09-08T00:20:15.123Z] [WARP] CONNECTED via Cloudflare (periodic) ipv4=104.28.x.x (warp=on) ipv6=2a09:bac1:xxxx::x:x (warp=on) colo=AMS loc=NL | warp-cli: Status update: Connected Network: healthy
```
If the tunnel drops it logs `NOT CONNECTED` and runs `warp-cli connect` to recover.
4. A Docker `HEALTHCHECK` performs the same `warp=on` check, so `docker ps` shows `unhealthy` if the
container ever falls off Cloudflare.
### Slash commands
| Command | What it does |
|---------|-------------------------------------------------------------------------------|
| `/ping` | Replies with round-trip and gateway latency. |
| `/warp` | Runs a live tunnel check and replies with the Cloudflare IP, colo and status. |
## Repository layout
```
bot/
package.json, package-lock.json Node project (discord.js 14)
src/index.js the bot
docker/
Dockerfile node:22-bookworm-slim + cloudflare-warp .deb
entrypoint.sh boots WARP, verifies, then execs the bot
docker-compose.yml one service per bot, each with its own WARP tunnel
scripts/deploy.sh ships the committed tree to the Docker host and restarts the stack
.env.example configuration template
CHANGELOG.md every change, newest first
```
## Requirements
* A Docker host (tested on Debian 12 with Docker 20.10 and Compose v5) with `/dev/net/tun` available.
* Containers need `NET_ADMIN` and the TUN device. Both are set in `docker-compose.yml`.
* A Discord bot token from the [Developer Portal](https://discord.com/developers/applications).
The Cloudflare WARP client is downloaded during the image build from
`https://downloads.cloudflareclient.com/v1/download/bookworm-intel/version/<WARP_VERSION>`
(`WARP_VERSION` defaults to `2026.7.1377.0`, the Debian 12 build).
## Quick start (on the Docker host)
```bash
git clone https://gitea.ikbengino.nl/ginoblij/DiscordBot-CloudflareWarp.git
cd DiscordBot-CloudflareWarp
cp .env.example .env # put your DISCORD_TOKEN in .env
docker compose up -d --build
docker compose logs -f
```
You should see the entrypoint report the tunnel, then the bot log in:
```
[entrypoint] warp-svc is up
[entrypoint] no registration found, creating a new one
[entrypoint] mode set to warp
[entrypoint] warp-cli status: Status update: Connected
[entrypoint] WARP tunnel verified: ip=104.28.x.x warp=on colo=AMS
[entrypoint] starting bot: node src/index.js
[bot] logged in as ... in 1 guild(s)
[WARP] CONNECTED via Cloudflare (startup) ipv4=104.28.x.x (warp=on) ipv6=2a09:bac1:xxxx::x:x (warp=on) colo=AMS loc=NL ...
```
## Configuration (`.env`)
| Variable | Default | Meaning |
|-----------------------|---------|----------------------------------------------------------------------|
| `DISCORD_TOKEN` | | Bot token. Required. |
| `WARP_CHECK_INTERVAL` | `60` | Seconds between tunnel checks logged by the bot. |
| `GUILD_ID` | | Register slash commands in one guild (instant) instead of globally. |
| `WARP_MODE` | `warp` | `warp-cli mode` to use (`warp`, `doh`, `warp+doh`, ...). |
| `WARP_CONNECT_TIMEOUT`| `60` | Seconds the entrypoint waits for `Connected` before giving up. |
## Running more than one bot (one VPN per container)
Each service in `docker-compose.yml` is an independent container with its own `warp-svc`, its own
registration volume and therefore its own tunnel and egress IP. To add a bot:
1. Create `.env.bot2` with the second token.
2. Uncomment the `bot2` service and the `warp-data-bot2` volume in `docker-compose.yml`.
3. `docker compose up -d --build`.
## Deploying from this repo
`scripts/deploy.sh` sends the **committed** tree (`git archive HEAD`) to the host over SSH, rebuilds the
image there and restarts the stack. The `.env` on the host is never touched or overwritten.
```bash
scripts/deploy.sh # defaults to root@192.168.5.67:/opt/discordbot-cloudflarewarp
scripts/deploy.sh user@host /some/dir
```
## Checking the tunnel by hand
```bash
docker compose exec bot warp-cli --accept-tos status
docker compose exec bot curl -s https://www.cloudflare.com/cdn-cgi/trace
docker compose logs -f | grep WARP
```
## Change history
See [CHANGELOG.md](CHANGELOG.md). Every change is committed and pushed with a changelog entry.