2018-07-02 15:12:26 -07:00
|
|
|
"""
|
|
|
|
|
Bootstrap an installation of TLJH.
|
|
|
|
|
|
|
|
|
|
Sets up just enough TLJH environments to invoke tljh.installer.
|
|
|
|
|
|
|
|
|
|
This script is run as:
|
|
|
|
|
|
|
|
|
|
curl <script-url> | sudo python3 -
|
|
|
|
|
|
|
|
|
|
Constraints:
|
2018-10-30 20:08:47 -07:00
|
|
|
- Entire script should be compatible with Python 3.6 (We run on Ubuntu 18.04+)
|
|
|
|
|
- Script should parse in Python 3.4 (since we exit with useful error message on Ubuntu 14.04+)
|
2018-07-02 15:12:26 -07:00
|
|
|
- Use stdlib modules only
|
|
|
|
|
"""
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
2018-07-03 16:18:32 -07:00
|
|
|
import sys
|
2018-07-29 02:17:12 -07:00
|
|
|
import logging
|
2019-05-19 22:44:49 -07:00
|
|
|
import shutil
|
2018-10-30 20:08:47 -07:00
|
|
|
|
2019-05-19 13:45:57 -07:00
|
|
|
logger = logging.getLogger(__name__)
|
2018-11-13 12:47:05 -08:00
|
|
|
|
|
|
|
|
def get_os_release_variable(key):
|
|
|
|
|
"""
|
|
|
|
|
Return value for key from /etc/os-release
|
|
|
|
|
|
|
|
|
|
/etc/os-release is a bash file, so should use bash to parse it.
|
|
|
|
|
|
|
|
|
|
Returns empty string if key is not found.
|
|
|
|
|
"""
|
|
|
|
|
return subprocess.check_output([
|
|
|
|
|
'/bin/bash', '-c',
|
2018-11-13 14:24:21 -08:00
|
|
|
"source /etc/os-release && echo ${{{key}}}".format(key=key)
|
2018-11-13 14:46:16 -08:00
|
|
|
]).decode().strip()
|
2018-07-02 15:12:26 -07:00
|
|
|
|
2019-05-19 13:45:57 -07:00
|
|
|
# Copied into tljh/utils.py. Make sure the copies are exactly the same!
|
|
|
|
|
def run_subprocess(cmd, *args, **kwargs):
|
|
|
|
|
"""
|
|
|
|
|
Run given cmd with smart output behavior.
|
|
|
|
|
|
|
|
|
|
If command succeeds, print output to debug logging.
|
|
|
|
|
If it fails, print output to info logging.
|
|
|
|
|
|
|
|
|
|
In TLJH, this sends successful output to the installer log,
|
|
|
|
|
and failed output directly to the user's screen
|
|
|
|
|
"""
|
2019-05-19 14:24:57 -07:00
|
|
|
logger = logging.getLogger('tljh')
|
2019-05-19 13:45:57 -07:00
|
|
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *args, **kwargs)
|
|
|
|
|
printable_command = ' '.join(cmd)
|
|
|
|
|
if proc.returncode != 0:
|
|
|
|
|
# Our process failed! Show output to the user
|
2019-05-19 14:24:57 -07:00
|
|
|
logger.error('Ran {command} with exit code {code}'.format(
|
|
|
|
|
command=printable_command, code=proc.returncode
|
2019-05-19 13:45:57 -07:00
|
|
|
))
|
2019-05-19 14:24:57 -07:00
|
|
|
logger.error(proc.stdout.decode())
|
|
|
|
|
raise subprocess.CalledProcessError(cmd=cmd, returncode=proc.returncode)
|
2019-05-19 13:45:57 -07:00
|
|
|
else:
|
|
|
|
|
# This goes into installer.log
|
|
|
|
|
logger.debug('Ran {command} with exit code {code}'.format(
|
|
|
|
|
command=printable_command, code=proc.returncode
|
|
|
|
|
))
|
|
|
|
|
# This produces multi line log output, unfortunately. Not sure how to fix.
|
|
|
|
|
# For now, prioritizing human readability over machine readability.
|
|
|
|
|
logger.debug(proc.stdout.decode())
|
|
|
|
|
|
2019-05-19 22:44:49 -07:00
|
|
|
def validate_host():
|
|
|
|
|
"""
|
|
|
|
|
Make sure TLJH is installable in current host
|
|
|
|
|
"""
|
2018-11-13 12:47:05 -08:00
|
|
|
# Support only Ubuntu 18.04+
|
|
|
|
|
distro = get_os_release_variable('ID')
|
|
|
|
|
version = float(get_os_release_variable('VERSION_ID'))
|
|
|
|
|
if distro != 'ubuntu':
|
|
|
|
|
print('The Littlest JupyterHub currently supports Ubuntu Linux only')
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
elif float(version) < 18.04:
|
|
|
|
|
print('The Littlest JupyterHub requires Ubuntu 18.04 or higher')
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
2019-05-19 22:44:49 -07:00
|
|
|
if sys.version_info < (3, 5):
|
|
|
|
|
print("bootstrap.py must be run with at least Python 3.5")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
if not (shutil.which('systemd') and shutil.which('systemctl')):
|
|
|
|
|
print("Systemd is required to run TLJH")
|
|
|
|
|
# Only fail running inside docker if systemd isn't present
|
|
|
|
|
if os.path.exists('/.dockerenv'):
|
2019-05-20 09:52:50 -07:00
|
|
|
print("Running inside a docker container without systemd isn't supported")
|
|
|
|
|
print("We recommend against running a production TLJH instance inside a docker container")
|
2019-05-19 22:44:49 -07:00
|
|
|
print("For local development, see http://tljh.jupyter.org/en/latest/contributing/dev-setup.html")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
validate_host()
|
2018-07-02 15:12:26 -07:00
|
|
|
install_prefix = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh')
|
|
|
|
|
hub_prefix = os.path.join(install_prefix, 'hub')
|
|
|
|
|
|
2018-07-29 02:17:12 -07:00
|
|
|
# Set up logging to print to a file and to stderr
|
|
|
|
|
os.makedirs(install_prefix, exist_ok=True)
|
2019-05-19 23:19:21 -07:00
|
|
|
file_logger_path = os.path.join(install_prefix, 'installer.log')
|
|
|
|
|
file_logger = logging.FileHandler(file_logger_path)
|
|
|
|
|
# installer.log should be readable only by root
|
|
|
|
|
os.chmod(file_logger_path, 0o500)
|
|
|
|
|
|
2018-07-29 02:17:12 -07:00
|
|
|
file_logger.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
|
2019-05-19 13:45:57 -07:00
|
|
|
file_logger.setLevel(logging.DEBUG)
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.addHandler(file_logger)
|
|
|
|
|
|
|
|
|
|
stderr_logger = logging.StreamHandler()
|
|
|
|
|
stderr_logger.setFormatter(logging.Formatter('%(message)s'))
|
2019-05-19 13:45:57 -07:00
|
|
|
stderr_logger.setLevel(logging.INFO)
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.addHandler(stderr_logger)
|
2019-05-19 13:45:57 -07:00
|
|
|
logger.setLevel(logging.DEBUG)
|
2018-07-29 02:17:12 -07:00
|
|
|
|
|
|
|
|
logger.info('Checking if TLJH is already installed...')
|
2018-07-19 17:30:09 -07:00
|
|
|
if os.path.exists(os.path.join(hub_prefix, 'bin', 'python3')):
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.info('TLJH already installed, upgrading...')
|
2018-07-02 15:12:26 -07:00
|
|
|
initial_setup = False
|
2018-07-19 17:30:09 -07:00
|
|
|
else:
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.info('Setting up hub environment')
|
2018-07-19 17:30:09 -07:00
|
|
|
initial_setup = True
|
2019-05-19 20:36:10 -07:00
|
|
|
# Install software-properties-common, so we can get add-apt-repository
|
|
|
|
|
# That helps us make sure the universe repository is enabled, since
|
|
|
|
|
# that's where the python3-pip package lives. In some very minimal base
|
|
|
|
|
# VM images, it looks like the universe repository is disabled by default,
|
|
|
|
|
# causing bootstrapping to fail.
|
2019-05-19 13:45:57 -07:00
|
|
|
run_subprocess(['apt-get', 'update', '--yes'])
|
|
|
|
|
run_subprocess(['apt-get', 'install', '--yes', 'software-properties-common'])
|
|
|
|
|
run_subprocess(['add-apt-repository', 'universe'])
|
2019-05-19 20:36:55 -07:00
|
|
|
|
2019-05-19 13:45:57 -07:00
|
|
|
run_subprocess(['apt-get', 'update', '--yes'])
|
|
|
|
|
run_subprocess(['apt-get', 'install', '--yes',
|
2019-05-19 20:36:55 -07:00
|
|
|
'python3',
|
|
|
|
|
'python3-venv',
|
2019-05-19 13:45:57 -07:00
|
|
|
'python3-pip',
|
|
|
|
|
'git'
|
|
|
|
|
])
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.info('Installed python & virtual environment')
|
2018-07-19 17:30:09 -07:00
|
|
|
os.makedirs(hub_prefix, exist_ok=True)
|
2019-05-19 13:45:57 -07:00
|
|
|
run_subprocess(['python3', '-m', 'venv', hub_prefix])
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.info('Set up hub virtual environment')
|
2018-07-02 15:12:26 -07:00
|
|
|
|
|
|
|
|
if initial_setup:
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.info('Setting up TLJH installer...')
|
2018-07-02 15:12:26 -07:00
|
|
|
else:
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.info('Upgrading TLJH installer...')
|
2018-07-02 15:12:26 -07:00
|
|
|
|
2018-07-19 17:30:09 -07:00
|
|
|
pip_flags = ['--upgrade']
|
|
|
|
|
if os.environ.get('TLJH_BOOTSTRAP_DEV', 'no') == 'yes':
|
|
|
|
|
pip_flags.append('--editable')
|
2018-07-03 16:18:32 -07:00
|
|
|
tljh_repo_path = os.environ.get(
|
|
|
|
|
'TLJH_BOOTSTRAP_PIP_SPEC',
|
2018-07-20 16:17:33 -07:00
|
|
|
'git+https://github.com/jupyterhub/the-littlest-jupyterhub.git'
|
2018-07-03 16:18:32 -07:00
|
|
|
)
|
|
|
|
|
|
2020-04-23 13:14:32 +03:00
|
|
|
# Upgrade pip
|
|
|
|
|
run_subprocess([
|
|
|
|
|
os.path.join(hub_prefix, 'bin', 'pip'),
|
|
|
|
|
'install',
|
|
|
|
|
'--upgrade',
|
|
|
|
|
'pip==20.0.*'
|
|
|
|
|
])
|
|
|
|
|
logger.info('Upgraded pip')
|
|
|
|
|
|
2019-05-19 13:45:57 -07:00
|
|
|
run_subprocess([
|
2018-07-19 17:30:09 -07:00
|
|
|
os.path.join(hub_prefix, 'bin', 'pip'),
|
|
|
|
|
'install'
|
2019-05-19 13:45:57 -07:00
|
|
|
] + pip_flags + [tljh_repo_path])
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.info('Setup tljh package')
|
2018-07-02 15:12:26 -07:00
|
|
|
|
2018-07-29 02:17:12 -07:00
|
|
|
logger.info('Starting TLJH installer...')
|
2018-07-03 16:18:32 -07:00
|
|
|
os.execv(
|
2018-07-02 15:12:26 -07:00
|
|
|
os.path.join(hub_prefix, 'bin', 'python3'),
|
2018-07-03 16:18:32 -07:00
|
|
|
[
|
|
|
|
|
os.path.join(hub_prefix, 'bin', 'python3'),
|
|
|
|
|
'-m',
|
|
|
|
|
'tljh.installer',
|
|
|
|
|
] + sys.argv[1:]
|
2018-07-02 15:12:26 -07:00
|
|
|
)
|
|
|
|
|
|
2018-07-02 15:55:53 -07:00
|
|
|
|
2018-07-02 15:12:26 -07:00
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|