Skip to content

Commit

Permalink
Add Leonidias and Open-Interpreter
Browse files Browse the repository at this point in the history
  • Loading branch information
ZackBradshaw committed Oct 18, 2023
1 parent 9f7b8e2 commit ed5c8ee
Show file tree
Hide file tree
Showing 18 changed files with 1,285 additions and 1 deletion.
2 changes: 2 additions & 0 deletions apps/Leonidias/orchistrator/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.env
3 changes: 3 additions & 0 deletions apps/Leonidias/orchistrator/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DISCORD_TOKEN=
DISCORD_CLIENT_ID=
DISCORD_GUILD_ID=
2 changes: 2 additions & 0 deletions apps/Leonidias/orchistrator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.env
14 changes: 14 additions & 0 deletions apps/Leonidias/orchistrator/Dockerfile
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" ]
42 changes: 42 additions & 0 deletions apps/Leonidias/orchistrator/README.md
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.
22 changes: 22 additions & 0 deletions apps/Leonidias/orchistrator/backend/deleteCommands.js
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);

53 changes: 53 additions & 0 deletions apps/Leonidias/orchistrator/backend/deployCommands.js
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);
}
})();

39 changes: 39 additions & 0 deletions apps/Leonidias/orchistrator/commands/allContainers.js
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] });
}
},
};
14 changes: 14 additions & 0 deletions apps/Leonidias/orchistrator/commands/ping.js
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!");
},
};
69 changes: 69 additions & 0 deletions apps/Leonidias/orchistrator/commands/restart.js
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}".`);
}
}
};
10 changes: 10 additions & 0 deletions apps/Leonidias/orchistrator/commands/server.js
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}`);
},
};
92 changes: 92 additions & 0 deletions apps/Leonidias/orchistrator/commands/startContainer.js
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.`);
}
},
};
Loading

0 comments on commit ed5c8ee

Please sign in to comment.