Spaces:
Paused
Paused
Anurag commited on
Commit ·
1f050ab
1
Parent(s): 7106068
Preserve venv pip behavior with Jupyter defaults
Browse files- Dockerfile +12 -11
- health-server.js +17 -1
- jupyter-devdata-sync.py +3 -2
- start.sh +28 -1
Dockerfile
CHANGED
|
@@ -34,6 +34,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 34 |
procps \
|
| 35 |
python3 \
|
| 36 |
python3-pip \
|
|
|
|
| 37 |
p7zip-full \
|
| 38 |
chromium \
|
| 39 |
libnss3 \
|
|
@@ -60,16 +61,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 60 |
pip3 install --no-cache-dir --break-system-packages huggingface_hub hf_transfer && \
|
| 61 |
rm -rf /var/lib/apt/lists/*
|
| 62 |
|
| 63 |
-
# Install JupyterLab
|
| 64 |
-
#
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
python3 -c "from pathlib import Path; import shutil, jupyter_server; d=Path(jupyter_server.__file__).parent/'templates'; d.mkdir(parents=True,exist_ok=True); shutil.copyfile('/home/node/app/login.html', d/'login.html')" || true; \
|
| 72 |
-
fi
|
| 73 |
|
| 74 |
# Reuse existing node user (UID 1000). Allow passwordless package-manager
|
| 75 |
# commands only so runtime apt installs can be replayed after HF Space restarts.
|
|
@@ -110,7 +109,7 @@ COPY --chown=1000:1000 env-builder.html /home/node/app/env-builder.html
|
|
| 110 |
COPY --chown=1000:1000 env-builder.js /home/node/app/env-builder.js
|
| 111 |
COPY --chown=1000:1000 key-rotator-manager.html /home/node/app/key-rotator-manager.html
|
| 112 |
COPY --chown=1000:1000 jupyter-devdata-sync.py /home/node/app/jupyter-devdata-sync.py
|
| 113 |
-
|
| 114 |
RUN chmod +x /home/node/app/start.sh \
|
| 115 |
/home/node/app/cloudflare-proxy-setup.py \
|
| 116 |
/home/node/app/cloudflare-keepalive-setup.py \
|
|
@@ -122,6 +121,8 @@ USER node
|
|
| 122 |
|
| 123 |
ENV HOME=/home/node \
|
| 124 |
OPENCLAW_VERSION=${OPENCLAW_VERSION} \
|
|
|
|
|
|
|
| 125 |
PATH=/home/node/.local/bin:/usr/local/bin:$PATH \
|
| 126 |
NODE_PATH=/home/node/browser-deps/node_modules \
|
| 127 |
NODE_OPTIONS="--require /opt/cloudflare-proxy.js"
|
|
|
|
| 34 |
procps \
|
| 35 |
python3 \
|
| 36 |
python3-pip \
|
| 37 |
+
python3-venv \
|
| 38 |
p7zip-full \
|
| 39 |
chromium \
|
| 40 |
libnss3 \
|
|
|
|
| 61 |
pip3 install --no-cache-dir --break-system-packages huggingface_hub hf_transfer && \
|
| 62 |
rm -rf /var/lib/apt/lists/*
|
| 63 |
|
| 64 |
+
# Install JupyterLab at build time because start.sh can auto-enable the
|
| 65 |
+
# terminal at runtime when GATEWAY_TOKEN is present. If the dependency is only
|
| 66 |
+
# installed when the build arg DEV_MODE=true, the documented default path
|
| 67 |
+
# starts a terminal process that cannot import jupyterlab.
|
| 68 |
+
RUN pip3 install --no-cache-dir --break-system-packages \
|
| 69 |
+
jupyterlab==4.5.7 \
|
| 70 |
+
tornado==6.5.5 \
|
| 71 |
+
ipywidgets==8.1.8
|
|
|
|
|
|
|
| 72 |
|
| 73 |
# Reuse existing node user (UID 1000). Allow passwordless package-manager
|
| 74 |
# commands only so runtime apt installs can be replayed after HF Space restarts.
|
|
|
|
| 109 |
COPY --chown=1000:1000 env-builder.js /home/node/app/env-builder.js
|
| 110 |
COPY --chown=1000:1000 key-rotator-manager.html /home/node/app/key-rotator-manager.html
|
| 111 |
COPY --chown=1000:1000 jupyter-devdata-sync.py /home/node/app/jupyter-devdata-sync.py
|
| 112 |
+
RUN python3 -c "from pathlib import Path; import shutil, jupyter_server; d=Path(jupyter_server.__file__).parent/'templates'; d.mkdir(parents=True,exist_ok=True); shutil.copyfile('/home/node/app/login.html', d/'login.html')"
|
| 113 |
RUN chmod +x /home/node/app/start.sh \
|
| 114 |
/home/node/app/cloudflare-proxy-setup.py \
|
| 115 |
/home/node/app/cloudflare-keepalive-setup.py \
|
|
|
|
| 121 |
|
| 122 |
ENV HOME=/home/node \
|
| 123 |
OPENCLAW_VERSION=${OPENCLAW_VERSION} \
|
| 124 |
+
PIP_BREAK_SYSTEM_PACKAGES=1 \
|
| 125 |
+
PYTHONUSERBASE=/home/node/.local \
|
| 126 |
PATH=/home/node/.local/bin:/usr/local/bin:$PATH \
|
| 127 |
NODE_PATH=/home/node/browser-deps/node_modules \
|
| 128 |
NODE_OPTIONS="--require /opt/cloudflare-proxy.js"
|
health-server.js
CHANGED
|
@@ -263,7 +263,22 @@ function escapeHtml(v) {
|
|
| 263 |
|
| 264 |
function parseCookies(req) {
|
| 265 |
const h = req.headers.cookie || "";
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
}
|
| 268 |
|
| 269 |
// Constant-time comparison using crypto — prevent timing attacks
|
|
@@ -1119,6 +1134,7 @@ server.on("upgrade", (req, socket, head) => {
|
|
| 1119 |
const header = req.rawHeaders[i];
|
| 1120 |
const lower = header.toLowerCase();
|
| 1121 |
if (["host", "x-forwarded-for", "x-forwarded-host", "x-forwarded-proto", "x-forwarded-prefix"].includes(lower)) continue;
|
|
|
|
| 1122 |
ps.write(`${header}: ${req.rawHeaders[i + 1]}\r\n`);
|
| 1123 |
}
|
| 1124 |
ps.write("\r\n");
|
|
|
|
| 263 |
|
| 264 |
function parseCookies(req) {
|
| 265 |
const h = req.headers.cookie || "";
|
| 266 |
+
const cookies = {};
|
| 267 |
+
for (const rawCookie of h.split(";")) {
|
| 268 |
+
const parts = rawCookie.trim().split("=");
|
| 269 |
+
if (parts.length < 2) continue;
|
| 270 |
+
const key = parts.shift().trim();
|
| 271 |
+
if (!key) continue;
|
| 272 |
+
const rawValue = parts.join("=").trim();
|
| 273 |
+
try {
|
| 274 |
+
cookies[key] = decodeURIComponent(rawValue);
|
| 275 |
+
} catch {
|
| 276 |
+
// Browsers and crawlers can send malformed percent-encoded cookie values.
|
| 277 |
+
// Treat that single cookie as unusable instead of letting URIError crash
|
| 278 |
+
// the dashboard/auth reverse proxy.
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
return cookies;
|
| 282 |
}
|
| 283 |
|
| 284 |
// Constant-time comparison using crypto — prevent timing attacks
|
|
|
|
| 1134 |
const header = req.rawHeaders[i];
|
| 1135 |
const lower = header.toLowerCase();
|
| 1136 |
if (["host", "x-forwarded-for", "x-forwarded-host", "x-forwarded-proto", "x-forwarded-prefix"].includes(lower)) continue;
|
| 1137 |
+
if (lower === "authorization" && isApp && bridgeGatewayAuth && GATEWAY_TOKEN) continue;
|
| 1138 |
ps.write(`${header}: ${req.rawHeaders[i + 1]}\r\n`);
|
| 1139 |
}
|
| 1140 |
ps.write("\r\n");
|
jupyter-devdata-sync.py
CHANGED
|
@@ -9,6 +9,7 @@ HF_USERNAME = os.environ.get("HF_USERNAME", "").strip() or os.environ.get("SPACE
|
|
| 9 |
DATASET_NAME = os.environ.get("DEVDATA_DATASET_NAME", "").strip() or "huggingclaw-devdata"
|
| 10 |
BACKUP_DATASET_NAME = os.environ.get("BACKUP_DATASET_NAME", "").strip() or os.environ.get("BACKUP_DATASET", "").strip() or "huggingclaw-backup"
|
| 11 |
JUPYTER_ROOT = Path(os.environ.get("JUPYTER_ROOT_DIR", "/home/node")).resolve()
|
|
|
|
| 12 |
INTERVAL = int((os.environ.get("DEVDATA_SYNC_INTERVAL", "").strip() or "180"))
|
| 13 |
# BUG FIX #5: Respect max file size so giant files don't stall uploads.
|
| 14 |
# Matches the 50 MB ceiling in openclaw-sync.py; override with DEVDATA_MAX_FILE_BYTES.
|
|
@@ -438,7 +439,7 @@ if __name__ == "__main__":
|
|
| 438 |
|
| 439 |
# Normal background sync mode — no restore; go straight to upload loop.
|
| 440 |
validate_jupyter_paths()
|
| 441 |
-
if is_jupyter_running():
|
| 442 |
print("DevData: background sync started (JupyterLab is live, restore already done by --restore).")
|
| 443 |
else:
|
| 444 |
# Fallback: JupyterLab not detected. Should not normally happen
|
|
@@ -446,6 +447,6 @@ if __name__ == "__main__":
|
|
| 446 |
# waits for the gateway before launching this background process.
|
| 447 |
# Log a warning and proceed to sync; do NOT restore to avoid racing
|
| 448 |
# with a JupyterLab that may be in the middle of starting up.
|
| 449 |
-
print("DevData: WARNING — JupyterLab not detected on port
|
| 450 |
|
| 451 |
sync_loop(api, rid)
|
|
|
|
| 9 |
DATASET_NAME = os.environ.get("DEVDATA_DATASET_NAME", "").strip() or "huggingclaw-devdata"
|
| 10 |
BACKUP_DATASET_NAME = os.environ.get("BACKUP_DATASET_NAME", "").strip() or os.environ.get("BACKUP_DATASET", "").strip() or "huggingclaw-backup"
|
| 11 |
JUPYTER_ROOT = Path(os.environ.get("JUPYTER_ROOT_DIR", "/home/node")).resolve()
|
| 12 |
+
JUPYTER_PORT = int((os.environ.get("JUPYTER_PORT", "").strip() or "8888"))
|
| 13 |
INTERVAL = int((os.environ.get("DEVDATA_SYNC_INTERVAL", "").strip() or "180"))
|
| 14 |
# BUG FIX #5: Respect max file size so giant files don't stall uploads.
|
| 15 |
# Matches the 50 MB ceiling in openclaw-sync.py; override with DEVDATA_MAX_FILE_BYTES.
|
|
|
|
| 439 |
|
| 440 |
# Normal background sync mode — no restore; go straight to upload loop.
|
| 441 |
validate_jupyter_paths()
|
| 442 |
+
if is_jupyter_running(JUPYTER_PORT):
|
| 443 |
print("DevData: background sync started (JupyterLab is live, restore already done by --restore).")
|
| 444 |
else:
|
| 445 |
# Fallback: JupyterLab not detected. Should not normally happen
|
|
|
|
| 447 |
# waits for the gateway before launching this background process.
|
| 448 |
# Log a warning and proceed to sync; do NOT restore to avoid racing
|
| 449 |
# with a JupyterLab that may be in the middle of starting up.
|
| 450 |
+
print(f"DevData: WARNING — JupyterLab not detected on port {JUPYTER_PORT}. Skipping restore to be safe; starting sync loop.")
|
| 451 |
|
| 452 |
sync_loop(api, rid)
|
start.sh
CHANGED
|
@@ -504,6 +504,13 @@ chmod 700 /home/node/.openclaw/credentials
|
|
| 504 |
export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/home/node/.local}"
|
| 505 |
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 506 |
export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
|
| 508 |
# Show current working directory in terminal prompt (JupyterLab terminals can
|
| 509 |
# otherwise display only "$" when PS1 is unset/minimal).
|
|
@@ -1665,8 +1672,13 @@ start_jupyter_once() {
|
|
| 1665 |
echo "Terminal : starting (root: $JUPYTER_ROOT_DIR)"
|
| 1666 |
JUPYTER_LOG_FILE="/tmp/jupyterlab.log"
|
| 1667 |
|
| 1668 |
-
# Use explicit Python to avoid PATH issues; set memory-friendly limits
|
|
|
|
|
|
|
| 1669 |
export PYTHONPATH=""
|
|
|
|
|
|
|
|
|
|
| 1670 |
hc_env_without_gateway_preloads python3 -m jupyterlab \
|
| 1671 |
--ip 127.0.0.1 \
|
| 1672 |
--port "$JUPYTER_PORT" \
|
|
@@ -1740,6 +1752,13 @@ export PATH="/home/node/.local/bin:$PATH"
|
|
| 1740 |
export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/home/node/.local}"
|
| 1741 |
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 1742 |
export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1743 |
export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
|
| 1744 |
export HISTFILE="${HISTFILE:-/home/node/.bash_history}"
|
| 1745 |
export HISTSIZE="${HISTSIZE:-50000}"
|
|
@@ -1958,6 +1977,8 @@ sudo() {
|
|
| 1958 |
pip() {
|
| 1959 |
if [ "${1:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "$@" && ! _hc_has_arg --prefix "$@"; then
|
| 1960 |
command pip install --user --break-system-packages "${@:2}"
|
|
|
|
|
|
|
| 1961 |
else
|
| 1962 |
command pip "$@"
|
| 1963 |
fi
|
|
@@ -1973,6 +1994,8 @@ pip() {
|
|
| 1973 |
pip3() {
|
| 1974 |
if [ "${1:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "$@" && ! _hc_has_arg --prefix "$@"; then
|
| 1975 |
command pip3 install --user --break-system-packages "${@:2}"
|
|
|
|
|
|
|
| 1976 |
else
|
| 1977 |
command pip3 "$@"
|
| 1978 |
fi
|
|
@@ -1987,6 +2010,8 @@ pip3() {
|
|
| 1987 |
python() {
|
| 1988 |
if [ "${1:-}" = "-m" ] && [ "${2:-}" = "pip" ] && [ "${3:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "${@:3}" && ! _hc_has_arg --prefix "${@:3}"; then
|
| 1989 |
command python -m pip install --user --break-system-packages "${@:4}"
|
|
|
|
|
|
|
| 1990 |
else
|
| 1991 |
command python "$@"
|
| 1992 |
fi
|
|
@@ -2001,6 +2026,8 @@ python() {
|
|
| 2001 |
python3() {
|
| 2002 |
if [ "${1:-}" = "-m" ] && [ "${2:-}" = "pip" ] && [ "${3:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "${@:3}" && ! _hc_has_arg --prefix "${@:3}"; then
|
| 2003 |
command python3 -m pip install --user --break-system-packages "${@:4}"
|
|
|
|
|
|
|
| 2004 |
else
|
| 2005 |
command python3 "$@"
|
| 2006 |
fi
|
|
|
|
| 504 |
export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/home/node/.local}"
|
| 505 |
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 506 |
export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
| 507 |
+
# Debian/Ubuntu images mark system Python as externally managed (PEP 668).
|
| 508 |
+
# JupyterLab's PyPI extension manager runs pip from the server process, so it
|
| 509 |
+
# does not see the interactive shell wrappers below. Allow that non-interactive
|
| 510 |
+
# pip to install into node's user site instead of failing with
|
| 511 |
+
# "externally-managed-environment".
|
| 512 |
+
export PIP_BREAK_SYSTEM_PACKAGES="${PIP_BREAK_SYSTEM_PACKAGES:-1}"
|
| 513 |
+
export PIP_USER="${PIP_USER:-1}"
|
| 514 |
export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
|
| 515 |
# Show current working directory in terminal prompt (JupyterLab terminals can
|
| 516 |
# otherwise display only "$" when PS1 is unset/minimal).
|
|
|
|
| 1672 |
echo "Terminal : starting (root: $JUPYTER_ROOT_DIR)"
|
| 1673 |
JUPYTER_LOG_FILE="/tmp/jupyterlab.log"
|
| 1674 |
|
| 1675 |
+
# Use explicit Python to avoid PATH issues; set memory-friendly limits.
|
| 1676 |
+
# Keep pip user-site defaults in this process so JupyterLab's PyPI Manager
|
| 1677 |
+
# can install extensions/packages under /home/node/.local on PEP 668 images.
|
| 1678 |
export PYTHONPATH=""
|
| 1679 |
+
export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
| 1680 |
+
export PIP_BREAK_SYSTEM_PACKAGES="${PIP_BREAK_SYSTEM_PACKAGES:-1}"
|
| 1681 |
+
export PIP_USER="${PIP_USER:-1}"
|
| 1682 |
hc_env_without_gateway_preloads python3 -m jupyterlab \
|
| 1683 |
--ip 127.0.0.1 \
|
| 1684 |
--port "$JUPYTER_PORT" \
|
|
|
|
| 1752 |
export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/home/node/.local}"
|
| 1753 |
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 1754 |
export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
| 1755 |
+
# Debian/Ubuntu images mark system Python as externally managed (PEP 668).
|
| 1756 |
+
# JupyterLab's PyPI extension manager runs pip from the server process, so it
|
| 1757 |
+
# does not see the interactive shell wrappers below. Allow that non-interactive
|
| 1758 |
+
# pip to install into node's user site instead of failing with
|
| 1759 |
+
# "externally-managed-environment".
|
| 1760 |
+
export PIP_BREAK_SYSTEM_PACKAGES="${PIP_BREAK_SYSTEM_PACKAGES:-1}"
|
| 1761 |
+
export PIP_USER="${PIP_USER:-1}"
|
| 1762 |
export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
|
| 1763 |
export HISTFILE="${HISTFILE:-/home/node/.bash_history}"
|
| 1764 |
export HISTSIZE="${HISTSIZE:-50000}"
|
|
|
|
| 1977 |
pip() {
|
| 1978 |
if [ "${1:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "$@" && ! _hc_has_arg --prefix "$@"; then
|
| 1979 |
command pip install --user --break-system-packages "${@:2}"
|
| 1980 |
+
elif [ -n "${VIRTUAL_ENV:-}" ]; then
|
| 1981 |
+
env -u PIP_USER pip "$@"
|
| 1982 |
else
|
| 1983 |
command pip "$@"
|
| 1984 |
fi
|
|
|
|
| 1994 |
pip3() {
|
| 1995 |
if [ "${1:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "$@" && ! _hc_has_arg --prefix "$@"; then
|
| 1996 |
command pip3 install --user --break-system-packages "${@:2}"
|
| 1997 |
+
elif [ -n "${VIRTUAL_ENV:-}" ]; then
|
| 1998 |
+
env -u PIP_USER pip3 "$@"
|
| 1999 |
else
|
| 2000 |
command pip3 "$@"
|
| 2001 |
fi
|
|
|
|
| 2010 |
python() {
|
| 2011 |
if [ "${1:-}" = "-m" ] && [ "${2:-}" = "pip" ] && [ "${3:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "${@:3}" && ! _hc_has_arg --prefix "${@:3}"; then
|
| 2012 |
command python -m pip install --user --break-system-packages "${@:4}"
|
| 2013 |
+
elif [ "${1:-}" = "-m" ] && [ "${2:-}" = "pip" ] && [ -n "${VIRTUAL_ENV:-}" ]; then
|
| 2014 |
+
env -u PIP_USER python "$@"
|
| 2015 |
else
|
| 2016 |
command python "$@"
|
| 2017 |
fi
|
|
|
|
| 2026 |
python3() {
|
| 2027 |
if [ "${1:-}" = "-m" ] && [ "${2:-}" = "pip" ] && [ "${3:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "${@:3}" && ! _hc_has_arg --prefix "${@:3}"; then
|
| 2028 |
command python3 -m pip install --user --break-system-packages "${@:4}"
|
| 2029 |
+
elif [ "${1:-}" = "-m" ] && [ "${2:-}" = "pip" ] && [ -n "${VIRTUAL_ENV:-}" ]; then
|
| 2030 |
+
env -u PIP_USER python3 "$@"
|
| 2031 |
else
|
| 2032 |
command python3 "$@"
|
| 2033 |
fi
|