nixos/preload: init
[NixPkgs.git] / nixos / modules / services / development / jupyter / default.nix
blob9f791084446851ca238a68deabfd01f795af3985
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
7   cfg = config.services.jupyter;
9   package = cfg.package;
11   kernels = (pkgs.jupyter-kernel.create  {
12     definitions = if cfg.kernels != null
13       then cfg.kernels
14       else  pkgs.jupyter-kernel.default;
15   });
17   notebookConfig = pkgs.writeText "jupyter_config.py" ''
18     ${cfg.notebookConfig}
20     c.NotebookApp.password = ${cfg.password}
21   '';
23 in {
24   meta.maintainers = with maintainers; [ aborsu ];
26   options.services.jupyter = {
27     enable = mkEnableOption (lib.mdDoc "Jupyter development server");
29     ip = mkOption {
30       type = types.str;
31       default = "localhost";
32       description = lib.mdDoc ''
33         IP address Jupyter will be listening on.
34       '';
35     };
37     package = mkOption {
38       type = types.package;
39       # NOTE: We don't use top-level jupyter because we don't
40       # want to pass in JUPYTER_PATH but use .environment instead,
41       # saving a rebuild.
42       default = pkgs.python3.pkgs.notebook;
43       defaultText = literalExpression "pkgs.python3.pkgs.notebook";
44       description = lib.mdDoc ''
45         Jupyter package to use.
46       '';
47     };
49     command = mkOption {
50       type = types.str;
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.
56        '';
57     };
59     port = mkOption {
60       type = types.port;
61       default = 8888;
62       description = lib.mdDoc ''
63         Port number Jupyter will be listening on.
64       '';
65     };
67     notebookDir = mkOption {
68       type = types.str;
69       default = "~/";
70       description = lib.mdDoc ''
71         Root directory for notebooks.
72       '';
73     };
75     user = mkOption {
76       type = types.str;
77       default = "jupyter";
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.
82       '';
83       example = "aborsu";
84     };
86     group = mkOption {
87       type = types.str;
88       default = "jupyter";
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.
92       '';
93       example = "users";
94     };
96     password = mkOption {
97       type = types.str;
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.
108       '';
109       example = "'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'";
110     };
112     notebookConfig = mkOption {
113       type = types.lines;
114       default = "";
115       description = lib.mdDoc ''
116         Raw jupyter config.
117       '';
118     };
120     kernels = mkOption {
121       type = types.nullOr (types.attrsOf(types.submodule (import ./kernel-options.nix {
122         inherit lib pkgs;
123       })));
125       default = null;
126       example = literalExpression ''
127         {
128           python3 = let
129             env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
130                     ipykernel
131                     pandas
132                     scikit-learn
133                   ]));
134           in {
135             displayName = "Python 3 for machine learning";
136             argv = [
137               "''${env.interpreter}"
138               "-m"
139               "ipykernel_launcher"
140               "-f"
141               "{connection_file}"
142             ];
143             language = "python";
144             logo32 = "''${env.sitePackages}/ipykernel/resources/logo-32x32.png";
145             logo64 = "''${env.sitePackages}/ipykernel/resources/logo-64x64.png";
146             extraPaths = {
147               "cool.txt" = pkgs.writeText "cool" "cool content";
148             };
149           };
150         }
151       '';
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.
159       '';
160     };
161   };
163   config = mkMerge [
164     (mkIf cfg.enable  {
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
174         environment = {
175           JUPYTER_PATH = toString kernels;
176         };
178         serviceConfig = {
179           Restart = "always";
180           ExecStart = ''${package}/bin/${cfg.command} \
181             --no-browser \
182             --ip=${cfg.ip} \
183             --port=${toString cfg.port} --port-retries 0 \
184             --notebook-dir=${cfg.notebookDir} \
185             --NotebookApp.config_file=${notebookConfig}
186           '';
187           User = cfg.user;
188           Group = cfg.group;
189           WorkingDirectory = "~";
190         };
191       };
192     })
193     (mkIf (cfg.enable && (cfg.group == "jupyter")) {
194       users.groups.jupyter = {};
195     })
196     (mkIf (cfg.enable && (cfg.user == "jupyter")) {
197       users.extraUsers.jupyter = {
198         extraGroups = [ cfg.group ];
199         home = "/var/lib/jupyter";
200         createHome = true;
201         isSystemUser = true;
202         useDefaultShell = true; # needed so that the user can start a terminal.
203       };
204     })
205   ];