Kapitel 13/Premium Rohtext.md hinzugefügt
This commit is contained in:
459
Kapitel 13/Premium Rohtext.md
Normal file
459
Kapitel 13/Premium Rohtext.md
Normal file
@@ -0,0 +1,459 @@
|
||||
# Premium – CI-Pipelines, Self-Hosted Runner & Webhooks mit Gitea
|
||||
|
||||
Im Free-Kapitel hast du eine stabile Gitea-Instanz eingerichtet, die deine Projekte versioniert und über HTTPS erreichbar ist.
|
||||
Damit steht das Fundament für saubere Versionsverwaltung und Team-Strukturen.
|
||||
|
||||
Im Premium-Teil erweitern wir dieses System zu einer **automatisierten Entwicklungs- und Produktionsumgebung**, die eigenständig Builds, Tests oder Deployments ausführt – komplett lokal, ohne externe CI-Dienste.
|
||||
Du lernst, wie Gitea mit Self-Hosted-Runnern arbeitet, wie du CI-Pipelines definierst und wie Webhooks Aktionen in anderen UCC-Systemen (z. B. n8n oder Vaultwarden) auslösen.
|
||||
|
||||
Ziel ist nicht mehr nur das reine Speichern von Code, sondern **automatisches Ausführen, Prüfen und Verteilen** – alles innerhalb deines Homelabs.
|
||||
|
||||
In diesem Kapitel behandeln wir:
|
||||
|
||||
1. **Einrichtung des Gitea-Actions-Systems**
|
||||
Aktivierung der integrierten CI-Umgebung auf deiner Instanz.
|
||||
Einrichtung des ersten Self-Hosted-Runners im Container oder auf einem separaten Node.
|
||||
|
||||
2. **Definition einer CI-Pipeline**
|
||||
Aufbau eines vollständigen Workflow-YAMLs, der Code überprüft, Dateien erstellt oder Skripte automatisch testet.
|
||||
Praxisbeispiel: Automatischer PDF-Build aus Markdown-Tutorials.
|
||||
|
||||
3. **Integration mit n8n und Vaultwarden**
|
||||
Nutzung von Webhooks für Benachrichtigungen, Status-Monitoring und automatisches Key-Handling.
|
||||
Beispiel: Push → Webhook → n8n startet Backup oder Deployment.
|
||||
|
||||
4. **Erweiterte Automatisierungen (optional)**
|
||||
Nutzung mehrerer Runner, paralleler Jobs und Artefakt-Uploads.
|
||||
Wie du Gitea als zentrales Automatisierungs-Backbone des UCC nutzt.
|
||||
|
||||
> [!NOTE]
|
||||
> Alle Schritte basieren auf der bestehenden Installation aus dem Free-Kapitel.
|
||||
> Die dort erstellte Gitea-Instanz bleibt unverändert – du erweiterst sie lediglich um CI- und Webhook-Funktionen.
|
||||
> Stelle sicher, dass dein Git-LXC aktiv und über HTTPS erreichbar ist, bevor du beginnst.
|
||||
|
||||
Mit diesen Erweiterungen wird Gitea zu einem **vollwertigen Automations-Server** –
|
||||
es verwaltet Projekte, testet Änderungen, verteilt Ergebnisse und kann mit anderen Komponenten deines UCC (n8n, Vaultwarden, Nextcloud) direkt interagieren.
|
||||
|
||||
---
|
||||
|
||||
## Gitea Actions – lokale CI/CD-Automatisierung
|
||||
|
||||
Mit **Gitea Actions** kannst du Prozesse automatisieren, die sonst manuell erledigt werden müssten – etwa das Erstellen, Testen oder Verteilen von Projekten.
|
||||
Das System funktioniert ähnlich wie GitHub Actions, läuft aber vollständig lokal und benötigt keine Verbindung zu externen Diensten.
|
||||
Damit bleibt deine Entwicklungs- und Produktionsumgebung komplett unter deiner Kontrolle.
|
||||
|
||||
> [!NOTE]
|
||||
> Gitea Actions ist seit Version 1.19 integriert.
|
||||
> Stelle sicher, dass dein Image aktuell ist, bevor du beginnst:
|
||||
> ```bash
|
||||
> docker pull gitea/gitea:latest
|
||||
> docker compose up -d
|
||||
> ```
|
||||
|
||||
Ziel dieses Abschnitts ist der Aufbau einer funktionsfähigen Actions-Umgebung mit einem eigenen **Self-Hosted Runner**, der deine Workflows im Homelab ausführt.
|
||||
|
||||
### Schritt 1 – Voraussetzungen prüfen
|
||||
|
||||
Bevor du Actions aktivierst, überprüfe:
|
||||
|
||||
* Deine Gitea-Version ist **≥ 1.19**.
|
||||
In der Weboberfläche unter **About** oder per CLI prüfen:
|
||||
```bash
|
||||
docker exec -it gitea gitea --version
|
||||
```
|
||||
* Der Server hat Zugriff auf lokale oder interne Runner (HTTP erreichbar).
|
||||
* Docker und Docker Compose laufen stabil.
|
||||
|
||||
> [!TIP]
|
||||
> Gitea Actions verwendet Runner, die containerisierte Jobs ausführen.
|
||||
> Je nach Projekt kannst du sie auf demselben Host oder in einem separaten LXC-Container betreiben.
|
||||
|
||||
### Schritt 2 – Actions aktivieren
|
||||
|
||||
Actions sind standardmäßig deaktiviert.
|
||||
Aktiviere sie über die Konfigurationsdatei `app.ini`:
|
||||
|
||||
```bash
|
||||
nano /gitea/gitea/conf/app.ini
|
||||
```
|
||||
|
||||
Füge unter `[actions]` den folgenden Block hinzu:
|
||||
|
||||
```
|
||||
[actions]
|
||||
ENABLED = true
|
||||
DEFAULT_ACTIONS_URL = https://gitea.com
|
||||
```
|
||||
|
||||
Datei speichern und Container neu starten:
|
||||
|
||||
```bash
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Die Angabe `DEFAULT_ACTIONS_URL` zeigt auf die Quelle für Standard-Workflows.
|
||||
> Wenn du keine externen Templates nutzen möchtest, kannst du sie auch leer lassen.
|
||||
|
||||
Nach dem Neustart ist im Repository-Menü der neue Tab **Actions** sichtbar.
|
||||
|
||||
👉 **Screenshot geeignet:** Gitea-Repository mit sichtbarem Reiter „Actions“
|
||||
|
||||
### Schritt 3 – Self-Hosted Runner installieren
|
||||
|
||||
Der Runner ist das lokale Programm, das die definierten Workflows ausführt.
|
||||
Er kann auf demselben LXC wie Gitea oder auf einem separaten Node laufen.
|
||||
|
||||
1. Wechsle auf deinen Ziel-Container oder Host:
|
||||
```bash
|
||||
pct enter <ID>
|
||||
```
|
||||
2. Lade den Runner:
|
||||
```bash
|
||||
mkdir -p /runner && cd /runner
|
||||
curl -L -o gitea-runner https://dl.gitea.com/runner/latest/gitea-runner-linux-amd64
|
||||
chmod +x gitea-runner
|
||||
```
|
||||
3. Registrierung starten:
|
||||
```bash
|
||||
./gitea-runner register
|
||||
```
|
||||
Danach wirst du nach folgenden Angaben gefragt:
|
||||
- **Gitea instance URL:** `https://git.deinedomain.tld`
|
||||
- **Token:** generierst du im Gitea-Webinterface
|
||||
→ *Site Administration → Actions → Runner Tokens → Add New Token*
|
||||
- **Runner name:** frei wählbar (z. B. `ucc-runner-01`)
|
||||
- **Executor:** `shell` oder `docker` (empfohlen)
|
||||
4. Dienst aktivieren:
|
||||
```bash
|
||||
./gitea-runner daemon &
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Wenn du Docker im Runner nutzt, kannst du Build- oder Test-Container isoliert starten.
|
||||
> Für einfache Automatisierungen reicht der `shell`-Modus.
|
||||
|
||||
Nach erfolgreicher Registrierung erscheint der Runner unter
|
||||
**Site Administration → Actions → Runners** mit Status **online**.
|
||||
|
||||
👉 **Screenshot geeignet:** Gitea-Weboberfläche mit aktivem Runner (Status: online)
|
||||
|
||||
### Schritt 4 – Test-Workflow anlegen
|
||||
|
||||
Im Repository legst du unter `.gitea/workflows` eine YAML-Datei an:
|
||||
|
||||
```yaml
|
||||
# Datei: .gitea/workflows/test.yml
|
||||
name: Test-Workflow
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
check-setup:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Repository klonen
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Testausgabe
|
||||
run: echo "Gitea Actions läuft erfolgreich!"
|
||||
```
|
||||
|
||||
Committe die Datei und pushe sie ins Repository:
|
||||
|
||||
```bash
|
||||
git add .gitea/workflows/test.yml
|
||||
git commit -m "CI: Testworkflow hinzugefügt"
|
||||
git push
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Jeder Push löst automatisch den hinterlegten Workflow aus.
|
||||
> Die Ausgabe kannst du direkt im Tab **Actions** ansehen.
|
||||
|
||||
👉 **Screenshot geeignet:** Gitea-Workflow-Log mit erfolgreichem Joblauf
|
||||
|
||||
### Schritt 5 – Erweiterung für reale Workflows
|
||||
|
||||
Beispiel: Automatischer PDF-Build aus Markdown-Tutorials (analog zum UCC-Dokumentationsprozess):
|
||||
|
||||
```yaml
|
||||
name: Build Tutorials
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-pdf:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Pandoc installieren
|
||||
run: apt update && apt install -y pandoc
|
||||
- name: PDF erstellen
|
||||
run: |
|
||||
pandoc README.md -o tutorial.pdf
|
||||
- name: Artefakt speichern
|
||||
run: cp tutorial.pdf /srv/assets/projects/
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Damit kannst du Markdown-Tutorials aus Gitea automatisch in dein Asset-LXC exportieren.
|
||||
> Gitea, Runner und Asset-System arbeiten dadurch direkt zusammen.
|
||||
|
||||
---
|
||||
|
||||
### Schritt 2 – n8n-Workflow aufbauen
|
||||
|
||||
In diesem Abschnitt baust du einen voll funktionsfähigen Workflow, der automatisch reagiert, wenn in Gitea ein Push oder Release erfolgt.
|
||||
Alle verwendeten Nodes sind in der kostenlosen n8n-Version enthalten.
|
||||
|
||||
#### Node 1 – Webhook
|
||||
|
||||
| Feld | Wert |
|
||||
|------|------|
|
||||
| **HTTP Method** | `POST` |
|
||||
| **Path** | `git-ci` |
|
||||
| **Response Mode** | `On Received` |
|
||||
| **Response Data** | `{ "status": "received" }` |
|
||||
| **Response Code** | `200` |
|
||||
|
||||
Nach dem Speichern siehst du oben die **Webhook-URL**, z. B.:
|
||||
```
|
||||
https://n8n.deinedomain.tld/webhook/git-ci
|
||||
```
|
||||
Diese trägst du im Gitea-Webhook ein.
|
||||
|
||||
> [!TIP]
|
||||
> Der Node funktioniert sofort nach Speichern.
|
||||
> Ein Test-Delivery in Gitea zeigt dir, ob n8n korrekt reagiert.
|
||||
|
||||
#### Node 2 – Set (Daten reduzieren)
|
||||
|
||||
Zweck: Nur die relevanten Felder aus dem JSON behalten.
|
||||
|
||||
1. Verbinde den **Webhook** mit einem **Set**-Node.
|
||||
2. Öffne den Node und wähle **Keep Only Set** → aktivieren.
|
||||
3. Trage die folgenden Felder ein:
|
||||
|
||||
| Name | Ausdruck |
|
||||
|------|-----------|
|
||||
| `repository` | `={{ $json["repository"]["full_name"] }}` |
|
||||
| `event` | `={{ $json["action"] || $json["event_name"] }}` |
|
||||
| `ref` | `={{ $json["ref"] }}` |
|
||||
| `user` | `={{ $json["sender"]["login"] }}` |
|
||||
|
||||
Speichern → **Execute Node** klicken → du solltest ein kompaktes JSON mit vier Feldern erhalten.
|
||||
|
||||
#### Node 3 – Switch (Ereignis prüfen)
|
||||
|
||||
1. Füge einen **Switch**-Node hinzu.
|
||||
2. Verbinde ihn mit dem **Set**-Node.
|
||||
3. Im Feld **Value to Compare**:
|
||||
```
|
||||
{{$json["event"]}}
|
||||
```
|
||||
4. Füge drei Cases hinzu:
|
||||
|
||||
| Case Name | Wert |
|
||||
|------------|-------|
|
||||
| Push | `push` |
|
||||
| Release | `release` |
|
||||
| Workflow | `workflow_run` |
|
||||
|
||||
Jetzt entscheidet der Node automatisch, welcher Zweig ausgeführt wird.
|
||||
|
||||
#### Node 4 – SSH (Befehl ausführen)
|
||||
|
||||
Dieser Node startet auf einem entfernten Server oder Container einen definierten Befehl.
|
||||
Beispiel für automatische Aktualisierung eines Projekts bei Push.
|
||||
|
||||
| Feld | Wert |
|
||||
|------|------|
|
||||
| **Host** | `10.0.0.20` |
|
||||
| **Port** | `22` |
|
||||
| **Username** | `deploy` |
|
||||
| **Password / SSH Key** | entsprechend eintragen |
|
||||
| **Command** | `cd /srv/project && git pull && systemctl restart app` |
|
||||
|
||||
Speichern → über den **Switch-Zweig Push** verbinden.
|
||||
|
||||
#### Node 5 – HTTP Request (Deployment oder Benachrichtigung)
|
||||
|
||||
Beispiel für automatisches Triggern eines Deployments bei Release.
|
||||
|
||||
| Feld | Wert |
|
||||
|------|------|
|
||||
| **Method** | `POST` |
|
||||
| **URL** | `https://ucc-api.deinedomain.tld/deploy` |
|
||||
| **Body Content Type** | `JSON` |
|
||||
| **JSON/RAW Parameters** | `{"repo":"={{$json.repository}}","ref":"={{$json.ref}}"}` |
|
||||
|
||||
Verbinde diesen Node mit dem **Switch-Zweig Release**.
|
||||
|
||||
#### Node 6 – Discord (optional)
|
||||
|
||||
Zum Senden von Statusmeldungen oder Benachrichtigungen.
|
||||
Nutze den kostenlosen Standard-Discord-Node.
|
||||
|
||||
| Feld | Wert |
|
||||
|------|------|
|
||||
| **Resource** | `Message` |
|
||||
| **Operation** | `Create` |
|
||||
| **Channel ID** | `123456789012345678` |
|
||||
| **Text** | `=📦 Neues Event in {{$json.repository}} ({{$json.event}})` |
|
||||
|
||||
Verbinde diesen Node mit allen Switch-Zweigen, falls du immer informiert werden möchtest.
|
||||
|
||||
👉 **Screenshot geeignet:** kompletter n8n-Workflow von Webhook → Set → Switch → SSH/HTTP/Discord
|
||||
|
||||
> [!NOTE]
|
||||
> Alle hier genutzten Nodes sind Standard-Bestandteil der n8n Community Edition.
|
||||
> Keine Cloud-Verbindung, keine Zusatzmodule nötig.
|
||||
> Du kannst den gesamten Workflow exportieren, sichern und auf jedem n8n-System importieren.
|
||||
|
||||
---
|
||||
|
||||
## Vaultwarden-Integration
|
||||
|
||||
Damit deine Automatisierung sicher bleibt, verwaltet Vaultwarden alle Zugangsdaten, Tokens und Secrets zentral.
|
||||
So liegen Passwörter und API-Keys nicht im Klartext in n8n oder Gitea, sondern werden nur bei Bedarf abgerufen.
|
||||
Das schützt dein System vor versehentlichen Leaks oder unbefugtem Zugriff.
|
||||
|
||||
> [!NOTE]
|
||||
> Für diese Integration genügt der Standard-Vaultwarden-Container aus dem UCC-System.
|
||||
> Es wird keine Erweiterung oder API-Plugin benötigt – die REST-API ist in jeder Installation verfügbar.
|
||||
|
||||
### Ziel
|
||||
|
||||
Du verknüpfst n8n mit Vaultwarden, um:
|
||||
- das Webhook-Secret aus Gitea sicher zu speichern
|
||||
- Zugriffsdaten für SSH oder API-Calls nur zur Laufzeit bereitzustellen
|
||||
- CI-Prozesse automatisiert und trotzdem verschlüsselt auszuführen
|
||||
|
||||
### Secret in Vaultwarden anlegen
|
||||
|
||||
1. Öffne die Weboberfläche von Vaultwarden.
|
||||
2. Klicke auf **+ Add Item** → Typ **Secure Note**.
|
||||
3. Trage folgende Werte ein:
|
||||
|
||||
| Feld | Beispiel |
|
||||
|------|-----------|
|
||||
| **Name** | `GITEA_WEBHOOK_SECRET` |
|
||||
| **Folder** | `CI/CD` |
|
||||
| **Notes** | dein Webhook-Secret aus Gitea |
|
||||
| **Favorite** | optional aktivieren |
|
||||
|
||||
4. Speichern.
|
||||
Vaultwarden verschlüsselt das Secret automatisch.
|
||||
Du kannst es später per API abrufen, ohne es jemals im Klartext zu sehen.
|
||||
|
||||
👉 **Screenshot geeignet:** Vaultwarden-Interface mit angelegtem Secret `GITEA_WEBHOOK_SECRET`
|
||||
|
||||
### API-Token für n8n erstellen
|
||||
|
||||
Damit n8n auf Vaultwarden zugreifen darf, benötigt es ein Zugriffstoken.
|
||||
|
||||
1. In Vaultwarden → **Settings → API Keys**
|
||||
2. Auf **Generate New Key** klicken.
|
||||
3. Einen Namen vergeben, z. B. `n8n-access`.
|
||||
4. Optional Ablaufdatum setzen.
|
||||
5. Key kopieren (er wird nur einmal angezeigt).
|
||||
|
||||
> [!TIP]
|
||||
> Der API-Key sollte **nur Lesezugriff** haben, wenn dein Vault mehrere Benutzer oder Secrets enthält.
|
||||
|
||||
### Secret aus Vaultwarden in n8n abrufen
|
||||
|
||||
In n8n fügst du vor sicherheitsrelevanten Schritten (z. B. SSH oder API-Call) einen **HTTP Request Node** ein, der das Secret abfragt.
|
||||
|
||||
| Feld | Wert |
|
||||
|------|------|
|
||||
| **Method** | `GET` |
|
||||
| **URL** | `https://vault.deinedomain.tld/api/secrets/GITEA_WEBHOOK_SECRET` |
|
||||
| **Response Format** | `JSON` |
|
||||
| **Authentication** | `Header Auth` |
|
||||
| **Header Name** | `Authorization` |
|
||||
| **Header Value** | `Bearer <API-Key>` |
|
||||
|
||||
Nach Ausführen erhältst du eine Antwort ähnlich:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "123abc",
|
||||
"type": "note",
|
||||
"name": "GITEA_WEBHOOK_SECRET",
|
||||
"notes": "meinSuperSecret123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Im nächsten Node kannst du den Wert dynamisch verwenden:
|
||||
|
||||
```js
|
||||
={{ $json["data"]["notes"] }}
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Verwende dieses Secret z. B. im Webhook-Verification-Schritt, SSH-Node oder API-Aufruf.
|
||||
> So bleibt der Schlüssel außerhalb des Workflows gespeichert und kann zentral rotiert werden.
|
||||
|
||||
### Secret-Rotation automatisieren (optional)
|
||||
|
||||
Vaultwarden unterstützt über die API das Ersetzen von Werten.
|
||||
Damit kannst du in regelmäßigen Abständen neue Tokens oder Passwörter generieren und direkt im Vault ablegen.
|
||||
|
||||
In n8n kombinierst du dazu:
|
||||
- **Cron Node** (z. B. alle 30 Tage)
|
||||
- **HTTP Request (PUT)** mit neuem Tokenwert
|
||||
- **Discord Node** oder **Email Node** zur Benachrichtigung
|
||||
|
||||
Beispiel für API-Request zur Aktualisierung:
|
||||
|
||||
| Feld | Wert |
|
||||
|------|------|
|
||||
| **Method** | `PUT` |
|
||||
| **URL** | `https://vault.deinedomain.tld/api/secrets/GITEA_WEBHOOK_SECRET` |
|
||||
| **Body Content Type** | `JSON` |
|
||||
| **JSON Body** | `{"notes":"{{ $json.newToken }}"}` |
|
||||
| **Header Auth** | `Bearer <API-Key>` |
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Automatische Token-Rotation ist optional, aber sehr empfehlenswert, wenn dein System dauerhaft online ist.
|
||||
> So stellst du sicher, dass alte oder kompromittierte Tokens keine Gefahr darstellen.
|
||||
|
||||
👉 **Screenshot geeignet:** n8n-Workflow mit HTTP-Node zum Secret-Abruf vor einem SSH- oder API-Schritt
|
||||
|
||||
Mit dieser Integration bleiben alle Zugangsdaten dezentral verwaltet, verschlüsselt gespeichert und jederzeit austauschbar.
|
||||
Vaultwarden wird so zur zentralen Sicherheitsinstanz deiner gesamten Automatisierung im UCC.
|
||||
|
||||
---
|
||||
|
||||
## Abschluss
|
||||
|
||||
Mit der Erweiterung aus diesem Premium-Kapitel hast du aus einer einfachen Git-Verwaltung eine vollwertige Automatisierungsplattform gemacht.
|
||||
Gitea arbeitet nun nicht nur als Code- und Dokumentenarchiv, sondern als aktiver Bestandteil deiner Produktions- und Streaming-Infrastruktur.
|
||||
Durch Actions, n8n und Vaultwarden ist ein geschlossenes System entstanden, das Builds, Backups und Deployments selbstständig steuert – komplett lokal und unabhängig von externen CI-Diensten.
|
||||
|
||||
Die Kombination aus **Self-Hosted Runnern** und **n8n-Webhooks** ermöglicht dir flexible Workflows:
|
||||
Markdown-Dateien können automatisch in PDFs umgewandelt, Projektdaten gesichert oder Statusmeldungen an Discord gesendet werden.
|
||||
Vaultwarden sorgt dabei dafür, dass sensible Daten nie im Klartext auftauchen und jederzeit zentral ausgetauscht werden können.
|
||||
|
||||
> [!NOTE]
|
||||
> Mit dieser Integration hast du die Grundlage geschaffen, um alle Projekte im UCC miteinander zu verknüpfen:
|
||||
> Git speichert und versioniert, n8n führt Aktionen aus, Vaultwarden verwaltet die Secrets –
|
||||
> zusammen bilden sie eine vollständig automatisierte Entwicklungs- und Betriebsumgebung.
|
||||
|
||||
Ein korrekt eingerichtetes CI-System mit Gitea bietet dir:
|
||||
|
||||
- Lokale, cloudfreie CI/CD-Pipelines
|
||||
- Self-Hosted Runner für flexible Aufgabenverteilung
|
||||
- Direkte Anbindung an n8n für Benachrichtigung, Backup oder Deployment
|
||||
- Sichere Geheimnisverwaltung durch Vaultwarden
|
||||
- Vollständige Kontrolle über Build- und Release-Prozesse
|
||||
|
||||
👉 **Screenshot geeignet:** Gitea Actions-Tab mit laufendem Workflow und verknüpftem Runner
|
||||
|
||||
Mit Abschluss dieses Kapitels hast du eine robuste, skalierbare und sichere Basis geschaffen,
|
||||
auf der künftige Premium-Kapitel wie **automatische Dokumentverteilung**, **Build-Pipelines für UCC-Projekte** oder **integrierte Testumgebungen** direkt aufbauen können.
|
||||
Gitea wird so zu einem der zentralen Werkzeuge im UCC – effizient, transparent und vollständig unter deiner Kontrolle.
|
||||
Reference in New Issue
Block a user