Agent-Div / Dockerfile
anurag008w
fix some bugs
1da7c35
Raw
History Blame Contribute Delete
8.46 kB
# ════════════════════════════════════════════════════════════════
# 🦞 HuggingClaw + 💻 JupyterLab Terminal
# ════════════════════════════════════════════════════════════════
# Port 7861 (exposed): Dashboard + reverse proxy
# / → HuggingClaw dashboard
# /app/ → OpenClaw gateway (internal :7860)
# /terminal/ → JupyterLab terminal (internal :8888)
# ════════════════════════════════════════════════════════════════
# ── Stage 1: Pull pre-built OpenClaw ──
ARG OPENCLAW_VERSION=latest
FROM ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION} AS openclaw
# ── Stage 2: Runtime ──
FROM node:22-slim
ARG OPENCLAW_VERSION=latest
ARG DEV_MODE=false
ARG HUGGINGCLAW_FULL_SUDO=false
# DEV_MODE intentionally not baked into runtime ENV — defaults to unset so
# start.sh can auto-enable terminal when GATEWAY_TOKEN is present. Users can
# override by setting DEV_MODE=false as an HF Space Variable to opt out.
# Install system dependencies (+ optional JupyterLab deps in DEV_MODE)
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
sudo \
file \
ca-certificates \
jq \
curl \
dbus \
dbus-x11 \
dbus-daemon \
libglib2.0-0 \
procps \
python3 \
python3-pip \
python3-venv \
p7zip-full \
chromium \
libnss3 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libdrm2 \
libgbm1 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libxkbcommon0 \
libx11-6 \
libxext6 \
libxfixes3 \
libasound2 \
fonts-dejavu-core \
fonts-liberation \
fonts-noto-color-emoji \
fonts-freefont-ttf \
fonts-ipafont-gothic \
fonts-wqy-zenhei \
xfonts-scalable \
--no-install-recommends && \
pip3 install --no-cache-dir --break-system-packages huggingface_hub hf_transfer && \
rm -rf /var/lib/apt/lists/*
# Install JupyterLab at build time because start.sh can auto-enable the
# terminal at runtime when GATEWAY_TOKEN is present. If the dependency is only
# installed when the build arg DEV_MODE=true, the documented default path
# starts a terminal process that cannot import jupyterlab.
RUN pip3 install --no-cache-dir --break-system-packages \
jupyterlab==4.5.7 \
tornado==6.5.5 \
ipywidgets==8.1.8
# Reuse existing node user (UID 1000). By default, allow passwordless
# package-manager commands only so runtime apt installs can be replayed after
# HF Space restarts without granting unrestricted root. Private Spaces can opt
# into full passwordless sudo either at build time (HUGGINGCLAW_FULL_SUDO=true
# build arg) OR at runtime (HUGGINGCLAW_FULL_SUDO=true env var), via the
# locked-down hc-apply-full-sudo helper below.
RUN mkdir -p /home/node/app /home/node/.openclaw && \
chown -R 1000:1000 /home/node && \
case "$(printf '%s' "${HUGGINGCLAW_FULL_SUDO}" | tr '[:upper:]' '[:lower:]')" in \
1|true|yes|on) \
printf '%s\n' \
'node ALL=(root) NOPASSWD: ALL' \
> /etc/sudoers.d/huggingclaw ;; \
*) \
printf '%s\n' \
'Cmnd_Alias HUGGINGCLAW_APT = /usr/bin/apt, /usr/bin/apt-get, /usr/bin/dpkg' \
'node ALL=(root) NOPASSWD: HUGGINGCLAW_APT, /usr/local/bin/hc-apply-full-sudo' \
> /etc/sudoers.d/huggingclaw ;; \
esac && \
chmod 0440 /etc/sudoers.d/huggingclaw && \
visudo -cf /etc/sudoers.d/huggingclaw
# Runtime full-sudo enablement helper.
# A non-root process cannot grant itself new privileges at runtime, so the image
# bakes in ONE root-owned, non-writable (by node) script that the sudoers rule
# above lets `node` invoke without a password. start.sh calls it when
# HUGGINGCLAW_FULL_SUDO=true is set as a runtime env var, so a single image can
# toggle full sudo per Space without rebuilding. The script is intentionally
# trivial and locked down: it only rewrites /etc/sudoers.d/huggingclaw to the
# unrestricted rule. Do NOT make it accept arguments or paths.
RUN printf '%s\n' \
'#!/bin/sh' \
'set -eu' \
'# Locked full-sudo escalation hook (root-owned, not writable by node).' \
'# Invoked only by start.sh when HUGGINGCLAW_FULL_SUDO=true.' \
'umask 022' \
'tmp="$(mktemp /etc/sudoers.d/huggingclaw.XXXXXX)"' \
'printf "%s\n" "node ALL=(root) NOPASSWD: ALL" > "$tmp"' \
'chmod 0440 "$tmp"' \
'# Resolve visudo from the known locations (/usr/sbin on Debian/Ubuntu).' \
'VISUDO=""; for c in /usr/sbin/visudo /sbin/visudo visudo; do' \
' command -v "$c" >/dev/null 2>&1 && { VISUDO="$c"; break; }' \
'done' \
'if [ -z "$VISUDO" ]; then' \
' rm -f "$tmp"' \
' echo "hc-apply-full-sudo: visudo not found; aborting." >&2' \
' exit 1' \
'fi' \
'if "$VISUDO" -cf "$tmp" >/dev/null 2>&1; then' \
' mv "$tmp" /etc/sudoers.d/huggingclaw' \
'else' \
' rm -f "$tmp"' \
' echo "hc-apply-full-sudo: generated sudoers file failed validation; aborting." >&2' \
' exit 1' \
'fi' \
'echo "hc-apply-full-sudo: full passwordless sudo enabled." >&2' \
> /usr/local/bin/hc-apply-full-sudo && \
chmod 0755 /usr/local/bin/hc-apply-full-sudo && \
chown root:root /usr/local/bin/hc-apply-full-sudo
# Copy pre-built OpenClaw (skips npm install entirely — much faster!)
COPY --from=openclaw --chown=1000:1000 /app /home/node/.openclaw/openclaw-app
# Add Playwright in an isolated sidecar node_modules
RUN mkdir -p /home/node/browser-deps && \
cd /home/node/browser-deps && \
npm init -y && \
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --omit=dev playwright@1.59.1
# Symlink openclaw CLI so it's available globally
RUN ln -s /home/node/.openclaw/openclaw-app/openclaw.mjs /usr/local/bin/openclaw 2>/dev/null || \
npm install -g openclaw@${OPENCLAW_VERSION}
# Copy HuggingClaw files
COPY --chown=1000:1000 cloudflare-proxy.js /opt/cloudflare-proxy.js
COPY --chown=1000:1000 cloudflare-proxy-setup.py /home/node/app/cloudflare-proxy-setup.py
COPY --chown=1000:1000 health-server.js /home/node/app/health-server.js
COPY --chown=1000:1000 login.html /home/node/app/login.html
COPY --chown=1000:1000 iframe-fix.cjs /home/node/app/iframe-fix.cjs
COPY --chown=1000:1000 start.sh /home/node/app/start.sh
COPY --chown=1000:1000 wa-guardian.js /home/node/app/wa-guardian.js
COPY --chown=1000:1000 cloudflare-keepalive-setup.py /home/node/app/cloudflare-keepalive-setup.py
COPY --chown=1000:1000 openclaw-sync.py /home/node/app/openclaw-sync.py
COPY --chown=1000:1000 multi-provider-key-rotator.cjs /home/node/app/multi-provider-key-rotator.cjs
COPY --chown=1000:1000 env-builder.html /home/node/app/env-builder.html
COPY --chown=1000:1000 env-builder.js /home/node/app/env-builder.js
COPY --chown=1000:1000 key-rotator-manager.html /home/node/app/key-rotator-manager.html
COPY --chown=1000:1000 jupyter-devdata-sync.py /home/node/app/jupyter-devdata-sync.py
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')"
RUN chmod +x /home/node/app/start.sh \
/home/node/app/cloudflare-proxy-setup.py \
/home/node/app/cloudflare-keepalive-setup.py \
/home/node/app/openclaw-sync.py \
/home/node/app/jupyter-devdata-sync.py \
/home/node/app/multi-provider-key-rotator.cjs
USER node
ENV HOME=/home/node \
OPENCLAW_VERSION=${OPENCLAW_VERSION} \
HUGGINGCLAW_FULL_SUDO_BUILT=${HUGGINGCLAW_FULL_SUDO} \
PIP_BREAK_SYSTEM_PACKAGES=1 \
PYTHONUSERBASE=/home/node/.local \
PATH=/home/node/.local/bin:/usr/local/bin:$PATH \
NODE_PATH=/home/node/browser-deps/node_modules \
NODE_OPTIONS="--require /opt/cloudflare-proxy.js"
WORKDIR /home/node/app
# 7861 = public entrypoint (dashboard + proxy for both OpenClaw and JupyterLab)
EXPOSE 7861
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s \
CMD curl -fsS http://localhost:7861/health || exit 1
CMD ["/home/node/app/start.sh"]