aboutsummaryrefslogtreecommitdiff
path: root/cogs/vm_management.py
diff options
context:
space:
mode:
Diffstat (limited to 'cogs/vm_management.py')
-rw-r--r--cogs/vm_management.py185
1 files changed, 185 insertions, 0 deletions
diff --git a/cogs/vm_management.py b/cogs/vm_management.py
new file mode 100644
index 0000000..fd76eaa
--- /dev/null
+++ b/cogs/vm_management.py
@@ -0,0 +1,185 @@
+import discord
+from discord import app_commands
+from discord.ext import commands
+from typing import Literal
+import asyncio
+from proxmoxer import ProxmoxAPI
+import config
+
+
+class VMManagement(commands.Cog):
+ """VM and LXC container management commands."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ def get_proxmox(self) -> ProxmoxAPI:
+ """Get a Proxmox API connection."""
+ return ProxmoxAPI(
+ config.PROXMOX_HOST,
+ user=config.PROXMOX_USER,
+ token_name=config.PROXMOX_TOKEN_NAME,
+ token_value=config.PROXMOX_TOKEN_VALUE,
+ verify_ssl=config.PROXMOX_VERIFY_SSL,
+ )
+
+ async def _check_owner(self, interaction: discord.Interaction) -> bool:
+ """Check if user is owner, send error if not."""
+ if interaction.user.id != config.OWNER_ID:
+ await interaction.response.send_message(
+ "You don't have permission to use this command.", ephemeral=True
+ )
+ return False
+ return True
+
+ def _get_vm_type(self, proxmox: ProxmoxAPI, vmid: int) -> str | None:
+ """Determine if VMID is a QEMU VM or LXC container."""
+ # Check QEMU VMs
+ try:
+ vms = proxmox.nodes(config.PROXMOX_NODE).qemu.get()
+ for vm in vms:
+ if vm.get("vmid") == vmid:
+ return "qemu"
+ except Exception:
+ pass
+
+ # Check LXC containers
+ try:
+ lxcs = proxmox.nodes(config.PROXMOX_NODE).lxc.get()
+ for lxc in lxcs:
+ if lxc.get("vmid") == vmid:
+ return "lxc"
+ except Exception:
+ pass
+
+ return None
+
+ def _get_vm_name(self, proxmox: ProxmoxAPI, vmid: int, vm_type: str) -> str:
+ """Get the name of a VM or container."""
+ try:
+ if vm_type == "qemu":
+ config_data = proxmox.nodes(config.PROXMOX_NODE).qemu(vmid).config.get()
+ else:
+ config_data = proxmox.nodes(config.PROXMOX_NODE).lxc(vmid).config.get()
+ return config_data.get("hostname", config_data.get("name", f"VM {vmid}"))
+ except Exception:
+ return f"VM {vmid}"
+
+ vm_group = app_commands.Group(
+ name="vm",
+ description="Manage VMs and containers (owner only)",
+ allowed_contexts=app_commands.AppCommandContext(guild=True, dm_channel=True, private_channel=True),
+ allowed_installs=app_commands.AppInstallationType(guild=True, user=True),
+ )
+
+ @vm_group.command(name="start", description="Start a VM or container")
+ @app_commands.describe(vmid="The VMID to start")
+ async def start_vm(self, interaction: discord.Interaction, vmid: int):
+ if not await self._check_owner(interaction):
+ return
+
+ await interaction.response.defer(ephemeral=True)
+
+ try:
+ proxmox = self.get_proxmox()
+ vm_type = self._get_vm_type(proxmox, vmid)
+
+ if not vm_type:
+ await interaction.followup.send(f"VM/Container {vmid} not found.", ephemeral=True)
+ return
+
+ vm_name = self._get_vm_name(proxmox, vmid, vm_type)
+
+ # Start the VM/container
+ if vm_type == "qemu":
+ await asyncio.to_thread(
+ lambda: proxmox.nodes(config.PROXMOX_NODE).qemu(vmid).status.start.post()
+ )
+ else:
+ await asyncio.to_thread(
+ lambda: proxmox.nodes(config.PROXMOX_NODE).lxc(vmid).status.start.post()
+ )
+
+ type_name = "VM" if vm_type == "qemu" else "Container"
+ await interaction.followup.send(
+ f":green_circle: {type_name} **{vmid}** ({vm_name}) is starting...", ephemeral=True
+ )
+
+ except Exception as e:
+ await interaction.followup.send(f"Failed to start VM {vmid}: {e}", ephemeral=True)
+
+ @vm_group.command(name="stop", description="Stop a VM or container")
+ @app_commands.describe(vmid="The VMID to stop")
+ async def stop_vm(self, interaction: discord.Interaction, vmid: int):
+ if not await self._check_owner(interaction):
+ return
+
+ await interaction.response.defer(ephemeral=True)
+
+ try:
+ proxmox = self.get_proxmox()
+ vm_type = self._get_vm_type(proxmox, vmid)
+
+ if not vm_type:
+ await interaction.followup.send(f"VM/Container {vmid} not found.", ephemeral=True)
+ return
+
+ vm_name = self._get_vm_name(proxmox, vmid, vm_type)
+
+ # Stop the VM/container
+ if vm_type == "qemu":
+ await asyncio.to_thread(
+ lambda: proxmox.nodes(config.PROXMOX_NODE).qemu(vmid).status.stop.post()
+ )
+ else:
+ await asyncio.to_thread(
+ lambda: proxmox.nodes(config.PROXMOX_NODE).lxc(vmid).status.stop.post()
+ )
+
+ type_name = "VM" if vm_type == "qemu" else "Container"
+ await interaction.followup.send(
+ f":red_circle: {type_name} **{vmid}** ({vm_name}) is stopping...", ephemeral=True
+ )
+
+ except Exception as e:
+ await interaction.followup.send(f"Failed to stop VM {vmid}: {e}", ephemeral=True)
+
+ @vm_group.command(name="restart", description="Restart a VM or container")
+ @app_commands.describe(vmid="The VMID to restart")
+ async def restart_vm(self, interaction: discord.Interaction, vmid: int):
+ if not await self._check_owner(interaction):
+ return
+
+ await interaction.response.defer(ephemeral=True)
+
+ try:
+ proxmox = self.get_proxmox()
+ vm_type = self._get_vm_type(proxmox, vmid)
+
+ if not vm_type:
+ await interaction.followup.send(f"VM/Container {vmid} not found.", ephemeral=True)
+ return
+
+ vm_name = self._get_vm_name(proxmox, vmid, vm_type)
+
+ # Reboot the VM/container
+ if vm_type == "qemu":
+ await asyncio.to_thread(
+ lambda: proxmox.nodes(config.PROXMOX_NODE).qemu(vmid).status.reboot.post()
+ )
+ else:
+ await asyncio.to_thread(
+ lambda: proxmox.nodes(config.PROXMOX_NODE).lxc(vmid).status.reboot.post()
+ )
+
+ type_name = "VM" if vm_type == "qemu" else "Container"
+ await interaction.followup.send(
+ f":arrows_counterclockwise: {type_name} **{vmid}** ({vm_name}) is restarting...", ephemeral=True
+ )
+
+ except Exception as e:
+ await interaction.followup.send(f"Failed to restart VM {vmid}: {e}", ephemeral=True)
+
+
+async def setup(bot: commands.Bot):
+ await bot.add_cog(VMManagement(bot))