#!/usr/bin/env bash # GOTT Remote Agent - Linux Bash Client # Verbindet sich per WebSocket zum GOTT-Server und fuehrt Befehle aus. # # Verwendung: # curl -s https://gott.example.com/remote.sh | bash # oder: # chmod +x remote.sh && ./remote.sh # oder mit eigener URL: # WS_URL="wss://example.com/api/remote" ./remote.sh WS_URL="${WS_URL:-{{WS_URL}}}" # Python finden PYTHON="" for cmd in python3 python py; do if command -v "$cmd" &>/dev/null; then PYTHON="$cmd" break fi done if [ -z "$PYTHON" ]; then echo "" echo " [FEHLER] Python nicht gefunden!" echo " Bitte installiere Python 3: sudo apt install python3" echo "" exit 1 fi # Python-Script inline ausfuehren exec "$PYTHON" - "$WS_URL" << 'PYTHON_SCRIPT' import sys import os import json import subprocess import signal import socket import ssl import hashlib import base64 import struct import time from urllib.parse import urlparse WS_URL = sys.argv[1] if len(sys.argv) > 1 else "ws://localhost/api/remote" CYAN = "\033[96m" GREEN = "\033[92m" YELLOW = "\033[93m" RED = "\033[91m" GRAY = "\033[90m" WHITE = "\033[97m" RESET = "\033[0m" def cprint(msg, color=""): print(f"{color}{msg}{RESET}", flush=True) class WebSocketClient: """Minimaler WebSocket-Client (RFC 6455) ohne externe Abhaengigkeiten.""" def __init__(self, url): self.url = url self.sock = None self.closed = False def connect(self): parsed = urlparse(self.url) use_ssl = parsed.scheme in ("wss", "https") host = parsed.hostname port = parsed.port or (443 if use_ssl else 80) path = parsed.path or "/" if parsed.query: path += "?" + parsed.query self.sock = socket.create_connection((host, port), timeout=10) if use_ssl: ctx = ssl.create_default_context() self.sock = ctx.wrap_socket(self.sock, server_hostname=host) # WebSocket-Handshake key = base64.b64encode(os.urandom(16)).decode() handshake = ( f"GET {path} HTTP/1.1\r\n" f"Host: {host}\r\n" f"Upgrade: websocket\r\n" f"Connection: Upgrade\r\n" f"Sec-WebSocket-Key: {key}\r\n" f"Sec-WebSocket-Version: 13\r\n" f"\r\n" ) self.sock.sendall(handshake.encode()) # Response lesen response = b"" while b"\r\n\r\n" not in response: chunk = self.sock.recv(4096) if not chunk: raise ConnectionError("Handshake fehlgeschlagen") response += chunk status_line = response.split(b"\r\n")[0].decode() if "101" not in status_line: raise ConnectionError(f"WebSocket-Handshake fehlgeschlagen: {status_line}") self.sock.settimeout(None) def send(self, data): if self.closed: return payload = data.encode("utf-8") frame = bytearray() frame.append(0x81) # FIN + Text length = len(payload) if length < 126: frame.append(0x80 | length) # Mask-Bit gesetzt elif length < 65536: frame.append(0x80 | 126) frame.extend(struct.pack(">H", length)) else: frame.append(0x80 | 127) frame.extend(struct.pack(">Q", length)) # Masking Key (Client muss maskieren) mask = os.urandom(4) frame.extend(mask) masked = bytearray(b ^ mask[i % 4] for i, b in enumerate(payload)) frame.extend(masked) self.sock.sendall(frame) def recv(self): if self.closed: return None def read_exact(n): data = b"" while len(data) < n: chunk = self.sock.recv(n - len(data)) if not chunk: raise ConnectionError("Verbindung geschlossen") data += chunk return data header = read_exact(2) opcode = header[0] & 0x0F is_masked = header[1] & 0x80 length = header[1] & 0x7F if opcode == 0x8: # Close self.closed = True return None if opcode == 0x9: # Ping self.send_pong() return self.recv() if length == 126: length = struct.unpack(">H", read_exact(2))[0] elif length == 127: length = struct.unpack(">Q", read_exact(8))[0] if is_masked: mask = read_exact(4) data = read_exact(length) data = bytearray(b ^ mask[i % 4] for i, b in enumerate(data)) else: data = read_exact(length) return data.decode("utf-8") def send_pong(self): if self.closed: return frame = bytearray([0x8A, 0x80]) # FIN + Pong, Mask-Bit, Length 0 frame.extend(os.urandom(4)) # Mask key self.sock.sendall(frame) def close(self): if self.closed: return self.closed = True try: # Close-Frame senden frame = bytearray([0x88, 0x82]) # FIN + Close, Mask-Bit, Length 2 mask = os.urandom(4) frame.extend(mask) # Status 1000 (Normal Closure) maskiert code = struct.pack(">H", 1000) frame.extend(bytearray(b ^ mask[i % 4] for i, b in enumerate(code))) self.sock.sendall(frame) except Exception: pass try: self.sock.close() except Exception: pass def main(): print() cprint(" ============================", CYAN) cprint(" GOTT Remote Agent", CYAN) cprint(" ============================", CYAN) print() cprint(f" Verbinde zu: {WS_URL}", GRAY) print() ws = WebSocketClient(WS_URL) running = True def on_signal(sig, frame): nonlocal running running = False signal.signal(signal.SIGINT, on_signal) signal.signal(signal.SIGTERM, on_signal) try: ws.connect() except Exception as e: cprint(f" [FEHLER] Verbindung fehlgeschlagen: {e}", RED) cprint(f" URL: {WS_URL}", GRAY) return cprint(" [OK] Verbunden!", GREEN) # Hello-Message senden hostname = os.uname().nodename if hasattr(os, "uname") else socket.gethostname() username = os.environ.get("USER", os.environ.get("LOGNAME", "unknown")) import platform os_version = f"{platform.system()} {platform.release()}" hello = json.dumps({ "type": "hello", "hostname": hostname, "username": username, "os": os_version }) ws.send(hello) cprint(f" Host: {username}@{hostname} ({os_version})", GRAY) print() cprint(" Warte auf Befehle... (Strg+C zum Beenden)", YELLOW) print() while running and not ws.closed: try: raw = ws.recv() except (ConnectionError, OSError): cprint(" Verbindung verloren.", RED) break if raw is None: cprint(" Server hat Verbindung geschlossen.", YELLOW) break try: msg = json.loads(raw) except json.JSONDecodeError: continue msg_type = msg.get("type", "") if msg_type == "execute": command = msg.get("command", "") timestamp = time.strftime("%H:%M:%S") print(f" {CYAN}[{timestamp}] Ausfuehren: {WHITE}{command}{RESET}", flush=True) exit_code = 0 stdout = "" stderr = "" try: result = subprocess.run( ["bash", "-c", command], capture_output=True, text=True, timeout=300 ) stdout = result.stdout stderr = result.stderr exit_code = result.returncode except subprocess.TimeoutExpired: stderr = "Timeout nach 300 Sekunden" exit_code = 124 except Exception as e: stderr = str(e) exit_code = 1 response = json.dumps({ "type": "result", "exitCode": exit_code, "output": stdout or "", "error": stderr or "" }) ws.send(response) # Kurzausgabe lokal if stdout: preview = stdout[:200] + "..." if len(stdout) > 200 else stdout.rstrip() cprint(f" -> {preview}", GRAY) if stderr: cprint(f" [stderr] {stderr.rstrip()}", RED) color = GREEN if exit_code == 0 else RED cprint(f" [Exit: {exit_code}]", color) print() elif msg_type == "ping": ws.send('{"type":"pong"}') elif msg_type == "pong": pass else: cprint(f" Unbekannte Nachricht: {msg_type}", YELLOW) ws.close() print() cprint(" Verbindung beendet.", YELLOW) main() PYTHON_SCRIPT