Files
dogfather-universe/server/pruef-kopfleiste-farbe.mjs
T
DogFatherGit 077925ab25 Dreizehn rote Pruefungen, eine Ursache: openssl steht nicht im PATH
DER BEFUND

Im naechtlichen Lauf standen dreizehn Pruefungen mit derselben Zeile
rot:

    [uncaughtException] Error: spawnSync openssl ENOENT

Kein Programmfehler. Die Pruefungen brauchen einen https-Vorbau (der
Sitzungskeks des Hauses ist `secure` und kommt ueber http nicht an),
und dafuer erzeugen sie ein Wegwerf-Zertifikat mit openssl.

GEMESSEN, NICHT VERMUTET

  openssl liegt hier:   C:\Program Files\Git\mingw64\bin\openssl.exe
                        C:\Program Files\Git\usr\bin\openssl.exe
  im System-PATH steht: C:\Program Files\Git\cmd   (nur git.exe)
  im Benutzer-PATH:     nichts mit Git

Wer aus Git Bash startet, erbt die mingw-Pfade und merkt nie etwas.
Die Aufgabenplanung startet `node.exe` DIREKT, ohne Shell -- und
bekommt sie nicht. Bei mir gruen, nachts rot, und der Grund steht
nicht im Code.

Das Schlimmste daran ist nicht der Ausfall: Dreizehn dauerhaft rote
Zeilen in einer Notiz, die Filipe morgens liest, gewoehnen einem das
Hinsehen ab -- und decken dabei die echten Befunde zu.

DIE REPARATUR

`server/helfer-openssl.mjs` sucht openssl erst im PATH (ohne `where`
oder `which` -- beides sind selbst Programme und koennen genauso
fehlen), danach an den Orten, an denen es auf einem Windows-Rechner
mit Git wirklich liegt.

Dazu `zertifikatBauen(schluessel, zertifikat, host)`. Die acht
Aufrufformen im Haus waren identisch bis auf die Variablennamen
(schl/zert, schluesselDatei/zertDatei, zKey/zCrt, sk/zt, key/crt) --
eine Stelle traegt alle neunundzwanzig. Zwei Tage zuvor war eine
davon um ein `-addext` aermer als die anderen; gemerkt hat es
niemand, weil der Browser den fehlenden Alternativnamen erst bei
einer Weiterleitung anmahnt. Eine Stelle kann nicht von sich selbst
abweichen.

KEIN EINTRAG IN DEN SYSTEM-PATH. `mingw64\bin` enthaelt rund hundert
Programme mit Unix-Namen (`find`, `sort`, `link`), die gleichnamige
Windows-Befehle verdecken. Das fuer eine Pruefung zu aendern haette
an ganz anderen Stellen Fehler verursacht, die niemand hierher
zurueckverfolgt.

GEGENPROBE IN BEIDE RICHTUNGEN, mit dem PATH des Nachtlaufs:

  vorher (direkter Aufruf):  ABSTURZ: spawnSync openssl ENOENT
  nachher (ueber den Helfer): Zertifikat gebaut, 1236 Bytes

Und eine echte Pruefung unter denselben Bedingungen:

  PATH ohne mingw64 -> pruef-chat-neu-stelle
  63 Pruefungen, 0 Fehler, 0 Abstuerze

Genau diese Datei stand im Nachtlauf mit ENOENT rot.

EINE WACHE DAGEGEN

pruef-struktur prueft ab jetzt, dass niemand openssl wieder direkt
ruft -- der naechste merkt es dort und nicht erst in einem
Nachtlauf, den niemand liest. Drei Gegenproben.

Beim ersten Anlauf schlug sie auf ihre EIGENEN Probetexte an: Sie
liest alle Serverdateien, und dazu gehoert sie selbst. Die Texte
werden jetzt zusammengesetzt. Derselbe Selbsttreffer ist mir heute
schon zweimal passiert.

DER DRITTE AUSGANG BLEIBT, WO ER HINGEHOERT

`helfer-kachel-echtfarbe.mjs` faengt den Fehler weiterhin ab und
meldet `moeglich: false` mit Grund, statt abzubrechen. Eine
Sicherung abzuschaffen, weil ihr Anlass gerade behoben ist, ist der
Anfang des naechsten stillen Fehlschlags.

NEBENBEI: pruef-community-sicht

Sie war rot mit "ZU VIEL: reaktion.html" -- mein eigener Rueckstand
vom 28.09. Die Reaction steht als Kachel im Community-Bereich; die
Liste war nicht nachgezogen. Jetzt 10 / 0.

Die Liste bleibt bewusst von Hand gepflegt: Sie ist eine ABSICHT,
keine Ableitung. Aus rechte.js gelesen verglichen sich zwei Kopien
derselben Quelle -- immer gruen, nie ein Beweis.

GEPRUEFT
  pruef-struktur         ALLES IN ORDNUNG (mit der neuen Wache)
  pruef-community-sicht  10 / 0
  pruef-chat-neu-stelle  63 / 0  (mit dem PATH des Nachtlaufs)
  pruef-notizen, -teamlage-karten, -werdegang: EXIT 0, keine Abstuerze
  mess-quer              EXIT 0
  29 Dateien: node --check auf allen, kein direkter Aufruf mehr
2026-09-29 16:14:13 +02:00

225 lines
10 KiB
JavaScript

/* =====================================================================
JEDE SEITE TRÄGT IHRE FARBE — auch ganz oben in der Leiste
=====================================================================
Filipe am 22.09.2026:
„je nachdem auf welcher seite ich bin wechseln gaaanz oben in der
leiste gewissene symbole und sachen mit ... ich will das auf
jeder seite ... nicht mehr wie auf screen3 sondern genau gleich
wie da."
Sein Vorbild war die Highlights-Seite. Und das ist der Schlüssel zum
Befund: Highlights ist `bereich.html`, und `bereich.html` ist eine
von DREI Seiten, die `bereich.css` laden. Dort stand die Regel, die
aus `data-ton` eine Akzentfarbe macht.
Gemessen: `kopf.js` setzt `data-ton` am <body> auf jeder Seite. Auf
zweiunddreißig von fünfunddreißig las es niemand — die Farbe war da
und kam nicht an. Seit dem 22.09. steht die Regel in `start.css`,
und die liegt auf allen fünfunddreißig.
---------------------------------------------------------------------
WAS HIER GEMESSEN WIRD — und warum nicht der Quelltext
Ob eine CSS-Regel WIRKT, steht nicht in der Datei. `--akzent` könnte
von einer späteren Regel überschrieben werden, `data-ton` könnte
fehlen, das Haus könnte etwas anderes sagen. Deshalb ein echter
Browser und `getComputedStyle` — die Farbe, die auf dem Schirm
ankommt.
ZWEI SEITEN MIT VERSCHIEDENEN TÖNEN, und beide Richtungen:
* auf jeder kommt eine Farbe an
* und es ist nicht dieselbe
Ohne den zweiten Teil wäre eine Seite, die überall dieselbe Farbe
zeigt, ebenfalls grün — und genau das war der Zustand vorher.
===================================================================== */
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { request as httpAnfrage } from "node:http";
import { execFileSync } from "node:child_process";
import { createServer as httpsServer } from "node:https";
import { notbremse } from "./helfer-notbremse.mjs";
import { eigenerPort } from "./helfer-port.mjs";
import { zertifikatBauen } from "./helfer-openssl.mjs";
const CREW = "crew.dogfather-universe.com";
const PORT = await eigenerPort(import.meta, "pruef-kopfleiste-farbe");
const ordner = mkdtempSync(join(tmpdir(), "ws-kopffarbe-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = `${PORT}`;
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
notbremse(180_000, "pruef-kopfleiste-farbe");
await new Promise((r) => setTimeout(r, 900));
let fehler = 0, geprueft = 0, offen = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const unklar = (t) => { offen++; console.log(" -- " + t); };
/* ---------- Ein Mensch ------------------------------------------------- */
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
const k = randomBytes(32).toString("hex");
d.prepare("INSERT INTO einstellungen (schluessel,wert,geaendert,von) VALUES (?,?,?,NULL)"
+ " ON CONFLICT(schluessel) DO NOTHING").run("code_kennung_schluessel", k, jetzt);
{
const salt = randomBytes(16).toString("hex");
const hash = scryptSync("CODE-DOGI-0001", salt, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name,rolle,code_hash,code_salt,code_n,code_kennung,aktiv,erstellt)"
+ " VALUES (?,?,?,?,?,?,1,?)")
.run("Filipe", "admin", hash, salt, 32768,
createHmac("sha256", k).update("CODE-DOGI-0001").digest("hex"), jetzt);
}
d.close();
/* ---------- Der Vorbau ------------------------------------------------- */
const schl = join(ordner, "p.key"), zert = join(ordner, "p.crt");
zertifikatBauen(schl, zert, CREW);
const HP = PORT + 1;
const vorbau = httpsServer({ key: readFileSync(schl), cert: readFileSync(zert) }, (von, nach) => {
const w = httpAnfrage({ host: "127.0.0.1", port: PORT, path: von.url, method: von.method,
headers: { ...von.headers, host: `${CREW}:${HP}` } },
(a) => { nach.writeHead(a.statusCode || 502, a.headers); a.pipe(nach); });
von.pipe(w);
});
await new Promise((r) => vorbau.listen(HP, "127.0.0.1", r));
const SEITE = `https://${CREW}:${HP}`;
const pw = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await pw.chromium.launch({
args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, "--ignore-certificate-errors"] });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
const seite = await ctx.newPage();
const meckern = [];
seite.on("pageerror", (f) => meckern.push(String(f).slice(0, 140)));
console.log("=== Jede Seite traegt ihre Farbe ===");
console.log("");
await seite.goto(`${SEITE}/workspace/`, { waitUntil: "networkidle" });
await seite.click('.rolle[data-rolle="admin"]');
await seite.fill("#code", "CODE-DOGI-0001");
await seite.click("button[type=submit]");
await seite.waitForTimeout(900);
/* WELCHE SEITEN GEMESSEN WERDEN, STEHT NICHT HIER.
-----------------------------------------------------------------
Die Kacheln der Startseite sagen, welche Seiten es gibt und welchen
Ton jede hat. Eine Liste in dieser Datei waere die, die beim
naechsten Umbau eine Seite nennt, die es nicht mehr gibt -- oder,
schlimmer, die neue nicht kennt. */
await seite.goto(`${SEITE}/workspace/start.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(1800);
/* DIE KACHEL IST DAS <li>, DER VERWEIS LIEGT DARIN.
Beim ersten Anlauf stand hier `.kachel[href]` -- das trifft nichts,
und die Pruefung meldete "0 Kacheln". Das war richtig gemeldet und
trotzdem ein Fehler von mir: Die Struktur steht in start.js
(`li.kachel` mit `a.kachel__link` darin), nachgesehen statt
geraten. */
const kacheln = await seite.evaluate(() =>
[...document.querySelectorAll('li.kachel[data-ton]')]
.map((li) => ({
ziel: li.querySelector('a')?.getAttribute("href") || null,
ton: li.dataset.ton || null,
}))
.filter((x) => x.ziel && /^[a-z-]+\.html$/.test(x.ziel) && x.ton !== null));
ok(kacheln.length >= 4,
`${kacheln.length} Kacheln mit eigenem Ton auf der Startseite gefunden`);
/* Zwei mit VERSCHIEDENEN Toenen -- ohne Unterschied liesse sich nicht
zeigen, dass die Farbe der Seite folgt und nicht der Leiste. */
const nachTon = new Map();
for (const x of kacheln) if (!nachTon.has(x.ton)) nachTon.set(x.ton, x);
const auswahl = [...nachTon.values()].slice(0, 3);
ok(auswahl.length >= 2,
`davon ${auswahl.length} mit verschiedenen Toenen (${auswahl.map((x) => x.ton).join(", ")})`);
const gemessen = [];
for (const x of auswahl) {
await seite.goto(`${SEITE}/workspace/${x.ziel}`, { waitUntil: "networkidle" });
await seite.waitForTimeout(1500);
const w = await seite.evaluate(() => {
const koerper = getComputedStyle(document.body);
const leiste = document.querySelector(".kopfleiste");
return {
adresse: location.pathname.split("/").pop(),
ton: document.body.dataset.ton || null,
akzent: koerper.getPropertyValue("--akzent").trim(),
tonFarbe: koerper.getPropertyValue("--ton").trim(),
leisteDa: !!leiste,
/* Die Kante unter der Leiste -- gemessen an dem, was wirklich
gezeichnet wird, nicht an der Regel im Quelltext. */
kante: leiste ? getComputedStyle(leiste, "::before").backgroundImage : "",
};
});
gemessen.push(w);
}
console.log("");
console.log("--- Was auf den Seiten ankommt ---");
for (const w of gemessen) {
console.log(` ${w.adresse.padEnd(18)} Ton ${String(w.ton).padStart(2)}`
+ ` --akzent ${w.akzent}`);
}
ok(gemessen.length >= 2 && gemessen.every((w) => w.ton !== null),
`auf allen ${gemessen.length} Seiten steht ein Ton am Koerper`);
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG. `every()` auf einem leeren Feld
liefert `true` -- beim ersten Lauf waren drei Zeilen hier gruen,
ohne dass eine einzige Seite gemessen worden war. Genau der Fehler,
vor dem die Hausregel warnt, und er ist mir trotzdem passiert. */
ok(gemessen.length >= 2 && gemessen.every((w) => w.akzent && w.akzent !== "none"),
`auf allen ${gemessen.length} kommt daraus eine Akzentfarbe an`);
/* DER ENTSCHEIDENDE VERGLEICH. Vorher war --akzent auf 32 von 35
Seiten die feste Hausfarbe -- also ueberall dieselbe. */
const verschieden = new Set(gemessen.map((w) => w.akzent)).size;
ok(gemessen.length >= 2 && verschieden === gemessen.length,
`und sie ist auf jeder Seite eine andere (${verschieden} von ${gemessen.length})`);
ok(gemessen.length >= 2 && gemessen.every((w) => w.leisteDa),
`die Kopfleiste steht auf allen ${gemessen.length} gemessenen Seiten`);
ok(gemessen.length >= 2 && gemessen.every((w) => /gradient/i.test(w.kante || "")),
"und traegt unten eine Kante in der Farbe der Seite");
/* GEGENPROBE: Eine Seite OHNE Ton darf die Hausfarbe behalten. Eine
Regel, die auch dort zuschlaegt, wuerde eine Farbe erfinden. */
console.log("");
console.log("--- Gegenprobe ---");
{
const ohne = await seite.evaluate(() => {
const vorher = document.body.dataset.ton;
delete document.body.dataset.ton;
const a = getComputedStyle(document.body).getPropertyValue("--akzent").trim();
if (vorher !== undefined) document.body.dataset.ton = vorher;
return a;
});
const mit = gemessen.at(-1)?.akzent || null;
ok(!!ohne && !!mit && ohne !== mit,
`ohne Ton gilt wieder die Hausfarbe (${ohne} statt ${mit})`);
}
ok(meckern.length === 0, `der Browser meldet nichts (${meckern[0] || "still"})`);
/* ---------- Ende ------------------------------------------------------ */
await browser.close();
await new Promise((r) => vorbau.close(r));
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt manchmal */ }
console.log("");
console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}`
+ ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`);
process.exit(fehler === 0 ? 0 : 1);