Ein zweites Zuhause für KISP: das Git-Upstream bei local-it

Verfasst von

in

Kurz gesagt: Das Projekt liegt jetzt nicht mehr nur auf dem Cube, sondern auch bei local-it. Jeder neue Commit wandert automatisch dorthin. Das Repository ist damit mein laufendes Backup.

Bisher lebte der gesamte Code von KISP an einem einzigen Ort: auf dem Cube, dem kleinen Server bei mir zu Hause. Geht dort etwas kaputt, ist alles weg. Das möchte ich nicht dem Zufall überlassen.

Deshalb bekommt das Projekt eine zweite Heimat. local-it ist ein Verein, in dem ich Mitglied bin. Er betreibt eine eigene Plattform namens Forgejo. Sie funktioniert ähnlich wie GitHub, wird aber gemeinschaftlich getragen und selbst gehostet. Das passt zu KISP: kleine Wege, klare Zuständigkeiten, digitale Souveränität.

Eine Begriffsklärung vorweg: Ein Git-Repository ist ein Ordner mit Versionsgedächtnis. Jeder abgeschlossene Schritt wird als Commit festgehalten. Ein Upstream ist die Gegenstelle, mit der das Repository seine Commits abgleicht. In meinem Fall liegt sie bei local-it.

Zwei Stolpersteine

Der Weg dorthin hatte zwei kleine Hindernisse.

Die falsche Tür. Mein erster Versuch klopfte auf Port 22 an, dem üblichen Eingang für SSH. Forgejo lauscht bei local-it auf Port 2222. Die Antwort lautete Permission denied (publickey). Die Adresse, die Forgejo beim Klonen anzeigt, nennt Benutzer, Host und Port verlässlich.

Die eigene Firewall. Danach stand die Firewall des Cube im Weg. Sie lässt ausgehende Verbindungen nur zu, wenn ich sie ausdrücklich freigebe. Das ist Absicht und eine gute Gewohnheit. Ich habe genau eine Verbindung freigegeben: zur Forgejo-Instanz, nur auf Port 2222. Danach klappte der Test.

Zeig mir, wie es geht: SSH-Schlüssel, Port und Firewall

### Ausgangslage

  • Lokales Repo: /data/terruhn.it/CLAUDE/kisp, Branch main, zunächst ohne Remote
  • Upstream: https://git.local-it.org/rene/kisp
  • SSH-URL laut Forgejo: ssh://git@git.local-it.org:2222/rene/kisp.git

### 1. SSH-Schlüssel anlegen und in Forgejo hinterlegen

ssh-keygen -t ed25519 -C "rene@cube" -f ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519.pub

Den öffentlichen Schlüssel in Forgejo unter Einstellungen → SSH-/GPG-Schlüssel hinzufügen.

### 2. Der richtige Port

Auf Port 22 endete der Test mit Permission denied (publickey). Dort läuft der SSH-Dienst des Servers selbst. Forgejo nutzt Port 2222. Der Benutzer git@ ist der gemeinsame Systemuser aller Forgejo-Konten, den Account erkennt Forgejo am Schlüssel.

### 3. Die Firewall auf dem Cube

Erreichbarkeit prüfen:

nc -vz -w 5 git.local-it.org 2222

Richtlinie prüfen (als root):

ufw status verbose

Ausgehende Freigabe, eng gefasst auf die Forgejo-Instanz (als root):

ufw allow out to 159.195.151.226 port 2222 proto tcp comment 'Forgejo local-it SSH'

Test:

ssh -T -p 2222 git@git.local-it.org

### 4. Remote eintragen und erster Push

cd /data/terruhn.it/CLAUDE/kisp
git remote add origin ssh://git@git.local-it.org:2222/rene/kisp.git
git push -u origin main
git status

Ergebnis: Your branch is up to date with 'origin/main'.

Was im Upstream liegt

Mit dem ersten Push ist der gesamte Projektordner umgezogen, inklusive der Versionsgeschichte mit allen bisherigen Commits. Der Ordner enthält die Teile, aus denen KISP besteht:

  • Steuerung des Projekts: CLAUDE.md mit den Arbeitsregeln für Claude Code, BOARD.md als Kanban, IDEEN.md als Ideenspeicher und ARBEITSPROTOKOLL.md mit jedem Schritt, jeder Entscheidung und dem Verbrauch.
  • Etappen: Der Ordner etappen/ mit Ziel, Aufgaben und Abschlusskriterium jeder Etappe.
  • Inhalte der Website: Der Ordner inhalte/ mit den Entwürfen aller Beiträge und Seiten. Dieser Beitrag ist einer davon.
  • Skripte: Der Ordner skripte/ mit den wiederverwendbaren Helfern, zum Beispiel für den Export, die Übertragung zu All-Inkl, die Ideenseite, die Umwandlung von Markdown in WordPress-Blöcke und die SEO-Felder.
  • Infrastruktur: Der Ordner infra/ mit dem Docker-Compose-Stack, der Caddy-Konfiguration, den systemd-Diensten und -Timern, dem WP-CLI-Wrapper kisp-wp samt Sudo-Regel, zwei kleinen Plugins (mu-plugins) und der Vorlage für die Fußzeile des Themes.
  • SEO-Audits: Der Ordner seo-audits/ mit Berichten, Aktionsplan und Bildschirmfotos des ersten Audits.
  • Recherche und Planung: Markt-und-Namensrecherche.md und chat-zusammenfassung-planung.md aus den ersten Tagen.
  • Einstellungen für Claude Code: .claude/settings.json, die für dieses Projekt das Plugin claude-seo aktiviert.

Nicht im Upstream liegen Zugangsdaten und persönliche Einstellungen: .env steht in der .gitignore, ebenso .claude/settings.local.json. Die Datenbank und die WordPress-Dateien selbst gehören ebenfalls nicht dazu, sie liegen im Stack kisp auf dem Cube. Das Repository enthält also den Weg und die Werkzeuge, nicht die laufende Instanz.

Zeig mir, wie es geht: Versionierte Dateien prüfen

### 5. Was gehört ins Repo?

Im Ordner .claude/ lag nur settings.json. Sie aktiviert für dieses Projekt das Plugin claude-seo. Persönliche Freigaben legt Claude Code in settings.local.json ab. Diese Datei gehört zum Arbeitsplatz und bleibt lokal:

echo ".claude/settings.local.json" >> .gitignore

Übersicht der versionierten Dateien je Oberordner:

git ls-files | awk -F/ '{print $1}' | sort | uniq -c

Der Push, der sich selbst erledigt

Zum Schluss blieb eine Frage: Muss ich bei jedem Commit daran denken, ihn hochzuladen? Ein Commit bleibt zunächst lokal, erst git push überträgt ihn. Die Antwort ist ein kleines Skript, ein sogenannter Hook. Git startet es nach jedem Commit von selbst. Es schickt den neuen Stand zu local-it und schreibt ein Protokoll. So wird das Repository dort zum laufenden Backup. Das gilt auch für Commits, die Claude Code anlegt.

Schlägt ein Push einmal fehl, zum Beispiel weil das Netz fehlt, steht das im Protokoll. Der nächste erfolgreiche Push nimmt alle offenen Commits mit.

Zeig mir, wie es geht: Hook für den automatischen Push

### 6. Automatischer Push nach jedem Commit

vi .git/hooks/post-commit
#!/bin/sh
# post-commit – pusht Commits auf main automatisch zu local-it
# Version 1.0 – 2026-10-08 UTC

branch=$(git rev-parse --abbrev-ref HEAD)
[ "$branch" = "main" ] || exit 0

(
  echo "=== $(date -u '+%Y-%m-%d %H:%M:%S UTC') $(git rev-parse --short HEAD)"
  git push origin main
) >> .git/auto-push.log 2>&1 &
chmod +x .git/hooks/post-commit

Kontrolle nach dem nächsten Commit:

cat .git/auto-push.log
git status

Eigenschaften des Hooks:

  • Er liegt in .git/hooks/ und wird selbst nicht versioniert. Er gilt für diese Kopie auf dem Cube.
  • Der Push läuft im Hintergrund, der Commit wartet nicht auf das Netz.
  • Nur Commits auf main lösen einen Push aus.

Was ich mitnehme

  • Die SSH-URL aus der Weboberfläche ist die verlässlichste Quelle für Benutzer, Host und Port.
  • Eine Firewall mit restriktiver Ausgangsregel ist gute Praxis. Neue Dienste brauchen eine bewusste Freigabe, und genau das ist ihr Sinn.
  • Ein Repository bei einem gemeinschaftlich betriebenen Verein verbindet Backup und digitale Souveränität.