Add Docker Compose setup and clean up legacy code
- Add docker-compose.yml with volume mounts for config/sfx - Simplify npm scripts (up/down/build/restart/logs) - Update README.md and CLAUDE.md with new commands - Remove unused lib/ directory (migrated to src/) - Update package.json scripts to cleaner naming Benefits: - Update configs and sound effects without rebuilding image - Simplified Docker workflow management - Cleaner project structure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
28
CLAUDE.md
28
CLAUDE.md
@@ -18,22 +18,30 @@ pnpm install
|
||||
pnpm start # Production mode
|
||||
pnpm dev # Development mode with auto-reload
|
||||
|
||||
# Docker commands
|
||||
pnpm docker:build # Build Docker image
|
||||
pnpm docker:run # Run Docker container with auto-restart
|
||||
# Docker Compose (recommended)
|
||||
pnpm up # Start bot with Docker Compose
|
||||
pnpm down # Stop bot
|
||||
pnpm restart # Restart bot (useful after config changes)
|
||||
pnpm logs # View logs
|
||||
pnpm build # Rebuild image
|
||||
|
||||
# Direct Docker (alternative)
|
||||
pnpm image:build # Build Docker image
|
||||
pnpm image:run # Run Docker container with auto-restart
|
||||
```
|
||||
|
||||
### Docker Management
|
||||
|
||||
```bash
|
||||
# Stop and remove container
|
||||
docker stop ghbot && docker rm ghbot
|
||||
# Docker Compose approach (recommended)
|
||||
pnpm up && pnpm logs # Start and follow logs
|
||||
pnpm restart # Restart after config changes
|
||||
pnpm build && pnpm up # Rebuild after code changes
|
||||
|
||||
# View logs
|
||||
docker logs ghbot -f
|
||||
|
||||
# Rebuild and restart
|
||||
docker stop ghbot && docker rm ghbot && pnpm docker:build && pnpm docker:run
|
||||
# Direct Docker approach
|
||||
docker stop discord-bot && docker rm discord-bot
|
||||
docker logs discord-bot -f
|
||||
pnpm image:build && pnpm image:run
|
||||
```
|
||||
|
||||
## Architecture & Key Components
|
||||
|
||||
51
README.md
51
README.md
@@ -14,7 +14,7 @@ A modern Discord bot built with Discord.js v14 that provides sound effects, text
|
||||
### 💬 Text Commands
|
||||
|
||||
- **Fun Facts**: Random or specific fact retrieval (`!funfact [number]`)
|
||||
- **Ham Facts**: Ham radio related facts (`!hamfact [number]`)
|
||||
- **Ham Facts**: Ham-related facts (`!hamfact [number]`)
|
||||
- **Static Commands**: Custom text responses loaded from configuration files
|
||||
- **Ankhbot Import**: Support for imported Ankhbot command databases
|
||||
|
||||
@@ -80,28 +80,39 @@ A modern Discord bot built with Discord.js v14 that provides sound effects, text
|
||||
|
||||
## 🐳 Docker Deployment
|
||||
|
||||
### Build and Run
|
||||
### Recommended: Docker Compose
|
||||
|
||||
```bash
|
||||
# Start the bot with Docker Compose
|
||||
pnpm up
|
||||
|
||||
# View logs
|
||||
pnpm logs
|
||||
|
||||
# Restart the bot (useful after config changes)
|
||||
pnpm restart
|
||||
|
||||
# Stop the bot
|
||||
pnpm down
|
||||
|
||||
# Rebuild and restart (after code changes)
|
||||
pnpm build && pnpm up
|
||||
```
|
||||
|
||||
**Benefits of Docker Compose:**
|
||||
- Update `config.json`, `sfx/`, and `conf/` files without rebuilding the image
|
||||
- Automatic restart on failure
|
||||
- Easy log management
|
||||
- Resource limits and health checks
|
||||
|
||||
### Alternative: Direct Docker
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
pnpm docker:build
|
||||
pnpm image:build
|
||||
|
||||
# Run the container
|
||||
pnpm docker:run
|
||||
```
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
ghbot:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config.json:/app/config.json:ro
|
||||
- ./sfx:/app/sfx:ro
|
||||
- ./conf:/app/conf:ro
|
||||
pnpm image:run
|
||||
```
|
||||
|
||||
## 📖 Usage
|
||||
@@ -149,21 +160,25 @@ services:
|
||||
### Creating a Discord Bot
|
||||
|
||||
1. **Go to Discord Developer Portal**
|
||||
|
||||
- Visit https://discord.com/developers/applications
|
||||
- Click "New Application" and give it a name
|
||||
|
||||
2. **Create Bot User**
|
||||
|
||||
- Go to the "Bot" section
|
||||
- Click "Add Bot"
|
||||
- Copy the bot token for your config.json
|
||||
|
||||
3. **Enable Required Intents**
|
||||
Under "Privileged Gateway Intents", enable:
|
||||
|
||||
- **SERVER MEMBERS INTENT** (Required for role management)
|
||||
- **MESSAGE CONTENT INTENT** (Required for prefix commands)
|
||||
|
||||
4. **Bot Permissions**
|
||||
When inviting the bot, ensure it has these permissions:
|
||||
|
||||
- Send Messages
|
||||
- Embed Links
|
||||
- Read Message History
|
||||
|
||||
File diff suppressed because one or more lines are too long
62
docker-compose.yml
Normal file
62
docker-compose.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
discord-bot:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: discord-bot
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# Configuration files (read-only)
|
||||
- ./config.json:/app/config.json:ro
|
||||
- ./conf:/app/conf:ro
|
||||
|
||||
# Sound effects directory (read-only)
|
||||
- ./sfx:/app/sfx:ro
|
||||
|
||||
# Optional: Mount logs directory if you want persistent logs
|
||||
# - ./logs:/app/logs
|
||||
|
||||
# Optional: Environment variables
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
# - DEBUG=true # Uncomment for debug mode
|
||||
|
||||
# Optional: Resource limits
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
reservations:
|
||||
memory: 256M
|
||||
|
||||
# Optional: Health check
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "process.exit(0)"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# Optional: Add a volume backup service
|
||||
# backup:
|
||||
# image: alpine:latest
|
||||
# container_name: ghbot-backup
|
||||
# volumes:
|
||||
# - ./config.json:/backup/config.json:ro
|
||||
# - ./conf:/backup/conf:ro
|
||||
# - ./sfx:/backup/sfx:ro
|
||||
# command: |
|
||||
# sh -c "
|
||||
# echo 'Creating backup...'
|
||||
# tar czf /backup/ghbot-backup-$(date +%Y%m%d_%H%M%S).tar.gz -C /backup config.json conf sfx
|
||||
# echo 'Backup complete'
|
||||
# "
|
||||
# profiles:
|
||||
# - backup
|
||||
|
||||
# Optional: Create named volumes for persistent data
|
||||
# volumes:
|
||||
# ghbot_logs:
|
||||
# driver: local
|
||||
@@ -1,49 +0,0 @@
|
||||
module.exports = {
|
||||
exists: exists,
|
||||
get: get
|
||||
};
|
||||
|
||||
const fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
const cmdPrefix = '!';
|
||||
const commandsFilePath = path.join(__dirname, '..', 'conf', 'ghbot.abcomg');
|
||||
let commands = parseCommands(commandsFilePath);
|
||||
fs.watchFile(commandsFilePath, (curr, prev) => {
|
||||
if (curr.mtime !== prev.mtime) {
|
||||
commands = require(commandsFilePath);
|
||||
}
|
||||
});
|
||||
|
||||
function exists(command)
|
||||
{
|
||||
let matches = commands.filter(obj => { return obj.Command === cmdPrefix+command; });
|
||||
if (matches.length > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get(command)
|
||||
{
|
||||
let matches = commands.filter(obj => { return obj.Command === cmdPrefix+command; });
|
||||
if (matches.length > 0) {
|
||||
let match = matches[0];
|
||||
if (match.Enabled === true) {
|
||||
return matches[0].Response;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Read in the array of objects exported from AnkhBot
|
||||
function parseCommands(filePath)
|
||||
{
|
||||
let data = fs.readFileSync(filePath, 'utf-8');
|
||||
let commands = eval(data);
|
||||
return commands;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
module.exports = {
|
||||
exists: exists,
|
||||
get: get
|
||||
};
|
||||
|
||||
const fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
const commandsFilePath = path.join(__dirname, '..', 'conf', 'text_commands');
|
||||
|
||||
// Read in basic text commands / definitions and watch for changes
|
||||
let commands = parseCommands(commandsFilePath);
|
||||
fs.watchFile(commandsFilePath, (curr, prev) => {
|
||||
if (curr.mtime !== prev.mtime) {
|
||||
commands = parseCommands(commandsFilePath);
|
||||
}
|
||||
});
|
||||
|
||||
// Read/parse text commands from the "database"
|
||||
function parseCommands(filePath)
|
||||
{
|
||||
let commands = {};
|
||||
let data = fs.readFileSync(filePath, 'utf-8');
|
||||
let commandLines = data.toString().split('\n');
|
||||
let commandParts;
|
||||
let aliases;
|
||||
commandLines.forEach(function(line) {
|
||||
if (line.length > 0 && line.indexOf('|') !== -1) {
|
||||
commandParts = line.split('|');
|
||||
// check for aliases
|
||||
aliases = commandParts[0].split(',');
|
||||
aliases.forEach(function(cmd) {
|
||||
commands[cmd] = commandParts[1];
|
||||
//console.log(`!${cmd},`);
|
||||
});
|
||||
}
|
||||
});
|
||||
return commands;
|
||||
}
|
||||
|
||||
function exists(command)
|
||||
{
|
||||
return commands.hasOwnProperty(command);
|
||||
}
|
||||
|
||||
function get(command)
|
||||
{
|
||||
if (exists(command)) {
|
||||
return commands[command];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
50
lib/utils.js
50
lib/utils.js
@@ -1,50 +0,0 @@
|
||||
// Converts seconds to human-readable time
|
||||
String.prototype.toHHMMSS = function () {
|
||||
let sec_num = parseInt(this, 10); // don't forget the second param
|
||||
let hours = Math.floor(sec_num / 3600);
|
||||
let minutes = Math.floor((sec_num - hours * 3600) / 60);
|
||||
let seconds = sec_num - hours * 3600 - minutes * 60;
|
||||
|
||||
if (hours < 10) {
|
||||
hours = "0" + hours;
|
||||
}
|
||||
if (minutes < 10) {
|
||||
minutes = "0" + minutes;
|
||||
}
|
||||
if (seconds < 10) {
|
||||
seconds = "0" + seconds;
|
||||
}
|
||||
return hours + ":" + minutes + ":" + seconds;
|
||||
};
|
||||
|
||||
async function asyncForEach(array, callback) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
}
|
||||
}
|
||||
|
||||
function randElement(arr) {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
function randSort() {
|
||||
return 0.5 - Math.random();
|
||||
}
|
||||
|
||||
function chunkSubstr(str, size) {
|
||||
const numChunks = Math.ceil(str.length / size);
|
||||
const chunks = new Array(numChunks);
|
||||
|
||||
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
|
||||
chunks[i] = str.substr(o, size);
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
asyncForEach,
|
||||
randElement,
|
||||
randSort,
|
||||
chunkSubstr
|
||||
};
|
||||
@@ -20,8 +20,13 @@
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js",
|
||||
"docker:build": "docker build -t ghbot:${VERSION:-latest} .",
|
||||
"docker:run": "docker run -d --name ghbot --restart always ghbot:${VERSION:-latest}"
|
||||
"up": "docker compose up -d",
|
||||
"down": "docker compose down",
|
||||
"build": "docker compose build",
|
||||
"restart": "docker compose restart",
|
||||
"logs": "docker compose logs -f",
|
||||
"image:build": "docker build -t ghbot:${VERSION:-latest} .",
|
||||
"image:run": "docker run -d --name ghbot --restart always ghbot:${VERSION:-latest}"
|
||||
},
|
||||
"author": "https://github.com/greenham",
|
||||
"license": "MIT"
|
||||
|
||||
Reference in New Issue
Block a user