Initial Discord bot in Docker behind a per-container Cloudflare WARP tunnel

- discord.js 14 bot with /ping and /warp, periodic WARP egress-IP logging
- Dockerfile: node:22-bookworm-slim + cloudflare-warp 2026.7.1377.0 (.deb)
- entrypoint boots dbus/warp-svc, registers, connects, verifies warp=on
- docker-compose with NET_ADMIN, /dev/net/tun, sysctls, healthcheck
- deploy script, README, CHANGELOG, lockfile generated inside Docker

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-08 01:16:32 +02:00
parent bcf7b67ef7
commit 1a9dc1d676
14 changed files with 904 additions and 1 deletions
+45
View File
@@ -0,0 +1,45 @@
# ---------------------------------------------------------------------------
# Stage 1: install Node dependencies (npm runs only inside Docker)
# ---------------------------------------------------------------------------
FROM node:22-bookworm-slim AS deps
WORKDIR /app
COPY bot/package.json bot/package-lock.json ./
RUN npm ci --omit=dev
# ---------------------------------------------------------------------------
# Stage 2: runtime image = Node 22 on Debian 12 (bookworm) + Cloudflare WARP
# ---------------------------------------------------------------------------
FROM node:22-bookworm-slim
ARG WARP_VERSION=2026.7.1377.0
ARG WARP_DEB_URL=https://downloads.cloudflareclient.com/v1/download/bookworm-intel/version/${WARP_VERSION}
ENV DEBIAN_FRONTEND=noninteractive \
NODE_ENV=production
# dbus + iproute2 are required by warp-svc; curl for health checks.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl gnupg dbus iproute2 procps \
&& curl -fsSL "${WARP_DEB_URL}" -o /tmp/cloudflare-warp.deb \
&& apt-get install -y --no-install-recommends /tmp/cloudflare-warp.deb \
&& rm -f /tmp/cloudflare-warp.deb \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& warp-cli --version
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY bot/package.json ./package.json
COPY bot/src ./src
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# WARP registration/state lives here; mount a volume per container to keep it.
VOLUME ["/var/lib/cloudflare-warp"]
HEALTHCHECK --interval=60s --timeout=15s --start-period=90s --retries=3 \
CMD curl -fsS --max-time 10 https://www.cloudflare.com/cdn-cgi/trace | grep -Eq '^warp=(on|plus)$' || exit 1
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["node", "src/index.js"]
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
# Boots Cloudflare WARP inside this container, verifies the tunnel is up and
# that Cloudflare sees us as a WARP client, then hands over to the bot.
set -euo pipefail
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [entrypoint] $*"; }
WARP_MODE="${WARP_MODE:-warp}"
WARP_CONNECT_TIMEOUT="${WARP_CONNECT_TIMEOUT:-60}"
TRACE_URL="https://www.cloudflare.com/cdn-cgi/trace"
cleanup() {
log "shutting down"
warp-cli --accept-tos disconnect >/dev/null 2>&1 || true
kill "${WARP_SVC_PID:-}" 2>/dev/null || true
}
trap cleanup EXIT
# --- 1. system dbus (warp-svc talks to warp-cli over it) --------------------
mkdir -p /run/dbus
rm -f /run/dbus/pid
if ! pgrep -x dbus-daemon >/dev/null; then
dbus-daemon --system --fork
log "dbus started"
fi
# --- 2. WARP daemon ---------------------------------------------------------
log "starting warp-svc ($(warp-cli --version | head -n1))"
warp-svc >/var/log/warp-svc.log 2>&1 &
WARP_SVC_PID=$!
for i in $(seq 1 30); do
if warp-cli --accept-tos status >/dev/null 2>&1; then break; fi
if ! kill -0 "$WARP_SVC_PID" 2>/dev/null; then
log "warp-svc exited early:"; cat /var/log/warp-svc.log; exit 1
fi
sleep 1
done
log "warp-svc is up"
# --- 3. registration (persisted in /var/lib/cloudflare-warp) ---------------
if warp-cli --accept-tos registration show >/dev/null 2>&1; then
log "existing WARP registration found, reusing it"
else
log "no registration found, creating a new one"
warp-cli --accept-tos registration new
fi
warp-cli --accept-tos mode "$WARP_MODE" >/dev/null
log "mode set to $WARP_MODE"
# --- 4. connect and wait ----------------------------------------------------
warp-cli --accept-tos connect >/dev/null
for i in $(seq 1 "$WARP_CONNECT_TIMEOUT"); do
if warp-cli --accept-tos status 2>/dev/null | grep -q "Connected"; then break; fi
sleep 1
done
STATUS="$(warp-cli --accept-tos status 2>/dev/null | tr -s '[:space:]' ' ')"
log "warp-cli status: $STATUS"
# --- 5. prove it: Cloudflare must report warp=on and give us the egress IP --
TRACE="$(curl -fsS --max-time 15 "$TRACE_URL" || true)"
WARP_FLAG="$(echo "$TRACE" | sed -n 's/^warp=//p')"
EGRESS_IP="$(echo "$TRACE" | sed -n 's/^ip=//p')"
COLO="$(echo "$TRACE" | sed -n 's/^colo=//p')"
if [[ "$WARP_FLAG" == "on" || "$WARP_FLAG" == "plus" ]]; then
log "WARP tunnel verified: ip=$EGRESS_IP warp=$WARP_FLAG colo=$COLO"
else
log "ERROR: Cloudflare does not see this container as a WARP client (warp='${WARP_FLAG:-none}', ip='${EGRESS_IP:-none}')"
log "warp-svc log tail:"; tail -n 30 /var/log/warp-svc.log || true
exit 1
fi
# --- 6. run the bot ---------------------------------------------------------
log "starting bot: $*"
exec "$@"