Skip to content

Instantly share code, notes, and snippets.

Last active April 12, 2017 04:05
Show Gist options
  • Save adnelson/45d4b2378dae90eee6c0 to your computer and use it in GitHub Desktop.
Save adnelson/45d4b2378dae90eee6c0 to your computer and use it in GitHub Desktop.
Nix release builder
# Function which takes a list of packages to install and creates a
# tarball which contains the full list of dependencies of those paths,
# and a script which will install them.
# For example, here's how you would create a tarball packaging up
# python3 and nodejs:
# let
# pkgs = import <nixpkgs> {};
# mkTarball = import ./mkTarball.nix {inherit pkgs;};
# in
# mkTarball {
# name = "python-and-node";
# packagesToInstall = [pkgs.python3 pkgs.nodejs];
# }
# The root package object (contains some essential things like nix
# and perl)
# Given a list of packages, creates a tarball containing the binaries for
# those packages and an installation script which will install all of them.
# Includes nix and a CA cert file by default (if no other packages are
# provided, only these will be installed).
# The name of the projects being bundled.
# The packages to include in this project. They will be copied into the
# nix store, but not installed into the user environment (~/.nix-profile).
packagesToInclude ? [],
# Packages to install into user environment in addition to nix.
packagesToInstall ? [],
# The compression type to use ("xz" or "gz")
compressionType ? "xz"
# Grab the following objects from the base packages object:
inherit (pkgs)
# The nix executable
# Runs the `pathsFromGraph` script below
# For showing progress while tarring
# A perl script that generates paths
# Compresses stuff (obviously)
# Provides the `uname` command
# Will run a shell script and create the derivation
# Root-level certificates used when nix is curling things.
# Contains low-level dependencies required when installing later.
inherit (pkgs.lib) zipListsWith concatStringsSep;
inherit (builtins) map;
# Tells us about the system (e.g. linux vs darwin).
system = builtins.currentSystem;
# Full list of packages to lump together.
packageList = packagesToInclude ++ packagesToInstall ++ [nix cacert stdenv];
# Names of closure files that we will generate.
closures = map (pkg: "${}-closure") packageList;
# Closures as they will appear in bash, a space-separated list.
closureArgs = concatStringsSep " " (map (x: "./${x}") closures);
compressor = if compressionType == "xz" then "xz -c -"
else if compressionType == "gz" then "gzip"
else throw "Invalid compression type: ${compressionType}";
runCommand "${name}-binary-tarball"
# Creates some files and populates them with the dependency closure of
# various expressions, which will be input into the perl `pathsFromGraph`
# invocation, below.
exportReferencesGraph = zipListsWith (a: b: [a b]) closures packageList;
buildInputs = [ perl coreutils pv ];
packageNames = map (p: packageList;
meta.description = "Builds a self-contained Nix installer for ${system}";
storePaths=$(${perl}/bin/perl ${pathsFromGraph} ${closureArgs})
printRegistration=1 ${perl}/bin/perl ${pathsFromGraph} \
${closureArgs} > $TMPDIR/reginfo
# `dest` is the folder in which the nix store appears.
# All of these files will be read by the install script.
dirname $NIX_STORE > $TMPDIR/dest_path
# Store the paths of nix and the cacert; these get special treatment.
echo ${nix} > $TMPDIR/nix_path
echo ${cacert} > $TMPDIR/cacert_path
# Store the system this was built on.
echo "$(uname -s).$(uname -m)" > $TMPDIR/expected_system
# Make these scalar files read-only.
chmod 440 $TMPDIR/dest_path $TMPDIR/reginfo \
$TMPDIR/cacert_path $TMPDIR/nix_path $TMPDIR/expected_system
# Store the paths of extra packages to install.
mkdir -p $TMPDIR/also_install
${concatStringsSep "\n" (map (p: ''
echo ${p} > $TMPDIR/also_install/${}
chmod 440 $TMPDIR/also_install/${}
'') packagesToInstall)}
cp ${./} $TMPDIR/install
chmod +x $TMPDIR/install
mkdir -p $out
echo "Building release tarball in ${compressionType} format"
files_to_compress="$TMPDIR/install $TMPDIR/reginfo $TMPDIR/dest_path
$TMPDIR/nix_path $TMPDIR/cacert_path
$TMPDIR/also_install $TMPDIR/expected_system
tar -cf $intermediate \
--owner=0 --group=0 --mode=u+rw,uga+r -P \
--absolute-names \
--hard-dereference \
--transform "s,$TMPDIR/install,$directory/install," \
--transform "s,$TMPDIR/dest_path,$directory/dest_path," \
--transform "s,$TMPDIR/reginfo,$directory/reginfo," \
--transform "s,$TMPDIR/nix_path,$directory/nix_path," \
--transform "s,$TMPDIR/cacert_path,$directory/cacert_path," \
--transform "s,$TMPDIR/also_install,$directory/also_install," \
--transform "s,$TMPDIR/expected_system,$directory/expected_system," \
--transform "s,$NIX_STORE,$directory/store,S" \
size="$(du -sb $intermediate | awk '{print $1}')"
cat $intermediate | pv -ptef -s $size | ${compressor} > $tarball
#! /usr/bin/env bash
# This script will install nix and a set of dependencies. You can see what
# other programs will be installed by looking at the file names in the
# ./package_paths directory.
set -e
# The argument passed to bash to run this script, e.g. 'nix-1.8/install'.
# The directory the script is in, e.g. `nix-1.8`.
dir=$(dirname $script)
# The current user running this script.
user=$(command whoami)
# Nix will create the following three files when it builds this tarball.
# The prefix into which the nix store and state folders will be installed.
# E.g., `/nix` or `/opt/nix`
dest=$(cat $dir/dest_path)
# The location within the nix store of the nix bin/lib/etc directories.
# E.g. `/nix/store/845yoiefnwe9084rtgunsdoifut-nix-1.8`
nix=$(cat $dir/nix_path)
# The location within the nix store of a root-level CA certificate file.
# E.g `/nix/store/394utnldfkutuw04893u5in3w4-cacert-20140417`
cacert=$(cat $dir/cacert_path)
# The system this package is meant to be installed onto (e.g. 64-bit linux).
expect_system=$(cat $dir/expected_system)
this_system="$(uname -s).$(uname -m)"
if [ $expect_system != $this_system ]; then
echo "Incompatible system:"
echo "This build is meant for $expect_system, but this system is $this_system." >&2
exit 1
try_or_abort() {
# Tries a command, exits with a message if the command fails.
errmsg=${2:-"Command '$1' failed"}
command bash -c "$1" || {
echo "${script}: ${errmsg}" >&2
exit 1
try_or_abort '! type -pf nix-env 2>&1 >/dev/null' \
"Nix has already been installed."
try_or_abort 'test "$(id -u)" != 0' "Refuse to install as root."
try_or_abort "mkdir -p ${dest}" "Could not create nix directory $dest."
try_or_abort "chmod 0755 ${dest}" "Couldn't change permissions of ${dest}."
try_or_abort "test -w ${dest}" "${dest} is not writable by ${user}."
echo "Installing Nix..."
mkdir -p ${dest}/store
echo -n "Copying Nix objects to ${dest}/store..."
for i in $(ls $dir/store); do
echo -n "."
if [ -e "$i_tmp" ]; then
rm -rf "$i_tmp"
if ! [ -e "${dest}/store/$i" ]; then
cp -Rp "${dir}/store/$i" "$i_tmp"
mv "$i_tmp" "${dest}/store/$i"
echo " OK"
echo "Initializing Nix database..."
try_or_abort "${nix}/bin/nix-store --init" \
"failed to initialize the Nix database"
try_or_abort "${nix}/bin/nix-store --load-db < ${dir}/reginfo" \
"unable to register valid paths"
source ${nix}/etc/profile.d/
echo "Installing nix..."
try_or_abort "${nix}/bin/nix-env -i ${nix} --option use-binary-caches false" \
"unable to install Nix into your default profile"
echo "Installing SSL certificate bundle..."
${nix}/bin/nix-env -i "$cacert"
export SSL_CERT_FILE="$HOME/.nix-profile/etc/ca-bundle.crt"
for pkg_path_file in $dir/also_install/*; do
echo "Installing $(basename pkg_path_file)..."
${nix}/bin/nix-env -i "$(cat $pkg_path_file)" --option use-binary-caches false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment