A Japanese version of this is on Note.
Building an app for a Mac is like LEGO.
The box comes full of well-made, standardized parts: tidy UI pieces, a finished Unix environment (a properly UNIX-certified one, at that), a huge shelf of compatible parts in Homebrew. Follow the booklet and even one person working alone can put together something that looks decent and holds up.
But LEGO has shapes that click and shapes that don’t. Try to force a part that isn’t cut to the stud system and it simply won’t attach. On macOS that stud system is code signing, notarization, entitlements, SIP. Inside the standard it’s comfortable; the moment you reach outside it, you’re refused.
Linux was Minecraft.
You spawn on an empty patch of ground with nothing around you. Punch a tree for an axe, mine ore for tools, wire up redstone for automation. The ceiling is basically infinite, but for a while the environment-building itself — wrestling config files, installing what you need by hand — is the game. And when night falls, mobs come. You build the walls yourself.
I took the same thing, a background security app I wrote, and assembled it in both. I was planning a port. Swap pf for nftables, Swift for Rust, the AppKit menu bar for a GTK tray, done.
What I felt building it is that the difference isn’t in the parts. It’s that who looks after safety and trust is arranged differently from the start. At least that’s how it looked to me, assembling the same app twice. And the thing I build lands right on that.
The booklet, and the parts that don’t fit
macOS has a clear approach: don’t make users evaluate developers. The box does it for them.
Apple checks the signature, runs the notarization, reviews the entitlements. A user never has to ask “can I trust this developer?” When you buy a sealed official LEGO set, you don’t inspect every brick; you trust the stamp on the box. Even an app from an unknown individual like me, once it clears that, gets trusted because the user trusts Apple and that trust carries through.
From a developer’s seat it’s cramped. The shapes you can build are fixed up front, and anything off-spec means requesting permission one piece at a time. The entitlements for Network Extension and Endpoint Security go through an Apple request process that can take months, or come back denied.
But that cramped feeling is also the guarantee to the user. This app can’t get outside the standard. It can’t do anything outside its entitlements, and the places SIP protects are off-limits even to root. If one bad brick gets in, the whole build is meshed on the same stud system, so it’s less likely to bring the structure down. The box carries part of the strength. The friction is, I think, a deliberate design choice.
(That said, when App Translocation broke my entire first-run flow, I did think “okay, this is overprotective.”)
Spawning on empty ground
Linux doesn’t have that box.
Peering at the whole filesystem with fanotify, rewriting the entire nftables ruleset, sending SIGSTOP to freeze somebody else’s process — as root you can do all of it. Like breaking any block in creative mode, there’s no mechanism that stops you at “that’s too far.” The moment your systemd daemon starts, it can do almost anything on the machine.
“Root can do anything” is an assumption that runs back through Unix. The awkward wrinkle: the one that actually holds The Open Group’s UNIX certification is macOS (UNIX 03, going back to Mac OS X 10.5); Linux is a Unix-like system that implements the same conventions independently and has never been certified. And yet it’s Linux that keeps the old “the administrator can touch everything” assumption plainly intact, while certified-UNIX macOS is the one layering SIP, the sandbox, and entitlements on top — places root can’t reach. It’s not about which is the “real” one. From the same starting point, the two picked opposite directions.
Freedom, if you want to call it that. But carrying a security app into it, it’s mostly unnerving. Nobody hands you the guarantee that “this app won’t go rogue.” You write it yourself.
My app detects bulk ransomware encryption by spotting “one process creating a lot of high-entropy files in a short window,” then freezes the culprit with SIGSTOP. The Mac version has the same logic. But on a Mac I can’t reach into arbitrary processes without limit in the first place. On Linux I can, so I ended up hardcoding a list of things to never freeze: systemd, dockerd, NetworkManager, and friends.
Before that list existed, testing on my own machine, I misfired on shred, flagged it as ransomware, and went to freeze it. The daemon ballooned to 1.8 GB of RAM, hung for 30 seconds, and got killed by the systemd watchdog. All that was left in my home directory was one half-written mystery file. (Fixed since.) On a Mac that’s the class of accident that would have stopped a step earlier.
So building the walls before nightfall was entirely on me. The emergency cutoff auto-releases on a timer. All firewall-touching code funnels through one place so two features can’t write nftables at once. The line the box won’t draw, I drew inside my own code.
But I drew it the way this world does things, or tried to. Everything opt-in, releasable at any time, and it expires on its own if you leave it. An app that’s supposed to protect you shouldn’t be the thing that overrides “the administrator is sovereign.”
Assembly conventions
The difference in thinking shows up in the small stuff.
Firewall. macOS pf just wants an anchor; coexisting with everything else is assumed. Linux nftables, even with a dedicated table, shares one netfilter space with ufw, firewalld, and Docker. If someone runs nft flush ruleset, all of it goes, Docker’s rules included. You watch your own rules.
Signing. macOS: Apple’s notary service. The stamp belongs to whoever owns the box; I run every build through it. Linux: I sign with my own GPG key and put the fingerprint in the PKGBUILD‘s validpgpkeys. The trust starts with me. (The AUR currently has new-account registration disabled while it cleans up a wave of malicious packages, so one route in is just closed. A system with no central desk is fragile in spots like this.)
Config file. macOS: a plist and UserDefaults, effectively unreadable to the user. Linux: plaintext at ~/.config/roamswitch/config.json, where it’s simply assumed the user can see exactly which block went where.
Uninstall. macOS: drag to Trash, done (and the helper leaves residue for a while). Linux: apt remove is expected to cleanly clear away everything you placed, and that’s on the package.
Launch at login. macOS: one SMAppService call. Linux: two places, an XDG autostart entry and a systemd --user unit, and turning it fully off was quietly hard (one half kept coming back).
Earning a stranger’s trust
The place the two diverged most was how you earn trust.
On macOS, the box’s stamp is the trust. A closed-source paid app is normal and mostly nobody asks why. Notarization passed; that’s often enough.
Do the same on Linux and “why isn’t this open source?” is the first question. It’s the same reason nobody on a multiplayer server installs a stranger’s mod without reading it. A lot of people here put “you can read the source” at the front of what trust means, and “someone important vouched for it” carries less weight. It’s the culture that grew up saying “given enough eyeballs, all bugs are shallow.”
So I kept the source closed but wrote the whole design into a whitepaper and shipped a set of commands the user can run to confirm the app never talks outside: ldd to see no HTTP library is linked, ss to see there’s not one outbound socket, strace to see every connect goes to AF_UNIX. If the box won’t vouch for you, you open up what you built and show people.
Looking back
I don’t think this is a question of which one is right.
macOS’s friction annoys developers. But for users it’s the comfort of “even if I install something sketchy, the box catches some of it.” Linux’s freedom lets you build anything, at the cost of owning your own strength and your own trust.
Assembling the same app in both taught me the difference isn’t in features. It changes what the work is. On macOS my job was to build neatly inside the standard. On Linux my job is to set the standard myself, be careful it doesn’t trample the resident’s sovereignty, and — since “I set it myself” convinces nobody — show every part of it.
For someone who kept groaning about the booklet, a world without one is surprisingly tiring. But a line I drew myself, I can at least explain the whole reason for. That’s not nothing.
The app is RoamSwitch. The Linux version is free for personal use. Full design details are in the whitepaper; the hands-on write-ups are on Zenn (Japanese).