Spaces:
Paused
Paused
Anurag commited on
Commit Β·
d085e58
1
Parent(s): 1f050ab
Fix JupyterLab writable extension paths
Browse files- Dockerfile +19 -8
- README.md +5 -1
- env-builder.js +18 -0
- start.sh +79 -15
Dockerfile
CHANGED
|
@@ -15,6 +15,7 @@ FROM ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION} AS openclaw
|
|
| 15 |
FROM node:22-slim
|
| 16 |
ARG OPENCLAW_VERSION=latest
|
| 17 |
ARG DEV_MODE=false
|
|
|
|
| 18 |
# DEV_MODE intentionally not baked into runtime ENV β defaults to unset so
|
| 19 |
# start.sh can auto-enable terminal when GATEWAY_TOKEN is present. Users can
|
| 20 |
# override by setting DEV_MODE=false as an HF Space Variable to opt out.
|
|
@@ -70,16 +71,25 @@ RUN pip3 install --no-cache-dir --break-system-packages \
|
|
| 70 |
tornado==6.5.5 \
|
| 71 |
ipywidgets==8.1.8
|
| 72 |
|
| 73 |
-
# Reuse existing node user (UID 1000).
|
| 74 |
-
# commands only so runtime apt installs can be replayed after
|
|
|
|
|
|
|
| 75 |
RUN mkdir -p /home/node/app /home/node/.openclaw && \
|
| 76 |
chown -R 1000:1000 /home/node && \
|
| 77 |
-
printf '%s
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
# Copy pre-built OpenClaw (skips npm install entirely β much faster!)
|
| 85 |
COPY --from=openclaw --chown=1000:1000 /app /home/node/.openclaw/openclaw-app
|
|
@@ -121,6 +131,7 @@ USER node
|
|
| 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 \
|
|
|
|
| 15 |
FROM node:22-slim
|
| 16 |
ARG OPENCLAW_VERSION=latest
|
| 17 |
ARG DEV_MODE=false
|
| 18 |
+
ARG HUGGINGCLAW_FULL_SUDO=false
|
| 19 |
# DEV_MODE intentionally not baked into runtime ENV β defaults to unset so
|
| 20 |
# start.sh can auto-enable terminal when GATEWAY_TOKEN is present. Users can
|
| 21 |
# override by setting DEV_MODE=false as an HF Space Variable to opt out.
|
|
|
|
| 71 |
tornado==6.5.5 \
|
| 72 |
ipywidgets==8.1.8
|
| 73 |
|
| 74 |
+
# Reuse existing node user (UID 1000). By default, allow passwordless
|
| 75 |
+
# package-manager commands only so runtime apt installs can be replayed after
|
| 76 |
+
# HF Space restarts without granting unrestricted root. Private Spaces can opt
|
| 77 |
+
# into full passwordless sudo at build time with HUGGINGCLAW_FULL_SUDO=true.
|
| 78 |
RUN mkdir -p /home/node/app /home/node/.openclaw && \
|
| 79 |
chown -R 1000:1000 /home/node && \
|
| 80 |
+
case "$(printf '%s' "${HUGGINGCLAW_FULL_SUDO}" | tr '[:upper:]' '[:lower:]')" in \
|
| 81 |
+
1|true|yes|on) \
|
| 82 |
+
printf '%s\n' \
|
| 83 |
+
'node ALL=(root) NOPASSWD: ALL' \
|
| 84 |
+
> /etc/sudoers.d/huggingclaw ;; \
|
| 85 |
+
*) \
|
| 86 |
+
printf '%s\n' \
|
| 87 |
+
'Cmnd_Alias HUGGINGCLAW_APT = /usr/bin/apt, /usr/bin/apt-get, /usr/bin/dpkg' \
|
| 88 |
+
'node ALL=(root) NOPASSWD: HUGGINGCLAW_APT' \
|
| 89 |
+
> /etc/sudoers.d/huggingclaw ;; \
|
| 90 |
+
esac && \
|
| 91 |
+
chmod 0440 /etc/sudoers.d/huggingclaw && \
|
| 92 |
+
visudo -cf /etc/sudoers.d/huggingclaw
|
| 93 |
|
| 94 |
# Copy pre-built OpenClaw (skips npm install entirely β much faster!)
|
| 95 |
COPY --from=openclaw --chown=1000:1000 /app /home/node/.openclaw/openclaw-app
|
|
|
|
| 131 |
|
| 132 |
ENV HOME=/home/node \
|
| 133 |
OPENCLAW_VERSION=${OPENCLAW_VERSION} \
|
| 134 |
+
HUGGINGCLAW_FULL_SUDO_BUILT=${HUGGINGCLAW_FULL_SUDO} \
|
| 135 |
PIP_BREAK_SYSTEM_PACKAGES=1 \
|
| 136 |
PYTHONUSERBASE=/home/node/.local \
|
| 137 |
PATH=/home/node/.local/bin:/usr/local/bin:$PATH \
|
README.md
CHANGED
|
@@ -210,6 +210,8 @@ Example:
|
|
| 210 |
```bash
|
| 211 |
HUGGINGCLAW_RUN="""
|
| 212 |
set -e
|
|
|
|
|
|
|
| 213 |
sudo apt-get update
|
| 214 |
sudo apt-get install -y ffmpeg
|
| 215 |
python3 -m pip install --user pandas requests
|
|
@@ -241,7 +243,9 @@ Advanced/backward-compatible variables still work if you prefer package-specific
|
|
| 241 |
|
| 242 |
|
| 243 |
> [!IMPORTANT]
|
| 244 |
-
> `sudo`
|
|
|
|
|
|
|
| 245 |
|
| 246 |
## π Staying Alive *(Recommended on Free HF Spaces)*
|
| 247 |
|
|
|
|
| 210 |
```bash
|
| 211 |
HUGGINGCLAW_RUN="""
|
| 212 |
set -e
|
| 213 |
+
# In HUGGINGCLAW_RUN use normal startup commands. For apt packages,
|
| 214 |
+
# HUGGINGCLAW_APT_PACKAGES=ffmpeg is preferred; sudo also works here.
|
| 215 |
sudo apt-get update
|
| 216 |
sudo apt-get install -y ffmpeg
|
| 217 |
python3 -m pip install --user pandas requests
|
|
|
|
| 243 |
|
| 244 |
|
| 245 |
> [!IMPORTANT]
|
| 246 |
+
> Terminal shells are admin-convenient but not unrestricted root shells. You normally do **not** need to type `sudo` for package installs in the Jupyter/OpenClaw terminal: `apt install ...` and `apt-get install ...` are wrapped to use the image's passwordless package-manager sudo internally, while `pip`, `python -m pip`, `npm`, and related user-space tools install into HuggingClaw's writable runtime prefix. Direct `sudo` remains limited to `apt`, `apt-get`, and `dpkg`; common user-space commands such as `sudo unzip`, `sudo tar`, `sudo curl`, and `sudo pip` are passed through without escalation for convenience. Apt-installed packages still disappear on Space restart, so put them in `HUGGINGCLAW_APT_PACKAGES`/`HUGGINGCLAW_RUN` or let the shell wrapper record the command in `startup.sh`.
|
| 247 |
+
|
| 248 |
+
If you really need an unrestricted root-capable Jupyter terminal for a private Space, rebuild the image with `--build-arg HUGGINGCLAW_FULL_SUDO=true` (setting this only as a runtime/env-builder variable is not enough). This changes the Docker sudoers file to `node ALL=(root) NOPASSWD: ALL`, so anyone who can access the Jupyter token can become root inside the container. Keep this disabled on public/shared Spaces.
|
| 249 |
|
| 250 |
## π Staying Alive *(Recommended on Free HF Spaces)*
|
| 251 |
|
env-builder.js
CHANGED
|
@@ -788,6 +788,24 @@ const FIELDS = [
|
|
| 788 |
"common": 1,
|
| 789 |
"tag": "build"
|
| 790 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 791 |
{
|
| 792 |
"g": "Startup",
|
| 793 |
"icon": "π©Ί",
|
|
|
|
| 788 |
"common": 1,
|
| 789 |
"tag": "build"
|
| 790 |
},
|
| 791 |
+
{
|
| 792 |
+
"g": "Startup",
|
| 793 |
+
"icon": "π",
|
| 794 |
+
"k": "HUGGINGCLAW_FULL_SUDO",
|
| 795 |
+
"lbl": "Full sudo for Jupyter terminal (Docker build arg only; private Spaces only)",
|
| 796 |
+
"type": "toggle",
|
| 797 |
+
"ph": "false",
|
| 798 |
+
"tag": "build"
|
| 799 |
+
},
|
| 800 |
+
{
|
| 801 |
+
"g": "Runtime",
|
| 802 |
+
"icon": "βοΈ",
|
| 803 |
+
"k": "HUGGINGCLAW_WRITABLE_BASE",
|
| 804 |
+
"lbl": "Writable runtime base (blank = /data, HOME fallback, then /tmp)",
|
| 805 |
+
"type": "text",
|
| 806 |
+
"ph": "/data",
|
| 807 |
+
"tag": "advanced"
|
| 808 |
+
},
|
| 809 |
{
|
| 810 |
"g": "Startup",
|
| 811 |
"icon": "π©Ί",
|
start.sh
CHANGED
|
@@ -228,6 +228,12 @@ echo ""
|
|
| 228 |
echo " ββββββββββββββββββββββββββββββββββββββββ"
|
| 229 |
echo " β π¦ HuggingClaw + π» JupyterLab β"
|
| 230 |
echo " ββββββββββββββββββββββββββββββββββββββββ"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
echo ""
|
| 232 |
|
| 233 |
# ββ Validate required secrets ββ
|
|
@@ -248,6 +254,29 @@ if [ -n "$ERRORS" ]; then
|
|
| 248 |
exit 1
|
| 249 |
fi
|
| 250 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
# Resolve the actual bundled OpenClaw version so the banner reflects what is
|
| 252 |
# inside the image, not just the requested tag.
|
| 253 |
if [ -f "$OPENCLAW_APP_DIR/package.json" ]; then
|
|
@@ -288,14 +317,14 @@ if [ "$_do_runtime_upgrade" = "true" ]; then
|
|
| 288 |
_upgrade_pkg="openclaw"
|
| 289 |
[ "$_requested_ver" != "latest" ] && _upgrade_pkg="openclaw@${_requested_ver}"
|
| 290 |
|
| 291 |
-
# npm install -g respects NPM_CONFIG_PREFIX
|
| 292 |
-
#
|
| 293 |
-
_npm_prefix="${NPM_CONFIG_PREFIX:-/
|
| 294 |
if NPM_CONFIG_PREFIX="$_npm_prefix" npm install -g "$_upgrade_pkg" --prefer-online 2>/tmp/openclaw-upgrade.log; then
|
| 295 |
# Re-read version from the installed package under the explicit prefix
|
| 296 |
_new_ver=$(node -p "require('${_npm_prefix}/lib/node_modules/openclaw/package.json').version" 2>/dev/null || true)
|
| 297 |
-
# PATH
|
| 298 |
-
#
|
| 299 |
# by 'command openclaw' without needing to update /usr/local/bin/openclaw.
|
| 300 |
echo "OpenClaw : upgraded to ${_new_ver:-${_requested_ver}} β"
|
| 301 |
OPENCLAW_RUNTIME_VERSION="${_new_ver:-$OPENCLAW_RUNTIME_VERSION}"
|
|
@@ -493,7 +522,8 @@ mkdir -p /home/node/.openclaw/credentials
|
|
| 493 |
mkdir -p /home/node/.openclaw/memory
|
| 494 |
mkdir -p /home/node/.openclaw/extensions
|
| 495 |
mkdir -p /home/node/.openclaw/workspace
|
| 496 |
-
mkdir -p /
|
|
|
|
| 497 |
chmod 700 /home/node/.openclaw
|
| 498 |
chmod 700 /home/node/.openclaw/credentials
|
| 499 |
|
|
@@ -501,9 +531,17 @@ chmod 700 /home/node/.openclaw/credentials
|
|
| 501 |
# npm/pip installs in user-writable locations, make apt noninteractive,
|
| 502 |
# and persist only a tiny replay script in the synced workspace so packages
|
| 503 |
# are re-installed after restart.
|
| 504 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 506 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
@@ -511,6 +549,13 @@ export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
|
| 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).
|
|
@@ -1461,7 +1506,7 @@ else
|
|
| 1461 |
RUNTIME_JUPYTER_ENABLED="$DEV_MODE_ENABLED"
|
| 1462 |
fi
|
| 1463 |
# Add user bin to PATH for jupyter-lab (installed in Dockerfile when DEV_MODE=true)
|
| 1464 |
-
export PATH="$HOME/.local/bin:$PATH"
|
| 1465 |
|
| 1466 |
# Runtime install fallback: only attempt if DEV_MODE is enabled but install failed during build
|
| 1467 |
if [ "$DEV_MODE_ENABLED" = "true" ] && ! python3 -c "import jupyterlab" >/dev/null 2>&1; then
|
|
@@ -1667,18 +1712,29 @@ start_jupyter_once() {
|
|
| 1667 |
fi
|
| 1668 |
|
| 1669 |
# Pre-create runtime directory
|
| 1670 |
-
mkdir -p "$JUPYTER_ROOT_DIR/.jupyter"
|
| 1671 |
|
| 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
|
| 1678 |
export PYTHONPATH=""
|
| 1679 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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" \
|
|
@@ -1748,10 +1804,11 @@ if [ ! -f "$STARTUP_FILE" ]; then
|
|
| 1748 |
echo "Created workspace/startup.sh"
|
| 1749 |
fi
|
| 1750 |
cat > /home/node/.bashrc << 'BASHRC'
|
| 1751 |
-
export
|
| 1752 |
-
export
|
|
|
|
| 1753 |
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 1754 |
-
export PYTHONUSERBASE="${PYTHONUSERBASE:-/
|
| 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
|
|
@@ -1759,6 +1816,13 @@ export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
|
| 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}"
|
|
|
|
| 228 |
echo " ββββββββββββββββββββββββββββββββββββββββ"
|
| 229 |
echo " β π¦ HuggingClaw + π» JupyterLab β"
|
| 230 |
echo " ββββββββββββββββββββββββββββββββββββββββ"
|
| 231 |
+
if hc_is_true "${HUGGINGCLAW_FULL_SUDO_BUILT:-false}"; then
|
| 232 |
+
echo "WARNING: full passwordless sudo was enabled at image build time. JupyterLab terminal users have root access."
|
| 233 |
+
elif hc_is_true "${HUGGINGCLAW_FULL_SUDO:-false}"; then
|
| 234 |
+
echo "WARNING: HUGGINGCLAW_FULL_SUDO=true was set at runtime, but full sudo requires rebuilding with --build-arg HUGGINGCLAW_FULL_SUDO=true."
|
| 235 |
+
fi
|
| 236 |
+
|
| 237 |
echo ""
|
| 238 |
|
| 239 |
# ββ Validate required secrets ββ
|
|
|
|
| 254 |
exit 1
|
| 255 |
fi
|
| 256 |
|
| 257 |
+
# ββ Runtime-writable locations ββ
|
| 258 |
+
# Hugging Face Docker Spaces only guarantee persistent writable storage under
|
| 259 |
+
# /data when persistent storage is attached. HOME can vary by base image and
|
| 260 |
+
# system/env Jupyter prefixes can be read-only, so pick one writable root once
|
| 261 |
+
# and point Python, npm, and JupyterLab user-writable state there.
|
| 262 |
+
hc_choose_writable_base() {
|
| 263 |
+
local candidate
|
| 264 |
+
for candidate in "${HUGGINGCLAW_WRITABLE_BASE:-}" /data "${HOME:-}/.huggingclaw-runtime" /tmp/huggingclaw-runtime; do
|
| 265 |
+
[ -n "$candidate" ] || continue
|
| 266 |
+
if mkdir -p "$candidate" 2>/dev/null && [ -w "$candidate" ]; then
|
| 267 |
+
printf '%s' "$candidate"
|
| 268 |
+
return 0
|
| 269 |
+
fi
|
| 270 |
+
done
|
| 271 |
+
return 1
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
HC_WRITABLE_BASE="$(hc_choose_writable_base)" || {
|
| 275 |
+
echo "ERROR: no writable runtime directory found for Jupyter/Python/npm state." >&2
|
| 276 |
+
exit 1
|
| 277 |
+
}
|
| 278 |
+
export HC_WRITABLE_BASE
|
| 279 |
+
|
| 280 |
# Resolve the actual bundled OpenClaw version so the banner reflects what is
|
| 281 |
# inside the image, not just the requested tag.
|
| 282 |
if [ -f "$OPENCLAW_APP_DIR/package.json" ]; then
|
|
|
|
| 317 |
_upgrade_pkg="openclaw"
|
| 318 |
[ "$_requested_ver" != "latest" ] && _upgrade_pkg="openclaw@${_requested_ver}"
|
| 319 |
|
| 320 |
+
# npm install -g respects NPM_CONFIG_PREFIX; use the selected writable
|
| 321 |
+
# prefix explicitly to avoid needing sudo or writing into a read-only HOME.
|
| 322 |
+
_npm_prefix="${NPM_CONFIG_PREFIX:-$HC_WRITABLE_BASE/.local}"
|
| 323 |
if NPM_CONFIG_PREFIX="$_npm_prefix" npm install -g "$_upgrade_pkg" --prefer-online 2>/tmp/openclaw-upgrade.log; then
|
| 324 |
# Re-read version from the installed package under the explicit prefix
|
| 325 |
_new_ver=$(node -p "require('${_npm_prefix}/lib/node_modules/openclaw/package.json').version" 2>/dev/null || true)
|
| 326 |
+
# PATH is updated below to prefer the selected writable prefix, so the
|
| 327 |
+
# newly installed binary is picked up automatically
|
| 328 |
# by 'command openclaw' without needing to update /usr/local/bin/openclaw.
|
| 329 |
echo "OpenClaw : upgraded to ${_new_ver:-${_requested_ver}} β"
|
| 330 |
OPENCLAW_RUNTIME_VERSION="${_new_ver:-$OPENCLAW_RUNTIME_VERSION}"
|
|
|
|
| 522 |
mkdir -p /home/node/.openclaw/memory
|
| 523 |
mkdir -p /home/node/.openclaw/extensions
|
| 524 |
mkdir -p /home/node/.openclaw/workspace
|
| 525 |
+
mkdir -p "$HC_WRITABLE_BASE/.local/bin" "$HC_WRITABLE_BASE/.local/lib" "$HC_WRITABLE_BASE/.npm-global"
|
| 526 |
+
mkdir -p "$HC_WRITABLE_BASE/.jupyter" "$HC_WRITABLE_BASE/.local/share/jupyter" "$HC_WRITABLE_BASE/.local/share/jupyter/runtime" "$HC_WRITABLE_BASE/.local/share/jupyter/lab"
|
| 527 |
chmod 700 /home/node/.openclaw
|
| 528 |
chmod 700 /home/node/.openclaw/credentials
|
| 529 |
|
|
|
|
| 531 |
# npm/pip installs in user-writable locations, make apt noninteractive,
|
| 532 |
# and persist only a tiny replay script in the synced workspace so packages
|
| 533 |
# are re-installed after restart.
|
| 534 |
+
if [ -z "${NPM_CONFIG_PREFIX:-}" ] || [ "${NPM_CONFIG_PREFIX:-}" = "/home/node/.local" ]; then
|
| 535 |
+
export NPM_CONFIG_PREFIX="$HC_WRITABLE_BASE/.local"
|
| 536 |
+
else
|
| 537 |
+
export NPM_CONFIG_PREFIX
|
| 538 |
+
fi
|
| 539 |
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 540 |
+
if [ -z "${PYTHONUSERBASE:-}" ] || [ "${PYTHONUSERBASE:-}" = "/home/node/.local" ]; then
|
| 541 |
+
export PYTHONUSERBASE="$HC_WRITABLE_BASE/.local"
|
| 542 |
+
else
|
| 543 |
+
export PYTHONUSERBASE
|
| 544 |
+
fi
|
| 545 |
# Debian/Ubuntu images mark system Python as externally managed (PEP 668).
|
| 546 |
# JupyterLab's PyPI extension manager runs pip from the server process, so it
|
| 547 |
# does not see the interactive shell wrappers below. Allow that non-interactive
|
|
|
|
| 549 |
# "externally-managed-environment".
|
| 550 |
export PIP_BREAK_SYSTEM_PACKAGES="${PIP_BREAK_SYSTEM_PACKAGES:-1}"
|
| 551 |
export PIP_USER="${PIP_USER:-1}"
|
| 552 |
+
export JUPYTER_CONFIG_DIR="${JUPYTER_CONFIG_DIR:-$HC_WRITABLE_BASE/.jupyter}"
|
| 553 |
+
export JUPYTER_DATA_DIR="${JUPYTER_DATA_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter}"
|
| 554 |
+
export JUPYTER_RUNTIME_DIR="${JUPYTER_RUNTIME_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter/runtime}"
|
| 555 |
+
export JUPYTERLAB_DIR="${JUPYTERLAB_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter/lab}"
|
| 556 |
+
export JUPYTERLAB_SETTINGS_DIR="${JUPYTERLAB_SETTINGS_DIR:-$JUPYTER_CONFIG_DIR/lab/user-settings}"
|
| 557 |
+
export JUPYTERLAB_WORKSPACES_DIR="${JUPYTERLAB_WORKSPACES_DIR:-$JUPYTER_CONFIG_DIR/lab/workspaces}"
|
| 558 |
+
export JUPYTER_PREFER_ENV_PATH="${JUPYTER_PREFER_ENV_PATH:-0}"
|
| 559 |
export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
|
| 560 |
# Show current working directory in terminal prompt (JupyterLab terminals can
|
| 561 |
# otherwise display only "$" when PS1 is unset/minimal).
|
|
|
|
| 1506 |
RUNTIME_JUPYTER_ENABLED="$DEV_MODE_ENABLED"
|
| 1507 |
fi
|
| 1508 |
# Add user bin to PATH for jupyter-lab (installed in Dockerfile when DEV_MODE=true)
|
| 1509 |
+
export PATH="$HC_WRITABLE_BASE/.local/bin:$HOME/.local/bin:$PATH"
|
| 1510 |
|
| 1511 |
# Runtime install fallback: only attempt if DEV_MODE is enabled but install failed during build
|
| 1512 |
if [ "$DEV_MODE_ENABLED" = "true" ] && ! python3 -c "import jupyterlab" >/dev/null 2>&1; then
|
|
|
|
| 1712 |
fi
|
| 1713 |
|
| 1714 |
# Pre-create runtime directory
|
| 1715 |
+
mkdir -p "$JUPYTER_ROOT_DIR/.jupyter" "$JUPYTER_CONFIG_DIR" "$JUPYTER_DATA_DIR" "$JUPYTER_RUNTIME_DIR" "$JUPYTERLAB_DIR" "$JUPYTERLAB_SETTINGS_DIR" "$JUPYTERLAB_WORKSPACES_DIR"
|
| 1716 |
|
| 1717 |
echo "Terminal : starting (root: $JUPYTER_ROOT_DIR)"
|
| 1718 |
JUPYTER_LOG_FILE="/tmp/jupyterlab.log"
|
| 1719 |
|
| 1720 |
# Use explicit Python to avoid PATH issues; set memory-friendly limits.
|
| 1721 |
# Keep pip user-site defaults in this process so JupyterLab's PyPI Manager
|
| 1722 |
+
# can install extensions/packages under the selected writable base on PEP 668 images.
|
| 1723 |
export PYTHONPATH=""
|
| 1724 |
+
if [ -z "${PYTHONUSERBASE:-}" ] || [ "${PYTHONUSERBASE:-}" = "/home/node/.local" ]; then
|
| 1725 |
+
export PYTHONUSERBASE="$HC_WRITABLE_BASE/.local"
|
| 1726 |
+
else
|
| 1727 |
+
export PYTHONUSERBASE
|
| 1728 |
+
fi
|
| 1729 |
export PIP_BREAK_SYSTEM_PACKAGES="${PIP_BREAK_SYSTEM_PACKAGES:-1}"
|
| 1730 |
export PIP_USER="${PIP_USER:-1}"
|
| 1731 |
+
export JUPYTER_CONFIG_DIR="${JUPYTER_CONFIG_DIR:-$HC_WRITABLE_BASE/.jupyter}"
|
| 1732 |
+
export JUPYTER_DATA_DIR="${JUPYTER_DATA_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter}"
|
| 1733 |
+
export JUPYTER_RUNTIME_DIR="${JUPYTER_RUNTIME_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter/runtime}"
|
| 1734 |
+
export JUPYTERLAB_DIR="${JUPYTERLAB_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter/lab}"
|
| 1735 |
+
export JUPYTERLAB_SETTINGS_DIR="${JUPYTERLAB_SETTINGS_DIR:-$JUPYTER_CONFIG_DIR/lab/user-settings}"
|
| 1736 |
+
export JUPYTERLAB_WORKSPACES_DIR="${JUPYTERLAB_WORKSPACES_DIR:-$JUPYTER_CONFIG_DIR/lab/workspaces}"
|
| 1737 |
+
export JUPYTER_PREFER_ENV_PATH="${JUPYTER_PREFER_ENV_PATH:-0}"
|
| 1738 |
hc_env_without_gateway_preloads python3 -m jupyterlab \
|
| 1739 |
--ip 127.0.0.1 \
|
| 1740 |
--port "$JUPYTER_PORT" \
|
|
|
|
| 1804 |
echo "Created workspace/startup.sh"
|
| 1805 |
fi
|
| 1806 |
cat > /home/node/.bashrc << 'BASHRC'
|
| 1807 |
+
export HC_WRITABLE_BASE="${HC_WRITABLE_BASE:-/tmp/huggingclaw-runtime}"
|
| 1808 |
+
export PATH="$HC_WRITABLE_BASE/.local/bin:/home/node/.local/bin:$PATH"
|
| 1809 |
+
export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-$HC_WRITABLE_BASE/.local}"
|
| 1810 |
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 1811 |
+
export PYTHONUSERBASE="${PYTHONUSERBASE:-$HC_WRITABLE_BASE/.local}"
|
| 1812 |
# Debian/Ubuntu images mark system Python as externally managed (PEP 668).
|
| 1813 |
# JupyterLab's PyPI extension manager runs pip from the server process, so it
|
| 1814 |
# does not see the interactive shell wrappers below. Allow that non-interactive
|
|
|
|
| 1816 |
# "externally-managed-environment".
|
| 1817 |
export PIP_BREAK_SYSTEM_PACKAGES="${PIP_BREAK_SYSTEM_PACKAGES:-1}"
|
| 1818 |
export PIP_USER="${PIP_USER:-1}"
|
| 1819 |
+
export JUPYTER_CONFIG_DIR="${JUPYTER_CONFIG_DIR:-$HC_WRITABLE_BASE/.jupyter}"
|
| 1820 |
+
export JUPYTER_DATA_DIR="${JUPYTER_DATA_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter}"
|
| 1821 |
+
export JUPYTER_RUNTIME_DIR="${JUPYTER_RUNTIME_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter/runtime}"
|
| 1822 |
+
export JUPYTERLAB_DIR="${JUPYTERLAB_DIR:-$HC_WRITABLE_BASE/.local/share/jupyter/lab}"
|
| 1823 |
+
export JUPYTERLAB_SETTINGS_DIR="${JUPYTERLAB_SETTINGS_DIR:-$JUPYTER_CONFIG_DIR/lab/user-settings}"
|
| 1824 |
+
export JUPYTERLAB_WORKSPACES_DIR="${JUPYTERLAB_WORKSPACES_DIR:-$JUPYTER_CONFIG_DIR/lab/workspaces}"
|
| 1825 |
+
export JUPYTER_PREFER_ENV_PATH="${JUPYTER_PREFER_ENV_PATH:-0}"
|
| 1826 |
export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
|
| 1827 |
export HISTFILE="${HISTFILE:-/home/node/.bash_history}"
|
| 1828 |
export HISTSIZE="${HISTSIZE:-50000}"
|