Ich arbeite Teilzeit als Software Engineer bei Renuo und studiere Teilzeit Informatik an der ZHAW. Die meisten Wochen mischen sich Kunden-Codebases, Vorlesungsstoff und persönliche Projekte auf demselben Laptop, mit denselben Tools: Neovim mit lazygit und delta, iTerm2, Raycast, Setapp, Hotkeys, Fensterverwaltung. Das Setup soll sich gleich anfühlen, egal woran ich gerade arbeite.
Gleichzeitig will ich eine saubere Trennung zwischen beiden. Andere geöffnete Apps, anderer Zustand, andere Shell-History.
Warum nicht ein einziger Benutzer?
Die Reibung war konstant, aber klein genug, dass ich schon ziemlich lange damit gelebt habe. Das Slack-Icon leuchtete während ZHAW-Deadlines auf. Im Dock mischten sich Arbeits-Apps mit privaten. Die Zwischenablage merkte sich Dinge aus dem falschen Kontext. Mail zeigte drei Konten in einer Inbox, und der Wechsel dazwischen war mühsam.
An ruhigen Abenden war das Slack-Icon einfach da in der Menüleiste. Ich öffnete es, um "kurz eine Sache nachzuschauen", und verlor eine halbe Stunde.
Das grössere Problem war, dass Kundenprojekte nur ein
cd von den privaten entfernt waren. Dieselbe Shell-History, dieselbe git-Config, dieselben geladenen SSH-Keys. Die Grenze zwischen Kunden- und Privatarbeit war mental, nicht erzwungen.Zuerst versuchte ich Apples Fokus-Modi. Fokus blendet nur aus.
Ein zweiter Laptop würde all das lösen, ist aber Overkill, teuer und doppelt so viel Hardware zum Pflegen.
Zwei macOS-Benutzerkonten erledigen jeden der obigen Punkte ohne clevere Software. macOS isoliert sie bereits. Wenn ich wirklich eine Datei rüberschieben muss, kann ich das per
sudo von ~dani nach ~cb341 (oder umgekehrt) machen.Das Setup landete bei zwei Admin-Benutzern auf einer Maschine:
cb341(privat): ZHAW, persönliche Projekte, iCloud Keychaindani(Arbeit): Renuo-Projekte, Google Drive, Slack, Renuo-Google-Konto, Renuo-1Password, NCLT
Jede App und jedes Konto lebt auf genau einer Seite. ZHAW-Module wie Low Level Programming und Communication & Technology laufen in ihren eigenen Linux-VMs und Docker-Kontexten auf der privaten Seite, getrennt von den Containern des Arbeits-Users. Mail läuft auf beiden, aber mit unterschiedlichen Konten.
Das ist keine vollständige Isolation. Beide Benutzer teilen sich dieselbe Hardware, dasselbe Netzwerk, dieselbe macOS-Installation. Alles, was systemweit installiert ist (Wireshark, Kernel-Erweiterungen, Daemons), ist von beiden Seiten sichtbar. Ein Admin-Benutzer kann sich per
sudo ins Home-Verzeichnis des anderen begeben, wenn er das wirklich will. Echte Isolation würde separate Maschinen oder zumindest separate VMs bedeuten. Zwei Benutzer sind keine Sicherheitsgrenze. Sie sind eine Kontextgrenze. Anderer Login, anderer Zustand, anderer mentaler Modus. Das reicht für die Probleme, die ich tatsächlich habe.Der Homebrew-Kaninchenbau
Sobald ich mich auf zwei Benutzer festgelegt hatte, war die nächste Frage, wie ich Homebrew zwischen ihnen teile. Ich liebe Homebrew. Es ist nicht pacman, aber für macOS kommt es dem am nächsten. Die meisten CLIs und viele GUI-Apps, die ich nutze, sind nur ein
brew install entfernt.Das Problem ist, dass Homebrew grundsätzlich darauf ausgelegt ist, dass ein einzelner Nicht-Root-Benutzer eine einzelne Installation besitzt. Es gibt nicht viele Möglichkeiten, das zu umgehen, und die drei, die ich unten beschreibe, sind nicht erschöpfend. Es sind die praktikabelsten, die ich gefunden habe, und die, die die Community immer wieder empfiehlt. Ich habe alle drei ausprobiert, bevor ich aufgegeben habe.
Ansatz 1: Geteilte Gruppenberechtigungen
Das ist die oberste Stack-Overflow-Antwort bei jeder "Homebrew multi-user"-Suche. Varianten davon findet man in unzähligen Gists und Medium-Posts. Man führt etwas aus wie:
sudo chgrp -R admin /opt/homebrew
sudo chmod -R g+w /opt/homebrew
Beide Benutzer in der Gruppe
admin können jetzt den Homebrew-Prefix lesen und beschreiben. In der Theorie. In der Praxis fällt es sofort auseinander.Die Ursache ist, dass die Standard-umask von Homebrew die Schreibrechte der Gruppe für neu erstellte Dateien nicht erhält. Sobald ein Benutzer etwas installiert, gehören die neuen Dateien diesem Benutzer ohne Group-Write-Bit. Der Prefix ist für den anderen Benutzer wieder halb kaputt. Die Symptome kaskadieren durch die Schichten, auf die ich angewiesen bin: meine Shell (
zsh, seit Catalina der macOS-Standard) und der polyglotte Versionsmanager, den Renuo verwendet (mise, wie nvm oder rvm, aber für jede Sprache).- zsh-Autocompletion hört stillschweigend auf zu funktionieren, weil Completion-Dateien in
/opt/homebrew/share/zsh/site-functionsnicht benutzerübergreifend lesbar sind. zsh weigert sich, sie überhaupt zu laden, mitzsh compinit: insecure directories-Warnungen. - Plugin-Manager (antigen, oh-my-zsh) können sich nicht aktualisieren, weil ihr gecachter Zustand unter dem brew-Prefix oder im Home-Verzeichnis des Benutzers liegt, aber Dateien referenziert, die der andere Benutzer geschrieben hat.
- mise hat ein Trust-System für
.tool-versions- undmise.toml-Dateien. Dateien, die von einem Benutzer geschrieben wurden, fallen beim Trust-Check durch, wenn der andere Benutzer sie liest. Bei jeder Shell wird man aufgefordert, erneut zu vertrauen. - Eigentümerschaft driftet schnell. Ich habe zwei Tage mit diesem Ansatz verbracht. Am Ende war
brew doctoreine Wand aus Warnungen, Formulae weigerten sich zu aktualisieren, und Teile meines Setups waren stillschweigend kaputt. Nichts davon liess sich sauber beheben. Die Lösung ist, den Prefix neu zu installieren.
Die üblichen Workarounds häufen sich. Den chmod-Hack nach jeder Installation erneut ausführen. Eine eigene umask global setzen. Oder, und das ist real (codejam.info erwähnt es), ein
chmod -R g+w in die ~/.zshrc packen, damit der Prefix jedes Mal beim Öffnen eines Terminals zwangskorrigiert wird. Eine rekursive Dateisystem-Operation bei jedem Shell-Start, nur damit der vorhergehende Workaround weiter funktioniert. An diesem Punkt benutzt man Homebrew nicht mehr wirklich. Man pflegt einen Wrapper darum.Ansatz 2: Separate Installationen pro Benutzer
Die "saubere" Alternative ist, Homebrew unter dem Home-Verzeichnis jedes Benutzers zu installieren. Etwa
~/.homebrew. Jeder Benutzer besitzt seine eigene Kopie. Auf dem Papier klingt das vernünftig. In der Praxis bricht es. Die vorgebauten Bottles von Homebrew sind für den Standard-Prefix kompiliert (/opt/homebrew auf Apple Silicon, /usr/local auf Intel). Installiert man irgendwo anders, verliert man die Bottles komplett. Einige Formulae lassen sich dann gar nicht mehr bauen. Andere kompilieren, linken aber gegen Pfade, die im neuen Prefix nicht existieren, und brechen zur Laufzeit auf unvorhersehbare Weise. Die offizielle Doku sagt es direkt: "Some things may not build when installed elsewhere." Das ist keine Warnung, die man gegen die Vorteile abwägen sollte. Es ist eine Beschreibung dessen, was passiert.Ansatz 3: Geteilte Installation + sudo
Das ist es, was die Homebrew-FAQ tatsächlich empfiehlt, wenn man nachhakt. Es ist in einem Abschnitt vergraben, der erklärt, warum Homebrew sich "weigert, mit sudo zu arbeiten". Ich habe länger gebraucht, es zu finden, als ich zugeben möchte. Man erstellt einen dedizierten Benutzer nur für Homebrew (z.B.
brew-admin), installiert Homebrew unter diesem Konto und führt dann von seinem eigentlichen Benutzer aus brew per sudo aus:
# in ~/.zshrc on each real user
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$PATH"
alias brew='sudo -Hu brew-admin brew'
Das
-H-Flag richtet HOME auf den imitierten Benutzer aus, damit Homebrews Cache und Zustand konsistent bleiben. Charles Loders Beitrag führt das speziell für Apple Silicon durch. Es funktioniert. Es ist auch der Punkt, an dem Homebrews Single-User-Annahme sichtbar wird. Man pflegt jetzt ein drittes Benutzerkonto, dessen einzige Aufgabe es ist, den Paketmanager zu besitzen. Tools, die intern brew aufrufen (z.B. nvm, Versionsmanager, Installationsskripte), wissen nichts von deinem Alias und brechen.Als ich alle drei durchgespielt hatte, wurde mir klar, dass ich gegen das Tool angekämpft hatte, um es etwas tun zu lassen, wofür es nicht gebaut wurde. An dem Punkt fing ich an, mir MacPorts anzusehen. Bevor ich dort ankam, habe ich den Laptop zerschossen.
Wie ich ihn zerschossen habe
Ich kann nicht beweisen, dass Homebrew allein das verursacht hat. Das System hatte seinen eigenen angesammelten Müll, bevor ich anfing: Jahre halb deinstallierter Tools, Framework-Drift, die Art von Fäulnis, die jede langlebige Installation ansammelt. Aber das Homebrew-Berechtigungschaos war es, das alles über den Punkt der Wiederherstellbarkeit hinausgeschoben hat.
Es fing mit zsh an. Ein neues Terminal als
dani zu öffnen, produzierte eine Wand aus Fehlern:
/Users/dani/.zshrc:17: compinit: function definition file not found
/Users/dani/.oh-my-zsh/oh-my-zsh.sh:127: compinit: function definition file not found
/Users/dani/.oh-my-zsh/lib/async_prompt.zsh:145: add-zsh-hook: function definition file not found
/Users/dani/.oh-my-zsh/lib/git.zsh:148: is-at-least: function definition file not found
zsh-syntax-highlighting: failed loading add-zsh-hook.
zsh: command not found: compdef
_abbr_init:156: colors: function definition file not found
compinit führt beim Start einen Sicherheitscheck durch und verwirft stillschweigend jedes Verzeichnis, das es als "unsicher" einstuft, also als beschreibbar für einen anderen Benutzer als den aktuellen. Schaut man sich das beanstandete Verzeichnis an:
$ ls -ld /opt/homebrew/Cellar/zsh/5.9/share/zsh/functions
drwxr-xr-x@ 1205 cb341 admin 38560 /opt/homebrew/Cellar/zsh/5.9/share/zsh/functions
Homebrew wurde unter
cb341 installiert. Jede Homebrew-Datei gehört cb341. Wenn ich mich als dani anmelde, sieht compinit diese Dateien im Besitz eines anderen Benutzers und steigt aus. Die Lösung für sich genommen ist eine Zeile:
sudo chmod -R go-w /opt/homebrew/share/zsh /opt/homebrew/Cellar/zsh
Ja, das macht es für den anderen Benutzer kaputt. Das ist das ganze Problem in einem Satz. Was auch immer ich tue, damit Homebrew für
dani funktioniert, macht es entweder für cb341 kaputt oder hinterlässt die Berechtigungen in einem Zustand, den compinit beim nächsten brew install von cb341 erneut bemängelt. Es gibt keine Berechtigungskonfiguration, die für zwei Benutzer mit einem geteilten Homebrew funktioniert, die ich gefunden hätte.Was folgte, waren Stunden, in denen ich es trotzdem versuchte. Gruppen-Eigentümerschaft umschalten.
.zshrc editieren. ~/.zcompdump löschen und compinit -u erneut ausführen. An einem Punkt habe ich versehentlich den oberen Teil meiner .zshrc gelöscht und export ZSH="$HOME/.oh-my-zsh" verloren, was oh-my-zsh komplett kaputt machte. Es wieder einzufügen, machte compinit kaputt. compinit zu reparieren, machte fpath kaputt. Jeder Fix war für sich genommen richtig und im Kontext falsch.Das ist der Teil, den ich aus dem Gedächtnis ehrlich nicht mehr rekonstruieren kann. Ich folgte Anleitungen, probierte zufällige Fixes, machte einige davon rückgängig, löschte Verzeichnisse, die ich wahrscheinlich nicht hätte löschen sollen. Als ich merkte, wie schlimm es war, war der Schaden über eine saubere Reparatur hinaus.
Die Kette ging ungefähr so. Der Berechtigungszustand auf
/opt/homebrew geriet in eine Konfiguration, die keiner der Benutzer vollständig reparieren konnte. Das macOS-Update über die Systemeinstellungen weigerte sich, durchzulaufen. Die manuelle Installation des "Update to macOS Tahoe"-.pkg schlug ebenfalls fehl. Die Xcode-CLI-Tools liessen sich nicht installieren. Dann liess sich Xcode selbst nicht installieren. Dann hörte der App Store auf zu laden. Ich konnte die Festplatte nicht über den normalen Recovery-Ablauf löschen. Schliesslich machte ich die GNU-Tools (make, git) so kaputt, dass Recovery-Skripte fehlschlugen.Zur Referenz: So sieht ein normaler macOS-Update-Ablauf aus. Nichts davon funktionierte bei mir. Selbst der Kommandozeilen-Notausgang
softwareupdate --fetch-full-installer, meine letzte Hoffnung, schlug fehl:
$ softwareupdate --fetch-full-installer --full-installer-version 26.4.1
Scanning for 26.4.1 installer
Install failed with error: Installation failed
Error Domain=PKDownloadError Code=8 "(null)"
... NSLocalizedDescription=The request timed out.
... https://swcdn.apple.com/.../InstallAssistant.pkg
Zeit für einen sauberen Reset.
Homebrew → MacPorts (widerwillig)
Da ich von Grund auf neu aufbaute, ging ich die Paketmanager-Frage erneut durch. Um es klar zu sagen: Ich wäre lieber bei Homebrew geblieben. Es ist der macOS-Paketmanager, den ich tatsächlich gerne benutze. Die Formula-Abdeckung ist unschlagbar. Aber die vorigen drei Abschnitte sind der Grund, warum ich es nicht konnte. Homebrew ist grundsätzlich ein Single-User-Tool. Keine Menge an Gruppen-Berechtigungshacks oder sudo-Aliasen ändert daran etwas.
MacPorts gibt es schon länger als Homebrew und ist ihm als de-facto BSD-artiges Ports-System für macOS vorausgegangen. Die ETH hat einen anständigen Beitrag, der mich angestubst hat, es tatsächlich zu probieren.
Die ehrlichen Tradeoffs:
MacPorts gewinnt bei
- Sauberen Unix-Berechtigungen.
sudo-Benutzer verwalten Pakete, normale Benutzer nutzen sie einfach. Multi-User ist Standard, kein Nachgedanke. - Vorhersehbaren Installationsorten (
/opt/local).
Homebrew gewinnt bei
- Grösserem Ökosystem. OrbStack, delta und viele Casks, auf die ich angewiesen bin, sind erstklassig in Homebrew, weniger so in MacPorts.
- Kein
sudofür tägliche Installationen nötig. macOS als Plattform tendiert weg vonsudofür User-Space-Arbeit, teils weil System Integrity Protection ohnehin Schreibvorgänge nach/Systemund/usrblockiert, unabhängig von root, und teils weil Apples Muster "drag to /Applications" ist statt "elevate to install". Wenn doch privilegierter Zugriff nötig ist, bevorzugt macOS Autorisierungsdialoge und Entitlements gegenübersudo. Sogar Renuos Laptop-Setup-Anleitung weist darauf hin: "On a Mac, you should seldom be required to use sudo." Homebrew respektiert diese Konvention. MacPorts nicht.
Wann gewinnt also welcher? Ein Disclaimer vorweg: Ich vergleiche nur auf der Multi-User-Dimension. Formula-Abdeckung, Build-Determinismus, Update-Frequenz, Dependency-Handling und Tap-Ökosysteme spielen alle eine Rolle bei der Wahl eines Paketmanagers, und andere Artikel decken sie gut ab. Nichts davon kippte meine Entscheidung. Das Multi-User-Modell tat es.
Nimm Homebrew, wenn ein einzelner Mensch den Mac besitzt. Grösseres Ökosystem, weniger Reibung, passt zur macOS-"seldom sudo"-Norm. Die meisten Setups landen hier. Wenn du der einzige bist, der sich einloggt, gibt es keinen Grund, etwas anderes zu wählen.
Nimm MacPorts, wenn sich mehrere Menschen den Mac teilen. Hier bricht Homebrews Single-User-Modell zusammen, und kein Berechtigungs-Hack repariert es sauber. MacPorts behandelt Installationen als privilegierte Operation, was bedeutet, dass alle Benutzer denselben Stack teilen, ohne dass einer von ihnen den Prefix besitzt. Man tauscht Ökosystem-Grösse gegen geistige Gesundheit. Die "seldom sudo"-Norm fliegt aus dem Fenster, aber diese Norm funktionierte nur, weil Homebrew einem Benutzer Schreibzugriff auf einen System-Pfad gab. Multi-User bricht den Trick. Etwas anderes vorzugeben, ist es, was mich überhaupt erst in den Schlamassel gebracht hat.
Für mein Setup war MacPorts die einzig vernünftige Wahl. Ich vermisse
brew install --cask orbstack immer noch. Die kleinere Cask-Auswahl tut weh, und ich fülle die Lücken mit manuellen .dmg-Installationen für die wenigen Apps, die nicht dabei sind.Was den Wiederaufbau erträglich machte
Ein paar Dinge haben beim Wiederaufbau echte Stunden gespart. Manche hatte ich lange vorher eingerichtet, manche habe ich kurz vor dem Wipe erzeugt. Wenn du macOS ohne sie nutzt, lohnt es sich.
Was ich bereit hatte
Ein Dotfiles-Repo. Kein Backup-Ordner, ein git-Repo. Meines liegt unter github.com/cb341/dotfiles: Neovim-Config,
.zshrc, lazygit, git-Config, Shell-Helper. git clone plus ein Bootstrap-Skript bringt die Configs an Ort und Stelle. Kombiniert mit dem Brewfile (gleich) ist die Shell wieder brauchbar.Ein
Brewfile. Vor dem Wipe der alten Installation habe ich brew bundle dump ausgeführt, um alles Installierte zu erfassen. Auf der MacPorts-Seite wurde es meine Checkliste. Ich arbeitete jede Zeile durch und führte das entsprechende sudo port install aus. Künftig auf MacPorts macht port installed requested > ports.txt dasselbe.Settings-Exports für die GUI-Apps, die sie unterstützen. iTerm2, Raycast und Timing exportieren alle Einstellungen in eine Datei. Der Import auf der frischen Installation dauerte Sekunden statt eines Abends Klickerei durch Einstellungs-Panels.
Eine schlichte
RESTORE.md für Dinge, die brew bundle nicht erfasst: Setapp-Apps, App-Store-Apps (mas list deckt die meisten ab), einmalige .dmg-Installationen, Browser-Erweiterungen, Systemeinstellungen (Caps Lock auf Escape, Tastenwiederholung, Bedienungshilfen), Schriftarten. Ich aktualisiere sie, wann immer ich etwas installiere, das ich vermissen würde.Eine schnelle externe Festplatte. Die Samsung T7 Shield 4TB über Thunderbolt war schnell genug, dass selektives Auslagern und Zurückkopieren schmerzlos war.
ncdu vor dem Backup. Ein lang laufender Mac sammelt jede Menge node_modules-, Rust-target/- und vendor/-Verzeichnisse an, die nicht mit auf die Reise müssen. Locker 100 GB gespart.Der Wiederaufbau selbst
Für die Renuo-Seite folgte ich Renuos Laptop-Setup-Anleitung. Sie gab mir eine bekannte gute Baseline: Ruby/Rails über mise, Postgres und Redis als Hintergrunddienste, git + git-flow, GPG, die Heroku-CLI und mas. Die Anleitung ist durchgehend mit der Annahme von Homebrew geschrieben, also übersetzte ich auf der MacPorts-Seite mental jedes
brew install X in sudo port install X und schlug die Handvoll Pakete nach, bei denen sich die Namen unterscheiden. brew services start postgresql wird zu einem launchd-plist, das MacPorts automatisch verdrahtet, wenn man den postgresql17-server-Port installiert. Gleiches Ergebnis, anderes Vokabular.Für die ZHAW-Seite gab es keine entsprechende Anleitung, also waren das Dotfiles-Repo plus das Brewfile die Baseline. Die Linux-VMs und Docker-Kontexte für Low Level Programming und Communication & Technology bauten sich aus ihren eigenen Vagrant- und Compose-Files neu auf, unabhängig davon, was auf dem Host war.
Zusätzlich zu dieser Baseline der Kern-Stack auf beiden Benutzern:
- Apple-Defaults beibehalten: Notes, Mail, Messages
Die Neovim-Config wird manuell zwischen den beiden Benutzern synchronisiert. Nicht ideal, aber ein kleiner Preis. Alles andere ist einfach doppelt installiert.
Die positive Seite
Eine lang laufende macOS-Installation sammelt viel Zustand an. Hintergrunddienste von Apps, die ich seit Monaten nicht geöffnet hatte, Config-Dateien in
~/Library/Application Support für Tools, die ich einmal probiert und dann liegengelassen hatte, Login-Items von Installern ohne richtige Deinstallationsskripte, Kernel-Erweiterungen von Treibern, die ich nicht mehr brauchte. All das ging mit dem Reset. Der Login ist schneller, die Idle-CPU niedriger, und die Menüleiste ist nicht mehr voll mit Icons von Sachen, die ich nicht nutze.Fürs Erste: Der Laptop bootet, beide Benutzer haben, was sie brauchen, und ich habe seit Tagen nichts mehr zerschossen. Das zählt als Sieg.