1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.jupyterhub;
8
9 kernels = (pkgs.jupyter-kernel.create {
10 definitions = if cfg.kernels != null
11 then cfg.kernels
12 else pkgs.jupyter-kernel.default;
13 });
14
15 jupyterhubConfig = pkgs.writeText "jupyterhub_config.py" ''
16 c.JupyterHub.bind_url = "http://${cfg.host}:${toString cfg.port}"
17
18 c.JupyterHub.authenticator_class = "${cfg.authentication}"
19 c.JupyterHub.spawner_class = "${cfg.spawner}"
20
21 c.SystemdSpawner.default_url = '/lab'
22 c.SystemdSpawner.cmd = "${cfg.jupyterlabEnv}/bin/jupyterhub-singleuser"
23 c.SystemdSpawner.environment = {
24 'JUPYTER_PATH': '${kernels}'
25 }
26
27 ${cfg.extraConfig}
28 '';
29in {
30 meta.maintainers = with maintainers; [ costrouc ];
31
32 options.services.jupyterhub = {
33 enable = mkEnableOption (lib.mdDoc "Jupyterhub development server");
34
35 authentication = mkOption {
36 type = types.str;
37 default = "jupyterhub.auth.PAMAuthenticator";
38 description = lib.mdDoc ''
39 Jupyterhub authentication to use
40
41 There are many authenticators available including: oauth, pam,
42 ldap, kerberos, etc.
43 '';
44 };
45
46 spawner = mkOption {
47 type = types.str;
48 default = "systemdspawner.SystemdSpawner";
49 description = lib.mdDoc ''
50 Jupyterhub spawner to use
51
52 There are many spawners available including: local process,
53 systemd, docker, kubernetes, yarn, batch, etc.
54 '';
55 };
56
57 extraConfig = mkOption {
58 type = types.lines;
59 default = "";
60 description = lib.mdDoc ''
61 Extra contents appended to the jupyterhub configuration
62
63 Jupyterhub configuration is a normal python file using
64 Traitlets. https://jupyterhub.readthedocs.io/en/stable/getting-started/config-basics.html. The
65 base configuration of this module was designed to have sane
66 defaults for configuration but you can override anything since
67 this is a python file.
68 '';
69 example = ''
70 c.SystemdSpawner.mem_limit = '8G'
71 c.SystemdSpawner.cpu_limit = 2.0
72 '';
73 };
74
75 jupyterhubEnv = mkOption {
76 type = types.package;
77 default = pkgs.python3.withPackages (p: with p; [
78 jupyterhub
79 jupyterhub-systemdspawner
80 ]);
81 defaultText = literalExpression ''
82 pkgs.python3.withPackages (p: with p; [
83 jupyterhub
84 jupyterhub-systemdspawner
85 ])
86 '';
87 description = lib.mdDoc ''
88 Python environment to run jupyterhub
89
90 Customizing will affect the packages available in the hub and
91 proxy. This will allow packages to be available for the
92 extraConfig that you may need. This will not normally need to
93 be changed.
94 '';
95 };
96
97 jupyterlabEnv = mkOption {
98 type = types.package;
99 default = pkgs.python3.withPackages (p: with p; [
100 jupyterhub
101 jupyterlab
102 ]);
103 defaultText = literalExpression ''
104 pkgs.python3.withPackages (p: with p; [
105 jupyterhub
106 jupyterlab
107 ])
108 '';
109 description = lib.mdDoc ''
110 Python environment to run jupyterlab
111
112 Customizing will affect the packages available in the
113 jupyterlab server and the default kernel provided. This is the
114 way to customize the jupyterlab extensions and jupyter
115 notebook extensions. This will not normally need to
116 be changed.
117 '';
118 };
119
120 kernels = mkOption {
121 type = types.nullOr (types.attrsOf(types.submodule (import ../jupyter/kernel-options.nix {
122 inherit lib pkgs;
123 })));
124
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}/''${env.sitePackages}/ipykernel/resources/logo-32x32.png";
145 logo64 = "''${env}/''${env.sitePackages}/ipykernel/resources/logo-64x64.png";
146 };
147 }
148 '';
149 description = lib.mdDoc ''
150 Declarative kernel config
151
152 Kernels can be declared in any language that supports and has
153 the required dependencies to communicate with a jupyter server.
154 In python's case, it means that ipykernel package must always be
155 included in the list of packages of the targeted environment.
156 '';
157 };
158
159 port = mkOption {
160 type = types.port;
161 default = 8000;
162 description = lib.mdDoc ''
163 Port number Jupyterhub will be listening on
164 '';
165 };
166
167 host = mkOption {
168 type = types.str;
169 default = "0.0.0.0";
170 description = lib.mdDoc ''
171 Bind IP JupyterHub will be listening on
172 '';
173 };
174
175 stateDirectory = mkOption {
176 type = types.str;
177 default = "jupyterhub";
178 description = lib.mdDoc ''
179 Directory for jupyterhub state (token + database)
180 '';
181 };
182 };
183
184 config = mkMerge [
185 (mkIf cfg.enable {
186 systemd.services.jupyterhub = {
187 description = "Jupyterhub development server";
188
189 after = [ "network.target" ];
190 wantedBy = [ "multi-user.target" ];
191
192 serviceConfig = {
193 Restart = "always";
194 ExecStart = "${cfg.jupyterhubEnv}/bin/jupyterhub --config ${jupyterhubConfig}";
195 User = "root";
196 StateDirectory = cfg.stateDirectory;
197 WorkingDirectory = "/var/lib/${cfg.stateDirectory}";
198 };
199 };
200 })
201 ];
202}