Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
./services/desktops/profile-sync-daemon.nix
./services/desktops/telepathy.nix
./services/development/hoogle.nix
./services/development/jupyter/default.nix
./services/editors/emacs.nix
./services/editors/infinoted.nix
./services/games/factorio.nix
Expand Down
179 changes: 179 additions & 0 deletions nixos/modules/services/development/jupyter/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{ config, lib, pkgs, ... }:

with lib;

let

cfg = config.services.jupyter;


kernels = (pkgs.jupyterKernels.create {
kernelDefinitions = if cfg.kernels != null
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've basically had to move the null check here now.
I though of passing the pkgs.jupyterKernels.defaultKernelConfig as the default value for jupyter.kernels, but the it complains that it contains a reference to a derivation. (I can understand that having to build a derivation to create the doc is not the greatest).

then cfg.kernels
else pkgs.jupyterKernels.defaultKernelDefinition;
});

notebookConfig = pkgs.writeText "jupyter_config.py" ''
${cfg.notebookConfig}

c.NotebookApp.password = ${cfg.password}
'';

in {
meta.maintainers = with maintainers; [ aborsu ];

options.services.jupyter = {
enable = mkEnableOption "Jupyter development server";

ip = mkOption {
type = types.str;
default = "localhost";
description = ''
IP address Jupyter will be listening on.
'';
};

port = mkOption {
type = types.int;
default = 8888;
description = ''
Port number Jupyter will be listening on.
'';
};

notebookDir = mkOption {
type = types.str;
default = "~/";
description = ''
Root directory for notebooks.
'';
};

user = mkOption {
type = types.str;
default = "jupyter";
description = ''
Name of the user used to run the jupyter service.
For security reason, jupyter should really not be run as root.
If not set (jupyter), the service will create a jupyter user with appropriate settings.
'';
example = "aborsu";
};

group = mkOption {
type = types.str;
default = "jupyter";
description = ''
Name of the group used to run the jupyter service.
Use this if you want to create a group of users that are able to view the notebook directory's content.
'';
example = "users";
};

password = mkOption {
type = types.str;
description = ''
Password to use with notebook.
Can be generated using:
In [1]: from notebook.auth import passwd
In [2]: passwd('test')
Out[2]: 'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'
NOTE: you need to keep the single quote inside the nix string.
Or you can use a python oneliner:
"open('/path/secret_file', 'r', encoding='utf8').read().strip()"
It will be interpreted at the end of the notebookConfig.
'';
example = [
"'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'"
"open('/path/secret_file', 'r', encoding='utf8').read().strip()"
];
};

notebookConfig = mkOption {
type = types.lines;
default = "";
description = ''
Raw jupyter config.
'';
};

kernels = mkOption {
type = types.nullOr (types.attrsOf(types.submodule (import ./kernel-options.nix {
inherit lib;
})));

default = null;
example = literalExample ''
{
python3 = let
env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
ipykernel
pandas
scikitlearn
]));
in {
displayName = "Python 3 for machine learning";
argv = [
"$ {env.interpreter}"
"-m"
"ipykernel_launcher"
"-f"
"{connection_file}"
];
language = "python";
logo32 = "$ {env.sitePackages}/ipykernel/resources/logo-32x32.png";
logo64 = "$ {env.sitePackages}/ipykernel/resources/logo-64x64.png";
};
}
'';
description = "Declarative kernel config

Kernels can be declared in any language that supports and has the required
dependencies to communicate with a jupyter server.
In python's case, it means that ipykernel package must always be included in
the list of packages of the targeted environment.
";
};
};

config = mkMerge [
(mkIf cfg.enable {
systemd.services.jupyter = {
description = "Jupyter development server";

wantedBy = [ "multi-user.target" ];

path = [ pkgs.bash ]; # needed for sh in cell magic to work
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered patching notebook to hardcode bash?


environment = {
JUPYTER_PATH = toString kernels;
};

serviceConfig = {
Restart = "always";
ExecStart = ''${pkgs.python3.pkgs.notebook}/bin/jupyter-notebook \
--no-browser \
--ip=${cfg.ip} \
--port=${toString cfg.port} --port-retries 0 \
--notebook-dir=${cfg.notebookDir} \
--NotebookApp.config_file=${notebookConfig}
'';
User = cfg.user;
Group = cfg.group;
WorkingDirectory = "~";
};
};
})
(mkIf (cfg.enable && (cfg.group == "jupyter")) {
users.groups.jupyter = {};
})
(mkIf (cfg.enable && (cfg.user == "jupyter")) {
users.extraUsers.jupyter = {
extraGroups = [ cfg.group ];
home = "/var/lib/jupyter";
createHome = true;
useDefaultShell = true; # needed so that the user can start a terminal.
};
})
];
}
60 changes: 60 additions & 0 deletions nixos/modules/services/development/jupyter/kernel-options.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Options that can be used for creating a jupyter kernel.
{lib }:

with lib;

{
options = {

displayName = mkOption {
type = types.str;
default = "";
example = [
"Python 3"
"Python 3 for Data Science"
];
description = ''
Name that will be shown to the user.
'';
};

argv = mkOption {
type = types.listOf types.str;
example = [
"{customEnv}/bin/python"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${customEnv.interpreter}

"-m"
"ipykernel_launcher"
"-f"
"{connection_file}"
];
description = ''
Command and arguments to start the kernel.
'';
};

language = mkOption {
type = types.str;
example = "python";
description = ''
Language of the environment. Typically the name of the binary.
'';
};

logo32 = mkOption {
type = types.nullOr types.path;
default = null;
example = "{env}/lib/python3.6/site-packages/ipykernel/resources/logo-32x32.png";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${env.sitePackages}

description = ''
Path to 32x32 logo png.
'';
};
logo64 = mkOption {
type = types.nullOr types.path;
default = null;
example = "{env}/lib/python3.6/site-packages/ipykernel/resources/logo-64x64.png";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${env.sitePackages}

description = ''
Path to 64x64 logo png.
'';
};
};
}
16 changes: 16 additions & 0 deletions pkgs/applications/editors/jupyter/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{ python3
, jupyterKernels
, kernelDefinitions ? jupyterKernels.defaultKernelDefinition
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is neat.

}:

let

jupyterPath = (jupyterKernels.create { kernelDefinitions = kernelDefinitions; });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inherit / space


in

with python3.pkgs; toPythonModule (
notebook.overrideAttrs(oldAttrs: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overridePythonAttrs

makeWrapperArgs = ["--set JUPYTER_PATH ${jupyterPath}"];
})
)
76 changes: 76 additions & 0 deletions pkgs/development/misc/jupyterKernels/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{ lib, pkgs, stdenv}:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no pkgs, but python3


let

defaultKernelDefinition = {
python3 = let
env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
ipykernel
pandas
scikitlearn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these Jupyter dependencies or just nice to have packages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ipykernel is needed, the others are just basic packages used for data science.
I was thinking of getting something inline with the jupyter dockerimages.
Base python, base data-science, data-science + spark

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the default as minimal as possible.

]));
in {
displayName = "Python 3";
argv = [
"${env.interpreter}"
"-m"
"ipykernel_launcher"
"-f"
"{connection_file}"
];
language = "python";
logo32 = "${env.sitePackages}/ipykernel/resources/logo-32x32.png";
logo64 = "${env.sitePackages}/ipykernel/resources/logo-64x64.png";
};
};

in
{
defaultKernelDefinition = defaultKernelDefinition;
create = { kernelDefinitions ? defaultKernelDefinition }:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FRidh I'm not certain this is better.
Now I'm exporting a set instead of exporting a function so I can re-use the default value in all the packages that might use it.

with lib; stdenv.mkDerivation rec {
name = "jupyter-kernels";

src = "/dev/null";

unpackCmd ="mkdir jupyter_kernels";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space


installPhase = ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space

mkdir kernels

${concatStringsSep "\n" (mapAttrsToList (kernelName: kernel:
let
config = builtins.toJSON {
display_name = if (kernel.displayName != "")
then kernel.displayName
else kernelName;
argv = kernel.argv;
language = kernel.language;
};
logo32 =
if (kernel.logo32 != null)
then "ln -s ${kernel.logo32} 'kernels/${kernelName}/logo-32x32.png';"
else "";
logo64 =
if (kernel.logo64 != null)
then "ln -s ${kernel.logo64} 'kernels/${kernelName}/logo-64x64.png';"
else "";
in ''
mkdir 'kernels/${kernelName}';
echo '${config}' > 'kernels/${kernelName}/kernel.json';
${logo32}
${logo64}
'') kernelDefinitions)}

mkdir $out
cp -r kernels $out
'';

meta = {
description = "Wrapper to create jupyter notebook kernel definitions";
homepage = http://jupyter.org/;
# NIXOS license as this is a nixos meta package.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need

maintainers = with maintainers; [ aborsu ];
};
};
}
4 changes: 4 additions & 0 deletions pkgs/top-level/all-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3109,6 +3109,10 @@ with pkgs;

jupp = callPackage ../applications/editors/jupp { };

jupyter = callPackage ../applications/editors/jupyter { };

jupyterKernels = callPackage ../development/misc/jupyterKernels { };

jwhois = callPackage ../tools/networking/jwhois { };

k2pdfopt = callPackage ../applications/misc/k2pdfopt { };
Expand Down