Update auf Skills
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import json, os, re, subprocess, sys
|
||||
from pathlib import Path
|
||||
PROTO='jarvis.skill.invoke.v1'
|
||||
NAMES=('compose.yaml','compose.yml','docker-compose.yaml','docker-compose.yml')
|
||||
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 root():
|
||||
p=Path(os.getenv('DOCKGE_STACKS_DIR','/opt/stacks')).resolve()
|
||||
if not p.exists() or not p.is_dir(): raise RuntimeError(f'Dockge-Stacks-Verzeichnis fehlt: {p}')
|
||||
return p
|
||||
def stack_dir(name):
|
||||
if not re.fullmatch(r'[A-Za-z0-9_.-]+',name or ''): raise RuntimeError('Ungültiger Stack-Name')
|
||||
p=(root()/name).resolve(); r=root()
|
||||
if p.parent!=r or not p.is_dir(): raise RuntimeError('Stack nicht gefunden')
|
||||
if not any((p/n).is_file() for n in NAMES): raise RuntimeError('Keine Compose-Datei im Stack gefunden')
|
||||
return p
|
||||
def allowed(name):
|
||||
vals=[x.strip() for x in os.getenv('DOCKGE_ALLOWED_STACKS','').split(',') if x.strip()]
|
||||
return '*' in vals or name in vals
|
||||
def require_allowed(name):
|
||||
if not allowed(name): raise RuntimeError('Mutation verweigert: Stack ist nicht in DOCKGE_ALLOWED_STACKS freigegeben')
|
||||
def compose_cmd():
|
||||
# Prefer modern plugin; fall back to legacy docker-compose.
|
||||
try:
|
||||
subprocess.run(['docker','compose','version'],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,check=True,timeout=5)
|
||||
return ['docker','compose']
|
||||
except Exception:
|
||||
return ['docker-compose']
|
||||
def run(name,args,timeout=90):
|
||||
p=stack_dir(name); cmd=compose_cmd()+args
|
||||
cp=subprocess.run(cmd,cwd=p,text=True,capture_output=True,timeout=timeout)
|
||||
if cp.returncode!=0: raise RuntimeError((cp.stderr or cp.stdout or f'Exit {cp.returncode}')[-1800:])
|
||||
return (cp.stdout or '').strip()
|
||||
def main(inv):
|
||||
a=inv.get('action'); x=inv.get('input') or {}
|
||||
if a=='list_stacks':
|
||||
out=[]
|
||||
for p in sorted(root().iterdir()):
|
||||
if not p.is_dir(): continue
|
||||
cf=next((n for n in NAMES if (p/n).is_file()),None)
|
||||
if cf: out.append({'name':p.name,'compose_file':cf,'mutation_allowed':allowed(p.name)})
|
||||
return ok({'stacks':out,'count':len(out)},'Dockge-Stacks geladen.')
|
||||
name=x['stack']
|
||||
if a=='stack_status':
|
||||
raw=run(name,['ps','--format','json'],30); items=[]
|
||||
for line in raw.splitlines():
|
||||
try:
|
||||
v=json.loads(line)
|
||||
items.extend(v if isinstance(v,list) else [v])
|
||||
except Exception: pass
|
||||
return ok({'stack':name,'services':items,'count':len(items)},'Stack-Status geladen.')
|
||||
require_allowed(name)
|
||||
if a=='start_stack': run(name,['up','-d','--remove-orphans']); return ok({'stack':name,'action':'start'},'Stack gestartet.',True)
|
||||
if a=='stop_stack': run(name,['stop']); return ok({'stack':name,'action':'stop'},'Stack gestoppt.',True)
|
||||
if a=='restart_stack': run(name,['restart']); return ok({'stack':name,'action':'restart'},'Stack neu gestartet.',True)
|
||||
if a=='update_stack':
|
||||
run(name,['pull'],120); run(name,['up','-d','--remove-orphans'],120); return ok({'stack':name,'action':'update'},'Stack aktualisiert.',True)
|
||||
return fail('UNKNOWN_ACTION',f'Unbekannte Aktion: {a}')
|
||||
try: res=main(json.load(sys.stdin))
|
||||
except subprocess.TimeoutExpired: res=fail('DOCKGE_TIMEOUT','Docker-Compose-Aktion hat das Zeitlimit überschritten')
|
||||
except Exception as e: res=fail('DOCKGE_ERROR',str(e))
|
||||
json.dump(res,sys.stdout,ensure_ascii=False)
|
||||
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"protocol": "jarvis.skill.v1",
|
||||
"id": "dockge.compose",
|
||||
"name": "Dockge Stack Control",
|
||||
"version": "1.0.0",
|
||||
"description": "Steuert Dockge-verwaltete Compose-Stacks direkt über deren Stack-Verzeichnis und Docker Compose. Nutzt bewusst nicht Dockges instabile interne Socket-API.",
|
||||
"runtime": {
|
||||
"type": "process",
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"main.py"
|
||||
],
|
||||
"timeout_ms": 120000,
|
||||
"env_from": [
|
||||
"DOCKGE_STACKS_DIR",
|
||||
"DOCKGE_ALLOWED_STACKS"
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"system_exec": true
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"name": "list_stacks",
|
||||
"description": "Listet Dockge-Stack-Verzeichnisse und deren Compose-Dateien.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stacks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"stacks",
|
||||
"count"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stack_status",
|
||||
"description": "Liest den Docker-Compose-Status eines Dockge-Stacks.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stack": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"stack"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "start_stack",
|
||||
"description": "Startet/deployed einen erlaubten Dockge-Stack mit docker compose up -d.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stack": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"stack"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stop_stack",
|
||||
"description": "Stoppt einen erlaubten Dockge-Stack.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stack": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"stack"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "restart_stack",
|
||||
"description": "Startet einen erlaubten Dockge-Stack neu.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stack": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"stack"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_stack",
|
||||
"description": "Pullt Images eines erlaubten Dockge-Stacks und deployed ihn anschließend neu.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stack": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"stack"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user