Skip to content

Commit

Permalink
Merge pull request jupyterhub#407 from costrouc/costrouc/nix-buildpack
Browse files Browse the repository at this point in the history
Adding support for nix buildpack in repo2docker
  • Loading branch information
betatim authored Nov 5, 2018
2 parents 35de7d8 + b08f5ac commit 5c6ab7e
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 2 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ env:
- REPO_TYPE=stencila
- REPO_TYPE=julia
- REPO_TYPE=r
- REPO_TYPE=nix
- REPO_TYPE=dockerfile
- REPO_TYPE=external/*
- REPO_TYPE=**/*.py
Expand Down
24 changes: 23 additions & 1 deletion docs/source/config_files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ used for installing libraries.
To see an example R repository, visit our `R
example in binder-examples <https://github.com/binder-examples/r/blob/master/runtime.txt>`_.


``Dockerfile`` - Advanced environments
======================================

Expand All @@ -179,3 +178,26 @@ With Dockerfiles, a regular Docker build will be performed.

See the `Advanced Binder Documentation <https://mybinder.readthedocs.io/en/latest/tutorials/dockerfile.html>`_ for
best-practices with Dockerfiles.

.. _default.nix:

``default.nix``
~~~~~~~~~~~~~~~

This allows you to use the `nix package manager <https://github.com/NixOS/nixpkgs>`_. It is hard to explain what nix
is to new users and why it is usefull. If you are inclined please read
more at the `nix homepage <https://nixos.org/nix/>`_. It is currently
the largest package repository, offers reproducible builds, multiple
versions of same package coexisting, source and binary based, and
packages many languages such as python, R, go, javascript, haskell,
ruby, etc. .

A ``default.nix`` file allows you to use `nix-shell <https://nixos.org/nix/manual/#sec-nix-shell>`_
to evaluate a ``nix`` expression to define a reproducible nix environment.
The only requirement is that you expose a ``jupyter`` command within the shell
(since jupyterlab is currently what ``repo2docker`` is designed
around). While the ``nix`` environment does have ``NIX_PATH`` set with
``nixpkgs=...`` you should not rely on it and make sure to
`pin your nixpkgs <https://discourse.nixos.org/t/nixops-pinning-nixpkgs/734>`_.
By doing this you are truley producing a reproducible environment. To see an
example repository visit a `nix binder example <https://gitlab.com/costrouc/nix-binder-example>`_.
4 changes: 3 additions & 1 deletion repo2docker/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
from . import __version__
from .buildpacks import (
PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack,
CondaBuildPack, JuliaBuildPack, RBuildPack
CondaBuildPack, JuliaBuildPack, BaseImage,
RBuildPack, NixBuildPack
)
from . import contentproviders
from .utils import (
Expand Down Expand Up @@ -77,6 +78,7 @@ def _default_log_level(self):
LegacyBinderDockerBuildPack,
DockerBuildPack,
JuliaBuildPack,
NixBuildPack,
RBuildPack,
CondaBuildPack,
PythonBuildPack,
Expand Down
1 change: 1 addition & 0 deletions repo2docker/buildpacks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from .docker import DockerBuildPack
from .legacy import LegacyBinderDockerBuildPack
from .r import RBuildPack
from .nix import NixBuildPack
70 changes: 70 additions & 0 deletions repo2docker/buildpacks/nix/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""BuildPack for nixpkgs environments"""
import os

from ..base import BuildPack


class NixBuildPack(BuildPack):
"""A nix Package Manager BuildPack"""

def get_path(self):
"""Return paths to be added to PATH environemnt variable
"""
return super().get_path() + [
'/home/${NB_USER}/.nix-profile/bin'
]

def get_env(self):
"""Ordered list of environment variables to be set for this image"""
return super().get_env() + [
('NIX_PATH', "nixpkgs=/home/${NB_USER}/.nix-defexpr/channels/nixpkgs"),
('NIX_SSL_CERT_FILE', '/etc/ssl/certs/ca-certificates.crt'),
('GIT_SSL_CAINFO', '/etc/ssl/certs/ca-certificates.crt')
]

def get_build_scripts(self):
"""
Return series of build-steps common to all nix repositories.
Notice how only root privileges are needed for creating nix
directory.
- create nix directory for user nix installation
- install nix package manager for user
"""
return super().get_build_scripts() + [
("root", """
mkdir -m 0755 /nix && \
chown -R ${NB_USER}:${NB_USER} /nix /usr/local/bin/nix-shell-wrapper /home/${NB_USER}
"""),
("${NB_USER}", """
bash /home/${NB_USER}/.local/bin/install-nix.bash && \
rm /home/${NB_USER}/.local/bin/install-nix.bash
""")
]

def get_build_script_files(self):
"""Dict of files to be copied to the container image for use in building
"""
return {
"nix/install-nix.bash": "/home/${NB_USER}/.local/bin/install-nix.bash",
"nix/nix-shell-wrapper": "/usr/local/bin/nix-shell-wrapper"
}

def get_assemble_scripts(self):
"""Return series of build-steps specific to this source repository.
"""
return super().get_assemble_scripts() + [
('${NB_USER}', """
nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs && \
nix-channel --update && \
nix-shell default.nix --command "command -v jupyter"
""")
]

def get_start_script(self):
"""The path to a script to be executed as ENTRYPOINT"""
return "/usr/local/bin/nix-shell-wrapper"

def detect(self):
"""Check if current repo should be built with the nix BuildPack"""
return os.path.exists(self.binder_path('default.nix'))
12 changes: 12 additions & 0 deletions repo2docker/buildpacks/nix/install-nix.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
# This downloads and installs a pinned version of nix
set -ex

NIX_VERSION="2.1.1"
NIX_SHA256="ad10b4da69035a585fe89d7330037c4a5d867a372bb0e52a1542ab95aec67999"

wget https://nixos.org/releases/nix/nix-$NIX_VERSION/nix-$NIX_VERSION-x86_64-linux.tar.bz2
echo "$NIX_SHA256 nix-2.1.1-x86_64-linux.tar.bz2" | sha256sum -c
tar xjf nix-*-x86_64-linux.tar.bz2
sh nix-*-x86_64-linux/install
rm -r nix-*-x86_64-linux*
15 changes: 15 additions & 0 deletions repo2docker/buildpacks/nix/nix-shell-wrapper
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

_term() {
echo "Caught SIGTERM signal!"
# kill -TERM "$PID" 2>/dev/null
exit 0
}

trap _term SIGTERM

echo "$*"
nix-shell default.nix --command "$*" &

PID=$!
wait "$PID"
6 changes: 6 additions & 0 deletions tests/nix/simple/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Nix environment - default.nix
-----------------------------

You can install a nix shell environment using the traditional default.nix.

Documentation on the syntax and typical setup of a ``nix-shell`` environment can be found `here <https://nixos.org/nix/manual/#sec-nix-shell>`_.
21 changes: 21 additions & 0 deletions tests/nix/simple/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
let
# Pinning nixpkgs to specific release
# To get sha256 use "nix-prefetch-git <url> --rev <commit>"
commitRev="5574b6a152b1b3ae5f93ba37c4ffd1981f62bf5a";
nixpkgs = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/${commitRev}.tar.gz";
sha256 = "1pqdddp4aiz726c7qs1dwyfzixi14shp0mbzi1jhapl9hrajfsjg";
};
pkgs = import nixpkgs { config = { allowUnfree = true; }; };
in
pkgs.mkShell {
buildInputs = with pkgs; [
python36Packages.numpy
python36Packages.scipy
python36Packages.jupyterlab
];

shellHook = ''
export NIX_PATH="nixpkgs=${nixpkgs}:."
'';
}
3 changes: 3 additions & 0 deletions tests/nix/simple/verify
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env python
import numpy
import scipy

0 comments on commit 5c6ab7e

Please sign in to comment.