code-server auf FreeBSD

🇬🇧 Read this article in English.

VS Code im Browser, nativ auf FreeBSD, und warum das fünf Jahre gedauert hat

Nach BunkerWeb kommt hier die nächste Portierungschronik: code-server, also Visual Studio Code als serverseitige Anwendung im Browser. Seit dem 21. Juni 2026 gibt es www/code-server in der FreeBSD Ports Collection. Und der Weg dorthin war alles andere als ein gewöhnlicher Node.js-Port.


1. Warum code-server auf FreeBSD?

code-server nimmt den Open-Source-Kern von VS Code und macht daraus einen Webdienst: Die Oberfläche läuft im Browser, der eigentliche Server, Dateien, Terminals, Git, Suche, Extensions, läuft unter Node.js auf dem Zielsystem. Für FreeBSD ist das ein perfekter Anwendungsfall: Entwicklung direkt auf Servern und in Jails, ohne lokalen Electron-Client, von jedem Gerät aus.

Der eigentliche Matchwinner-Aspekt zeigt sich aber im Vergleich mit dem naheliegenden Weg: VS Code auf dem Mac oder Windows-Rechner, dazu die Remote-SSH-Extension auf das FreeBSD-System. Das funktioniert nämlich nicht. Remote-SSH installiert auf dem Zielsystem einen VS Code Server, und den veröffentlicht Microsoft nur für Linux (sowie macOS und Windows). FreeBSD-Binaries existieren schlicht nicht.

Die Community behilft sich seit Jahren mit Workarounds über den Linuxulator: linux_enable="YES", eine Linux-Base installieren, per RemoteCommand /compat/linux/usr/bin/bash eine Linux-Shell vortäuschen und beim Verbinden als Plattform „Linux“ angeben. Wer die einschlägigen Forum-Threads liest, sieht allerdings, wie fragil das ist: PATH-Verrenkungen, hängende Verbindungen, glibc-Anforderungen des Servers, und mit jedem VS-Code-Update kann die Bastelei von vorn losgehen.

code-server dreht das Problem einfach um: Statt einem Linux-Binary ein FreeBSD-System unterzuschieben, läuft der Editorserver nativ auf FreeBSD, als sauberes Paket mit rc.d-Skript. Der Browser ist der Client, egal ob auf macOS, Windows oder Linux. Kein Linuxulator, keine vorgetäuschte Plattform, kein Bruch beim nächsten Update.

Klingt nach einem simplen Node.js-Port. Ist es nicht. In einer einzigen Anwendung treffen Browser-Sandboxing, serverseitiges Node.js, TypeScript-/esbuild-/Gulp-Buildketten, native C++-Add-ons, VS-Code-Produktdateien und Marketplace-Infrastruktur aufeinander. Deshalb braucht ein „reiner JavaScript-Port“ plötzlich Compiler, Python, node-gyp und umfangreiche Betriebssystem-Patches.


2. Die Vorgeschichte, und ein Zufallsfund

Die Entscheidung, code-server zu portieren, habe ich unabhängig von allem Vorherigen getroffen, ich wollte das Werkzeug schlicht nativ auf FreeBSD haben. Erst bei der Recherche stieß ich eher zufällig darauf, dass es Anläufe schon früher gegeben hatte: PR 256144 von 2021 etwa, und auch später wurde es immer wieder versucht, teils mit Ansätzen, die den Build praktisch komplett aus dem Port heraus verlagerten.

Interessant war der Fund trotzdem: Er zeigte, dass das Kernproblem seit Jahren bekannt war, ein npm install während des Builds, also Netzwerkzugriff, den ein regulärer Poudriere-Build nicht erlaubt. Genau daran war bisher jeder Versuch gescheitert oder vorbeigebaut worden.

Für meinen Ansatz habe ich mir deshalb von Anfang an klare Ziele gesetzt: Der Quellcode wird weiterhin im Port gebaut, FreeBSD-Binärmodule entstehen im Port, Poudriere braucht keinerlei Netz, und Updates folgen einem dokumentierten, wiederholbaren Releaseprozess.


3. Warum ein normales npm ci nicht genügt

Bei einem einfachen Node.js-Projekt wäre der Ablauf: npm ci, npm run build, fertig. Für einen FreeBSD-Port scheitert das gleich doppelt.

Erstens: kein Netz während des Builds. Poudriere erwartet, dass alle Quelldateien in der Fetch-Phase bekannt sind. Ein npm ci, das während do-build Tausende Pakete lädt, verletzt dieses Prinzip, und es bleibt nicht bei npm selbst: prebuild-install, node-pre-gyp, Git-Downloads aus Buildskripten, nachgeladene vorkompilierte Add-ons. Im code-server-Quellbaum fanden sich Buildskripte mit npm install, curl, wget und Linux-Container-Builds. Das Postinstall-Skript konnte bei Bedarf sogar selbst erneut npm install ausführen.

Zweitens: Es gibt nicht einen node_modules-Baum. code-server hat eigene Abhängigkeiten, aber der eingebettete VS-Code-Quellbaum enthält daneben eine ganze Landschaft separater npm-Projekte:

lib/vscode/
lib/vscode/build/
lib/vscode/extensions/   # dutzende eingebaute Extensions, je eigenes npm-Projekt
lib/vscode/remote/
lib/vscode/remote/web/

Ein einzelnes Archiv an der Projektwurzel reicht nicht. Die Abhängigkeiten müssen in genau den Unterprojekten liegen, in denen die VS-Code-Buildkette sie erwartet.


4. Die Lösung: ein reproduzierbarer Offline-Build

Die Architektur orientiert sich am Prinzip, das bereits editors/vscode verwendet: Für jede Version werden vorbereitete Archive als Distfiles bereitgestellt.

code-server-node-modules-${DISTVERSION}.tar.gz   # npm-Baum von code-server selbst
vscode-node-modules-${VSCODE_VERSION}.tar.gz     # npm-Baeume aller VS-Code-Teilprojekte
vscode-reh-web-linux-x64-${DISTVERSION}.tar.gz   # Remote Extension Host

Erzeugt werden sie über einen Bootstrap-Target im Port (make-node-modules-archive), der ein kontrolliertes npm ci --ignore-scripts ausführt und das Ergebnis verpackt. Veröffentlicht werden die Archive als Release-Assets im Repository joneum/FreeBSD-CodeServer; gebaut werden sie auf einer separaten Build-VM, damit Bootstrap-Downloads und der eigentliche Poudriere-Test sauber getrennt bleiben.

Wichtig dabei: Der Port installiert kein fertig vorgebautes Komplettpaket. Kompiliert wird weiterhin im Port, code-server, VS Code, die nativen FreeBSD-Module. Vorbereitet wird nur das, was sich mit der FreeBSD-Fetch-Infrastruktur nicht sinnvoll ausdrücken lässt. Damit bleibt der Port näher am klassischen Portsmodell als ein reiner Binär-Repackaging-Port.

Ein Detail verdient Erklärung: Das Remote-Host-Artefakt heißt weiterhin linux-x64, obwohl das Ergebnis auf FreeBSD läuft. Die VS-Code-Buildlogik kennt für diesen Build schlicht keinen FreeBSD-Target, also dient linux-x64 als strukturelle Vorlage, deren Inhalt anschließend mit FreeBSD-Binärmodulen und Patches FreeBSD-tauglich gemacht wird. Das ist auch der Grund, warum der Port vorerst auf amd64 beschränkt ist.


5. Die FreeBSD-Patches: eine Auswahl

Harte Plattformprüfungen

Die häufigste Fehlerklasse der gesamten Portierung sieht so aus:

switch (process.platform) {
  case "win32":
  case "darwin":
  case "linux":
    break;
  default:
    throw new Error("Unsupported platform");
}

Code, der technisch vollkommen Unix-kompatibel wäre, fällt aus, nur weil freebsd nicht in einer Liste steht. Genau daran scheiterten unter anderem @vscode/deviceid und die eingebaute Extension microsoft-authentication, deren esbuild-Skript den Build des Remote Extension Hosts mit Error: Unsupported platform: freebsd beendete.

Die Lektion mit den mehrfachen Kopien

Der lehrreichste Bug der Portierung: Der erste deviceid-Patch wurde an einem node_modules-Baum vorgenommen, den ein späterer Buildschritt neu erzeugte, beim Staging war der Patch wieder verschwunden. Die endgültige Lösung patcht direkt die generierte Kopie im Remote Extension Host, nach npm run build:vscode. Es reicht nicht zu wissen, welche Datei gepatcht werden muss, entscheidend ist, in welcher Buildphase die tatsächlich installierte Kopie entsteht.

node-pty: ohne Terminal kein Editor

node-pty verbindet das integrierte Terminal mit den Pseudo-Terminals des Betriebssystems, nativer C++-Code, für den es keine FreeBSD-Prebuilds gibt. Also: Quellcode anpassen (u. a. den Aufruf von pty_close_inherited_fds()), mit node-gyp gegen die FreeBSD-Node-Header neu bauen, als prebuilds/freebsd-x64/pty.node ablegen und zusätzlich in den Remote Extension Host kopieren. Gleiches Spiel bei @vscode/spdlog (Logging) und @vscode/native-watchdog, das erst mit devel/libuv als zusätzlicher Abhängigkeit baute.

Kleinvieh, das den Build stoppt

  • Git-Annahmen: Upstream ermittelte die Build-ID per git rev-parse HEAD: ein Port arbeitet aber mit extrahierten Distfiles, nicht mit einem Git-Checkout. Der Patch akzeptiert eine gesetzte BUILD_SOURCEVERSION.
  • Falscher Zielordner: Buildskripte wechselten trotz gemapptem Target ins falsche Verzeichnis, gelöst über eine separate Variable GULP_VSCODE_TARGET.
  • Konfigurationspfade: userDataPath.ts kannte unter Unix vor allem Linux und macOS; FreeBSD wird jetzt dem passenden XDG-Pfadmodell zugeordnet.
  • tsgo: Neuere Typecheck-Schritte nutzen Binärdateien, die es für FreeBSD nicht gibt, diese konkreten Targets werden übersprungen, die eigentliche Kompilierung läuft vollständig.
  • Linux-Binaries im Baum: Eingebettete ELF-Dateien wie rg und tgrep werden durch native FreeBSD-Binaries aus textproc/ripgrep und textproc/tgrep ersetzt, die Pfade heißen weiter linux-x64, der Inhalt ist FreeBSD.

6. Betrieb: ein sauberer FreeBSD-Dienst

Installiert wird wie gewohnt:

pkg install code-server

sysrc code_server_enable=YES
service code-server start

Das rc.d-Skript ist dabei mehr als Komfort, es beantwortet die Betriebsfragen: Unter welchem Benutzer läuft der Dienst, wo liegen config.yaml, Benutzerdaten und Extensions, welche Home- und XDG-Verzeichnisse sieht Node.js. Der Port installiert einen Wrapper unter /usr/local/bin/code-server und den Anwendungsbaum getrennt unter /usr/local/share/code-server. Zur Wartbarkeit ist der Port in mehrere Makefile-Fragmente aufgeteilt (Makefile, Makefile.vscode, Makefile.build, Makefile.install), plus eine sehr umfangreiche pkg-plist, denn installiert werden viele tausend Dateien.


7. Extensions: Warum nicht alles funktioniert

Als Extension Gallery ist Open VSX eingerichtet, die offene Registry der Eclipse Foundation, inoffizielle VS-Code-Produkte dürfen den Microsoft Marketplace nicht ohne Weiteres verwenden. Das muss man ehrlich dazusagen: Nicht alle Erweiterungen aus dem gewohnten VS Code stehen zur Verfügung. Manche fehlen schon auf Registry-Ebene, weil der Hersteller nur im Microsoft Marketplace veröffentlicht oder Open VSX nur eine ältere Version enthält. Aber auch „lässt sich installieren“ heißt nicht „funktioniert vollständig“. Die wichtigsten Gründe:

  • Workspace-Extensions laufen auf dem Server. Sie sehen process.platform === "freebsd": nicht das Betriebssystem des Browsers. Mitgelieferte Windows-, macOS- oder Linux-Binaries helfen dann nicht.
  • Mitgelieferte Binärdateien: Viele Extensions bringen Language Server, Debug Adapter oder native Add-ons für linux-x64, darwin-arm64, win32-x64 mit. Fehlt freebsd-x64, scheitert die Aktivierung, und ein Linux-Binary läuft auch mit Linuxulator nicht automatisch.
  • Downloads beim ersten Start: Manche Extensions bauen ihre Download-URL aus process.platform zusammen. Für freebsd existiert schlicht keine URL.
  • Electron- und Microsoft-Spezifika: Extensions, die den Desktop-Electron-Prozess, proprietäre Microsoft-Dienste oder Produktkennungen des offiziellen VS Code erwarten, funktionieren nicht oder nur teilweise. Remote-WSL, Dev Containers und Microsoft Remote-SSH sind konzeptionell nicht übertragbar, wobei code-server das Remote-Problem ja gerade anders löst: Der Editorserver läuft bereits direkt auf FreeBSD.
  • Versionskopplung: Maßgeblich für engines.vscode ist die eingebettete VS-Code-Version, code-server 4.123.0 enthielt z. B. VS Code 1.122.1, nicht 1.123.

Ein Grenzfall verdient eigene Erwähnung: GitHub Copilot. Der VS-Code-Build erwartet plattformspezifische Copilot-Pakete und würde sonst versuchen, sie per npm nachzuladen, in Poudriere unmöglich. Der Port legt deshalb lokale Paketstrukturen an, damit der offene Teil baut. Nutzbar ist Copilot auf FreeBSD trotzdem nicht: Die Komponente ist geschlossen, und wo weder Quellcode noch FreeBSD-Binaries existieren, endet die Portierbarkeit.


8. Wartung: Ein Update ist mehr als make makesum

code-server und der eingebettete VS Code haben getrennte Versionsnummern, die sich unabhängig entwickeln. Jedes Update heißt deshalb: Welche VS-Code-Version erwartet code-server? Haben sich die npm-Teilprojekte geändert? Neue native Module? Greifen die Patches noch? Neue Plattformprüfungen? Neue Binärdateien im Remote-Web-Build? Dazu kommen frische node_modules-Archive, Poudriere-Läufe und Extension-Tests. Der Port ist ehrlicherweise wartungsintensiv, und genau deshalb gut aufgehoben bei jemandem, der ihn langfristig pflegt.


Fazit

Das größte Hindernis war nicht FreeBSD. Es war eine moderne JavaScript-Buildkette, die davon ausgeht, während des Builds jederzeit Pakete und Binärdateien aus dem Internet laden zu dürfen. FreeBSD ist selten grundsätzlich inkompatibel, meist fehlen nur Plattformpfade, Binärartefakte oder ein reproduzierbarer Buildprozess. Offener Quellcode lässt sich anpassen und neu kompilieren; nur bei geschlossenen Komponenten ist Schluss.

Seit dem 21. Juni 2026 ist www/code-server (zunächst als 4.123.0, amd64) Teil der Ports Collection, gesponsert von der Netzkommune GmbH. Damit dokumentiert der Port nebenbei, wie sich ein sehr großes, modernes TypeScript-Projekt in das reproduzierbare FreeBSD-Portsmodell integrieren lässt.

Wer meine Arbeit an diesem und über 40 weiteren Ports unterstützen möchte, findet in der Seitenleiste die Möglichkeit dazu.

Nutzt ihr code-server auf FreeBSD, oder plant es in einer Jail? Erfahrungen und Extension-Berichte gern in die Kommentare!

Schreibe einen Kommentar