feat(tooling): Gitea-Token aus den Git-Zugangsdaten lesen (#020)
Serveradresse und Repository stammen aus dem Remote, der Token aus dem Schluesselbund bzw. ~/.git-credentials. Ein zweiter Token in der .env entfaellt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,11 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
|
|||||||
|
|
||||||
### Sicherheit
|
### Sicherheit
|
||||||
|
|
||||||
|
## [0.4.2] – 2026-09-17
|
||||||
|
|
||||||
|
### Geändert
|
||||||
|
- Der Issue-Abgleich holt Serveradresse und Repository aus dem Git-Remote und den Token aus den hinterlegten Git-Zugangsdaten; ein zweiter Token in der `.env` ist nicht mehr nötig (#020)
|
||||||
|
|
||||||
## [0.4.1] – 2026-09-17
|
## [0.4.1] – 2026-09-17
|
||||||
|
|
||||||
### Hinzugefügt
|
### Hinzugefügt
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Zeitübersicht
|
## Zeitübersicht
|
||||||
|
|
||||||
**Gesamt:** 2 h 48 min
|
**Gesamt:** 2 h 58 min
|
||||||
|
|
||||||
| Issue | Zeit |
|
| Issue | Zeit |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
| #005 Agent-Skript | 0 h 25 min |
|
| #005 Agent-Skript | 0 h 25 min |
|
||||||
| #006 Kunden und Hostzuweisung | 0 h 35 min |
|
| #006 Kunden und Hostzuweisung | 0 h 35 min |
|
||||||
| #007 Host-Tabelle und Detailansichten | 0 h 25 min |
|
| #007 Host-Tabelle und Detailansichten | 0 h 25 min |
|
||||||
| #020 Issues nach Gitea übertragen | 0 h 10 min |
|
| #020 Issues nach Gitea übertragen | 0 h 20 min |
|
||||||
|
|
||||||
| Meilenstein | Zeit |
|
| Meilenstein | Zeit |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -22,12 +22,13 @@
|
|||||||
| v0.3.0 | 0 h 35 min | **abgeschlossen**
|
| v0.3.0 | 0 h 35 min | **abgeschlossen**
|
||||||
| v0.4.0 | 0 h 25 min | **abgeschlossen**
|
| v0.4.0 | 0 h 25 min | **abgeschlossen**
|
||||||
| v0.4.1 | 0 h 10 min | **abgeschlossen**
|
| v0.4.1 | 0 h 10 min | **abgeschlossen**
|
||||||
|
| v0.4.2 | 0 h 10 min | **abgeschlossen**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2026-09-17 · Sitzung 1 · 19:36–laufend
|
## 2026-09-17 · Sitzung 1 · 19:36–laufend
|
||||||
**Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0)
|
**Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0)
|
||||||
**Version:** 0.4.1
|
**Version:** 0.4.2
|
||||||
|
|
||||||
### Erledigt
|
### Erledigt
|
||||||
- Auftrag gelesen, Rückfragen gestellt
|
- Auftrag gelesen, Rückfragen gestellt
|
||||||
@@ -49,6 +50,8 @@
|
|||||||
- #007 abgeschlossen: Host-Tabelle mit Suche, fünf Filtern, Sortierung, Seitenblättern und CSV-Export; Host-Detail mit sechs Tabs, Notizen, Tags, Kundenzuweisung und Token-Widerruf
|
- #007 abgeschlossen: Host-Tabelle mit Suche, fünf Filtern, Sortierung, Seitenblättern und CSV-Export; Host-Detail mit sechs Tabs, Notizen, Tags, Kundenzuweisung und Token-Widerruf
|
||||||
- Meilenstein v0.4.0 abgeschlossen und getaggt; 59 Tests
|
- Meilenstein v0.4.0 abgeschlossen und getaggt; 59 Tests
|
||||||
- #020 abgeschlossen: Abgleichskript für den Gitea-Issue-Tracker (Missverständnis geklärt – die Issues lagen bisher nur als Dateien im Repo, nicht im Tracker)
|
- #020 abgeschlossen: Abgleichskript für den Gitea-Issue-Tracker (Missverständnis geklärt – die Issues lagen bisher nur als Dateien im Repo, nicht im Tracker)
|
||||||
|
- Token kommt aus den Git-Zugangsdaten statt aus einer zweiten Stelle; 20 Issues, 15 Labels und 13 Meilensteine in Gitea angelegt
|
||||||
|
- Alles nach Gitea gepusht: main bei 51243c2, Tags v0.1.0 bis v0.4.1
|
||||||
|
|
||||||
### Entscheidungen
|
### Entscheidungen
|
||||||
- **Screenshots mit Firefox headless** statt Playwright: Firefox ist vorhanden, es muss kein Browser samt Systembibliotheken nachinstalliert werden (dafür wäre root nötig).
|
- **Screenshots mit Firefox headless** statt Playwright: Firefox ist vorhanden, es muss kein Browser samt Systembibliotheken nachinstalliert werden (dafür wäre root nötig).
|
||||||
@@ -75,5 +78,4 @@
|
|||||||
- Offen: Rolle „readonly“ zusätzlich zu Admin? (vorläufig vorgesehen)
|
- Offen: Rolle „readonly“ zusätzlich zu Admin? (vorläufig vorgesehen)
|
||||||
|
|
||||||
### Nächster Schritt
|
### Nächster Schritt
|
||||||
- Gitea-Token erzeugen und `npm run issues:sync` einmal echt laufen lassen
|
|
||||||
- #008 Laufzeiten und Rechnungen: Vorschläge bestätigen, Rechnungen erfassen, Häkchen direkt in der Tabelle
|
- #008 Laufzeiten und Rechnungen: Vorschläge bestätigen, Rechnungen erfassen, Häkchen direkt in der Tabelle
|
||||||
|
|||||||
@@ -52,16 +52,16 @@ jedem Klon mit. Wer sie zusätzlich im Gitea-Tracker haben möchte, gleicht sie
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run issues:sync -- --dry-run # zeigt nur, was passieren würde
|
npm run issues:sync -- --dry-run # zeigt nur, was passieren würde
|
||||||
|
npm run issues:sync # echter Abgleich
|
||||||
GITEA_URL=https://git.example.de \
|
|
||||||
GITEA_REPO=benutzer/ProxLedger \
|
|
||||||
GITEA_TOKEN=... \
|
|
||||||
npm run issues:sync
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Token braucht das Recht `issue:write` (Gitea: Einstellungen → Anwendungen → Zugriffstoken). Er kann auch in
|
Serveradresse und Repository liest das Skript aus dem Git-Remote, den Token aus den hinterlegten
|
||||||
`.env` stehen, die Datei ist von Git ausgenommen. Der Abgleich legt Labels und Meilensteine an, erkennt
|
Git-Zugangsdaten (Schlüsselbund oder `~/.git-credentials`) – derselbe Token wie fürs Pushen, er braucht
|
||||||
vorhandene Issues an der Nummer im Titel, aktualisiert sie und schließt erledigte.
|
zusätzlich das Recht `issue:write`. Wer beides trennen möchte, setzt `GITEA_URL`, `GITEA_REPO` und
|
||||||
|
`GITEA_TOKEN` in der `.env`.
|
||||||
|
|
||||||
|
Der Abgleich legt Labels und Meilensteine an, erkennt vorhandene Issues an der Nummer im Titel,
|
||||||
|
aktualisiert sie und schließt erledigte. Ein zweiter Lauf erzeugt keine Dubletten.
|
||||||
|
|
||||||
## Dokumentation
|
## Dokumentation
|
||||||
|
|
||||||
|
|||||||
@@ -27,5 +27,9 @@ aktuell bleiben – ohne dass sie von Hand nachgepflegt werden müssen.
|
|||||||
## Notizen / Entscheidungen
|
## Notizen / Entscheidungen
|
||||||
- Die Markdown-Dateien bleiben die Quelle der Wahrheit: Sie liegen im Repo, sind ohne Netz lesbar und
|
- Die Markdown-Dateien bleiben die Quelle der Wahrheit: Sie liegen im Repo, sind ohne Netz lesbar und
|
||||||
wandern mit jedem Klon mit. Gitea bekommt eine Kopie zum Kommentieren und Zuweisen.
|
wandern mit jedem Klon mit. Gitea bekommt eine Kopie zum Kommentieren und Zuweisen.
|
||||||
- Der erste echte Lauf steht noch aus: Dafür braucht es einen Gitea-Token mit `issue:write`.
|
- Der Token kommt aus den Git-Zugangsdaten (`git credential fill`), Adresse und Repo aus dem Remote.
|
||||||
Der Trockenlauf liest alle 20 Dateien korrekt.
|
Damit muss derselbe Token nicht zweimal gepflegt werden.
|
||||||
|
- Gefragt werden zuerst die eingerichteten Helfer (Schlüsselbund), danach zusätzlich `~/.git-credentials` –
|
||||||
|
je nach Rechner ist mal das eine, mal das andere gefüllt.
|
||||||
|
- Erster echter Lauf am 17.09.2026: 20 Issues, 15 Labels und 13 Meilensteine angelegt, erledigte Issues
|
||||||
|
direkt geschlossen. Zweiter Lauf: 0 neue, 20 aktualisiert – keine Dubletten.
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "proxledger",
|
"name": "proxledger",
|
||||||
"version": "0.4.1",
|
"version": "0.4.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
+56
-5
@@ -10,6 +10,7 @@
|
|||||||
* Die Markdown-Dateien bleiben die Quelle der Wahrheit; Gitea erhält eine Kopie zum Kommentieren.
|
* Die Markdown-Dateien bleiben die Quelle der Wahrheit; Gitea erhält eine Kopie zum Kommentieren.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
import { readFile, readdir } from "node:fs/promises";
|
import { readFile, readdir } from "node:fs/promises";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
@@ -138,6 +139,49 @@ class Gitea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ermittelt Serveradresse und Repo aus dem Git-Remote, damit beides nicht doppelt gepflegt werden muss.
|
||||||
|
* Beispiel: https://git.example.de/benutzer/ProxLedger.git → { basis, repo }
|
||||||
|
*/
|
||||||
|
function ausRemote(): { basis: string; repo: string } | null {
|
||||||
|
try {
|
||||||
|
const url = execFileSync("git", ["remote", "get-url", "origin"], { encoding: "utf8" }).trim();
|
||||||
|
const treffer = /^https?:\/\/(?:[^@/]*@)?([^/]+)\/(.+?)(?:\.git)?$/.exec(url);
|
||||||
|
if (!treffer) return null;
|
||||||
|
return { basis: `https://${treffer[1]}`, repo: treffer[2] };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt den Token aus den Git-Zugangsdaten (Schlüsselbund oder `~/.git-credentials`).
|
||||||
|
* So genügt ein einziger Token für Push und API – er muss nicht zusätzlich in der .env stehen.
|
||||||
|
*/
|
||||||
|
function tokenAusGitCredentials(basis: string): string | null {
|
||||||
|
const host = new URL(basis).host;
|
||||||
|
|
||||||
|
// Erst die eingerichteten Helfer (z. B. Schlüsselbund), dann zusätzlich die Datei
|
||||||
|
// ~/.git-credentials – je nach Rechner ist mal das eine, mal das andere gefüllt.
|
||||||
|
for (const zusatz of [[], ["-c", "credential.helper=store"]]) {
|
||||||
|
try {
|
||||||
|
const antwort = execFileSync("git", [...zusatz, "credential", "fill"], {
|
||||||
|
input: `protocol=https\nhost=${host}\n\n`,
|
||||||
|
encoding: "utf8",
|
||||||
|
// Keine Rückfrage im Terminal: Wenn nichts hinterlegt ist, soll der Aufruf scheitern.
|
||||||
|
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
||||||
|
stdio: ["pipe", "pipe", "ignore"],
|
||||||
|
});
|
||||||
|
const passwort = /^password=(.*)$/m.exec(antwort);
|
||||||
|
if (passwort?.[1]) return passwort[1];
|
||||||
|
} catch {
|
||||||
|
// Nächste Möglichkeit versuchen.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/** Titel in Gitea, z. B. „#004 Agent-Skript“ – daran werden vorhandene Issues wiedererkannt. */
|
/** Titel in Gitea, z. B. „#004 Agent-Skript“ – daran werden vorhandene Issues wiedererkannt. */
|
||||||
function giteaTitel(issue: IssueDatei): string {
|
function giteaTitel(issue: IssueDatei): string {
|
||||||
return `#${String(issue.nummer).padStart(3, "0")} ${issue.titel}`;
|
return `#${String(issue.nummer).padStart(3, "0")} ${issue.titel}`;
|
||||||
@@ -145,9 +189,11 @@ function giteaTitel(issue: IssueDatei): string {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const trockenlauf = process.argv.includes("--dry-run");
|
const trockenlauf = process.argv.includes("--dry-run");
|
||||||
const basis = (process.env.GITEA_URL ?? "").replace(/\/$/, "");
|
const remote = ausRemote();
|
||||||
const repo = process.env.GITEA_REPO ?? "";
|
const basis = (process.env.GITEA_URL ?? remote?.basis ?? "").replace(/\/$/, "");
|
||||||
const token = process.env.GITEA_TOKEN ?? "";
|
const repo = process.env.GITEA_REPO ?? remote?.repo ?? "";
|
||||||
|
const token = process.env.GITEA_TOKEN || (basis ? (tokenAusGitCredentials(basis) ?? "") : "");
|
||||||
|
const tokenQuelle = process.env.GITEA_TOKEN ? "Umgebungsvariable" : "Git-Zugangsdaten";
|
||||||
|
|
||||||
const verzeichnis = path.join(process.cwd(), "docs", "issues");
|
const verzeichnis = path.join(process.cwd(), "docs", "issues");
|
||||||
const issues = await dateienLesen(verzeichnis);
|
const issues = await dateienLesen(verzeichnis);
|
||||||
@@ -155,7 +201,10 @@ async function main() {
|
|||||||
|
|
||||||
if (trockenlauf || !basis || !repo || !token) {
|
if (trockenlauf || !basis || !repo || !token) {
|
||||||
if (!trockenlauf) {
|
if (!trockenlauf) {
|
||||||
console.log("\nGITEA_URL, GITEA_REPO oder GITEA_TOKEN fehlen – es läuft nur ein Trockenlauf.\n");
|
console.log(
|
||||||
|
"\nAdresse, Repo oder Token nicht ermittelbar – es läuft nur ein Trockenlauf.\n" +
|
||||||
|
"Entweder GITEA_TOKEN setzen oder einmal `git push` ausführen, damit der Token hinterlegt wird.\n",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for (const issue of issues) {
|
for (const issue of issues) {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -165,11 +214,13 @@ async function main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log(
|
console.log(
|
||||||
"\nEchter Abgleich: GITEA_URL, GITEA_REPO und GITEA_TOKEN setzen (Token mit Recht „issue:write“).",
|
`\nZiel wäre: ${basis || "?"}/${repo || "?"} – Token ${token ? "vorhanden" : "fehlt"}.` +
|
||||||
|
"\nDer Token wird aus den Git-Zugangsdaten gelesen; er braucht zusätzlich das Recht „issue:write“.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Ziel: ${basis}/${repo} (Token aus: ${tokenQuelle})`);
|
||||||
const gitea = new Gitea(basis, repo, token);
|
const gitea = new Gitea(basis, repo, token);
|
||||||
|
|
||||||
const vorhandeneLabels = new Map((await gitea.labels()).map((l) => [l.name.toLowerCase(), l.id]));
|
const vorhandeneLabels = new Map((await gitea.labels()).map((l) => [l.name.toLowerCase(), l.id]));
|
||||||
|
|||||||
Reference in New Issue
Block a user