From f862f46e160f2edebca3b43766d1424c3c7a99ca Mon Sep 17 00:00:00 2001 From: Pengu Date: Sat, 20 Sep 2025 14:21:26 -0500 Subject: [PATCH] Proxmox Integration --- .env | 7 +++ CHANGELOG.md | 1 + README.md | 3 ++ app.py | 117 ++++++++++++++++++++++++++++++++++++++++++++++- requirements.txt | 3 +- 5 files changed, 129 insertions(+), 2 deletions(-) diff --git a/.env b/.env index ee94a85..f54610b 100644 --- a/.env +++ b/.env @@ -28,6 +28,13 @@ QBIT_HOST=http://localhost:8080 QBIT_USERNAME=your_username QBIT_PASSWORD=your_password +# Proxmox +ENABLE_PROXMOX=false +PROXMOX_HOST=https://your-proxmox-server:8006 +PROXMOX_TOKEN_NAME=root@pam!yourtokenname +PROXMOX_TOKEN_VALUE=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +PROXMOX_VERIFY_SSL=false + # MySQL DB_HOST=localhost DB_USER=root diff --git a/CHANGELOG.md b/CHANGELOG.md index 470ae22..1752b7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 1.0.7 - Fixed JFA-GO API keys expiring. The bot now schedules a key refresh +- Added Proxmox support for checking storage pool size # 1.0.6 diff --git a/README.md b/README.md index db0bce3..53ed892 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,9 @@ Fill out values in the .env and you're good to go! ***💾 qBittorrent Commands*** - `!qbview` - View current qBittorrent downloads +***🗳️ Proxmox Commands*** +- `!storage` - Show available storage pools and free space + ***🔑 JFA Commands*** - `!createinvite` - Create a new JFA invite link diff --git a/app.py b/app.py index 53f9e9a..b1bc464 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,7 @@ from dotenv import load_dotenv import pytz import random import qbittorrentapi +from proxmoxer import ProxmoxAPI # ===================== # ENV + VALIDATION @@ -49,6 +50,12 @@ QBIT_HOST = os.getenv("QBIT_HOST") QBIT_USERNAME = os.getenv("QBIT_USERNAME") QBIT_PASSWORD = os.getenv("QBIT_PASSWORD") +ENABLE_PROXMOX = os.getenv("ENABLE_PROXMOX", "False").lower() == "true" +PROXMOX_HOST = os.getenv("PROXMOX_HOST") +PROXMOX_TOKEN_NAME = os.getenv("PROXMOX_TOKEN_NAME") +PROXMOX_TOKEN_VALUE = os.getenv("PROXMOX_TOKEN_VALUE") +PROXMOX_VERIFY_SSL = os.getenv("PROXMOX_VERIFY_SSL", "False").lower() == "true" + DB_HOST = get_env_var("DB_HOST") DB_USER = get_env_var("DB_USER") DB_PASSWORD = get_env_var("DB_PASSWORD") @@ -358,6 +365,42 @@ def progress_bar(progress: float, length: int = 20) -> str: bar = '█' * filled_length + '░' * (length - filled_length) return f"[{bar}] {progress*100:.2f}%" +# ===================== +# PROXMOX HELPERS +# ===================== + +def get_proxmox_client(): + """Create and return a Proxmox client using API token auth.""" + if not (PROXMOX_HOST and PROXMOX_TOKEN_NAME and PROXMOX_TOKEN_VALUE): + raise ValueError("Proxmox API credentials are not fully configured in .env") + + # Parse host + port safely + base_host = PROXMOX_HOST.replace("https://", "").replace("http://", "") + if ":" in base_host: + host, port = base_host.split(":") + else: + host, port = base_host, 8006 # default Proxmox port + + # Split token into user + token name + try: + user, token_name = PROXMOX_TOKEN_NAME.split("!") + except ValueError: + raise ValueError( + "❌ PROXMOX_TOKEN_NAME must be in the format 'user@realm!tokenid'" + ) + + log_event(f"[Proxmox] Connecting to {host}:{port} as {user} with token '{token_name}'") + + return ProxmoxAPI( + host, + port=int(port), + user=user, + token_name=token_name, + token_value=PROXMOX_TOKEN_VALUE, + verify_ssl=PROXMOX_VERIFY_SSL + ) + + # ===================== # JFA-GO HELPERS # ===================== @@ -1343,6 +1386,71 @@ async def qbview(ctx): await ctx.send(embed=embed) +@bot.command() +async def storage(ctx): + """Check Proxmox storage pools and ZFS pools.""" + if not ENABLE_PROXMOX: + await ctx.send("⚠️ Proxmox integration is disabled in the configuration.") + return + + if not has_admin_role(ctx.author): + await ctx.send("❌ You don’t have permission to use this command.") + return + + try: + proxmox = get_proxmox_client() + embed = discord.Embed( + title="📦 Proxmox Storage", + description="Storage pool usage and ZFS pools", + color=discord.Color.green() + ) + + for node in proxmox.nodes.get(): + node_name = node["node"] + + # ---- ZFS ---- + try: + zfs_pools = proxmox.nodes(node_name).disks.zfs.get() + if zfs_pools: + zfs_info = [ + f"**{p['name']}**: {p['alloc']/1024**3:.2f} GiB / " + f"{p['size']/1024**3:.2f} GiB ({(p['alloc']/p['size']*100):.1f}%)" + for p in zfs_pools + ] + else: + zfs_info = ["No ZFS pools found"] + except Exception as e: + zfs_info = [f"⚠️ Failed to fetch ZFS pools ({e})"] + + embed.add_field( + name=f"🖥️ {node_name} - ZFS Pools", + value="\n".join(zfs_info), + inline=False + ) + + # ---- Normal storage (skip ZFS) ---- + try: + storage_info = proxmox.nodes(node_name).storage.get() + normal_lines = [ + f"**{s['storage']}**: {s['used']/1024**3:.2f} GiB / " + f"{s['total']/1024**3:.2f} GiB ({(s['used']/s['total']*100):.1f}%)" + for s in storage_info + if s.get("type", "").lower() not in ("zfspool", "zfs") + ] + except Exception as e: + normal_lines = [f"⚠️ Failed to fetch normal storage ({e})"] + + embed.add_field( + name=f"🗳️ {node_name} - Normal Storage", + value="\n".join(normal_lines) or "No non‑ZFS storage found", + inline=False + ) + + await ctx.send(embed=embed) + except Exception as e: + await ctx.send(f"⚠️ Unexpected error: {e}") + + @bot.command() async def link(ctx, jellyfin_username: str = None, user: discord.User = None, js_id: str = None): log_event(f"link invoked by {ctx.author}") @@ -1522,6 +1630,13 @@ async def help_command(ctx): f"`{PREFIX}qbview` - Show current qBittorrent downloads with progress, peers, and seeders", ] embed.add_field(name="💾 qBittorrent Commands", value="\n".join(qb_cmds), inline=False) + + # --- Proxmox Commands --- + if ENABLE_PROXMOX: + qb_cmds = [ + f"`{PREFIX}storage` - Show available storage pools and free space", + ] + embed.add_field(name="🗳️ Proxmox Commands", value="\n".join(qb_cmds), inline=False) # --- JFA Commands --- if ENABLE_JFA: @@ -1686,7 +1801,7 @@ async def cleanup_task(): # ===================== if ENABLE_JFA: - @tasks.loop(hours=18) + @tasks.loop(hours=1) async def refresh_jfa_loop(): success = refresh_jfa_token() if success: diff --git a/requirements.txt b/requirements.txt index ac7b321..4901208 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ mysql-connector-python==9.0.0 python-dotenv==1.0.1 pytz==2025.2 apscheduler==3.11.0 -qbittorrent-api==2025.7.0 \ No newline at end of file +qbittorrent-api==2025.7.0 +proxmoxer==2.2.0 \ No newline at end of file