Proxmox Integration

This commit is contained in:
2025-09-20 14:21:26 -05:00
parent c4a1ccf770
commit f862f46e16
5 changed files with 129 additions and 2 deletions

117
app.py
View File

@@ -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 dont 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 nonZFS 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: