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:
|
2021-10-18 00:01:55 +02:00
|
|
|
|
2024-09-20 21:43:55 +02:00
|
|
|
- The entire script should be compatible with Python 3.9, which is the default on
|
|
|
|
|
Debian 11.
|
|
|
|
|
- The script should parse in Python 3.8 as we print error messages for using
|
|
|
|
|
Ubuntu 20.04 which comes with Python 3.8 by default.
|
2021-10-18 00:01:55 +02:00
|
|
|
- The script must depend only on stdlib modules, as no previous installation
|
|
|
|
|
of dependencies can be assumed.
|
|
|
|
|
|
|
|
|
|
Environment variables:
|
|
|
|
|
|
|
|
|
|
TLJH_INSTALL_PREFIX Defaults to "/opt/tljh", determines the location
|
|
|
|
|
of the tljh installations root folder.
|
|
|
|
|
TLJH_BOOTSTRAP_PIP_SPEC From this location, the bootstrap script will
|
|
|
|
|
pip install --upgrade the tljh installer.
|
|
|
|
|
TLJH_BOOTSTRAP_DEV Determines if --editable is passed when
|
|
|
|
|
installing the tljh installer. Pass the values
|
|
|
|
|
yes or no.
|
|
|
|
|
|
2023-04-15 10:27:39 +02:00
|
|
|
Command line flags, from "bootstrap.py --help":
|
2021-10-18 00:01:55 +02:00
|
|
|
|
|
|
|
|
The bootstrap.py script accept the following command line flags. All other
|
|
|
|
|
flags are passed through to the tljh installer without interception by this
|
|
|
|
|
script.
|
|
|
|
|
|
|
|
|
|
--show-progress-page Starts a local web server listening on port 80 where
|
|
|
|
|
logs can be accessed during installation. If this is
|
|
|
|
|
passed, it will pass --progress-page-server-pid=<pid>
|
|
|
|
|
to the tljh installer for later termination.
|
2023-06-06 14:21:09 +02:00
|
|
|
--version VERSION TLJH version or Git reference. Default 'latest' is
|
2023-04-15 10:27:39 +02:00
|
|
|
the most recent release. Partial versions can be
|
|
|
|
|
specified, for example '1', '1.0' or '1.0.0'. You
|
|
|
|
|
can also pass a branch name such as 'main' or a
|
|
|
|
|
commit hash.
|
2018-07-02 15:12:26 -07:00
|
|
|
"""
|
2024-02-05 20:29:32 +00:00
|
|
|
|
2023-05-15 08:51:35 +00:00
|
|
|
import logging
|
2020-07-30 22:59:56 +03:00
|
|
|
import multiprocessing
|
2023-05-15 08:51:35 +00:00
|
|
|
import os
|
2022-06-16 23:32:35 +01:00
|
|
|
import re
|
2023-05-15 08:51:35 +00:00
|
|
|
import shutil
|
2018-07-02 15:12:26 -07:00
|
|
|
import subprocess
|
2018-07-03 16:18:32 -07:00
|
|
|
import sys
|
2020-07-30 22:59:56 +03:00
|
|
|
import urllib.request
|
2023-05-15 08:51:35 +00:00
|
|
|
from argparse import ArgumentParser
|
|
|
|
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
2018-10-30 20:08:47 -07:00
|
|
|
|
2022-04-27 15:41:20 +03:00
|
|
|
progress_page_favicon_url = "https://raw.githubusercontent.com/jupyterhub/jupyterhub/main/share/jupyterhub/static/favicon.ico"
|
2021-10-18 00:01:55 +02:00
|
|
|
progress_page_html = """
|
2020-08-17 15:36:02 +03:00
|
|
|
<html>
|
|
|
|
|
<head>
|
2021-10-18 00:01:55 +02:00
|
|
|
<title>The Littlest Jupyterhub</title>
|
2020-08-17 15:36:02 +03:00
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<meta http-equiv="refresh" content="30" >
|
|
|
|
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width">
|
2022-11-28 09:18:31 +01:00
|
|
|
<img class="logo" src="https://raw.githubusercontent.com/jupyterhub/the-littlest-jupyterhub/HEAD/docs/_static/images/logo/logo.png">
|
2020-08-17 15:36:02 +03:00
|
|
|
<div class="loader center"></div>
|
2021-10-18 00:01:55 +02:00
|
|
|
<div class="center main-msg">Please wait while your TLJH is setting up...</div>
|
2020-08-17 15:36:02 +03:00
|
|
|
<div class="center logs-msg">Click the button below to see the logs</div>
|
|
|
|
|
<div class="center tip" >Tip: to update the logs, refresh the page</div>
|
|
|
|
|
<button class="logs-button center" onclick="window.location.href='/logs'">View logs</button>
|
|
|
|
|
</body>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
button:hover {
|
|
|
|
|
background: grey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logo {
|
|
|
|
|
width: 150px;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
.center {
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
margin-top: 50px;
|
|
|
|
|
text-align:center;
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
.main-msg {
|
|
|
|
|
font-size: 30px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: grey;
|
|
|
|
|
text-align:center;
|
|
|
|
|
}
|
|
|
|
|
.logs-msg {
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
color: grey;
|
|
|
|
|
}
|
|
|
|
|
.tip {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: grey;
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
font-style: italic;
|
|
|
|
|
}
|
|
|
|
|
.logs-button {
|
|
|
|
|
margin-top:15px;
|
|
|
|
|
border: 0;
|
|
|
|
|
color: white;
|
|
|
|
|
padding: 15px 32px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
background: #f5a252;
|
|
|
|
|
}
|
|
|
|
|
.loader {
|
|
|
|
|
width: 150px;
|
|
|
|
|
height: 150px;
|
|
|
|
|
border-radius: 90%;
|
|
|
|
|
border: 7px solid transparent;
|
|
|
|
|
animation: spin 2s infinite ease;
|
|
|
|
|
animation-direction: alternate;
|
|
|
|
|
}
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
0% {
|
|
|
|
|
transform: rotateZ(0deg);
|
|
|
|
|
border-top-color: #f17c0e
|
|
|
|
|
}
|
|
|
|
|
100% {
|
|
|
|
|
transform: rotateZ(360deg);
|
|
|
|
|
border-top-color: #fce5cf;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
2020-10-19 11:56:56 +02:00
|
|
|
</html>
|
2020-08-17 15:36:02 +03:00
|
|
|
"""
|
|
|
|
|
|
2019-05-19 13:45:57 -07:00
|
|
|
logger = logging.getLogger(__name__)
|
2018-11-13 12:47:05 -08:00
|
|
|
|
2023-03-21 10:15:22 +01:00
|
|
|
|
|
|
|
|
def _parse_version(vs):
|
|
|
|
|
"""Parse a simple version into a tuple of ints"""
|
2023-03-21 10:45:54 +01:00
|
|
|
return tuple(int(part) for part in vs.split("."))
|
2023-03-21 10:15:22 +01:00
|
|
|
|
|
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
# This function is needed both by the process starting this script, and by the
|
|
|
|
|
# TLJH installer that this script execs in the end. Make sure its replica at
|
|
|
|
|
# tljh/utils.py stays in sync with this version!
|
2019-05-19 13:45:57 -07:00
|
|
|
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
|
|
|
|
|
"""
|
2021-11-03 23:55:34 +01:00
|
|
|
logger = logging.getLogger("tljh")
|
2021-11-01 09:42:45 +01:00
|
|
|
proc = subprocess.run(
|
|
|
|
|
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *args, **kwargs
|
|
|
|
|
)
|
2021-11-03 23:55:34 +01:00
|
|
|
printable_command = " ".join(cmd)
|
2019-05-19 13:45:57 -07:00
|
|
|
if proc.returncode != 0:
|
|
|
|
|
# Our process failed! Show output to the user
|
2021-11-01 09:42:45 +01:00
|
|
|
logger.error(
|
2021-11-03 23:55:34 +01:00
|
|
|
"Ran {command} with exit code {code}".format(
|
2021-11-01 09:42:45 +01:00
|
|
|
command=printable_command, code=proc.returncode
|
|
|
|
|
)
|
|
|
|
|
)
|
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
|
2021-11-01 09:42:45 +01:00
|
|
|
logger.debug(
|
2021-11-03 23:55:34 +01:00
|
|
|
"Ran {command} with exit code {code}".format(
|
2021-11-01 09:42:45 +01:00
|
|
|
command=printable_command, code=proc.returncode
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-06-16 23:32:35 +01:00
|
|
|
output = proc.stdout.decode()
|
2019-05-19 13:45:57 -07:00
|
|
|
# This produces multi line log output, unfortunately. Not sure how to fix.
|
|
|
|
|
# For now, prioritizing human readability over machine readability.
|
2022-06-16 23:32:35 +01:00
|
|
|
logger.debug(output)
|
|
|
|
|
return output
|
2019-05-19 13:45:57 -07:00
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
|
2023-06-06 23:29:53 +02: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",
|
|
|
|
|
"source /etc/os-release && echo ${{{key}}}".format(key=key),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
.decode()
|
|
|
|
|
.strip()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
def ensure_host_system_can_install_tljh():
|
2019-05-19 22:44:49 -07:00
|
|
|
"""
|
2021-10-18 00:01:55 +02:00
|
|
|
Check if TLJH is installable in current host system and exit with a clear
|
|
|
|
|
error message otherwise.
|
2019-05-19 22:44:49 -07:00
|
|
|
"""
|
2024-09-20 21:43:55 +02:00
|
|
|
# Require Ubuntu 22.04+ or Debian 11+
|
2021-11-03 23:55:34 +01:00
|
|
|
distro = get_os_release_variable("ID")
|
2023-03-21 10:39:40 +01:00
|
|
|
version = get_os_release_variable("VERSION_ID")
|
2022-02-20 10:44:24 +01:00
|
|
|
if distro not in ["ubuntu", "debian"]:
|
2022-02-20 10:33:23 +01:00
|
|
|
print("The Littlest JupyterHub currently supports Ubuntu or Debian Linux only")
|
2018-11-13 12:47:05 -08:00
|
|
|
sys.exit(1)
|
2024-09-20 21:43:55 +02:00
|
|
|
elif distro == "ubuntu" and _parse_version(version) < (22, 4):
|
|
|
|
|
print("The Littlest JupyterHub requires Ubuntu 22.04 or higher")
|
2018-11-13 12:47:05 -08:00
|
|
|
sys.exit(1)
|
2023-03-21 10:45:54 +01:00
|
|
|
elif distro == "debian" and _parse_version(version) < (11,):
|
2023-03-22 14:01:33 +01:00
|
|
|
print("The Littlest JupyterHub requires Debian 11 or higher")
|
2022-02-20 10:33:23 +01:00
|
|
|
sys.exit(1)
|
2018-11-13 12:47:05 -08:00
|
|
|
|
2024-09-20 21:43:55 +02:00
|
|
|
# Require Python 3.9+
|
|
|
|
|
if sys.version_info < (3, 9):
|
|
|
|
|
print(f"bootstrap.py must be run with at least Python 3.9, found {sys.version}")
|
2021-10-18 19:26:40 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
# Require systemd (systemctl is a part of systemd)
|
2021-11-03 23:55:34 +01:00
|
|
|
if not shutil.which("systemd") or not shutil.which("systemctl"):
|
2019-05-19 22:44:49 -07:00
|
|
|
print("Systemd is required to run TLJH")
|
2021-10-18 00:01:55 +02:00
|
|
|
# Provide additional information about running in docker containers
|
2021-11-03 23:55:34 +01:00
|
|
|
if os.path.exists("/.dockerenv"):
|
2019-05-20 09:52:50 -07:00
|
|
|
print("Running inside a docker container without systemd isn't supported")
|
2021-11-01 09:42:45 +01:00
|
|
|
print(
|
|
|
|
|
"We recommend against running a production TLJH instance inside a docker container"
|
|
|
|
|
)
|
|
|
|
|
print(
|
|
|
|
|
"For local development, see http://tljh.jupyter.org/en/latest/contributing/dev-setup.html"
|
|
|
|
|
)
|
2019-05-19 22:44:49 -07:00
|
|
|
sys.exit(1)
|
2023-03-21 10:15:22 +01:00
|
|
|
return distro, version
|
2019-05-19 22:44:49 -07:00
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
|
|
|
|
|
class ProgressPageRequestHandler(SimpleHTTPRequestHandler):
|
2020-07-30 22:59:56 +03:00
|
|
|
def do_GET(self):
|
|
|
|
|
if self.path == "/logs":
|
2021-10-31 11:26:40 +01:00
|
|
|
with open("/opt/tljh/installer.log") as log_file:
|
2020-08-17 19:04:25 +03:00
|
|
|
logs = log_file.read()
|
|
|
|
|
|
|
|
|
|
self.send_response(200)
|
2021-11-03 23:55:34 +01:00
|
|
|
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
2020-08-17 19:04:25 +03:00
|
|
|
self.end_headers()
|
2021-11-03 23:55:34 +01:00
|
|
|
self.wfile.write(logs.encode("utf-8"))
|
2020-08-17 15:36:02 +03:00
|
|
|
elif self.path == "/index.html":
|
|
|
|
|
self.path = "/var/run/index.html"
|
|
|
|
|
return SimpleHTTPRequestHandler.do_GET(self)
|
|
|
|
|
elif self.path == "/favicon.ico":
|
|
|
|
|
self.path = "/var/run/favicon.ico"
|
2020-07-30 22:59:56 +03:00
|
|
|
return SimpleHTTPRequestHandler.do_GET(self)
|
2020-08-11 12:23:50 +03:00
|
|
|
elif self.path == "/":
|
2020-08-11 16:01:44 +03:00
|
|
|
self.send_response(302)
|
2021-11-03 23:55:34 +01:00
|
|
|
self.send_header("Location", "/index.html")
|
2020-08-11 12:23:50 +03:00
|
|
|
self.end_headers()
|
|
|
|
|
else:
|
|
|
|
|
SimpleHTTPRequestHandler.send_error(self, code=403)
|
2020-07-30 22:59:56 +03:00
|
|
|
|
|
|
|
|
|
2022-06-16 23:32:35 +01:00
|
|
|
def _find_matching_version(all_versions, requested):
|
|
|
|
|
"""
|
|
|
|
|
Find the latest version that is less than or equal to requested.
|
|
|
|
|
all_versions must be int-tuples.
|
|
|
|
|
requested must be an int-tuple or "latest"
|
|
|
|
|
|
|
|
|
|
Returns None if no version is found.
|
|
|
|
|
"""
|
|
|
|
|
sorted_versions = sorted(all_versions, reverse=True)
|
|
|
|
|
if requested == "latest":
|
|
|
|
|
return sorted_versions[0]
|
|
|
|
|
components = len(requested)
|
|
|
|
|
for v in sorted_versions:
|
|
|
|
|
if v[:components] == requested:
|
|
|
|
|
return v
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_git_version(version):
|
|
|
|
|
"""
|
|
|
|
|
Resolve the version argument to a git ref using git ls-remote
|
|
|
|
|
- If version looks like MAJOR.MINOR.PATCH or a partial tag then fetch all tags
|
|
|
|
|
and return the most latest tag matching MAJOR.MINOR.PATCH
|
|
|
|
|
(e.g. version=0.1 -> 0.1.PATCH). This should ignore dev tags
|
|
|
|
|
- If version='latest' then return the latest release tag
|
|
|
|
|
- Otherwise assume version is a branch or hash and return it without checking
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if version != "latest" and not re.match(r"\d+(\.\d+)?(\.\d+)?$", version):
|
|
|
|
|
return version
|
|
|
|
|
|
|
|
|
|
all_versions = set()
|
|
|
|
|
out = run_subprocess(
|
|
|
|
|
[
|
|
|
|
|
"git",
|
|
|
|
|
"ls-remote",
|
|
|
|
|
"--tags",
|
|
|
|
|
"--refs",
|
|
|
|
|
"https://github.com/jupyterhub/the-littlest-jupyterhub.git",
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for line in out.splitlines():
|
|
|
|
|
m = re.match(r"(?P<sha>[a-f0-9]+)\s+refs/tags/(?P<tag>[\S]+)$", line)
|
|
|
|
|
if not m:
|
|
|
|
|
raise Exception("Unexpected git ls-remote output: {}".format(line))
|
|
|
|
|
tag = m.group("tag")
|
|
|
|
|
if tag == version:
|
|
|
|
|
return tag
|
|
|
|
|
if re.match(r"\d+\.\d+\.\d+$", tag):
|
|
|
|
|
all_versions.add(tuple(int(v) for v in tag.split(".")))
|
|
|
|
|
|
|
|
|
|
if not all_versions:
|
|
|
|
|
raise Exception("No MAJOR.MINOR.PATCH git tags found")
|
|
|
|
|
|
|
|
|
|
if version == "latest":
|
|
|
|
|
requested = "latest"
|
|
|
|
|
else:
|
|
|
|
|
requested = tuple(int(v) for v in version.split("."))
|
|
|
|
|
found = _find_matching_version(all_versions, requested)
|
|
|
|
|
if not found:
|
|
|
|
|
raise Exception(
|
|
|
|
|
"No version matching {} found {}".format(version, sorted(all_versions))
|
|
|
|
|
)
|
|
|
|
|
return ".".join(str(f) for f in found)
|
|
|
|
|
|
|
|
|
|
|
2019-05-19 22:44:49 -07:00
|
|
|
def main():
|
2021-10-18 00:01:55 +02:00
|
|
|
"""
|
2022-06-16 20:54:54 +01:00
|
|
|
This bootstrap script intercepts some command line flags, everything else is
|
|
|
|
|
passed through to the TLJH installer script.
|
2021-10-18 00:01:55 +02:00
|
|
|
|
|
|
|
|
The --show-progress-page flag indicates that the bootstrap script should
|
|
|
|
|
start a local webserver temporarily and report its installation progress via
|
|
|
|
|
a web site served locally on port 80.
|
|
|
|
|
"""
|
2022-02-20 10:33:23 +01:00
|
|
|
distro, version = ensure_host_system_can_install_tljh()
|
2021-10-18 00:01:55 +02:00
|
|
|
|
2023-04-15 10:27:39 +02:00
|
|
|
parser = ArgumentParser(
|
|
|
|
|
description=(
|
|
|
|
|
"The bootstrap.py script accept the following command line flags. "
|
|
|
|
|
"All other flags are passed through to the tljh installer without "
|
|
|
|
|
"interception by this script."
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--show-progress-page",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help=(
|
|
|
|
|
"Starts a local web server listening on port 80 where logs can be "
|
|
|
|
|
"accessed during installation. If this is passed, it will pass "
|
|
|
|
|
"--progress-page-server-pid=<pid> to the tljh installer for later "
|
|
|
|
|
"termination."
|
|
|
|
|
),
|
|
|
|
|
)
|
2022-06-16 20:54:54 +01:00
|
|
|
parser.add_argument(
|
2022-11-27 00:04:00 +00:00
|
|
|
"--version",
|
2023-06-06 22:48:06 +02:00
|
|
|
default="",
|
2022-11-27 00:04:00 +00:00
|
|
|
help=(
|
|
|
|
|
"TLJH version or Git reference. "
|
|
|
|
|
"Default 'latest' is the most recent release. "
|
|
|
|
|
"Partial versions can be specified, for example '1', '1.0' or '1.0.0'. "
|
|
|
|
|
"You can also pass a branch name such as 'main' or a commit hash."
|
|
|
|
|
),
|
2022-06-16 20:54:54 +01:00
|
|
|
)
|
|
|
|
|
args, tljh_installer_flags = parser.parse_known_args()
|
|
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
# Various related constants
|
2021-11-03 23:55:34 +01:00
|
|
|
install_prefix = os.environ.get("TLJH_INSTALL_PREFIX", "/opt/tljh")
|
2023-04-15 08:50:20 +02:00
|
|
|
hub_env_prefix = os.path.join(install_prefix, "hub")
|
|
|
|
|
hub_env_python = os.path.join(hub_env_prefix, "bin", "python3")
|
|
|
|
|
hub_env_pip = os.path.join(hub_env_prefix, "bin", "pip")
|
|
|
|
|
initial_setup = not os.path.exists(hub_env_python)
|
2020-08-17 16:47:55 +03:00
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
# Attempt to start a web server to serve a progress page reporting
|
|
|
|
|
# installation progress.
|
2022-06-16 20:54:54 +01:00
|
|
|
if args.show_progress_page:
|
2021-10-18 00:01:55 +02:00
|
|
|
# Write HTML and a favicon to be served by our webserver
|
2020-08-17 15:36:02 +03:00
|
|
|
with open("/var/run/index.html", "w+") as f:
|
2021-10-18 00:01:55 +02:00
|
|
|
f.write(progress_page_html)
|
|
|
|
|
urllib.request.urlretrieve(progress_page_favicon_url, "/var/run/favicon.ico")
|
2020-08-11 14:09:11 +03:00
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
# If TLJH is already installed and Traefik is already running, port 80
|
|
|
|
|
# will be busy and we will get an "Address already in use" error. This
|
|
|
|
|
# is acceptable and we can ignore the error.
|
2020-08-11 14:09:11 +03:00
|
|
|
try:
|
2021-10-18 00:01:55 +02:00
|
|
|
# Serve the loading page until manually aborted or until the TLJH
|
|
|
|
|
# installer terminates the process
|
|
|
|
|
def serve_forever(server):
|
|
|
|
|
try:
|
|
|
|
|
server.serve_forever()
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
pass
|
2021-11-01 09:42:45 +01:00
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
progress_page_server = HTTPServer(("", 80), ProgressPageRequestHandler)
|
2021-11-01 09:42:45 +01:00
|
|
|
p = multiprocessing.Process(
|
|
|
|
|
target=serve_forever, args=(progress_page_server,)
|
|
|
|
|
)
|
2020-08-11 14:09:11 +03:00
|
|
|
p.start()
|
2020-08-17 19:04:25 +03:00
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
# Pass the server's pid to the installer for later termination
|
|
|
|
|
tljh_installer_flags.extend(["--progress-page-server-pid", str(p.pid)])
|
2020-08-11 14:09:11 +03:00
|
|
|
except OSError:
|
|
|
|
|
pass
|
2020-07-30 22:59:56 +03:00
|
|
|
|
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)
|
2021-11-03 23:55:34 +01:00
|
|
|
file_logger_path = os.path.join(install_prefix, "installer.log")
|
2019-05-19 23:19:21 -07:00
|
|
|
file_logger = logging.FileHandler(file_logger_path)
|
|
|
|
|
# installer.log should be readable only by root
|
|
|
|
|
os.chmod(file_logger_path, 0o500)
|
|
|
|
|
|
2021-11-03 23:55:34 +01: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()
|
2021-11-03 23:55:34 +01:00
|
|
|
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)
|
2021-10-18 00:01:55 +02:00
|
|
|
|
2019-05-19 13:45:57 -07:00
|
|
|
logger.setLevel(logging.DEBUG)
|
2018-07-29 02:17:12 -07:00
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
if not initial_setup:
|
2021-11-03 23:55:34 +01:00
|
|
|
logger.info("Existing TLJH installation detected, upgrading...")
|
2018-07-19 17:30:09 -07:00
|
|
|
else:
|
2021-11-03 23:55:34 +01:00
|
|
|
logger.info("Existing TLJH installation not detected, installing...")
|
|
|
|
|
logger.info("Setting up hub environment...")
|
|
|
|
|
logger.info("Installing Python, venv, pip, and git via apt-get...")
|
2019-05-19 20:36:55 -07:00
|
|
|
|
2021-10-19 15:18:02 +02:00
|
|
|
# In some very minimal base VM images, it looks like the "universe" apt
|
|
|
|
|
# package repository is disabled by default, causing bootstrapping to
|
|
|
|
|
# fail. We install the software-properties-common package so we can get
|
|
|
|
|
# the add-apt-repository command to make sure the universe repository is
|
|
|
|
|
# enabled, since that's where the python3-pip package lives.
|
|
|
|
|
#
|
|
|
|
|
# In Ubuntu 21.10 DEBIAN_FRONTEND has found to be needed to avoid
|
|
|
|
|
# getting stuck on an input prompt during apt-get install.
|
|
|
|
|
#
|
|
|
|
|
apt_get_adjusted_env = os.environ.copy()
|
|
|
|
|
apt_get_adjusted_env["DEBIAN_FRONTEND"] = "noninteractive"
|
2021-11-03 23:55:34 +01:00
|
|
|
run_subprocess(["apt-get", "update"])
|
2021-11-01 09:42:45 +01:00
|
|
|
run_subprocess(
|
2021-11-03 23:55:34 +01:00
|
|
|
["apt-get", "install", "--yes", "software-properties-common"],
|
2021-11-01 09:42:45 +01:00
|
|
|
env=apt_get_adjusted_env,
|
|
|
|
|
)
|
2022-09-15 17:34:25 +02:00
|
|
|
# Section "universe" exists and is required only in ubuntu.
|
2022-02-20 10:41:36 +01:00
|
|
|
if distro == "ubuntu":
|
2022-02-20 10:33:23 +01:00
|
|
|
run_subprocess(["add-apt-repository", "universe", "--yes"])
|
2021-11-03 23:55:34 +01:00
|
|
|
run_subprocess(["apt-get", "update"])
|
2021-11-01 09:42:45 +01:00
|
|
|
run_subprocess(
|
|
|
|
|
[
|
2021-11-03 23:55:34 +01:00
|
|
|
"apt-get",
|
|
|
|
|
"install",
|
|
|
|
|
"--yes",
|
|
|
|
|
"python3",
|
|
|
|
|
"python3-venv",
|
|
|
|
|
"python3-pip",
|
|
|
|
|
"git",
|
2023-03-21 10:15:22 +01:00
|
|
|
"sudo", # sudo is missing in default debian install
|
2021-11-01 09:42:45 +01:00
|
|
|
],
|
|
|
|
|
env=apt_get_adjusted_env,
|
|
|
|
|
)
|
2021-10-18 00:01:55 +02:00
|
|
|
|
2023-04-15 08:50:20 +02:00
|
|
|
logger.info("Setting up virtual environment at {}".format(hub_env_prefix))
|
|
|
|
|
os.makedirs(hub_env_prefix, exist_ok=True)
|
|
|
|
|
run_subprocess(["python3", "-m", "venv", hub_env_prefix])
|
2018-07-02 15:12:26 -07:00
|
|
|
|
2021-11-03 23:55:34 +01:00
|
|
|
logger.info("Upgrading pip...")
|
2023-05-18 23:49:39 +02:00
|
|
|
run_subprocess([hub_env_pip, "install", "--upgrade", "pip"])
|
2021-10-18 00:01:55 +02:00
|
|
|
|
2023-06-06 22:48:06 +02:00
|
|
|
# pip install TLJH installer based on
|
|
|
|
|
#
|
|
|
|
|
# 1. --version, _resolve_git_version is used
|
|
|
|
|
# 2. TLJH_BOOTSTRAP_PIP_SPEC (then also respect TLJH_BOOTSTRAP_DEV)
|
|
|
|
|
# 3. latest, _resolve_git_version is used
|
|
|
|
|
#
|
2023-04-15 08:50:20 +02:00
|
|
|
tljh_install_cmd = [hub_env_pip, "install", "--upgrade"]
|
2022-11-27 19:03:48 +00:00
|
|
|
bootstrap_pip_spec = os.environ.get("TLJH_BOOTSTRAP_PIP_SPEC")
|
2023-06-06 22:48:06 +02:00
|
|
|
if args.version or not bootstrap_pip_spec:
|
|
|
|
|
version_to_resolve = args.version or "latest"
|
2022-11-27 19:03:48 +00:00
|
|
|
bootstrap_pip_spec = (
|
2022-06-16 23:32:35 +01:00
|
|
|
"git+https://github.com/jupyterhub/the-littlest-jupyterhub.git@{}".format(
|
2023-06-06 22:48:06 +02:00
|
|
|
_resolve_git_version(version_to_resolve)
|
2022-11-27 19:03:48 +00:00
|
|
|
)
|
2021-10-18 00:01:55 +02:00
|
|
|
)
|
2023-06-06 22:48:06 +02:00
|
|
|
elif os.environ.get("TLJH_BOOTSTRAP_DEV", "no") == "yes":
|
|
|
|
|
logger.info("Selected TLJH_BOOTSTRAP_DEV=yes...")
|
|
|
|
|
tljh_install_cmd.append("--editable")
|
2022-11-27 19:03:48 +00:00
|
|
|
tljh_install_cmd.append(bootstrap_pip_spec)
|
2023-06-06 22:48:06 +02:00
|
|
|
|
2018-07-02 15:12:26 -07:00
|
|
|
if initial_setup:
|
2021-11-03 23:55:34 +01:00
|
|
|
logger.info("Installing TLJH installer...")
|
2018-07-02 15:12:26 -07:00
|
|
|
else:
|
2021-11-03 23:55:34 +01:00
|
|
|
logger.info("Upgrading TLJH installer...")
|
2021-10-18 00:01:55 +02:00
|
|
|
run_subprocess(tljh_install_cmd)
|
2018-07-02 15:12:26 -07:00
|
|
|
|
2021-10-18 00:01:55 +02:00
|
|
|
# Run TLJH installer
|
2021-11-03 23:55:34 +01:00
|
|
|
logger.info("Running TLJH installer...")
|
2023-04-15 08:50:20 +02:00
|
|
|
os.execv(
|
|
|
|
|
hub_env_python, [hub_env_python, "-m", "tljh.installer"] + tljh_installer_flags
|
|
|
|
|
)
|
2021-10-18 00:01:55 +02:00
|
|
|
|
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()
|