1 { config, lib, pkgs, ... }:
7 cfg = config.services.jupyter;
11 kernels = (pkgs.jupyter-kernel.create {
12 definitions = if cfg.kernels != null
14 else pkgs.jupyter-kernel.default;
17 notebookConfig = pkgs.writeText "jupyter_config.py" ''
20 c.NotebookApp.password = ${cfg.password}
24 meta.maintainers = with maintainers; [ aborsu ];
26 options.services.jupyter = {
27 enable = mkEnableOption (lib.mdDoc "Jupyter development server");
31 default = "localhost";
32 description = lib.mdDoc ''
33 IP address Jupyter will be listening on.
39 # NOTE: We don't use top-level jupyter because we don't
40 # want to pass in JUPYTER_PATH but use .environment instead,
42 default = pkgs.python3.pkgs.notebook;
43 defaultText = literalExpression "pkgs.python3.pkgs.notebook";
44 description = lib.mdDoc ''
45 Jupyter package to use.
51 default = "jupyter-notebook";
52 example = "jupyter-lab";
53 description = lib.mdDoc ''
54 Which command the service runs. Note that not all jupyter packages
55 have all commands, e.g. jupyter-lab isn't present in the default package.
62 description = lib.mdDoc ''
63 Port number Jupyter will be listening on.
67 notebookDir = mkOption {
70 description = lib.mdDoc ''
71 Root directory for notebooks.
78 description = lib.mdDoc ''
79 Name of the user used to run the jupyter service.
80 For security reason, jupyter should really not be run as root.
81 If not set (jupyter), the service will create a jupyter user with appropriate settings.
89 description = lib.mdDoc ''
90 Name of the group used to run the jupyter service.
91 Use this if you want to create a group of users that are able to view the notebook directory's content.
98 description = lib.mdDoc ''
99 Password to use with notebook.
100 Can be generated using:
101 In [1]: from notebook.auth import passwd
102 In [2]: passwd('test')
103 Out[2]: 'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'
104 NOTE: you need to keep the single quote inside the nix string.
105 Or you can use a python oneliner:
106 "open('/path/secret_file', 'r', encoding='utf8').read().strip()"
107 It will be interpreted at the end of the notebookConfig.
109 example = "'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'";
112 notebookConfig = mkOption {
115 description = lib.mdDoc ''
121 type = types.nullOr (types.attrsOf(types.submodule (import ./kernel-options.nix {
126 example = literalExpression ''
129 env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
135 displayName = "Python 3 for machine learning";
137 "''${env.interpreter}"
144 logo32 = "''${env.sitePackages}/ipykernel/resources/logo-32x32.png";
145 logo64 = "''${env.sitePackages}/ipykernel/resources/logo-64x64.png";
147 "cool.txt" = pkgs.writeText "cool" "cool content";
152 description = lib.mdDoc ''
153 Declarative kernel config.
155 Kernels can be declared in any language that supports and has the required
156 dependencies to communicate with a jupyter server.
157 In python's case, it means that ipykernel package must always be included in
158 the list of packages of the targeted environment.
165 systemd.services.jupyter = {
166 description = "Jupyter development server";
168 after = [ "network.target" ];
169 wantedBy = [ "multi-user.target" ];
171 # TODO: Patch notebook so we can explicitly pass in a shell
172 path = [ pkgs.bash ]; # needed for sh in cell magic to work
175 JUPYTER_PATH = toString kernels;
180 ExecStart = ''${package}/bin/${cfg.command} \
183 --port=${toString cfg.port} --port-retries 0 \
184 --notebook-dir=${cfg.notebookDir} \
185 --NotebookApp.config_file=${notebookConfig}
189 WorkingDirectory = "~";
193 (mkIf (cfg.enable && (cfg.group == "jupyter")) {
194 users.groups.jupyter = {};
196 (mkIf (cfg.enable && (cfg.user == "jupyter")) {
197 users.extraUsers.jupyter = {
198 extraGroups = [ cfg.group ];
199 home = "/var/lib/jupyter";
202 useDefaultShell = true; # needed so that the user can start a terminal.