Skip to content

Instantly share code, notes, and snippets.

Last active June 5, 2023 16:01
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dckc/2e6b5c8029246ab38c16e254fc3d3f4d to your computer and use it in GitHub Desktop.
Save dckc/2e6b5c8029246ab38c16e254fc3d3f4d to your computer and use it in GitHub Desktop.
nix + sandstorm = <3

The Generic steps to Sandstorm packaging shows a trivial nodejs server. We have a basic package.json to go a long with it.

But rather than using spk dev to find the relevant files, we write a a nix expression in default.nix including:

buildInputs = [ pkgs.nodejs ];

A Makefile ties the nix and sandstorm worlds together:

$ make
rm -rf result
these derivations will be built:
building path(s) ‘/nix/store/caskj1d5idk4zng9hhnmnw35j7hn285j-spk1-0.1’
patching script interpreter paths in /nix/store/caskj1d5idk4zng9hhnmnw35j7hn285j-spk1-0.1/bin/spk1
/nix/store/caskj1d5idk4zng9hhnmnw35j7hn285j-spk1-0.1/bin/spk1: interpreter directive changed from "/usr/bin/env node" to "/nix/store/g2b5wwyqz2khi7c36nmafvj1wdv9zykh-nodejs-4.4.6/bin/node"
find $(nix-store --query --requisites `readlink result`) | sed -e 's,^/,,' >sandstorm-files.list
spk pack spk1.spk
{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation rec {
name = "spk1-0.1";
src = ./.;
script = ./index.js;
buildInputs = [ pkgs.nodejs ];
buildCommand = ''
mkdir -p $out/bin/
install -D -m755 $script $out/bin/spk1
patchShebangs $out/bin/spk1
#!/usr/bin/env node
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(11000, '');
console.log('Server running at');
spk1.spk: sandstorm-pkgdef.capnp sandstorm-files.list
spk pack $@
# Destination (in-package) path must not start with '/': /nix/store/6f...
sandstorm-files.list: result
# nix-store --query --requisites result | sed -e 's,^/,,' >$@
find $$(nix-store --query --requisites `readlink result`) | sed -e 's,^/,,' >$@
result: default.nix
rm -rf result
$(RM) -rf result sandstorm-files.list spk1 spk1.spk *~
"name": "spk1",
"version": "0.1.0",
"description": "Sandstorm Package built with nix",
"dependencies": {
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
"keywords": [
"author": "Dan Connolly",
"license": "MIT"
using Spk = import "/sandstorm/package.capnp";
# This imports:
# $SANDSTORM_HOME/latest/usr/include/sandstorm/package.capnp
# Check out that file to see the full, documented package definition format.
const pkgdef :Spk.PackageDefinition = (
# The package definition. Note that the spk tool looks specifically for the
# "pkgdef" constant.
id = "kxtqtg41d4smvxrahyss5221uhqahun1a4ukm03fep6prs6j9zr0",
# Your app ID is actually its public key. The private key was placed in
# your keyring. All updates must be signed with the same key.
manifest = (
# This manifest is included in your app package to tell Sandstorm
# about your app.
appVersion = 0, # Increment this for every release.
appTitle = (defaultText = "Dan Sandstorm Pkg 1"),
appMarketingVersion = (defaultText = "0.0.0"),
actions = [
# Define your "new document" handlers here.
( title = (defaultText = "Dan Sandstorm Pkg 1"),
command = .myCommand
# The command to run when starting for the first time. (".myCommand"
# is just a constant defined at the bottom of the file.)
continueCommand = .myCommand
# This is the command called to start your app back up after it has been
# shut down for inactivity. Here we're using the same command as for
# starting a new instance, but you could use different commands for each
# case.
sourceMap = (
# Here we defined where to look for files to copy into your package. The
# `spk dev` command actually figures out what files your app needs
# automatically by running it on a FUSE filesystem. So, the mappings
# here are only to tell it where to find files that the app wants.
searchPath = [
( sourcePath = "result" ), # Search this directory first.
( sourcePath = "/", # Then search the system root directory.
hidePaths = [ "home", "proc", "sys",
"etc/passwd", "etc/hosts", "etc/host.conf",
"etc/nsswitch.conf", "etc/resolv.conf" ]
# You probably don't want the app pulling files from these places,
# so we hide them. Note that /dev, /var, and /tmp are implicitly
# hidden because Sandstorm itself provides them.
fileList = "sandstorm-files.list",
# `spk dev` will write a list of all the files your app uses to this file.
# You should review it later, before shipping your app.
alwaysInclude = [
# Fill this list with more names of files or directories that should be
# included in your package, even if not listed in sandstorm-files.list.
# Use this to force-include stuff that you know you need but which may
# not have been detected as a dependency during `spk dev`. If you list
# a directory here, its entire contents will be included recursively.
const myCommand :Spk.Manifest.Command = (
# Here we define the command used to start up your server.
argv = ["/sandstorm-http-bridge", "11000", "--", "/bin/spk1"],
environ = [
# Note that this defines the *entire* environment seen by your app.
(key = "PATH", value = "/usr/local/bin:/usr/bin:/bin"),
(key = "HOME", value = "/var")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment