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:
Chris Ham
2025-08-16 12:06:40 -07:00
parent e53360e887
commit 9661ba92d5
8 changed files with 557 additions and 183 deletions

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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
};

View File

@@ -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"