From a082c8398e7094d7644de60e7c7ddbddfc5f204f Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 16 Jul 2026 23:57:57 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Workspace-Umbau=20Schritt=206=20?= =?UTF-8?q?=E2=80=94=20QEMU=20fuer=20alle=20Architekturen=20+=20set=5Fproj?= =?UTF-8?q?ect=5Fkind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QEMU auf dem Host + ARIA-Anbindung: - host-provisioning/qemu-setup.sh: installiert qemu-system-* (x86/arm/mips/ppc/ sparc/misc), qemu-utils, Firmware, socat, imagemagick + den aria-vm-Helper. - host-provisioning/aria-vm: VM-Verwaltung fuer JEDE Architektur (create/boot/ screenshot/list/stop/rm). VNC bindet nur 127.0.0.1: (Tunnel via Bridge), KVM nur fuer x86-Gaeste, sonst TCG. - Brain: Projekt-Modell bekommt kind ('chat'|'code'); neues Tool set_project_kind markiert das aktive Projekt (blendet Editor/Desktop in der App ein) + feuert project_changed. - Seed-Regel: ARIA weiss jetzt, dass Code unter /shared/projects// gehoert und wie sie per `ssh aria-wohnung aria-vm ...` VMs baut/testet; VNC landet automatisch in der Desktop-Kachel. py-compile clean, Host-Skripte bash -n OK. Co-Authored-By: Claude Opus 4.8 --- aria-brain/agent.py | 43 ++++++++ aria-brain/projects.py | 3 +- aria-brain/seed_rules.py | 37 +++++++ host-provisioning/aria-vm | 185 ++++++++++++++++++++++++++++++++ host-provisioning/qemu-setup.sh | 53 +++++++++ 5 files changed, 320 insertions(+), 1 deletion(-) create mode 100755 host-provisioning/aria-vm create mode 100755 host-provisioning/qemu-setup.sh diff --git a/aria-brain/agent.py b/aria-brain/agent.py index d16725d..3bf519a 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -1080,6 +1080,28 @@ META_TOOLS = [ }, }, }, + { + "type": "function", + "function": { + "name": "set_project_kind", + "description": ( + "Markiert das AKTUELLE Projekt als Code-Projekt ('code') oder " + "normalen Chat ('chat'). Bei 'code' blendet die App zusaetzlich " + "einen Live-Code-Editor und den QEMU-Desktop (VNC) ein. Rufe es auf, " + "sobald aus einem Gespraech ein Programmier-/Bau-Projekt wird (Du " + "faengst an Code zu schreiben, eine VM zu bauen, etc.). Dein " + "Arbeitsverzeichnis fuer Code-Dateien ist dann /shared/projects/" + "/ — nur was Du DORT schreibst erscheint im Editor." + ), + "parameters": { + "type": "object", + "properties": { + "kind": {"type": "string", "enum": ["code", "chat"], "description": "'code' oder 'chat'."}, + }, + "required": ["kind"], + }, + }, + }, ] @@ -2576,6 +2598,27 @@ class Agent: "action": "ended", }) return f"OK — Projekt '{p['name']}' beendet (id={p['id']}). Bleibt in der Liste, aktiv ist jetzt der Hauptthread." + if name == "set_project_kind": + kind = (arguments.get("kind") or "").strip().lower() + if kind not in ("code", "chat"): + return "FEHLER: kind muss 'code' oder 'chat' sein." + active_id = projects_mod.get_active() + if not active_id: + return ("Kein aktives Projekt — Du bist im Hauptthread. Erst ein Projekt " + "anlegen/betreten (project_create/project_enter), dann set_project_kind.") + updated = projects_mod.update_project(active_id, {"kind": kind}) + if not updated: + return f"FEHLER: aktives Projekt '{active_id}' nicht gefunden." + self._pending_events.append({ + "type": "project_changed", + "project": updated, + "action": "kind_changed", + }) + if kind == "code": + return (f"OK — Projekt '{updated['name']}' ist jetzt ein Code-Projekt. " + f"Editor + Desktop erscheinen in der App. Code-Dateien unter " + f"/shared/projects/{active_id}/ schreiben, damit sie im Editor auftauchen.") + return f"OK — Projekt '{updated['name']}' ist wieder ein normaler Chat." return f"Unbekanntes Tool: {name}" except Exception as exc: logger.exception("Tool '%s' fehlgeschlagen", name) diff --git a/aria-brain/projects.py b/aria-brain/projects.py index 5d8bebe..8974dac 100644 --- a/aria-brain/projects.py +++ b/aria-brain/projects.py @@ -133,6 +133,7 @@ def create_project(name: str, description: str = "") -> dict: "description": description.strip(), "status": "active", # active | ended | archived "hidden": False, # optisch aus Listen ausblenden (bleibt nutzbar) + "kind": "chat", # chat | code — 'code' blendet Editor/VNC in der App ein "created_at": now, "updated_at": now, "last_activity_at": now, @@ -149,7 +150,7 @@ def update_project(project_id: str, patch: dict) -> Optional[dict]: projects = _load_all() for p in projects: if p["id"] == project_id: - for k in ("name", "description", "status", "hidden"): + for k in ("name", "description", "status", "hidden", "kind"): if k in patch and patch[k] is not None: p[k] = patch[k] p["updated_at"] = _now() diff --git a/aria-brain/seed_rules.py b/aria-brain/seed_rules.py index 25a33bf..b867dca 100644 --- a/aria-brain/seed_rules.py +++ b/aria-brain/seed_rules.py @@ -400,6 +400,43 @@ SEED_RULES: List[dict] = [ "Brain-Resources: erst denken, sonst Brain-Tool nehmen." ), }, + { + "migration_key": "seed/architecture/qemu-vm-code-projects", + "type": "rule", + "title": "Code-Projekte + QEMU: aria-vm auf dem Host, Editor/Desktop in der App", + "category": "architektur", + "content": ( + "Wenn aus einem Gespraech ein PROGRAMMIER- oder BAU-Projekt wird " + "(Du schreibst Code, baust ein System, testest eine VM):\n" + "\n" + "1. Ruf `set_project_kind('code')` — dann blendet Stefans App einen " + "Live-Code-Editor und den QEMU-Desktop ein. Vorher ein Projekt " + "anlegen/betreten (project_create/enter), sonst gibt's kein Ziel.\n" + "2. Schreib Code-Dateien NUR unter `/shared/projects//` " + "(das Volume ist in proxy+bridge+brain gemountet). Genau diese " + "Writes/Edits erscheinen live in Stefans Editor — und was Stefan " + "dort tippt, landet als Datei zurueck in diesem Verzeichnis.\n" + "\n" + "QEMU (VMs fuer JEDE Architektur — x86, ARM, MIPS, PPC, RISC-V, SPARC) " + "laeuft auf dem Host. Du steuerst sie per `ssh aria-wohnung aria-vm ...`:\n" + " - `aria-vm create [groesse]` Disk anlegen (z.B. i386 " + "fuer Win 3.11, aarch64, mips ...).\n" + " - `aria-vm boot [--iso ] [--vnc-display 1] [--mem 1024]` " + "startet die VM. VNC bindet an 127.0.0.1: (Display 1 = Port " + "5901). Nicht selbst nach aussen oeffnen!\n" + " - `aria-vm screenshot ` PNG in die Shared-Uploads (kannst Du " + "Stefan mit [FILE:] schicken).\n" + " - `aria-vm list` / `aria-vm stop ` / `aria-vm rm `.\n" + "\n" + "Der VNC-Stream kommt AUTOMATISCH ueber den RVS-Tunnel der Bridge in " + "Stefans App-Desktop-Kachel (Display 1 / Port 5901) — dort kann er die " + "VM live mit Maus/Tastatur bedienen. Du musst nur die VM booten und ggf. " + "sagen 'schau in die Desktop-Kachel'. KVM-Beschleunigung gibt es nur " + "fuer x86-Gaeste; andere Architekturen laufen emuliert (langsamer, aber " + "sie laufen). Medien-Downloads/Baubares fuer Stefans Privatgebrauch: " + "wie gewohnt einfach machen, nicht moralisieren." + ), + }, { "migration_key": "seed/architecture/brain-tools-xml-tag", "type": "rule", diff --git a/host-provisioning/aria-vm b/host-provisioning/aria-vm new file mode 100755 index 0000000..fbcbc39 --- /dev/null +++ b/host-provisioning/aria-vm @@ -0,0 +1,185 @@ +#!/usr/bin/env bash +# +# aria-vm — ARIAs QEMU-VM-Verwaltung fuer alle Architekturen (laeuft auf dem +# Host). ARIA ruft das per SSH (aria-wohnung) ueber den qemu-vm-Skill auf. +# +# Unterkommandos: +# aria-vm create [size] Disk anlegen (qcow2) +# aria-vm boot [optionen] VM starten (VNC 127.0.0.1:) +# aria-vm screenshot PNG-Screenshot → Shared-Uploads +# aria-vm list laufende/vorhandene VMs +# aria-vm stop VM beenden +# aria-vm rm VM + Disk loeschen +# +# boot-Optionen: +# --iso Boot-ISO (setzt Boot-Reihenfolge auf CD) +# --disk-boot von der Festplatte booten (Default nach Installation) +# --vnc-display VNC-Display (Port = 5900+N, Default 1) +# --mem RAM (Default 1024) +# --machine QEMU-Maschine ueberschreiben +# +# VNC bindet immer nur an 127.0.0.1 — von aussen erreichbar ausschliesslich +# ueber den RVS-Tunnel der Bridge (host.docker.internal:). +set -euo pipefail + +VM_ROOT="${ARIA_VM_ROOT:-/var/lib/aria-vms}" +# Wohin Screenshots geschrieben werden — Host-Pfad des /shared-Volumes, damit +# Bridge/App sie sehen. Ueberschreibbar via ARIA_VM_SHOT_DIR. +SHOT_DIR="${ARIA_VM_SHOT_DIR:-/root/ARIA-AGENT/aria-shared/uploads}" + +die() { echo "aria-vm: $*" >&2; exit 1; } + +qemu_bin_for() { + case "$1" in + x86_64|amd64) echo qemu-system-x86_64 ;; + i386|i686|x86) echo qemu-system-i386 ;; + arm|armv7) echo qemu-system-arm ;; + aarch64|arm64) echo qemu-system-aarch64 ;; + mips) echo qemu-system-mips ;; + mipsel) echo qemu-system-mipsel ;; + mips64) echo qemu-system-mips64 ;; + ppc) echo qemu-system-ppc ;; + ppc64) echo qemu-system-ppc64 ;; + riscv64) echo qemu-system-riscv64 ;; + sparc) echo qemu-system-sparc ;; + *) echo "" ;; + esac +} + +vm_dir() { echo "${VM_ROOT}/$1"; } +vm_pid() { local d; d="$(vm_dir "$1")"; [[ -f "${d}/pid" ]] && cat "${d}/pid" || echo ""; } +vm_running() { + local p; p="$(vm_pid "$1")" + [[ -n "${p}" ]] && kill -0 "${p}" 2>/dev/null +} + +cmd_create() { + local name="${1:?name}" arch="${2:?arch}" size="${3:-10G}" + local bin; bin="$(qemu_bin_for "${arch}")" + [[ -n "${bin}" ]] || die "unbekannte Architektur: ${arch}" + command -v "${bin}" >/dev/null || die "${bin} nicht installiert (qemu-setup.sh?)" + local d; d="$(vm_dir "${name}")" + [[ -e "${d}/disk.qcow2" ]] && die "VM '${name}' existiert schon" + mkdir -p "${d}" + echo "${arch}" > "${d}/arch" + qemu-img create -f qcow2 "${d}/disk.qcow2" "${size}" >/dev/null + echo "VM '${name}' angelegt (${arch}, ${size})." +} + +cmd_boot() { + local name="${1:?name}"; shift || true + local d; d="$(vm_dir "${name}")" + [[ -f "${d}/disk.qcow2" ]] || die "VM '${name}' nicht gefunden (erst 'create')" + vm_running "${name}" && die "VM '${name}' laeuft bereits" + local arch; arch="$(cat "${d}/arch" 2>/dev/null || echo x86_64)" + local bin; bin="$(qemu_bin_for "${arch}")" + + local iso="" bootdev="c" display=1 mem=1024 machine="" + while [[ $# -gt 0 ]]; do + case "$1" in + --iso) iso="${2:?}"; bootdev="d"; shift 2 ;; + --disk-boot) bootdev="c"; shift ;; + --vnc-display) display="${2:?}"; shift 2 ;; + --mem) mem="${2:?}"; shift 2 ;; + --machine) machine="${2:?}"; shift 2 ;; + *) die "unbekannte Option: $1" ;; + esac + done + + local args=(-name "${name}" -m "${mem}" + -drive "file=${d}/disk.qcow2,format=qcow2" + -vnc "127.0.0.1:${display}" + -monitor "unix:${d}/monitor.sock,server,nowait" + -pidfile "${d}/pid" -daemonize) + + # KVM nur fuer x86 auf x86-Host. + case "${arch}" in + x86_64|amd64|i386|i686|x86) + [[ -e /dev/kvm ]] && args+=(-enable-kvm) ;; + esac + # ARM/AArch64 brauchen eine Maschine (kein Default). + if [[ -z "${machine}" ]]; then + case "${arch}" in + arm|armv7|aarch64|arm64) machine="virt" ;; + esac + fi + [[ -n "${machine}" ]] && args+=(-M "${machine}") + [[ -n "${iso}" ]] && args+=(-cdrom "${iso}") + args+=(-boot "${bootdev}") + + "${bin}" "${args[@]}" + echo "VM '${name}' gestartet (${arch}) — VNC 127.0.0.1:${display} (Port $((5900+display)))." + echo "vnc_display=${display} vnc_port=$((5900+display))" +} + +cmd_screenshot() { + local name="${1:?name}" + local d; d="$(vm_dir "${name}")" + vm_running "${name}" || die "VM '${name}' laeuft nicht" + command -v socat >/dev/null || die "socat fehlt (qemu-setup.sh?)" + mkdir -p "${SHOT_DIR}" + local ts; ts="$(date +%s)" + local ppm="${d}/shot-${ts}.ppm" + printf 'screendump %s\n' "${ppm}" | socat - "unix-connect:${d}/monitor.sock" >/dev/null + sleep 0.3 + local out="${SHOT_DIR}/${name}-${ts}.png" + if command -v convert >/dev/null; then + convert "${ppm}" "${out}" && rm -f "${ppm}" + else + out="${SHOT_DIR}/${name}-${ts}.ppm"; mv "${ppm}" "${out}" + fi + echo "screenshot=${out}" +} + +cmd_list() { + [[ -d "${VM_ROOT}" ]] || { echo "(keine VMs)"; return; } + local any=0 + for d in "${VM_ROOT}"/*/; do + [[ -d "${d}" ]] || continue + any=1 + local name arch state + name="$(basename "${d}")" + arch="$(cat "${d}/arch" 2>/dev/null || echo '?')" + if vm_running "${name}"; then state="laeuft (pid $(vm_pid "${name}"))"; else state="gestoppt"; fi + echo "${name} [${arch}] ${state}" + done + [[ "${any}" -eq 1 ]] || echo "(keine VMs)" +} + +cmd_stop() { + local name="${1:?name}" + local d; d="$(vm_dir "${name}")" + if vm_running "${name}"; then + printf 'quit\n' | socat - "unix-connect:${d}/monitor.sock" >/dev/null 2>&1 || true + sleep 0.5 + vm_running "${name}" && kill "$(vm_pid "${name}")" 2>/dev/null || true + echo "VM '${name}' gestoppt." + else + echo "VM '${name}' lief nicht." + fi + rm -f "${d}/pid" "${d}/monitor.sock" +} + +cmd_rm() { + local name="${1:?name}" + vm_running "${name}" && cmd_stop "${name}" + rm -rf "$(vm_dir "${name}")" + echo "VM '${name}' geloescht." +} + +main() { + local sub="${1:-}"; shift || true + case "${sub}" in + create) cmd_create "$@" ;; + boot) cmd_boot "$@" ;; + screenshot) cmd_screenshot "$@" ;; + list) cmd_list "$@" ;; + stop) cmd_stop "$@" ;; + rm) cmd_rm "$@" ;; + ""|-h|--help) + sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//' ;; + *) die "unbekanntes Kommando: ${sub} (siehe --help)" ;; + esac +} + +main "$@" diff --git a/host-provisioning/qemu-setup.sh b/host-provisioning/qemu-setup.sh new file mode 100755 index 0000000..8d1a186 --- /dev/null +++ b/host-provisioning/qemu-setup.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# +# qemu-setup.sh — installiert QEMU fuer ALLE Architekturen auf dem ARIA-Host +# (172.0.2.33) plus den aria-vm-Helper. Einmalig als root ausfuehren. +# +# sudo bash host-provisioning/qemu-setup.sh +# +# KVM-Beschleunigung gibt es nur fuer x86-Gaeste auf einem x86-Host; ARM/MIPS/ +# PPC/RISC-V laufen unter TCG (voll emuliert, langsamer, aber alle Architekturen +# baubar). websockify/noVNC werden NICHT installiert — der VNC-Stream wird als +# RFB-Bytes durch die Bridge/RVS getunnelt (siehe aria_bridge.py VNC-Bruecke). +set -euo pipefail + +if [[ "${EUID}" -ne 0 ]]; then + echo "Bitte als root ausfuehren (sudo)." >&2 + exit 1 +fi + +echo "[qemu-setup] apt update ..." +apt-get update -qq + +echo "[qemu-setup] Installiere QEMU (alle Architekturen) + Werkzeuge ..." +DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + qemu-system \ + qemu-system-x86 \ + qemu-system-arm \ + qemu-system-mips \ + qemu-system-ppc \ + qemu-system-sparc \ + qemu-system-misc \ + qemu-utils \ + seabios \ + ovmf \ + ipxe-qemu \ + socat \ + imagemagick + +echo "[qemu-setup] KVM-Status:" +if [[ -e /dev/kvm ]]; then + echo " /dev/kvm vorhanden → x86-Gaeste mit KVM-Beschleunigung." +else + echo " /dev/kvm FEHLT → alle Gaeste laufen unter TCG (emuliert, langsamer)." +fi + +# aria-vm-Helper installieren (liegt neben diesem Skript). +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +install -m 0755 "${SCRIPT_DIR}/aria-vm" /usr/local/bin/aria-vm +echo "[qemu-setup] /usr/local/bin/aria-vm installiert." + +mkdir -p /var/lib/aria-vms +echo "[qemu-setup] VM-Verzeichnis: /var/lib/aria-vms" + +echo "[qemu-setup] Fertig. Test: aria-vm list"