8 Commits
Author SHA1 Message Date
claude 309826ccb6 feat(release-plugin): Auto-Modus bei push auf main
- Trigger-Erkennung im "Variablen ableiten"-Step (refs/tags/* vs refs/heads/*)
- Auto-Modus: Header-Version aus Plugin-PHP lesen, Tag v{version} prüfen
  → wenn Tag existiert: stiller Skip; sonst Pre-Flight + Build + Release
- Release-API-Call mit target_commitish, damit Tag im Auto-Modus aus
  dem aktuellen HEAD-Commit erzeugt wird
- Klassischer Tag-Push-Modus bleibt unverändert
- Nachfolgende Steps mit if: steps.vars.outputs.skip != 'true' geguarded
2026-05-12 05:34:06 +00:00
claude b6d49882ea docs(readme): Caller-Workflow auf branches:[main] + tags Trigger umstellen 2026-05-12 05:32:40 +00:00
claude 6f85fd3e4b docs(changelog): initial CHANGELOG.md mit Historie bis v1.2.0 2026-05-12 05:32:04 +00:00
claude 7334bd587a feat(deploy-rsync): Reusable Workflow fuer rsync-Deploys via SSH
Wiederverwendbare Action fuer alle IDF-Repos, die per rsync auf einen
Plesk-Server deployen wollen (Flow, Apps, kuenftig weitere).

Inputs: ssh_host, ssh_user, deploy_path, optional ref + extra_excludes.
Secret: SSH_PRIVATE_KEY (im Caller-Repo zu hinterlegen).
Standard-Excludes: .git/, .gitea/, .gitignore, CHANGELOG.md, README.md.
2026-04-27 17:29:24 +00:00
claude 0f7062b0c4 fix(release-plugin): Regex — .* greedy mit DOTALL schluckt bis EOF
Mit re.DOTALL matcht .* auch Newlines. Kombination mit (?:\s+.*)?$\n
führte dazu, dass die erste Version-Zeile bis zum Dateiende gefressen
wurde und Capture-Group leer blieb (0 Zeichen).

Fix: .* durch [^\n]* ersetzen, damit erste Zeile genau auf eine Zeile
begrenzt ist. Gilt für beide Changelog-Parser (Block-Extraktion und
entry_pat in readme.txt-Generator).
2026-04-24 14:18:45 +00:00
claude 6c8f3f2d21 docs(runner-setup): Node.js als Pflicht-Voraussetzung für actions/checkout 2026-04-24 14:13:29 +00:00
claude 1eb87ecac6 docs(runner-setup): Sichtbarkeit idf-ci, Security-Einschätzung 2026-04-24 13:35:53 +00:00
claude 64d20da165 docs(runner-setup): Labels gehören in config.yaml, nicht ins register-CLI 2026-04-24 13:02:38 +00:00
5 changed files with 294 additions and 24 deletions
+114
View File
@@ -0,0 +1,114 @@
name: Reusable Deploy via rsync
# Wiederverwendbarer Deploy-Workflow fuer IDF-Projekte.
# Repository-Pfad: ideenfabrik/idf-ci/.gitea/workflows/deploy-rsync.yml
#
# Aufruf aus einem Caller-Repo:
#
# jobs:
# deploy:
# uses: ideenfabrik/idf-ci/.gitea/workflows/deploy-rsync.yml@v1
# with:
# ssh_host: ${{ vars.SSH_HOST }}
# ssh_user: ${{ vars.SSH_USER }}
# deploy_path: ${{ vars.DEPLOY_PATH }}
# ref: ${{ github.event.inputs.ref || github.ref }}
# extra_excludes: |
# kiosk-config.php
# kiosk-config.example.php
# secrets: inherit
#
# Caller-Repo muss hinterlegen:
# Secret SSH_PRIVATE_KEY private Key fuer den Deploy-User
# Variable SSH_HOST Host/IP des Live-Servers
# Variable SSH_USER SSH-Login-User
# Variable DEPLOY_PATH Zielpfad auf dem Server
on:
workflow_call:
inputs:
ssh_host:
required: true
type: string
description: 'Ziel-Host fuer rsync ueber SSH'
ssh_user:
required: true
type: string
description: 'SSH-Login-User'
deploy_path:
required: true
type: string
description: 'Zielpfad auf dem Server'
ref:
required: false
type: string
default: ''
description: 'Branch/Tag zum Deployen (default: github.ref)'
extra_excludes:
required: false
type: string
default: ''
description: 'Zusaetzliche rsync --exclude Patterns, eine pro Zeile'
secrets:
SSH_PRIVATE_KEY:
required: true
jobs:
deploy:
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
- name: Verify rsync + ssh available
run: |
command -v rsync >/dev/null || { echo "::error::rsync ist auf dem Runner nicht installiert"; exit 1; }
command -v ssh >/dev/null || { echo "::error::ssh ist auf dem Runner nicht installiert"; exit 1; }
rsync --version | head -1
ssh -V
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
KEY_FILE="$HOME/.ssh/id_ed25519_deploy_${{ github.run_id }}"
printf '%s\n' "${{ secrets.SSH_PRIVATE_KEY }}" > "$KEY_FILE"
chmod 600 "$KEY_FILE"
echo "DEPLOY_KEY_FILE=$KEY_FILE" >> "$GITHUB_ENV"
ssh-keyscan -H "${{ inputs.ssh_host }}" >> ~/.ssh/known_hosts 2>/dev/null
- name: Deploy via rsync
env:
EXTRA_EXCLUDES: ${{ inputs.extra_excludes }}
run: |
# Standard-Excludes -- gilt fuer alle Deploys
BASE_EXCLUDES=(
--exclude='.git/'
--exclude='.gitea/'
--exclude='.gitignore'
--exclude='CHANGELOG.md'
--exclude='README.md'
)
# Caller-spezifische Excludes (multiline -> array)
EXTRA=()
while IFS= read -r line; do
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[ -n "$line" ] && EXTRA+=( "--exclude=$line" )
done <<<"$EXTRA_EXCLUDES"
rsync -avz "${BASE_EXCLUDES[@]}" "${EXTRA[@]}" \
-e "ssh -i $DEPLOY_KEY_FILE -o StrictHostKeyChecking=yes" \
./ \
"${{ inputs.ssh_user }}@${{ inputs.ssh_host }}:${{ inputs.deploy_path }}/"
- name: Cleanup deploy key
if: always()
run: rm -f "$DEPLOY_KEY_FILE"
- name: Done
run: |
echo "Deployed ${{ github.ref_name }} to ${{ inputs.deploy_path }} on ${{ inputs.ssh_host }}"
+62 -11
View File
@@ -21,7 +21,12 @@ name: Release Plugin
# "== Changelog ==" in readme.txt wird beim Build aus CHANGELOG.md
# generiert und ist nur im fertigen ZIP vollständig.
#
# Trigger im aufrufenden Repo: push eines Tags der Form v<MAJOR>.<MINOR>.<PATCH>.
# Trigger im aufrufenden Repo:
# - push auf main → Auto-Modus: Header-Version wird ausgelesen, Tag v<X.Y.Z>
# wird erzeugt (sofern noch nicht vorhanden), Release wird gebaut.
# Existiert der Tag bereits, beendet der Workflow still (kein Release).
# - push eines Tags v<MAJOR>.<MINOR>.<PATCH> → klassischer Modus: Tag wird
# direkt für den Release verwendet.
# Ausführung auf dem IDF-Runner (self-hosted, Host-Modus).
on:
@@ -50,14 +55,12 @@ jobs:
- name: Checkout Tag
uses: actions/checkout@v4
- name: Variablen ableiten
- name: Variablen ableiten (Auto-Modus oder Tag-Modus)
id: vars
shell: bash
run: |
set -euo pipefail
SLUG="${{ inputs.slug }}"
TAG="${GITHUB_REF_NAME}"
VERSION="${TAG#v}"
MAIN_FILE="${{ inputs.main_file }}"
[[ -z "$MAIN_FILE" ]] && MAIN_FILE="${SLUG}.php"
CONST="${{ inputs.version_constant }}"
@@ -66,6 +69,37 @@ jobs:
base_upper="$(echo "$base" | tr '[:lower:]-' '[:upper:]_')"
CONST="IDF_${base_upper}_VERSION"
fi
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
# Klassischer Modus — Tag-Push
MODE="tag"
TAG="${GITHUB_REF_NAME}"
VERSION="${TAG#v}"
else
# Auto-Modus — Push auf Branch (z.B. main)
MODE="auto"
if [[ ! -f "$MAIN_FILE" ]]; then
echo "::error::Plugin-Haupt-PHP nicht gefunden: $MAIN_FILE"
exit 1
fi
HEADER_VERSION="$(grep -iE '^[[:space:]]*\*?[[:space:]]*Version:[[:space:]]*' "$MAIN_FILE" | head -n1 | sed -E 's/^[^0-9]*([0-9]+\.[0-9]+\.[0-9]+).*$/\1/')"
if [[ -z "$HEADER_VERSION" ]]; then
echo "::error::Header-Version in $MAIN_FILE nicht lesbar"
exit 1
fi
VERSION="$HEADER_VERSION"
TAG="v$VERSION"
fi
# Skip-Check (nur Auto-Modus): existiert der Tag schon, kein neues Release
SKIP=false
if [[ "$MODE" == "auto" ]]; then
if git ls-remote --tags origin "refs/tags/${TAG}" 2>/dev/null | grep -q "refs/tags/${TAG}"; then
echo "Tag $TAG existiert bereits — Skip (kein neues Release)."
SKIP=true
fi
fi
ZIPNAME="${SLUG}_v${VERSION}.zip"
{
echo "slug=$SLUG"
@@ -74,10 +108,13 @@ jobs:
echo "main_file=$MAIN_FILE"
echo "const=$CONST"
echo "zipname=$ZIPNAME"
echo "mode=$MODE"
echo "skip=$SKIP"
} >> "$GITHUB_OUTPUT"
echo "Ermittelt: slug=$SLUG tag=$TAG version=$VERSION main_file=$MAIN_FILE const=$CONST"
echo "Ermittelt: mode=$MODE tag=$TAG version=$VERSION main_file=$MAIN_FILE const=$CONST skip=$SKIP"
- name: Pre-Flight — Tag-Format
if: steps.vars.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
@@ -89,6 +126,7 @@ jobs:
echo "Tag-Format OK: $TAG"
- name: Pre-Flight — Pflichtdateien
if: steps.vars.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
@@ -102,6 +140,7 @@ jobs:
echo "Pflichtdateien OK"
- name: Pre-Flight — Plugin-Header-Version
if: steps.vars.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
@@ -123,6 +162,7 @@ jobs:
echo "Header-Version OK: $HEADER_VERSION"
- name: Pre-Flight — Versions-Konstante
if: steps.vars.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
@@ -141,6 +181,7 @@ jobs:
echo "Konstante OK: $CONST=$CONST_VERSION"
- name: Pre-Flight — readme.txt Stable tag
if: steps.vars.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
@@ -157,6 +198,7 @@ jobs:
echo "Stable tag OK: $STABLE_TAG"
- name: Changelog-Block aus CHANGELOG.md extrahieren
if: steps.vars.outputs.skip != 'true'
id: changelog
shell: bash
env:
@@ -168,10 +210,11 @@ jobs:
version = os.environ["VERSION"]
with open("CHANGELOG.md", encoding="utf-8") as f:
content = f.read()
# "## v<version>" optional gefolgt von " — Datum" und beliebigem Suffix,
# Inhalt bis zum nächsten "## v<x.y.z>" oder einer "---"-Trennzeile oder EOF.
# "## v<version>" gefolgt von beliebigem Rest der Zeile (auch "— Datum"),
# Inhalt ab nächster Zeile bis zum nächsten "## v<x.y.z>" oder "---" oder EOF.
# [^\n]* statt .*, damit die erste Zeile nicht mit DOTALL bis EOF gefressen wird.
pat = re.compile(
r"^##\s+v" + re.escape(version) + r"(?:\s+.*)?$\n(.*?)(?=^##\s+v\d|^---\s*$|\Z)",
r"^##\s+v" + re.escape(version) + r"[^\n]*\n(.*?)(?=^##\s+v\d|^---\s*$|\Z)",
re.MULTILINE | re.DOTALL,
)
m = pat.search(content)
@@ -189,6 +232,7 @@ jobs:
echo "Changelog-Block extrahiert (${#BLOCK} Zeichen)."
- name: ZIP bauen (readme.txt Changelog-Section aus CHANGELOG.md generieren)
if: steps.vars.outputs.skip != 'true'
id: zip
shell: bash
env:
@@ -229,9 +273,10 @@ jobs:
readme_path = pathlib.Path(sys.argv[1])
changelog_path = pathlib.Path(sys.argv[2])
# CHANGELOG.md parsen: Einträge "## v<x.y.z> — <Datum?>" bis zum nächsten "## v…" oder "---" oder EOF
# CHANGELOG.md parsen: Einträge "## v<x.y.z> — <Datum?>" bis zum nächsten "## v…" oder "---" oder EOF.
# [^\n]* für die erste Zeile, damit DOTALL nicht greedy über Zeilen frisst.
entry_pat = re.compile(
r"^##\s+v(\d+\.\d+\.\d+)(?:\s+—\s+(\S+))?(?:\s+.*)?$\n(.*?)(?=^##\s+v\d|^---\s*$|\Z)",
r"^##\s+v(\d+\.\d+\.\d+)(?:\s+—\s+(\S+))?[^\n]*\n(.*?)(?=^##\s+v\d|^---\s*$|\Z)",
re.MULTILINE | re.DOTALL,
)
content = changelog_path.read_text(encoding="utf-8")
@@ -301,6 +346,7 @@ jobs:
echo "zippath=/tmp/$ZIPNAME" >> "$GITHUB_OUTPUT"
- name: Gitea-Release anlegen und Asset anhängen
if: steps.vars.outputs.skip != 'true'
shell: bash
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN || github.token }}
@@ -312,11 +358,16 @@ jobs:
set -euo pipefail
SERVER="${GITHUB_SERVER_URL:-https://git.ihre-ideenfabrik.de}"
REPO="${GITHUB_REPOSITORY}"
# target_commitish setzt den Commit, aus dem der Tag erzeugt wird,
# falls er noch nicht existiert (Auto-Modus). Bei Tag-Push ist es egal,
# weil der Tag schon den Commit kennt — Gitea ignoriert das Feld dann.
TARGET_SHA="${GITHUB_SHA}"
PAYLOAD="$(jq -n \
--arg tag "$TAG" \
--arg name "$TAG" \
--arg body "$BODY" \
'{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:false}')"
--arg target "$TARGET_SHA" \
'{tag_name:$tag, name:$name, body:$body, target_commitish:$target, draft:false, prerelease:false}')"
RESP="$(curl -sSf \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
+31
View File
@@ -0,0 +1,31 @@
# Changelog
## v1.2.0
### Hinzugefügt
- Auto-Modus für `release-plugin.yml`: Push auf main triggert ein Release, sobald die Header-Version eines Plugins erhöht wurde. Tag wird automatisch erzeugt, kein manueller `git tag` mehr nötig.
- Wenn der Tag zur aktuellen Header-Version bereits existiert, beendet der Workflow still (kein neues Release, keine Mail).
- Caller-Repos können den Workflow weiterhin im klassischen Modus (Tag-Push) nutzen — beide Trigger werden unterstützt.
### Geändert
- Release-API-Call nutzt jetzt `target_commitish`, damit Tags im Auto-Modus aus dem aktuellen Commit erzeugt werden können.
## v1.1.0
### Geändert
- Frühere Iteration des Workflows (vor Auto-Modus). Details siehe Git-Historie.
## v1.0.2
### Behoben
- Diverse Pre-Flight-Korrekturen.
## v1.0.1
### Behoben
- Initial-Hotfix nach v1.0.0.
## v1.0.0
### Hinzugefügt
- Erste produktive Version von `release-plugin.yml` (Tag-Push-Modus).
+7 -2
View File
@@ -25,7 +25,8 @@ In jedem Plugin-Repo (`ideenfabrik/idf-<slug>`) liegt ein schlanker Caller-Workf
name: Release
on:
push:
tags: ['v*.*.*']
branches: [main] # Auto-Modus: Release sobald Header-Version hochgezogen wurde
tags: ['v*.*.*'] # Klassischer Modus: manuelle Tags sind weiterhin möglich
jobs:
release:
@@ -35,7 +36,11 @@ jobs:
secrets: inherit
```
Für die Anwendung reicht `git tag v1.2.3 && git push --tags`. Alles andere macht der Reusable Workflow. Details und Fehlerdiagnose im [Caller-Template](docs/plugin-caller-template.md).
**Auto-Modus (Standard, seit v1.2.0):** Plugin-Header-Version, PHP-Konstante, `Stable tag:` und CHANGELOG-Block hochziehen, committen, nach `main` pushen. Der Workflow erzeugt Tag `v<X.Y.Z>` selbst und legt das Release an. Wenn die Version nicht erhöht wurde, beendet der Workflow still (kein Release, keine Mail).
**Klassischer Modus:** Falls du lieber explizit taggst — `git tag v1.2.3 && git push --tags` triggert denselben Workflow im Tag-Modus. Beide Pfade können koexistieren.
Details und Fehlerdiagnose im [Caller-Template](docs/plugin-caller-template.md).
## Pflichtdateien im Plugin-Repo
+80 -11
View File
@@ -10,10 +10,55 @@ Der Runner ist ein einzelnes ausführbares Binary (`act_runner`), das sich beim
Der Runner **baut** die Plugin-ZIPs und legt sie als Gitea-Release-Assets ab. Er **verteilt** nichts an Kunden — das ist Aufgabe des Master-Key-Plugins.
## Voraussetzungen in Gitea
Damit der Runner Reusable-Workflows aus `idf-ci` lesen kann, muss das Repo für ihn erreichbar sein. Der beim Job generierte Token ist nur für das aufrufende Plugin-Repo gültig und kann kein anderes privates Repo klonen.
**Daher: `ideenfabrik/idf-ci` muss auf Sichtbarkeit „Public" (oder „Limited", falls in der Gitea-Version verfügbar) stehen.**
- **Public** — lesbar für alle, die die Gitea-Instanz erreichen.
- **Limited** — lesbar für alle eingeloggten Gitea-User. In neueren Gitea-Versionen verfügbar. Für unseren Fall äquivalent zu Public, weil der Runner eh mit Token angemeldet ist.
- **Private** — funktioniert nicht, weil der Job-Token keinen Repo-übergreifenden Lesezugriff hat.
**Zusätzlich:** Gitea-weite Einstellung `REQUIRE_SIGNIN_VIEW` muss auf `false` stehen. Sonst überschreibt sie die per-Repo-Sichtbarkeit und erzwingt Login auch für Public-Repos. Bei Docker-Setups wird die `app.ini` oft aus Environment-Variablen regeneriert — Änderungen direkt in der Datei gehen beim Container-Neustart verloren. Korrekter Weg: Environment-Variable setzen:
```
GITEA__service__REQUIRE_SIGNIN_VIEW=false
```
Je nach Setup in der `docker-compose.yml`, im systemd-Unit oder in der Plesk-Gitea-Extension. Container neu starten.
### Ist das ein Sicherheitsproblem?
Das Risiko ist niedrig, weil das Repo ausschließlich CI-Baustoff enthält:
- Keine Access-Tokens, API-Keys oder Passwörter (Secrets werden zur Laufzeit injiziert, nie committet)
- Keine Plugin-Quellcodes, keine Business-Logik, keine Kundendaten
- Kein Zugriff auf Produktions-Systeme
Öffentlich lesbar sind nur: der Release-Workflow (Bau-Logik), die Doku und das Caller-Template. Standard-CI-Praxis, wie bei vielen Open-Source-Projekten.
Falls der Gitea-Server aus dem Internet erreichbar ist, bedeutet „Public" tatsächlich weltweit lesbar. Ist der Gitea-Server nur intern/VPN-gebunden erreichbar, ist „Public" effektiv identisch mit „intern lesbar". Im Zweifel: Sichtbarkeit auf „Limited" setzen, dann muss man wenigstens angemeldet sein, um zu lesen.
### Umstellung der Repo-Sichtbarkeit
1. Repo öffnen: https://git.ihre-ideenfabrik.de/ideenfabrik/idf-ci
2. Settings → ganz runterscrollen → „Danger Zone"
3. **Change Visibility** → Public (oder Limited, falls verfügbar)
4. Bestätigen
Einmalige Aktion pro Instanz.
## Voraussetzungen auf dem Server
- Linux (Debian/Ubuntu; der IDF-Plesk-Host reicht)
- Outbound-HTTPS zu `git.ihre-ideenfabrik.de`
- Outbound-HTTPS zu `git.ihre-ideenfabrik.de` und zu `github.com` (für standard Gitea-/GitHub-Actions wie `actions/checkout`)
- **Node.js 20 (LTS) oder neuer** — zahlreiche Actions (`actions/checkout`, viele weitere) sind JavaScript-basiert und benötigen Node zur Ausführung. Installation (Debian/Ubuntu):
```bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt install -y nodejs
```
Prüfen: `node --version` (mindestens `v20.x`).
- Standard-Tools verfügbar: `bash`, `python3`, `zip`, `unzip`, `rsync`, `curl`, `jq`, `git`
Falls einzelne Tools fehlen: `sudo apt install -y python3 zip unzip rsync curl jq git`
- systemd (Standard bei Debian/Ubuntu)
@@ -35,16 +80,30 @@ sudo chmod +x act_runner
Architektur-Variante: bei ARM-Servern `linux-arm64` statt `linux-amd64` wählen.
### 2. Konfigurationsdatei erzeugen
### 2. Konfigurationsdatei erzeugen und Labels eintragen
```bash
sudo ./act_runner generate-config | sudo tee config.yaml > /dev/null
```
Die Default-Konfiguration ist ok. Relevante Stellen, falls angepasst werden muss:
**Wichtig:** `act_runner` akzeptiert Labels **nur aus der Config**, nicht als CLI-Flag beim Registrieren. Die Default-Labels zeigen auf Docker-Images, die wir nicht brauchen. Ersetze sie durch Host-Modus-Labels:
- `runner.capacity` — wie viele Jobs parallel laufen (Default: 1, für den Anfang ok)
- `runner.labels` — Labels, die der Runner kann. Hier setzen wir sie beim `register` explizit.
```bash
sudo nano /opt/act_runner/config.yaml
```
Im Block `runner:` den vorhandenen `labels:`-Block **vollständig ersetzen** durch:
```yaml
labels:
- "self-hosted:host"
- "linux:host"
- "x64:host"
```
Suffix `:host` = Jobs laufen direkt auf dem Host, ohne Docker. Damit sparen wir uns Docker als weitere Abhängigkeit. Speichern und verlassen.
Andere Default-Werte in `config.yaml` können bleiben. Bei Bedarf später anpassen: `runner.capacity` für parallele Jobs (Default 1 ist für den Anfang ok).
### 3. Registrierungstoken in Gitea holen
@@ -53,23 +112,22 @@ Organisation-scoped Runner (empfohlen, läuft für alle IDF-Repos):
1. In Gitea einloggen als Admin.
2. Navigation: Organization **ideenfabrik** → Settings → **Actions** → **Runners**.
3. Button **„Create new Runner"**.
4. Registrierungstoken kopieren.
4. Registrierungstoken kopieren. Token ist einmalig — für eine zweite Registrierung einen neuen generieren.
Alternativ: Instance-weit unter Site Administration → Actions → Runners (dann verfügbar für alle Organisationen).
### 4. Runner registrieren
```bash
sudo ./act_runner register \
sudo /opt/act_runner/act_runner register \
--config /opt/act_runner/config.yaml \
--instance https://git.ihre-ideenfabrik.de \
--token <TOKEN_AUS_SCHRITT_3> \
--name idf-plesk-runner \
--labels self-hosted:host,linux:host,x64:host \
--no-interactive
```
Die Labels `self-hosted`, `linux` und `x64` decken die Workflow-`runs-on:`-Kombinationen ab. Suffix `:host` = Jobs laufen direkt auf dem Host, ohne Docker. Damit sparen wir uns Docker als weitere Abhängigkeit.
Kein `--labels`-Flag — die Labels kommen aus der Config (siehe Schritt 2). Falls versehentlich mitgegeben, erscheint die Warnung `Labels from command will be ignored, use labels defined in config file.`
Nach erfolgreicher Registrierung liegt eine Datei `.runner` im Arbeitsverzeichnis — die ist der Runner-State, nicht weiterkopieren oder committen.
@@ -111,6 +169,7 @@ In Gitea prüfen:
1. Organization **ideenfabrik** → Settings → **Actions** → **Runners**.
2. Der neue Runner taucht mit Status **Online** auf.
3. In der Labels-Spalte müssen `self-hosted`, `linux`, `x64` stehen. Wenn stattdessen `ubuntu-latest` o. Ä. dort stehen, wurde ohne die Labels aus Schritt 2 registriert → Schritt 2 prüfen und bei Runs-Hängen neu registrieren (siehe Troubleshooting).
### 7. Pilot-Test
@@ -120,7 +179,8 @@ Einen Plugin-Repo mit Caller-Workflow nehmen (z. B. `idf-post-prefix`), Tag `v<X
- **Logs:** `sudo journalctl -u act_runner -f` (tail-folgen) oder `journalctl -u act_runner --since '1 hour ago'`.
- **Runner-Update:** neues `act_runner`-Binary herunterladen, ersetzen, `sudo systemctl restart act_runner`.
- **Label-Änderung:** `act_runner register` mit neuen Labels erneut ausführen (alten Runner in Gitea-UI vorher deaktivieren), Service neu starten.
- **Label-Änderung:** Labels in `config.yaml` anpassen, Service neu starten (`sudo systemctl restart act_runner`). Der Daemon sendet die aktualisierten Labels beim nächsten Handshake an Gitea.
- **Neu-Registrierung (z. B. nach kaputtem State):** `sudo rm /opt/act_runner/.runner`, neuen Token aus Gitea holen, Schritt 4 erneut ausführen, Service neu starten.
- **Deregistrierung:** Runner in Gitea-UI löschen, `.runner`-Datei auf dem Server entfernen, Service stoppen.
## Server-Wechsel — Checkliste
@@ -136,8 +196,17 @@ Damit ist das komplette Build-System portabel: die Bauanleitung lebt in diesem R
## Troubleshooting
**Warnung `Labels from command will be ignored` beim Registrieren.**
Du hast `--labels …` als CLI-Flag gesetzt, aber `act_runner` nimmt Labels nur aus der Config. Schritt 2 durchgehen, Labels in `config.yaml` eintragen. Dann `sudo rm /opt/act_runner/.runner`, neuen Token holen, Registrierung ohne `--labels`-Flag erneut ausführen.
**Run bricht ab mit `Unable to clone … authentication required`.**
Entweder `idf-ci` steht auf Private, oder Gitea-weit ist `REQUIRE_SIGNIN_VIEW = true` aktiv. Abschnitt „Voraussetzungen in Gitea" durchgehen. Bei Docker-Setup die Environment-Variable `GITEA__service__REQUIRE_SIGNIN_VIEW=false` setzen und Container neu starten. Danach den Run über den Re-run-Button in der Gitea-Actions-UI neu starten.
**Run bricht ab mit `Cannot find: node in PATH`.**
Node.js ist nicht installiert oder nicht im PATH. Abschnitt „Voraussetzungen auf dem Server" durchgehen und Node 20 LTS installieren. Nach `sudo apt install -y nodejs` läuft der nächste Re-run.
**Runs hängen auf `queued`, Runner steht aber auf Online.**
Label-Mismatch. Der Workflow schreibt `runs-on: self-hosted`, der Runner muss dasselbe Label haben. `act_runner register --labels …` erneut, Service neu starten.
Label-Mismatch. Der Workflow schreibt `runs-on: self-hosted`, der Runner muss dasselbe Label anbieten. In der Gitea-UI die Labels des Runners prüfen — stehen dort nicht `self-hosted, linux, x64`, liegt es an Schritt 2.
**Runner zeigt `offline` nach systemd-Start.**
`sudo journalctl -u act_runner --since '5 min ago'` lesen. Häufige Ursachen: Token abgelaufen, DNS funktioniert nicht, `git.ihre-ideenfabrik.de` nicht erreichbar.