Map-Editor Selbst-Update gehärtet (t_1457e935): KEIN Credential-Dialog mehr — git-Läufe nie interaktiv — Root-Cause: der systemd-User-Manager exportiert SSH_ASKPASS (ksshaskpass); ein nackter git-Spawn im Editor hat damit mitten in der Update-Prüfung das KDE-Login-Fenster geöffnet, und der Credential-Store (~/.git-credentials) war leer (0 Bytes). Fix (Belt AND Braces, EditorUpdater.spawn_argv): JEDE git-Ausführung läuft über /usr/bin/env mit GIT_TERMINAL_PROMPT=0 + GIT_ASKPASS=/bin/true + HOME aus der eigenen Umgebung (kein Hardcode), dazu -c credential.helper= (Helper-Reset, sonst öffnet z. B. der Windows-Credential-Manager sein GUI) + -c credential.helper=store + core.askPass=/bin/true (Askpass-Fallback tot, auch ohne env-Wrapper). Askpass-Präzedenz am PC verifiziert: core.askPass/GIT_ASKPASS schlagen geerbtes SSH_ASKPASS — Fake-Askpass startet nicht, sauberes Authentication failed Exit 128 statt GUI. Neuer Status auth_failed mit klarem UI-Text („Updates nicht prüfbar — Zugangsdaten fehlen (kein Login-Dialog geöffnet)“), Auth-Erkennung pure (looks_like_auth_error: could-not-read-Username / Authentication failed / terminal prompts disabled / Unauthorized — Connection refused ist KEIN Auth-Fehler); pull-Fehler bekommen denselben Zusatz-Hinweis. Credential-Store am PC befüllt (Forgejo-Token für 127.0.0.1:13020, chmod 600) — ls-remote/pull/ls-remote-push laufen stimmlos. Tests: test_updater +10 Checks (auth-Erkennung 4, spawn_argv-Zusammensetzung 6) → 47/47 ALL OK; komplette Suite 12/13 grün (test_testrun: 1/14 FAILED — PRE-EXISTING, per git-stash-Verfahren verifiziert, Ursache unabhängig von diesem Diff, Follow-up-Karte folgt). docs/tasten.md: Abschnitt Updates um „Zugangsdaten fehlen“ ergänzt.
This commit is contained in:
parent
f2b4f6a824
commit
73847ed1ce
4 changed files with 123 additions and 13 deletions
|
|
@ -95,6 +95,12 @@ kein Programmdownload, nur „Versionsstände holen“:
|
|||
- **Offline / Tunnel weg:** kein Banner, keine Meldung — der Editor bleibt
|
||||
einfach so, wie er ist. Prüfen kann man trotzdem: die Meldung sagt es dann
|
||||
in der Statuszeile.
|
||||
- **Zugangsdaten fehlen** (Karte t_1457e935): die Update-Prüfung läuft
|
||||
bewusst NIE interaktiv — es öffnet sich **niemals** ein Login-Fenster im
|
||||
Editor. Stattdessen kommt in der Statuszeile „Updates nicht prüfbar —
|
||||
Zugangsdaten fehlen“. Die Zugangsdaten liegen im Git-Credential-Store
|
||||
(`~/.git-credentials`, Host `127.0.0.1:13020`); wenn sie fehlen, füllt sie
|
||||
ein Eintrag der Form `http://<user>:<token>@127.0.0.1:13020` nach.
|
||||
- **Fehlgeschlagener Import:** bricht das Update mit Klartext-Meldung ab —
|
||||
der Editor läuft UNVERÄNDERT weiter (kein Neustart in einen kaputten Stand,
|
||||
Lektion 17.10. class_name-Cache).
|
||||
|
|
|
|||
|
|
@ -20,23 +20,79 @@ extends RefCounted
|
|||
## Map-Ansicht sehen.
|
||||
## - Offline / Tunnel weg → status "unreachable" → die UI bleibt still
|
||||
## (kein Banner, kein Fehler-Geballer).
|
||||
## - Git darf NIE interaktiv werden (Karte t_1457e935): kein GUI-Credential-
|
||||
## Dialog mitten im Editor! Jeder git-Lauf läuft deshalb mit
|
||||
## credential.helper=store und abgeschaltetem Askpass (spawn_argv) —
|
||||
## fehlen die Zugangsdaten, endet der Lauf sofort mit einer klaren
|
||||
## Fehlermeldung (status "auth_failed"), nie mit einem Prompt oder Hang.
|
||||
## Hintergrund 17.10.: der systemd-User-Manager exportiert SSH_ASKPASS
|
||||
## (ksshaskpass) — ein nackter git-Spawn hat damit mitten im Editor das
|
||||
## Login-Fenster geöffnet. GIT_ASKPASS-/core.askPass-Override schlägt das
|
||||
## geerbte SSH_ASKPASS (verifiziert am PC, 17.10.).
|
||||
|
||||
const BRANCH := "main"
|
||||
const UNIT_NAME := "map-editor"
|
||||
|
||||
## Statuszeilen-Text für den auth_failed-Pfad (UI + Tests).
|
||||
const AUTH_ERROR_TEXT := "Updates nicht prüfbar — Zugangsdaten fehlen (kein Login-Dialog geöffnet)."
|
||||
|
||||
## Ein Git-Kommando im Projektordner ausführen. Rückgabe {ok, code, output}.
|
||||
|
||||
## Belt-and-braces (Karte t_1457e935): argv für einen NICHT-interaktiven
|
||||
## git-Lauf bauen. Rückgabe: [0] = Programm, Rest = Argumente.
|
||||
## - "-c credential.helper=" leert ERST alle geerbten Helfer (sonst öffnet
|
||||
## z. B. der Windows-Credential-Manager sein GUI-Fenster), danach
|
||||
## "-c credential.helper=store": ausschließlich der Credential-Store
|
||||
## (~/.git-credentials per HOME).
|
||||
## - POSIX: Spawn über /usr/bin/env mit GIT_TERMINAL_PROMPT=0 und
|
||||
## GIT_ASKPASS=/bin/true — das überstimmt auch ein GEERBTES SSH_ASKPASS
|
||||
## (KDE/GNOME-Login-Dialog!). HOME kommt aus der eigenen Umgebung
|
||||
## (OS.get_environment, kein Hardcode) und wird nur mitgegeben, wenn da.
|
||||
## - Fallback ohne env-Wrapper (env fehlt / kein HOME): trotzdem kein Prompt
|
||||
## möglich, weil core.askPass=/bin/true das Askpass-Fallback abschaltet.
|
||||
static func spawn_argv(git_exe: String, project_dir: String, args: PackedStringArray) -> PackedStringArray:
|
||||
var git_args := PackedStringArray(["-C", project_dir,
|
||||
"-c", "credential.helper=", "-c", "credential.helper=store"])
|
||||
if not OS.has_feature("windows"):
|
||||
git_args.append_array(PackedStringArray(["-c", "core.askPass=/bin/true"]))
|
||||
git_args.append_array(args)
|
||||
var home := OS.get_environment("HOME")
|
||||
if not OS.has_feature("windows") and FileAccess.file_exists("/usr/bin/env") \
|
||||
and not home.is_empty():
|
||||
var out := PackedStringArray(["/usr/bin/env",
|
||||
"GIT_TERMINAL_PROMPT=0", "GIT_ASKPASS=/bin/true", "HOME=" + home])
|
||||
out.append(git_exe)
|
||||
out.append_array(git_args)
|
||||
return out
|
||||
var out := PackedStringArray([git_exe])
|
||||
out.append_array(git_args)
|
||||
return out
|
||||
|
||||
|
||||
## Ein Git-Kommando im Projektordner ausführen — IMMER nicht-interaktiv
|
||||
## (spawn_argv). Rückgabe {ok, code, output} (output = stdout+stderr gemischt,
|
||||
## damit Fehlerklassifikation sehen kann, was git gesagt hat).
|
||||
static func git_run(git_exe: String, project_dir: String, args: PackedStringArray) -> Dictionary:
|
||||
var full := PackedStringArray(["-C", project_dir])
|
||||
full.append_array(args)
|
||||
var argv := spawn_argv(git_exe, project_dir, args)
|
||||
var exe := str(argv[0])
|
||||
var rest := argv.slice(1)
|
||||
var output: Array = []
|
||||
var code := OS.execute(git_exe, full, output, true)
|
||||
var code := OS.execute(exe, rest, output, true)
|
||||
var text := ""
|
||||
for line in output:
|
||||
text += str(line)
|
||||
return {"ok": code == 0, "code": code, "output": text}
|
||||
|
||||
|
||||
## Pure Prüfung (testbar): deutet die git-Ausgabe auf FEHLENDE Zugangsdaten?
|
||||
## (Askpass ist abgeschaltet — „could not read Username“ ist dann der
|
||||
## dokumentierte Nicht-Interaktiv-Fehler, kein Anlass für einen Retry.)
|
||||
static func looks_like_auth_error(output: String) -> bool:
|
||||
return output.contains("could not read Username") \
|
||||
or output.contains("Authentication failed") \
|
||||
or output.contains("terminal prompts disabled") \
|
||||
or output.contains("Unauthorized")
|
||||
|
||||
|
||||
## Git-Binary: Konfiguration (user://scopedvr_editor.cfg → [update]
|
||||
## git_command) gewinnt, sonst das Standard-"git" (PATH).
|
||||
static func git_exe(cfg: ConfigFile = null) -> String:
|
||||
|
|
@ -59,7 +115,7 @@ static func head_sha(git_exe: String, project_dir: String) -> String:
|
|||
|
||||
|
||||
## Remote-SHA per git ls-remote (cheap, lesend — keine lokalen Referenzen
|
||||
## verändert). "" = nicht erreichbar / Branch fehlt.
|
||||
## verändert). "" = nicht erreichbar / Branch fehlt / Credentials fehlen.
|
||||
static func remote_sha(git_exe: String, project_dir: String, branch: String) -> String:
|
||||
var r := git_run(git_exe, project_dir, PackedStringArray([
|
||||
"ls-remote", "origin", "refs/heads/%s" % branch]))
|
||||
|
|
@ -73,18 +129,30 @@ static func remote_sha(git_exe: String, project_dir: String, branch: String) ->
|
|||
|
||||
## Update-Check: lokaler HEAD vs. origin/main.
|
||||
## Rückgabe {status, local, remote, error} — status:
|
||||
## "update_available" | "up_to_date" | "unreachable" | "error"
|
||||
## "error" = kein Repo/git nicht da (Editor-spezifisches Problem)
|
||||
## "unreachable"= Origin nicht erreichbar (offline, Tunnel weg)
|
||||
## "update_available" | "up_to_date" | "unreachable" | "error" | "auth_failed"
|
||||
## "error" = kein Repo/git nicht da (Editor-spezifisches Problem)
|
||||
## "unreachable" = Origin nicht erreichbar (offline, Tunnel weg)
|
||||
## "auth_failed" = Zugangsdaten fehlen → UI zeigt klaren Hinweis, NIEMALS
|
||||
## einen Login-Prompt (Karte t_1457e935).
|
||||
static func check_update(git_exe: String, project_dir: String) -> Dictionary:
|
||||
var local := head_sha(git_exe, project_dir)
|
||||
if local.is_empty():
|
||||
return {"status": "error", "local": "", "remote": "",
|
||||
"error": "Kein Git-Repository (oder git nicht gefunden)."}
|
||||
var remote := remote_sha(git_exe, project_dir, BRANCH)
|
||||
if remote.is_empty():
|
||||
var r := git_run(git_exe, project_dir, PackedStringArray([
|
||||
"ls-remote", "origin", "refs/heads/%s" % BRANCH]))
|
||||
if not bool(r["ok"]):
|
||||
var text := str(r["output"])
|
||||
if looks_like_auth_error(text):
|
||||
return {"status": "auth_failed", "local": local, "remote": "",
|
||||
"error": AUTH_ERROR_TEXT}
|
||||
return {"status": "unreachable", "local": local, "remote": "",
|
||||
"error": "Origin nicht erreichbar (offline oder Tunnel weg)."}
|
||||
var text2 := str(r["output"]).strip_edges()
|
||||
if text2.is_empty():
|
||||
return {"status": "unreachable", "local": local, "remote": "",
|
||||
"error": "Origin nicht erreichbar (offline oder Tunnel weg)."}
|
||||
var remote := text2.split("\n")[0].strip_edges().split("\t")[0]
|
||||
if local == remote:
|
||||
return {"status": "up_to_date", "local": local, "remote": remote, "error": ""}
|
||||
return {"status": "update_available", "local": local, "remote": remote, "error": ""}
|
||||
|
|
@ -101,17 +169,20 @@ static func banner_text(local_sha: String, remote_sha: String) -> String:
|
|||
|
||||
|
||||
## Update ausführen: git pull --ff-only (Klon zieht nur nach, dreht nichts
|
||||
## um). Rückgabe {ok, output, errors}.
|
||||
## um). Rückgabe {ok, output, errors}. Auth-Fehler bekommen einen klaren
|
||||
## Zusatz-Hinweis (kein Login-Prompt — Karte t_1457e935).
|
||||
static func pull(git_exe: String, project_dir: String) -> Dictionary:
|
||||
var r := git_run(git_exe, project_dir, PackedStringArray(["pull", "--ff-only"]))
|
||||
var errors: Array = []
|
||||
var text := str(r["output"]).strip_edges()
|
||||
if not bool(r["ok"]):
|
||||
errors.append("git pull --ff-only fehlgeschlagen (Exit-Code %s):" % str(r["code"]))
|
||||
var text := str(r["output"]).strip_edges()
|
||||
if not text.is_empty():
|
||||
errors.append(text)
|
||||
else:
|
||||
errors.append("(keine Ausgabe — git nicht gefunden oder Verbindung sofort weg)")
|
||||
if looks_like_auth_error(text):
|
||||
errors.append("Zugangsdaten fehlen — es wird bewusst KEIN Login-Dialog geöffnet (Credential-Store prüfen).")
|
||||
return {"ok": bool(r["ok"]), "output": str(r["output"]), "errors": errors}
|
||||
|
||||
|
||||
|
|
@ -179,4 +250,4 @@ static func resolve_restart(cfg: ConfigFile = null) -> Dictionary:
|
|||
## Projektordner im Editor-Modus („--editor“ ist nötig — ein nacktes
|
||||
## „--path“ würde das Projekt als SPIEL starten, nicht als Editor).
|
||||
static func self_restart_args(project_dir: String) -> PackedStringArray:
|
||||
return PackedStringArray(["--editor", "--path", project_dir])
|
||||
return PackedStringArray(["--editor", "--path", project_dir])
|
||||
|
|
@ -1154,6 +1154,11 @@ func _updater_check_done(result: Dictionary, report: bool) -> void:
|
|||
"unreachable":
|
||||
if report:
|
||||
_set_status("Update-Check nicht erreichbar (offline oder Tunnel weg) — später nochmal probieren.", true)
|
||||
"auth_failed":
|
||||
# Karte t_1457e935: Zugangsdaten fehlen → klarer Hinweis statt
|
||||
# GUI-Login-Dialog (Askpass ist im Updater abgeschaltet).
|
||||
if report:
|
||||
_set_status(EditorUpdater.AUTH_ERROR_TEXT, true)
|
||||
_:
|
||||
if report:
|
||||
_set_status("Update-Check fehlgeschlagen: %s" % str(result.get("error", "")), true)
|
||||
|
|
|
|||
|
|
@ -143,6 +143,34 @@ func _run_tests() -> void:
|
|||
var bad := EditorUpdater.check_update("gibt-es-nicht-xyz", work)
|
||||
_check(str(bad["status"]) == "error", "git nicht gefunden → error (nicht unreachable)")
|
||||
|
||||
print("== Credential-Guard (Karte t_1457e935): git wird NIE interaktiv ==")
|
||||
_check(EditorUpdater.looks_like_auth_error(
|
||||
"fatal: could not read Username for 'http://127.0.0.1:13020': No such device or address"),
|
||||
"auth-Erkennung: could-not-read-Username")
|
||||
_check(EditorUpdater.looks_like_auth_error(
|
||||
"remote: Unauthorized\nfatal: Authentication failed for 'http://x/'"),
|
||||
"auth-Erkennung: Authentication failed / Unauthorized")
|
||||
_check(EditorUpdater.looks_like_auth_error("fatal: could not read Username; terminal prompts disabled"),
|
||||
"auth-Erkennung: terminal prompts disabled")
|
||||
_check(not EditorUpdater.looks_like_auth_error(
|
||||
"fatal: unable to access 'http://127.0.0.1:1/': Connection refused"),
|
||||
"auth-Erkennung: Connection refused ist KEIN Auth-Fehler")
|
||||
var spawn := EditorUpdater.spawn_argv("git", "/projekt", PackedStringArray(["ls-remote", "origin"]))
|
||||
if OS.has_feature("linux"):
|
||||
_check(str(spawn[0]) == "/usr/bin/env", "argv: POSIX-Spawn über /usr/bin/env")
|
||||
_check(spawn.has("GIT_TERMINAL_PROMPT=0"), "argv: GIT_TERMINAL_PROMPT=0 (kein Terminal-Prompt)")
|
||||
_check(spawn.has("GIT_ASKPASS=/bin/true"), "argv: GIT_ASKPASS=/bin/true (überstimmt geerbtes SSH_ASKPASS)")
|
||||
_check(spawn.has("-c") and spawn.has("credential.helper=") and spawn.has("credential.helper=store"),
|
||||
"argv: Helper-Reset + ausschließlich store")
|
||||
_check(spawn.has("core.askPass=/bin/true"), "argv: core.askPass-Fallback gesetzt")
|
||||
var hat_home := false
|
||||
for eintrag in spawn:
|
||||
if str(eintrag).begins_with("HOME="):
|
||||
hat_home = true
|
||||
_check(hat_home, "argv: HOME aus der Umgebung mitgegeben (kein Hardcode)")
|
||||
_check(str(spawn[spawn.size() - 2]) == "ls-remote" and str(spawn[spawn.size() - 1]) == "origin",
|
||||
"argv: originale git-Argumente bleiben hinten")
|
||||
|
||||
print("== Pull ==")
|
||||
_git(work, ["remote", "set-url", "origin", origin])
|
||||
var pulled := EditorUpdater.pull(git, work)
|
||||
|
|
|
|||
Loading…
Reference in a new issue