Het artikel behandelt de frustraties van een ontwikkelaar bij het probeerbaar maken van zijn software, 'Fresh', voor Linux-gebruikers. Waar Windows en macOS relatief uniform zijn, zorgt de fragmentatie van Linux voor grote uitdagingen.
De auteur analyseert diverse distributiemethoden:
- Flatpak en AppImage: Problemen met sandboxing (TUI-beperkingen) en onacceptabel trage opstarttijden door FUSE-mounts.
- Debian/Ubuntu (.deb): De enorme overhead om aan officiële pakketregels te voldoen en problemen met verouderde libc-versies.
- AUR en Mise: Kwetsbaarheid voor externe wijzigingen, zoals gecertificeerde trust roots of read-only modi.
De conclusie is dat distributie-specifieke kanalen onhoudbaar zijn voor een kleine ontwikkelaar. De nieuwe strategie is om over te stappen op een statisch gelinkte musl-binary met een ingebouwd self-updating mechanisme, waardoor de noodzaak voor externe package managers wordt omzeild.
Ik haat het om mijn software voor Linux te verpakken
Windows en macOS vallen relatief mee omdat ze uniform zijn. Voor Windows gebruik ik winget; lelijk, maar het werkt (nadat ik de initiële hoofdpijn voorbij was). Voor macOS gebruik ik voorlopig Homebrew, wat niet ideaal is, maar wel eenvoudig op te zetten was. Waarschijnlijk stap ik over op een native signed macOS-app zodra ik de tijd neem om dit correct te verpakken. Beide oplossingen werken prima voor moderne Windows- en macOS-gebruikers.
Linux is een ander verhaal. Ik begon met het uitbrengen van Fresh als een npm-pakket. Dit voelde onhandig en irriteerde sommige mensen op Hacker News, maar het bracht ook echte problemen met zich mee:
- Beveiliging: npmjs heeft meerdere datalekken gehad en ik wil 's nachts beter kunnen slapen.
- Niet universeel: veel gebruikers hebben geen npm geïnstalleerd; waarom zouden ze npm installeren, enkel om Fresh te installeren?
- Vreemde installer: de flow van
npm install is eigenlijk een script dat je via npmjs krijgt, dat vervolgens naar GitHub gaat en het juiste binaire artifact voor jouw machine downloadt. Dit moet je elke keer doen als je wilt updaten.
Ik heb geluisterd naar de feedback van de gefrustreerde gebruikers en (met hulp van verschillende vrijwilligers) hebben we pakketten gemaakt voor "ALLES":
- Rust's cargo op crates.io (en ook cargo-binstall)
- Distributie-onafhankelijke formaten zoals AppImage en Flatpak
- deb
- rpm
- AUR (Arch Linux), in twee varianten: source build en pre-built binary (-bin pakket)
- nix
- mise
- Homebrew voor Linux
- npm / npx
- Terra
- Gentoo GURU
Daarnaast breng ik pre-built binaries uit als een tarball. Je kunt je voorstellen hoe fragiel dit alles is. Na elke release vraag ik me af of ik tegen problemen aan loop bij een van de vele kanalen. Elke oplossing werkt voor sommige gebruikers, maar geen enkele werkt voor álle gebruikers. En ze hebben allemaal nadelen.
De beperkingen van specifieke oplossingen
"Waarom gebruik je niet gewoon Nix?!" Omdat veel mensen GEEN Nix geïnstalleerd hebben en dit niet willen doen enkel om mijn app te gebruiken. Ik ondersteun Nix als methode, maar het is geen universele oplossing.
"Gebruik gewoon Flatpak!" Ik breng een Flatpak uit, maar ik werk er eigenlijk tegenin. Flatpak is ontworpen voor zelfstandige, gesandboxed desktop GUI-applicaties, terwijl ik een terminal-gebaseerde TUI uitbreng waarmee je vrij door je machine en netwerk kunt bewegen. Ik moet verschrikkelijke "bad practice"-vlaggen gebruiken om het werkend te krijgen. Flatpak sandboxing is simpelweg niet geschikt.
"Gebruik gewoon AppImage!" Ik breng een AppImage uit, maar deze start extreem traag vanwege de squashfs FUSE-mount on-demand, wat de opstarttijd onacceptabel maakt. Ik wil dat mijn programma direct start, zo snel als technisch mogelijk. Om de opstarttijden redelijk te maken, gebruikt mijn installer-script een vreselijke hack om de squashfs-inhoud ergens uit te pakken en de AppImage te verlaten. Je kunt de AppImage nog steeds gewoon uitvoeren als je dat wilt, maar het start irritant traag. Bovendien is in beide gevallen FUSE vereist. Waarom heeft mijn app FUSE nodig?!
Daarnaast is mijn binary niet echt universeel portable omdat deze een minimale libc-versie vereist (voor een distro van 2-3 jaar oud werkt het mogelijk niet). Ik had net zo goed static linking kunnen gebruiken en AppImage kunnen laten vallen; ik had me voor een bepaalde reden ingebild dat AppImage dit portability-probleem alleen zou oplossen. Ten slotte hebben veel mensen vanwege diverse redenen een slecht beeld van AppImage, Flatpak (en Snap) en weigeren ze deze simpelweg te gebruiken.
De pijn van de Debian-familie
Zoals uitgelegd in een andere Hacker News-discussie, heb ik het nog niet gered om mijn .deb als officieel Debian- (en Ubuntu-) pakket aan te bieden. Dit vereist namelijk dat alle (vele) Rust-dependencies ook Debian-pakketten zijn. Er zijn goede redenen voor dit beleid — volledig reproduceerbare en zelfstandige builds, verminderen van "supply chain hell", etc. — maar het is enorm veel werk. Hoe kan ik dit bijhouden? Elke directe dependency zou bij elk beveiligingsprobleem op Debian opnieuw moeten worden geüpdatet. Daar heb ik geen tijd voor. Ik kan mijn dependencies niet zomaar als broncode in mijn deb-sourcepakket stoppen, want dat is tegen het beleid.
Een andere anekdote is dat ik oudere machines wil ondersteunen die bijvoorbeeld een oudere Ubuntu draaien. Deze hebben echter een oudere libc, wat betekent dat mijn build voor nieuwere Ubuntu's niet geïnstalleerd kan worden op oudere versies omdat ze falen op een link-error tijdens het laden van de binary. Ik zou dan in een oude Ubuntu container image moeten bouwen en de ongepatchte pakketten daaruit moeten overnemen — of deze gebruikers volledig moeten laten vallen. Geen van beide opties is houdbaar.
Geen automatische updates voor .deb / .rpm
Omdat het zo'n hoofdpijn is, zijn mijn .deb (of .rpm) pakketten niet geaccepteerd in de officiële bronnen. Hierdoor krijgen mensen die deze pakketten installeren geen automatische updates wanneer ze het native update-mechanisme van hun systeem gebruiken (apt-get upgrade of equivalent). Het zou fijn zijn geweest als er een snelle serverloze oplossing bestond waarbij je simpelweg een URL opgeeft waar gezocht kan worden naar nieuwere versies van één enkel pakket, en zowel apt als dnf dat zouden onthouden.
Ik ben me ervan bewust dat het "juiste" om te doen is om mijn pakket in de officiële kanalen te krijgen. Ik heb een Bugzilla-ticket geopend voor Fedora en iemand is iets begonnen voor Ubuntu, maar ik heb simpelweg niet de tijd en energie om deze verzoeken door te drukken. Ik wil gewoon MIJN SOFTWARE GRATIS WEGGEVEN. Mijn punt is: omdat mensen allerlei verschillende distro's gebruiken, is de cumulatieve inspanning om dit correct te doen extreem hoog.
Mise werkte, totdat het niet meer werkte
Het blijkt dat sommige mensen mise gebruiken als hun lokale (user-level) package manager. Iemand kreeg het werkend voor Fresh, wat geweldig was! Maar op een dag stopte het ermee, zonder dat er aan mijn kant iets veranderde. Het bleek dat GitHub het certificaat van zijn build attestation-systeem had geroteerd, en mise had een verouderde trust root vastgepind in plaats van de huidige op te halen. Ik wist niet eens dat er iets kapot was totdat iemand klaagde. Uiteindelijk heb ik het zelf opgelost, maar waarom besteed ik mijn tijd aan dit soort problemen?
Arch Linux AUR
Ik gebruik persoonlijk Arch AUR en bied Fresh ook via dit kanaal aan (zowel als source als pre-built -bin pakket). Onlangs is dit echter geblokkeerd voor nieuwe package releases. De AUR staat in read-only modus vanwege enkele beveiligingslekken, waardoor ik al een paar versie-releases heb gemist. Voorlopig weet ik niet wanneer de AUR weer beschikbaar is. Als tijdelijke oplossing heb ik contact opgenomen met een maintainer om Fresh in de Arch 'extra' repo te krijgen in plaats van de AUR.
Conclusie en volgende stappen
De kern van dit hele betoog is dat distributie-specifieke kanalen geen goede oplossing zijn. Naarmate het aantal distro's en varianten toeneemt, wordt het bijna onmogelijk om zelfs een kleine update uit te brengen.
Focus op statisch gelinkte musl en self-updating binary Heb ik eigenlijk wel een package manager nodig? Ik bouw al een musl binary als onderdeel van de release. De volgende versie zal een nieuw ingebouwd self-updating mechanisme bevatten dat gebruikers op aanvraag kunnen triggeren. Dit zal het belangrijkste releasekanaal voor Linux worden en hopelijk de enige die ik in de toekomst moet ondersteunen. Alle bestaande pakketten blijven voorlopig ondersteund worden, maar het doel is om de statische binary als de aanbevolen standaard te maken.
Hiermee neem ik een risico; wie weet welke verrassingen dit brengt — zoals een Linux-portability probleem dat mijn statisch gelinkte binary voor sommige mensen breekt. Zij kunnen dan nog steeds vanuit broncode bouwen of gebruikmaken van de pakketbestanden die ik (voorlopig) blijf uitbrengen.
In feite implementeer ik mijn eigen kleine package manager. Zal het voor iedereen werken? Hopelijk! Fresh is ongeveer 12 MB om te downloaden en pakt uit tot circa 35 MB, dus dat zou een redelijke ervaring moeten zijn. Ik werk constant aan het verder verkleinen van die uiteindelijke binary-grootte.
Ik haat het om mijn software voor Linux te verpakken
Windows en macOS vallen relatief mee omdat ze uniform zijn. Voor Windows gebruik ik winget; lelijk, maar het werkt (nadat ik de initiële hoofdpijn voorbij was). Voor macOS gebruik ik voorlopig Homebrew, wat niet ideaal is, maar wel eenvoudig op te zetten was. Waarschijnlijk stap ik over op een native signed macOS-app zodra ik de tijd neem om dit correct te verpakken. Beide oplossingen werken prima voor moderne Windows- en macOS-gebruikers.
Linux is een ander verhaal. Ik begon met het uitbrengen van Fresh als een npm-pakket. Dit voelde onhandig en irriteerde sommige mensen op Hacker News, maar het bracht ook echte problemen met zich mee:
- Beveiliging: npmjs heeft meerdere datalekken gehad en ik wil 's nachts beter kunnen slapen.
- Niet universeel: veel gebruikers hebben geen npm geïnstalleerd; waarom zouden ze npm installeren, enkel om Fresh te installeren?
- Vreemde installer: de flow van
npm install is eigenlijk een script dat je via npmjs krijgt, dat vervolgens naar GitHub gaat en het juiste binaire artifact voor jouw machine downloadt. Dit moet je elke keer doen als je wilt updaten.
Ik heb geluisterd naar de feedback van de gefrustreerde gebruikers en (met hulp van verschillende vrijwilligers) hebben we pakketten gemaakt voor "ALLES":
- Rust's cargo op crates.io (en ook cargo-binstall)
- Distributie-onafhankelijke formaten zoals AppImage en Flatpak
- deb
- rpm
- AUR (Arch Linux), in twee varianten: source build en pre-built binary (-bin pakket)
- nix
- mise
- Homebrew voor Linux
- npm / npx
- Terra
- Gentoo GURU
Daarnaast breng ik pre-built binaries uit als een tarball. Je kunt je voorstellen hoe fragiel dit alles is. Na elke release vraag ik me af of ik tegen problemen aan loop bij een van de vele kanalen. Elke oplossing werkt voor sommige gebruikers, maar geen enkele werkt voor álle gebruikers. En ze hebben allemaal nadelen.
De beperkingen van specifieke oplossingen
"Waarom gebruik je niet gewoon Nix?!" Omdat veel mensen GEEN Nix geïnstalleerd hebben en dit niet willen doen enkel om mijn app te gebruiken. Ik ondersteun Nix als methode, maar het is geen universele oplossing.
"Gebruik gewoon Flatpak!" Ik breng een Flatpak uit, maar ik werk er eigenlijk tegenin. Flatpak is ontworpen voor zelfstandige, gesandboxed desktop GUI-applicaties, terwijl ik een terminal-gebaseerde TUI uitbreng waarmee je vrij door je machine en netwerk kunt bewegen. Ik moet verschrikkelijke "bad practice"-vlaggen gebruiken om het werkend te krijgen. Flatpak sandboxing is simpelweg niet geschikt.
"Gebruik gewoon AppImage!" Ik breng een AppImage uit, maar deze start extreem traag vanwege de squashfs FUSE-mount on-demand, wat de opstarttijd onacceptabel maakt. Ik wil dat mijn programma direct start, zo snel als technisch mogelijk. Om de opstarttijden redelijk te maken, gebruikt mijn installer-script een vreselijke hack om de squashfs-inhoud ergens uit te pakken en de AppImage te verlaten. Je kunt de AppImage nog steeds gewoon uitvoeren als je dat wilt, maar het start irritant traag. Bovendien is in beide gevallen FUSE vereist. Waarom heeft mijn app FUSE nodig?!
Daarnaast is mijn binary niet echt universeel portable omdat deze een minimale libc-versie vereist (voor een distro van 2-3 jaar oud werkt het mogelijk niet). Ik had net zo goed static linking kunnen gebruiken en AppImage kunnen laten vallen; ik had me voor een bepaalde reden ingebild dat AppImage dit portability-probleem alleen zou oplossen. Ten slotte hebben veel mensen vanwege diverse redenen een slecht beeld van AppImage, Flatpak (en Snap) en weigeren ze deze simpelweg te gebruiken.
De pijn van de Debian-familie
Zoals uitgelegd in een andere Hacker News-discussie, heb ik het nog niet gered om mijn .deb als officieel Debian- (en Ubuntu-) pakket aan te bieden. Dit vereist namelijk dat alle (vele) Rust-dependencies ook Debian-pakketten zijn. Er zijn goede redenen voor dit beleid — volledig reproduceerbare en zelfstandige builds, verminderen van "supply chain hell", etc. — maar het is enorm veel werk. Hoe kan ik dit bijhouden? Elke directe dependency zou bij elk beveiligingsprobleem op Debian opnieuw moeten worden geüpdatet. Daar heb ik geen tijd voor. Ik kan mijn dependencies niet zomaar als broncode in mijn deb-sourcepakket stoppen, want dat is tegen het beleid.
Een andere anekdote is dat ik oudere machines wil ondersteunen die bijvoorbeeld een oudere Ubuntu draaien. Deze hebben echter een oudere libc, wat betekent dat mijn build voor nieuwere Ubuntu's niet geïnstalleerd kan worden op oudere versies omdat ze falen op een link-error tijdens het laden van de binary. Ik zou dan in een oude Ubuntu container image moeten bouwen en de ongepatchte pakketten daaruit moeten overnemen — of deze gebruikers volledig moeten laten vallen. Geen van beide opties is houdbaar.
Geen automatische updates voor .deb / .rpm
Omdat het zo'n hoofdpijn is, zijn mijn .deb (of .rpm) pakketten niet geaccepteerd in de officiële bronnen. Hierdoor krijgen mensen die deze pakketten installeren geen automatische updates wanneer ze het native update-mechanisme van hun systeem gebruiken (apt-get upgrade of equivalent). Het zou fijn zijn geweest als er een snelle serverloze oplossing bestond waarbij je simpelweg een URL opgeeft waar gezocht kan worden naar nieuwere versies van één enkel pakket, en zowel apt als dnf dat zouden onthouden.
Ik ben me ervan bewust dat het "juiste" om te doen is om mijn pakket in de officiële kanalen te krijgen. Ik heb een Bugzilla-ticket geopend voor Fedora en iemand is iets begonnen voor Ubuntu, maar ik heb simpelweg niet de tijd en energie om deze verzoeken door te drukken. Ik wil gewoon MIJN SOFTWARE GRATIS WEGGEVEN. Mijn punt is: omdat mensen allerlei verschillende distro's gebruiken, is de cumulatieve inspanning om dit correct te doen extreem hoog.
Mise werkte, totdat het niet meer werkte
Het blijkt dat sommige mensen mise gebruiken als hun lokale (user-level) package manager. Iemand kreeg het werkend voor Fresh, wat geweldig was! Maar op een dag stopte het ermee, zonder dat er aan mijn kant iets veranderde. Het bleek dat GitHub het certificaat van zijn build attestation-systeem had geroteerd, en mise had een verouderde trust root vastgepind in plaats van de huidige op te halen. Ik wist niet eens dat er iets kapot was totdat iemand klaagde. Uiteindelijk heb ik het zelf opgelost, maar waarom besteed ik mijn tijd aan dit soort problemen?
Arch Linux AUR
Ik gebruik persoonlijk Arch AUR en bied Fresh ook via dit kanaal aan (zowel als source als pre-built -bin pakket). Onlangs is dit echter geblokkeerd voor nieuwe package releases. De AUR staat in read-only modus vanwege enkele beveiligingslekken, waardoor ik al een paar versie-releases heb gemist. Voorlopig weet ik niet wanneer de AUR weer beschikbaar is. Als tijdelijke oplossing heb ik contact opgenomen met een maintainer om Fresh in de Arch 'extra' repo te krijgen in plaats van de AUR.
Conclusie en volgende stappen
De kern van dit hele betoog is dat distributie-specifieke kanalen geen goede oplossing zijn. Naarmate het aantal distro's en varianten toeneemt, wordt het bijna onmogelijk om zelfs een kleine update uit te brengen.
Focus op statisch gelinkte musl en self-updating binary Heb ik eigenlijk wel een package manager nodig? Ik bouw al een musl binary als onderdeel van de release. De volgende versie zal een nieuw ingebouwd self-updating mechanisme bevatten dat gebruikers op aanvraag kunnen triggeren. Dit zal het belangrijkste releasekanaal voor Linux worden en hopelijk de enige die ik in de toekomst moet ondersteunen. Alle bestaande pakketten blijven voorlopig ondersteund worden, maar het doel is om de statische binary als de aanbevolen standaard te maken.
Hiermee neem ik een risico; wie weet welke verrassingen dit brengt — zoals een Linux-portability probleem dat mijn statisch gelinkte binary voor sommige mensen breekt. Zij kunnen dan nog steeds vanuit broncode bouwen of gebruikmaken van de pakketbestanden die ik (voorlopig) blijf uitbrengen.
In feite implementeer ik mijn eigen kleine package manager. Zal het voor iedereen werken? Hopelijk! Fresh is ongeveer 12 MB om te downloaden en pakt uit tot circa 35 MB, dus dat zou een redelijke ervaring moeten zijn. Ik werk constant aan het verder verkleinen van die uiteindelijke binary-grootte.