at 25.11-pre 6.6 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 options, 6 ... 7}: 8let 9 10 cfg = config.services.jupyter; 11 12 package = pkgs.python3.withPackages ( 13 ps: 14 [ 15 cfg.package 16 ] 17 ++ cfg.extraPackages 18 ); 19 20 kernels = ( 21 pkgs.jupyter-kernel.create { 22 definitions = if cfg.kernels != null then cfg.kernels else pkgs.jupyter-kernel.default; 23 } 24 ); 25 26 notebookConfig = pkgs.writeText "jupyter_server_config.py" '' 27 ${cfg.notebookConfig} 28 c.ServerApp.password = "${cfg.password}" 29 ''; 30 31in 32{ 33 meta.maintainers = with lib.maintainers; [ 34 aborsu 35 b-m-f 36 ]; 37 38 options.services.jupyter = { 39 enable = lib.mkEnableOption "Jupyter development server"; 40 41 ip = lib.mkOption { 42 type = lib.types.str; 43 default = "localhost"; 44 description = '' 45 IP address Jupyter will be listening on. 46 ''; 47 }; 48 49 package = lib.mkPackageOption pkgs [ 50 "python3" 51 "pkgs" 52 "jupyter" 53 ] { }; 54 55 extraPackages = lib.mkOption { 56 type = lib.types.listOf lib.types.package; 57 default = [ ]; 58 example = lib.literalExpression '' 59 [ 60 pkgs.python3.pkgs.nbconvert 61 pkgs.python3.pkgs.playwright 62 ] 63 ''; 64 description = "Extra packages to be available in the jupyter runtime environment"; 65 }; 66 extraEnvironmentVariables = lib.mkOption { 67 description = "Extra environment variables to be set in the runtime context of jupyter notebook"; 68 default = { }; 69 example = lib.literalExpression '' 70 { 71 PLAYWRIGHT_BROWSERS_PATH = "''${pkgs.playwright-driver.browsers}"; 72 PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = "true"; 73 } 74 ''; 75 inherit (options.environment.variables) type apply; 76 }; 77 78 command = lib.mkOption { 79 type = lib.types.str; 80 default = "jupyter notebook"; 81 example = "jupyter lab"; 82 description = '' 83 Which command the service runs. Note that not all jupyter packages 84 have all commands, e.g. `jupyter lab` isn't present in the `notebook` package. 85 ''; 86 }; 87 88 port = lib.mkOption { 89 type = lib.types.port; 90 default = 8888; 91 description = '' 92 Port number Jupyter will be listening on. 93 ''; 94 }; 95 96 notebookDir = lib.mkOption { 97 type = lib.types.str; 98 default = "~/"; 99 description = '' 100 Root directory for notebooks. 101 ''; 102 }; 103 104 user = lib.mkOption { 105 type = lib.types.str; 106 default = "jupyter"; 107 description = '' 108 Name of the user used to run the jupyter service. 109 For security reason, jupyter should really not be run as root. 110 If not set (jupyter), the service will create a jupyter user with appropriate settings. 111 ''; 112 example = "aborsu"; 113 }; 114 115 group = lib.mkOption { 116 type = lib.types.str; 117 default = "jupyter"; 118 description = '' 119 Name of the group used to run the jupyter service. 120 Use this if you want to create a group of users that are able to view the notebook directory's content. 121 ''; 122 example = "users"; 123 }; 124 125 password = lib.mkOption { 126 type = lib.types.str; 127 description = '' 128 Password to use with notebook. 129 Can be generated following: https://jupyter-server.readthedocs.io/en/stable/operators/public-server.html#preparing-a-hashed-password 130 ''; 131 example = "argon2:$argon2id$v=19$m=10240,t=10,p=8$48hF+vTUuy1LB83/GzNhUg$J1nx4jPWD7PwOJHs5OtDW8pjYK2s0c1R3rYGbSIKB54"; 132 }; 133 134 notebookConfig = lib.mkOption { 135 type = lib.types.lines; 136 default = ""; 137 description = '' 138 Raw jupyter config. 139 Please use the password configuration option to set a password instead of passing it in here. 140 ''; 141 }; 142 143 kernels = lib.mkOption { 144 type = lib.types.nullOr ( 145 lib.types.attrsOf ( 146 lib.types.submodule ( 147 import ./kernel-options.nix { 148 inherit lib pkgs; 149 } 150 ) 151 ) 152 ); 153 154 default = null; 155 example = lib.literalExpression '' 156 { 157 python3 = let 158 env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [ 159 ipykernel 160 pandas 161 scikit-learn 162 ])); 163 in { 164 displayName = "Python 3 for machine learning"; 165 argv = [ 166 "''${env.interpreter}" 167 "-m" 168 "ipykernel_launcher" 169 "-f" 170 "{connection_file}" 171 ]; 172 language = "python"; 173 logo32 = "''${env.sitePackages}/ipykernel/resources/logo-32x32.png"; 174 logo64 = "''${env.sitePackages}/ipykernel/resources/logo-64x64.png"; 175 extraPaths = { 176 "cool.txt" = pkgs.writeText "cool" "cool content"; 177 }; 178 }; 179 } 180 ''; 181 description = '' 182 Declarative kernel config. 183 184 Kernels can be declared in any language that supports and has the required 185 dependencies to communicate with a jupyter server. 186 In python's case, it means that ipykernel package must always be included in 187 the list of packages of the targeted environment. 188 ''; 189 }; 190 }; 191 192 config = lib.mkMerge [ 193 (lib.mkIf cfg.enable { 194 systemd.services.jupyter = { 195 description = "Jupyter development server"; 196 197 after = [ "network.target" ]; 198 wantedBy = [ "multi-user.target" ]; 199 200 # TODO: Patch notebook so we can explicitly pass in a shell 201 path = [ pkgs.bash ]; # needed for sh in cell magic to work 202 203 environment = { 204 JUPYTER_PATH = toString kernels; 205 } // cfg.extraEnvironmentVariables; 206 207 serviceConfig = { 208 Restart = "always"; 209 ExecStart = '' 210 ${package}/bin/${cfg.command} \ 211 --no-browser \ 212 --ip=${cfg.ip} \ 213 --port=${toString cfg.port} --port-retries 0 \ 214 --notebook-dir=${cfg.notebookDir} \ 215 --JupyterApp.config_file=${notebookConfig} 216 217 ''; 218 User = cfg.user; 219 Group = cfg.group; 220 WorkingDirectory = "~"; 221 }; 222 }; 223 }) 224 (lib.mkIf (cfg.enable && (cfg.group == "jupyter")) { 225 users.groups.jupyter = { }; 226 }) 227 (lib.mkIf (cfg.enable && (cfg.user == "jupyter")) { 228 users.extraUsers.jupyter = { 229 inherit (cfg) group; 230 home = "/var/lib/jupyter"; 231 createHome = true; 232 isSystemUser = true; 233 useDefaultShell = true; # needed so that the user can start a terminal. 234 }; 235 }) 236 ]; 237}