Upload files to "/"
This commit is contained in:
420
main.py
420
main.py
@@ -2,22 +2,26 @@ import discord
|
|||||||
import docker
|
import docker
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime, timezone
|
||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks
|
||||||
|
from discord import app_commands
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
BOT_TOKEN = os.getenv("BOT_TOKEN")
|
BOT_TOKEN = os.getenv("BOT_TOKEN")
|
||||||
DOCKER_HOST = os.getenv("DOCKER_HOST")
|
DOCKER_HOST = os.getenv("DOCKER_HOST")
|
||||||
GUILD_ID = int(os.getenv("GUILD_ID")) # Your server ID
|
GUILD_ID = int(os.getenv("GUILD_ID"))
|
||||||
CATEGORY_NAME = os.getenv("CATEGORY_NAME") # Category name for the channels
|
CATEGORY_NAME = os.getenv("CATEGORY_NAME", "Docker Status")
|
||||||
|
|
||||||
intents = discord.Intents.default()
|
intents = discord.Intents.default()
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
bot = commands.Bot(command_prefix="!", intents=intents)
|
bot = commands.Bot(command_prefix="!", intents=intents)
|
||||||
|
|
||||||
# Stores groups: { "groupname": { "channel_id": 123, "containers": ["nginx", "mysql"], "message_id": 456 } }
|
|
||||||
GROUPS_FILE = "groups.json"
|
GROUPS_FILE = "groups.json"
|
||||||
|
SETTINGS_FILE = "settings.json"
|
||||||
|
|
||||||
def load_groups():
|
def load_groups():
|
||||||
if os.path.exists(GROUPS_FILE):
|
if os.path.exists(GROUPS_FILE):
|
||||||
@@ -29,77 +33,216 @@ def save_groups(groups):
|
|||||||
with open(GROUPS_FILE, "w") as f:
|
with open(GROUPS_FILE, "w") as f:
|
||||||
json.dump(groups, f, indent=2)
|
json.dump(groups, f, indent=2)
|
||||||
|
|
||||||
|
def load_settings():
|
||||||
|
if os.path.exists(SETTINGS_FILE):
|
||||||
|
with open(SETTINGS_FILE, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {
|
||||||
|
"alert_mode": None,
|
||||||
|
"alert_channel_id": None,
|
||||||
|
"setup_done": False
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_settings(settings):
|
||||||
|
with open(SETTINGS_FILE, "w") as f:
|
||||||
|
json.dump(settings, f, indent=2)
|
||||||
|
|
||||||
def status_symbol(status):
|
def status_symbol(status):
|
||||||
symbols = {
|
return {
|
||||||
"running": "🟢",
|
"running": "🟢",
|
||||||
"exited": "🔴",
|
"exited": "🔴",
|
||||||
"paused": "🟡",
|
"paused": "🟡",
|
||||||
"created": "⚪",
|
"created": "⚪",
|
||||||
}
|
}.get(status, "❓")
|
||||||
return symbols.get(status, "❓")
|
|
||||||
|
|
||||||
# --- Commands ---
|
def get_docker_client():
|
||||||
|
return docker.DockerClient(base_url=DOCKER_HOST, timeout=5)
|
||||||
|
|
||||||
@bot.command()
|
def get_all_containers():
|
||||||
async def container_list(ctx):
|
client = get_docker_client()
|
||||||
"""Shows all available containers on the host"""
|
return client.containers.list(all=True)
|
||||||
|
|
||||||
|
def format_uptime(started_at: str) -> str:
|
||||||
try:
|
try:
|
||||||
client = docker.DockerClient(base_url=DOCKER_HOST, timeout=5)
|
started = datetime.fromisoformat(started_at[:26].replace("Z", "+00:00"))
|
||||||
container_list = client.containers.list(all=True)
|
now = datetime.now(timezone.utc)
|
||||||
|
delta = now - started
|
||||||
|
days = delta.days
|
||||||
|
hours, remainder = divmod(delta.seconds, 3600)
|
||||||
|
minutes, _ = divmod(remainder, 60)
|
||||||
|
if days > 0:
|
||||||
|
return f"{days}d {hours}h {minutes}m"
|
||||||
|
elif hours > 0:
|
||||||
|
return f"{hours}h {minutes}m"
|
||||||
|
else:
|
||||||
|
return f"{minutes}m"
|
||||||
|
except Exception:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
message = "**🐳 Available Containers:**\n```\n"
|
def build_status_embed(group_name: str, containers: list, all_containers: dict, container_objects: list) -> discord.Embed:
|
||||||
for c in container_list:
|
embed = discord.Embed(
|
||||||
message += f"{status_symbol(c.status)} {c.name}\n"
|
title=f"🐳 {group_name.upper()}",
|
||||||
message += "```\nUse `!group_create <name> <container1> <container2> ...` to create a group."
|
color=discord.Color.blurple(),
|
||||||
|
timestamp=datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
container_map = {c.name: c for c in container_objects}
|
||||||
|
for name in containers:
|
||||||
|
if name in all_containers:
|
||||||
|
status = all_containers[name]
|
||||||
|
uptime = ""
|
||||||
|
if status == "running" and name in container_map:
|
||||||
|
try:
|
||||||
|
started_at = container_map[name].attrs["State"]["StartedAt"]
|
||||||
|
uptime = f"\nUptime: `{format_uptime(started_at)}`"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
embed.add_field(
|
||||||
|
name=f"{status_symbol(status)} {name}",
|
||||||
|
value=f"`{status}`{uptime}",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
embed.add_field(
|
||||||
|
name=f"❓ {name}",
|
||||||
|
value="`not found`",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
embed.set_footer(text="Last updated")
|
||||||
|
return embed
|
||||||
|
|
||||||
await ctx.send(message)
|
async def health_handler(request):
|
||||||
|
return web.Response(text="OK")
|
||||||
|
|
||||||
|
async def start_health_server():
|
||||||
|
app = web.Application()
|
||||||
|
app.router.add_get("/health", health_handler)
|
||||||
|
runner = web.AppRunner(app)
|
||||||
|
await runner.setup()
|
||||||
|
site = web.TCPSite(runner, "0.0.0.0", 8080)
|
||||||
|
await site.start()
|
||||||
|
print("Health check running on :8080/health")
|
||||||
|
|
||||||
|
async def container_autocomplete(interaction: discord.Interaction, current: str):
|
||||||
|
try:
|
||||||
|
containers = get_all_containers()
|
||||||
|
names = [c.name for c in containers if current.lower() in c.name.lower()]
|
||||||
|
return [app_commands.Choice(name=n, value=n) for n in names[:25]]
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def group_autocomplete(interaction: discord.Interaction, current: str):
|
||||||
|
groups = load_groups()
|
||||||
|
return [
|
||||||
|
app_commands.Choice(name=n, value=n)
|
||||||
|
for n in groups if current.lower() in n.lower()
|
||||||
|
][:25]
|
||||||
|
|
||||||
|
@bot.tree.command(name="setup", description="Setup the bot (alert preferences etc.)")
|
||||||
|
@app_commands.describe(
|
||||||
|
alert_mode="Where should crash alerts be sent?",
|
||||||
|
alert_channel="Channel for alerts (only needed if alert_mode is 'channel')"
|
||||||
|
)
|
||||||
|
@app_commands.choices(alert_mode=[
|
||||||
|
app_commands.Choice(name="In each group channel", value="group"),
|
||||||
|
app_commands.Choice(name="In a separate alert channel", value="channel"),
|
||||||
|
])
|
||||||
|
async def setup(interaction: discord.Interaction, alert_mode: app_commands.Choice[str], alert_channel: discord.TextChannel = None):
|
||||||
|
settings = load_settings()
|
||||||
|
|
||||||
|
if alert_mode.value == "channel" and alert_channel is None:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"❌ Please also select an alert channel when using 'separate alert channel' mode.",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
settings["alert_mode"] = alert_mode.value
|
||||||
|
settings["alert_channel_id"] = alert_channel.id if alert_channel else None
|
||||||
|
settings["setup_done"] = True
|
||||||
|
save_settings(settings)
|
||||||
|
|
||||||
|
embed = discord.Embed(title="✅ Setup Complete", color=discord.Color.green())
|
||||||
|
embed.add_field(name="Alert Mode", value=alert_mode.name, inline=False)
|
||||||
|
if alert_channel:
|
||||||
|
embed.add_field(name="Alert Channel", value=alert_channel.mention, inline=False)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.tree.command(name="container_list", description="Lists all containers on the Docker host")
|
||||||
|
async def container_list(interaction: discord.Interaction):
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
try:
|
||||||
|
containers = get_all_containers()
|
||||||
|
embed = discord.Embed(title="🐳 Available Containers", color=discord.Color.blurple())
|
||||||
|
for c in containers:
|
||||||
|
try:
|
||||||
|
started_at = c.attrs["State"]["StartedAt"]
|
||||||
|
uptime = f"\nUptime: `{format_uptime(started_at)}`" if c.status == "running" else ""
|
||||||
|
except Exception:
|
||||||
|
uptime = ""
|
||||||
|
embed.add_field(
|
||||||
|
name=f"{status_symbol(c.status)} {c.name}",
|
||||||
|
value=f"`{c.status}`{uptime}",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
embed.set_footer(text=f"{len(containers)} containers found")
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await ctx.send(f"❌ Error: {e}")
|
await interaction.followup.send(f"❌ Error: {e}", ephemeral=True)
|
||||||
|
|
||||||
@bot.command()
|
|
||||||
async def group_create(ctx, group_name: str, *container_names):
|
@bot.tree.command(name="group_create", description="Creates a container group with a dedicated channel")
|
||||||
"""Creates a group and a channel for it
|
@app_commands.describe(
|
||||||
Example: !group_create webservices nginx apache"""
|
group_name="Name of the group",
|
||||||
|
containers="Container names separated by spaces (e.g. nginx mysql redis)"
|
||||||
|
)
|
||||||
|
async def group_create(interaction: discord.Interaction, group_name: str, containers: str):
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
|
||||||
|
container_names = containers.split()
|
||||||
if not container_names:
|
if not container_names:
|
||||||
await ctx.send("❌ Please specify at least one container!\nExample: `!group_create webservices nginx apache`")
|
await interaction.followup.send("❌ Please specify at least one container!", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
guild = bot.get_guild(GUILD_ID)
|
guild = bot.get_guild(GUILD_ID)
|
||||||
groups = load_groups()
|
groups = load_groups()
|
||||||
|
|
||||||
# Create category if it doesn't exist
|
|
||||||
category = discord.utils.get(guild.categories, name=CATEGORY_NAME)
|
category = discord.utils.get(guild.categories, name=CATEGORY_NAME)
|
||||||
if not category:
|
if not category:
|
||||||
category = await guild.create_category(CATEGORY_NAME)
|
category = await guild.create_category(CATEGORY_NAME)
|
||||||
|
|
||||||
# Create channel
|
|
||||||
channel_name = f"🐳{group_name.lower()}"
|
channel_name = f"🐳{group_name.lower()}"
|
||||||
channel = discord.utils.get(category.channels, name=channel_name)
|
channel = discord.utils.get(category.channels, name=channel_name)
|
||||||
if not channel:
|
if not channel:
|
||||||
# Only bot is allowed to write
|
|
||||||
overwrites = {
|
overwrites = {
|
||||||
guild.default_role: discord.PermissionOverwrite(send_messages=False),
|
guild.default_role: discord.PermissionOverwrite(send_messages=False),
|
||||||
guild.me: discord.PermissionOverwrite(send_messages=True)
|
guild.me: discord.PermissionOverwrite(send_messages=True)
|
||||||
}
|
}
|
||||||
channel = await category.create_text_channel(channel_name, overwrites=overwrites)
|
channel = await category.create_text_channel(channel_name, overwrites=overwrites)
|
||||||
|
|
||||||
# Save group
|
|
||||||
groups[group_name] = {
|
groups[group_name] = {
|
||||||
"channel_id": channel.id,
|
"channel_id": channel.id,
|
||||||
"containers": list(container_names),
|
"containers": container_names,
|
||||||
"message_id": None
|
"message_id": None
|
||||||
}
|
}
|
||||||
save_groups(groups)
|
save_groups(groups)
|
||||||
|
|
||||||
await ctx.send(f"✅ Group **{group_name}** created with channel {channel.mention}\nContainers: `{', '.join(container_names)}`")
|
embed = discord.Embed(title="✅ Group Created", color=discord.Color.green())
|
||||||
|
embed.add_field(name="Group", value=group_name, inline=True)
|
||||||
|
embed.add_field(name="Channel", value=channel.mention, inline=True)
|
||||||
|
embed.add_field(name="Containers", value="`" + ", ".join(container_names) + "`", inline=False)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
@bot.command()
|
|
||||||
async def group_remove(ctx, group_name: str):
|
@bot.tree.command(name="group_remove", description="Deletes a group and its channel")
|
||||||
"""Deletes a group and its channel"""
|
@app_commands.describe(group_name="Name of the group to delete")
|
||||||
|
@app_commands.autocomplete(group_name=group_autocomplete)
|
||||||
|
async def group_remove(interaction: discord.Interaction, group_name: str):
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
groups = load_groups()
|
groups = load_groups()
|
||||||
|
|
||||||
if group_name not in groups:
|
if group_name not in groups:
|
||||||
await ctx.send(f"❌ Group `{group_name}` not found.")
|
await interaction.followup.send(f"❌ Group `{group_name}` not found.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
guild = bot.get_guild(GUILD_ID)
|
guild = bot.get_guild(GUILD_ID)
|
||||||
@@ -109,103 +252,202 @@ async def group_remove(ctx, group_name: str):
|
|||||||
|
|
||||||
del groups[group_name]
|
del groups[group_name]
|
||||||
save_groups(groups)
|
save_groups(groups)
|
||||||
await ctx.send(f"✅ Group **{group_name}** deleted.")
|
|
||||||
|
|
||||||
@bot.command()
|
embed = discord.Embed(title="✅ Group Deleted", color=discord.Color.red())
|
||||||
async def group_list(ctx):
|
embed.add_field(name="Group", value=group_name)
|
||||||
"""Shows all groups"""
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.tree.command(name="group_list", description="Shows all existing groups")
|
||||||
|
async def group_list(interaction: discord.Interaction):
|
||||||
groups = load_groups()
|
groups = load_groups()
|
||||||
|
|
||||||
if not groups:
|
if not groups:
|
||||||
await ctx.send("No groups found. Use `!group_create` to create one.")
|
await interaction.response.send_message("No groups found. Use `/group_create` to create one.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
message = "**📋 Groups:**\n"
|
embed = discord.Embed(title="📋 Groups", color=discord.Color.blurple())
|
||||||
for name, data in groups.items():
|
for name, data in groups.items():
|
||||||
message += f"\n**{name}:** `{', '.join(data['containers'])}`"
|
guild = bot.get_guild(GUILD_ID)
|
||||||
await ctx.send(message)
|
channel = guild.get_channel(data["channel_id"])
|
||||||
|
embed.add_field(
|
||||||
@bot.command()
|
name=f"📁 {name}",
|
||||||
async def info(ctx):
|
value=f"Channel: {channel.mention if channel else '`deleted`'}\nContainers: `{', '.join(data['containers'])}`",
|
||||||
"""Shows all available commands and their usage"""
|
inline=False
|
||||||
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)
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
# --- Monitoring ---
|
|
||||||
|
@bot.tree.command(name="container_start", description="Start a container")
|
||||||
|
@app_commands.describe(container_name="Name of the container to start")
|
||||||
|
@app_commands.autocomplete(container_name=container_autocomplete)
|
||||||
|
async def container_start(interaction: discord.Interaction, container_name: str):
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
try:
|
||||||
|
client = get_docker_client()
|
||||||
|
container = client.containers.get(container_name)
|
||||||
|
container.start()
|
||||||
|
embed = discord.Embed(title="▶️ Container Started", color=discord.Color.green())
|
||||||
|
embed.add_field(name="Container", value=f"`{container_name}`")
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.followup.send(f"❌ Error: {e}", ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.tree.command(name="container_stop", description="Stop a container")
|
||||||
|
@app_commands.describe(container_name="Name of the container to stop")
|
||||||
|
@app_commands.autocomplete(container_name=container_autocomplete)
|
||||||
|
async def container_stop(interaction: discord.Interaction, container_name: str):
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
try:
|
||||||
|
client = get_docker_client()
|
||||||
|
container = client.containers.get(container_name)
|
||||||
|
container.stop()
|
||||||
|
embed = discord.Embed(title="⏹️ Container Stopped", color=discord.Color.red())
|
||||||
|
embed.add_field(name="Container", value=f"`{container_name}`")
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.followup.send(f"❌ Error: {e}", ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.tree.command(name="container_logs", description="Show the last 20 log lines of a container")
|
||||||
|
@app_commands.describe(container_name="Name of the container")
|
||||||
|
@app_commands.autocomplete(container_name=container_autocomplete)
|
||||||
|
async def container_logs(interaction: discord.Interaction, container_name: str):
|
||||||
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
try:
|
||||||
|
client = get_docker_client()
|
||||||
|
container = client.containers.get(container_name)
|
||||||
|
logs = container.logs(tail=20).decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
if not logs.strip():
|
||||||
|
logs = "No logs available."
|
||||||
|
|
||||||
|
if len(logs) > 1900:
|
||||||
|
logs = "..." + logs[-1900:]
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"📋 Logs: {container_name}",
|
||||||
|
description=f"```\n{logs}\n```",
|
||||||
|
color=discord.Color.blurple(),
|
||||||
|
timestamp=datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.followup.send(f"❌ Error: {e}", ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.tree.command(name="info", description="Shows all available commands")
|
||||||
|
async def info(interaction: discord.Interaction):
|
||||||
|
embed = discord.Embed(title="🤖 Docker Monitor Bot", color=discord.Color.blurple())
|
||||||
|
embed.add_field(name="/setup", value="Configure alert preferences", inline=False)
|
||||||
|
embed.add_field(name="/container_list", value="List all containers on the host", inline=False)
|
||||||
|
embed.add_field(name="/container_start <n>", value="Start a container", inline=False)
|
||||||
|
embed.add_field(name="/container_stop <n>", value="Stop a container", inline=False)
|
||||||
|
embed.add_field(name="/container_logs <n>", value="Show last 20 log lines", inline=False)
|
||||||
|
embed.add_field(name="/group_create <n> <containers>", value="Create a group with a dedicated channel", inline=False)
|
||||||
|
embed.add_field(name="/group_remove <n>", value="Delete a group and its channel", inline=False)
|
||||||
|
embed.add_field(name="/group_list", value="Show all existing groups", inline=False)
|
||||||
|
embed.add_field(name="🔄 Monitoring", value="Status + Uptime updates every **30 seconds**\n🟢 running 🔴 exited 🟡 paused ⚪ created ❓ unknown", inline=False)
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
previous_statuses = {}
|
||||||
|
|
||||||
@tasks.loop(seconds=30)
|
@tasks.loop(seconds=30)
|
||||||
async def monitor_containers():
|
async def monitor_containers():
|
||||||
|
global previous_statuses
|
||||||
|
|
||||||
groups = load_groups()
|
groups = load_groups()
|
||||||
|
settings = load_settings()
|
||||||
if not groups:
|
if not groups:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
docker_client = docker.DockerClient(base_url=DOCKER_HOST, timeout=5)
|
container_objects = get_all_containers()
|
||||||
all_containers = {c.name: c.status for c in docker_client.containers.list(all=True)}
|
all_containers = {c.name: c.status for c in container_objects}
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
guild = bot.get_guild(GUILD_ID)
|
guild = bot.get_guild(GUILD_ID)
|
||||||
|
|
||||||
|
for container_name, status in all_containers.items():
|
||||||
|
prev = previous_statuses.get(container_name)
|
||||||
|
if prev == "running" and status == "exited":
|
||||||
|
await send_crash_alert(guild, settings, container_name, groups)
|
||||||
|
|
||||||
|
previous_statuses = dict(all_containers)
|
||||||
|
|
||||||
|
running_count = sum(1 for s in all_containers.values() if s == "running")
|
||||||
|
await bot.change_presence(activity=discord.Activity(
|
||||||
|
type=discord.ActivityType.watching,
|
||||||
|
name=f"{running_count} containers"
|
||||||
|
))
|
||||||
|
|
||||||
for group_name, data in groups.items():
|
for group_name, data in groups.items():
|
||||||
channel = guild.get_channel(data["channel_id"])
|
channel = guild.get_channel(data["channel_id"])
|
||||||
if not channel:
|
if not channel:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Build status text
|
embed = build_status_embed(group_name, data["containers"], all_containers, container_objects)
|
||||||
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:
|
try:
|
||||||
if data["message_id"]:
|
if data["message_id"]:
|
||||||
# Edit existing message
|
|
||||||
message = await channel.fetch_message(data["message_id"])
|
message = await channel.fetch_message(data["message_id"])
|
||||||
await message.edit(content=text)
|
await message.edit(embed=embed)
|
||||||
else:
|
else:
|
||||||
# Send first message
|
message = await channel.send(embed=embed)
|
||||||
message = await channel.send(text)
|
|
||||||
data["message_id"] = message.id
|
data["message_id"] = message.id
|
||||||
save_groups(groups)
|
save_groups(groups)
|
||||||
except discord.NotFound:
|
except discord.NotFound:
|
||||||
# Message was deleted, resend
|
message = await channel.send(embed=embed)
|
||||||
message = await channel.send(text)
|
|
||||||
data["message_id"] = message.id
|
data["message_id"] = message.id
|
||||||
save_groups(groups)
|
save_groups(groups)
|
||||||
|
|
||||||
@bot.event
|
|
||||||
async def on_command_error(ctx, error):
|
async def send_crash_alert(guild, settings, container_name: str, groups: dict):
|
||||||
if isinstance(error, commands.CommandNotFound):
|
embed = discord.Embed(
|
||||||
return # Ignore unknown commands silently
|
title="🚨 Container Crashed!",
|
||||||
raise error # All other errors still show in logs
|
description=f"Container **{container_name}** has stopped unexpectedly.",
|
||||||
|
color=discord.Color.red(),
|
||||||
|
timestamp=datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
embed.set_footer(text="Docker Monitor Bot")
|
||||||
|
|
||||||
|
alert_mode = settings.get("alert_mode")
|
||||||
|
|
||||||
|
if alert_mode == "channel":
|
||||||
|
channel_id = settings.get("alert_channel_id")
|
||||||
|
if channel_id:
|
||||||
|
channel = guild.get_channel(channel_id)
|
||||||
|
if channel:
|
||||||
|
await channel.send(embed=embed)
|
||||||
|
|
||||||
|
elif alert_mode == "group":
|
||||||
|
for group_name, data in groups.items():
|
||||||
|
if container_name in data["containers"]:
|
||||||
|
channel = guild.get_channel(data["channel_id"])
|
||||||
|
if channel:
|
||||||
|
await channel.send(embed=embed)
|
||||||
|
else:
|
||||||
|
for data in groups.values():
|
||||||
|
channel = guild.get_channel(data["channel_id"])
|
||||||
|
if channel:
|
||||||
|
await channel.send(embed=embed)
|
||||||
|
break
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
print(f"Bot is online as {bot.user}")
|
print(f"Bot is online as {bot.user}")
|
||||||
|
try:
|
||||||
|
guild = discord.Object(id=GUILD_ID)
|
||||||
|
bot.tree.copy_global_to(guild=guild)
|
||||||
|
await bot.tree.sync(guild=guild)
|
||||||
|
print("Slash commands synced.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to sync commands: {e}")
|
||||||
|
|
||||||
|
await start_health_server()
|
||||||
monitor_containers.start()
|
monitor_containers.start()
|
||||||
|
|
||||||
bot.run(BOT_TOKEN)
|
bot.run(BOT_TOKEN)
|
||||||
Reference in New Issue
Block a user