294 lines
9.7 KiB
JavaScript
294 lines
9.7 KiB
JavaScript
const SystemCommandBase = require('../../classes/SystemCommandBase');
|
|
const { SlashCommandBuilder } = require('discord.js');
|
|
|
|
class PM2Control extends SystemCommandBase {
|
|
constructor(client) {
|
|
super(client);
|
|
this.name = 'pm2';
|
|
this.description = 'Control PM2 processes on the server';
|
|
}
|
|
|
|
addOptions(builder) {
|
|
builder
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName('list')
|
|
.setDescription('List all PM2 processes')
|
|
)
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName('restart')
|
|
.setDescription('Restart a PM2 process')
|
|
.addStringOption(option =>
|
|
option.setName('process')
|
|
.setDescription('Process name or ID to restart')
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName('stop')
|
|
.setDescription('Stop a PM2 process')
|
|
.addStringOption(option =>
|
|
option.setName('process')
|
|
.setDescription('Process name or ID to stop')
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName('start')
|
|
.setDescription('Start a stopped PM2 process')
|
|
.addStringOption(option =>
|
|
option.setName('process')
|
|
.setDescription('Process name or ID to start')
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName('logs')
|
|
.setDescription('Get logs from a PM2 process')
|
|
.addStringOption(option =>
|
|
option.setName('process')
|
|
.setDescription('Process name or ID to get logs from')
|
|
.setRequired(true)
|
|
)
|
|
.addIntegerOption(option =>
|
|
option.setName('lines')
|
|
.setDescription('Number of log lines to retrieve')
|
|
.setRequired(false)
|
|
)
|
|
);
|
|
}
|
|
|
|
async execute(interaction) {
|
|
try {
|
|
await interaction.deferReply();
|
|
|
|
// Get the subcommand
|
|
const subcommand = interaction.options.getSubcommand();
|
|
|
|
// Handle each subcommand
|
|
switch (subcommand) {
|
|
case 'list':
|
|
await this.handleListCommand(interaction);
|
|
break;
|
|
case 'restart':
|
|
await this.handleRestartCommand(interaction);
|
|
break;
|
|
case 'stop':
|
|
await this.handleStopCommand(interaction);
|
|
break;
|
|
case 'start':
|
|
await this.handleStartCommand(interaction);
|
|
break;
|
|
case 'logs':
|
|
await this.handleLogsCommand(interaction);
|
|
break;
|
|
default:
|
|
await interaction.editReply('Unknown subcommand');
|
|
}
|
|
} catch (error) {
|
|
console.error('PM2 command error:', error);
|
|
await this.sendErrorResponse(interaction, `Error executing PM2 command: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async handleListCommand(interaction) {
|
|
const { stdout, stderr, success } = await this.execCommand('pm2 jlist');
|
|
|
|
if (!success) {
|
|
return await this.sendErrorResponse(interaction, `Failed to list PM2 processes: ${stderr}`);
|
|
}
|
|
|
|
try {
|
|
const processes = JSON.parse(stdout);
|
|
if (processes.length === 0) {
|
|
return await interaction.editReply('No PM2 processes found');
|
|
}
|
|
|
|
const embed = {
|
|
title: 'PM2 Processes',
|
|
color: 0x00FF00,
|
|
fields: [],
|
|
timestamp: new Date(),
|
|
footer: {
|
|
text: 'blahaj.tr PM2 Manager'
|
|
}
|
|
};
|
|
|
|
for (const proc of processes) {
|
|
// Get the status emoji
|
|
let statusEmoji = '⚪';
|
|
switch (proc.pm2_env.status) {
|
|
case 'online': statusEmoji = '🟢'; break;
|
|
case 'stopping': statusEmoji = '🟠'; break;
|
|
case 'stopped': statusEmoji = '🔴'; break;
|
|
case 'errored': statusEmoji = '❌'; break;
|
|
case 'launching': statusEmoji = '🟡'; break;
|
|
}
|
|
|
|
// Calculate memory in MB
|
|
const memoryUsage = proc.monit?.memory ? Math.round(proc.monit.memory / 1024 / 1024 * 100) / 100 : 0;
|
|
|
|
// Calculate uptime
|
|
const uptime = proc.pm2_env?.pm_uptime ? Math.floor((Date.now() - proc.pm2_env.pm_uptime) / 1000) : 0;
|
|
const days = Math.floor(uptime / 86400);
|
|
const hours = Math.floor((uptime % 86400) / 3600);
|
|
const minutes = Math.floor((uptime % 3600) / 60);
|
|
const uptimeStr = days > 0 ?
|
|
`${days}d ${hours}h ${minutes}m` :
|
|
`${hours}h ${minutes}m`;
|
|
|
|
embed.fields.push({
|
|
name: `${statusEmoji} ${proc.name} (id: ${proc.pm_id})`,
|
|
value: `**Status:** ${proc.pm2_env.status}\n` +
|
|
`**Memory:** ${memoryUsage} MB\n` +
|
|
`**CPU:** ${proc.monit?.cpu || 0}%\n` +
|
|
`**Uptime:** ${uptimeStr}\n` +
|
|
`**Restarts:** ${proc.pm2_env.restart_time || 0}`,
|
|
inline: true
|
|
});
|
|
}
|
|
|
|
await interaction.editReply({ embeds: [embed] });
|
|
} catch (error) {
|
|
return await this.sendErrorResponse(interaction, `Error parsing PM2 process list: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async handleRestartCommand(interaction) {
|
|
const processId = interaction.options.getString('process');
|
|
|
|
// Validate input to prevent command injection
|
|
if (!/^[a-zA-Z0-9_.-]+$/.test(processId)) {
|
|
return await this.sendErrorResponse(interaction, 'Invalid process name or ID. Use only alphanumeric characters, underscores, dots, or hyphens.');
|
|
}
|
|
|
|
await interaction.editReply(`Restarting PM2 process: ${processId}...`);
|
|
|
|
const { stdout, stderr, success } = await this.execCommand(`pm2 restart ${processId}`);
|
|
|
|
if (success) {
|
|
const embed = {
|
|
title: 'PM2 Process Restarted',
|
|
description: `Successfully restarted process: **${processId}**`,
|
|
color: 0x00FF00,
|
|
timestamp: new Date(),
|
|
footer: {
|
|
text: 'blahaj.tr PM2 Manager'
|
|
}
|
|
};
|
|
|
|
await interaction.editReply({ embeds: [embed] });
|
|
} else {
|
|
await this.sendErrorResponse(interaction, `Failed to restart process: ${stderr || stdout}`);
|
|
}
|
|
}
|
|
|
|
async handleStopCommand(interaction) {
|
|
const processId = interaction.options.getString('process');
|
|
|
|
// Validate input to prevent command injection
|
|
if (!/^[a-zA-Z0-9_.-]+$/.test(processId)) {
|
|
return await this.sendErrorResponse(interaction, 'Invalid process name or ID. Use only alphanumeric characters, underscores, dots, or hyphens.');
|
|
}
|
|
|
|
await interaction.editReply(`Stopping PM2 process: ${processId}...`);
|
|
|
|
const { stdout, stderr, success } = await this.execCommand(`pm2 stop ${processId}`);
|
|
|
|
if (success) {
|
|
const embed = {
|
|
title: 'PM2 Process Stopped',
|
|
description: `Successfully stopped process: **${processId}**`,
|
|
color: 0xFFA500, // Orange color
|
|
timestamp: new Date(),
|
|
footer: {
|
|
text: 'blahaj.tr PM2 Manager'
|
|
}
|
|
};
|
|
|
|
await interaction.editReply({ embeds: [embed] });
|
|
} else {
|
|
await this.sendErrorResponse(interaction, `Failed to stop process: ${stderr || stdout}`);
|
|
}
|
|
}
|
|
|
|
async handleStartCommand(interaction) {
|
|
const processId = interaction.options.getString('process');
|
|
|
|
// Validate input to prevent command injection
|
|
if (!/^[a-zA-Z0-9_.-]+$/.test(processId)) {
|
|
return await this.sendErrorResponse(interaction, 'Invalid process name or ID. Use only alphanumeric characters, underscores, dots, or hyphens.');
|
|
}
|
|
|
|
await interaction.editReply(`Starting PM2 process: ${processId}...`);
|
|
|
|
const { stdout, stderr, success } = await this.execCommand(`pm2 start ${processId}`);
|
|
|
|
if (success) {
|
|
const embed = {
|
|
title: 'PM2 Process Started',
|
|
description: `Successfully started process: **${processId}**`,
|
|
color: 0x00FF00,
|
|
timestamp: new Date(),
|
|
footer: {
|
|
text: 'blahaj.tr PM2 Manager'
|
|
}
|
|
};
|
|
|
|
await interaction.editReply({ embeds: [embed] });
|
|
} else {
|
|
await this.sendErrorResponse(interaction, `Failed to start process: ${stderr || stdout}`);
|
|
}
|
|
}
|
|
|
|
async handleLogsCommand(interaction) {
|
|
const processId = interaction.options.getString('process');
|
|
const lines = interaction.options.getInteger('lines') || 10;
|
|
|
|
// Validate input to prevent command injection
|
|
if (!/^[a-zA-Z0-9_.-]+$/.test(processId)) {
|
|
return await this.sendErrorResponse(interaction, 'Invalid process name or ID. Use only alphanumeric characters, underscores, dots, or hyphens.');
|
|
}
|
|
|
|
// Limit number of lines to prevent huge messages
|
|
const safeLinesCount = Math.min(lines, 30);
|
|
|
|
await interaction.editReply(`Fetching last ${safeLinesCount} lines of logs for ${processId}...`);
|
|
|
|
const { stdout, stderr, success } = await this.execCommand(`pm2 logs ${processId} --lines ${safeLinesCount} --nostream`);
|
|
|
|
if (success) {
|
|
// Format the logs into a manageable message
|
|
let logsContent = stdout.trim();
|
|
|
|
// If logs are too long, truncate
|
|
if (logsContent.length > 1900) {
|
|
logsContent = logsContent.substring(logsContent.length - 1900) + '...';
|
|
}
|
|
|
|
const embed = {
|
|
title: `PM2 Logs: ${processId}`,
|
|
color: 0x3498db,
|
|
description: `Last ${safeLinesCount} lines of logs:`,
|
|
timestamp: new Date(),
|
|
footer: {
|
|
text: 'blahaj.tr PM2 Manager'
|
|
}
|
|
};
|
|
|
|
await interaction.editReply({
|
|
embeds: [embed],
|
|
content: `\`\`\`\n${logsContent || 'No logs available'}\n\`\`\``
|
|
});
|
|
} else {
|
|
await this.sendErrorResponse(interaction, `Failed to get logs: ${stderr || 'Unknown error'}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = PM2Control;
|