Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/VasquezRivero92/Discord_Faceit/llms.txt

Use this file to discover all available pages before exploring further.

The Discord Faceit Hub Bot is a Node.js application that runs a Discord client and an Express HTTP server in the same process. It needs a persistent, publicly accessible server so Faceit can deliver webhooks over HTTPS. This guide covers deploying on a VPS with PM2 and Nginx, as well as a Docker approach.

Requirements

  • Node.js 18+ — the bot uses ES modules ("type": "module") and top-level await
  • A public domain or IP with HTTPS — Faceit requires HTTPS for webhook delivery
  • A Firebase/Firestore project — all persistent state (users, matches, config) lives in Firestore
  • Discord application with a bot token, OAuth2 client ID and secret

Direct VPS Deployment

1

SSH Into Your Server

ssh user@your-server-ip
2

Install Node.js 18+

Using nvm (recommended):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 18
nvm use 18
Or via your system package manager (Ubuntu/Debian):
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
3

Clone the Repository and Install Dependencies

git clone https://github.com/your-org/discord-faceit-hub-bot.git
cd discord-faceit-hub-bot
npm install
4

Configure Environment Variables

Copy the example env file and fill in every value:
cp .env.example .env
nano .env
Minimum required variables:
DISCORD_TOKEN=
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_GUILD_ID=
ADMIN_JWT_SECRET=
FACEIT_API_KEY=
FACEIT_HUB_ID=
FACEIT_WEBHOOK_SECRET=
FIREBASE_SERVICE_ACCOUNT_PATH=./firebase-credentials.json
BASE_URL=https://your-domain.com
PORT=3000
NODE_ENV=production
5

Set Up PM2 for Process Management

Install PM2 globally, start the bot, and configure it to survive reboots:
npm install -g pm2
pm2 start index.js --name faceit-bot
pm2 save
pm2 startup
Follow the output of pm2 startup — it prints a system-specific command to run as root to register the PM2 service. After running it, your bot will automatically restart on server reboot.Useful PM2 commands:
pm2 logs faceit-bot        # tail logs
pm2 restart faceit-bot     # restart
pm2 status                 # process table

Nginx Reverse Proxy

Place Nginx in front of the Node.js server to handle SSL termination and forward requests to port 3000. The WebSocket upgrade headers are required for the Mixton Arena real-time panel (/api/lobby/mixton-ws).
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Required for WebSocket support (Mixton Arena panel)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
The Express server is configured with app.set('trust proxy', 1) so it correctly reads the client IP from X-Forwarded-For instead of seeing Nginx’s loopback address. This is needed for rate limiting to work correctly per real client IP.

SSL/TLS with Let’s Encrypt

Faceit requires HTTPS for webhook delivery. Use Certbot to obtain a free certificate:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
Certbot automatically edits your Nginx config to add listen 443 ssl and sets up auto-renewal. After this step, set BASE_URL=https://your-domain.com in your .env. The Express app automatically enables secure: true on session cookies when BASE_URL starts with https:
const cookieConfig = {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production' || BASE_URL.startsWith('https'),
  maxAge: 8 * 60 * 60 * 1000, // 8 hours
};

Docker Deployment

For containerized environments, use this minimal Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
Build and run:
docker build -t faceit-bot .
docker run -d \
  --name faceit-bot \
  --restart unless-stopped \
  -p 3000:3000 \
  --env-file .env \
  -v $(pwd)/firebase-credentials.json:/app/firebase-credentials.json:ro \
  faceit-bot
Keep your Firebase service account JSON outside the Docker image. Mount it as a read-only volume (as shown above) and point FIREBASE_SERVICE_ACCOUNT_PATH to its container path (/app/firebase-credentials.json). Alternatively, on Google Cloud Platform you can omit the file entirely and use Application Default Credentialsadmin.initializeApp() with no arguments picks up the GCP service account automatically.

Environment Variables Reference

VariableRequiredDescription
DISCORD_TOKENBot token from Discord Developer Portal
DISCORD_CLIENT_IDApplication client ID for OAuth2
DISCORD_CLIENT_SECRETApplication client secret for OAuth2
DISCORD_GUILD_IDDefault Discord guild ID
ADMIN_JWT_SECRETSecret used to sign admin session JWTs
FACEIT_API_KEYFaceit server-side API key
FACEIT_HUB_IDYour Faceit Hub UUID
FACEIT_LEADERBOARD_IDLeaderboard ID for /leaderboard command
FACEIT_WEBHOOK_SECRETWebhook signature secret (required if webhookValidation is enabled)
FIREBASE_SERVICE_ACCOUNT_PATHPath to Firebase service account JSON file
BASE_URLPublic URL of your server (e.g. https://your-domain.com)
PORTHTTP listen port (default: 3000)
NODE_ENVSet to production to enable secure cookies
ADMIN_ROLE_NAMEDiscord role name for admins (default: ADMIN)
ADMIN_JWT_SECRET must be a strong, random string in production. If it is weak or guessable, attackers can forge admin session tokens and gain full control of the bot. Generate a secure value with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Never commit this value to source control.

Graceful Shutdown

The bot handles SIGTERM and SIGINT to shut down cleanly — disconnecting the Discord client and closing the Express server before exiting. PM2 sends SIGTERM on pm2 restart and pm2 stop, so in-flight requests complete before the process exits.
async function shutdown(signal) {
  await client.destroy();      // Disconnect Discord WebSocket
  server.close(() => {         // Stop accepting new HTTP connections
    process.exit(0);
  });
}

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT',  () => shutdown('SIGINT'));

Rate Limiting

Two rate limiters are applied using express-rate-limit with standardHeaders: true:
ScopeLimitWindow
Global (all routes)100 requests1 minute
/webhook/faceit60 requests1 minute
With app.set('trust proxy', 1), rate limiting is keyed on the real client IP from X-Forwarded-For, not the Nginx loopback IP.

Health Check

Monitor the bot’s status with the health endpoint — no authentication required:
curl https://your-domain.com/health
{
  "status": "ok",
  "discord": "connected",
  "uptime": 86421.3,
  "timestamp": "2024-11-01T12:00:00.000Z"
}
Configure your uptime monitor (UptimeRobot, Betterstack, etc.) to poll GET /health every 60 seconds and alert if the response is not 200 or if discord is not "connected".

Build docs developers (and LLMs) love