Files
mymc-premium/mymc-premium.js
2025-07-23 02:52:20 -04:00

264 lines
11 KiB
JavaScript

const Discord = require('discord.js');
const Docker = require('dockerode');
const { CronJob } = require('cron');
const { execSync } = require('child_process');
require('dotenv').config();
const client = new Discord.Client({
intents: [
Discord.GatewayIntentBits.Guilds,
Discord.GatewayIntentBits.GuildMembers
]
});
const docker = new Docker(); // Assumes local Docker socket; adjust if remote
const DISCORD_TOKEN = process.env.DISCORD_TOKEN;
const GUILD_ID = process.env.GUILD_ID;
const ROLE_IDS = {
standard: process.env.ROLE_ID_STANDARD,
manualUpgrade: process.env.ROLE_ID_MANUAL_UPGRADE,
superUpgrade: process.env.ROLE_ID_SUPER_UPGRADE
};
const DEFAULT_CPUS = parseInt(process.env.DEFAULT_CPUS);
const DEFAULT_MEMORY = parseInt(process.env.DEFAULT_MEMORY) * 1024 * 1024; // Convert MiB to bytes
const DEFAULT_SWAP = parseInt(process.env.DEFAULT_SWAP) * 1024 * 1024; // Convert MiB to bytes
const UPGRADED_CPUS = parseInt(process.env.UPGRADED_CPUS);
const UPGRADED_MEMORY = parseInt(process.env.UPGRADED_MEMORY) * 1024 * 1024; // Convert MiB to bytes
const UPGRADED_SWAP = parseInt(process.env.UPGRADED_SWAP) * 1024 * 1024; // Convert MiB to bytes
const SUPER_UPGRADED_CPUS = parseInt(process.env.SUPER_UPGRADED_CPUS);
const SUPER_UPGRADED_MEMORY = parseInt(process.env.SUPER_UPGRADED_MEMORY) * 1024 * 1024; // Convert MiB to bytes
const SUPER_UPGRADED_SWAP = parseInt(process.env.SUPER_UPGRADED_SWAP) * 1024 * 1024; // Convert MiB to bytes
const RESET_UNKNOWN_TO_DEFAULT = process.env.RESET_UNKNOWN_TO_DEFAULT === 'true';
const EXEC_TIMEOUT = parseInt(process.env.EXEC_TIMEOUT);
async function execWithTimeout(container, cmd, timeout = EXEC_TIMEOUT) {
try {
const exec = await container.exec({
Cmd: cmd,
AttachStdout: true,
AttachStderr: true
});
const stream = await exec.start();
let output = '';
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Command ${cmd.join(' ')} timed out after ${timeout}ms`)), timeout);
});
await Promise.race([
new Promise((resolve, reject) => {
stream.on('data', (chunk) => output += chunk.toString());
stream.on('end', () => resolve(output));
stream.on('error', reject);
}),
timeoutPromise
]);
return output;
} catch (err) {
throw err;
}
}
async function updateContainerConfig(container, name, userId, memoryLimit) {
try {
let sourceFile;
if (memoryLimit === SUPER_UPGRADED_MEMORY) {
sourceFile = 'startServer_superUpgrade.json';
} else if (memoryLimit === UPGRADED_MEMORY) {
sourceFile = 'startServer_upgrade.json';
} else {
sourceFile = 'startServer_downgrade.json';
}
console.log(` 🔄 Updating container ${name} with ${sourceFile}...`);
// Check if startServer.json exists
console.log(` 🔍 Checking if startServer.json exists in container ${name}...`);
try {
await execWithTimeout(container, ['test', '-f', '/var/tools/pm2/startServer.json']);
console.log(` ✅ startServer.json exists`);
// Remove existing startServer.json
console.log(` 🗑️ Removing existing startServer.json in container ${name}...`);
await execWithTimeout(container, ['rm', '-f', '/var/tools/pm2/startServer.json']);
console.log(` ✅ Removed startServer.json`);
} catch (err) {
console.log(` ⚠️ startServer.json does not exist or cannot be removed: ${err.message}`);
}
// Copy new startServer.json file
console.log(` 📤 Copying ${sourceFile} to container ${name}...`);
try {
execSync(`docker cp ${sourceFile} ${name}:/var/tools/pm2/startServer.json`, { stdio: 'inherit', timeout: EXEC_TIMEOUT });
console.log(` ✅ Copied ${sourceFile} to /var/tools/pm2/startServer.json`);
} catch (cpErr) {
console.error(` ❌ Error copying ${sourceFile} to container ${name}: ${cpErr.message}`);
return;
}
// Delete existing PM2 process
console.log(` 🗑️ Deleting PM2 process in container ${name}...`);
try {
await execWithTimeout(container, ['su', '-', 'mc', '-c', 'cd /var/tools/pm2 && pm2 delete 0']);
console.log(` ✅ PM2 process deleted`);
} catch (err) {
console.error(` ⚠️ Error deleting PM2 process in container ${name}: ${err.message}`);
// Continue to attempt starting the process
}
// Start new PM2 process
console.log(` ▶️ Starting PM2 process in container ${name}...`);
try {
await execWithTimeout(container, ['su', '-', 'mc', '-c', 'cd /var/tools/pm2 && pm2 start startServer.json']);
console.log(` ✅ PM2 process started with startServer.json`);
} catch (err) {
console.error(` ❌ Error starting PM2 process in container ${name}: ${err.message}`);
return;
}
} catch (err) {
console.error(` ❌ Error updating container ${name}: ${err.message}`);
}
}
async function checkContainers() {
console.log('\n=== Starting Container Check ===\n');
try {
// List running containers
const containers = await docker.listContainers({ all: false });
for (const contInfo of containers) {
// Container names start with '/', e.g., '/mc_1234567890'
const name = contInfo.Names[0].slice(1);
if (name.startsWith('mc_')) {
const userId = name.slice(3); // Extract Discord ID
const container = docker.getContainer(contInfo.Id);
const inspect = await container.inspect();
const currentCpus = inspect.HostConfig.NanoCpus / 1e9;
const currentMem = inspect.HostConfig.Memory;
const currentSwap = inspect.HostConfig.MemorySwap;
// Log container details in a structured format
console.log(`📦 Container: ${name}`);
console.log(` User ID: ${userId}`);
console.log(` Current Settings:`);
console.log(` CPUs: ${currentCpus}`);
console.log(` Memory: ${currentMem / 1024 / 1024} MiB`);
console.log(` Swap: ${currentSwap / 1024 / 1024} MiB`);
const isDefault =
currentCpus === DEFAULT_CPUS &&
currentMem === DEFAULT_MEMORY &&
currentSwap === DEFAULT_SWAP;
const isUpgraded =
currentCpus === UPGRADED_CPUS &&
currentMem === UPGRADED_MEMORY &&
currentSwap === UPGRADED_SWAP;
const isSuperUpgraded =
currentCpus === SUPER_UPGRADED_CPUS &&
currentMem === SUPER_UPGRADED_MEMORY &&
currentSwap === SUPER_UPGRADED_SWAP;
// Handle unknown limits
if (!isDefault && !isUpgraded && !isSuperUpgraded) {
console.log(` ⚠️ Warning: Unknown limits detected!`);
console.log(` Expected Default: CPUs=${DEFAULT_CPUS}, Memory=${DEFAULT_MEMORY / 1024 / 1024} MiB, Swap=${DEFAULT_SWAP / 1024 / 1024} MiB`);
console.log(` Expected Upgraded: CPUs=${UPGRADED_CPUS}, Memory=${UPGRADED_MEMORY / 1024 / 1024} MiB, Swap=${UPGRADED_SWAP / 1024 / 1024} MiB`);
console.log(` Expected Super Upgraded: CPUs=${SUPER_UPGRADED_CPUS}, Memory=${SUPER_UPGRADED_MEMORY / 1024 / 1024} MiB, Swap=${SUPER_UPGRADED_SWAP / 1024 / 1024} MiB`);
if (RESET_UNKNOWN_TO_DEFAULT) {
console.log(` 🔄 Resetting to default settings...`);
await container.update({
NanoCpus: DEFAULT_CPUS * 1e9,
Memory: DEFAULT_MEMORY,
MemorySwap: DEFAULT_SWAP
});
await updateContainerConfig(container, name, userId, DEFAULT_MEMORY);
console.log(` ✅ Container reset to default settings.`);
} else {
console.log(` ⏭️ Skipping due to unknown limits.`);
continue;
}
}
// Fetch guild and check roles
const guild = client.guilds.cache.get(GUILD_ID);
if (!guild) {
console.log(` ❌ Guild ${GUILD_ID} not found.`);
continue;
}
let hasSuperUpgradeRole = false;
let hasStandardOrManualRole = false;
try {
const member = await guild.members.fetch(userId);
hasSuperUpgradeRole = member.roles.cache.has(ROLE_IDS.superUpgrade);
hasStandardOrManualRole = [ROLE_IDS.standard, ROLE_IDS.manualUpgrade].some(roleId => member.roles.cache.has(roleId));
console.log(` Role Check: User ${hasSuperUpgradeRole ? 'has superUpgrade role' : hasStandardOrManualRole ? 'has standard or manual upgrade role' : 'has no relevant roles'} (${Object.values(ROLE_IDS).join(' or ')})`);
} catch (err) {
console.log(` ❌ Error fetching member ${userId}: ${err.message}`);
continue;
}
if (hasSuperUpgradeRole && !isSuperUpgraded) {
// Apply super upgrade
console.log(` 🔼 Applying super upgrade to container...`);
await container.update({
NanoCpus: SUPER_UPGRADED_CPUS * 1e9,
Memory: SUPER_UPGRADED_MEMORY,
MemorySwap: SUPER_UPGRADED_SWAP
});
await updateContainerConfig(container, name, userId, SUPER_UPGRADED_MEMORY);
console.log(` ✅ Super Upgraded to: CPUs=${SUPER_UPGRADED_CPUS}, Memory=${SUPER_UPGRADED_MEMORY / 1024 / 1024} MiB, Swap=${SUPER_UPGRADED_SWAP / 1024 / 1024} MiB`);
} else if (!hasSuperUpgradeRole && hasStandardOrManualRole && !isUpgraded) {
// Apply standard upgrade
console.log(` 🔼 Upgrading container...`);
await container.update({
NanoCpus: UPGRADED_CPUS * 1e9,
Memory: UPGRADED_MEMORY,
MemorySwap: UPGRADED_SWAP
});
await updateContainerConfig(container, name, userId, UPGRADED_MEMORY);
console.log(` ✅ Upgraded to: CPUs=${UPGRADED_CPUS}, Memory=${UPGRADED_MEMORY / 1024 / 1024} MiB, Swap=${UPGRADED_SWAP / 1024 / 1024} MiB`);
} else if (!hasSuperUpgradeRole && !hasStandardOrManualRole && (isUpgraded || isSuperUpgraded)) {
// Downgrade
console.log(` 🔽 Downgrading container...`);
await container.update({
NanoCpus: DEFAULT_CPUS * 1e9,
Memory: DEFAULT_MEMORY,
MemorySwap: DEFAULT_SWAP
});
await updateContainerConfig(container, name, userId, DEFAULT_MEMORY);
console.log(` ✅ Downgraded to: CPUs=${DEFAULT_CPUS}, Memory=${DEFAULT_MEMORY / 1024 / 1024} MiB, Swap=${DEFAULT_SWAP / 1024 / 1024} MiB`);
} else {
console.log(` ✅ No action needed. Container settings match role status.`);
}
console.log('----------------------------------------');
}
}
console.log('\n=== Container Check Completed ===\n');
} catch (err) {
console.error(`\n❌ Error in container check: ${err.message}\n`);
}
}
client.once('ready', () => {
console.log(`✅ Logged in as ${client.user.tag}. Bot is ready.`);
// Run initial check on startup
checkContainers();
// Cron job every 5 minutes
const job = new CronJob('*/5 * * * *', checkContainers, null, true, 'UTC');
job.start();
});
client.login(DISCORD_TOKEN);