Advanced Configuration

Last Updated: 2026-05-26 • For System Administrators & DevOps

Overview

This guide covers production configuration, external service integration, and deployment strategies for QuranBot.

  • Environment variable management
  • Lavalink node configuration
  • Redis + Firebase setup
  • PM2 deployment & scaling

Environment Variables

QuranBot uses a dual-environment system with development.env and production.env. The active environment is determined by NODE_ENV in the base .env file.

Core Variables (Required)

Variable Description Example
DISCORD_TOKEN Bot authentication token from Discord Developer Portal MTEyMz...xyz
CLIENT_ID Discord application client ID (snowflake) 1505646575962292314
SPE_USER_ID Comma-separated list of developer user IDs with admin access 123456789,987654321
BACKUP_INTERVAL_MS Interval for automatic Firebase backups (milliseconds) 600000 (10 minutes)

Environment-Specific Variables

Variable Development Production
DEVELOPMENT_SERVER_ID Test server ID for backup channel Not used
DEVELOPMENT_CHANNEL_ID Channel ID for receiving backup files Not used
PRODUCTION_SERVER_ID Not used Production server ID for backups
PRODUCTION_CHANNEL_ID Not used Production backup channel ID
TOPGG_TOKEN Removed automatically top.gg API token for bot listing
Security Note

In development mode, TOPGG_TOKEN is automatically removed from process.env to prevent accidental use of production API keys during testing. Never commit real tokens to version control.

Environment Switcher

The src/config/envSwitcher.js module handles environment selection at startup:


require('pathlra-aliaser')();

const path = require('path');
const dotenv = require('dotenv');
const logger = require('@logging/logger');
const fs = require('fs');

const baseEnvFilePath = path.resolve(__dirname, '../../.env');
let baseEnvConfig = {};

if (fs.existsSync(baseEnvFilePath)) {
    const rawBaseEnv = fs.readFileSync(baseEnvFilePath, 'utf8');
    baseEnvConfig = dotenv.parse(rawBaseEnv);
}

const activeEnv = baseEnvConfig.NODE_ENV;
const targetedEnvPath = path.resolve(__dirname, `../../${activeEnv}.env`);

const loadResult = dotenv.config({ path: targetedEnvPath });

if (loadResult.error) {
    logger.error(`Could Not Load ${activeEnv} Env File`, loadResult.error.message);
    process.exit(1);
} else {
    logger.info(`Loaded ${activeEnv} Env`);
}

// Remove TOPGG_TOKEN in development to prevent accidental use of production API key during dev
if (activeEnv === 'development') {
    if (process.env.TOPGG_TOKEN) {
        delete process.env.TOPGG_TOKEN;
        logger.info('Development Mode TOPGG_TOKEN Removed');
    }
}

module.exports.isDevelopment = activeEnv === 'development';
module.exports.isProduction = activeEnv === 'production';
module.exports.currentEnv = activeEnv;

Setup Steps:

  1. Copy development.env.exampledevelopment.env
  2. Copy production.env.exampleproduction.env
  3. Set NODE_ENV=production (or development) in base .env

Redis Setup

Redis provides hot caching for guild states, enabling stateless sharding and sub-millisecond lookups.

Configuration

Variable Default Description
REDIS_URL redis://127.0.0.1:6379 Redis connection string (supports auth, TLS)

Connection String Examples

# Local development
REDIS_URL=redis://localhost:6379

# With password authentication
REDIS_URL=redis://:my_password@localhost:6379

# Redis Cloud / Managed service
REDIS_URL=rediss://default:password@redis-12345.c1.us-east-1-2.ec2.redns.redis-cloud.com:12345

# With database selection
REDIS_URL=redis://localhost:6379/2
async function get(key) {
    try {
        if (clientManager.isRedisReady && client) {
            const result = await client.get(key);
            return result ? JSON.parse(result) : result;
        }
    } catch (error) {
        logger.error(`Redis get failed: ${error.message}`);
    }
    
    // Fallback to memory
    return memoryFallbackMap.get(key) || null;
}

Firebase Integration

Firebase Realtime Database serves as the cold storage layer for persistent state, complaints, and statistics.

Service Account Setup

  1. Go to Firebase Console → Project Settings → Service Accounts
  2. Generate new private key → Download JSON file
  3. Extract values into environment variables (see table below)

Required Firebase Variables

Variable Source in JSON
FIREBASE_ADMIN_TYPE type
FIREBASE_ADMIN_PROJECT_ID project_id
FIREBASE_ADMIN_PRIVATE_KEY private_key (see note below)
FIREBASE_ADMIN_PRIVATE_KEY_ID private_key_id
FIREBASE_ADMIN_CLIENT_EMAIL client_email
FIREBASE_ADMIN_CLIENT_ID client_id
FIREBASE_DATABASE_URL https://your-project.firebaseio.com
Private Key Formatting

The FIREBASE_ADMIN_PRIVATE_KEY value must preserve newlines. In .env files, wrap the key in quotes and escape newlines as \n, or use a multi-line string if your environment supports it. The bot's getSanitizedPrivateKey() function handles common formatting issues.

Database Rules

Ensure your firebase.rules.json denies public access:

{
  "rules": {
    ".read": false,
    ".write": false
  }
}

The bot uses server-side firebase-admin with service account credentials, which bypasses these rules for authenticated operations.

Deployment with PM2

QuranBot is optimized for process management via PM2, supporting auto-restart, logging, and clustering.

PM2 Configuration (ecosystem.config.js)

module.exports = {
  apps: [{
    name: 'QuranBot',
    script: 'src/bot/core.js',
    node_args: '--trace-warnings --trace-deprecation --unhandled-rejections=strict --enable-source-maps',
    autorestart: true,
    watch: false,
    ignore_watch: ['node_modules', 'storage/logs', 'logs', '.git', '.test'],
    instances: 1,
    exec_mode: 'fork',
    env: {
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      error_file: 'storage/pm2/logs/pm2-error.log',
      out_file: 'storage/pm2/logs/pm2-out.log',
      restart_delay: 7000,
    },
  }],
};

Deployment Commands

Command Description
pnpm run pm2:start Start bot with PM2 using ecosystem.config.js
pnpm run pm2:logs View last 500 lines of PM2 logs
pnpm run pm2:restart Graceful restart with 7s delay
pnpm run pm2:stop Stop the bot process
pnpm run pm2:delete Remove bot from PM2 process list

Production Startup Script

#!/bin/bash
# deploy.sh - Production deployment script
set -e

# Load environment
export NODE_ENV=production
source .env

# Install dependencies
pnpm install --prod

# Run database migrations if needed
# pnpm run migrate

# Start with PM2
pnpm run pm2:start

# Save PM2 process list for auto-start on reboot
pm2 save

echo "QuranBot deployed successfully"

Sharding Strategy

For large-scale deployments, QuranBot supports Discord's sharding via discord-hybrid-sharding.

Shard Configuration Variables

Variable Default Description
TOTAL_SHARDS auto Number of shards (or auto for Discord recommendation)
SHARD_LIST undefined Comma-separated list of shard IDs for this instance
SHARDS_PER_CLUSTER 2 Shards per PM2 cluster process

Example: Multi-Instance Setup

For a bot serving 10,000+ servers, you might run 4 instances with 2 shards each:

# Instance 1 (.env.cluster1)
NODE_ENV=production
TOTAL_SHARDS=8
SHARD_LIST=0,1
SHARDS_PER_CLUSTER=2

# Instance 2 (.env.cluster2)
NODE_ENV=production
TOTAL_SHARDS=8
SHARD_LIST=2,3
SHARDS_PER_CLUSTER=2

# ... instances 3 and 4 for shards 4-7

# Start each with:
NODE_ENV=production pnpm run run:shard

Stateless Design: With Redis caching, each shard instance operates independently. Guild state is fetched from Redis on demand, enabling horizontal scaling without sticky sessions.

Health Checks & Monitoring

Built-in Health Endpoint

The bot exposes a lightweight HTTP health check on port HH_CH_PORT (default: 3000):

# Example request
curl http://localhost:3000/health

# Response
{
  "status": "ok",
  "voiceConnections": 42,
  "uptime": 86400.123,
  "memory": {
    "rss": 256000000,
    "heapTotal": 128000000,
    "heapUsed": 96000000
  },
  "timestamp": "2026-05-24T12:00:00.000Z"
}

Key Metrics to Monitor

  • Memory Usage: Alert if heapUsed > 1.5GB (triggers aggressive GC)
  • Voice Connections: Sudden drops may indicate Lavalink issues
  • Uptime: Track restarts via pm2 logs or health endpoint
  • Log Volume: Spike in error logs may indicate external API failures

Log Rotation

Logs are automatically archived daily at Cairo midnight:

  • Location: storage/logs/archive/
  • Format: logs-general-YYYY-MM-DD.zip
  • Retention: 60 days (configurable in logging_config)

Troubleshooting Guide

Bot fails to start with "Firebase Private Key Format Invalid"

This indicates newline escaping issues in the .env file. Solutions:

  • Use echo -n "$PRIVATE_KEY" | base64 to encode, then decode at runtime
  • Wrap the key in triple quotes if your shell supports it
  • Use a secrets manager (e.g., HashiCorp Vault) instead of env files

The bot's getSanitizedPrivateKey() handles common cases, but complex keys may require preprocessing.

Lavalink nodes show "No compatible encryption modes"

This Discord voice encryption mismatch occurs when:

  • Lavalink version is incompatible with Discord's current encryption suite
  • Node is behind a proxy that strips TLS headers
  • Bot and Lavalink have mismatched secure settings

Verify Lavalink is v4.x and secure matches your deployment (false for HTTP, true for HTTPS).

High memory usage after several days

The bot includes automatic memory management:

  • Every 5 minutes: Check if heap > 250MB → trigger GC
  • Every hour: Clean destroyed voice connections from state maps
  • On interaction cache TTL expiry: Remove stale entries

If memory still grows, check for:

  • Large guilds with many channels (state maps scale linearly)
  • Long-running intervals without cleanup
  • Event listeners not properly removed on guild leave

Use /سرعة command to view real-time memory stats.

Diagnostic Commands

Command Use Case
/سرعة View bot latency, uptime, memory, and Lavalink node status
pnpm run pm2:logs --lines 100 Inspect recent logs for error patterns
redis-cli ping Verify Redis connectivity
curl https://your-lavalink-host/v4/stats Check Lavalink node health directly