-
-
Notifications
You must be signed in to change notification settings - Fork 255
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9f7b8e2
commit ed5c8ee
Showing
18 changed files
with
1,285 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
DISCORD_TOKEN= | ||
DISCORD_CLIENT_ID= | ||
DISCORD_GUILD_ID= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
FROM node:19-slim | ||
|
||
WORKDIR /app | ||
|
||
COPY package.json /app | ||
RUN npm install | ||
|
||
COPY . . | ||
|
||
ENV DISCORD_TOKEN "" \ | ||
DISCORD_CLIENT_ID "" \ | ||
DISCORD_GUILD_ID "" | ||
|
||
CMD [ "node", "index.js" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
|
||
# Server-Bot | ||
|
||
[View on Docker Hub](https://hub.docker.com/r/allenrkeen/server-bot) | ||
### Discord bot to remotely monitor and control a docker based server. Using the docker socket. | ||
|
||
Setup is pretty straightforward. | ||
1. Create a new application in the *[discord developer portal](https://discord.com/developers/applications)* | ||
2. Go to the bot section and click *Add Bot* | ||
3. Reset Token and keep the token somewhere secure (This will be referred to as "DISCORD_TOKEN" in .env and docker environment variables) | ||
4. Get the "Application ID" from the General Information tab of your application (This will be referred to as "DISCORD_CLIENT_ID" in .env and docker environment variables) | ||
5. *Optional:* If you have developer mode enabled in Discord, get your server's ID by right-clicking on the server name and clicking "Copy ID" (This will be referred to as "DISCORD_GUILD_ID" in .env and docker environment variables) | ||
- If you skip this, it will still work, but commands will be published globally instead of to your server and can take up to an hour to be available in your server. | ||
- Using the Server ID will be more secure, making the commands available only in the specified server. | ||
6. Run the application in your preffered method. | ||
- Run the docker container with the provided [docker-compose.yml](docker-compose.yml) or the docker run command below. | ||
|
||
```bash | ||
docker run -v /var/run/docker.sock:/var/run/docker.sock --name server-bot \ | ||
-e DISCORD_TOKEN=your_token_here \ | ||
-e DISCORD_CLIENT_ID=your_client_id_here \ | ||
-e DISCORD_GUILD_ID=your_guild_id_here \ | ||
allenrkeen/server-bot:latest | ||
``` | ||
|
||
- Clone the repo, cd into the server-bot directory and run "npm install" to install dependencies, then "npm run start" to start the server | ||
7. The program will build an invite link with the correct permissions and put it in the logs. Click the link and confirm the server to add the bot to. | ||
|
||
|
||
Current commands: | ||
- /allcontainers | ||
- provides container name and status for all containers | ||
- /restartcontainer | ||
- provides an autocomplete list of running containers to select from, or just type in container name then restarts the container | ||
- /stopcontainer | ||
- provides an autocomplete list of running containers to select from, or just type in container name then stops the container | ||
- /startcontainer | ||
- provides an autocomplete list of stopped containers to select from, or just type in container name then starts the container | ||
- /ping | ||
- Replies with "Pong!" when the bot is listening | ||
- /server | ||
- Replies with Server Name and member count, good for testing. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* This file is used to delete all commands from the Discord API. | ||
* Only use this if you want to delete all commands and understand the consequences. | ||
*/ | ||
|
||
require('dotenv').config(); | ||
const token = process.env.DISCORD_TOKEN; | ||
const clientID = process.env.DISCORD_CLIENT_ID; | ||
const guildID = process.env.DISCORD_GUILD_ID; | ||
const { REST, Routes } = require('discord.js'); | ||
const fs = require('node:fs'); | ||
|
||
const rest = new REST({ version: '10' }).setToken(token); | ||
|
||
rest.put(Routes.applicationCommands(clientID), { body: [] }) | ||
.then(() => console.log('Successfully deleted application (/) commands.')) | ||
.catch(console.error); | ||
|
||
rest.put(Routes.applicationGuildCommands(clientID, guildID), { body: [] }) | ||
.then(() => console.log('Successfully deleted guild (/) commands.')) | ||
.catch(console.error); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
This script pushes all commands in the commands folder to be usable in discord. | ||
*/ | ||
|
||
require('dotenv').config(); | ||
const token = process.env.DISCORD_TOKEN; | ||
const clientID = process.env.DISCORD_CLIENT_ID; | ||
const guildID = process.env.DISCORD_GUILD_ID; | ||
const { REST, Routes } = require('discord.js'); | ||
const fs = require('node:fs'); | ||
|
||
const commands = []; | ||
|
||
// Get all commands from the commands folder | ||
|
||
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js')); | ||
console.log(commandFiles); | ||
|
||
for (const file of commandFiles) { | ||
const command = require(`../commands/${file}`); | ||
commands.push(command.data.toJSON()); | ||
} | ||
|
||
const rest = new REST({ version: '10' }).setToken(token); | ||
|
||
// console.log(commands); | ||
|
||
(async () => { | ||
try { | ||
const rest = new REST({ version: '10' }).setToken(token); | ||
|
||
console.log('Started refreshing application (/) commands.'); | ||
|
||
//publish to guild if guildID is set, otherwise publish to global | ||
if (guildID) { | ||
const data = await rest.put( | ||
Routes.applicationGuildCommands(clientID, guildID), | ||
{ body: commands }, | ||
); | ||
console.log('Successfully reloaded '+ data.length +' commands.'); | ||
} else { | ||
const data = await rest.put( | ||
Routes.applicationCommands(clientID), | ||
{ body: commands }, | ||
); | ||
console.log('Successfully reloaded '+ data.length +' commands.'); | ||
} | ||
|
||
} catch (error) { | ||
console.error(error); | ||
} | ||
})(); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* A command that lists all containers with their status */ | ||
|
||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); | ||
const Docker = require('node-docker-api').Docker; | ||
|
||
module.exports = { | ||
data: new SlashCommandBuilder() | ||
.setName("allcontainers") | ||
.setDescription("Lists all containers"), | ||
async execute(interaction) { | ||
outArray = []; | ||
interaction.reply('Listing all containers...'); | ||
|
||
//create docker client | ||
const docker = new Docker({ socketPath: '/var/run/docker.sock' }); | ||
|
||
// get all containers | ||
const containers = await docker.container.list({ all: true}); | ||
|
||
// create array of containers with name and status | ||
outArray = containers.map(c => { | ||
return { | ||
name: c.data.Names[0].slice(1), | ||
status: c.data.State | ||
}; | ||
}); | ||
|
||
embedCount = Math.ceil(outArray.length / 25); | ||
for (let i = 0; i < embedCount; i++) { | ||
const embed = new EmbedBuilder() | ||
.setTitle('Containers') | ||
.addFields(outArray.slice(i * 25, (i + 1) * 25).map(e => { | ||
return { name: e.name, value: e.status }; | ||
})) | ||
.setColor(0x00AE86); | ||
interaction.channel.send({ embeds: [embed] }); | ||
} | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
A ping command that replies with "Pong!" when bot is running. | ||
*/ | ||
|
||
const { SlashCommandBuilder } = require("discord.js"); | ||
|
||
module.exports = { | ||
data: new SlashCommandBuilder() | ||
.setName("ping") | ||
.setDescription("Replies with Pong!"), | ||
async execute(interaction) { | ||
await interaction.reply("Pong!"); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); | ||
const Docker = require('node-docker-api').Docker; | ||
|
||
module.exports = { | ||
data: new SlashCommandBuilder() | ||
.setName("restartcontainer") | ||
.setDescription("Restarts a Docker container") | ||
.addStringOption(option => | ||
option.setName('container') | ||
.setDescription('The container to restart') | ||
.setRequired(true) | ||
.setAutocomplete(true)), | ||
async autocomplete(interaction) { | ||
try { | ||
// Create docker client | ||
const docker = new Docker({ socketPath: '/var/run/docker.sock' }); | ||
|
||
// Get list of running containers | ||
const containers = await docker.container.list({ all: true, filters: { status: ['running'] } }); | ||
const runningContainers = containers.map(c => c.data.Names[0].slice(1)); | ||
|
||
// Filter list of containers by focused value | ||
const focusedValue = interaction.options.getFocused(true); | ||
const filteredContainers = runningContainers.filter(container => container.startsWith(focusedValue.value)); | ||
|
||
//slice if more than 25 | ||
let sliced; | ||
if (filteredContainers.length > 25) { | ||
sliced = filteredContainers.slice(0, 25); | ||
} else { | ||
sliced = filteredContainers; | ||
} | ||
|
||
// Respond with filtered list of containers | ||
await interaction.respond(sliced.map(container => ({ name: container, value: container }))); | ||
|
||
} catch (error) { | ||
// Handle error | ||
console.error(error); | ||
await interaction.reply('An error occurred while getting the list of running containers.'); | ||
} | ||
}, | ||
async execute(interaction) { | ||
try { | ||
// create docker client | ||
const docker = new Docker({ socketPath: '/var/run/docker.sock' }); | ||
|
||
// Get container name from options | ||
const container = interaction.options.getString('container'); | ||
|
||
// Restart container | ||
await interaction.reply(`Restarting container "${container}"...`); | ||
const containers = await docker.container.list({ all: true, filters: { name: [container] } }); | ||
if (containers.length === 0) { | ||
await interaction.followUp(`Container "${container}" does not exist.`); | ||
throw new Error(`Container "${container}" does not exist.`); | ||
} | ||
await containers[0].restart(); | ||
|
||
|
||
// Confirm that container was restarted | ||
await interaction.followUp(`Container "${container}" was successfully restarted.`); | ||
} catch (error) { | ||
// Handle error | ||
console.error(error); | ||
await interaction.followUp(`An error occurred while trying to restart the container "${container}".`); | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const { SlashCommandBuilder } = require('discord.js'); | ||
|
||
module.exports = { | ||
data: new SlashCommandBuilder() | ||
.setName("server") | ||
.setDescription("Replies with server name and member count."), | ||
async execute(interaction) { | ||
await interaction.reply(`Server name: ${interaction.guild.name}\nTotal members: ${interaction.guild.memberCount}`); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); | ||
const Docker = require('node-docker-api').Docker; | ||
|
||
module.exports = { | ||
data: new SlashCommandBuilder() | ||
.setName("startcontainer") | ||
.setDescription("Starts a Docker container") | ||
.addStringOption(option => | ||
option.setName('container') | ||
.setDescription('The container to start') | ||
.setRequired(true) | ||
.setAutocomplete(true)), | ||
async autocomplete(interaction) { | ||
try { | ||
// Create docker client | ||
const docker = new Docker({ socketPath: '/var/run/docker.sock' }); | ||
|
||
// Get list of running containers | ||
const containers = await docker.container.list({ all: true, filters: { status: ['exited'] } }); | ||
const runningContainers = containers.map(c => c.data.Names[0].slice(1)); | ||
|
||
// Filter list of containers by focused value | ||
const focusedValue = interaction.options.getFocused(true); | ||
const filteredContainers = runningContainers.filter(container => container.startsWith(focusedValue.value)); | ||
|
||
//slice if more than 25 | ||
let sliced; | ||
if (filteredContainers.length > 25) { | ||
sliced = filteredContainers.slice(0, 25); | ||
} else { | ||
sliced = filteredContainers; | ||
} | ||
|
||
// Respond with filtered list of containers | ||
await interaction.respond(sliced.map(container => ({ name: container, value: container }))); | ||
|
||
} catch (error) { | ||
// Handle error | ||
console.error(error); | ||
await interaction.reply('An error occurred while getting the list of running containers.'); | ||
} | ||
}, | ||
async execute(interaction) { | ||
try { | ||
// Get container name from options | ||
const containerName = interaction.options.getString('container'); | ||
|
||
// Start container in interactive mode | ||
await interaction.reply(`Starting container "${containerName}" in interactive mode...`); | ||
const container = docker.getContainer(containerName); | ||
const info = await container.inspect(); | ||
if (!info) { | ||
await interaction.followUp(`Container "${containerName}" does not exist.`); | ||
throw new Error(`Container "${containerName}" does not exist.`); | ||
} | ||
await container.start({ | ||
AttachStdin: true, | ||
AttachStdout: true, | ||
AttachStderr: true, | ||
Tty: true, | ||
OpenStdin: true, | ||
StdinOnce: false | ||
}); | ||
|
||
// Attach to container's streams | ||
const stream = await container.attach({ | ||
stream: true, | ||
stdin: true, | ||
stdout: true, | ||
stderr: true | ||
}); | ||
|
||
// Use socket.io for real-time communication with the container | ||
io.on('connection', (socket) => { | ||
socket.on('containerInput', (data) => { | ||
stream.write(data + '\n'); // Send input to the container | ||
}); | ||
|
||
stream.on('data', (data) => { | ||
socket.emit('containerOutput', data.toString()); // Send container's output to the client | ||
}); | ||
}); | ||
|
||
// Confirm that container was started | ||
await interaction.followUp(`Container "${containerName}" was successfully started in interactive mode.`); | ||
} catch (error) { | ||
// Handle error | ||
console.error(error); | ||
await interaction.followUp(`An error occurred while trying to start the container "${containerName}" in interactive mode.`); | ||
} | ||
}, | ||
}; |
Oops, something went wrong.