fix(testkit): Remote-Testen, das wirklich traegt - vier Fehler und ein Weg
Die Testwege waren beschrieben, aber nicht begehbar. Auf der Test-VM lag die Anleitung vom 26.08. ohne das gesamte Testkit-Kapitel, ein Laufzeit-Token ohne Rechte, und ein Testkit, das 236 Zeilen aelter war als das ausgelieferte - beide meldeten 1.0.0. Deshalb sagte "doctor" dort "Bereit", waehrend nichts davon lauffaehig war. Vier Fehler, die beim Nachpruefen herauskamen: * doctor brach unter "set -e" wortlos ab. Der Rueckgabewert von version_lt wurde nicht aufgefangen - das beendet die Sitzung, sobald der Server neu genug ist (Rueckgabewert 1, also der Normalfall). Token und Projekt wurden nie geprueft. * Die Konfigurationsdatei ueberschrieb die Umgebung, genau umgekehrt zur dokumentierten Rangfolge. "DC_PROJECT=x dc-testkit report" meldete stillschweigend in das Projekt aus der Datei. * Der doctor-Bericht ging teils auf stderr. Ueber SSH werden beide Kanaele getrennt uebertragen und beim Aufrufer verschraenkt - die Befunde standen unter der falschen Ueberschrift. Genau so liest ein Pruefagent den Bericht. * "~/bin" liegt ueber SSH nicht im PATH: .profile wird nur von einer Login-Shell gelesen. Die Antwort war "Kommando nicht gefunden" - das sieht aus wie ein fehlgeschlagener Bezug und ist keiner. Neu im Testkit (1.1.0): * selfupdate mit --pruefen. Unterscheidet die Richtung: Eine neuere lokale Fassung ist ein Entwicklungsstand und kein Grund zum Update; der Rueckschritt wird verweigert (--force hebt es auf). Gleiche Nummer bei anderem Inhalt ist ein Befund, kein Achselzucken - genau daran ist es unbemerkt vorbeigelaufen. * doctor prueft den eigenen Stand, die Herkunft des Tokens, die fehlende Projektbindung, die Oberflaechenwerkzeuge und das eigene Display. * display start|stop|status und shot: der generische Teil des Verfahrens aus 5.8. Das sind die drei Stellen, an denen Eigenbau-Skripte danebengreifen - :0 statt eigenem Display, pkill ohne Nummer, Dateinamen ohne Projektpraefix. * DC_PROJECT_DIR: Das Werkbank-Token kommt aus der .mcp.json des Projekt-Repos, wo es fuer den MCP-Adapter ohnehin liegt. Kein zweiter Ablageort, der beim naechsten Wechsel vergessen wird. Fehlt die Datei, sagt das Testkit warum - sie ist gitignored und kommt ueber Git nie mit. dc-testkit-remote (neu): Der Agent laeuft auf dem Arbeitsplatz, der Testlauf auf der VM. Der Wrapper haelt das Token aus "ps aux" der geteilten Maschine (es geht ueber die Standardeingabe in die Umgebung), ruft bash -l, schuetzt die Argumente vor drei Ebenen Quoting und buendelt ganze Pruefplaene in eine Verbindung. Auf der VM wird keine Konfiguration mehr gelesen - was dort liegt, bleibt wirkungslos. Doku: TESTUMGEBUNG.md auf den Remote-Betrieb umgestellt, 4.2 auf den realen Ablageort der Token, neuer 4.8 "Ein Agent, der nur prueft", Belegung in 7 von der Maschine erhoben statt fortgeschrieben. Baustein E in der Prompt-Vorlage zieht die Rollengrenze, die serverseitig noch kein Recht zieht: bugtracker:manage erlaubt auch resolve. Richtiggestellt: Ein Token OHNE Projektbindung ist nicht eingeschraenkt, sondern uneingeschraenkt. Der Server leitet kein Heimatprojekt ab, also zaehlt jede Meldung als Heimmeldung - ohne Begruendungspflicht, ohne Deckelung auf medium, ohne triage_external, ohne reported_from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b4450c5f63
commit
60731c5104
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# dc-testkit-remote - dc-testkit auf der Test-VM aufrufen, vom Arbeitsplatz aus
|
||||
#
|
||||
# Der Agent laeuft NICHT auf der Test-VM (TESTUMGEBUNG.md 9: Claude Code
|
||||
# blockiert sie bis zum Neustart). Er laeuft auf dem Arbeitsplatz, wo auch die
|
||||
# Projekt-Repos liegen - und damit das Werkbank-Token in deren .mcp.json.
|
||||
# Getestet wird trotzdem dort drueben.
|
||||
#
|
||||
# Dieses Skript ist der Weg dazwischen. Es nimmt vier Dinge ab, an denen sonst
|
||||
# jeder Agent einzeln scheitert:
|
||||
#
|
||||
# 1. Das Token muss zur VM, darf dort aber nicht in "ps aux" auftauchen. Auf
|
||||
# einer geteilten Maschine liest jeder andere Benutzer die Befehlszeilen
|
||||
# fremder Prozesse. "ssh vm 'DC_TOKEN=xyz dc-testkit run'" legt es offen -
|
||||
# der Befehlsstring IST die Befehlszeile der entfernten Shell. Hier geht
|
||||
# es ueber die Standardeingabe und von dort direkt in die Umgebung; die
|
||||
# liest nur, wem der Prozess gehoert.
|
||||
#
|
||||
# 2. "~/bin" liegt ueber SSH nicht im PATH: ".profile" wird nur von einer
|
||||
# Login-Shell gelesen, und "ssh vm 'befehl'" startet keine. Die Antwort
|
||||
# waere "Kommando nicht gefunden" - das sieht aus wie ein fehlgeschlagener
|
||||
# Bezug und ist keiner. Deshalb "bash -l".
|
||||
#
|
||||
# 3. Die Einstellungen kommen vom Arbeitsplatz, nicht von der VM. Dort liegt
|
||||
# keine Konfiguration mehr - DC_TESTKIT_CONFIG zeigt bewusst ins Leere.
|
||||
# Sonst gilt drueben, was irgendwann einmal jemand eingetragen hat, und
|
||||
# genau daran ist es schon gescheitert: In der Konfiguration auf der VM
|
||||
# stand ein altes Laufzeit-Token, waehrend das richtige Werkbank-Token im
|
||||
# Projekt laengst ein anderes war.
|
||||
#
|
||||
# 4. Windows-OpenSSH kann kein Verbindungs-Multiplexing. Jeder Aufruf kostet
|
||||
# einen vollen Aufbau durch den Tunnel - also mehrere Schritte je Aufruf
|
||||
# buendeln (--skript) statt sie einzeln abzusetzen.
|
||||
#
|
||||
# Aufruf:
|
||||
# dc-testkit-remote <projekt> doctor
|
||||
# dc-testkit-remote <projekt> report --title "..." --desc "..."
|
||||
# dc-testkit-remote <projekt> --skript pruefplan.sh
|
||||
#
|
||||
# Vollstaendige Anleitung: docs/TESTUMGEBUNG.md im Deploymentcenter-Repo.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DC_TESTKIT_REMOTE_VERSION="1.1.0"
|
||||
|
||||
if [ -t 2 ] && [ -z "${NO_COLOR:-}" ]; then
|
||||
C_RESET=$'\033[0m'; C_RED=$'\033[31m'
|
||||
else
|
||||
C_RESET=''; C_RED=''
|
||||
fi
|
||||
|
||||
die() {
|
||||
printf '%sFehler%s %s\n' "$C_RED" "$C_RESET" "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
dc-testkit-remote - dc-testkit auf der Test-VM aufrufen
|
||||
|
||||
dc-testkit-remote <projekt> <befehl> [argumente...]
|
||||
dc-testkit-remote <projekt> --skript <datei>
|
||||
|
||||
<projekt> Slug; waehlt ~/.config/dc-testkit/<projekt> auf DIESEM Rechner
|
||||
--skript Mehrere dc-testkit-Aufrufe in einem Rutsch. Die Datei geht ueber
|
||||
die Standardeingabe und liegt nie auf der geteilten Maschine.
|
||||
|
||||
Konfiguration je Projekt in ~/.config/dc-testkit/<projekt>:
|
||||
|
||||
DC_REMOTE=testvm-<projekt> SSH-Alias der Testmaschine (Pflicht)
|
||||
DC_PROJECT_DIR=<Repo-Pfad> Token aus dessen .mcp.json
|
||||
DC_TOKEN=<token> Alternative, wenn es kein Repo gibt
|
||||
DC_PROJECT=<slug> Vorgabe: der <projekt>-Parameter
|
||||
DC_CHANNEL=<kanal> Vorgabe: prod
|
||||
DC_DISPLAY=:<nr> fuer display/shot (TESTUMGEBUNG.md 5.5)
|
||||
DC_INSTANCE=<kennung> nur noetig bei mehreren Laeufen je Maschine
|
||||
|
||||
Alles davon gilt auf DIESEM Rechner. Auf der VM wird keine Konfiguration
|
||||
gelesen - was dort liegt, bleibt ohne Wirkung.
|
||||
EOF
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Token aus der .mcp.json des Projekt-Repos - dieselbe Quelle, die auch der
|
||||
# MCP-Adapter benutzt. Ein zweiter Ablageort waere einer, der beim naechsten
|
||||
# Wechsel vergessen wird.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Welcher Python laeuft hier?
|
||||
#
|
||||
# Anders als das Testkit laeuft dieses Skript auf dem Arbeitsplatz, und der ist
|
||||
# oft Windows. Dort gibt es "python3" zwar im PATH - als App-Ausfuehrungsalias
|
||||
# des Microsoft Store, der bei jedem Aufruf nur eine Werbebotschaft ausgibt und
|
||||
# mit 9009 endet. Ein blosses "command -v python3" faellt darauf herein.
|
||||
# Deshalb wird ausprobiert statt nachgesehen.
|
||||
python_finden() {
|
||||
local kandidat
|
||||
|
||||
for kandidat in python3 python py; do
|
||||
command -v "$kandidat" >/dev/null 2>&1 || continue
|
||||
"$kandidat" -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)' >/dev/null 2>&1 \
|
||||
|| continue
|
||||
printf '%s\n' "$kandidat"
|
||||
return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
token_aus_projekt() {
|
||||
local wurzel="$1" datei python
|
||||
|
||||
python="$(python_finden)" \
|
||||
|| die "Kein funktionsfaehiges Python 3 gefunden (python3, python, py).
|
||||
|
||||
Gebraucht wird es, um das Token aus der .mcp.json zu lesen. Alternativ
|
||||
DC_TOKEN direkt in der Konfiguration hinterlegen."
|
||||
|
||||
for datei in "$wurzel/.mcp.json" "$wurzel"/*/.mcp.json; do
|
||||
[ -f "$datei" ] || continue
|
||||
|
||||
"$python" -c '
|
||||
import json, re, sys
|
||||
|
||||
try:
|
||||
with open(sys.argv[1], encoding="utf-8-sig") as fh:
|
||||
daten = json.load(fh)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
|
||||
for server in (daten.get("mcpServers") or {}).values():
|
||||
tok = (server.get("env") or {}).get("DC_TOKEN")
|
||||
if isinstance(tok, str) and tok and not re.search(r"[$%<]", tok):
|
||||
print(tok)
|
||||
sys.exit(0)
|
||||
|
||||
sys.exit(1)
|
||||
' "$datei" && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
[ $# -ge 1 ] || { usage; exit 1; }
|
||||
|
||||
case "$1" in
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--version) printf 'dc-testkit-remote %s\n' "$DC_TESTKIT_REMOTE_VERSION"; exit 0 ;;
|
||||
esac
|
||||
|
||||
PROJEKT="$1"; shift
|
||||
[ $# -ge 1 ] || { usage; exit 1; }
|
||||
|
||||
CONFIG_FILE="${DC_TESTKIT_CONFIG:-$HOME/.config/dc-testkit/$PROJEKT}"
|
||||
|
||||
[ -f "$CONFIG_FILE" ] || die "Keine Konfiguration: $CONFIG_FILE
|
||||
|
||||
Anlegen (TESTUMGEBUNG.md 4.1):
|
||||
DC_REMOTE=testvm-$PROJEKT
|
||||
DC_PROJECT_DIR=<lokaler Pfad zum Repo von $PROJEKT>"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
. "$CONFIG_FILE"
|
||||
|
||||
DC_REMOTE="${DC_REMOTE:-}"
|
||||
DC_PROJECT_DIR="${DC_PROJECT_DIR:-}"
|
||||
DC_TOKEN="${DC_TOKEN:-}"
|
||||
DC_PROJECT="${DC_PROJECT:-$PROJEKT}"
|
||||
DC_CHANNEL="${DC_CHANNEL:-prod}"
|
||||
DC_DISPLAY="${DC_DISPLAY:-}"
|
||||
DC_INSTANCE="${DC_INSTANCE:-}"
|
||||
|
||||
[ -n "$DC_REMOTE" ] || die "DC_REMOTE fehlt in $CONFIG_FILE - der SSH-Alias der Testmaschine."
|
||||
|
||||
if [ -z "$DC_TOKEN" ] && [ -n "$DC_PROJECT_DIR" ]; then
|
||||
DC_TOKEN="$(token_aus_projekt "$DC_PROJECT_DIR" || true)"
|
||||
fi
|
||||
|
||||
if [ -z "$DC_TOKEN" ]; then
|
||||
printf '%sFehler%s Kein Werkbank-Token.\n\n' "$C_RED" "$C_RESET" >&2
|
||||
|
||||
if [ -n "$DC_PROJECT_DIR" ]; then
|
||||
printf 'In %s liegt keine .mcp.json mit einem DC_TOKEN.\n\n' "$DC_PROJECT_DIR" >&2
|
||||
printf 'Das ist kein Fehler im Repo: .mcp.json ist per .gitignore ausgeschlossen\n' >&2
|
||||
printf 'und wird ueber Git NICHT mitgeliefert. Auf einer frisch geklonten\n' >&2
|
||||
printf 'Maschine fehlt sie deshalb immer.\n\n' >&2
|
||||
printf 'Das Dev-Agent-Token von "%s" muss dort eingetragen werden.\n' "$PROJEKT" >&2
|
||||
else
|
||||
printf 'Setze DC_PROJECT_DIR (Repo-Pfad) oder DC_TOKEN in %s.\n' "$CONFIG_FILE" >&2
|
||||
fi
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Der entfernte Teil
|
||||
#
|
||||
# Der SSH-Befehlsstring ist KONSTANT - er enthaelt weder Token noch Argumente.
|
||||
# Alles Veraenderliche geht ueber die Standardeingabe:
|
||||
#
|
||||
# Zeile 1 das Token
|
||||
# Zeile 2 der Befehl, mit %q so zitiert, dass eval ihn heil zurueckbaut
|
||||
# danach was der Befehl selbst von der Standardeingabe liest
|
||||
# (z.B. "dc-testkit report", das seine Beschreibung von dort nimmt)
|
||||
#
|
||||
# "read -r" holt genau eine Zeile und liest nicht darueber hinaus; der Rest
|
||||
# bleibt fuer den Befehl uebrig. Ohne diese Trennung muesste der Befehl durch
|
||||
# drei Ebenen Quoting - lokale Shell, ssh, entfernte Shell - und ein Titel mit
|
||||
# Anfuehrungszeichen kaeme dort zerlegt an.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
FERN='read -r DC_TOKEN; export DC_TOKEN; read -r DC_BEFEHL; eval "$DC_BEFEHL"'
|
||||
|
||||
# DC_TESTKIT_CONFIG bewusst auf /dev/null: Die Einstellungen kommen von hier,
|
||||
# nicht von der VM. Was dort in ~/.config/dc-testkit liegt, bleibt wirkungslos.
|
||||
UMGEBUNG="export DC_TESTKIT_CONFIG=/dev/null"
|
||||
UMGEBUNG="$UMGEBUNG DC_PROJECT=$(printf '%q' "$DC_PROJECT")"
|
||||
UMGEBUNG="$UMGEBUNG DC_CHANNEL=$(printf '%q' "$DC_CHANNEL")"
|
||||
[ -n "$DC_DISPLAY" ] && UMGEBUNG="$UMGEBUNG DC_DISPLAY=$(printf '%q' "$DC_DISPLAY")"
|
||||
[ -n "$DC_INSTANCE" ] && UMGEBUNG="$UMGEBUNG DC_INSTANCE=$(printf '%q' "$DC_INSTANCE")"
|
||||
[ -n "${NO_COLOR:-}" ] && UMGEBUNG="$UMGEBUNG NO_COLOR=1"
|
||||
|
||||
if [ "${1:-}" = '--skript' ]; then
|
||||
[ $# -eq 2 ] || die "--skript nimmt genau eine Datei."
|
||||
[ -f "$2" ] || die "Nicht gefunden: $2"
|
||||
|
||||
# Der Pruefplan wird als Befehl "bash -s" ausgefuehrt und selbst ueber die
|
||||
# Standardeingabe nachgeliefert - er liegt damit nie als Datei auf der
|
||||
# geteilten Maschine.
|
||||
{ printf '%s\n' "$DC_TOKEN"
|
||||
printf '%s; exec bash -s\n' "$UMGEBUNG"
|
||||
cat "$2"
|
||||
} | ssh "$DC_REMOTE" "bash -l -c '$FERN'"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
BEFEHL="$UMGEBUNG; dc-testkit"
|
||||
for arg in "$@"; do
|
||||
BEFEHL="$BEFEHL $(printf '%q' "$arg")"
|
||||
done
|
||||
|
||||
{ printf '%s\n' "$DC_TOKEN"
|
||||
printf '%s\n' "$BEFEHL"
|
||||
# Was auf unserer eigenen Standardeingabe steht, reichen wir durch - sonst
|
||||
# koennte "dc-testkit report" seine Beschreibung nicht von dort lesen.
|
||||
[ -t 0 ] || cat
|
||||
} | ssh "$DC_REMOTE" "bash -l -c '$FERN'"
|
||||
Reference in New Issue
Block a user