diff --git a/Dockerfile b/Dockerfile index 48cffc1..6905d07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ strace \ file \ less \ - && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* @@ -99,6 +99,16 @@ RUN mkdir -p /data /data/.cache/pip /data/.cache/npm /data/.local && \ # Set working directory to the persistent volume WORKDIR /data +# Install startup and OpenClaw node helper scripts +COPY scripts/clawdbox-entrypoint /usr/local/bin/clawdbox-entrypoint +COPY scripts/start-clawdbox-node /usr/local/bin/start-clawdbox-node +COPY scripts/status-clawdbox-node /usr/local/bin/status-clawdbox-node +COPY scripts/stop-clawdbox-node /usr/local/bin/stop-clawdbox-node +RUN chmod +x /usr/local/bin/clawdbox-entrypoint \ + /usr/local/bin/start-clawdbox-node \ + /usr/local/bin/status-clawdbox-node \ + /usr/local/bin/stop-clawdbox-node + # Expose SSH port (non-privileged) EXPOSE 2222 @@ -119,16 +129,5 @@ RUN echo 'export PS1="\[\e[32m\]\u@clawdbox\[\e[m\]:\[\e[34m\]\w\[\e[m\]\$ "' >> echo 'export HISTSIZE=10000' >> /home/claw/.bashrc && \ echo 'export HISTFILESIZE=10000' >> /home/claw/.bashrc -# Start SSH daemon -# The keys are generated if they don't exist on the persistent volume -CMD ["/bin/bash", "-c", "\ -mkdir -p /data/ssh && \ -for keytype in rsa ecdsa ed25519; do \ - if [ ! -f /data/ssh/ssh_host_${keytype}_key ]; then \ - echo \"Generating persistent host ${keytype} key...\"; \ - ssh-keygen -q -f /data/ssh/ssh_host_${keytype}_key -N '' -t ${keytype}; \ - fi; \ -done && \ -chmod 600 /data/ssh/ssh_host_*_key && \ -ln -sf /data/.gitconfig ~/.gitconfig && \ -/usr/sbin/sshd -D -e -f /etc/ssh/sshd_config"] +# Start SSH in the foreground and opportunistically start the OpenClaw node host. +CMD ["/usr/local/bin/clawdbox-entrypoint"] diff --git a/README.md b/README.md index 1cceb14..a7f5a71 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,14 @@ This container provides a stable, tool-rich environment for the AI agent to: ## Tools Included - **Core:** curl, wget, git, jq, yq, unzip, tar, vim/nano, tree, less -- **Dev:** python3 (pip/venv), build-essential, nodejs (v20), npm +- **Dev:** python3 (pip/venv), build-essential, nodejs (v22), npm - **Python Libraries:** httpie, pyyaml, requests, black, ipython - **Network:** ping, dnsutils, net-tools, openssh-server/client, sshpass - **Media:** ffmpeg - **Monitoring:** htop, tmux, ncdu, strace - **Kubernetes:** OpenShift CLI (oc) - **Search:** ripgrep (fast grep alternative) +- **Agent node:** self-starting OpenClaw node host helper for `ClawdBox-CLI` ## Prerequisites @@ -76,6 +77,14 @@ The `/data` volume preserves data across container restarts: ``` /data/ ├── ssh/ # SSH host keys (auto-generated on first run) +├── agents/ +│ └── clawd/ # Durable OpenClaw agent workspace and node host +│ ├── bin/ # start/status/stop helpers and openclaw symlink +│ ├── logs/ # node-host logs and pid file +│ ├── notes/ # operator notes +│ ├── src/ # source checkouts and experiments +│ ├── tmp/ # scratch files +│ └── tools/ # persistent tool installs ├── scripts/ # Helper scripts (from ConfigMap, read-only) │ ├── disk-usage.sh │ ├── health-check.sh @@ -92,6 +101,38 @@ The `/data` volume preserves data across container restarts: **Storage:** 10Gi PersistentVolumeClaim (ReadWriteOnce) +## OpenClaw Node Host + +On container startup, ClawdBox keeps SSH as the foreground service and +opportunistically starts an OpenClaw node host named `ClawdBox-CLI` in the +background. + +The helper scripts are copied into `/data/agents/clawd/bin/` on each startup: + +```bash +start-clawdbox-node +status-clawdbox-node +stop-clawdbox-node +``` + +The node host installs `openclaw@2026.6.1` under +`/data/agents/clawd/tools/openclaw-node` if it is missing or stale. It gets the +Gateway token from `OPENCLAW_GATEWAY_TOKEN` when set, otherwise from the live +`openclaw` deployment via the mounted kubeconfig. + +Useful overrides: + +```bash +OPENCLAW_VERSION=2026.6.1 +OPENCLAW_GATEWAY_HOST=openclaw.apps.lab.apilab.us +OPENCLAW_GATEWAY_PORT=443 +OPENCLAW_GATEWAY_TLS=true +CLAWDBOX_NODE_DISPLAY_NAME=ClawdBox-CLI +``` + +If node startup fails, the pod still starts and SSH remains available. Check +`/data/agents/clawd/logs/node-host.log` for details. + ## Common Tasks ### Quick Operations (Using Makefile) diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index 745ee12..e3f9acb 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -41,6 +41,16 @@ data: fi echo "" + echo "=== OpenClaw Node Host ===" + if command -v status-clawdbox-node > /dev/null 2>&1; then + status-clawdbox-node + elif [ -x /data/agents/clawd/bin/status-clawdbox-node ]; then + /data/agents/clawd/bin/status-clawdbox-node + else + echo "status-clawdbox-node helper not found" + fi + echo "" + echo "=== Disk Space ===" df -h /data echo "" diff --git a/manifests/deployment.yaml b/manifests/deployment.yaml index 9d0b664..c51aedc 100644 --- a/manifests/deployment.yaml +++ b/manifests/deployment.yaml @@ -58,6 +58,16 @@ spec: value: noninteractive - name: TZ value: UTC + - name: OPENCLAW_VERSION + value: "2026.6.1" + - name: OPENCLAW_GATEWAY_HOST + value: openclaw.apps.lab.apilab.us + - name: OPENCLAW_GATEWAY_PORT + value: "443" + - name: OPENCLAW_GATEWAY_TLS + value: "true" + - name: CLAWDBOX_NODE_DISPLAY_NAME + value: ClawdBox-CLI startupProbe: tcpSocket: port: ssh diff --git a/scripts/clawdbox-entrypoint b/scripts/clawdbox-entrypoint new file mode 100644 index 0000000..2fab600 --- /dev/null +++ b/scripts/clawdbox-entrypoint @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=/data/agents/clawd + +mkdir -p /data/ssh /data/.cache/pip /data/.cache/npm /data/.local +mkdir -p "$ROOT/bin" "$ROOT/src" "$ROOT/tmp" "$ROOT/logs" "$ROOT/notes" "$ROOT/tools" + +for keytype in rsa ecdsa ed25519; do + if [ ! -f "/data/ssh/ssh_host_${keytype}_key" ]; then + echo "Generating persistent host ${keytype} key..." + ssh-keygen -q -f "/data/ssh/ssh_host_${keytype}_key" -N '' -t "$keytype" + fi +done +chmod 600 /data/ssh/ssh_host_*_key + +if [ -f /data/.gitconfig ]; then + ln -sf /data/.gitconfig "$HOME/.gitconfig" +fi + +cat > "$ROOT/README.md" <<'EOF' +# ClawdBox Agent Workspace + +Durable agent workspace for OpenClaw work that should survive pod restarts. + +- `bin/` helper commands +- `src/` source checkouts and experiments +- `tmp/` scratch files +- `logs/` process logs +- `notes/` operator notes +- `tools/` persistent tool installs +EOF + +for script in start-clawdbox-node status-clawdbox-node stop-clawdbox-node; do + cp "/usr/local/bin/${script}" "$ROOT/bin/${script}" +done + +export PATH="$ROOT/bin:/usr/local/bin:$PATH" + +start-clawdbox-node || { + echo "ClawdBox-CLI node host did not start; SSH will remain available." >&2 +} + +exec /usr/sbin/sshd -D -e -f /etc/ssh/sshd_config diff --git a/scripts/start-clawdbox-node b/scripts/start-clawdbox-node new file mode 100644 index 0000000..3a703f2 --- /dev/null +++ b/scripts/start-clawdbox-node @@ -0,0 +1,84 @@ +#!/usr/bin/env sh +set -eu + +ROOT=${CLAWDBOX_AGENT_ROOT:-/data/agents/clawd} +LOG_DIR="$ROOT/logs" +PID_FILE="$LOG_DIR/node-host.pid" +LOG_FILE="$LOG_DIR/node-host.log" +OPENCLAW_DIR="$ROOT/tools/openclaw-node" +OPENCLAW_VERSION=${OPENCLAW_VERSION:-2026.6.1} +DISPLAY_NAME=${CLAWDBOX_NODE_DISPLAY_NAME:-ClawdBox-CLI} +GATEWAY_HOST=${OPENCLAW_GATEWAY_HOST:-openclaw.apps.lab.apilab.us} +GATEWAY_PORT=${OPENCLAW_GATEWAY_PORT:-443} + +mkdir -p "$ROOT/bin" "$ROOT/tools" "$LOG_DIR" "$OPENCLAW_DIR" + +log() { + printf '%s %s\n' "$(date -Iseconds)" "$*" | tee -a "$LOG_FILE" +} + +if [ -f "$PID_FILE" ]; then + old=$(cat "$PID_FILE" 2>/dev/null || true) + case "$old" in + *[!0-9]*|"") ;; + *) + if kill -0 "$old" 2>/dev/null; then + echo "$DISPLAY_NAME already running: pid $old" + exit 0 + fi + ;; + esac +fi + +if [ ! -f "$OPENCLAW_DIR/package.json" ]; then + cat > "$OPENCLAW_DIR/package.json" </dev/null || true) +fi + +if [ "$installed_version" != "$OPENCLAW_VERSION" ]; then + log "Installing openclaw@$OPENCLAW_VERSION under $OPENCLAW_DIR" + npm install --prefix "$OPENCLAW_DIR" "openclaw@$OPENCLAW_VERSION" --omit=dev >> "$LOG_FILE" 2>&1 +fi + +OPENCLAW_BIN="$OPENCLAW_DIR/node_modules/.bin/openclaw" +if [ ! -x "$OPENCLAW_BIN" ]; then + log "OpenClaw binary not found at $OPENCLAW_BIN" + exit 1 +fi +ln -sf "$OPENCLAW_BIN" "$ROOT/bin/openclaw" + +TOKEN=${OPENCLAW_GATEWAY_TOKEN:-} +if [ -z "$TOKEN" ]; then + TOKEN=$(oc get deployment openclaw -n openclaw -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="OPENCLAW_GATEWAY_TOKEN")].value}' 2>/dev/null || true) +fi +if [ -z "$TOKEN" ]; then + log "OPENCLAW_GATEWAY_TOKEN is unavailable; skipping node-host startup" + exit 1 +fi + +TLS_FLAG="" +if [ "${OPENCLAW_GATEWAY_TLS:-true}" = "true" ]; then + TLS_FLAG="--tls" +fi + +log "Starting $DISPLAY_NAME against $GATEWAY_HOST:$GATEWAY_PORT" +OPENCLAW_GATEWAY_TOKEN="$TOKEN" nohup "$OPENCLAW_BIN" node run \ + --display-name "$DISPLAY_NAME" \ + --host "$GATEWAY_HOST" \ + --port "$GATEWAY_PORT" \ + $TLS_FLAG >> "$LOG_FILE" 2>&1 & +echo $! > "$PID_FILE" +sleep 1 +echo "Started $DISPLAY_NAME node host: pid $(cat "$PID_FILE")" diff --git a/scripts/status-clawdbox-node b/scripts/status-clawdbox-node new file mode 100644 index 0000000..abb14eb --- /dev/null +++ b/scripts/status-clawdbox-node @@ -0,0 +1,18 @@ +#!/usr/bin/env sh +set -eu + +ROOT=${CLAWDBOX_AGENT_ROOT:-/data/agents/clawd} +PID_FILE="$ROOT/logs/node-host.pid" +LOG_FILE="$ROOT/logs/node-host.log" + +if [ -f "$PID_FILE" ]; then + pid=$(cat "$PID_FILE" 2>/dev/null || true) + case "$pid" in + *[!0-9]*|"") echo "Invalid pid file: $pid" ;; + *) ps -p "$pid" -o pid,etime,cmd || true ;; + esac +else + echo "No pid file" +fi + +sed -n '1,120p' "$LOG_FILE" 2>/dev/null || true diff --git a/scripts/stop-clawdbox-node b/scripts/stop-clawdbox-node new file mode 100644 index 0000000..9754ae7 --- /dev/null +++ b/scripts/stop-clawdbox-node @@ -0,0 +1,23 @@ +#!/usr/bin/env sh +set -eu + +ROOT=${CLAWDBOX_AGENT_ROOT:-/data/agents/clawd} +PID_FILE="$ROOT/logs/node-host.pid" + +if [ ! -f "$PID_FILE" ]; then + echo "No pid file" + exit 0 +fi + +pid=$(cat "$PID_FILE" 2>/dev/null || true) +case "$pid" in + *[!0-9]*|"") + echo "Invalid pid file: $pid" + exit 1 + ;; + *) + kill "$pid" 2>/dev/null || true + rm -f "$PID_FILE" + echo "Stopped ClawdBox-CLI node host: pid $pid" + ;; +esac