# Leitfaden: Continuous Integration **Stand: 22.08.2026** · Workflow: [`.gitea/workflows/ci.yml`](../.gitea/workflows/ci.yml) > **Status: Der Workflow liegt, der Runner fehlt noch.** Gitea 1.26.2 hat Actions aktiviert > (`has_actions: true`), aber auf der Instanz ist **kein einziger Runner registriert** — geprüft > auf Repo-, Benutzer- und Instanzebene. Bis Abschnitt 3 erledigt ist, passiert bei einem Push > nichts. Der Workflow ist dann sofort lauffähig, ohne weitere Änderung. --- ## 1. Warum überhaupt PolyTrader soll auf einem Linux-Server laufen. Der plattformneutrale Zustand wurde am 22.08.2026 mit dem WinForms-Ausbau hergestellt — und er **driftet ohne Wächter wieder weg**. Eine einzige `net10.0-windows`-Zeile oder ein `using System.Drawing` genügt, und der Linux-Build ist kaputt, ohne dass es auf einer Windows-Entwicklermaschine auffällt: dort baut es weiter. Genau deshalb läuft die CI **auf Linux**. Sie ist kein Selbstzweck, sondern die einzige Instanz, die den mühsam hergestellten Zustand verteidigt. ## 2. Was geprüft wird Zwei Jobs, beide auf `ubuntu-latest`: ### `build-test` — Build & Tests | Schritt | Zweck | |---|---| | `dotnet restore` | Der lokale Feed `lib/nuget` (Deploymentcenter-SDK) ist relativ eingebunden und liegt im Repo — nichts einzurichten | | `dotnet build -c Release` | Baut alle 7 Projekte | | `dotnet test` | Die 476 Tests. Brauchen **keine Datenbank** — sie laufen gegen EF-InMemory | | `dotnet publish -r linux-x64` | Kein Selbstzweck: hier fällt auf, wenn ein Paket doch windows-only ist | | Publish-Prüfung | Erwartet `PolyTrader.App.Avalonia`, `appsettings.json`, `setup.json` und `libSkiaSharp.so` — ohne die native Skia-Bibliothek wäre Avalonia auf Linux nicht lauffähig | ### `guard` — Plattformneutralität & Hygiene | Prüfung | Schlägt fehl bei | |---|---| | Zielframeworks | irgendeinem `…-windows` in einer `.csproj` | | WinForms/WPF | `true` oder `true` | | Namespaces | echten `using System.Windows.Forms;` / `using System.Drawing;`-Direktiven in `src/` oder `tests/` | | Secret-Dateien | `deploy/packager.config.json`, `appsettings.Local.json`, `master.key`, `openrouter.key`, `.gitea-token` oder `server_settings.xml` **versioniert**; oder einem Deploymentcenter-Token im Klartext | | Schwachstellen | `dotnet list package --vulnerable --include-transitive` findet etwas | **Zur Namespace-Prüfung:** Sie trifft bewusst nur echte `using`-Direktiven, keine Kommentare. Im Bestand steht an vielen Stellen erklärt, *warum* `System.Drawing` nicht verwendet wird — das darf keinen Fehlalarm auslösen. Beide Richtungen sind gegengeprüft. **Zum Schwachstellen-Check:** Das ist Punkt 1 der wiederkehrenden Audit-Checkliste aus [`sicherheit/SICHERHEITSKONZEPT.md`](./sicherheit/SICHERHEITSKONZEPT.md) — läuft ab jetzt bei jedem Push statt quartalsweise von Hand. Er würde zum Beispiel anschlagen, wenn der `Newtonsoft.Json`-Pin im Core entfernt wird: Nethereum 6.1.0 löst dann transitiv auf 11.0.2 auf (GHSA-5crp-9r3c-p9vr, Schweregrad hoch). > Die Warnungen des Builds (aktuell 15) lassen die CI **nicht** fehlschlagen. `-warnaserror` > wäre hier verfrüht: die vorhandenen Warnungen müssten erst abgearbeitet werden, sonst ist > die CI ab dem ersten Tag rot und wird ignoriert. ## 3. Runner einrichten (einmalig) Ohne Runner führt Gitea den Workflow nicht aus. Der Runner ist ein eigenes Programm (`act_runner`), das sich beim Gitea-Server meldet und Jobs abholt. Er läuft sinnvollerweise **auf dem Gitea-Host** (`192.168.178.10`) oder jeder anderen Maschine im selben Netz, die Docker hat. ### 3.1 Registrierungstoken holen In der Weboberfläche: **Repo → Einstellungen → Actions → Runner → „Runner erstellen"**. Dort steht ein Token der Form `…`. Alternativ instanzweit unter **Website-Verwaltung → Actions → Runner**, wenn der Runner mehreren Repos dienen soll — für den Anfang genügt der Repo-Runner. ### 3.2 Runner per Docker starten ```bash docker run -d --restart always \ --name gitea-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /opt/gitea-runner:/data \ -e GITEA_INSTANCE_URL=http://192.168.178.10:8418 \ -e GITEA_RUNNER_REGISTRATION_TOKEN= \ -e GITEA_RUNNER_NAME=polytrader-runner \ -e GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-latest \ gitea/act_runner:latest ``` **Das `GITEA_RUNNER_LABELS`-Feld ist der kritische Teil.** Es bildet `runs-on: ubuntu-latest` aus dem Workflow auf ein Container-Image ab. Fehlt das Label, bleibt der Job auf „warten auf Runner" stehen, ohne Fehlermeldung. Das Image `catthehacker/ubuntu:act-latest` bringt Node mit, das `actions/checkout` und `actions/setup-dotnet` benötigen. Der Mount von `docker.sock` ist nötig, weil der Runner die Job-Container selbst startet. ### 3.3 Prüfen ```bash curl -s -H "Authorization: token $(cat ~/.gitea-token)" http://192.168.178.10:8418/api/v1/repos/Richard/PolyTraderSharp/actions/runners ``` Steht dort `"total_count": 0`, hat sich der Runner nicht registriert — dann in die Logs sehen: `docker logs gitea-runner`. Danach den Workflow von Hand anstoßen: **Repo → Actions → CI → „Run workflow"** (`workflow_dispatch` ist im Workflow vorgesehen). Der erste Lauf dauert länger, weil das .NET-SDK heruntergeladen wird. ## 4. Wenn kein Internetzugang besteht `actions/setup-dotnet` lädt das SDK von Microsoft. Ist der Runner offline, gibt es zwei Wege: 1. **Container-Image mit SDK** statt `setup-dotnet` — im Workflow je Job ergänzen: ```yaml container: image: mcr.microsoft.com/dotnet/sdk:10.0 ``` Dann muss das `setup-dotnet`-Steps-Paar entfallen. **Achtung:** Dieses Image bringt kein Node mit; `actions/checkout` braucht es. Entweder ein eigenes Image bauen (`dotnet/sdk:10.0` + `nodejs`) oder auschecken per `git clone` statt per Action. 2. **SDK im Runner-Image vorinstallieren** und `setup-dotnet` weglassen. Weg 1 mit eigenem Image ist der sauberere, sobald das Netz wirklich zu ist. ## 5. Was die CI (noch) nicht tut - **Kein Deployment.** Die Auslieferung läuft über den Deploymentcenter-Packager (`deploy/packager.config.json`, siehe Schnitt D-4). Das bewusst nicht automatisiert, solange die Live-Abnahme aussteht. - **Keine Integrationstests gegen eine echte MySQL.** Die Testsuite läuft gegen EF-InMemory. Ein MySQL-Service-Container wäre der nächste sinnvolle Ausbauschritt, wenn die Repository-Schicht einmal gegen echtes SQL geprüft werden soll. - **Kein `-warnaserror`.** Siehe Kasten in Abschnitt 2.