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:
Deploymentcenter Bot
2026-08-31 10:46:34 +02:00
co-authored by Claude Opus 5
parent b4450c5f63
commit 60731c5104
5 changed files with 1396 additions and 60 deletions
+66 -4
View File
@@ -33,6 +33,11 @@ gelesen zu haben.
## 1. Werkbank-Token anlegen
**Erst nachsehen, ob es schon eines gibt.** Für ein angebundenes Projekt steht
es in der `.mcp.json` des Repos, unter `env.DC_TOKEN` — dort braucht der
MCP-Adapter es ohnehin. Ein zweites danebenzustellen bringt nichts außer der
Frage, welches von beiden gilt.
Im WebUI: **Token-Verwaltung → ➕ Token erstellen**.
Die Ansicht hieß bis 2.7.0 „Master-Token erstellen" — deshalb war das
@@ -48,9 +53,27 @@ anderem Identitätstyp.
`supervisor:manage` nur, wenn der Agent Befunde auch abhaken soll.
`license:dev` und `updateservice:read` nur für die Test-VM (Schritt 5).
**Je Projekt ein eigenes Token.** Aus der Projektbindung leitet der Server
`reported_from` ab. Ein Token für alle, und das Signal „drei Projekte melden
dasselbe" ist wertlos — genau das Signal, das eine Meldung dringlich macht.
**Das Projekt darf nicht leer bleiben** — und zwar nicht, weil ohne Bindung
weniger ginge, sondern weil mehr geht. Der Server leitet aus ihr das
Heimatprojekt ab. Fehlt sie, ist für ihn *jede* Meldung eine Heimmeldung: keine
Begründungspflicht, keine Deckelung auf `medium`, kein Eingang in
`triage_external`, kein `reported_from`. Fehlmeldungen landen dann unmittelbar
in der offenen Liste fremder Projekte.
Kein hypothetischer Fall: Genau so ein Token lag bis zum 31.08.2026 auf der
Test-VM, `dev_agent` und ohne Projekt. Nachsehen, bevor du dich darauf
verlässt:
```bash
curl -s -H "Authorization: Bearer $DC_TOKEN" https://dc.mhdf.de/api/tokens/v1/whoami
```
Auf der Test-VM antwortet `dc-testkit doctor` dieselbe Frage vollständiger —
Bindung, jedes fehlende Recht einzeln, und ob das Skript selbst noch aktuell ist.
**Je Projekt ein eigenes Token.** Eines für alle, und das Signal „drei
Projekte melden dasselbe" ist wertlos — genau das Signal, das eine Meldung
dringlich macht.
**Kein Master-Token an einen Agenten.** Er darf melden, übernehmen und
abschließen; er darf keine Releases veröffentlichen und keine Tokens ausstellen.
@@ -98,7 +121,8 @@ In `CLAUDE.md` bzw. `AGENTS.md` des Projekts, aus
| Lage | Bausteine |
|---|---|
| Mit `.mcp.json` | **A** + **B** |
| Test-VM | **A** + **D** |
| Test-VM, Agent entwickelt und testet | **A** + **D** |
| Test-VM, Agent **prüft nur** | **A** + **E** |
| Ohne MCP | **A** + **C** |
**Nicht alles einkopieren.** Wo der Adapter läuft, tragen die Werkzeuge ihre
@@ -142,6 +166,30 @@ Zusätzlich `license:dev` und `updateservice:read` am Token, Ablage unter
**Kein Kundenlizenzschlüssel auf die VM.** Er verbraucht dort
Aktivierungsplätze, die hinterher niemand sauber freibekommt.
### Wenn der Agent nur prüfen soll
Dann Baustein **E** statt **D**, und vier Dinge stehen vorher fest, nicht erst
wenn er sie braucht:
| | |
|---|---|
| SSH-Alias und Schlüssel | `testvm-<projekt>` / `~/.ssh/testvm_<projekt>` |
| Testkit-Konfiguration | `~/.config/dc-testkit/<projekt>`, Pfad **im Prompt** |
| Werkbank-Token | die `.mcp.json` des Projekts muss **auf der VM** liegen; `DC_PROJECT_DIR` zeigt darauf. Über Git kommt sie nicht mit |
| Display, Datenbank, Verzeichnis | aus [TESTUMGEBUNG.md](TESTUMGEBUNG.md) §7 — vorher nachsehen, ob die Liste stimmt |
| Node.js auf der VM | **nicht vorhanden**, also kein MCP — der Prüfagent arbeitet ausschließlich über `dc-testkit` |
Der Token-Punkt ist der, an dem es beim ersten Mal hängt: Auf der VM liegt unter
`~/RiderProjects/` keine einzige `.mcp.json`. Der Agent kann das nicht selbst
beheben und soll es auch nicht versuchen — Baustein E sagt ihm, dass er sich in
diesem Fall meldet, statt ein Token zu suchen oder ein fremdes zu nehmen.
Die Rollengrenze steht in [TESTUMGEBUNG.md](TESTUMGEBUNG.md) §4.8. Sie lässt
sich heute **nicht** über Rechte erzwingen: `bugtracker:manage` erlaubt auch
`resolve` und das Ändern der Roadmap. Bis es ein `bugtracker:verify` gibt, ist
Baustein E das Einzige, was die Grenze zieht — er darf deshalb nicht gekürzt
werden.
---
## Was NICHT an die anderen Projekte geht
@@ -185,6 +233,20 @@ genau die Stellen ab, an denen es erfahrungsgemäß hakt:
Antwortet er auf die erste Frage mit dem Inhalt einer Markdown-Datei, fehlt
Baustein A oder der Adapter läuft nicht.
**Einen Prüfagenten** (Baustein E) fragst du anderes — seine typischen Fehler
sind andere:
| Frage | Erwartete Reaktion |
|---|---|
| „Leg los." | ruft **zuerst** `selfupdate --pruefen` und `doctor`, bevor er irgendetwas prüft |
| „Der Fix in Item 42 trägt nicht." | meldet den Fehler erneut — ruft **nicht** `resolve` und fängt nicht an, ihn selbst zu beheben |
| „Die Oberfläche sieht komisch aus." | startet sein **eigenes** Display, nicht `:0` und nicht `xvfb-run -a` |
| „Melde das." | nennt die geprüfte Fassung; bei fremder Ursache `--project` mit `evidence` |
| „Das Token fehlt." | meldet sich und nennt den erwarteten Pfad — sucht kein Token und nimmt kein fremdes |
Bietet er auf die zweite Frage an, den Fehler gleich selbst zu beheben, fehlt in
Baustein E der Absatz „Du behebst nichts" — oder er wurde gekürzt.
---
## Reihenfolge über alle Projekte
+134 -1
View File
@@ -11,7 +11,8 @@ arbeitet:
| Lage | Bausteine | Warum |
|---|---|---|
| Projektverzeichnis **mit** `.mcp.json` | **A** + **B** | Die Werkzeuge tragen ihre Beschreibung selbst; eine zweite Erklärung im Prompt veraltet nur und kostet Kontext |
| Linux-Test-VM | **A** + **D** | `dc-testkit` kapselt die Aufrufe, kein `curl` nötig |
| Linux-Test-VM, Agent entwickelt **und** testet | **A** + **D** | `dc-testkit` kapselt die Aufrufe, kein `curl` nötig |
| Linux-Test-VM, Agent **prüft nur** | **A** + **E** | E steht anstelle von D: dieselben Aufrufe, aber mit der Grenze, die kein Recht erzwingt |
| Alles Übrige (kein MCP) | **A** + **C** | Der Rückfallweg über REST, vollständig |
**A** ist die Grundhaltung und gilt immer — sie ist kurz. **B** ist eine halbe
@@ -425,6 +426,138 @@ Anleitung: `Deploymentcenter/docs/TESTUMGEBUNG.md`, Fremdmeldungen in §4.6.
---
## Baustein E — Prüfagent (statt D)
Für einen Agenten, dessen einzige Aufgabe das Prüfen ist. Er enthält alles aus **D** und zieht
zusätzlich die Grenze, die serverseitig noch kein Recht zieht: `bugtracker:manage` erlaubt heute
auch `resolve` und das Ändern der Roadmap. Wird der Absatz „Du prüfst" weggelassen, fängt das
Modell früher oder später an, den Fehler gleich selbst zu beheben — hilfsbereit gemeint und genau
das, was ein Prüfstand nicht tun darf.
```markdown
### Deine Rolle: prüfen, nicht beheben
Du prüfst **ein** Projekt: <PROJEKT>. Der Testlauf findet auf der geteilten
Linux-Test-VM statt — **du läufst nicht auf ihr**, sondern hier, wo auch das
Projekt-Repo liegt.
Jeder Aufruf geht über den Wrapper, nie über `ssh` von Hand:
dc-testkit-remote <projekt> doctor
dc-testkit-remote <projekt> run
dc-testkit-remote <projekt> --skript pruefplan.sh
Er nimmt dir vier Dinge ab, die von Hand zuverlässig schiefgehen: Das Token
landet nicht in `ps aux` der geteilten Maschine, `~/bin` liegt im PATH,
Anführungszeichen in `--title` überleben den Weg, und `--skript` bündelt einen
ganzen Prüfplan in eine Verbindung. Baue dir **keine** eigene SSH-Zeile — genau
darum gibt es den Wrapper.
Was du auf der VM direkt brauchst — Bilder abholen, Prozesse ansehen — geht
weiter über `ssh testvm-<projekt>` bzw. `scp`.
**Sagt der Wrapper „Kein Werkbank-Token", ist nichts kaputt.** Das Token deines
Projekts steht in der `.mcp.json` des Repos, und die ist per `.gitignore`
ausgeschlossen — über Git kommt sie nie mit. Auf einem frisch geklonten
Arbeitsplatz fehlt sie deshalb immer. Fang nicht an, ein Token zu suchen, zu
raten oder ein fremdes zu benutzen: **melde dich mit genau diesem Satz** und
nenne den Pfad, an dem die Datei erwartet wird. Ein Mensch bringt sie dorthin.
**Du behebst nichts.** Du änderst keinen Code des Projekts, rufst nie `dc-testkit
resolve` auf und änderst nie die Roadmap. Was als „behoben in Build X" gilt,
entscheidet, wer den Fix geschrieben hat. Du entscheidest etwas anderes, und nur
das: ob er trägt.
### Jede Sitzung beginnt gleich
dc-testkit-remote <projekt> selfupdate --pruefen # Werkzeug aktuell?
dc-testkit-remote <projekt> doctor # Rechte, Netz, Display
Sagt doctor nicht „Bereit", hörst du hier auf und meldest, was fehlt. Ein Testlauf
mit halben Rechten scheitert später an einer Stelle, die nichts mit der Sache zu
tun hat — und kostet jemanden einen Nachmittag Fehlersuche am falschen Ende.
### Was du prüfst
dc-testkit-remote <projekt> license # DEV-Schlüssel (wiederholbar)
dc-testkit-remote <projekt> run # ausliefern, prüfen, aktualisieren
dc-testkit-remote <projekt> check # auch: list, repair
dc-testkit-remote <projekt> update --version <alt> # der Rückfall
Oberflächen prüfst du selbst. Dein Display ist **fest vergeben** und steht als
`DC_DISPLAY` in deiner Konfiguration; benutze nie `:0`, nie `:1` und nie
`xvfb-run -a` (das nimmt sich :99, also die Nummer eines anderen Projekts).
dc-testkit-remote <projekt> display start # eigenes Xvfb hoch
dc-testkit-remote <projekt> shot 1_start # /tmp/<projekt>_1_start.png
dc-testkit-remote <projekt> display stop # danach, immer
Dazwischen navigierst du mit `xdotool` und holst die Bilder mit `scp` ab, um sie
anzusehen. Warte großzügig — 25 Sekunden nach dem Start, 5 nach jedem Klick.
Ein halb gezeichnetes Fenster erzeugt Befunde, die keine sind. Das vollständige
Verfahren steht in TESTUMGEBUNG.md 5.8.
### Was du meldest
dc-testkit-remote <projekt> report --type bug --severity <grad> \
--title "..." --desc "..."
**Immer mit der geprüften Fassung** — das Testkit liest sie aus der `manifest.json`
im Sandkasten. Ohne sie kann der Server einen echten Befund nicht von „diese
Maschine fährt einen älteren Stand" unterscheiden und legt im Zweifel ein
Regressionsitem an.
Bevor du meldest, ist die Frage nicht „ist das ein Fehler?", sondern **„ist das
schon bekannt, und in welcher Fassung wurde es behoben?"** Das beantwortet der
Tracker dir beim Melden von selbst. Antwortet er „schon behoben in einer späteren
Fassung", ist nichts entstanden und nichts verloren — aktualisieren und erneut
prüfen.
Was am **Deploymentcenter, am Testkit oder an TESTUMGEBUNG.md** selbst nicht
stimmt, meldest du dorthin, mit Begründung:
dc-testkit-remote <projekt> report --project deploymentcenter \
--type bug --severity medium --title "..." --desc "..." \
--evidence "was beobachtet wurde, und warum die Ursache dort liegt"
### Prüfaufträge — deine eigentliche Arbeit
dc-testkit-remote <projekt> next
Ein Prüfauftrag sieht aus wie jede andere Arbeit, stellt aber eine andere Frage:
nicht „behebe das", sondern „trägt der Fix?". Lies den Hinweis in der Ausgabe.
Danach genau eines von beidem:
dc-testkit-remote <projekt> confirm <id> "was geprüft wurde"
# der Fix trägt
dc-testkit-remote <projekt> report ...
# er trägt nicht; das Item wechselt selbst auf fix_insufficient
Kommst du nicht weiter: `dc-testkit-remote <projekt> unclaim <id> "<grund>"`.
Ein gehaltenes Item sperrt es für alle anderen bis zum Lease-Ende. Antwortet der Server mit `409
already_claimed`, nimm das nächste.
### Grenzen
- **Kein Kundenlizenzschlüssel auf die VM.** Nur `dc-testkit license`.
- **Kein Passwort, nirgends.** Deshalb prüft das Testkit die Auslieferung und
nicht den Installationsdialog.
- **Nichts veröffentlichen.** `updateservice:publish` gehört nicht auf diese
Maschine.
- **Nur dein Projekt anfassen** — eigene Datenbank, eigenes Verzeichnis, eigenes
Display. Die Vergabe steht in TESTUMGEBUNG.md 7; verlässt du dich darauf, prüf
sie vorher nach, sie war schon einmal im Rückstand.
- **Nichts Dauerhaftes hinterlassen.** Nach dem Lauf:
`dc-testkit-remote <projekt> cleanup`,
eigenes Xvfb beenden, Dienst abschalten.
Anleitung: `Deploymentcenter/docs/TESTUMGEBUNG.md` — deine Rolle in 4.8,
Fremdmeldungen in 4.6, Oberflächenprüfung in 5.8.
```
---
## Kurzfassung für knappe Prompt-Budgets
```markdown
+353 -25
View File
@@ -1,6 +1,6 @@
# Linux-Testumgebung — Anleitung für Agenten
**Stand: 28.08.2026** · Gilt projektübergreifend für alle Agenten und Entwickler, die auf der
**Stand: 31.08.2026** · Gilt projektübergreifend für alle Agenten und Entwickler, die auf der
geteilten Test-VM arbeiten.
> **Diese Datei ist die maßgebliche Fassung.** Sie liegt im Deploymentcenter-Repo unter
@@ -8,6 +8,15 @@ geteilten Test-VM arbeiten.
> den Projektrepos zeigen hierher. Wer etwas an der Umgebung ändert, ändert es **hier** und
> kopiert danach auf die VM — nicht umgekehrt.
>
> **Das ist zweimal unterblieben**, zuletzt am 31.08.2026 bemerkt: Auf der Maschine lag noch der
> Stand vom 26.08. — ohne das gesamte Testkit-Kapitel und mit verschobenen Abschnittsnummern, so
> dass der Verweis „§4.6, Fremdmeldungen" dort bei „Erhöhte Rechte" landete. Wer die Datei hier
> ändert, kopiert sie **im selben Arbeitsgang**:
>
> ```bash
> scp docs/TESTUMGEBUNG.md testvm-<projekt>:~/TESTUMGEBUNG.md
> ```
>
> **Bewusst nicht unter `public/`:** Diese Datei nennt eine interne Adresse, einen Benutzernamen
> und den Weg, `sudo` zeitweise freizuschalten. `/docs/` auf dem Deploymentcenter ist öffentlich
> erreichbar; dort gehört das nicht hin. Das Testkit-Skript dagegen schon — es enthält nichts
@@ -61,7 +70,7 @@ mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '<INHALT DER .pub-DATEI>' >> ~/.ssh/
### 2.3 SSH-Konfiguration (auf dem eigenen Rechner)
```
Host testvm
Host testvm-<projekt>
HostName 192.168.40.48
User richard
IdentityFile ~/.ssh/testvm_<projekt>
@@ -70,11 +79,21 @@ Host testvm
ServerAliveCountMax 4
```
**Der Aliasname trägt das Projekt.** Ein Agent hat genau einen Namen, unter dem er die Maschine
erreicht, und dieser Name sagt, mit welchem Schlüssel er das tut. Ein gemeinsamer Alias `testvm`
für alle wäre bequemer und macht die Trennung aus 2.1 wieder zunichte: Der Schlüssel wird dann
nach Zufall gewählt, und ein Entzug trifft entweder niemanden oder alle.
⚠️ **Kein `ControlMaster`/`ControlPath` eintragen, wenn der Client unter Windows läuft** —
Windows-OpenSSH unterstützt Verbindungs-Multiplexing nicht und scheitert mit
„Failed to connect to new control master". Stattdessen mehrere Prüfschritte pro Aufruf bündeln.
Test: `ssh testvm "hostname"`
Test: `ssh testvm-<projekt> "hostname"`
> **Bestand (31.08.2026):** Auf dem Arbeitsplatz existiert bisher nur `polytrader-vm` mit
> `~/.ssh/polytrader_vm` — ein Schlüssel, den alle Projekte mitbenutzen. Wer den nächsten Agenten
> aufsetzt, legt für ihn einen eigenen an und benennt bei der Gelegenheit den alten Eintrag nach
> `testvm-polytrader` um. Bis dahin schlagen die Beispiele in dieser Datei wörtlich fehl.
---
@@ -92,7 +111,7 @@ Test: `ssh testvm "hostname"`
| JetBrains Rider | vorhanden | |
| Docker | **nicht** installiert | |
| Node.js | **nicht** installiert | |
| jq | **nicht** installiert | Wird auch nicht gebraucht — das Testkit liest JSON mit `python3` |
| jq | vorhanden (nachträglich) | Das Testkit benutzt es **nicht** — es liest JSON mit `python3`, damit es auch auf einer Maschine ohne jq läuft |
**Nicht vorhandene Werkzeuge dürfen installiert werden** — aber bitte in der Belegungsliste
(Abschnitt 7) vermerken, damit andere wissen, was sich geändert hat.
@@ -116,8 +135,23 @@ der im WebUI etwas anklickt.
### 4.1 Einmal einrichten
Das Skript liegt im Deploymentcenter-Repo unter `public/testkit/dc-testkit.sh` und wird mit
`deploy.py` ins Webroot ausgeliefert. Auf der VM:
**Der Agent läuft nicht auf der VM, sondern auf dem Arbeitsplatz.** Claude Code blockiert die
Maschine bis zum Neustart (siehe 9) — deshalb arbeitet er über SSH von außen. Daraus folgt die
Aufteilung:
| Was | Wo | Warum dort |
|---|---|---|
| `dc-testkit` | **auf der VM**, `~/bin/dc-testkit` | Dort läuft der Test: Sandkasten, Update-Agent, Display |
| `dc-testkit-remote` | **auf dem Arbeitsplatz** | Dort sitzt der Agent |
| Konfiguration | **auf dem Arbeitsplatz**, `~/.config/dc-testkit/<projekt>` | Dort liegen auch die Projekt-Repos — und damit die Token (4.2) |
| Werkbank-Token | **auf dem Arbeitsplatz**, in der `.mcp.json` des Repos | Es muss nirgends sonst hin |
**Auf der VM liegt keine Konfiguration.** `dc-testkit-remote` setzt `DC_TESTKIT_CONFIG` bewusst
ins Leere: Sonst gälte drüben, was irgendwann einmal jemand eingetragen hat. Genau daran ist es
gescheitert — dort lag ein altes Laufzeit-Token, während das richtige Werkbank-Token des Projekts
längst ein anderes war, und `doctor` meldete trotzdem „Bereit".
**Auf der VM** (einmal je Maschine):
```bash
mkdir -p ~/bin
@@ -125,6 +159,45 @@ curl -fsSL https://dc.mhdf.de/testkit/dc-testkit.sh -o ~/bin/dc-testkit
chmod +x ~/bin/dc-testkit
```
**Auf dem Arbeitsplatz** (einmal je Arbeitsplatz):
```bash
mkdir -p ~/bin
curl -fsSL https://dc.mhdf.de/testkit/dc-testkit-remote.sh -o ~/bin/dc-testkit-remote
chmod +x ~/bin/dc-testkit-remote
```
Danach läuft jeder Aufruf über den Wrapper — mit dem Projekt-Slug als erstem Argument:
```bash
dc-testkit-remote polytrader doctor
dc-testkit-remote polytrader run
dc-testkit-remote polytrader --skript pruefplan.sh
```
**Warum ein Wrapper und nicht `ssh` von Hand?** Aus demselben Grund, aus dem es `dc-testkit`
überhaupt gibt: Sonst baut jeder Agent seine eigene Fassung derselben SSH-Zeile. Vier Dinge gehen
dabei zuverlässig schief:
1. **Das Token stünde in `ps aux`.** `ssh vm 'DC_TOKEN=xyz dc-testkit run'` legt es offen — der
Befehlsstring **ist** die Befehlszeile der entfernten Shell, und auf einer geteilten Maschine
liest jeder andere Benutzer fremde Befehlszeilen. Der Wrapper schickt es über die
Standardeingabe; von dort geht es direkt in die Umgebung, und die liest nur, wem der Prozess
gehört.
2. **`~/bin` liegt über SSH nicht im PATH.** `~/.profile` nimmt es auf, wird aber nur von einer
**Login-Shell** gelesen — und `ssh vm 'befehl'` startet keine. Die Antwort wäre
`Kommando nicht gefunden`: sieht aus wie ein fehlgeschlagener Bezug und ist keiner. Der
Wrapper ruft `bash -l`.
3. **Quoting durch drei Ebenen.** Ein `--title "Update bricht ab"` muss durch lokale Shell, SSH
und entfernte Shell. Ohne Sorgfalt kommt der Titel dort zerlegt an und die Meldung trägt den
halben Text. Der Wrapper schickt den Befehl über die Standardeingabe, nicht durch das Quoting.
4. **Kein Verbindungs-Multiplexing unter Windows.** Jeder Aufruf kostet einen vollen Aufbau durch
den Tunnel; `--skript` bündelt einen ganzen Prüfplan in einen.
> ⚠️ **Was `dc-testkit-remote` nicht kann:** alles, was einen Bildschirminhalt zurückbringt.
> Aufnahmen entstehen mit `shot` auf der VM unter `/tmp/<projekt>_<name>.png` und werden danach
> abgeholt — dafür bleibt `scp` der Weg (5.8).
> ⚠️ **Beides — dieser Download und `dc-testkit license` — setzt Deploymentcenter 2.6.1 voraus.**
> Ist die Fassung auf dem Server älter, fehlt der Endpunkt `/api/license/v1/dev-key`. Er fällt
> dabei **nicht** mit 404 auf: Die Lizenz-API antwortet bei unbekannten Pfadsegmenten mit ihrer
@@ -136,10 +209,30 @@ chmod +x ~/bin/dc-testkit
> der Fassung: Im Webroot lag die Datei nur unter `/public/testkit/`, die Route auf `/testkit/`
> fehlte in der `.htaccess`. Wer vor 2.7.1 ein 404 sah, hat deshalb nicht unbedingt einen zu alten
> Server vor sich. Ausweichweg für alte Stände:
> `scp public/testkit/dc-testkit.sh testvm:~/bin/dc-testkit`
> `scp public/testkit/dc-testkit.sh testvm-<projekt>:~/bin/dc-testkit`
> — und für den Arbeitsplatz `cp public/testkit/dc-testkit-remote.sh ~/bin/dc-testkit-remote`.
Dann je Projekt eine Konfiguration — **das Token gehört in die Datei, nicht in die Befehlszeile**,
sonst steht es in der Shell-History:
Dann je Projekt eine Konfiguration. **Der bessere Weg ist `DC_PROJECT_DIR`** — dann steht das
Token nur an einer Stelle, in der `.mcp.json` des Projekts (4.2):
```bash
mkdir -p ~/.config/dc-testkit
cat > ~/.config/dc-testkit/polytrader <<'EOF'
DC_REMOTE=testvm-polytrader
DC_PROJECT=polytrader
DC_PROJECT_DIR=J:/Softwareprojekte/PolytraderSharp/PolyTraderSharp
DC_CHANNEL=prod
DC_DISPLAY=:99
EOF
chmod 600 ~/.config/dc-testkit/polytrader
```
`DC_PROJECT_DIR` ist der Pfad **auf dem Arbeitsplatz** — dort liegt das Repo mit der `.mcp.json`.
Was in den Projektverzeichnissen auf der VM liegt, spielt keine Rolle; der Agent arbeitet nie
von dort aus.
Wo es kein Projekt-Repo gibt, kommt das Token in die Datei — **nie in die Befehlszeile**, sonst
steht es in der Shell-History:
```bash
mkdir -p ~/.config/dc-testkit
@@ -155,28 +248,136 @@ chmod 600 ~/.config/dc-testkit/config
Arbeitet ein Agent an mehreren Projekten, hält er je Projekt eine eigene Datei und wählt sie über
`DC_TESTKIT_CONFIG=~/.config/dc-testkit/polytrader dc-testkit …`.
**Ein Testagent gehört zu genau einem Projekt** (siehe 4.8). Bei ihm steht der Pfad fest in seinem
Prompt und nicht in seinem Gedächtnis — wer die Datei vergisst, prüft stillschweigend das falsche
Projekt und meldet dorthin.
Was in der Datei stehen darf:
| Schlüssel | Bedeutung | Vorgabe |
|---|---|---|
| `DC_URL` | Instanz, gegen die gearbeitet wird | `https://dc.mhdf.de` |
| `DC_TOKEN` | Werkbank-Token des Projekts (4.2) | — |
| `DC_PROJECT_DIR` | Projekt-Repo; ohne `DC_TOKEN` wird es aus dessen `.mcp.json` gelesen | — |
| `DC_PROJECT` | Slug des Projekts | — |
| `DC_CHANNEL` | Release-Kanal | `prod` |
| `DC_INSTANCE` | Maschinenkennung, an der der DEV-Schlüssel hängt | Kurzname des Rechners |
| `DC_DISPLAY` | Virtuelles Display dieses Projekts (5.5, 5.8) | — (dann keine Oberflächenprüfung) |
`DC_INSTANCE` nur setzen, wenn mehrere Testläufe auf **derselben** Maschine auseinandergehalten
werden müssen. Sie muss über Läufe hinweg gleich bleiben: Wechselt sie, sucht der Server jedes Mal
einen anderen Schlüssel heraus, bis die Grenze von zwölf je Projekt greift.
**Rangfolge:** Schalter auf der Befehlszeile schlägt Umgebungsvariable schlägt Datei schlägt
Vorgabe. Bis 1.1.0 war das genau verkehrt herum — die Datei überschrieb die Umgebung, und
`DC_PROJECT=x dc-testkit report` meldete stillschweigend in das Projekt aus der Datei. Auf einer
Maschine mit mehreren Projekten ist das die teuerste Sorte Fehler: Es geht nichts kaputt, es landet
nur alles am falschen Ort.
Prüfen, ob alles steht:
```bash
dc-testkit doctor
```
`doctor` ist die einzige Auskunft, der zu trauen ist — aber nur, wenn er selbst aktuell ist. Er
prüft deshalb seit 1.1.0 als Erstes den eigenen Stand gegen die ausgelieferte Fassung:
```bash
dc-testkit selfupdate # auf die ausgelieferte Fassung bringen
dc-testkit selfupdate --pruefen # nur nachsehen, nichts ändern
```
> **Warum das gebraucht wurde.** Das Skript liegt auf jeder Maschine als Kopie und altert dort
> still. Am 31.08.2026 lag auf der Test-VM eine Fassung, die 236 Zeilen älter war als die
> ausgelieferte — beide meldeten `1.0.0`. Deshalb sagte `doctor` dort „Bereit", während die
> aktuelle Fassung drei fehlende Rechte des Tokens angemeckert hätte. **Die Fassungsnummer wird
> bei jeder Änderung hochgezählt**; ohne das ist die Selbstprüfung wertlos. Sie sagt das auch:
> gleiche Nummer bei anderem Inhalt ist ein Befund, kein Achselzucken.
>
> Ist die eigene Fassung **neuer** als die ausgelieferte, meldet `doctor` einen Entwicklungsstand
> und rät ausdrücklich **nicht** zum `selfupdate` — das holte die ältere Fassung zurück.
> `selfupdate` verweigert diesen Rückschritt von sich aus; `--force` hebt die Sperre auf.
### 4.2 Das Werkbank-Token
Ein Mensch legt es **einmal je Projekt** an — im WebUI unter *System → Tokens*:
**Es existiert wahrscheinlich schon.** Jedes angebundene Projekt hat eines — es steht in der
`.mcp.json` des Projekt-Repos, wo der MCP-Adapter es ohnehin braucht:
```json
{ "mcpServers": { "deploymentcenter": { "env": { "DC_TOKEN": "dc_master_…" } } } }
```
Das Testkit holt es sich von dort. Dazu zeigt `DC_PROJECT_DIR` auf das Repo — gesucht wird im
Verzeichnis selbst und eine Ebene tiefer:
```bash
DC_PROJECT_DIR=~/RiderProjects/PolyTraderSharp
```
Damit gibt es das Geheimnis **einmal je Projekt**, nicht zusätzlich in
`~/.config/dc-testkit/config`. Zwei Ablagen desselben Tokens heißt: Beim nächsten Wechsel wird
eine davon vergessen. Genau so war es auf der Test-VM — dort lag in der Testkit-Konfiguration
noch ein altes Laufzeit-Token (`watchdog:ping`, `bugtracker:report`, ohne Bindung), während das
richtige Werkbank-Token des Projekts längst ein anderes war. `doctor` meldete trotzdem „Bereit",
und die Rechte fehlten erst dort, wo sie gebraucht wurden.
> **`.mcp.json` kommt über Git nicht mit.** Sie ist per `.gitignore` ausgeschlossen — auf einer
> frisch geklonten Maschine fehlt sie deshalb **immer**. Das ist kein Fehler im Repo und keiner
> in den Rechten. Das Token muss von Hand dorthin, aus der `.mcp.json` des Projekts auf dem
> Arbeitsplatz. `dc-testkit` sagt genau das, statt an einem 401 zu scheitern:
>
> ```
> Fehler Kein Werkbank-Token.
>
> In ~/RiderProjects/PolyTraderSharp liegt keine .mcp.json mit einem DC_TOKEN.
>
> Das ist kein Fehler im Repo: .mcp.json ist per .gitignore ausgeschlossen
> und wird ueber Git NICHT mitgeliefert.
> ```
>
> Ein Platzhalter (`"${DC_TOKEN}"`) zählt dabei nicht als Token — sonst liefe der Aufruf mit
> einer Zeichenkette los, die der Server nur mit 401 beantworten kann.
**Wenn wirklich keines existiert**, legt ein Mensch es an — im WebUI unter
*Token-Verwaltung → ➕ Token erstellen*:
| Feld | Wert |
|---|---|
| Typ | Master |
| Besitzerart | `dev_agent` |
| Projekt | der Slug des Projekts |
| Projekt | der Slug des Projekts — **nicht leer** |
| Rechte | `bugtracker:manage`, `license:dev`, `updateservice:read` |
Mehr braucht es nicht, und mehr sollte es nicht sein. `bugtracker:manage` schließt Lesen und
Melden ein, `license:dev` schließt `updateservice:read` ein — zwei Häkchen genügen also.
`bugtracker:manage` schließt Lesen und Melden ein, `license:dev` schließt `updateservice:read`
ein — zwei Häkchen genügen also.
**Nicht `*` vergeben.** Ein Token mit allen Rechten auf einer geteilten Testmaschine darf auch
Releases veröffentlichen und Lizenzen fremder Projekte anfassen.
#### Warum die Projektbindung nicht optional ist
**Ein Token je Projekt, gebunden an dieses Projekt.** Ein „allgemeines Token für die ganze VM"
klingt praktischer und ist es nicht — aus zwei Gründen, die in verschiedene Richtungen wirken:
**Es reicht nicht weit genug.** Ein Testlauf prüft die Auslieferung *eines Produkts*: dessen
Release aus der Ablage, dessen Lizenzschlüssel, dessen Update. Beim Ausstellen des
DEV-Schlüssels prüft der Server die Bindung ausdrücklich und lässt hier **keine** Ausnahme für
Werkbank-Token zu. Ein an `deploymentcenter` gebundenes Token bekommt für `polytrader` ein
`403 project_forbidden` — ein Lizenzschlüssel öffnet die Release-Ablage eines Produkts, und das
ist etwas anderes als eine Meldung.
**Und es reicht zu weit.** Ein Token **ohne** Bindung ist nicht etwa eingeschränkt, sondern
uneingeschränkt: Der Server leitet daraus kein Heimatprojekt ab, also ist für ihn *jede* Meldung
eine Heimmeldung. Damit fällt der gesamte Schutz für Fremdmeldungen weg — keine
Begründungspflicht, keine Deckelung auf `medium`, kein Eingang in `triage_external`, kein
`reported_from`. Meldungen landen unmittelbar in der offenen Liste fremder Projekte und
verderben die Zahlen, auf denen die Supervisor-Detektoren kalibrieren.
Die bezeugte Herkunft ist der Punkt: Melden drei Projekte dasselbe, ist das eine Aussage über
die Sache. Ohne Bindung ist es eine Aussage über niemanden.
**Zu `*`:** Die vorhandenen Projekt-Token tragen durchweg Vollzugriff. Das läuft, deckt die drei
gebrauchten Rechte ab — und erlaubt auf einer **geteilten** Maschine eben auch
`updateservice:publish`. Ein Release aus einem Testlauf heraus wäre für Kunden sofort sichtbar.
Wer die Token neu ausstellt, schneidet sie auf die drei Rechte zu.
### 4.3 DEV-Lizenzschlüssel selbst holen
@@ -220,6 +421,18 @@ dc-testkit update # Update auf latest
dc-testkit verify
```
Drei weitere Aufrufe reichen den jeweiligen Modus des `update-agent` durch. Sie verändern den
Sandkasten nicht — außer `repair`, der genau dafür da ist:
| Aufruf | Wofür |
|---|---|
| `dc-testkit check` | Gibt es überhaupt ein Update? Antwortet, ohne etwas zu holen |
| `dc-testkit list` | Welche Fassungen führt der Server in diesem Kanal? |
| `dc-testkit repair` | Integritätsreparatur — stellt her, was im Sandkasten fehlt oder nicht zur Prüfsumme passt |
`repair` gehört in einen Prüfplan, weil es der Weg ist, den ein Kunde nach einem abgebrochenen
Update nimmt. Ein Modus, den niemand testet, ist im Ernstfall genau der, der nicht funktioniert.
Oder alles auf einmal, mit automatischer Meldung dessen, was scheitert:
```bash
@@ -497,6 +710,39 @@ und das Schreiben der Konfiguration bleiben ungeprüft. Ein kopfloser Installati
(`--action install --answers antworten.json`) würde diese Lücke schließen; er steht in der
[Roadmap](./ROADMAP.md) unter G1.
### 4.8 Ein Agent, der nur prüft
Ein **Prüfagent** ist etwas anderes als ein Entwickleragent, der nebenbei testet. Er gehört zu
genau einem Projekt, läuft auf dieser Maschine — und **behebt nichts**.
Der Unterschied ist keine Geschmacksfrage. Wer einen Fix schreibt und ihn anschließend selbst
abnimmt, prüft gegen die Erwartung, die er beim Schreiben hatte. Genau dagegen ist der Tracker
gebaut: `resolve` behauptet „ich habe es behoben, in Build X"; `confirm` behauptet nichts, sondern
bestätigt den Fix eines anderen (siehe 4.5, „Der Lebenslauf").
| Der Prüfagent tut | Er tut **nicht** |
|---|---|
| Auslieferung und Update gegen den Sandkasten prüfen (4.4) | Code des Projekts ändern |
| Oberflächen begutachten (5.8) | `dc-testkit resolve` aufrufen |
| Befunde melden — auch in fremden Projekten (4.5, 4.6) | Die Roadmap ändern |
| Prüfaufträge übernehmen und mit `confirm` oder erneuter Meldung schließen | Ein Item beheben, das er selbst gemeldet hat |
**Diese Trennung erzwingt derzeit kein Recht.** `bugtracker:manage` ist ein einziger Schreib-Scope:
Wer bestätigen darf, darf auch `resolve`, `bulk_update` und die Roadmap umschreiben. Bis es ein
eigenes `bugtracker:verify` gibt, steht die Grenze **nur im Prompt des Agenten** — und gehört
deshalb dort ausdrücklich hinein. Der fertige Textbaustein dafür ist **E** in
[AGENT_PROMPT_TEMPLATE.md](AGENT_PROMPT_TEMPLATE.md).
**Gegen welche Fassung?** Jede Meldung trägt die geprüfte Fassung mit, gelesen aus der
`manifest.json` im Sandkasten. Ohne sie kann der Server nicht unterscheiden, ob ein Befund neu ist
oder ob die Maschine schlicht einen älteren Stand fährt — und legt im Zweifel ein Regressionsitem
an (4.5). Für einen Agenten, der nichts anderes tut als prüfen, ist das die wichtigste einzelne
Angabe überhaupt.
**Was ihn einrichtet**, steht in [AGENTEN_ANBINDEN.md](AGENTEN_ANBINDEN.md); die Reihenfolge ist
dort dieselbe wie für jeden anderen Agenten, nur mit anderem Prompt-Baustein und anderer
Abnahmefrage.
---
## 5. Konventionen für mehrere Projekte
@@ -619,6 +865,11 @@ sudo rm /etc/sudoers.d/99-testing
Das hebt den Schutz für das Konto vollständig auf — vertretbar auf einer Test-VM hinter VPN, aber
nicht dauerhaft und niemals auf einer Produktivmaschine.
> **Stand 31.08.2026: `/etc/sudoers.d/99-testing` liegt dort und gilt.** Aus „befristet" ist
> „dauerhaft" geworden — der Weg, den solche Freigaben immer nehmen, wenn das Entfernen nicht
> zum selben Arbeitsgang gehört. Wer als Nächstes etwas mit `sudo` erledigt, nimmt sie danach
> zurück. Ein Prüfagent braucht sie nicht.
**Für das Testkit wird das nicht gebraucht.** Es arbeitet vollständig im Benutzerkontext.
### 5.7 GUI-Anwendungen ohne Bildschirm testen
@@ -642,7 +893,28 @@ Bildschirmfotos schicken. Das Verfahren hat sich am 26.08.2026 bewährt und **si
zutage gefördert**, darunter einen, der sämtliche Tabellen der Anwendung unsichtbar machte.
Drei Werkzeuge, alle installiert: **Xvfb** (virtuelles Display), **xdotool** (Klicken und Tippen),
**ImageMagick** (`import` für Aufnahmen, `convert` zum Zuschneiden).
**ImageMagick** (`import` für Aufnahmen, `convert` zum Zuschneiden). `dc-testkit doctor` sagt seit
1.1.0, ob sie da sind und ob das eigene Display frei ist.
**Den immer gleichen Teil nimmt das Testkit ab** — Display starten, aufnehmen, abräumen. Setze
dazu `DC_DISPLAY` in deiner Konfiguration (die Nummer steht in Abschnitt 7):
```bash
dc-testkit display status # frei, belegt oder verwaister Socket?
dc-testkit display start # Xvfb auf DC_DISPLAY, Vorgabe 1600x1000x24
dc-testkit shot 1_start # nach /tmp/<projekt>_1_start.png
dc-testkit display stop # nur das eigene, nie fremde
```
Das sind genau die drei Stellen, an denen ein selbstgeschriebenes Skript regelmäßig danebengreift:
`DISPLAY` nicht gesetzt (dann landet alles auf `:0`, dem echten Desktop), `pkill Xvfb` ohne Nummer
(das erschlägt die Sitzungen aller anderen Projekte), Dateinamen ohne Projektpräfix (zwei Läufe
überschreiben sich gegenseitig). `display start` verweigert `:0` und `:1` von sich aus und erkennt
einen verwaisten Socket, statt an ihm zu scheitern.
**Was projektspezifisch bleibt**, ist der Klickplan dazwischen — Fenstername, Koordinaten,
Wartezeiten. Den kapselt das Testkit bewusst nicht: Er ist bei jedem Programm anders, und ein
Werkzeug, das ihn zu erraten versucht, wäre unzuverlässiger als das Rezept unten.
Anders als `xvfb-run` wird das Display hier **dauerhaft** gestartet, damit sich zwischen den
Aufnahmen navigieren lässt:
@@ -681,7 +953,7 @@ kill $APP 2>/dev/null; sleep 2; pkill -f "Xvfb $DISP" 2>/dev/null
Danach die Bilder abholen und ansehen:
```bash
scp testvm:'/tmp/polytrader_*.png' .
scp testvm-polytrader:'/tmp/polytrader_*.png' .
```
**Was sich dabei bewährt hat:**
@@ -730,11 +1002,20 @@ nutzen, Port 3389 ist offen) — nötig ist es für die Sichtprüfung nicht.
## 7. Belegung (bei Änderungen fortschreiben)
**Erhoben am 31.08.2026 von der Maschine selbst**, nicht aus dieser Datei fortgeschrieben — sie
war zu dem Zeitpunkt vier Projekte im Rückstand. Wer etwas anlegt, trägt es hier ein; wer sich
darauf verlassen will, prüft nach:
```bash
ssh testvm-<projekt> 'sudo mariadb -e "SHOW DATABASES;"; ls /opt ~; ss -tlnp; ls /tmp/.X11-unix/; pgrep -a Xvfb'
```
**Datenbanken**
| Datenbank | Benutzer | Projekt | seit |
|---|---|---|---|
| `polytrader_test` | `polytrader_test`@localhost | PolyTrader | 26.08.2026 |
| `ibkrtrader_test` | `ibkrtrader_test`@localhost | IBKRTrader | vor 31.08.2026 |
**Dienste**
@@ -742,41 +1023,81 @@ nutzen, Port 3389 ist offen) — nötig ist es für die Sichtprüfung nicht.
|---|---|---|---|---|
| `polytrader.service` | `polytrader` | `/opt/polytrader` | PolyTrader | 26.08.2026 |
Am 31.08.2026 lief keiner dieser Dienste — wie es Abschnitt 8 verlangt.
**Verzeichnisse**
| Projekt | Publish-Verzeichnis | Dienst |
|---|---|---|
| PolyTrader | `~/polytrader-test` | `/opt/polytrader` |
| IBKRTrader | `~/ibkrtrader-test` | — |
| ClawdDotNet | `~/clawddotnet-test` | — |
| Predictalytics | `~/predictalytics` ⚠️ | — |
⚠️ **`~/predictalytics` bricht die Konvention aus 5.2** (`~/<projekt>-test`). Aus dem Namen ist
nicht mehr abzulesen, ob dort ein Testlauf oder eine Arbeitskopie liegt — und genau diese Frage
stellt sich, wenn jemand aufräumt. Beim nächsten Anfassen umbenennen.
Quellbäume liegen daneben unter `~/RiderProjects/<Projekt>` (5.2): ClawdDotNet, Deploymentcenter,
IBKRTrader, PolyTraderSharp, Predictalytics.
**Werkbank-Token** (Vergabe siehe 4.2)
| Projekt | Token angelegt | Rechte |
|---|---|---|
| *noch keins* | | |
Die richtigen existieren längst — je Projekt eines, in der `.mcp.json` des Repos auf dem
Arbeitsplatz:
| Projekt | Token | Bindung | Rechte |
|---|---|---|---|
| PolyTrader | `DEV-Polytrader` | `polytrader` | `*` |
| IBKRTrader | `DEV-IBKRTrader` | `ibkrtrader` | `*` |
| Deploymentcenter | `DEV-Deploymentcenter` | `deploymentcenter` | `*` |
| ClawdDotNet | — | — | `.mcp.json` enthält nur den Platzhalter |
Alle sind `dev_agent` und korrekt gebunden. Zu `*` siehe die Anmerkung am Ende von 4.2.
⚠️ **Auf der VM kommt davon nichts an.** Unter `~/RiderProjects/` liegt keine einzige
`.mcp.json` — die Repos sind dort frisch geklont, und die Datei ist per `.gitignore`
ausgeschlossen. In `~/.config/dc-testkit/config` steckt stattdessen ein altes Sub-Token
(„PolyTrader Avalonia (Dev, alle Umgebungen)", `watchdog:ping` + `bugtracker:report`, ohne
Bindung). Damit sind `license`, `install`, `update`, `verify`, `run`, `next`, `comment`,
`resolve` und `confirm` gesperrt; übrig bleibt `report`.
**Zu beheben ist das nicht durch ein neues Token**, sondern indem das vorhandene
`DEV-Polytrader` auf die Maschine kommt — als `.mcp.json` im Projektverzeichnis, auf das dann
`DC_PROJECT_DIR` zeigt. `dc-testkit doctor` benennt seit 1.1.0 jeden dieser Punkte einzeln und
nennt die Herkunft des benutzten Tokens.
**X-Displays** (Vergabe siehe 5.5)
| Display | Projekt | Stand |
|---|---|---|
| `:0` | echter Desktop — **nie belegen** | |
| `:0`, `:1` | echter Desktop und Anmeldebildschirm — **nie belegen** | |
| `:99` | PolyTrader | 26.08.2026 |
| `:98` | Predictalytics | reserviert |
| `:97` | ClawdDotNet | reserviert |
| `:96` | IBKRTrader | reserviert |
| `:95` abwärts | frei für künftige Projekte | |
**Ports** (Stand 26.08.2026)
Am 31.08.2026 lief **kein** Xvfb; belegt waren nur `:0` und `:1`. Dass `:1` existiert, stand hier
bisher nicht — es gehört zum Anmeldebildschirm und ist genauso tabu wie `:0`.
**Ports** (Stand 31.08.2026)
| Port | Belegt durch |
|---|---|
| 22 | SSH |
| 53 | systemd-resolved (nur localhost) |
| 631 | CUPS (localhost, IPv4 **und** IPv6) |
| 3306 | MariaDB (nur localhost) |
| 3389 | RDP |
| 5000 | vorhandener Dienst (nur IPv6-localhost) |
| 631 | CUPS |
| 5000 | vorhandener Dienst (localhost, IPv4 **und** IPv6) |
Port 5000 war hier als „nur IPv6-localhost" geführt. Das stimmt nicht mehr — wer ihn für einen
Testdienst nehmen wollte, wäre auf beiden Familien kollidiert.
**Nachträglich installiert:** MariaDB 11.8.6, Xvfb, xdotool, ImageMagick, x11-utils
(alle am 26.08.2026 für PolyTrader).
(alle am 26.08.2026 für PolyTrader), jq (undatiert, von unbekannter Hand — das Testkit braucht
es nicht).
---
@@ -823,6 +1144,13 @@ Danach Abschnitt 7 nachziehen.
Neustart; mehrere Reparaturversuche waren erfolglos, es wird auf ein Update gewartet. Agenten
arbeiten deshalb **über SSH von außen**. (Speichermangel ist **nicht** die Ursache — die
Maschine hat 31 GB, davon 25 GB frei.)
**Für Prüfagenten ist das die bestimmende Einschränkung.** Ein Prüfagent je Projekt läuft
nicht *auf* der Maschine, sondern auf einem Arbeitsplatz und greift über `testvm-<projekt>`
zu. Daraus folgt zweierlei: Er kann `node` und damit den MCP-Adapter dort ohnehin nicht
benutzen (der ist auch nicht installiert), und er sollte mehrere Prüfschritte pro
SSH-Aufruf bündeln — Verbindungs-Multiplexing steht ihm unter Windows nicht zur Verfügung
(2.3).
- **Verbindungs-Multiplexing funktioniert nicht mit Windows-OpenSSH** (siehe 2.3).
- **Der Installationsdialog ist nicht kopflos prüfbar** (siehe 4.7). Damit bleiben die Auflösung
von `setup.json` und das Schreiben der Konfiguration ungeprüft.
+250
View File
@@ -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'"
+593 -30
View File
@@ -15,14 +15,26 @@
# war. Ein Weg, den alle nehmen, ist der einzige, dessen Fehler man findet.
#
# Abhaengigkeiten: curl und python3. Beides ist auf der VM vorhanden; jq wird
# bewusst NICHT vorausgesetzt, weil es dort nicht installiert ist und eine
# Installation nur fuer das Lesen von JSON die Maschine fuer alle veraendert.
# bewusst NICHT vorausgesetzt. Inzwischen ist es dort zwar installiert - aber
# das Skript soll auf einer frisch aufgesetzten Maschine sofort laufen, ohne
# dass jemand sie erst fuer alle veraendert. Was vorausgesetzt wird, muss
# ueberall gelten, nicht nur auf der einen Maschine, die man vor sich hat.
#
# Vollstaendige Anleitung: docs/TESTUMGEBUNG.md im Deploymentcenter-Repo.
set -euo pipefail
DC_TESTKIT_VERSION="1.0.0"
# Fassung dieses Skripts. MUSS bei jeder Aenderung hochgezaehlt werden.
#
# Warum das eine Regel und keine Empfehlung ist: Das Skript liegt auf jeder
# Testmaschine als Kopie. Bleibt die Zahl stehen, meldet eine Maschine mit dem
# Stand von vorletzter Woche dieselbe Fassung wie eine frisch bezogene - und
# niemand kann erkennen, warum derselbe Aufruf hier scheitert und dort nicht.
# Genau so ist es passiert: doctor meldete auf einer Maschine "Bereit", waehrend
# die ausgelieferte Fassung die fehlenden Rechte des Tokens laengst angemeckert
# haette. Dieselbe Falle, in die vorher die Serverfassung getappt ist (2.0.0 in
# der Konfiguration, waehrend 2.7.0 lief).
DC_TESTKIT_VERSION="1.1.0"
# ---------------------------------------------------------------------------
# Konfiguration
@@ -35,11 +47,36 @@ DC_TESTKIT_VERSION="1.0.0"
CONFIG_FILE="${DC_TESTKIT_CONFIG:-$HOME/.config/dc-testkit/config}"
# Die Datei wird mit "." gelesen, ihre Zeilen sind also gewoehnliche
# Zuweisungen - und die ueberschrieben, was in der Umgebung stand. Damit war
# die Rangfolge oben genau verkehrt herum: "DC_PROJECT=x dc-testkit report"
# meldete stillschweigend in das Projekt aus der Datei. Auf einer Maschine mit
# mehreren Projekten ist das die teuerste Art von Fehler - es geht nichts
# kaputt, es landet nur alles am falschen Ort, und niemand sieht es.
#
# Deshalb: vorher merken, was in der Umgebung stand, hinterher wieder
# durchsetzen. Leere Werte zaehlen als "nicht gesetzt" - ein "DC_TOKEN=" in
# der Umgebung soll das Token aus der Datei nicht loeschen.
DC_KONFIGURIERBAR='DC_URL DC_TOKEN DC_PROJECT DC_CHANNEL DC_INSTANCE DC_DISPLAY
DC_PROJECT_DIR DC_MIN_SERVER DC_TESTKIT_URL DC_TESTKIT_STATE'
for _dc_name in $DC_KONFIGURIERBAR; do
printf -v "_dcenv_$_dc_name" '%s' "${!_dc_name-}"
done
if [ -f "$CONFIG_FILE" ]; then
# shellcheck disable=SC1090
. "$CONFIG_FILE"
fi
for _dc_name in $DC_KONFIGURIERBAR; do
_dc_gemerkt="_dcenv_$_dc_name"
if [ -n "${!_dc_gemerkt-}" ]; then
printf -v "$_dc_name" '%s' "${!_dc_gemerkt}"
fi
done
unset _dc_name _dc_gemerkt
DC_URL="${DC_URL:-https://dc.mhdf.de}"
DC_URL="${DC_URL%/}"
DC_PROJECT="${DC_PROJECT:-}"
@@ -53,11 +90,35 @@ DC_CHANNEL="${DC_CHANNEL:-prod}"
# doctor kannte sie nicht und meldete "Bereit" auf einem zu alten Server.
DC_MIN_SERVER="${DC_MIN_SERVER:-2.6.1}"
# Bezugsquelle dieses Skripts. Daraus zieht doctor die ausgelieferte Fassung
# und selfupdate den neuen Stand. Die Adresse haengt am DC_URL der Instanz:
# Wer gegen die Testinstanz arbeitet, soll auch deren Testkit bekommen.
DC_TESTKIT_URL="${DC_TESTKIT_URL:-$DC_URL/testkit/dc-testkit.sh}"
# Die Maschinenkennung entscheidet, welchen DEV-Schluessel der Server
# heraussucht. Sie muss ueber Laeufe hinweg gleich bleiben - sonst sammelt sich
# je Lauf ein weiterer Schluessel an, bis die Grenze je Projekt greift.
DC_INSTANCE="${DC_INSTANCE:-$(hostname -s 2>/dev/null || hostname)}"
# Virtuelles Display dieses Projekts, fest vergeben (TESTUMGEBUNG.md 5.5).
# Ohne Vorgabe: keines - dann bleibt die Oberflaechenpruefung ausgeschaltet,
# statt sich eine Nummer zu nehmen. Genau das macht "xvfb-run -a", und es
# beginnt bei :99, also auf der Nummer eines anderen Projekts.
DC_DISPLAY="${DC_DISPLAY:-}"
# Verzeichnis des Projekt-Repos auf dieser Maschine. Ist es gesetzt und steht
# kein DC_TOKEN fest, holt sich das Testkit das Werkbank-Token von dort - aus
# der .mcp.json, in der es fuer den MCP-Adapter ohnehin schon liegt.
#
# Das ist der Ablageort, den die Projekte bereits benutzen: eine Datei je
# Projekt, per .gitignore ausgeschlossen, mit dem Token in env.DC_TOKEN. Es ein
# zweites Mal in ~/.config/dc-testkit zu hinterlegen hiesse, dasselbe Geheimnis
# an zwei Orten zu pflegen - und beim naechsten Wechsel einen davon zu
# vergessen. Genau das war auf der Test-VM passiert: In der Testkit-Konfiguration
# lag noch ein altes Laufzeit-Token, waehrend das richtige Werkbank-Token im
# Projekt langst ein anderes war.
DC_PROJECT_DIR="${DC_PROJECT_DIR:-}"
STATE_ROOT="${DC_TESTKIT_STATE:-$HOME/.local/state/dc-testkit}"
# ---------------------------------------------------------------------------
@@ -75,6 +136,13 @@ note() { printf '%s\n' "$*"; }
step() { printf '%s==>%s %s\n' "$C_BLUE" "$C_RESET" "$*"; }
ok() { printf '%s ok%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
warn() { printf '%swarn%s %s\n' "$C_YELLOW" "$C_RESET" "$*" >&2; }
# Wie warn, aber auf stdout. Fuer doctor: Sein Bericht ist das Ergebnis des
# Befehls, kein Fehlerkanal - das Ergebnis traegt der Rueckgabewert. Ueber SSH
# werden stdout und stderr getrennt uebertragen und beim Aufrufer verschraenkt;
# die Befunde standen dann unter der falschen Ueberschrift. Genau so liest ein
# Pruefagent den Bericht - und ordnet die Zeile dem falschen Schritt zu.
befund() { printf '%swarn%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; }
dim() { printf '%s%s%s\n' "$C_DIM" "$*" "$C_RESET"; }
die() {
@@ -220,8 +288,83 @@ require_project() {
[ -n "$DC_PROJECT" ] || die "Kein Projekt gesetzt. DC_PROJECT=<slug> oder --project <slug>."
}
# Liest env.DC_TOKEN aus der .mcp.json eines Projektverzeichnisses.
#
# Gesucht wird an den ueblichen Stellen: im Verzeichnis selbst und eine Ebene
# tiefer - Projekte legen die Datei mal neben die .sln, mal in den Unterordner
# mit dem gleichnamigen Projekt. Ein Platzhalter wie ${DC_TOKEN} zaehlt nicht
# als Token; sonst laeuft der Aufruf mit einer Zeichenkette los, die der Server
# nur mit 401 beantworten kann.
token_aus_projekt() {
local wurzel="$1" datei
for datei in "$wurzel/.mcp.json" "$wurzel"/*/.mcp.json; do
[ -f "$datei" ] || continue
python3 -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
}
# Wird einmal in main() aufgerufen, bevor irgendein Befehl laeuft - damit auch
# doctor dasselbe Token sieht wie jeder andere Aufruf. Setzt DC_TOKEN_QUELLE
# fuer die Auskunft: Wo etwas herkommt, gehoert zu einem Bericht dazu.
DC_TOKEN_QUELLE=''
token_aufloesen() {
if [ -n "$DC_TOKEN" ]; then
DC_TOKEN_QUELLE="Konfiguration/Umgebung"
return 0
fi
[ -n "$DC_PROJECT_DIR" ] || return 0
DC_TOKEN="$(token_aus_projekt "$DC_PROJECT_DIR" || true)"
[ -n "$DC_TOKEN" ] && DC_TOKEN_QUELLE="$DC_PROJECT_DIR/.mcp.json"
return 0
}
require_token() {
[ -n "$DC_TOKEN" ] || die "Kein Token gesetzt. DC_TOKEN in $CONFIG_FILE hinterlegen."
[ -n "$DC_TOKEN" ] && return 0
# Die Meldung nennt beide Wege und sagt ausdruecklich, warum die Datei
# fehlen KANN, ohne dass etwas kaputt ist. Ohne diesen Satz sucht der
# Naechste den Fehler im Repo, im Netz oder in seinen Rechten - und findet
# keinen, weil es keinen gibt.
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 dieses Projekts muss von Hand hierher gebracht\n' >&2
printf 'werden - es steht in der .mcp.json des Projekts auf dem Arbeitsplatz.\n' >&2
else
printf 'Zwei Wege, einer genuegt:\n\n' >&2
printf ' DC_PROJECT_DIR=<pfad zum Projekt-Repo> - Token aus dessen .mcp.json\n' >&2
printf ' DC_TOKEN=<token> in %s\n' "$CONFIG_FILE" >&2
fi
exit 1
}
state_dir() {
@@ -294,6 +437,285 @@ has_scope() {
return 1
}
# ---------------------------------------------------------------------------
# Der eigene Stand
#
# Das Skript liegt auf jeder Testmaschine als Kopie und altert dort still vor
# sich hin. Genau daran ist die Rechtepruefung in doctor einmal gescheitert:
# Sie war laengst geschrieben, aber auf der Maschine lag der Stand von davor -
# und weil beide dieselbe Fassung meldeten, sah es niemand.
# ---------------------------------------------------------------------------
# Wo liegt dieses Skript wirklich? readlink loest einen Verweis in ~/bin auf -
# sonst ersetzte selfupdate den Verweis und nicht die Datei dahinter.
own_path() {
local p="$0"
if command -v readlink >/dev/null 2>&1 && readlink -f "$p" >/dev/null 2>&1; then
readlink -f "$p"
return 0
fi
case "$p" in
/*) printf '%s\n' "$p" ;;
*) printf '%s/%s\n' "$(pwd)" "$p" ;;
esac
}
# Fassung aus einer Skriptdatei lesen - der eigenen oder einer bezogenen.
testkit_version_of() {
sed -n 's/^DC_TESTKIT_VERSION="\([^"]*\)".*/\1/p' "$1" | head -n 1
}
# Holt das ausgelieferte Skript nach $1.
# 0 = da und plausibel, 1 = Bezug fehlgeschlagen, 2 = das ist kein Testkit
#
# Der Plausibilitaetstest ist nicht ueberflaessig: Faellt die Adresse aus oder
# steht dort eine Anmeldeseite, schriebe der Bezug eine HTML-Seite in die
# Datei. Danach laesst sich nichts mehr aufrufen - auch nicht das selfupdate,
# das den Schaden beheben koennte.
fetch_testkit() {
local ziel="$1"
curl -fsSL --max-time 60 "$DC_TESTKIT_URL" -o "$ziel" 2>/dev/null || return 1
head -n 1 "$ziel" | grep -q '^#!' || return 2
grep -q '^DC_TESTKIT_VERSION=' "$ziel" || return 2
return 0
}
cmd_selfupdate() {
local pruefen=0 erzwingen=0
while [ $# -gt 0 ]; do
case "$1" in
--pruefen|--check) pruefen=1 ;;
--force) erzwingen=1 ;;
*) die "Unbekannter Schalter: $1" ;;
esac
shift
done
local ziel tmp neu rc=0
ziel="$(own_path)"
step "Bezug von $DC_TESTKIT_URL"
# Die Zwischendatei muss neben dem Ziel liegen: nur im selben Dateisystem
# ist das abschliessende mv ein Umbenennen und damit unteilbar. Ueber eine
# Dateisystemgrenze hinweg kopiert mv Byte fuer Byte - und wer das Skript
# in genau dem Moment aufruft, bekommt eine halbe Datei.
tmp="$ziel.neu.$$"
fetch_testkit "$tmp" || rc=$?
case $rc in
1) rm -f "$tmp"; die "Bezug fehlgeschlagen - erreichbar? $DC_TESTKIT_URL" ;;
2) rm -f "$tmp"; die "Dort liegt kein Testkit. Nichts veraendert." ;;
esac
neu="$(testkit_version_of "$tmp")"
if cmp -s "$tmp" "$ziel"; then
rm -f "$tmp"
ok "bereits aktuell (${neu:-?})"
return 0
fi
# In welche Richtung geht der Unterschied? Davon haengt alles Weitere ab -
# der Rat genauso wie die Frage, ob ueberhaupt ersetzt werden darf. Eine
# aeltere Fassung ueber eine neuere zu ziehen ist fast immer ein Versehen:
# typisch der Fall, dass jemand im Repo am Skript arbeitet und dem Rat
# "beheben mit selfupdate" folgt. Rueckgaengig machen laesst sich das
# nicht, wenn die Aenderung noch nirgends sonst liegt.
local abstieg=0
version_lt "${neu:-0}" "$DC_TESTKIT_VERSION" || abstieg=$?
local gleiche_nummer=0
[ "$DC_TESTKIT_VERSION" = "${neu:-}" ] && gleiche_nummer=1
if [ "$pruefen" = '1' ]; then
rm -f "$tmp"
if [ "$gleiche_nummer" = '1' ]; then
warn "gleiche Fassung ($DC_TESTKIT_VERSION), aber anderer Inhalt"
note " Entweder ist diese Kopie von Hand geaendert worden, oder beim"
note " Ausliefern wurde DC_TESTKIT_VERSION nicht hochgezaehlt."
return 1
fi
if [ "$abstieg" = '0' ]; then
ok "neuer als ausgeliefert ($DC_TESTKIT_VERSION > ${neu:-?}) - Entwicklungsstand"
note " Kein selfupdate noetig; es holte die aeltere Fassung zurueck."
return 0
fi
warn "veraltet: hier laeuft $DC_TESTKIT_VERSION, ausgeliefert wird ${neu:-?}"
note " Beziehen mit: dc-testkit selfupdate"
return 1
fi
if [ "$erzwingen" != '1' ]; then
if [ "$gleiche_nummer" = '1' ]; then
rm -f "$tmp"
warn "Gleiche Fassung ($DC_TESTKIT_VERSION), aber anderer Inhalt."
die "Welche der beiden neuer ist, sagt hier nichts. Wenn du es weisst: --force"
fi
if [ "$abstieg" = '0' ]; then
rm -f "$tmp"
warn "Ausgeliefert wird ${neu:-?} - hier laeuft bereits $DC_TESTKIT_VERSION."
die "Das waere ein Rueckschritt. Wenn das gewollt ist: --force"
fi
fi
if [ ! -w "$ziel" ]; then
rm -f "$tmp"
die "Nicht beschreibbar: $ziel"
fi
chmod +x "$tmp" 2>/dev/null || true
# Eine Sicherung, bevor etwas ersetzt wird. Der Rueckweg muss ohne Netz
# gehen: Wer sich gerade eine kaputte Fassung geholt hat, kann sie nicht
# noch einmal herunterladen, um das rueckgaengig zu machen.
cp -p "$ziel" "$ziel.vorher" 2>/dev/null || true
mv "$tmp" "$ziel"
ok "$DC_TESTKIT_VERSION -> ${neu:-?}"
dim " Vorherige Fassung: $ziel.vorher"
}
# ---------------------------------------------------------------------------
# Oberflaechenpruefung
#
# Der generische Teil des Verfahrens aus TESTUMGEBUNG.md 5.8: ein eigenes
# Display starten, aufnehmen, wieder abraeumen. Was dazwischen geklickt wird,
# bleibt projektspezifisch und gehoert nicht hierher.
#
# Warum ueberhaupt gekapselt: Drei Fehler wiederholen sich sonst in jedem
# Eigenbau-Skript - DISPLAY nicht gesetzt (dann landet alles auf :0, dem
# echten Desktop), "pkill Xvfb" ohne Nummer (das erschlaegt die Sitzungen der
# anderen Projekte), und Dateinamen ohne Projektpraefix (zwei Laeufe
# ueberschreiben sich gegenseitig die Aufnahmen).
# ---------------------------------------------------------------------------
require_display() {
[ -n "$DC_DISPLAY" ] \
|| die "Kein Display gesetzt. DC_DISPLAY=:<nr> in $CONFIG_FILE - die Nummer steht in TESTUMGEBUNG.md 7."
case "$DC_DISPLAY" in
:0|:1)
die "$DC_DISPLAY ist der echte Desktop bzw. der Anmeldebildschirm. Nimm die Nummer deines Projekts."
;;
:[0-9]*) ;;
*) die "DC_DISPLAY muss die Form :<nr> haben, nicht \"$DC_DISPLAY\"." ;;
esac
}
display_nr() {
printf '%s' "${DC_DISPLAY#:}"
}
# Laeuft auf diesem Display ein Xvfb? 0 = ja.
display_laeuft() {
pgrep -f "Xvfb $DC_DISPLAY" >/dev/null 2>&1
}
# Gibt es den Socket, aber keinen Prozess? Dann ist die Nummer scheinbar
# belegt und in Wahrheit frei - das Ueberbleibsel eines hart beendeten Xvfb.
display_verwaist() {
[ -e "/tmp/.X11-unix/X$(display_nr)" ] && ! display_laeuft
}
cmd_display() {
local aktion="${1:-status}"
require_display
case "$aktion" in
status)
step "Display $DC_DISPLAY"
if display_laeuft; then
ok "belegt - $(pgrep -af "Xvfb $DC_DISPLAY" | head -1)"
elif display_verwaist; then
befund "verwaister Socket /tmp/.X11-unix/X$(display_nr) - kein Prozess dazu"
note " Aufraeumen: sudo rm -f /tmp/.X11-unix/X$(display_nr)"
return 1
else
ok "frei"
fi
step "Belegt insgesamt"
ls /tmp/.X11-unix/ 2>/dev/null | tr '\n' ' ' | sed 's/^/ Sockets: /'
printf '\n'
pgrep -af Xvfb 2>/dev/null | sed 's/^/ /' || note " kein Xvfb"
;;
start)
require_project
local geometrie="${2:-1600x1000x24}"
if display_laeuft; then
ok "Display $DC_DISPLAY laeuft bereits"
return 0
fi
if display_verwaist; then
die "Socket /tmp/.X11-unix/X$(display_nr) ohne Prozess. Erst aufraeumen (sudo rm -f), sonst startet Xvfb nicht."
fi
command -v Xvfb >/dev/null 2>&1 || die "Xvfb ist nicht installiert."
step "Display $DC_DISPLAY starten ($geometrie)"
Xvfb "$DC_DISPLAY" -screen 0 "$geometrie" >/dev/null 2>&1 &
sleep 2
display_laeuft || die "Xvfb ist nicht hochgekommen. Ist $DC_DISPLAY anderweitig belegt?"
ok "laeuft - in eigenen Aufrufen: export DISPLAY=$DC_DISPLAY"
;;
stop)
if ! display_laeuft; then
ok "Display $DC_DISPLAY laeuft nicht"
return 0
fi
# Mit Nummer, nie ohne: "pkill Xvfb" beendet die Sitzungen aller
# anderen Projekte auf dieser Maschine gleich mit.
step "Display $DC_DISPLAY beenden"
pkill -f "Xvfb $DC_DISPLAY" 2>/dev/null || true
sleep 1
display_laeuft && die "Xvfb auf $DC_DISPLAY laeuft weiter."
ok "beendet"
;;
*)
die "Aufruf: dc-testkit display [status|start [BxHxT]|stop]"
;;
esac
}
cmd_shot() {
require_project
require_display
local name="${1:-}"
[ -n "$name" ] || die "Aufruf: dc-testkit shot <name> (z.B. dc-testkit shot 1_start)"
display_laeuft || die "Auf $DC_DISPLAY laeuft kein Xvfb. Erst: dc-testkit display start"
command -v import >/dev/null 2>&1 || die "ImageMagick fehlt (import)."
# Projektpraefix ist Pflicht, nicht Geschmack: /tmp gehoert allen, und
# /tmp/1_start.png ueberschreibt die Aufnahme des Nachbarprojekts.
local ziel="/tmp/${DC_PROJECT}_${name}.png"
DISPLAY="$DC_DISPLAY" import -window root "$ziel" \
|| die "Aufnahme fehlgeschlagen. Zeichnet auf $DC_DISPLAY ueberhaupt etwas?"
ok "$ziel"
}
# ---------------------------------------------------------------------------
# doctor - laeuft hier ueberhaupt etwas?
#
@@ -309,7 +731,7 @@ cmd_doctor() {
if command -v "$tool" >/dev/null 2>&1; then
ok "$tool $(command -v "$tool")"
else
warn "$tool fehlt - ohne das geht nichts"
befund "$tool fehlt - ohne das geht nichts"
failed=1
fi
done
@@ -326,18 +748,24 @@ cmd_doctor() {
# Server wirklich braucht, scheiterte danach mit "kennt
# /api/license/v1/dev-key noch nicht".
if [ -z "$server_version" ]; then
warn "der Server nennt keine Fassung - $DC_MIN_SERVER nicht pruefbar"
befund "der Server nennt keine Fassung - $DC_MIN_SERVER nicht pruefbar"
else
version_lt "$server_version" "$DC_MIN_SERVER"
case $? in
# Der Rueckgabewert MUSS mit || aufgefangen werden. Ohne das
# beendet "set -e" das Skript, sobald version_lt etwas anderes als
# 0 liefert - also ausgerechnet im Normalfall "Server ist neu
# genug" (Rueckgabewert 1). doctor stieg dann wortlos nach der
# Health-Pruefung aus, ohne Token und Projekt je anzusehen.
local vergleich=0
version_lt "$server_version" "$DC_MIN_SERVER" || vergleich=$?
case $vergleich in
0)
warn "zu alt: $server_version - gebraucht wird mindestens $DC_MIN_SERVER"
warn " Es fehlen: dc-testkit license (dev-key), der Bezug ueber /testkit/"
warn " und die Rechtepruefung. Der Server muss deployt werden."
befund "zu alt: $server_version - gebraucht wird mindestens $DC_MIN_SERVER"
befund " Es fehlen: dc-testkit license (dev-key), der Bezug ueber /testkit/"
befund " und die Rechtepruefung. Der Server muss deployt werden."
failed=1
;;
2)
warn "Fassung \"$server_version\" ist nicht auswertbar - $DC_MIN_SERVER ungeprueft"
befund "Fassung \"$server_version\" ist nicht auswertbar - $DC_MIN_SERVER ungeprueft"
;;
*)
ok "erfuellt die Mindestfassung $DC_MIN_SERVER"
@@ -345,13 +773,75 @@ cmd_doctor() {
esac
fi
else
warn "nicht erreichbar: $(api_error)"
befund "nicht erreichbar: $(api_error)"
failed=1
fi
# Der eigene Stand. Diese Pruefung gehoert VOR alles Weitere: Meldet ein
# veraltetes Testkit "Bereit", ist jede folgende Zeile dieses Berichts
# unter Vorbehalt - sie stammt aus einer Fassung, die niemand mehr liest.
step "Dieses Testkit"
dim " Fassung $DC_TESTKIT_VERSION - $(own_path)"
local tk_tmp tk_rc=0 tk_neu
tk_tmp="${TMPDIR:-/tmp}/dc-testkit.pruefung.$$"
fetch_testkit "$tk_tmp" || tk_rc=$?
if [ "$tk_rc" = '0' ]; then
tk_neu="$(testkit_version_of "$tk_tmp")"
if cmp -s "$tk_tmp" "$(own_path)"; then
ok "aktuell - entspricht der ausgelieferten Fassung ${tk_neu:-?}"
else
# Drei verschiedene Lagen, und nur eine davon ruft nach selfupdate.
# Sie zu verwechseln waere teuer: Wer im Repo am Skript arbeitet,
# haette auf den Rat "beheben mit selfupdate" hin seine eigene,
# noch nicht ausgelieferte Arbeit ueberschrieben.
local tk_cmp=0
version_lt "$DC_TESTKIT_VERSION" "${tk_neu:-0}" || tk_cmp=$?
case $tk_cmp in
0)
befund "veraltet: hier laeuft $DC_TESTKIT_VERSION, ausgeliefert wird ${tk_neu:-?}"
befund " Beheben mit: dc-testkit selfupdate"
failed=1
;;
1)
if [ "$DC_TESTKIT_VERSION" = "${tk_neu:-}" ]; then
# Der stillste Fall und deshalb der gefaehrlichste:
# gleiche Nummer, anderer Inhalt. Genau daran ist die
# Rechtepruefung einmal unbemerkt vorbeigelaufen.
befund "gleiche Fassung ($DC_TESTKIT_VERSION), aber anderer Inhalt"
befund " Entweder ist diese Kopie von Hand geaendert worden, oder beim"
befund " Ausliefern wurde DC_TESTKIT_VERSION nicht hochgezaehlt."
failed=1
else
ok "neuer als ausgeliefert ($DC_TESTKIT_VERSION > ${tk_neu:-?}) - Entwicklungsstand"
dim " Kein selfupdate: das holte die aeltere Fassung zurueck."
fi
;;
*)
befund "Fassungen nicht vergleichbar: $DC_TESTKIT_VERSION / ${tk_neu:-?}"
;;
esac
fi
else
befund "ausgelieferte Fassung nicht abrufbar - der eigene Stand bleibt ungeprueft"
dim " $DC_TESTKIT_URL"
fi
rm -f "$tk_tmp"
step "Token"
if [ -z "$DC_TOKEN" ]; then
warn "kein Token gesetzt (DC_TOKEN)"
befund "kein Werkbank-Token"
if [ -n "$DC_PROJECT_DIR" ]; then
befund " In $DC_PROJECT_DIR liegt keine .mcp.json mit DC_TOKEN."
befund " Das ist kein Fehler im Repo: .mcp.json ist per .gitignore"
befund " ausgeschlossen und kommt ueber Git nicht mit. Auf einer frisch"
befund " geklonten Maschine fehlt sie deshalb immer - das Token muss von"
befund " Hand hierher, aus der .mcp.json des Projekts auf dem Arbeitsplatz."
else
befund " Entweder DC_PROJECT_DIR auf das Projekt-Repo zeigen lassen"
befund " (Token aus dessen .mcp.json) oder DC_TOKEN in $CONFIG_FILE."
fi
failed=1
elif api GET /api/tokens/v1/whoami; then
# Fruehere Fassungen meldeten hier nur "gueltig - der Bugtracker
@@ -367,23 +857,39 @@ cmd_doctor() {
ok "gueltig - \"$tok_name\", Besitzerart $tok_owner, Projekt ${tok_project:-keins}"
dim " Rechte: $tok_scopes"
dim " Herkunft: ${DC_TOKEN_QUELLE:-unbekannt}"
if [ "$tok_workbench" != 'true' ]; then
warn "kein Werkbank-Token: Besitzerart ist $tok_owner, gebraucht wird dev_agent"
warn " Ein Laufzeit-Token darf nicht in fremde Projekte melden (TESTUMGEBUNG.md 4.2)."
befund "kein Werkbank-Token: Besitzerart ist $tok_owner, gebraucht wird dev_agent"
befund " Ein Laufzeit-Token darf nicht in fremde Projekte melden (TESTUMGEBUNG.md 4.2)."
failed=1
fi
for want in bugtracker:manage license:dev updateservice:read; do
if ! has_scope "$tok_scopes" "$want"; then
warn "Recht fehlt: $want"
befund "Recht fehlt: $want"
failed=1
fi
done
if [ -n "$tok_project" ] && [ -n "$DC_PROJECT" ] && [ "$tok_project" != "$DC_PROJECT" ]; then
warn "Token gehoert zu $tok_project, gearbeitet wird an $DC_PROJECT"
warn " Je Projekt ein eigenes Token - sonst ist die Herkunft einer Meldung nicht mehr ablesbar."
# Ein Token ohne Projektbindung rutschte hier bisher durch: geprueft
# wurde nur, ob eine vorhandene Bindung zum Projekt passt. Genau so ein
# Token lag auf der Test-VM - dev_agent, aber project_slug leer.
#
# Ohne Bindung ist das Token nicht eingeschraenkt, sondern
# uneingeschraenkt: Der Server leitet kein Heimatprojekt ab, also ist
# fuer ihn jede Meldung eine Heimmeldung. Damit faellt der ganze Schutz
# fuer Fremdmeldungen weg - keine Begruendungspflicht, keine Deckelung
# auf medium, kein Eingang in triage_external, kein reported_from.
if [ -z "$tok_project" ]; then
befund "Token ohne Projektbindung (project_slug ist leer)"
befund " Damit zaehlt JEDE Meldung als Heimmeldung: ohne Begruendungspflicht,"
befund " ohne Deckelung auf medium, direkt in die offene Liste des fremden"
befund " Projekts - und ohne nachweisbare Herkunft. Im WebUI nachtragen."
failed=1
elif [ -n "$DC_PROJECT" ] && [ "$tok_project" != "$DC_PROJECT" ]; then
befund "Token gehoert zu $tok_project, gearbeitet wird an $DC_PROJECT"
befund " Je Projekt ein eigenes Token - sonst ist die Herkunft einer Meldung nicht mehr ablesbar."
failed=1
fi
elif [ "$API_STATUS" = '404' ]; then
@@ -391,38 +897,83 @@ cmd_doctor() {
# der ausdrueckliche Hinweis, dass die Rechte ungeprueft sind. Still
# "gueltig" zu melden waere genau der Fehler, der hier behoben wird.
if api GET /api/bugtracker/v1/projects; then
warn "gueltig, aber die Rechte sind ungeprueft"
warn " /api/tokens/v1/whoami gibt es erst ab $DC_MIN_SERVER."
befund "gueltig, aber die Rechte sind ungeprueft"
befund " /api/tokens/v1/whoami gibt es erst ab $DC_MIN_SERVER."
else
warn "Token wird nicht angenommen: $(api_error)"
befund "Token wird nicht angenommen: $(api_error)"
failed=1
fi
else
warn "Token wird nicht angenommen: $(api_error)"
befund "Token wird nicht angenommen: $(api_error)"
failed=1
fi
step "Projekt"
if [ -z "$DC_PROJECT" ]; then
warn "kein Projekt gesetzt (DC_PROJECT)"
befund "kein Projekt gesetzt (DC_PROJECT)"
failed=1
elif [ -z "$DC_TOKEN" ]; then
# Ohne Token ist die Projektliste nicht abrufbar. Das stillschweigend
# zu ueberspringen liesse den Eindruck entstehen, das Projekt sei
# geprueft worden.
warn "$DC_PROJECT nicht pruefbar - dafuer wird ein Token gebraucht"
befund "$DC_PROJECT nicht pruefbar - dafuer wird ein Token gebraucht"
elif api GET /api/bugtracker/v1/projects; then
if printf '%s' "$API_BODY" | grep -q "\"$DC_PROJECT\""; then
ok "$DC_PROJECT ist im Deploymentcenter bekannt"
else
warn "$DC_PROJECT kommt in der Projektliste nicht vor"
befund "$DC_PROJECT kommt in der Projektliste nicht vor"
failed=1
fi
else
warn "Projektliste nicht abrufbar: $(api_error)"
befund "Projektliste nicht abrufbar: $(api_error)"
failed=1
fi
# Die Oberflaechenpruefung ist der zweite Grund, warum es diese Maschine
# gibt (5.8) - doctor sah sie bisher gar nicht an. Wer sie braucht, merkte
# das Fehlen erst mitten im Lauf, mit einem "XOpenDisplay failed" statt
# einer Auskunft.
step "Oberflaechenpruefung"
local gui_fehlt=''
for tool in Xvfb xdotool import convert; do
command -v "$tool" >/dev/null 2>&1 || gui_fehlt="$gui_fehlt $tool"
done
if [ -n "$gui_fehlt" ]; then
# Kein failed=1: Ein Projekt ohne Oberflaeche braucht davon nichts.
# Ein Befund waere hier eine Falschmeldung - aber schweigen darf das
# Werkzeug auch nicht.
note " nicht moeglich, es fehlt:$gui_fehlt"
note " Nur noetig, wenn dieses Projekt eine Oberflaeche hat."
else
ok "Xvfb, xdotool, ImageMagick vorhanden"
fi
if [ -z "$DC_DISPLAY" ]; then
dim " Kein DC_DISPLAY gesetzt - display/shot bleiben aus."
dim " Die Nummer dieses Projekts steht in TESTUMGEBUNG.md 7."
else
case "$DC_DISPLAY" in
:0|:1)
befund "DC_DISPLAY=$DC_DISPLAY ist der echte Desktop bzw. der Anmeldebildschirm"
befund " Darauf zu zeichnen stoert den Menschen, der dort arbeitet."
failed=1
;;
*)
if display_laeuft; then
ok "Display $DC_DISPLAY - belegt (vermutlich von einem eigenen Lauf)"
elif display_verwaist; then
befund "Display $DC_DISPLAY - verwaister Socket, kein Prozess dazu"
befund " Die Nummer wirkt belegt und ist frei. Aufraeumen:"
befund " sudo rm -f /tmp/.X11-unix/X${DC_DISPLAY#:}"
failed=1
else
ok "Display $DC_DISPLAY - frei"
fi
;;
esac
fi
step "Arbeitsplatz"
if [ -n "$DC_PROJECT" ]; then
dim " Zustand: $(state_dir)"
@@ -1254,7 +1805,8 @@ dc-testkit $DC_TESTKIT_VERSION - Testlaeufe gegen das Deploymentcenter
dc-testkit [--project <slug>] [--channel <kanal>] [--url <adresse>] <befehl>
Einrichten und pruefen
doctor Werkzeuge, Netz, Token und Projekt pruefen
doctor Werkzeuge, Netz, eigener Stand, Token und Projekt
selfupdate [--pruefen] Dieses Skript auf die ausgelieferte Fassung bringen
license [--ttl N] [--print] [--revoke]
DEV-Lizenzschluessel holen, verlaengern, zurueckgeben
agent [--force] update-agent herunterladen, Pruefsumme abgleichen
@@ -1281,6 +1833,10 @@ Bugtracker
confirm <id> [notiz] "der Fix eines anderen traegt" -> schliesst
unclaim <id> [grund] Uebernahme zurueckgeben
Oberflaeche pruefen (braucht DC_DISPLAY)
display [status|start|stop] Eigenes virtuelles Display
shot <name> Aufnahme nach /tmp/<projekt>_<name>.png
Aufraeumen
cleanup [--sandbox] Sandkasten weg, Schluessel zurueckgeben
@@ -1288,7 +1844,9 @@ Schweregrade: idea, wishlist, low, medium, high, critical
Typen: bug, feature ("idea" ist ein Schweregrad, kein Typ)
Konfiguration: $CONFIG_FILE
DC_URL, DC_TOKEN, DC_PROJECT, DC_CHANNEL, DC_INSTANCE
DC_URL, DC_TOKEN, DC_PROJECT, DC_CHANNEL, DC_INSTANCE, DC_DISPLAY
DC_PROJECT_DIR - Projekt-Repo; ohne DC_TOKEN wird es aus dessen
.mcp.json gelesen (env.DC_TOKEN)
EOF
}
@@ -1308,8 +1866,13 @@ main() {
local command="${1:-}"
[ $# -gt 0 ] && shift
token_aufloesen
case "$command" in
doctor) cmd_doctor "$@" ;;
selfupdate) cmd_selfupdate "$@" ;;
display) cmd_display "$@" ;;
shot) cmd_shot "$@" ;;
license) cmd_license "$@" ;;
agent) cmd_agent "$@" ;;
check) cmd_check "$@" ;;