last thread: Speeding up a custom-built Nix package installer
Project URL:
- first sample project narflinger-nodejs-18
- installer development narflinger
I’ve been using the Nix package manager to build a suite of newer software for Glitch for a while. The hope has been to use the packages built that way in projects that want a newer version of something, such as a language runtime. But it’s been complicated to set up Nix in order to install those packages.
In this post, I present a simplified installer script, NAR Flinger, for setting up packages from a binary cache. NAR Flinger avoids several sources of friction that Nix encounters when running on Glitch.
Background: Nix the proper way
Setting up and using Nix on Glitch complicated. Here are several of those complications to illustrate why.
Nix itself isn’t available in our project containers, so you’d install that.
First is getting the binaries. I have a great big tarball with Nix and all its dependencies. Incidentally, that’s served as a tarball of a Nix “store,” which means unpacking it sets it up as if you had used Nix to install Nix. And that’s quite big, so you’d want to install it into /tmp. And /tmp is ephemeral, so you have to write something in your project startup to do it. But also not to do it if Nix is already installed. Complicated.
Second, Nix operates on /nix/store by default, which doesn’t exist in project containers, and we’re not permitted to create it, so you’d set up some environment variables to have it work somewhere else, /tmp/nix/store in this case. In Glitch, the normal way to do that is to write something in your .env file. But that means it doesn’t get copied over to remixes, and people viewing your code can’t see what values you’ve put. Complicated.
Third, Nix downloads prebuilt packages from a “substituter,” which can be a “binary cache.” But the binary cache that it uses by default only has packages compiled to be used from /nix/store, which you can’t use in a Glitch project container, so you need to set it up with a different substituter and the public key for that substituter. The normal way to do that in Nix is to create a config file, which goes in /app/.config/nix/nix.conf. But on Glitch, /app/.config is in the global gitignore, so it won’t show up in the editor. Complicated.
Fourth, the Nix program is in some gnarly path in /tmp/nix/store/somecryptographichash-nix-whateverversion/bin, which you wouldn’t want to type out, so you need to do something to get that into your PATH. The normal way to do this in Nix is to have a “profile,” which collects a bunch of symbolic links in a relative stable location, /tmp/nix/var/nix/profiles/per-user/app/profile/bin, and add that to your path. You can use Nix itself to set up the profile, so you’d just run this one command from the complicated /tmp/nix/store/… path. But of course since the profile is in /tmp, you gotta set something up to do that if it doesn’t exist. Complicated.
Fifth, it turns out the more normal way is not actually to put /tmp/nix/var/nix/profiles/per-user/app/profile/bin in your path, but to put a shell script provided by Nix into your .bashrc, which does the PATH setup for you, so you somehow do that. But Glitch doesn’t run your .bashrc when it executes your project’s “install” and “start” scripts in your package.json. So you need to arrange for it to run explicitly. Possibly by adding it to the beginning of your install and start scripts. Oh but that shell script itself, it’s installed as part of Nix. So it’s not going to exist when your project starts up. Make sure to write your install script in such a way that it doesn’t crash when Glitch runs it before you install Nix. Complicated.
Then you’ll have the Nix package manager set up, but you haven’t actually installed the thing you wanted yet.
Sixth, you’d use Nix actually to install something. And I’ve seen people recommend various packages to do this declaratively. Nix does have a native way where you run nix-env -i
to install something, which I think works too. So you can go ahead and put a bunch of those into your “install” script. Not too complicated, but kind of ugly to write all those commands in a JSON string.
NAR Flinger
NAR Flinger (I’ve changed the capitalization and spacing since the provisional name in the last thread), in contrast, is a single script (currently written in Python), ~9 KB at the time of writing, that you configure in your package.json file. It’s small enough that you can just put it in your /app directory. And the configuration in package.json makes it easy to carry the configuration across project remixes.
Here’s how to configure it. From the sample project:
// package.json
{
"scripts": {
"install": "python3 narflinger.py", // (1)
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2"
},
"narflinger": {
"basenames": [ // (2)
"r9gqywii82drj1m893kdsdkidc80ir9z-nodejs-18.14.1"
]
}
}
- You set an install script to run NAR Flinger.
- You put a list of packages to install.
Sample project
Here’s a sample that uses Node.js 18’s built-in fetch
to check if glitch.me
appears on the Hacker News front page.
const express = require('express');
const app = express();
app.get('/', async (req, res, next) => {
let result;
try {
const hnRes = await fetch('https://news.ycombinator.com/');
if (!hnRes.ok) throw new Error(`Hacker News response ${hnRes.status} not ok`);
const hnText = await hnRes.text();
result = hnText.indexOf('glitch.me') !== -1;
} catch (e) {
next(e);
return;
}
res.set('Content-Type', 'text/plain');
res.send(`Is glitch.me on the Hacker News front page? ${result ? 'yes' : 'no'}`)
});
app.listen(process.env.PORT, () => {
console.error('listening on port', process.env.PORT);
});
See if it is: https://narflinger-nodejs-18.glitch.me/
In the next post, I’ll talk about what NAR Flinger doesn’t support and walk through what the script does.