mirror of
https://github.com/jupyterhub/the-littlest-jupyterhub.git
synced 2025-12-18 21:54:05 +08:00
Merge pull request #67 from minrk/traefik
Add HTTPS support with traefik
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
include tljh/systemd-units/*
|
include tljh/systemd-units/*
|
||||||
|
include tljh/*.tpl
|
||||||
|
|||||||
79
docs/howto/https.rst
Normal file
79
docs/howto/https.rst
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
.. _howto/https:
|
||||||
|
|
||||||
|
============
|
||||||
|
Enable HTTPS
|
||||||
|
============
|
||||||
|
|
||||||
|
Every JupyterHub deployment should enable HTTPS!
|
||||||
|
HTTPS encrypts traffic so that usernames and passwords and other potentially sensitive bits of information are communicated securely.
|
||||||
|
The Littlest JupyterHub supports automatically configuring HTTPS via `Let's Encrypt <https://letsencrypt.org>`_,
|
||||||
|
or setting it up :ref:`manually <manual_https>` with your own TLS key and certificate.
|
||||||
|
If you don't know how to do that,
|
||||||
|
then :ref:`Let's Encrypt <letsencrypt>` is probably the right path for you.
|
||||||
|
|
||||||
|
|
||||||
|
.. _letsencrypt:
|
||||||
|
|
||||||
|
Automatic HTTPS with Let's Encrypt
|
||||||
|
==================================
|
||||||
|
|
||||||
|
To enable HTTPS via letsencrypt::
|
||||||
|
|
||||||
|
sudo -E tljh-config set https.enabled true
|
||||||
|
sudo -E tljh-config set https.letsencrypt.email you@example.com
|
||||||
|
sudo -E tljh-config add-item https.letsencrypt.domains yourhub.yourdomain.edu
|
||||||
|
|
||||||
|
where ``you@example.com`` is your email address and ``yourhub.yourdomain.edu`` is the domain where your hub will be running.
|
||||||
|
|
||||||
|
Once you have loaded this, your config should look like::
|
||||||
|
|
||||||
|
sudo -E tljh-config show
|
||||||
|
|
||||||
|
|
||||||
|
.. sourcecode:: yaml
|
||||||
|
|
||||||
|
https:
|
||||||
|
enabled: true
|
||||||
|
letsencrypt:
|
||||||
|
email: you@example.com
|
||||||
|
domains:
|
||||||
|
- yourhub.yourdomain.edu
|
||||||
|
|
||||||
|
Finally, you can reload the proxy to load the new configuration::
|
||||||
|
|
||||||
|
sudo -E tljh-config reload proxy
|
||||||
|
|
||||||
|
At this point, the proxy should negotiate with Let's Encrypt to set up a trusted HTTPS certificate for you.
|
||||||
|
It may take a moment for the proxy to negotiate with Let's Encrypt to get your certificates, after which you can access your Hub securely at https://yourhub.yourdomain.edu.
|
||||||
|
|
||||||
|
.. _manual_https:
|
||||||
|
|
||||||
|
Manual HTTPS with existing key and certificate
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
You may already have an SSL key and certificate.
|
||||||
|
If so, you can tell your deployment to use these files::
|
||||||
|
|
||||||
|
sudo -E tljh-config set https.enabled true
|
||||||
|
sudo -E tljh-config set https.tls.key /etc/mycerts/mydomain.key
|
||||||
|
sudo -E tljh-config set https.tls.cert /etc/mycerts/mydomain.cert
|
||||||
|
|
||||||
|
|
||||||
|
Once you have loaded this, your config should look like::
|
||||||
|
|
||||||
|
sudo -E tljh-config show
|
||||||
|
|
||||||
|
|
||||||
|
.. sourcecode:: yaml
|
||||||
|
|
||||||
|
https:
|
||||||
|
enabled: true
|
||||||
|
tls:
|
||||||
|
key: /etc/mycerts/mydomain.key
|
||||||
|
cert: /etc/mycerts/mydomain.cert
|
||||||
|
|
||||||
|
Finally, you can reload the proxy to load the new configuration::
|
||||||
|
|
||||||
|
sudo -E tljh-config reload proxy
|
||||||
|
|
||||||
|
and now access your Hub securely at https://yourhub.yourdomain.edu.
|
||||||
@@ -35,6 +35,9 @@ Ubuntu 18.04. We have a bunch of tutorials to get you started.
|
|||||||
You should use this if your cloud provider does not already have a direct tutorial,
|
You should use this if your cloud provider does not already have a direct tutorial,
|
||||||
or if you have experience setting up servers.
|
or if you have experience setting up servers.
|
||||||
|
|
||||||
|
Once you are ready to run your server for real,
|
||||||
|
it's a good idea to proceed directly to :doc:`howto/https`.
|
||||||
|
|
||||||
Tutorials
|
Tutorials
|
||||||
=========
|
=========
|
||||||
|
|
||||||
@@ -53,6 +56,7 @@ How-To guides answer the question 'How do I...?' for a lot of topics.
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:titlesonly:
|
:titlesonly:
|
||||||
|
|
||||||
|
howto/https
|
||||||
howto/user-environment
|
howto/user-environment
|
||||||
howto/admin-users
|
howto/admin-users
|
||||||
howto/notebook-interfaces
|
howto/notebook-interfaces
|
||||||
|
|||||||
9
setup.py
9
setup.py
@@ -12,11 +12,12 @@ setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'pyyaml==3.*',
|
'pyyaml==3.*',
|
||||||
'ruamel.yaml==0.15.*'
|
'ruamel.yaml==0.15.*',
|
||||||
|
'jinja2',
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'tljh-config = tljh.config:main'
|
'tljh-config = tljh.config:main',
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ def reload_component(component):
|
|||||||
print('Hub reload with new configuration complete')
|
print('Hub reload with new configuration complete')
|
||||||
elif component == 'proxy':
|
elif component == 'proxy':
|
||||||
systemd.restart_service('configurable-http-proxy')
|
systemd.restart_service('configurable-http-proxy')
|
||||||
|
systemd.restart_service('traefik')
|
||||||
print('Proxy reload with new configuration complete')
|
print('Proxy reload with new configuration complete')
|
||||||
|
|
||||||
|
|
||||||
@@ -238,5 +239,6 @@ def main():
|
|||||||
elif args.action == 'reload':
|
elif args.action == 'reload':
|
||||||
reload_component(args.component)
|
reload_component(args.component)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ be called many times per lifetime of a jupyterhub.
|
|||||||
Traitlets that modify the startup of JupyterHub should not be here.
|
Traitlets that modify the startup of JupyterHub should not be here.
|
||||||
FIXME: A strong feeling that JSON Schema should be involved somehow.
|
FIXME: A strong feeling that JSON Schema should be involved somehow.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
INSTALL_PREFIX = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh')
|
||||||
|
CONFIG_FILE = os.path.join(INSTALL_PREFIX, 'config.yaml')
|
||||||
|
|
||||||
# Default configuration for tljh
|
# Default configuration for tljh
|
||||||
# User provided config is merged into this
|
# User provided config is merged into this
|
||||||
default = {
|
default = {
|
||||||
@@ -19,19 +26,46 @@ default = {
|
|||||||
'users': {
|
'users': {
|
||||||
'allowed': [],
|
'allowed': [],
|
||||||
'banned': [],
|
'banned': [],
|
||||||
'admin': []
|
'admin': [],
|
||||||
},
|
},
|
||||||
'limits': {
|
'limits': {
|
||||||
'memory': '1G',
|
'memory': '1G',
|
||||||
'cpu': None
|
'cpu': None,
|
||||||
|
},
|
||||||
|
'http': {
|
||||||
|
'port': 80,
|
||||||
|
},
|
||||||
|
'https': {
|
||||||
|
'enabled': False,
|
||||||
|
'port': 443,
|
||||||
|
'tls': {
|
||||||
|
'cert': '',
|
||||||
|
'key': '',
|
||||||
|
},
|
||||||
|
'letsencrypt': {
|
||||||
|
'email': '',
|
||||||
|
'domains': [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'user_environment': {
|
'user_environment': {
|
||||||
'default_app': 'classic'
|
'default_app': 'classic',
|
||||||
}
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(config_file=CONFIG_FILE):
|
||||||
|
"""Load the current config as a dictionary
|
||||||
|
|
||||||
|
merges overrides from config.yaml with default config
|
||||||
|
"""
|
||||||
|
if os.path.exists(config_file):
|
||||||
|
with open(config_file) as f:
|
||||||
|
config_overrides = yaml.safe_load(f)
|
||||||
|
else:
|
||||||
|
config_overrides = {}
|
||||||
|
return _merge_dictionaries(dict(default), config_overrides)
|
||||||
|
|
||||||
|
|
||||||
def apply_config(config_overrides, c):
|
def apply_config(config_overrides, c):
|
||||||
"""
|
"""
|
||||||
Merge config_overrides with config defaults & apply to JupyterHub config c
|
Merge config_overrides with config defaults & apply to JupyterHub config c
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from urllib.request import urlopen, URLError
|
|||||||
|
|
||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
|
|
||||||
from tljh import conda, systemd, user, apt
|
from tljh import conda, systemd, traefik, user, apt
|
||||||
|
|
||||||
INSTALL_PREFIX = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh')
|
INSTALL_PREFIX = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh')
|
||||||
HUB_ENV_PREFIX = os.path.join(INSTALL_PREFIX, 'hub')
|
HUB_ENV_PREFIX = os.path.join(INSTALL_PREFIX, 'hub')
|
||||||
@@ -110,25 +110,32 @@ def ensure_chp_package(prefix):
|
|||||||
|
|
||||||
def ensure_jupyterhub_service(prefix):
|
def ensure_jupyterhub_service(prefix):
|
||||||
"""
|
"""
|
||||||
Ensure JupyterHub & CHP Services are set up properly
|
Ensure JupyterHub Services are set up properly
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
os.makedirs(STATE_DIR, mode=0o700, exist_ok=True)
|
||||||
|
|
||||||
with open(os.path.join(HERE, 'systemd-units', 'jupyterhub.service')) as f:
|
with open(os.path.join(HERE, 'systemd-units', 'jupyterhub.service')) as f:
|
||||||
hub_unit_template = f.read()
|
hub_unit_template = f.read()
|
||||||
|
|
||||||
with open(os.path.join(HERE, 'systemd-units', 'configurable-http-proxy.service')) as f:
|
with open(os.path.join(HERE, 'systemd-units', 'configurable-http-proxy.service')) as f:
|
||||||
proxy_unit_template = f.read()
|
proxy_unit_template = f.read()
|
||||||
|
|
||||||
|
with open(os.path.join(HERE, 'systemd-units', 'traefik.service')) as f:
|
||||||
|
traefik_unit_template = f.read()
|
||||||
|
|
||||||
|
traefik.ensure_traefik_config(STATE_DIR)
|
||||||
|
|
||||||
unit_params = dict(
|
unit_params = dict(
|
||||||
python_interpreter_path=sys.executable,
|
python_interpreter_path=sys.executable,
|
||||||
jupyterhub_config_path=os.path.join(HERE, 'jupyterhub_config.py'),
|
jupyterhub_config_path=os.path.join(HERE, 'jupyterhub_config.py'),
|
||||||
install_prefix=INSTALL_PREFIX
|
install_prefix=INSTALL_PREFIX,
|
||||||
)
|
)
|
||||||
systemd.install_unit('configurable-http-proxy.service', proxy_unit_template.format(**unit_params))
|
systemd.install_unit('configurable-http-proxy.service', proxy_unit_template.format(**unit_params))
|
||||||
systemd.install_unit('jupyterhub.service', hub_unit_template.format(**unit_params))
|
systemd.install_unit('jupyterhub.service', hub_unit_template.format(**unit_params))
|
||||||
|
systemd.install_unit('traefik.service', traefik_unit_template.format(**unit_params))
|
||||||
systemd.reload_daemon()
|
systemd.reload_daemon()
|
||||||
|
|
||||||
os.makedirs(STATE_DIR, mode=0o700, exist_ok=True)
|
|
||||||
|
|
||||||
# Set up proxy / hub secret oken if it is not already setup
|
# Set up proxy / hub secret oken if it is not already setup
|
||||||
proxy_secret_path = os.path.join(STATE_DIR, 'configurable-http-proxy.secret')
|
proxy_secret_path = os.path.join(STATE_DIR, 'configurable-http-proxy.secret')
|
||||||
if not os.path.exists(proxy_secret_path):
|
if not os.path.exists(proxy_secret_path):
|
||||||
@@ -141,10 +148,12 @@ def ensure_jupyterhub_service(prefix):
|
|||||||
systemd.start_service('configurable-http-proxy')
|
systemd.start_service('configurable-http-proxy')
|
||||||
# If JupyterHub is running, we want to restart it.
|
# If JupyterHub is running, we want to restart it.
|
||||||
systemd.restart_service('jupyterhub')
|
systemd.restart_service('jupyterhub')
|
||||||
|
systemd.restart_service('traefik')
|
||||||
|
|
||||||
# Mark JupyterHub & CHP to start at boot ime
|
# Mark JupyterHub & CHP to start at boot time
|
||||||
systemd.enable_service('jupyterhub')
|
systemd.enable_service('jupyterhub')
|
||||||
systemd.enable_service('configurable-http-proxy')
|
systemd.enable_service('configurable-http-proxy')
|
||||||
|
systemd.enable_service('traefik')
|
||||||
|
|
||||||
|
|
||||||
def ensure_jupyterhub_package(prefix):
|
def ensure_jupyterhub_package(prefix):
|
||||||
@@ -165,6 +174,7 @@ def ensure_jupyterhub_package(prefix):
|
|||||||
'jupyterhub-ldapauthenticator==1.2.2',
|
'jupyterhub-ldapauthenticator==1.2.2',
|
||||||
'oauthenticator==0.7.3',
|
'oauthenticator==0.7.3',
|
||||||
])
|
])
|
||||||
|
traefik.ensure_traefik_binary(prefix)
|
||||||
|
|
||||||
|
|
||||||
def ensure_usergroups():
|
def ensure_usergroups():
|
||||||
@@ -252,7 +262,7 @@ def ensure_jupyterhub_running(times=4):
|
|||||||
urlopen('http://127.0.0.1')
|
urlopen('http://127.0.0.1')
|
||||||
return
|
return
|
||||||
except HTTPError as h:
|
except HTTPError as h:
|
||||||
if h.code in [404, 503]:
|
if h.code in [404, 502, 503]:
|
||||||
# May be transient
|
# May be transient
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ class CustomSpawner(SystemdSpawner):
|
|||||||
|
|
||||||
c.JupyterHub.spawner_class = CustomSpawner
|
c.JupyterHub.spawner_class = CustomSpawner
|
||||||
|
|
||||||
c.JupyterHub.port = 80
|
|
||||||
|
|
||||||
# Use a high port so users can try this on machines with a JupyterHub already present
|
# Use a high port so users can try this on machines with a JupyterHub already present
|
||||||
c.JupyterHub.hub_port = 15001
|
c.JupyterHub.hub_port = 15001
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
User=root
|
User=nobody
|
||||||
Restart=always
|
Restart=always
|
||||||
# chp process should have no write access anywhere on disk
|
# chp process should have no write access anywhere on disk
|
||||||
ProtectHome=tmpfs
|
ProtectHome=tmpfs
|
||||||
@@ -16,8 +16,8 @@ ProtectKernelTunables=yes
|
|||||||
ProtectKernelModules=yes
|
ProtectKernelModules=yes
|
||||||
EnvironmentFile={install_prefix}/state/configurable-http-proxy.secret
|
EnvironmentFile={install_prefix}/state/configurable-http-proxy.secret
|
||||||
ExecStart={install_prefix}/hub/node_modules/.bin/configurable-http-proxy \
|
ExecStart={install_prefix}/hub/node_modules/.bin/configurable-http-proxy \
|
||||||
--ip 0.0.0.0 \
|
--ip 127.0.0.1 \
|
||||||
--port 80 \
|
--port 15003 \
|
||||||
--api-ip 127.0.0.1 \
|
--api-ip 127.0.0.1 \
|
||||||
--api-port 15002 \
|
--api-port 15002 \
|
||||||
--error-target http://127.0.0.1:15001/hub/error
|
--error-target http://127.0.0.1:15001/hub/error
|
||||||
|
|||||||
24
tljh/systemd-units/traefik.service
Normal file
24
tljh/systemd-units/traefik.service
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Template file for Traefik systemd service
|
||||||
|
# Uses simple string.format() for 'templating'
|
||||||
|
[Unit]
|
||||||
|
# Wait for network stack to be fully up before starting proxy
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
Restart=always
|
||||||
|
# process only needs to write state/acme.json file, no other files
|
||||||
|
ProtectHome=tmpfs
|
||||||
|
ProtectSystem=strict
|
||||||
|
PrivateTmp=yes
|
||||||
|
PrivateDevices=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ReadWritePaths={install_prefix}/state/acme.json
|
||||||
|
WorkingDirectory={install_prefix}/state
|
||||||
|
ExecStart={install_prefix}/hub/bin/traefik \
|
||||||
|
-c {install_prefix}/state/traefik.toml
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
# Start service when system boots
|
||||||
|
WantedBy=multi-user.target
|
||||||
84
tljh/traefik.py
Normal file
84
tljh/traefik.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
"""Traefik installation and setup"""
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
from urllib.request import urlretrieve
|
||||||
|
|
||||||
|
from jinja2 import Environment, Template
|
||||||
|
|
||||||
|
from tljh.configurer import load_config
|
||||||
|
|
||||||
|
# FIXME: support more than one platform here
|
||||||
|
plat = "linux-amd64"
|
||||||
|
traefik_version = "1.6.5"
|
||||||
|
|
||||||
|
# record sha256 hashes for supported platforms here
|
||||||
|
checksums = {
|
||||||
|
"linux-amd64": "9e77c7664e316953e3f5463c323dffeeecbb35d0b1db7fb49f52e1d9464ca193"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def checksum_file(path):
|
||||||
|
"""Compute the sha256 checksum of a path"""
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(4096), b""):
|
||||||
|
hasher.update(chunk)
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_traefik_binary(prefix):
|
||||||
|
"""Download and install the traefik binary"""
|
||||||
|
traefik_bin = os.path.join(prefix, "bin", "traefik")
|
||||||
|
if os.path.exists(traefik_bin):
|
||||||
|
checksum = checksum_file(traefik_bin)
|
||||||
|
if checksum == checksums[plat]:
|
||||||
|
# already have the right binary
|
||||||
|
# ensure permissions and we're done
|
||||||
|
os.chmod(traefik_bin, 0o755)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print(f"checksum mismatch on {traefik_bin}")
|
||||||
|
os.remove(traefik_bin)
|
||||||
|
|
||||||
|
traefik_url = (
|
||||||
|
"https://github.com/containous/traefik/releases"
|
||||||
|
f"/download/v{traefik_version}/traefik_{plat}"
|
||||||
|
)
|
||||||
|
print(f"Downloading traefik {traefik_version}...")
|
||||||
|
# download the file
|
||||||
|
urlretrieve(traefik_url, traefik_bin)
|
||||||
|
os.chmod(traefik_bin, 0o755)
|
||||||
|
|
||||||
|
# verify that we got what we expected
|
||||||
|
checksum = checksum_file(traefik_bin)
|
||||||
|
if checksum != checksums[plat]:
|
||||||
|
raise IOError(f"Checksum failed {traefik_bin}: {checksum} != {checksums[plat]}")
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_traefik_config(state_dir):
|
||||||
|
"""Render the traefik.toml config file"""
|
||||||
|
config = load_config()
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), "traefik.toml.tpl")) as f:
|
||||||
|
template = Template(f.read())
|
||||||
|
new_toml = template.render(config)
|
||||||
|
https = config["https"]
|
||||||
|
letsencrypt = https["letsencrypt"]
|
||||||
|
tls = https["tls"]
|
||||||
|
# validate https config
|
||||||
|
if https["enabled"]:
|
||||||
|
if not tls["cert"] and not letsencrypt["email"]:
|
||||||
|
raise ValueError(
|
||||||
|
"To enable https, you must set tls.cert+key or letsencrypt.email+domains"
|
||||||
|
)
|
||||||
|
if (letsencrypt["email"] and not letsencrypt["domains"]) or (
|
||||||
|
letsencrypt["domains"] and not letsencrypt["email"]
|
||||||
|
):
|
||||||
|
raise ValueError("Both email and domains must be set for letsencrypt")
|
||||||
|
with open(os.path.join(state_dir, "traefik.toml"), "w") as f:
|
||||||
|
os.fchmod(f.fileno(), 0o744)
|
||||||
|
f.write(new_toml)
|
||||||
|
|
||||||
|
# ensure acme.json exists and is private
|
||||||
|
with open(os.path.join(state_dir, "acme.json"), "a") as f:
|
||||||
|
os.fchmod(f.fileno(), 0o600)
|
||||||
|
|
||||||
69
tljh/traefik.toml.tpl
Normal file
69
tljh/traefik.toml.tpl
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# traefik.toml file template
|
||||||
|
{% if https['enabled'] %}
|
||||||
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
{% else %}
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
logLevel = "INFO"
|
||||||
|
# log errors, which could be proxy errors
|
||||||
|
[accessLog]
|
||||||
|
format = "json"
|
||||||
|
[accessLog.filters]
|
||||||
|
statusCodes = ["500-999"]
|
||||||
|
|
||||||
|
[accessLog.fields.headers]
|
||||||
|
[accessLog.fields.headers.names]
|
||||||
|
Authorization = "redact"
|
||||||
|
Cookie = "redact"
|
||||||
|
Set-Cookie = "redact"
|
||||||
|
X-Xsrftoken = "redact"
|
||||||
|
|
||||||
|
[respondingTimeouts]
|
||||||
|
idleTimeout = "10m0s"
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":{{http['port']}}"
|
||||||
|
{% if https['enabled'] %}
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
entryPoint = "https"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if https['enabled'] %}
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":{{https['port']}}"
|
||||||
|
backend = "jupyterhub"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
{% if https['tls']['cert'] %}
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
certFile = "{{https['tls']['cert']}}"
|
||||||
|
keyFile = "{{https['tls']['key']}}"
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if https['enabled'] and https['letsencrypt']['email'] %}
|
||||||
|
[acme]
|
||||||
|
email = "{{https['letsencrypt']['email']}}"
|
||||||
|
storage = "acme.json"
|
||||||
|
entryPoint = "https"
|
||||||
|
[acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
|
|
||||||
|
{% for domain in https['letsencrypt']['domains'] %}
|
||||||
|
[[acme.domains]]
|
||||||
|
main = "{{domain}}"
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
[file]
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.jupyterhub]
|
||||||
|
backend = "jupyterhub"
|
||||||
|
passHostHeader = true
|
||||||
|
[backends]
|
||||||
|
[backends.jupyterhub]
|
||||||
|
[backends.jupyterhub.servers.chp]
|
||||||
|
url = "http://127.0.0.1:15003"
|
||||||
|
|
||||||
Reference in New Issue
Block a user