2018-08-28 14:00:43 +02:00
""" Installation logic for TLJH """
2018-07-19 00:02:15 -07:00
import argparse
2019-07-10 15:07:40 +03:00
import dbm
2018-08-28 14:00:43 +02:00
import itertools
import logging
2018-06-26 18:37:24 -07:00
import os
2018-06-27 02:00:52 -07:00
import secrets
2020-07-30 22:59:56 +03:00
import signal
2018-07-19 00:02:15 -07:00
import subprocess
import sys
2018-07-11 12:56:52 -07:00
import time
2019-05-29 12:35:19 +03:00
import warnings
2018-07-19 00:02:15 -07:00
2019-07-10 15:07:40 +03:00
import bcrypt
2018-08-28 14:00:43 +02:00
import pluggy
2019-05-24 14:37:42 +03:00
import requests
2019-05-29 12:35:19 +03:00
from requests . packages . urllib3 . exceptions import InsecureRequestWarning
2018-06-26 18:37:24 -07:00
2023-05-15 08:51:35 +00:00
from tljh import apt , conda , hooks , migrator , systemd , traefik , user
2023-03-21 14:21:00 +01:00
2018-11-01 11:34:16 +01:00
from . config import (
2018-08-28 14:00:43 +02:00
CONFIG_DIR ,
CONFIG_FILE ,
HUB_ENV_PREFIX ,
INSTALL_PREFIX ,
STATE_DIR ,
USER_ENV_PREFIX ,
)
2023-03-21 14:21:00 +01:00
from . utils import parse_version as V
2018-11-01 11:34:16 +01:00
from . yaml import yaml
2018-06-26 18:37:24 -07:00
HERE = os . path . abspath ( os . path . dirname ( __file__ ) )
2018-12-12 14:45:39 -05:00
logger = logging . getLogger ( " tljh " )
2018-07-29 02:17:12 -07:00
2018-07-19 17:30:09 -07:00
2019-02-18 15:08:53 +02:00
def remove_chp ( ) :
2018-07-19 17:30:09 -07:00
"""
2019-02-18 15:08:53 +02:00
Ensure CHP is not running
2018-07-19 17:30:09 -07:00
"""
2019-02-19 17:28:43 +02:00
if os . path . exists ( " /etc/systemd/system/configurable-http-proxy.service " ) :
2021-11-03 23:55:34 +01:00
if systemd . check_service_active ( " configurable-http-proxy.service " ) :
2019-02-18 15:08:53 +02:00
try :
2021-11-03 23:55:34 +01:00
systemd . stop_service ( " configurable-http-proxy.service " )
2019-02-18 15:08:53 +02:00
except subprocess . CalledProcessError :
logger . info ( " Cannot stop configurable-http-proxy... " )
2021-11-03 23:55:34 +01:00
if systemd . check_service_enabled ( " configurable-http-proxy.service " ) :
2019-02-18 15:08:53 +02:00
try :
2021-11-03 23:55:34 +01:00
systemd . disable_service ( " configurable-http-proxy.service " )
2019-02-18 15:08:53 +02:00
except subprocess . CalledProcessError :
logger . info ( " Cannot disable configurable-http-proxy... " )
try :
2021-11-03 23:55:34 +01:00
systemd . uninstall_unit ( " configurable-http-proxy.service " )
2019-02-18 15:08:53 +02:00
except subprocess . CalledProcessError :
logger . info ( " Cannot uninstall configurable-http-proxy... " )
2018-07-19 17:30:09 -07:00
2018-06-26 18:37:24 -07:00
def ensure_jupyterhub_service ( prefix ) :
2018-06-27 02:00:52 -07:00
"""
2018-07-21 00:20:29 -07:00
Ensure JupyterHub Services are set up properly
2018-07-03 16:18:32 -07:00
"""
2018-07-22 23:56:58 -07:00
2019-02-18 15:08:53 +02:00
remove_chp ( )
systemd . reload_daemon ( )
2021-11-03 23:55:34 +01:00
with open ( os . path . join ( HERE , " systemd-units " , " jupyterhub.service " ) ) as f :
2018-06-27 02:00:52 -07:00
hub_unit_template = f . read ( )
2018-06-26 18:37:24 -07:00
2021-11-03 23:55:34 +01:00
with open ( os . path . join ( HERE , " systemd-units " , " traefik.service " ) ) as f :
2018-07-21 00:20:29 -07:00
traefik_unit_template = f . read ( )
2021-11-01 09:42:45 +01:00
# Set up proxy / hub secret token if it is not already setup
2021-11-03 23:55:34 +01:00
proxy_secret_path = os . path . join ( STATE_DIR , " traefik-api.secret " )
2019-02-13 14:10:28 +02:00
if not os . path . exists ( proxy_secret_path ) :
2021-11-03 23:55:34 +01:00
with open ( proxy_secret_path , " w " ) as f :
2019-02-13 14:10:28 +02:00
f . write ( secrets . token_hex ( 32 ) )
2018-07-21 00:20:29 -07:00
traefik . ensure_traefik_config ( STATE_DIR )
2018-06-27 02:00:52 -07:00
unit_params = dict (
2018-06-26 18:37:24 -07:00
python_interpreter_path = sys . executable ,
2021-11-03 23:55:34 +01:00
jupyterhub_config_path = os . path . join ( HERE , " jupyterhub_config.py " ) ,
2018-07-21 00:20:29 -07:00
install_prefix = INSTALL_PREFIX ,
2018-06-26 18:37:24 -07:00
)
2021-11-03 23:55:34 +01:00
systemd . install_unit ( " jupyterhub.service " , hub_unit_template . format ( * * unit_params ) )
systemd . install_unit ( " traefik.service " , traefik_unit_template . format ( * * unit_params ) )
2018-06-28 00:06:11 -07:00
systemd . reload_daemon ( )
2018-06-27 02:00:52 -07:00
2018-06-28 00:06:11 -07:00
# If JupyterHub is running, we want to restart it.
2021-11-03 23:55:34 +01:00
systemd . restart_service ( " jupyterhub " )
systemd . restart_service ( " traefik " )
2018-06-27 14:21:08 -07:00
2019-02-13 14:10:28 +02:00
# Mark JupyterHub & traefik to start at boot time
2021-11-03 23:55:34 +01:00
systemd . enable_service ( " jupyterhub " )
systemd . enable_service ( " traefik " )
2018-06-28 00:49:36 -07:00
2018-06-26 18:37:24 -07:00
def ensure_jupyterhub_package ( prefix ) :
"""
Install JupyterHub into our conda environment if needed .
2018-07-16 01:28:18 -07:00
We install all python packages from PyPI as much as possible in the
hub environment . A lot of spawners & authenticators do not have conda - forge
packages , but do have pip packages . Keeping all python packages in the
hub environment be installed with pip prevents accidental mixing of python
and conda packages !
2018-06-26 18:37:24 -07:00
"""
2019-05-29 11:34:23 -07:00
# Install pycurl. JupyterHub prefers pycurl over SimpleHTTPClient automatically
# pycurl is generally more bugfree - see https://github.com/jupyterhub/the-littlest-jupyterhub/issues/289
# build-essential is also generally useful to everyone involved, and required for pycurl
2021-11-03 23:55:34 +01:00
apt . install_packages ( [ " libssl-dev " , " libcurl4-openssl-dev " , " build-essential " ] )
conda . ensure_pip_packages ( prefix , [ " pycurl==7.* " ] , upgrade = True )
2019-05-29 11:34:23 -07:00
2020-12-07 12:53:38 +01:00
conda . ensure_pip_packages (
prefix ,
[
2023-04-24 01:55:34 +02:00
" jupyterhub==4.* " ,
2023-01-10 16:40:59 -08:00
" jupyterhub-systemdspawner==0.17.* " ,
2021-10-28 13:39:14 +02:00
" jupyterhub-firstuseauthenticator==1.* " ,
2021-10-19 11:56:16 +02:00
" jupyterhub-nativeauthenticator==1.* " ,
" jupyterhub-ldapauthenticator==1.* " ,
" jupyterhub-tmpauthenticator==0.6.* " ,
2023-01-10 16:40:59 -08:00
" oauthenticator==15.* " ,
2021-10-19 11:56:16 +02:00
" jupyterhub-idle-culler==1.* " ,
2021-11-01 09:42:45 +01:00
" git+https://github.com/yuvipanda/jupyterhub-configurator@317759e17c8e48de1b1352b836dac2a230536dba " ,
2020-12-07 12:53:38 +01:00
] ,
2021-10-27 02:29:04 +02:00
upgrade = True ,
2020-12-07 12:53:38 +01:00
)
2018-07-21 00:20:29 -07:00
traefik . ensure_traefik_binary ( prefix )
2018-06-26 18:37:24 -07:00
2018-07-03 16:18:32 -07:00
def ensure_usergroups ( ) :
"""
Sets up user groups & sudo rules
"""
2021-11-03 23:55:34 +01:00
user . ensure_group ( " jupyterhub-admins " )
user . ensure_group ( " jupyterhub-users " )
2018-07-02 15:12:26 -07:00
2018-07-29 02:17:12 -07:00
logger . info ( " Granting passwordless sudo to JupyterHub admins... " )
2021-11-03 23:55:34 +01:00
with open ( " /etc/sudoers.d/jupyterhub-admins " , " w " ) as f :
2018-07-02 15:12:26 -07:00
# JupyterHub admins should have full passwordless sudo access
2021-11-03 23:55:34 +01:00
f . write ( " % jupyterhub-admins ALL = (ALL) NOPASSWD: ALL \n " )
2018-07-02 15:12:26 -07:00
# `sudo -E` should preserve the $PATH we set. This allows
# admins in jupyter terminals to do `sudo -E pip install <package>`,
# `pip` is in the $PATH we set in jupyterhub_config.py to include the user conda env.
2021-11-03 23:55:34 +01:00
f . write ( " Defaults exempt_group = jupyterhub-admins \n " )
2018-07-02 15:12:26 -07:00
2018-07-03 16:18:32 -07:00
2023-03-21 14:21:00 +01:00
# Install mambaforge using an installer from
# https://github.com/conda-forge/miniforge/releases
2023-04-15 11:06:29 +02:00
MAMBAFORGE_VERSION = " 23.1.0-1 "
2023-03-21 14:21:00 +01:00
# sha256 checksums
MAMBAFORGE_CHECKSUMS = {
2023-04-15 11:06:29 +02:00
" aarch64 " : " d9d89c9e349369702171008d9ee7c5ce80ed420e5af60bd150a3db4bf674443a " ,
" x86_64 " : " cfb16c47dc2d115c8b114280aa605e322173f029fdb847a45348bf4bd23c62ab " ,
2023-03-21 14:21:00 +01:00
}
2023-03-27 13:10:31 +02:00
# minimum versions of packages
MINIMUM_VERSIONS = {
# if conda/mamba are lower than this, upgrade them before installing the user packages
" mamba " : " 0.16.0 " ,
" conda " : " 4.10 " ,
# minimum Python version (if not matched, abort to avoid big disruptive updates)
" python " : " 3.9 " ,
}
2023-03-21 14:21:00 +01:00
def _mambaforge_url ( version = MAMBAFORGE_VERSION , arch = None ) :
""" Return (URL, checksum) for mambaforge download for a given version and arch
Default values provided for both version and arch
"""
if arch is None :
arch = os . uname ( ) . machine
installer_url = " https://github.com/conda-forge/miniforge/releases/download/ {v} /Mambaforge- {v} -Linux- {arch} .sh " . format (
v = version ,
arch = arch ,
)
# Check system architecture, set appropriate installer checksum
checksum = MAMBAFORGE_CHECKSUMS . get ( arch )
if not checksum :
raise ValueError (
f " Unsupported architecture: { arch } . TLJH only supports { ' , ' . join ( MAMBAFORGE_CHECKSUMS . keys ( ) ) } "
)
return installer_url , checksum
2018-07-18 01:00:48 -07:00
def ensure_user_environment ( user_requirements_txt_file ) :
2018-07-03 16:18:32 -07:00
"""
Set up user conda environment with required packages
"""
2018-07-29 02:17:12 -07:00
logger . info ( " Setting up user environment... " )
2021-11-03 22:47:57 +01:00
# Check OS, set appropriate string for conda installer path
2021-11-03 23:55:34 +01:00
if os . uname ( ) . sysname != " Linux " :
2021-11-03 22:47:57 +01:00
raise OSError ( " TLJH is only supported on Linux platforms. " )
2023-03-27 13:10:31 +02:00
# Check the existing environment for what to do
package_versions = conda . get_conda_package_versions ( USER_ENV_PREFIX )
# Case 1: no existing environment
if not package_versions :
# 1a. no environment, but prefix exists.
# Abort to avoid clobbering something we don't recognize
if os . path . exists ( USER_ENV_PREFIX ) and os . listdir ( USER_ENV_PREFIX ) :
msg = f " Found non-empty directory that is not a conda install in { USER_ENV_PREFIX } . Please remove it (or rename it to preserve files) and run tljh again. "
logger . error ( msg )
raise OSError ( msg )
# 1b. No environment, directory empty or doesn't exist
# start fresh install
2021-11-03 23:55:34 +01:00
logger . info ( " Downloading & setting up user environment... " )
2023-03-21 14:21:00 +01:00
installer_url , installer_sha256 = _mambaforge_url ( )
2021-11-03 23:55:34 +01:00
with conda . download_miniconda_installer (
installer_url , installer_sha256
) as installer_path :
2018-07-19 17:30:09 -07:00
conda . install_miniconda ( installer_path , USER_ENV_PREFIX )
2023-03-27 13:10:31 +02:00
package_versions = conda . get_conda_package_versions ( USER_ENV_PREFIX )
# quick sanity check: we should have conda and mamba!
assert " conda " in package_versions
assert " mamba " in package_versions
# next, check Python
python_version = package_versions [ " python " ]
logger . debug ( f " Found python= { python_version } in { USER_ENV_PREFIX } " )
if V ( python_version ) < V ( MINIMUM_VERSIONS [ " python " ] ) :
msg = (
f " TLJH requires Python >= { MINIMUM_VERSIONS [ ' python ' ] } , found python= { python_version } in { USER_ENV_PREFIX } . "
f " \n Please upgrade Python (may be highly disruptive!), or remove/rename { USER_ENV_PREFIX } to allow TLJH to make a fresh install. "
f " \n You can use ` { USER_ENV_PREFIX } /bin/conda list` to save your current list of packages. "
)
logger . error ( msg )
raise ValueError ( msg )
# at this point, we know we have an env ready with conda and are going to start installing
# first, check if we should upgrade/install conda and/or mamba
to_upgrade = [ ]
for pkg in ( " conda " , " mamba " ) :
version = package_versions . get ( pkg )
min_version = MINIMUM_VERSIONS [ pkg ]
if not version :
logger . warning ( f " { USER_ENV_PREFIX } is missing { pkg } , installing it... " )
to_upgrade . append ( pkg )
else :
logger . debug ( f " Found { pkg } == { version } in { USER_ENV_PREFIX } " )
if V ( version ) < V ( min_version ) :
logger . info (
f " { USER_ENV_PREFIX } has { pkg } == { version } , it will be upgraded to { pkg } >= { min_version } "
)
to_upgrade . append ( pkg )
if to_upgrade :
conda . ensure_conda_packages (
USER_ENV_PREFIX ,
# we _could_ explicitly pin Python here,
# but conda already does this by default
to_upgrade ,
)
2018-07-02 15:12:26 -07:00
2019-10-28 11:07:11 +01:00
conda . ensure_pip_requirements (
USER_ENV_PREFIX ,
2021-11-03 23:55:34 +01:00
os . path . join ( HERE , " requirements-base.txt " ) ,
2021-10-27 02:29:04 +02:00
upgrade = True ,
2019-10-28 11:07:11 +01:00
)
2018-07-02 15:12:26 -07:00
2018-07-18 01:00:48 -07:00
if user_requirements_txt_file :
# FIXME: This currently fails hard, should fail soft and not abort installer
2021-10-27 02:29:04 +02:00
conda . ensure_pip_requirements (
USER_ENV_PREFIX ,
user_requirements_txt_file ,
upgrade = True ,
)
2018-07-18 01:00:48 -07:00
2018-07-03 16:18:32 -07:00
2019-07-16 11:49:15 +03:00
def ensure_admins ( admin_password_list ) :
2018-07-03 16:18:32 -07:00
"""
Setup given list of users as admins .
"""
2019-07-16 20:18:45 +03:00
os . makedirs ( STATE_DIR , mode = 0o700 , exist_ok = True )
2019-07-16 11:49:15 +03:00
if not admin_password_list :
2018-07-03 16:18:32 -07:00
return
2018-07-29 02:17:12 -07:00
logger . info ( " Setting up admin users " )
2018-08-28 14:00:43 +02:00
config_path = CONFIG_FILE
2018-07-03 16:18:32 -07:00
if os . path . exists ( config_path ) :
2021-10-31 11:26:40 +01:00
with open ( config_path ) as f :
2018-11-01 11:34:16 +01:00
config = yaml . load ( f )
2018-07-03 16:18:32 -07:00
else :
config = { }
2021-11-03 23:55:34 +01:00
config [ " users " ] = config . get ( " users " , { } )
2018-07-03 16:18:32 -07:00
2021-11-03 23:55:34 +01:00
db_passw = os . path . join ( STATE_DIR , " passwords.dbm " )
2019-07-16 11:49:15 +03:00
admins = [ ]
2019-07-16 20:18:45 +03:00
for admin_password_entry in admin_password_list :
for admin_password_pair in admin_password_entry :
2019-07-16 11:49:15 +03:00
if " : " in admin_password_pair :
2021-11-03 23:55:34 +01:00
admin , password = admin_password_pair . split ( " : " )
2019-07-16 11:49:15 +03:00
admins . append ( admin )
# Add admin:password to the db
password = bcrypt . hashpw ( password . encode ( ) , bcrypt . gensalt ( ) )
2021-11-03 23:55:34 +01:00
with dbm . open ( db_passw , " c " , 0o600 ) as db :
2019-07-16 11:49:15 +03:00
db [ admin ] = password
else :
admins . append ( admin_password_pair )
2021-11-03 23:55:34 +01:00
config [ " users " ] [ " admin " ] = admins
2019-07-10 15:07:40 +03:00
2021-11-03 23:55:34 +01:00
with open ( config_path , " w+ " ) as f :
2018-11-01 11:34:16 +01:00
yaml . dump ( config , f )
2018-07-03 16:18:32 -07:00
2019-01-22 16:24:38 +02:00
def ensure_jupyterhub_running ( times = 20 ) :
2018-07-11 12:56:52 -07:00
"""
Ensure that JupyterHub is up and running
Loops given number of times , waiting a second each .
"""
2018-07-13 15:36:44 +02:00
for i in range ( times ) :
2018-07-11 12:56:52 -07:00
try :
2021-11-03 23:55:34 +01:00
logger . info ( f " Waiting for JupyterHub to come up ( { i + 1 } / { times } tries) " )
2019-05-29 12:35:19 +03:00
# Because we don't care at this level that SSL is valid, we can suppress
# InsecureRequestWarning for this request.
with warnings . catch_warnings ( ) :
warnings . filterwarnings ( " ignore " , category = InsecureRequestWarning )
2021-11-03 23:55:34 +01:00
requests . get ( " http://127.0.0.1 " , verify = False )
2018-07-11 13:01:57 -07:00
return
2019-05-24 14:37:42 +03:00
except requests . HTTPError as h :
if h . response . status_code in [ 404 , 502 , 503 ] :
2018-07-13 14:56:00 +00:00
# May be transient
2018-07-11 12:56:52 -07:00
time . sleep ( 1 )
continue
# Everything else should immediately abort
raise
2019-05-24 14:37:42 +03:00
except requests . ConnectionError :
2021-11-01 09:42:45 +01:00
# Hub isn't up yet, sleep & loop
time . sleep ( 1 )
continue
2019-05-24 14:37:42 +03:00
except Exception :
2018-07-13 15:55:41 +02:00
# Everything else should immediately abort
raise
2018-07-11 12:56:52 -07:00
2021-10-31 11:26:40 +01:00
raise Exception ( f " Installation failed: JupyterHub did not start in { times } s " )
2018-07-11 12:56:52 -07:00
2018-07-31 13:16:57 -07:00
def ensure_symlinks ( prefix ) :
"""
2018-08-12 21:52:04 -07:00
Ensure we symlink appropriate things into / usr / bin
2018-07-31 13:16:57 -07:00
We add the user conda environment to PATH for notebook terminals ,
but not the hub venv . This means tljh - config is not actually accessible .
2018-08-12 21:52:04 -07:00
We symlink to / usr / bin and not / usr / local / bin , since / usr / local / bin is
not place , and works with sudo - E in sudo ' s search $PATH. We can work
around this with sudo - E and extra entries in the sudoers file , but this
is far more secure at the cost of upsetting some FHS purists .
2018-07-31 13:16:57 -07:00
"""
2021-11-03 23:55:34 +01:00
tljh_config_src = os . path . join ( prefix , " bin " , " tljh-config " )
tljh_config_dest = " /usr/bin/tljh-config "
2018-08-01 18:53:50 -07:00
if os . path . exists ( tljh_config_dest ) :
if os . path . realpath ( tljh_config_dest ) != tljh_config_src :
# tljh-config exists that isn't ours. We should *not* delete this file,
# instead we throw an error and abort. Deleting files owned by other people
# while running as root is dangerous, especially with symlinks involved.
2021-11-01 09:42:45 +01:00
raise FileExistsError (
2021-11-03 23:55:34 +01:00
f " /usr/bin/tljh-config exists but is not a symlink to { tljh_config_src } "
2021-11-01 09:42:45 +01:00
)
2018-08-01 18:53:50 -07:00
else :
# We have a working symlink, so do nothing
return
os . symlink ( tljh_config_src , tljh_config_dest )
2018-07-31 13:16:57 -07:00
2018-08-11 01:19:51 -07:00
2018-08-28 14:16:09 +02:00
def setup_plugins ( plugins = None ) :
2018-08-11 00:42:28 -07:00
"""
2018-08-11 01:19:51 -07:00
Install plugins & setup a pluginmanager
2018-08-11 00:42:28 -07:00
"""
# Install plugins
if plugins :
2021-10-27 02:29:04 +02:00
conda . ensure_pip_packages ( HUB_ENV_PREFIX , plugins , upgrade = True )
2018-08-11 00:42:28 -07:00
# Set up plugin infrastructure
2021-11-03 23:55:34 +01:00
pm = pluggy . PluginManager ( " tljh " )
2018-08-11 00:42:28 -07:00
pm . add_hookspecs ( hooks )
2021-11-03 23:55:34 +01:00
pm . load_setuptools_entrypoints ( " tljh " )
2018-08-11 00:42:28 -07:00
2018-08-11 01:19:51 -07:00
return pm
2018-08-28 14:00:43 +02:00
2020-04-29 11:43:53 +02:00
def run_plugin_actions ( plugin_manager ) :
2018-08-11 01:19:51 -07:00
"""
Run installer hooks defined in plugins
"""
hook = plugin_manager . hook
2018-08-11 00:42:28 -07:00
# Install apt packages
2018-08-11 01:19:51 -07:00
apt_packages = list ( set ( itertools . chain ( * hook . tljh_extra_apt_packages ( ) ) ) )
2018-08-11 00:42:28 -07:00
if apt_packages :
2021-11-01 09:42:45 +01:00
logger . info (
2021-11-03 23:55:34 +01:00
" Installing {} apt packages collected from plugins: {} " . format (
len ( apt_packages ) , " " . join ( apt_packages )
2021-11-01 09:42:45 +01:00
)
)
2018-08-11 00:42:28 -07:00
apt . install_packages ( apt_packages )
2019-05-31 16:52:51 -07:00
# Install hub pip packages
hub_pip_packages = list ( set ( itertools . chain ( * hook . tljh_extra_hub_pip_packages ( ) ) ) )
if hub_pip_packages :
2021-11-01 09:42:45 +01:00
logger . info (
2021-11-03 23:55:34 +01:00
" Installing {} hub pip packages collected from plugins: {} " . format (
len ( hub_pip_packages ) , " " . join ( hub_pip_packages )
2021-11-01 09:42:45 +01:00
)
)
2021-10-27 02:29:04 +02:00
conda . ensure_pip_packages (
HUB_ENV_PREFIX ,
hub_pip_packages ,
upgrade = True ,
)
2019-05-31 16:52:51 -07:00
2018-08-11 00:42:28 -07:00
# Install conda packages
2018-08-11 01:19:51 -07:00
conda_packages = list ( set ( itertools . chain ( * hook . tljh_extra_user_conda_packages ( ) ) ) )
2018-08-11 00:42:28 -07:00
if conda_packages :
2021-11-01 09:42:45 +01:00
logger . info (
2021-11-03 23:55:34 +01:00
" Installing {} user conda packages collected from plugins: {} " . format (
len ( conda_packages ) , " " . join ( conda_packages )
2021-11-01 09:42:45 +01:00
)
)
2018-08-11 00:42:28 -07:00
conda . ensure_conda_packages ( USER_ENV_PREFIX , conda_packages )
# Install pip packages
2019-05-31 16:52:51 -07:00
user_pip_packages = list ( set ( itertools . chain ( * hook . tljh_extra_user_pip_packages ( ) ) ) )
if user_pip_packages :
2021-11-01 09:42:45 +01:00
logger . info (
2021-11-03 23:55:34 +01:00
" Installing {} user pip packages collected from plugins: {} " . format (
len ( user_pip_packages ) , " " . join ( user_pip_packages )
2021-11-01 09:42:45 +01:00
)
)
2021-10-27 02:29:04 +02:00
conda . ensure_pip_packages (
USER_ENV_PREFIX ,
user_pip_packages ,
upgrade = True ,
)
2018-08-11 00:42:28 -07:00
2019-06-27 11:45:36 +02:00
# Custom post install actions
hook . tljh_post_install ( )
2018-07-31 13:16:57 -07:00
2018-08-11 01:19:51 -07:00
def ensure_config_yaml ( plugin_manager ) :
"""
Ensure we have a config . yaml present
"""
2018-08-28 14:00:43 +02:00
# ensure config dir exists and is private
2021-11-03 23:55:34 +01:00
for path in [ CONFIG_DIR , os . path . join ( CONFIG_DIR , " jupyterhub_config.d " ) ] :
2018-08-28 14:00:43 +02:00
os . makedirs ( path , mode = 0o700 , exist_ok = True )
2018-08-31 12:17:16 +02:00
migrator . migrate_config_files ( )
2018-08-28 14:00:43 +02:00
2018-08-11 01:19:51 -07:00
if os . path . exists ( CONFIG_FILE ) :
2021-10-31 11:26:40 +01:00
with open ( CONFIG_FILE ) as f :
2018-11-01 11:34:16 +01:00
config = yaml . load ( f )
2018-08-11 01:19:51 -07:00
else :
config = { }
hook = plugin_manager . hook
hook . tljh_config_post_install ( config = config )
2021-11-03 23:55:34 +01:00
with open ( CONFIG_FILE , " w+ " ) as f :
2018-11-01 11:34:16 +01:00
yaml . dump ( config , f )
2018-08-11 01:19:51 -07:00
2018-07-03 16:18:32 -07:00
def main ( ) :
2018-08-31 12:00:42 +02:00
from . log import init_logging
2021-11-01 09:42:45 +01:00
2018-08-31 12:00:42 +02:00
init_logging ( )
2018-07-03 16:18:32 -07:00
argparser = argparse . ArgumentParser ( )
argparser . add_argument (
2021-11-03 23:55:34 +01:00
" --admin " , nargs = " * " , action = " append " , help = " List of usernames set to be admin "
2018-07-03 16:18:32 -07:00
)
2018-07-18 01:00:48 -07:00
argparser . add_argument (
2021-11-03 23:55:34 +01:00
" --user-requirements-txt-url " ,
help = " URL to a requirements.txt file that should be installed in the user environment " ,
2018-08-11 00:42:28 -07:00
)
2021-11-03 23:55:34 +01:00
argparser . add_argument ( " --plugin " , nargs = " * " , help = " Plugin pip-specs to install " )
2020-08-11 14:09:11 +03:00
argparser . add_argument (
2021-11-03 23:55:34 +01:00
" --progress-page-server-pid " ,
2020-08-17 16:47:55 +03:00
type = int ,
2021-11-03 23:55:34 +01:00
help = " The pid of the progress page server " ,
2020-08-11 14:09:11 +03:00
)
2018-07-03 16:18:32 -07:00
args = argparser . parse_args ( )
2018-08-11 01:19:51 -07:00
pm = setup_plugins ( args . plugin )
2018-08-28 14:00:43 +02:00
ensure_config_yaml ( pm )
2019-07-16 11:49:15 +03:00
ensure_admins ( args . admin )
2018-07-03 16:18:32 -07:00
ensure_usergroups ( )
2022-01-24 21:50:51 -05:00
if args . user_requirements_txt_url :
2022-06-17 17:36:42 +01:00
logger . info ( " installing packages from user_requirements_txt_url " )
2018-07-18 01:00:48 -07:00
ensure_user_environment ( args . user_requirements_txt_url )
2018-07-03 16:18:32 -07:00
2018-07-29 02:17:12 -07:00
logger . info ( " Setting up JupyterHub... " )
2018-07-03 16:18:32 -07:00
ensure_jupyterhub_package ( HUB_ENV_PREFIX )
2020-07-30 22:59:56 +03:00
2020-08-21 18:03:21 +03:00
# Stop the http server with the progress page before traefik starts
2020-08-17 19:04:25 +03:00
if args . progress_page_server_pid :
2020-08-11 14:09:11 +03:00
try :
2020-08-17 19:04:25 +03:00
os . kill ( args . progress_page_server_pid , signal . SIGINT )
2020-08-21 18:03:21 +03:00
# Log and print the message to make testing easier
print ( " Progress page server stopped successfully. " )
2020-08-11 14:09:11 +03:00
except Exception as e :
2020-08-21 18:03:21 +03:00
logger . error ( f " Couldn ' t stop the progress page server. Exception was { e } . " )
2020-07-30 22:59:56 +03:00
2018-07-03 16:18:32 -07:00
ensure_jupyterhub_service ( HUB_ENV_PREFIX )
2018-07-11 12:56:52 -07:00
ensure_jupyterhub_running ( )
2018-07-31 13:16:57 -07:00
ensure_symlinks ( HUB_ENV_PREFIX )
2018-07-03 16:18:32 -07:00
2018-08-11 00:42:28 -07:00
# Run installer plugins last
2020-04-29 11:43:53 +02:00
run_plugin_actions ( pm )
2018-08-11 00:42:28 -07:00
2018-07-29 02:17:12 -07:00
logger . info ( " Done! " )
2018-07-02 15:12:26 -07:00
2021-11-03 23:55:34 +01:00
if __name__ == " __main__ " :
2018-07-02 15:12:26 -07:00
main ( )