212 lines
6.8 KiB
Python
212 lines
6.8 KiB
Python
import discord
|
||
import docker
|
||
import os
|
||
import json
|
||
from discord.ext import commands, tasks
|
||
from dotenv import load_dotenv
|
||
|
||
load_dotenv()
|
||
|
||
BOT_TOKEN = os.getenv("BOT_TOKEN")
|
||
DOCKER_HOST = os.getenv("DOCKER_HOST")
|
||
GUILD_ID = int(os.getenv("GUILD_ID")) # Your server ID
|
||
CATEGORY_NAME = os.getenv("CATEGORY_NAME") # Category name for the channels
|
||
|
||
intents = discord.Intents.default()
|
||
intents.message_content = True
|
||
bot = commands.Bot(command_prefix="!", intents=intents)
|
||
|
||
# Stores groups: { "groupname": { "channel_id": 123, "containers": ["nginx", "mysql"], "message_id": 456 } }
|
||
GROUPS_FILE = "groups.json"
|
||
|
||
def load_groups():
|
||
if os.path.exists(GROUPS_FILE):
|
||
with open(GROUPS_FILE, "r") as f:
|
||
return json.load(f)
|
||
return {}
|
||
|
||
def save_groups(groups):
|
||
with open(GROUPS_FILE, "w") as f:
|
||
json.dump(groups, f, indent=2)
|
||
|
||
def status_symbol(status):
|
||
symbols = {
|
||
"running": "🟢",
|
||
"exited": "🔴",
|
||
"paused": "🟡",
|
||
"created": "⚪",
|
||
}
|
||
return symbols.get(status, "❓")
|
||
|
||
# --- Commands ---
|
||
|
||
@bot.command()
|
||
async def container_list(ctx):
|
||
"""Shows all available containers on the host"""
|
||
try:
|
||
client = docker.DockerClient(base_url=DOCKER_HOST, timeout=5)
|
||
container_list = client.containers.list(all=True)
|
||
|
||
message = "**🐳 Available Containers:**\n```\n"
|
||
for c in container_list:
|
||
message += f"{status_symbol(c.status)} {c.name}\n"
|
||
message += "```\nUse `!group_create <name> <container1> <container2> ...` to create a group."
|
||
|
||
await ctx.send(message)
|
||
except Exception as e:
|
||
await ctx.send(f"❌ Error: {e}")
|
||
|
||
@bot.command()
|
||
async def group_create(ctx, group_name: str, *container_names):
|
||
"""Creates a group and a channel for it
|
||
Example: !group_create webservices nginx apache"""
|
||
if not container_names:
|
||
await ctx.send("❌ Please specify at least one container!\nExample: `!group_create webservices nginx apache`")
|
||
return
|
||
|
||
guild = bot.get_guild(GUILD_ID)
|
||
groups = load_groups()
|
||
|
||
# Create category if it doesn't exist
|
||
category = discord.utils.get(guild.categories, name=CATEGORY_NAME)
|
||
if not category:
|
||
category = await guild.create_category(CATEGORY_NAME)
|
||
|
||
# Create channel
|
||
channel_name = f"🐳{group_name.lower()}"
|
||
channel = discord.utils.get(category.channels, name=channel_name)
|
||
if not channel:
|
||
# Only bot is allowed to write
|
||
overwrites = {
|
||
guild.default_role: discord.PermissionOverwrite(send_messages=False),
|
||
guild.me: discord.PermissionOverwrite(send_messages=True)
|
||
}
|
||
channel = await category.create_text_channel(channel_name, overwrites=overwrites)
|
||
|
||
# Save group
|
||
groups[group_name] = {
|
||
"channel_id": channel.id,
|
||
"containers": list(container_names),
|
||
"message_id": None
|
||
}
|
||
save_groups(groups)
|
||
|
||
await ctx.send(f"✅ Group **{group_name}** created with channel {channel.mention}\nContainers: `{', '.join(container_names)}`")
|
||
|
||
@bot.command()
|
||
async def group_remove(ctx, group_name: str):
|
||
"""Deletes a group and its channel"""
|
||
groups = load_groups()
|
||
|
||
if group_name not in groups:
|
||
await ctx.send(f"❌ Group `{group_name}` not found.")
|
||
return
|
||
|
||
guild = bot.get_guild(GUILD_ID)
|
||
channel = guild.get_channel(groups[group_name]["channel_id"])
|
||
if channel:
|
||
await channel.delete()
|
||
|
||
del groups[group_name]
|
||
save_groups(groups)
|
||
await ctx.send(f"✅ Group **{group_name}** deleted.")
|
||
|
||
@bot.command()
|
||
async def group_list(ctx):
|
||
"""Shows all groups"""
|
||
groups = load_groups()
|
||
if not groups:
|
||
await ctx.send("No groups found. Use `!group_create` to create one.")
|
||
return
|
||
|
||
message = "**📋 Groups:**\n"
|
||
for name, data in groups.items():
|
||
message += f"\n**{name}:** `{', '.join(data['containers'])}`"
|
||
await ctx.send(message)
|
||
|
||
@bot.command()
|
||
async def info(ctx):
|
||
"""Shows all available commands and their usage"""
|
||
message = (
|
||
"**🤖 Docker Monitor Bot - Commands**\n\n"
|
||
"**📋 Container**\n"
|
||
"`!container_list`\n"
|
||
"→ Shows all available containers on the host\n\n"
|
||
"**📁 Groups**\n"
|
||
"`!group_create <name> <container1> <container2> ...`\n"
|
||
"→ Creates a group with a dedicated channel for the given containers\n"
|
||
"→ Example: `!group_create webservices nginx apache`\n\n"
|
||
"`!group_remove <name>`\n"
|
||
"→ Deletes a group and its channel\n"
|
||
"→ Example: `!group_remove webservices`\n\n"
|
||
"`!group_list`\n"
|
||
"→ Shows all existing groups and their containers\n\n"
|
||
"**ℹ️ Other**\n"
|
||
"`!info`\n"
|
||
"→ Shows this help message\n\n"
|
||
"**🔄 Monitoring**\n"
|
||
"Container statuses are automatically updated every **30 seconds** in their group channels.\n"
|
||
"🟢 running 🔴 exited 🟡 paused ⚪ created ❓ unknown"
|
||
)
|
||
await ctx.send(message)
|
||
|
||
# --- Monitoring ---
|
||
|
||
@tasks.loop(seconds=30)
|
||
async def monitor_containers():
|
||
groups = load_groups()
|
||
if not groups:
|
||
return
|
||
|
||
try:
|
||
docker_client = docker.DockerClient(base_url=DOCKER_HOST, timeout=5)
|
||
all_containers = {c.name: c.status for c in docker_client.containers.list(all=True)}
|
||
except Exception:
|
||
return
|
||
|
||
guild = bot.get_guild(GUILD_ID)
|
||
|
||
for group_name, data in groups.items():
|
||
channel = guild.get_channel(data["channel_id"])
|
||
if not channel:
|
||
continue
|
||
|
||
# Build status text
|
||
text = f"**🐳 {group_name.upper()}**\n```\n"
|
||
for container_name in data["containers"]:
|
||
if container_name in all_containers:
|
||
status = all_containers[container_name]
|
||
text += f"{status_symbol(status)} {container_name:<25} {status}\n"
|
||
else:
|
||
text += f"❓ {container_name:<25} not found\n"
|
||
text += "```"
|
||
|
||
try:
|
||
if data["message_id"]:
|
||
# Edit existing message
|
||
message = await channel.fetch_message(data["message_id"])
|
||
await message.edit(content=text)
|
||
else:
|
||
# Send first message
|
||
message = await channel.send(text)
|
||
data["message_id"] = message.id
|
||
save_groups(groups)
|
||
except discord.NotFound:
|
||
# Message was deleted, resend
|
||
message = await channel.send(text)
|
||
data["message_id"] = message.id
|
||
save_groups(groups)
|
||
|
||
@bot.event
|
||
async def on_command_error(ctx, error):
|
||
if isinstance(error, commands.CommandNotFound):
|
||
return # Ignore unknown commands silently
|
||
raise error # All other errors still show in logs
|
||
|
||
@bot.event
|
||
async def on_ready():
|
||
print(f"Bot is online as {bot.user}")
|
||
monitor_containers.start()
|
||
|
||
bot.run(BOT_TOKEN)
|