Moved last set of Tutorials

This commit is contained in:
alwasega
2023-02-03 15:57:35 +03:00
parent 63f164ca53
commit f857b17022
13 changed files with 44 additions and 37 deletions

View File

@@ -0,0 +1,133 @@
(authenticators)=
# Authentication and User Basics
The default Authenticator uses [PAM][] (Pluggable Authentication Module) to authenticate system users with
their usernames and passwords. With the default Authenticator, any user
with an account and password on the system will be allowed to login.
## Create a set of allowed users (`allowed_users`)
You can restrict which users are allowed to login with a set,
`Authenticator.allowed_users`:
```python
c.Authenticator.allowed_users = {'mal', 'zoe', 'inara', 'kaylee'}
```
Users in the `allowed_users` set are added to the Hub database when the Hub is
started.
```{warning}
If this configuration value is not set, then **all authenticated users will be allowed into your hub**.
```
## Configure admins (`admin_users`)
```{note}
As of JupyterHub 2.0, the full permissions of `admin_users`
should not be required.
Instead, you can assign [roles](define-role-target) to users or groups
with only the scopes they require.
```
Admin users of JupyterHub, `admin_users`, can add and remove users from
the user `allowed_users` set. `admin_users` can take actions on other users'
behalf, such as stopping and restarting their servers.
A set of initial admin users, `admin_users` can be configured as follows:
```python
c.Authenticator.admin_users = {'mal', 'zoe'}
```
Users in the admin set are automatically added to the user `allowed_users` set,
if they are not already present.
Each Authenticator may have different ways of determining whether a user is an
administrator. By default, JupyterHub uses the PAMAuthenticator which provides the
`admin_groups` option and can set administrator status based on a user
group. For example, we can let any user in the `wheel` group be an admin:
```python
c.PAMAuthenticator.admin_groups = {'wheel'}
```
## Give admin access to other users' notebook servers (`admin_access`)
Since the default `JupyterHub.admin_access` setting is `False`, the admins
do not have permission to log in to the single user notebook servers
owned by _other users_. If `JupyterHub.admin_access` is set to `True`,
then admins have permission to log in _as other users_ on their
respective machines for debugging. **As a courtesy, you should make
sure your users know if admin_access is enabled.**
## Add or remove users from the Hub
Users can be added to and removed from the Hub via the admin
panel or the REST API. When a user is **added**, the user will be
automatically added to the `allowed_users` set and database. Restarting the Hub
will not require manually updating the `allowed_users` set in your config file,
as the users will be loaded from the database.
After starting the Hub once, it is not sufficient to **remove** a user
from the allowed users set in your config file. You must also remove the user
from the Hub's database, either by deleting the user from JupyterHub's
admin page, or you can clear the `jupyterhub.sqlite` database and start
fresh.
## Use LocalAuthenticator to create system users
The `LocalAuthenticator` is a special kind of Authenticator that has
the ability to manage users on the local system. When you try to add a
new user to the Hub, a `LocalAuthenticator` will check if the user
already exists. If you set the configuration value, `create_system_users`,
to `True` in the configuration file, the `LocalAuthenticator` has
the ability to add users to the system. The setting in the config
file is:
```python
c.LocalAuthenticator.create_system_users = True
```
Adding a user to the Hub that doesn't already exist on the system will
result in the Hub creating that user via the system `adduser` command
line tool. This option is typically used on hosted deployments of
JupyterHub to avoid the need to manually create all your users before
launching the service. This approach is not recommended when running
JupyterHub in situations where JupyterHub users map directly onto the
system's UNIX users.
## Use OAuthenticator to support OAuth with popular service providers
JupyterHub's [OAuthenticator][] currently supports the following
popular services:
- [Auth0](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.auth0.html)
- [Azure AD](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.azuread.html)
- [Bitbucket](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.bitbucket.html)
- [CILogon](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.cilogon.html)
- [GitHub](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.github.html)
- [GitLab](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.gitlab.html)
- [Globus](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.globus.html)
- [Google](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.google.html)
- [MediaWiki](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.mediawiki.html)
- [Okpy](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.okpy.html)
- [OpenShift](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.openshift.html)
A [generic implementation](https://oauthenticator.readthedocs.io/en/latest/reference/api/gen/oauthenticator.generic.html), which you can use for OAuth authentication
with any provider, is also available.
## Use DummyAuthenticator for testing
The `DummyAuthenticator` is a simple Authenticator that
allows for any username or password unless a global password has been set. If
set, it will allow for any username as long as the correct password is provided.
To set a global password, add this to the config file:
```python
c.DummyAuthenticator.password = "some_password"
```
[pam]: https://en.wikipedia.org/wiki/Pluggable_authentication_module
[oauthenticator]: https://github.com/jupyterhub/oauthenticator

View File

@@ -0,0 +1,102 @@
# Configuration Basics
This section contains basic information about configuring settings for a JupyterHub
deployment. The [Technical Reference](reference-index)
documentation provides additional details.
This section will help you learn how to:
- generate a default configuration file, `jupyterhub_config.py`
- start with a specific configuration file
- configure JupyterHub using command line options
- find information and examples for some common deployments
(generate-config-file)=
## Generate a default config file
On startup, JupyterHub will look by default for a configuration file,
`jupyterhub_config.py`, in the current working directory.
To generate a default config file, `jupyterhub_config.py`:
```bash
jupyterhub --generate-config
```
This default `jupyterhub_config.py` file contains comments and guidance for all
configuration variables and their default values. We recommend storing
configuration files in the standard UNIX filesystem location, i.e.
`/etc/jupyterhub`.
## Start with a specific config file
You can load a specific config file and start JupyterHub using:
```bash
jupyterhub -f /path/to/jupyterhub_config.py
```
If you have stored your configuration file in the recommended UNIX filesystem
location, `/etc/jupyterhub`, the following command will start JupyterHub using
the configuration file:
```bash
jupyterhub -f /etc/jupyterhub/jupyterhub_config.py
```
The IPython documentation provides additional information on the
[config system](https://ipython.readthedocs.io/en/stable/development/config.html)
that Jupyter uses.
## Configure using command line options
To display all command line options that are available for configuration run the following command:
```bash
jupyterhub --help-all
```
Configuration using the command line options is done when launching JupyterHub.
For example, to start JupyterHub on `10.0.1.2:443` with https, you
would enter:
```bash
jupyterhub --ip 10.0.1.2 --port 443 --ssl-key my_ssl.key --ssl-cert my_ssl.cert
```
All configurable options may technically be set on the command line,
though some are inconvenient to type. To set a particular configuration
parameter, `c.Class.trait`, you would use the command line option,
`--Class.trait`, when starting JupyterHub. For example, to configure the
`c.Spawner.notebook_dir` trait from the command line, use the
`--Spawner.notebook_dir` option:
```bash
jupyterhub --Spawner.notebook_dir='~/assignments'
```
## Configure for various deployment environments
The default authentication and process spawning mechanisms can be replaced, and
specific [authenticators](authenticators-users-basics) and
[spawners](spawners-basics) can be set in the configuration file.
This enables JupyterHub to be used with a variety of authentication methods or
process control and deployment environments. [Some examples](config-examples),
meant as illustrations, are:
- Using GitHub OAuth instead of PAM with [OAuthenticator](https://github.com/jupyterhub/oauthenticator)
- Spawning single-user servers with Docker, using the [DockerSpawner](https://github.com/jupyterhub/dockerspawner)
## Run the proxy separately
This is _not_ strictly necessary, but useful in many cases. If you
use a custom proxy (e.g. Traefik), this is also not needed.
Connections to user servers go through the proxy, and _not_ the hub
itself. If the proxy stays running when the hub restarts (for
maintenance, re-configuration, etc.), then user connections are not
interrupted. For simplicity, by default the hub starts the proxy
automatically, so if the hub restarts, the proxy restarts, and user
connections are interrupted. It is easy to run the proxy separately,
for information see [the separate proxy page](separate-proxy).

View File

@@ -0,0 +1,101 @@
# Networking basics
This section will help you with basic proxy and network configuration to:
- set the proxy's IP address and port
- set the proxy's REST API URL
- configure the Hub if the Proxy or Spawners are remote or isolated
- set the `hub_connect_ip` which services will use to communicate with the hub
## Set the Proxy's IP address and port
The Proxy's main IP address setting determines where JupyterHub is available to users.
By default, JupyterHub is configured to be available on all network interfaces
(`''`) on port 8000. _Note_: Use of `'*'` is discouraged for IP configuration;
instead, use of `'0.0.0.0'` is preferred.
Changing the Proxy's main IP address and port can be done with the following
JupyterHub **command line options**:
```bash
jupyterhub --ip=192.168.1.2 --port=443
```
Or by placing the following lines in a **configuration file**,
`jupyterhub_config.py`:
```python
c.JupyterHub.ip = '192.168.1.2'
c.JupyterHub.port = 443
```
Port 443 is used in the examples since 443 is the default port for SSL/HTTPS.
Configuring only the main IP and port of JupyterHub should be sufficient for
most deployments of JupyterHub. However, more customized scenarios may need
additional networking details to be configured.
Note that `c.JupyterHub.ip` and `c.JupyterHub.port` are single values,
not tuples or lists JupyterHub listens to only a single IP address and
port.
## Set the Proxy's REST API communication URL (optional)
By default, the proxy's REST API listens on port 8081 of `localhost` only.
The Hub service talks to the proxy via a REST API on a secondary port.
The REST API URL (hostname and port) can be configured separately and override the default settings.
### Set api_url
The URL to access the API, `c.configurableHTTPProxy.api_url`, is configurable.
An example entry to set the proxy's API URL in `jupyterhub_config.py` is:
```python
c.ConfigurableHTTPProxy.api_url = 'http://10.0.1.4:5432'
```
### proxy_api_ip and proxy_api_port (Deprecated in 0.8)
If running the Proxy separate from the Hub, configure the REST API communication
IP address and port by adding this to the `jupyterhub_config.py` file:
```python
# ideally a private network address
c.JupyterHub.proxy_api_ip = '10.0.1.4'
c.JupyterHub.proxy_api_port = 5432
```
We recommend using the proxy's `api_url` setting instead of the deprecated
settings, `proxy_api_ip` and `proxy_api_port`.
## Configure the Hub if the Proxy or Spawners are remote or isolated
The Hub service listens only on `localhost` (port 8081) by default.
The Hub needs to be accessible from both the proxy and all Spawners.
When spawning local servers, an IP address setting of `localhost` is fine.
If _either_ the Proxy _or_ (more likely) the Spawners will be remote or
isolated in containers, the Hub must listen on an IP that is accessible.
```python
c.JupyterHub.hub_ip = '10.0.1.4'
c.JupyterHub.hub_port = 54321
```
**Added in 0.8:** The `c.JupyterHub.hub_connect_ip` setting is the IP address or
hostname that other services should use to connect to the Hub. A common
configuration for, e.g. docker, is:
```python
c.JupyterHub.hub_ip = '0.0.0.0' # listen on all interfaces
c.JupyterHub.hub_connect_ip = '10.0.1.4' # IP as seen on the docker network. Can also be a hostname.
```
## Adjusting the hub's URL
The hub will most commonly be running on a hostname of its own. If it
is not for example, if the hub is being reverse-proxied and being
exposed at a URL such as `https://proxy.example.org/jupyter/` then
you will need to tell JupyterHub the base URL of the service. In such
a case, it is both necessary and sufficient to set
`c.JupyterHub.base_url = '/jupyter/'` in the configuration.

View File

@@ -0,0 +1,236 @@
(security-basics)=
# Security settings
:::{important}
You should not run JupyterHub without SSL encryption on a public network.
:::
Security is the most important aspect of configuring Jupyter.
Three (3) configuration settings are the main aspects of security configuration:
1. {ref}`SSL encryption <ssl-encryption>` (to enable HTTPS)
2. {ref}`Cookie secret <cookie-secret>` (a key for encrypting browser cookies)
3. Proxy {ref}`authentication token <authentication-token>` (used for the Hub and
other services to authenticate to the Proxy)
The Hub hashes all secrets (e.g. auth tokens) before storing them in its
database. A loss of control over read-access to the database should have
minimal impact on your deployment. If your database has been compromised, it
is still a good idea to revoke existing tokens.
(ssl-encryption)=
## Enabling SSL encryption
Since JupyterHub includes authentication and allows arbitrary code execution,
you should not run it without SSL (HTTPS).
### Using an SSL certificate
This will require you to obtain an official, trusted SSL certificate or create a
self-signed certificate. Once you have obtained and installed a key and
certificate, you need to specify their locations in the `jupyterhub_config.py`
configuration file as follows:
```python
c.JupyterHub.ssl_key = '/path/to/my.key'
c.JupyterHub.ssl_cert = '/path/to/my.cert'
```
Some cert files also contain the key, in which case only the cert is needed. It
is important that these files be put in a secure location on your server, where
they are not readable by regular users.
If you are using a **chain certificate**, see also chained certificate for SSL
in the JupyterHub [Troubleshooting FAQ](troubleshooting).
### Using letsencrypt
It is also possible to use [letsencrypt](https://letsencrypt.org/) to obtain
a free, trusted SSL certificate. If you run letsencrypt using the default
options, the needed configuration is (replace `mydomain.tld` by your fully
qualified domain name):
```python
c.JupyterHub.ssl_key = '/etc/letsencrypt/live/{mydomain.tld}/privkey.pem'
c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/{mydomain.tld}/fullchain.pem'
```
If the fully qualified domain name (FQDN) is `example.com`, the following
would be the needed configuration:
```python
c.JupyterHub.ssl_key = '/etc/letsencrypt/live/example.com/privkey.pem'
c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/example.com/fullchain.pem'
```
### If SSL termination happens outside of the Hub
In certain cases, for example, if the hub is running behind a reverse proxy, and
[SSL termination is being provided by NGINX](https://www.nginx.com/resources/admin-guide/nginx-ssl-termination/),
it is reasonable to run the hub without SSL.
To achieve this, remove `c.JupyterHub.ssl_key` and `c.JupyterHub.ssl_cert`
from your configuration (setting them to `None` or an empty string does not
have the same effect, and will result in an error).
(authentication-token)=
## Proxy authentication token
The Hub authenticates its requests to the Proxy using a secret token that
the Hub and Proxy agree upon. Note that this applies to the default
`ConfigurableHTTPProxy` implementation. Not all proxy implementations
use an auth token.
The value of this token should be a random string (for example, generated by
`openssl rand -hex 32`). You can store it in the configuration file or an
environment variable.
### Generating and storing token in the configuration file
You can set the value in the configuration file, `jupyterhub_config.py`:
```python
c.ConfigurableHTTPProxy.api_token = 'abc123...' # any random string
```
### Generating and storing as an environment variable
You can pass this value of the proxy authentication token to the Hub and Proxy
using the `CONFIGPROXY_AUTH_TOKEN` environment variable:
```bash
export CONFIGPROXY_AUTH_TOKEN=$(openssl rand -hex 32)
```
This environment variable needs to be visible to the Hub and Proxy.
### Default if token is not set
If you do not set the Proxy authentication token, the Hub will generate a random
key itself. This means that any time you restart the Hub, you **must also
restart the Proxy**. If the proxy is a subprocess of the Hub, this should happen
automatically (this is the default configuration).
(cookie-secret)=
## Cookie secret
The cookie secret is an encryption key, used to encrypt the browser cookies,
which are used for authentication. Three common methods are described for
generating and configuring the cookie secret.
### Generating and storing as a cookie secret file
The cookie secret should be 32 random bytes, encoded as hex, and is typically
stored in a `jupyterhub_cookie_secret` file. Below, is an example command to generate the
`jupyterhub_cookie_secret` file:
```bash
openssl rand -hex 32 > /srv/jupyterhub/jupyterhub_cookie_secret
```
In most deployments of JupyterHub, you should point this to a secure location on
the file system, such as `/srv/jupyterhub/jupyterhub_cookie_secret`.
The location of the `jupyterhub_cookie_secret` file can be specified in the
`jupyterhub_config.py` file as follows:
```python
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'
```
If the cookie secret file doesn't exist when the Hub starts, a new cookie
secret is generated and stored in the file. The file must not be readable by
`group` or `other`, otherwise the server won't start. The recommended permissions
for the cookie secret file are `600` (owner-only rw).
### Generating and storing as an environment variable
If you would like to avoid the need for files, the value can be loaded in the
Hub process from the `JPY_COOKIE_SECRET` environment variable, which is a
hex-encoded string. You can set it this way:
```bash
export JPY_COOKIE_SECRET=$(openssl rand -hex 32)
```
For security reasons, this environment variable should only be visible to the
Hub. If you set it dynamically as above, all users will be logged out each time
the Hub starts.
### Generating and storing as a binary string
You can also set the cookie secret, as a binary string,
in the configuration file (`jupyterhub_config.py`) itself:
```python
c.JupyterHub.cookie_secret = bytes.fromhex('64 CHAR HEX STRING')
```
(cookies)=
## Cookies used by JupyterHub authentication
The following cookies are used by the Hub for handling user authentication.
This section was created based on this [post] from Discourse.
### jupyterhub-hub-login
This is the login token used when visiting Hub-served pages that are
protected by authentication, such as the main home, the spawn form, etc.
If this cookie is set, then the user is logged in.
Resetting the Hub cookie secret effectively revokes this cookie.
This cookie is restricted to the path `/hub/`.
### jupyterhub-user-\<username>
This is the cookie used for authenticating with a single-user server.
It is set by the single-user server, after OAuth with the Hub.
Effectively the same as `jupyterhub-hub-login`, but for the
single-user server instead of the Hub. It contains an OAuth access token,
which is checked with the Hub to authenticate the browser.
Each OAuth access token is associated with a session id (see `jupyterhub-session-id` section
below).
To avoid hitting the Hub on every request, the authentication response is cached.
The cache key is comprised of both the token and session id, to avoid a stale cache.
Resetting the Hub cookie secret effectively revokes this cookie.
This cookie is restricted to the path `/user/<username>`,
to ensure that only the users server receives it.
### jupyterhub-session-id
This is a random string, meaningless in itself, and the only cookie
shared by the Hub and single-user servers.
Its sole purpose is to coordinate the logout of the multiple OAuth cookies.
This cookie is set to `/` so all endpoints can receive it, clear it, etc.
### jupyterhub-user-\<username>-oauth-state
A short-lived cookie, used solely to store and validate OAuth state.
It is only set while OAuth between the single-user server and the Hub
is processing.
If you use your browser development tools, you should see this cookie
for a very brief moment before you are logged in,
with an expiration date shorter than `jupyterhub-hub-login` or
`jupyterhub-user-<username>`.
This cookie should not exist after you have successfully logged in.
This cookie is restricted to the path `/user/<username>`, so that only
the users server receives it.
[post]: https://discourse.jupyter.org/t/how-to-force-re-login-for-users/1998/6

View File

@@ -0,0 +1,149 @@
# External services
When working with JupyterHub, a **Service** is defined as a process
that interacts with the Hub's REST API. A Service may perform a specific
action or task. For example, shutting down individuals' single user
notebook servers that have been idle for some time is a good example of
a task that could be automated by a Service. Let's look at how the
[jupyterhub_idle_culler][] script can be used as a Service.
## Real-world example to cull idle servers
JupyterHub has a REST API that can be used by external services. This
document will:
- explain some basic information about API tokens
- clarify that API tokens can be used to authenticate to
single-user servers as of [version 0.8.0](changelog)
- show how the [jupyterhub_idle_culler][] script can be:
- used in a Hub-managed service
- run as a standalone script
Both examples for `jupyterhub_idle_culler` will communicate tasks to the
Hub via the REST API.
## API Token basics
### Step 1: Generate an API token
To run such an external service, an API token must be created and
provided to the service.
As of [version 0.6.0](changelog), the preferred way of doing
this is to first generate an API token:
```bash
openssl rand -hex 32
```
In [version 0.8.0](changelog), a TOKEN request page for
generating an API token is available from the JupyterHub user interface:
![Request API TOKEN page](/images/token-request.png)
![API TOKEN success page](/images/token-request-success.png)
### Step 2: Pass environment variable with token to the Hub
In the case of `cull_idle_servers`, it is passed as the environment
variable called `JUPYTERHUB_API_TOKEN`.
### Step 3: Use API tokens for services and tasks that require external access
While API tokens are often associated with a specific user, API tokens
can be used by services that require external access for activities
that may not correspond to a specific human, e.g. adding users during
setup for a tutorial or workshop. Add a service and its API token to the
JupyterHub configuration file, `jupyterhub_config.py`:
```python
c.JupyterHub.services = [
{'name': 'adding-users', 'api_token': 'super-secret-token'},
]
```
### Step 4: Restart JupyterHub
Upon restarting JupyterHub, you should see a message like below in the
logs:
```none
Adding API token for <username>
```
## Authenticating to single-user servers using API token
In JupyterHub 0.7, there is no mechanism for token authentication to
single-user servers, and only cookies can be used for authentication.
0.8 supports using JupyterHub API tokens to authenticate to single-user
servers.
## How to configure the idle culler to run as a Hub-Managed Service
### Step 1: Install the idle culler:
```
pip install jupyterhub-idle-culler
```
### Step 2: In `jupyterhub_config.py`, add the following dictionary for the `idle-culler` Service to the `c.JupyterHub.services` list:
```python
c.JupyterHub.services = [
{
'name': 'idle-culler',
'command': [sys.executable, '-m', 'jupyterhub_idle_culler', '--timeout=3600'],
}
]
c.JupyterHub.load_roles = [
{
"name": "list-and-cull", # name the role
"services": [
"idle-culler", # assign the service to this role
],
"scopes": [
# declare what permissions the service should have
"list:users", # list users
"read:users:activity", # read user last-activity
"admin:servers", # start/stop servers
],
}
]
```
where:
- `command` indicates that the Service will be launched as a
subprocess, managed by the Hub.
```{versionchanged} 2.0
Prior to 2.0, the idle-culler required 'admin' permissions.
It now needs the scopes:
- `list:users` to access the user list endpoint
- `read:users:activity` to read activity info
- `admin:servers` to start/stop servers
```
## How to run `cull-idle` manually as a standalone script
Now you can run your script by providing it
the API token and it will authenticate through the REST API to
interact with it.
This will run the idle culler service manually. It can be run as a standalone
script anywhere with access to the Hub, and will periodically check for idle
servers and shut them down via the Hub's REST API. In order to shutdown the
servers, the token given to `cull-idle` must have permission to list users
and admin their servers.
Generate an API token and store it in the `JUPYTERHUB_API_TOKEN` environment
variable. Run `jupyterhub_idle_culler` manually.
```bash
export JUPYTERHUB_API_TOKEN='token'
python -m jupyterhub_idle_culler [--timeout=900] [--url=http://127.0.0.1:8081/hub/api]
```
[jupyterhub_idle_culler]: https://github.com/jupyterhub/jupyterhub-idle-culler

View File

@@ -0,0 +1,35 @@
(spawners)=
# Spawners and single-user notebook servers
A Spawner starts each single-user notebook server. Since the single-user server is an instance of `jupyter notebook`, an entire separate
multi-process application, many aspects of that server can be configured and there are a lot
of ways to express that configuration.
At the JupyterHub level, you can set some values on the Spawner. The simplest of these is
`Spawner.notebook_dir`, which lets you set the root directory for a user's server. This root
notebook directory is the highest-level directory users will be able to access in the notebook
dashboard. In this example, the root notebook directory is set to `~/notebooks`, where `~` is
expanded to the user's home directory.
```python
c.Spawner.notebook_dir = '~/notebooks'
```
You can also specify extra command line arguments to the notebook server with:
```python
c.Spawner.args = ['--debug', '--profile=PHYS131']
```
This could be used to set the user's default page for the single-user server:
```python
c.Spawner.args = ['--NotebookApp.default_url=/notebooks/Welcome.ipynb']
```
Since the single-user server extends the notebook server application,
it still loads configuration from the `jupyter_notebook_config.py` config file.
Each user may have one of these files in `$HOME/.jupyter/`.
Jupyter also supports loading system-wide config files from `/etc/jupyter/`,
which is the place to put configuration that you want to affect all of your users.