Docker: add cron-enabled gateway runtime and non-root global tool paths
This commit is contained in:
parent
2d67c9b2a0
commit
07287c7f27
99
Dockerfile
99
Dockerfile
@ -19,16 +19,32 @@ ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
ENV PNPM_HOME=/home/node/.local/share/pnpm
|
||||
ENV NPM_CONFIG_PREFIX=/home/node/.npm-global
|
||||
ENV GOPATH=/home/node/go
|
||||
ENV PATH="${PNPM_HOME}:${NPM_CONFIG_PREFIX}/bin:${GOPATH}/bin:${PATH}"
|
||||
RUN mkdir -p "${PNPM_HOME}" "${NPM_CONFIG_PREFIX}/bin" "${GOPATH}/bin" && \
|
||||
chown -R node:node /home/node/.local /home/node/.npm-global /home/node/go
|
||||
|
||||
# Install runtime helpers used by gateway entrypoint:
|
||||
# - cron: enables `crontab` + cron daemon for scheduled jobs in containerized setups
|
||||
# - gosu: drop privileges back to `node` after starting root-only services
|
||||
# - jq: required by later build steps (Go/gogcli version discovery)
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends cron gosu jq && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
|
||||
WORKDIR /app
|
||||
RUN chown node:node /app
|
||||
|
||||
ARG OPENCLAW_DOCKER_APT_PACKAGES=""
|
||||
RUN if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
fi
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
fi
|
||||
|
||||
COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
||||
COPY --chown=node:node ui/package.json ./ui/package.json
|
||||
@ -47,15 +63,68 @@ RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
||||
USER root
|
||||
ARG OPENCLAW_INSTALL_BROWSER=""
|
||||
RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends xvfb && \
|
||||
mkdir -p /home/node/.cache/ms-playwright && \
|
||||
PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright \
|
||||
node /app/node_modules/playwright-core/cli.js install --with-deps chromium && \
|
||||
chown -R node:node /home/node/.cache/ms-playwright && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
fi
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends xvfb && \
|
||||
mkdir -p /home/node/.cache/ms-playwright && \
|
||||
PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright \
|
||||
node /app/node_modules/playwright-core/cli.js install --with-deps chromium && \
|
||||
chown -R node:node /home/node/.cache/ms-playwright && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
fi
|
||||
|
||||
# ---- Install Go (official) ----
|
||||
# Fetch the latest stable version from go.dev and install the correct arch.
|
||||
RUN set -eux; \
|
||||
arch="$(dpkg --print-architecture)"; \
|
||||
case "$arch" in \
|
||||
amd64) GOARCH=amd64 ;; \
|
||||
arm64) GOARCH=arm64 ;; \
|
||||
*) echo "Unsupported arch: $arch" >&2; exit 1 ;; \
|
||||
esac; \
|
||||
GOVERSION="$(curl -fsSL 'https://go.dev/dl/?mode=json' | jq -r 'map(select(.stable==true)) | .[0].version')" ; \
|
||||
echo "Installing Go ${GOVERSION} for linux-${GOARCH}"; \
|
||||
curl -fsSL "https://go.dev/dl/${GOVERSION}.linux-${GOARCH}.tar.gz" -o /tmp/go.tgz; \
|
||||
rm -rf /usr/local/go; \
|
||||
tar -C /usr/local -xzf /tmp/go.tgz; \
|
||||
rm -f /tmp/go.tgz; \
|
||||
/usr/local/go/bin/go version
|
||||
|
||||
# Ensure Go is first in PATH (no old go ahead of it)
|
||||
ENV PATH="/usr/local/go/bin:${PATH}"
|
||||
|
||||
# ---- Install gog (gogcli) ----
|
||||
# Pin version by setting GOGCLI_TAG at build time, or default to latest release tag.
|
||||
ARG GOGCLI_TAG=latest
|
||||
RUN set -eux; \
|
||||
arch="$(dpkg --print-architecture)"; \
|
||||
case "$arch" in \
|
||||
amd64) GOGARCH=amd64 ;; \
|
||||
arm64) GOGARCH=arm64 ;; \
|
||||
*) echo "Unsupported arch: $arch" >&2; exit 1 ;; \
|
||||
esac; \
|
||||
if [ "$GOGCLI_TAG" = "latest" ]; then \
|
||||
GOGCLI_TAG="$(curl -fsSL https://api.github.com/repos/steipete/gogcli/releases/latest | jq -r .tag_name)"; \
|
||||
fi; \
|
||||
ver="${GOGCLI_TAG#v}"; \
|
||||
url="https://github.com/steipete/gogcli/releases/download/${GOGCLI_TAG}/gogcli_${ver}_linux_${GOGARCH}.tar.gz"; \
|
||||
echo "Downloading: $url"; \
|
||||
curl -fsSL "$url" -o /tmp/gogcli.tgz; \
|
||||
tar -xzf /tmp/gogcli.tgz -C /tmp; \
|
||||
install -m 0755 /tmp/gog /usr/local/bin/gog; \
|
||||
rm -f /tmp/gog /tmp/gogcli.tgz; \
|
||||
gog --help >/dev/null
|
||||
|
||||
# Install Linuxbrew in a node-writable prefix so brew installs work at runtime.
|
||||
ENV HOMEBREW_PREFIX=/home/linuxbrew/.linuxbrew
|
||||
ENV HOMEBREW_CELLAR=/home/linuxbrew/.linuxbrew/Cellar
|
||||
ENV HOMEBREW_REPOSITORY=/home/linuxbrew/.linuxbrew/Homebrew
|
||||
RUN set -eux; \
|
||||
mkdir -p "${HOMEBREW_REPOSITORY}" "${HOMEBREW_CELLAR}" "${HOMEBREW_PREFIX}/bin"; \
|
||||
curl -fsSL https://github.com/Homebrew/brew/tarball/master | tar xz --strip-components=1 -C "${HOMEBREW_REPOSITORY}"; \
|
||||
ln -sf ../Homebrew/bin/brew "${HOMEBREW_PREFIX}/bin/brew"; \
|
||||
chown -R node:node /home/linuxbrew
|
||||
ENV PATH="${HOMEBREW_PREFIX}/bin:${HOMEBREW_PREFIX}/sbin:${PATH}"
|
||||
|
||||
# Optionally install Docker CLI for sandbox container management.
|
||||
# Build with: docker build --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1 ...
|
||||
@ -90,6 +159,7 @@ RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
|
||||
fi
|
||||
|
||||
USER node
|
||||
RUN brew --version
|
||||
COPY --chown=node:node . .
|
||||
# Normalize copied plugin/agent paths so plugin safety checks do not reject
|
||||
# world-writable directories inherited from source file modes.
|
||||
@ -99,6 +169,7 @@ RUN for dir in /app/extensions /app/.agent /app/.agents; do \
|
||||
find "$dir" -type f -exec chmod 644 {} +; \
|
||||
fi; \
|
||||
done
|
||||
RUN chmod +x scripts/docker/gateway-entrypoint.sh
|
||||
RUN pnpm build
|
||||
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
||||
ENV OPENCLAW_PREFER_PNPM=1
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
services:
|
||||
openclaw-gateway:
|
||||
image: ${OPENCLAW_IMAGE:-openclaw:local}
|
||||
user: root
|
||||
entrypoint: ["/app/scripts/docker/gateway-entrypoint.sh"]
|
||||
environment:
|
||||
HOME: /home/node
|
||||
TERM: xterm-256color
|
||||
@ -10,6 +12,7 @@ services:
|
||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-}
|
||||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
|
||||
volumes:
|
||||
- ./.env:/app/.env:ro
|
||||
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
||||
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
||||
## Uncomment the lines below to enable sandbox isolation
|
||||
@ -66,6 +69,7 @@ services:
|
||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-}
|
||||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
|
||||
volumes:
|
||||
- ./.env:/app/.env:ro
|
||||
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
||||
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
||||
stdin_open: true
|
||||
|
||||
@ -200,6 +200,7 @@ export OPENCLAW_BRIDGE_PORT="${OPENCLAW_BRIDGE_PORT:-18790}"
|
||||
export OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
|
||||
export OPENCLAW_IMAGE="$IMAGE_NAME"
|
||||
export OPENCLAW_DOCKER_APT_PACKAGES="${OPENCLAW_DOCKER_APT_PACKAGES:-}"
|
||||
export OPENCLAW_INSTALL_BROWSER="${OPENCLAW_INSTALL_BROWSER:-}"
|
||||
export OPENCLAW_EXTRA_MOUNTS="$EXTRA_MOUNTS"
|
||||
export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME"
|
||||
export OPENCLAW_ALLOW_INSECURE_PRIVATE_WS="${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}"
|
||||
@ -382,13 +383,15 @@ upsert_env "$ENV_FILE" \
|
||||
OPENCLAW_DOCKER_SOCKET \
|
||||
DOCKER_GID \
|
||||
OPENCLAW_INSTALL_DOCKER_CLI \
|
||||
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS
|
||||
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS \
|
||||
OPENCLAW_INSTALL_BROWSER
|
||||
|
||||
if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then
|
||||
echo "==> Building Docker image: $IMAGE_NAME"
|
||||
docker build \
|
||||
--build-arg "OPENCLAW_DOCKER_APT_PACKAGES=${OPENCLAW_DOCKER_APT_PACKAGES}" \
|
||||
--build-arg "OPENCLAW_INSTALL_DOCKER_CLI=${OPENCLAW_INSTALL_DOCKER_CLI:-}" \
|
||||
--build-arg "OPENCLAW_INSTALL_BROWSER=${OPENCLAW_INSTALL_BROWSER}" \
|
||||
-t "$IMAGE_NAME" \
|
||||
-f "$ROOT_DIR/Dockerfile" \
|
||||
"$ROOT_DIR"
|
||||
|
||||
20
scripts/docker/gateway-entrypoint.sh
Normal file
20
scripts/docker/gateway-entrypoint.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "gateway-entrypoint requires root (set docker compose service user: root)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${OPENCLAW_ENABLE_CRON:-1}" = "1" ] && command -v cron >/dev/null 2>&1; then
|
||||
# Start cron daemon once; jobs are configured by user crontabs via `crontab`.
|
||||
if ! pgrep -x cron >/dev/null 2>&1; then
|
||||
cron
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v gosu >/dev/null 2>&1; then
|
||||
exec gosu node "$@"
|
||||
fi
|
||||
|
||||
exec su -s /bin/sh node -c "$*"
|
||||
108
scripts/openclaw-keepawake.sh
Executable file
108
scripts/openclaw-keepawake.sh
Executable file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PID_FILE="${OPENCLAW_KEEPAWAKE_PID_FILE:-$HOME/.openclaw/run/keepawake.pid}"
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/openclaw-keepawake.sh <on|off|status|restart>
|
||||
|
||||
Keeps macOS awake using `caffeinate` for long-running OpenClaw Docker sessions.
|
||||
|
||||
Commands:
|
||||
on Start keep-awake background process
|
||||
off Stop keep-awake background process
|
||||
status Show current keep-awake status
|
||||
restart Restart keep-awake background process
|
||||
USAGE
|
||||
}
|
||||
|
||||
ensure_caffeinate() {
|
||||
if ! command -v caffeinate >/dev/null 2>&1; then
|
||||
echo "caffeinate not found (this helper is for macOS)." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
read_pid() {
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
tr -d '[:space:]' <"$PID_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
is_caffeinate_pid() {
|
||||
local pid="$1"
|
||||
[[ -n "$pid" ]] || return 1
|
||||
kill -0 "$pid" >/dev/null 2>&1 || return 1
|
||||
local comm
|
||||
comm="$(ps -p "$pid" -o comm= 2>/dev/null | tr -d '[:space:]')"
|
||||
[[ "$comm" == "caffeinate" ]]
|
||||
}
|
||||
|
||||
start_awake() {
|
||||
ensure_caffeinate
|
||||
mkdir -p "$(dirname "$PID_FILE")"
|
||||
|
||||
local pid
|
||||
pid="$(read_pid || true)"
|
||||
if is_caffeinate_pid "$pid"; then
|
||||
echo "keep-awake already on (pid $pid)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
caffeinate -dimsu >/dev/null 2>&1 &
|
||||
pid="$!"
|
||||
echo "$pid" >"$PID_FILE"
|
||||
echo "keep-awake on (pid $pid)"
|
||||
}
|
||||
|
||||
stop_awake() {
|
||||
local pid
|
||||
pid="$(read_pid || true)"
|
||||
if [[ -z "$pid" ]]; then
|
||||
echo "keep-awake already off"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if is_caffeinate_pid "$pid"; then
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
echo "keep-awake off (stopped pid $pid)"
|
||||
else
|
||||
echo "keep-awake off (stale pid file removed)"
|
||||
fi
|
||||
rm -f "$PID_FILE"
|
||||
}
|
||||
|
||||
status_awake() {
|
||||
local pid
|
||||
pid="$(read_pid || true)"
|
||||
if is_caffeinate_pid "$pid"; then
|
||||
echo "keep-awake is on (pid $pid)"
|
||||
else
|
||||
echo "keep-awake is off"
|
||||
if [[ -n "$pid" ]]; then
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
case "$cmd" in
|
||||
on)
|
||||
start_awake
|
||||
;;
|
||||
off)
|
||||
stop_awake
|
||||
;;
|
||||
status)
|
||||
status_awake
|
||||
;;
|
||||
restart)
|
||||
stop_awake
|
||||
start_awake
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Loading…
x
Reference in New Issue
Block a user