Nachdem derselbe Xcode-Build-Job mehrere Tage lang problemlos ausgeführt wurde, meldet er beim Schreiben in DerivedData, beim Aktualisieren des Abhängigkeits-Caches oder beim Löschen alter Artefakte plötzlich Permission denied. Ein erneutes Auschecken des Repositorys kann das Problem vorübergehend beheben, doch beim nächsten parallelen Job tritt es wieder auf. In diesem Fall sollte weder Xcode als Erstes verdächtigt noch sollten die Verzeichnisrechte pauschal erweitert werden. Häufiger liegt die Ursache darin, dass ein Skript unter einem anderen Benutzer ausgeführt wurde, eine abweichende umask geerbt hat oder zusätzliche ACLs im Arbeitsbereich hinterlassen wurden.
Fehlerzustand zuerst sichern
Berechtigungsprobleme lassen sich besonders leicht durch „Bereinigen und erneut ausführen“ verschleiern. Erfassen Sie zuerst die Ausführungsidentität des Jobs, das Home-Verzeichnis, das aktuelle Verzeichnis und die standardmäßige Berechtigungsmaske. Untersuchen Sie anschließend den ersten fehlgeschlagenen Pfad, ohne sofort das gesamte Laufwerk zu durchsuchen.
printf 'user=%s
' "$(id -un)"
printf 'groups=%s
' "$(id -Gn)"
printf 'home=%s
' "$HOME"
printf 'cwd=%s
' "$PWD"
printf 'umask=%s
' "$(umask)"
target="${FAILED_PATH:?set FAILED_PATH first}"
stat -f 'owner=%Su group=%Sg mode=%Sp path=%N' "$target"
ls -led "$target"
stat zeigt, wem das Objekt gehört und welcher Basismodus gilt. ls -le gibt zusätzlich die ACL aus. Selbst wenn der Verzeichnismodus Schreibzugriff zu erlauben scheint, muss der Zugriff nicht erfolgreich sein: Fehlende Ausführungsrechte in einem übergeordneten Verzeichnis, einschränkende ACL-Einträge oder Dateien, die einem anderen Job-Benutzer gehören, können den Vorgang blockieren.
Prüfen Sie nur den ersten fehlgeschlagenen Pfad und dessen übergeordnete Verzeichnisse, Ebene für Ebene. Eine rekursive Änderung der Berechtigungen für den gesamten Arbeitsbereich zerstört nicht nur den Fehlerzustand, sondern kann auch Schlüssel, Caches und Build-Artefakte für nicht beteiligte Prozesse zugänglich machen.
Drei Arten von Berechtigungsdrift unterscheiden
Geänderter Eigentümer
Am häufigsten installiert ein Schritt Abhängigkeiten oder kopiert Dateien mit erhöhten Rechten und hinterlässt die erzeugten Inhalte anschließend im Besitz eines anderen Benutzers. Listen Sie zunächst alle Objekte auf, die nicht dem aktuellen Job-Benutzer gehören:
workspace="${WORKSPACE:?set WORKSPACE first}"
find "$workspace" -x ! -user "$(id -un)" -print
Konzentriert sich die Ausgabe auf ein einzelnes Job-Verzeichnis, sollte das Skript untersucht werden, das dieses Verzeichnis angelegt hat. Ein rekursives chown würde die eigentliche Ursache lediglich verdecken. Ein CI-Arbeitsbereich sollte vom Anlegen an dem Job-Benutzer gehören. Sind Operationen mit erhöhten Rechten unvermeidlich, dürfen deren Ausgaben nicht zurück in das Repository, den Cache oder DerivedData geschrieben werden.
Unerwartete ACLs
Finder-Aktionen, Migrationsskripte oder Kopierwerkzeuge können ACLs beibehalten. Werden in der Ausgabe von ls -le nummerierte Einträge unterhalb der Zeile mit den Basisrechten angezeigt, muss deren Herkunft geklärt werden. ACLs dürfen nur gezielt entfernt werden, wenn es sich nachweislich um einen neu erzeugbaren, temporären Arbeitsbereich handelt:
job_dir="${JOB_DIR:?set JOB_DIR first}"
chmod -RN "$job_dir"
Führen Sie diesen Befehl nicht für das Home-Verzeichnis eines Benutzers oder für Verzeichnisse mit Zugangsdaten aus. Rufen Sie nach der Korrektur erneut ls -led auf und vergewissern Sie sich, dass die ACL entfernt wurde und der Basismodus weiterhin den Anforderungen entspricht.
Abweichende umask
Interaktive SSH-Sitzungen, CI-Daemons und eigenständige Skripte lesen nicht zwangsläufig dieselbe Shell-Konfiguration. Erstellt ein Job einen Cache mit 077, kann ein späterer Job ihn möglicherweise selbst dann nicht wiederverwenden, wenn beide Benutzer derselben Gruppe angehören. Statt sich auf Startdateien zu verlassen, sollte die Maske am Einstiegspunkt des Jobs ausdrücklich festgelegt werden:
umask 022
install -d -m 0755 "$JOB_DIR"
install -d -m 0755 "$JOB_DIR/DerivedData"
install -d -m 0755 "$JOB_DIR/Artifacts"
Vertrauliche Daten gehören in ein separates Verzeichnis mit strengeren Berechtigungen. Die Rechte dürfen nicht pauschal gelockert werden, nur um einen Build-Cache gemeinsam zu nutzen.
Für jeden Job einen eigenen Arbeitsbereich verwenden
Wenn parallele Jobs dasselbe DerivedData- oder Archivverzeichnis verwenden, kann die Bereinigung eines Jobs mit Dateien kollidieren, in die ein anderer Job gerade schreibt. Bilden Sie den Verzeichnispfad aus Repository-Kennung, Commit-Kennung und Job-Nummer und übergeben Sie ihn ausdrücklich an den Build-Befehl.
run_id="${CI_RUN_ID:?set CI_RUN_ID first}"
root="$HOME/ci-runs/$run_id"
src="$root/source"
derived="$root/DerivedData"
artifacts="$root/Artifacts"
umask 022
install -d -m 0755 "$src" "$derived" "$artifacts"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-derivedDataPath "$derived" \
archive \
-archivePath "$artifacts/App.xcarchive"
Gemeinsame Caches und Job-Ausgaben sollten getrennt werden: Ein Cache für heruntergeladene Abhängigkeiten kann schreibgeschützt wiederverwendet werden, während DerivedData, Archive und Protokolle pro Job isoliert bleiben. Das reduziert wechselseitige Beeinträchtigungen der Berechtigungen und bewahrt zugleich den vollständigen Fehlerzustand bis zum Ende des Jobs.
| Pfadtyp | Empfohlener Eigentümer | Lebensdauer | Paralleles Schreiben zulässig |
|---|---|---|---|
| Quellcode-Checkout | Einzelner Job | Einzelner Lauf | Nein |
| DerivedData | Einzelner Job | Einzelner Lauf | Nein |
| Archive und Protokolle | Einzelner Job | Bereinigung nach Abnahme | Nein |
| Download-Cache | Fester Job-Benutzer | Jobübergreifend | Nur durch den Cache-Prozess aktualisieren |
Prüfkriterien vor und nach dem Build festlegen
Berechtigungsprüfungen sollten Teil des Job-Einstiegs sein und nicht erst nach einem Fehler manuell erfolgen. Prüfen Sie vor dem Build, ob die Verzeichnisse beschreibbar sind, dem richtigen Benutzer gehören und keine unerwarteten ACLs enthalten. Kontrollieren Sie nach dem Build, ob Dateien mit einem abweichenden Eigentümer erzeugt wurden. Die folgende Prüfung beendet den Job sofort, sobald eine Abweichung erkannt wird:
test -d "$JOB_DIR"
test -w "$JOB_DIR"
unexpected_owner="$(
find "$JOB_DIR" -x ! -user "$(id -un)" -print -quit
)"
if [ -n "$unexpected_owner" ]; then
printf 'unexpected owner: %s
' "$unexpected_owner" >&2
exit 1
fi
if ls -led "$JOB_DIR" | tail -n +2 | grep -q '^[[:space:]]*[0-9]:'; then
printf 'unexpected ACL on job directory
' >&2
exit 1
fi
Benötigt das Team einen gemeinsamen Cache, sollten der schreibberechtigte Cache-Prozess, der Aktualisierungszeitpunkt und das Verfahren für den atomaren Austausch separat festgelegt werden. Nicht alle Build-Jobs dürfen dasselbe Verzeichnis gleichzeitig ändern. Auch die Bereinigung nach Abschluss eines Jobs darf nur den Pfad löschen, der zur aktuellen run_id gehört. Zuvor muss geprüft werden, ob sich dieser Pfad unterhalb des erwarteten Stammverzeichnisses befindet.
Die Ursache im Skript beheben
Eine verlässliche Korrektur muss vier Fragen beantworten: Welcher Schritt hat die auffällige Datei angelegt, unter welchem Benutzer wurde sie erstellt, welche umask wurde geerbt und warum wurde in ein gemeinsames Verzeichnis geschrieben? Sobald diese Antworten vorliegen, sollten Verzeichniserstellung, Ausführungsidentität und Ausgabeort ausdrücklich im Skript definiert werden, statt von der Umgebung nach einer manuellen Anmeldung abzuhängen.
Auch bei der Ausführung von CI auf Cloud-Macs von RunnerVM sollte zu Beginn jedes Jobs ein minimaler Umgebungs-Snapshot ausgegeben und die aktuell auswählbare Konfiguration in der Konsole geprüft werden. Solange das Einstiegsskript nach einem Neustart der Maschine oder einer Verlagerung des Jobs die Verzeichnis- und Berechtigungsbasis neu herstellen kann, ist der Build nicht auf den Zustand angewiesen, den ein vorheriger Lauf hinterlassen hat. Das Ziel besteht nicht darin, sämtliche Zugriffsbeschränkungen zu beseitigen, sondern Ersteller, Lese- und Schreibumfang sowie Bereinigungsverantwortung für jede Datei vorhersehbar zu machen.
Häufig gestellte Fragen
Sollte man bei Permission denied den gesamten Arbeitsbereich mit chmod 777 freigeben?
Nein. Damit werden falsche Eigentümer und ACLs verdeckt und fremde Prozesse erhalten Schreibzugriff. Prüfen Sie zuerst den betroffenen Pfad mit stat und ls -le und reparieren Sie nur das Job-Verzeichnis.
Warum läuft dasselbe Skript per SSH, aber nicht als CI-Job?
Benutzer, HOME, umask, PATH und Startumgebung können abweichen. Protokollieren Sie diese Werte am Jobanfang und setzen Sie die benötigte Rechtebasis ausdrücklich im Skript.
Den nächsten Build auf einem exklusiven physischen Mac ausführen
Wählen Sie einen Cloud-Mac-Knoten passend zur Auftragsdauer und verwalten Sie Bestellungen, Verbindungsdaten und Support-Tickets in der Konsole.