dmjio
4/12/2017 - 4:05 AM

Nix release builder

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)
{pkgs}:

# 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.
  name,

  # 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"
}:


let
  # Grab the following objects from the base packages object:
  inherit (pkgs)
    # The nix executable
    nix
    # Runs the `pathsFromGraph` script below
    perl
    # For showing progress while tarring
    pv
    # A perl script that generates paths
    pathsFromGraph
    # Compresses stuff (obviously)
    gnutar
    # Provides the `uname` command
    coreutils
    # Will run a shell script and create the derivation
    runCommand
    # Root-level certificates used when nix is curling things.
    cacert
    # Contains low-level dependencies required when installing later.
    stdenv;
  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: "${pkg.name}-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}";
in

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: p.name) 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/${p.name}
        chmod 440 $TMPDIR/also_install/${p.name}
      '') packagesToInstall)}
    cp ${./installation.sh} $TMPDIR/install
    chmod +x $TMPDIR/install
    mkdir -p $out
    directory=${name}-${system}
    tarball=$out/$directory.tar.${compressionType}
    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
                       $storePaths"

    intermediate=$TMPDIR/intermediate.tar
    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" \
      $files_to_compress
    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'.
script=$0
# 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
fi

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 "."
  i_tmp="${dest}/store/$i.$$"
  if [ -e "$i_tmp" ]; then
    rm -rf "$i_tmp"
  fi
  if ! [ -e "${dest}/store/$i" ]; then
    cp -Rp "${dir}/store/$i" "$i_tmp"
    mv "$i_tmp" "${dest}/store/$i"
  fi
done
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/nix.sh

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
done