available here and on GitHub
Macnite: a Ninite for Mac, built in a static HTML file

If you’ve ever set up a Windows machine, you’ve probably used Ninite. You tick the apps you want, you get one installer, you double-click it, and a few minutes later your machine is loaded up with Chrome, VLC, 7-Zip, and so on. No wizards. No “decline” buttons for browser toolbars. No signups. It’s lovely.
There’s nothing like it for Mac.
There’s Homebrew, which is amazing — but it’s a command-line tool, and asking a friend who just got a new MacBook to “open Terminal and run brew install –cask google-chrome firefox 1password zoom” is asking a lot.
So I built Macnite: a single HTML page where you tick apps, hit a button, and get a one-line command (or a downloadable installer) that does the whole thing. Homebrew under the hood, no signup, no tracking, no install wizard.
Here’s how it works.
The whole app is static
Macnite is index.html, app.js, styles.css, and a curated popular.js. There’s no backend. There’s no build step. The deploy is “drag the folder into Netlify.”
That’s possible because Homebrew already publishes a complete machine-readable catalog as JSON. One endpoint lists every GUI app brew knows about, another lists every CLI tool. Both serve permissive CORS headers, so the browser can fetch them directly. About ten megabytes combined. I cache the result in localStorage for 24 hours so the second page load is instant.
Picking apps is just a set of strings
When you tick a tile, I store “cask:google-chrome” in a Set. When you untick it, I remove it. That’s the entire selection model. The same app can appear in both the popular grid and the search results — a small helper syncs the checked state across mirrored tiles so they stay in lockstep.
The popular list itself is a hand-curated array. I started out heavy on dev tools and got feedback that the homepage felt too technical, so I rebalanced toward everyday apps: browsers, messaging, media, password managers, ChatGPT, Steam. CLI tools are still findable via search.
Generating the install command
This was the part I got wrong twice before getting right.
First attempt: just chain two brew install calls with &&. This assumes brew is already installed. If it isn’t, the command fails with “command not found: brew” and the user is stranded.
Second attempt: detect brew, run the official Homebrew install script if missing, then run brew install. Works on Intel Macs. Fails on Apple Silicon. Why? Because on Apple Silicon, Homebrew installs to /opt/homebrew/bin, which isn’t on PATH by default. The brew install script tells you to add it to your shell profile — but that affects future shells, not the one you’re sitting in right now. So brew install immediately after the bootstrap still fails.
Final form: detect brew, install if missing, then eval the output of brew shellenv to put brew on PATH in the current shell. Try Apple Silicon’s path first, fall back to Intel’s. Now the command works on a fresh Mac.
The downloadable shell script does the same thing in a more readable form.
The “what do I do with this” dialog
After you hit Copy or Download, Macnite pops up a modal with literal-English instructions: open Terminal, paste, hit Enter, type your password when asked, wait. A first user got tripped up by an earlier version of the instructions because the words “bash” and “brew” look similar at a glance — they typed “brew script.sh” instead of “bash script.sh”, and brew helpfully replied with its usage page.
So the dialog now shows the entire pasteable command in a code block with a Copy button. No typing required, no word confusion.
The download path has its own gotcha: if you’ve already downloaded the installer once, Safari names the new one macnite-install-2.sh, and a hardcoded path to macnite-install.sh won’t find it. The fix is to use a shell snippet that lists files by modification time, takes the most recent one, and runs that. Whatever name Safari gave the file, the command finds the newest matching script in Downloads and runs it.
Reporting problems
A static site can’t run code on a server, but Netlify Forms gives you a zero-backend way to collect submissions. You drop a form element with a netlify attribute in your HTML, Netlify scrapes it at deploy time, and form POSTs to the page URL get routed to your dashboard.
I started with an auto-popping error banner, but a global error listener picks up plenty of unrelated noise from extensions and content blockers, so the page kept telling users “Something went wrong.” with no detail. Bad UX. The current version is a plain textarea at the bottom: “Need help or seeing a problem? Paste any error message and we’ll take a look.” That’s it.
What’s next
Probably: icons that aren’t favicons (the favicon service we use is fine but inconsistent across vendors), a way to share a Macnite link to a specific selection, and a “did this work?” callback so I can see which apps actually install cleanly on first try.
The whole thing is about five hundred lines of JavaScript. The hardest parts were the things that looked simple: one shell line that works on every Mac, four sentences of instructions that don’t mislead a non-technical user. Software is funny like that.

Leave a Reply