unsloth/docker/studio_launch.sh
Daniel Han 81b0d1ef10 docker: second review pass fixes
- Dockerfile: lift numba past vllm's 0.61.2 pin after the numpy>=2.4
  re-upgrade; 0.61.2 refuses numpy 2.3+ at import time and the stack
  cannot move numpy down. Verified numba 0.65 + numpy 2.4.6 + vllm
  import cleanly together.
- docker-publish.yml: resolve UNSLOTH_ZOO_REF in a step that mirrors
  the pushed tag only when the tag exists in unsloth-zoo (the zoo
  currently cuts no tags, so blind mirroring broke every tag publish);
  falls back to main.
- Dockerfile.studio: Studio venv stays on cu128 for arm64 too, matching
  the base venv (cu130 wheels would lift the driver floor to 580+), and
  gets the same NVRTC cu13 swap for DGX Spark / GB10 sm_121 support.
- docker_confirm.sh: do not drop to CPU mode when docker info lacks a
  nvidia runtime entry; CDI installs and Docker Desktop WSL2 expose
  GPUs without one. The phase 3 --gpus probe is now the authority.
- docker_confirm.ps1: GPU selector built as an args array; comma device
  lists get version-aware CSV quoting (native arg passing changed in
  PowerShell 7.3).
- studio_launch.sh: no fixed Jupyter default password; generate a
  random one and print it when JUPYTER_PASSWORD is unset. Env snapshot
  for SSH sessions now written via shlex.quote instead of sed so
  values with quotes or command substitution cannot break or inject
  into /etc/profile.d.
- install.ps1: honour UNSLOTH_TORCH_INDEX_FAMILY like install.sh does.
2026-06-12 05:59:52 +00:00

86 lines
3.7 KiB
Bash

#!/usr/bin/env bash
# Default CMD of the full Unsloth image (Dockerfile.studio).
#
# Bootstraps the three services managed by supervisord:
# studio port 8000 first-boot admin password printed in `docker logs`
# jupyter port 8888 password from JUPYTER_PASSWORD, or a random one
# printed in `docker logs` when unset
# sshd port 22 key-only; enabled when PUBLIC_KEY / SSH_KEY is set
#
# Environment:
# JUPYTER_PORT Jupyter port inside the container (default 8888)
# JUPYTER_PASSWORD Jupyter login password (unset: generated and printed)
# PUBLIC_KEY/SSH_KEY OpenSSH public key for root login; sshd stays disabled
# when neither is set (nothing to authenticate with --
# password login is never enabled for root)
set -euo pipefail
export JUPYTER_PORT="${JUPYTER_PORT:-8888}"
export UNSLOTH_STUDIO_HOME="${UNSLOTH_STUDIO_HOME:-/opt/unsloth-studio}"
# Make the runtime env visible to SSH sessions, which get a fresh login shell
# without the `docker run -e` vars. Secrets are excluded on purpose: tokens,
# API keys and passwords stay in process env only, never on disk where an
# SSH session (or anything reading /etc/profile.d) could pick them up.
# shlex.quote() each value: env vars can contain quotes, $, backticks etc,
# and this file is sourced by every login shell.
python - > /etc/profile.d/unsloth_env.sh <<'PY' || true
import os, re, shlex
keep = re.compile(r"^(HF_|CUDA_|NCCL_|JUPYTER_|UNSLOTH_|WANDB_|TRITON_)|^PATH$")
secret = re.compile(r"(_TOKEN|_API_KEY|_PASSWORD|_SECRET|_LICENSE)$")
for key, value in sorted(os.environ.items()):
if keep.search(key) and not secret.search(key):
print(f"export {key}={shlex.quote(value)}")
PY
# --- Jupyter -----------------------------------------------------------------
# Hash the password with jupyter's own helper; never store the plaintext.
# No fixed default password: when JUPYTER_PASSWORD is unset we generate a
# random one and print it once in the boot banner (docker logs).
JUPYTER_CONFIG_DIR=/root/.jupyter
JUPYTER_NOTE="password from JUPYTER_PASSWORD env"
if [[ -f "${JUPYTER_CONFIG_DIR}/jupyter_lab_config.py" ]]; then
JUPYTER_NOTE="existing jupyter config reused"
else
if [[ -z "${JUPYTER_PASSWORD:-}" ]]; then
JUPYTER_PASSWORD="$(python -c 'import secrets; print(secrets.token_urlsafe(12))')"
JUPYTER_NOTE="generated password: ${JUPYTER_PASSWORD}"
fi
export JUPYTER_PASSWORD
mkdir -p "${JUPYTER_CONFIG_DIR}"
HASH=$(python - <<PY
from jupyter_server.auth import passwd
import os
print(passwd(os.environ["JUPYTER_PASSWORD"]))
PY
)
cat > "${JUPYTER_CONFIG_DIR}/jupyter_lab_config.py" <<EOF
c.ServerApp.ip = "0.0.0.0"
c.ServerApp.open_browser = False
c.ServerApp.root_dir = "/workspace"
c.PasswordIdentityProvider.hashed_password = "${HASH}"
EOF
fi
# --- sshd (opt-in) -----------------------------------------------------------
# Enabled only when a public key is provided; root password login is never
# allowed. Cloud GPU platforms (e.g. runpod-style hosts) inject PUBLIC_KEY.
PUBLIC_SSH_KEY="${SSH_KEY:-${PUBLIC_KEY:-}}"
export UNSLOTH_ENABLE_SSHD=false
if [[ -n "${PUBLIC_SSH_KEY}" ]] && command -v sshd >/dev/null 2>&1; then
mkdir -p /root/.ssh && chmod 700 /root/.ssh
echo "${PUBLIC_SSH_KEY}" > /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
ssh-keygen -A
mkdir -p /run/sshd
export UNSLOTH_ENABLE_SSHD=true
fi
mkdir -p /workspace
echo "Unsloth Studio -> http://localhost:8000 (first-boot password below)"
echo "JupyterLab -> http://localhost:${JUPYTER_PORT} (${JUPYTER_NOTE})"
if [[ "${UNSLOTH_ENABLE_SSHD}" == "true" ]]; then
echo "sshd -> port 22 (key-only)"
fi
exec supervisord -c /etc/supervisor/supervisord.conf