58 lines
4.0 KiB
Python
58 lines
4.0 KiB
Python
import json, os, ssl, sys
|
|
from urllib.parse import urlencode
|
|
from urllib.request import Request, urlopen
|
|
from urllib.error import HTTPError, URLError
|
|
PROTO='jarvis.skill.invoke.v1'
|
|
def truthy(v): return str(v or '').strip().lower() in ('1','true','yes','on')
|
|
def fail(c,m): return {'protocol':PROTO,'success':False,'error':{'code':c,'message':m}}
|
|
def ok(d,m='OK',mut=False): return {'protocol':PROTO,'success':True,'data':d,'message':m,'mutated':mut}
|
|
def cfg():
|
|
b=os.getenv('PROXMOX_BASE_URL','').strip().rstrip('/'); tid=os.getenv('PROXMOX_TOKEN_ID','').strip(); sec=os.getenv('PROXMOX_TOKEN_SECRET','').strip()
|
|
if not b or not tid or not sec: raise RuntimeError('PROXMOX_BASE_URL, PROXMOX_TOKEN_ID und PROXMOX_TOKEN_SECRET müssen gesetzt sein')
|
|
if not b.startswith(('http://','https://')): b='https://'+b
|
|
return b,tid,sec,truthy(os.getenv('PROXMOX_VERIFY_TLS','false'))
|
|
def request(method,path,form=None,query=None):
|
|
b,tid,sec,verify=cfg(); url=b+'/api2/json'+path
|
|
if query: url+='?'+urlencode({k:v for k,v in query.items() if v not in (None,'')})
|
|
data=None; h={'Accept':'application/json','Authorization':f'PVEAPIToken={tid}={sec}'}
|
|
if form is not None: data=urlencode(form).encode(); h['Content-Type']='application/x-www-form-urlencoded'
|
|
elif method=='POST': data=b''
|
|
ctx=ssl._create_unverified_context() if url.startswith('https://') and not verify else None
|
|
try:
|
|
with urlopen(Request(url,data=data,headers=h,method=method),timeout=12,context=ctx) as r:
|
|
raw=r.read(); obj=json.loads(raw.decode()) if raw else {}; return obj.get('data') if isinstance(obj,dict) and 'data' in obj else obj
|
|
except HTTPError as e: raise RuntimeError(f'Proxmox HTTP {e.code}: {e.read().decode(errors="replace")[:1000]}')
|
|
except URLError as e: raise RuntimeError(f'Proxmox nicht erreichbar: {e.reason}')
|
|
def guest_path(x):
|
|
typ=x['guest_type'];
|
|
if typ not in ('qemu','lxc'): raise RuntimeError('guest_type muss qemu oder lxc sein')
|
|
return f"/nodes/{x['node']}/{typ}/{int(x['vmid'])}"
|
|
def main(inv):
|
|
a=inv.get('action'); x=inv.get('input') or {}
|
|
if a=='version': return ok(request('GET','/version'),'Proxmox API erreichbar.')
|
|
if a=='list_nodes':
|
|
vals=request('GET','/nodes') or []; out=[{'node':v.get('node',''),'status':v.get('status',''),'cpu':v.get('cpu'),'maxcpu':v.get('maxcpu'),'mem':v.get('mem'),'maxmem':v.get('maxmem'),'uptime':v.get('uptime')} for v in vals]
|
|
return ok({'nodes':out,'count':len(out)},'Proxmox-Nodes geladen.')
|
|
if a=='list_guests':
|
|
vals=request('GET','/cluster/resources',query={'type':'vm'}) or []; out=[]
|
|
for v in vals:
|
|
typ=v.get('type','')
|
|
if typ not in ('qemu','lxc'): continue
|
|
if x.get('node') and v.get('node')!=x['node']: continue
|
|
if x.get('status') and v.get('status')!=x['status']: continue
|
|
out.append({'vmid':v.get('vmid'),'name':v.get('name',''),'type':typ,'node':v.get('node',''),'status':v.get('status',''),'cpu':v.get('cpu'),'mem':v.get('mem'),'maxmem':v.get('maxmem'),'uptime':v.get('uptime')})
|
|
return ok({'guests':out,'count':len(out)},'Proxmox-Guests geladen.')
|
|
if a=='guest_status': return ok(request('GET',guest_path(x)+'/status/current') or {},'Guest-Status geladen.')
|
|
if a in ('start_guest','shutdown_guest','reboot_guest'):
|
|
action={'start_guest':'start','shutdown_guest':'shutdown','reboot_guest':'reboot'}[a]; data=request('POST',guest_path(x)+'/status/'+action)
|
|
return ok({'upid':data or '', 'action':action},f'Guest-Aktion {action} ausgelöst.',True)
|
|
if a=='snapshot_guest':
|
|
form={'snapname':x['name']}
|
|
if x.get('description'): form['description']=x['description']
|
|
if x.get('include_ram'): form['vmstate']='1'
|
|
data=request('POST',guest_path(x)+'/snapshot',form=form); return ok({'upid':data or '', 'snapshot':x['name']},'Snapshot angelegt.',True)
|
|
return fail('UNKNOWN_ACTION',f'Unbekannte Aktion: {a}')
|
|
try: res=main(json.load(sys.stdin))
|
|
except Exception as e: res=fail('PROXMOX_ERROR',str(e))
|
|
json.dump(res,sys.stdout,ensure_ascii=False)
|