Spaces:
Running
Running
Commit Β·
c26fdc3
1
Parent(s): 49cc5fe
Allow startup installed plugins before gateway
Browse files- Dockerfile +11 -2
- README.md +45 -0
- start.sh +420 -14
Dockerfile
CHANGED
|
@@ -14,6 +14,7 @@ ARG OPENCLAW_VERSION=latest
|
|
| 14 |
# Install system dependencies
|
| 15 |
RUN apt-get update && apt-get install -y \
|
| 16 |
git \
|
|
|
|
| 17 |
ca-certificates \
|
| 18 |
jq \
|
| 19 |
curl \
|
|
@@ -44,9 +45,17 @@ RUN apt-get update && apt-get install -y \
|
|
| 44 |
pip3 install --no-cache-dir --break-system-packages huggingface_hub && \
|
| 45 |
rm -rf /var/lib/apt/lists/*
|
| 46 |
|
| 47 |
-
# Reuse existing node user (UID 1000)
|
|
|
|
|
|
|
| 48 |
RUN mkdir -p /home/node/app /home/node/.openclaw && \
|
| 49 |
-
chown -R 1000:1000 /home/node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
# Copy pre-built OpenClaw (skips npm install entirely β much faster!)
|
| 52 |
COPY --from=openclaw --chown=1000:1000 /app /home/node/.openclaw/openclaw-app
|
|
|
|
| 14 |
# Install system dependencies
|
| 15 |
RUN apt-get update && apt-get install -y \
|
| 16 |
git \
|
| 17 |
+
sudo \
|
| 18 |
ca-certificates \
|
| 19 |
jq \
|
| 20 |
curl \
|
|
|
|
| 45 |
pip3 install --no-cache-dir --break-system-packages huggingface_hub && \
|
| 46 |
rm -rf /var/lib/apt/lists/*
|
| 47 |
|
| 48 |
+
# Reuse existing node user (UID 1000). Allow passwordless package-manager
|
| 49 |
+
# commands only so runtime apt installs can be replayed after HF Space restarts
|
| 50 |
+
# without granting unrestricted sudo access.
|
| 51 |
RUN mkdir -p /home/node/app /home/node/.openclaw && \
|
| 52 |
+
chown -R 1000:1000 /home/node && \
|
| 53 |
+
printf '%s\n' \
|
| 54 |
+
'Cmnd_Alias HUGGINGCLAW_APT = /usr/bin/apt, /usr/bin/apt-get, /usr/bin/dpkg' \
|
| 55 |
+
'node ALL=(root) NOPASSWD: HUGGINGCLAW_APT' \
|
| 56 |
+
> /etc/sudoers.d/huggingclaw-apt && \
|
| 57 |
+
chmod 0440 /etc/sudoers.d/huggingclaw-apt && \
|
| 58 |
+
visudo -cf /etc/sudoers.d/huggingclaw-apt
|
| 59 |
|
| 60 |
# Copy pre-built OpenClaw (skips npm install entirely β much faster!)
|
| 61 |
COPY --from=openclaw --chown=1000:1000 /app /home/node/.openclaw/openclaw-app
|
README.md
CHANGED
|
@@ -164,6 +164,51 @@ HuggingClaw automatically syncs your workspace (chats, settings, sessions) to a
|
|
| 164 |
| `HF_TOKEN` | β | HF token with **Write** access |
|
| 165 |
| `SYNC_INTERVAL` | `180` | Backup frequency in seconds |
|
| 166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
## π Staying Alive *(Recommended on Free HF Spaces)*
|
| 168 |
|
| 169 |
Your Space will automatically be kept awake by a background Cloudflare Worker when you configure the `CLOUDFLARE_WORKERS_TOKEN` secret. The worker uses a cron trigger to regularly ping your Space's `/health` endpoint. The dashboard displays the current keep-alive worker status.
|
|
|
|
| 164 |
| `HF_TOKEN` | β | HF token with **Write** access |
|
| 165 |
| `SYNC_INTERVAL` | `180` | Backup frequency in seconds |
|
| 166 |
|
| 167 |
+
## π¦ Ephemeral Package Re-install *(Optional)*
|
| 168 |
+
|
| 169 |
+
Yes β you can use extra packages after a Space restart without storing package files. The easiest option is to remember **one variable**:
|
| 170 |
+
|
| 171 |
+
| Variable | What to put in it |
|
| 172 |
+
| :--- | :--- |
|
| 173 |
+
| `HUGGINGCLAW_RUN` | Any bash commands you want to run on every startup |
|
| 174 |
+
|
| 175 |
+
Example:
|
| 176 |
+
|
| 177 |
+
```bash
|
| 178 |
+
HUGGINGCLAW_RUN="""
|
| 179 |
+
set -e
|
| 180 |
+
sudo apt-get update
|
| 181 |
+
sudo apt-get install -y ffmpeg
|
| 182 |
+
python3 -m pip install --user pandas requests
|
| 183 |
+
npm install -g typescript
|
| 184 |
+
"""
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
For very quote-heavy or strange scripts, put a base64 script in the same variable:
|
| 188 |
+
|
| 189 |
+
```bash
|
| 190 |
+
# locally
|
| 191 |
+
base64 -w0 setup.sh
|
| 192 |
+
|
| 193 |
+
# HF Variable
|
| 194 |
+
HUGGINGCLAW_RUN=base64:<paste-output-here>
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
How it works:
|
| 198 |
+
|
| 199 |
+
1. `HUGGINGCLAW_RUN` is run as a full bash script on every boot before the OpenClaw gateway launches, so multi-line commands, `if`, loops, functions, and heredocs work. Long installs will delay gateway startup.
|
| 200 |
+
2. Startup scripts load the same HuggingClaw shell wrappers as the interactive shell, so `apt install ...`, `pip install ...`, `npm install -g ...`, and `openclaw plugins install ...` behave consistently.
|
| 201 |
+
3. OpenClaw plugins installed at startup are synced into `plugins.allow` before the gateway launches, so the gateway can load them instead of reporting them as not installed.
|
| 202 |
+
4. If you install from the OpenClaw shell manually, HuggingClaw records only successful install commands in `/home/node/.openclaw/workspace/startup.sh` for replay. Failed or dummy commands are not saved by the wrapper.
|
| 203 |
+
5. Package files are not persisted; commands are replayed to reconstruct them after restart.
|
| 204 |
+
|
| 205 |
+
Errors are always printed as `ERROR:` lines in Space logs. By default HuggingClaw logs the error and continues booting; set `HUGGINGCLAW_STARTUP_STRICT=true` if the Space should fail fast when any startup install command fails.
|
| 206 |
+
|
| 207 |
+
Advanced/backward-compatible variables still work if you prefer package-specific fields: `HUGGINGCLAW_APT_PACKAGES`, `HUGGINGCLAW_PIP_PACKAGES`, `HUGGINGCLAW_NPM_PACKAGES`, `HUGGINGCLAW_OPENCLAW_PLUGINS`, `HUGGINGCLAW_STARTUP_COMMANDS`, `HUGGINGCLAW_STARTUP_COMMAND_1`...`100`, `HUGGINGCLAW_STARTUP_SCRIPT`, and `HUGGINGCLAW_STARTUP_SCRIPT_B64`.
|
| 208 |
+
|
| 209 |
+
> [!IMPORTANT]
|
| 210 |
+
> `sudo` is available for package-manager commands only (`apt`, `apt-get`, and `dpkg`). This is enough for `sudo apt-get update` and `sudo apt-get install -y ...`, but it is not unrestricted root access. Apt-installed packages still disappear on Space restart, so put them in `HUGGINGCLAW_RUN` or let the shell wrapper record the command in `startup.sh`.
|
| 211 |
+
|
| 212 |
## π Staying Alive *(Recommended on Free HF Spaces)*
|
| 213 |
|
| 214 |
Your Space will automatically be kept awake by a background Cloudflare Worker when you configure the `CLOUDFLARE_WORKERS_TOKEN` secret. The worker uses a cron trigger to regularly ping your Space's `/health` endpoint. The dashboard displays the current keep-alive worker status.
|
start.sh
CHANGED
|
@@ -129,9 +129,20 @@ mkdir -p /home/node/.openclaw/credentials
|
|
| 129 |
mkdir -p /home/node/.openclaw/memory
|
| 130 |
mkdir -p /home/node/.openclaw/extensions
|
| 131 |
mkdir -p /home/node/.openclaw/workspace
|
|
|
|
| 132 |
chmod 700 /home/node/.openclaw
|
| 133 |
chmod 700 /home/node/.openclaw/credentials
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
# ββ Restore workspace/state from HF Dataset ββ
|
| 136 |
BACKUP_DATASET="${BACKUP_DATASET_NAME:-huggingclaw-backup}"
|
| 137 |
if [ -n "${HF_TOKEN:-}" ]; then
|
|
@@ -421,19 +432,36 @@ fi
|
|
| 421 |
|
| 422 |
# Write config
|
| 423 |
EXISTING_CONFIG="/home/node/.openclaw/openclaw.json"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
if [ -f "$EXISTING_CONFIG" ]; then
|
| 425 |
-
echo "Restored config found β patching required fields
|
| 426 |
PATCHED=$(jq \
|
| 427 |
--arg token "$GATEWAY_TOKEN" \
|
| 428 |
--arg model "$LLM_MODEL" \
|
| 429 |
--arg fileLevel "$OPENCLAW_FILE_LOG_LEVEL" \
|
| 430 |
--arg consoleLevel "$OPENCLAW_CONSOLE_LOG_LEVEL" \
|
| 431 |
--arg consoleStyle "$OPENCLAW_CONSOLE_LOG_STYLE" \
|
|
|
|
|
|
|
| 432 |
'.gateway.auth.token = $token
|
| 433 |
| .agents.defaults.model = $model
|
| 434 |
| .logging.level = $fileLevel
|
| 435 |
| .logging.consoleLevel = $consoleLevel
|
| 436 |
-
| .logging.consoleStyle = $consoleStyle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
"$EXISTING_CONFIG" 2>/dev/null)
|
| 438 |
|
| 439 |
if [ -n "$PATCHED" ]; then
|
|
@@ -537,26 +565,190 @@ if [ -n "${CLOUDFLARE_WORKERS_TOKEN:-}" ]; then
|
|
| 537 |
fi
|
| 538 |
|
| 539 |
# ββ Write shell capture wrappers to .bashrc ββ
|
| 540 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
cat > /home/node/.bashrc << 'BASHRC'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 542 |
STARTUP_FILE="/home/node/.openclaw/workspace/startup.sh"
|
| 543 |
_hc_append() {
|
| 544 |
local line="$*"
|
|
|
|
|
|
|
|
|
|
| 545 |
grep -qxF "$line" "$STARTUP_FILE" 2>/dev/null || echo "$line" >> "$STARTUP_FILE"
|
| 546 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
apt-get() {
|
| 548 |
-
|
| 549 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
}
|
| 551 |
apt() {
|
| 552 |
-
|
| 553 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
}
|
| 555 |
-
pip() { command pip "$@"; [[ "$1" == "install" ]] && _hc_append "pip install ${@:2}"; }
|
| 556 |
-
pip3() { command pip3 "$@"; [[ "$1" == "install" ]] && _hc_append "pip3 install ${@:2}"; }
|
| 557 |
-
npm() { command npm "$@"; [[ "$1" == "install" && "$2" == "-g" ]] && _hc_append "npm install -g ${@:3}"; }
|
| 558 |
-
openclaw() { command openclaw "$@"; [[ "$1" == "plugins" && "$2" == "install" ]] && _hc_append "openclaw plugins install ${@:3}"; }
|
| 559 |
BASHRC
|
|
|
|
|
|
|
|
|
|
| 560 |
echo "Shell capture wrappers ready."
|
| 561 |
|
| 562 |
# ββ Re-install previously installed plugins ββ
|
|
@@ -580,6 +772,218 @@ if [ -f "$EXISTING_CONFIG" ]; then
|
|
| 580 |
fi
|
| 581 |
fi
|
| 582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
# ββ Run workspace startup script ββ
|
| 584 |
STARTUP_FILE="/home/node/.openclaw/workspace/startup.sh"
|
| 585 |
if [ ! -f "$STARTUP_FILE" ]; then
|
|
@@ -588,10 +992,12 @@ if [ ! -f "$STARTUP_FILE" ]; then
|
|
| 588 |
echo "Created workspace/startup.sh"
|
| 589 |
fi
|
| 590 |
if [ -s "$STARTUP_FILE" ]; then
|
| 591 |
-
echo "Running workspace/startup.sh..."
|
| 592 |
-
|
| 593 |
-
echo "
|
| 594 |
fi
|
|
|
|
|
|
|
| 595 |
|
| 596 |
# ββ Launch gateway ββ
|
| 597 |
echo "Launching OpenClaw gateway on port 7860..."
|
|
|
|
| 129 |
mkdir -p /home/node/.openclaw/memory
|
| 130 |
mkdir -p /home/node/.openclaw/extensions
|
| 131 |
mkdir -p /home/node/.openclaw/workspace
|
| 132 |
+
mkdir -p /home/node/.local/bin /home/node/.local/lib /home/node/.npm-global
|
| 133 |
chmod 700 /home/node/.openclaw
|
| 134 |
chmod 700 /home/node/.openclaw/credentials
|
| 135 |
|
| 136 |
+
# User-installed packages are intentionally ephemeral in the container. Keep
|
| 137 |
+
# npm/pip installs in user-writable locations, make apt noninteractive,
|
| 138 |
+
# and persist only a tiny replay script in the synced workspace so packages
|
| 139 |
+
# are re-installed after restart.
|
| 140 |
+
export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/home/node/.local}"
|
| 141 |
+
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 142 |
+
export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
| 143 |
+
export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
|
| 144 |
+
STARTUP_FILE="/home/node/.openclaw/workspace/startup.sh"
|
| 145 |
+
|
| 146 |
# ββ Restore workspace/state from HF Dataset ββ
|
| 147 |
BACKUP_DATASET="${BACKUP_DATASET_NAME:-huggingclaw-backup}"
|
| 148 |
if [ -n "${HF_TOKEN:-}" ]; then
|
|
|
|
| 432 |
|
| 433 |
# Write config
|
| 434 |
EXISTING_CONFIG="/home/node/.openclaw/openclaw.json"
|
| 435 |
+
WHATSAPP_CONFIG_ENABLED=false
|
| 436 |
+
if [ "$WHATSAPP_ENABLED_NORMALIZED" = "true" ]; then
|
| 437 |
+
WHATSAPP_CONFIG_ENABLED=true
|
| 438 |
+
fi
|
| 439 |
if [ -f "$EXISTING_CONFIG" ]; then
|
| 440 |
+
echo "Restored config found β patching required fields and runtime channel/plugin toggles..."
|
| 441 |
PATCHED=$(jq \
|
| 442 |
--arg token "$GATEWAY_TOKEN" \
|
| 443 |
--arg model "$LLM_MODEL" \
|
| 444 |
--arg fileLevel "$OPENCLAW_FILE_LOG_LEVEL" \
|
| 445 |
--arg consoleLevel "$OPENCLAW_CONSOLE_LOG_LEVEL" \
|
| 446 |
--arg consoleStyle "$OPENCLAW_CONSOLE_LOG_STYLE" \
|
| 447 |
+
--argjson desired "$CONFIG_JSON" \
|
| 448 |
+
--argjson whatsappEnabled "$WHATSAPP_CONFIG_ENABLED" \
|
| 449 |
'.gateway.auth.token = $token
|
| 450 |
| .agents.defaults.model = $model
|
| 451 |
| .logging.level = $fileLevel
|
| 452 |
| .logging.consoleLevel = $consoleLevel
|
| 453 |
+
| .logging.consoleStyle = $consoleStyle
|
| 454 |
+
| .channels = ((.channels // {}) * ($desired.channels // {}))
|
| 455 |
+
| .plugins.allow = (((.plugins.allow // []) + ($desired.plugins.allow // [])) | unique)
|
| 456 |
+
| .plugins.deny = (((.plugins.deny // []) + ($desired.plugins.deny // [])) | unique)
|
| 457 |
+
| .plugins.entries = ((.plugins.entries // {}) * ($desired.plugins.entries // {}))
|
| 458 |
+
| if $whatsappEnabled then
|
| 459 |
+
.plugins.entries.whatsapp.enabled = true
|
| 460 |
+
| .channels.whatsapp = ($desired.channels.whatsapp // {"dmPolicy": "pairing"})
|
| 461 |
+
else
|
| 462 |
+
.plugins.entries.whatsapp.enabled = false
|
| 463 |
+
| del(.channels.whatsapp)
|
| 464 |
+
end' \
|
| 465 |
"$EXISTING_CONFIG" 2>/dev/null)
|
| 466 |
|
| 467 |
if [ -n "$PATCHED" ]; then
|
|
|
|
| 565 |
fi
|
| 566 |
|
| 567 |
# ββ Write shell capture wrappers to .bashrc ββ
|
| 568 |
+
# The wrappers persist only install commands, not downloaded package files.
|
| 569 |
+
# On the next boot the synced workspace/startup.sh replays those commands.
|
| 570 |
+
if [ ! -f "$STARTUP_FILE" ]; then
|
| 571 |
+
touch "$STARTUP_FILE"
|
| 572 |
+
chmod +x "$STARTUP_FILE"
|
| 573 |
+
echo "Created workspace/startup.sh"
|
| 574 |
+
fi
|
| 575 |
cat > /home/node/.bashrc << 'BASHRC'
|
| 576 |
+
export PATH="/home/node/.local/bin:$PATH"
|
| 577 |
+
export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/home/node/.local}"
|
| 578 |
+
export npm_config_prefix="$NPM_CONFIG_PREFIX"
|
| 579 |
+
export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
|
| 580 |
+
export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
|
| 581 |
STARTUP_FILE="/home/node/.openclaw/workspace/startup.sh"
|
| 582 |
_hc_append() {
|
| 583 |
local line="$*"
|
| 584 |
+
mkdir -p "$(dirname "$STARTUP_FILE")"
|
| 585 |
+
touch "$STARTUP_FILE"
|
| 586 |
+
chmod +x "$STARTUP_FILE" 2>/dev/null || true
|
| 587 |
grep -qxF "$line" "$STARTUP_FILE" 2>/dev/null || echo "$line" >> "$STARTUP_FILE"
|
| 588 |
}
|
| 589 |
+
_hc_quote_args() {
|
| 590 |
+
local quoted=()
|
| 591 |
+
local arg
|
| 592 |
+
for arg in "$@"; do
|
| 593 |
+
printf -v arg '%q' "$arg"
|
| 594 |
+
quoted+=("$arg")
|
| 595 |
+
done
|
| 596 |
+
printf '%s' "${quoted[*]}"
|
| 597 |
+
}
|
| 598 |
+
_hc_append_cmd() {
|
| 599 |
+
local cmd="$1"
|
| 600 |
+
shift
|
| 601 |
+
local args
|
| 602 |
+
args=$(_hc_quote_args "$@")
|
| 603 |
+
if [ -n "$args" ]; then
|
| 604 |
+
_hc_append "$cmd $args"
|
| 605 |
+
else
|
| 606 |
+
_hc_append "$cmd"
|
| 607 |
+
fi
|
| 608 |
+
}
|
| 609 |
+
_hc_allow_openclaw_plugins() {
|
| 610 |
+
local config="/home/node/.openclaw/openclaw.json"
|
| 611 |
+
[ -f "$config" ] || return 0
|
| 612 |
+
|
| 613 |
+
local plugins=()
|
| 614 |
+
local plugin
|
| 615 |
+
for plugin in "$@"; do
|
| 616 |
+
[ -n "$plugin" ] || continue
|
| 617 |
+
[[ "$plugin" == -* ]] && continue
|
| 618 |
+
plugins+=("$plugin")
|
| 619 |
+
if [[ "$plugin" == @openclaw/* ]]; then
|
| 620 |
+
plugins+=("${plugin#@openclaw/}")
|
| 621 |
+
fi
|
| 622 |
+
done
|
| 623 |
+
[ "${#plugins[@]}" -gt 0 ] || return 0
|
| 624 |
+
|
| 625 |
+
local plugins_json
|
| 626 |
+
plugins_json=$(printf '%s\n' "${plugins[@]}" | jq -R 'select(length > 0)' | jq -s 'unique') || return 0
|
| 627 |
+
jq --argjson plugins "$plugins_json" \
|
| 628 |
+
'.plugins.allow = (((.plugins.allow // []) + $plugins) | unique)' \
|
| 629 |
+
"$config" > "$config.tmp" && mv "$config.tmp" "$config"
|
| 630 |
+
}
|
| 631 |
+
_hc_has_arg() {
|
| 632 |
+
local needle="$1"
|
| 633 |
+
shift
|
| 634 |
+
local arg
|
| 635 |
+
for arg in "$@"; do
|
| 636 |
+
[ "$arg" = "$needle" ] && return 0
|
| 637 |
+
done
|
| 638 |
+
return 1
|
| 639 |
+
}
|
| 640 |
+
_hc_can_sudo_apt() {
|
| 641 |
+
command -v sudo >/dev/null 2>&1 && sudo -n apt-get --version >/dev/null 2>&1
|
| 642 |
+
}
|
| 643 |
+
_hc_apt_install() {
|
| 644 |
+
if [ "$(id -u)" -eq 0 ]; then
|
| 645 |
+
command apt-get update && command apt-get install -y "$@"
|
| 646 |
+
elif _hc_can_sudo_apt; then
|
| 647 |
+
sudo apt-get update && sudo apt-get install -y "$@"
|
| 648 |
+
else
|
| 649 |
+
echo "Error: apt install needs root. Rebuild with the latest HuggingClaw image or add packages to Dockerfile." >&2
|
| 650 |
+
return 1
|
| 651 |
+
fi
|
| 652 |
+
}
|
| 653 |
apt-get() {
|
| 654 |
+
case "${1:-}" in
|
| 655 |
+
install)
|
| 656 |
+
shift
|
| 657 |
+
_hc_apt_install "$@"
|
| 658 |
+
local rc=$?
|
| 659 |
+
if [ $rc -eq 0 ]; then
|
| 660 |
+
_hc_append_cmd "sudo apt-get update && sudo apt-get install -y" "$@"
|
| 661 |
+
fi
|
| 662 |
+
return $rc
|
| 663 |
+
;;
|
| 664 |
+
update)
|
| 665 |
+
if [ "$(id -u)" -eq 0 ]; then
|
| 666 |
+
command apt-get "$@"
|
| 667 |
+
elif _hc_can_sudo_apt; then
|
| 668 |
+
sudo apt-get "$@"
|
| 669 |
+
else
|
| 670 |
+
command apt-get "$@"
|
| 671 |
+
fi
|
| 672 |
+
return $?
|
| 673 |
+
;;
|
| 674 |
+
*)
|
| 675 |
+
command apt-get "$@"
|
| 676 |
+
return $?
|
| 677 |
+
;;
|
| 678 |
+
esac
|
| 679 |
}
|
| 680 |
apt() {
|
| 681 |
+
case "${1:-}" in
|
| 682 |
+
install)
|
| 683 |
+
shift
|
| 684 |
+
_hc_apt_install "$@"
|
| 685 |
+
local rc=$?
|
| 686 |
+
if [ $rc -eq 0 ]; then
|
| 687 |
+
_hc_append_cmd "sudo apt-get update && sudo apt-get install -y" "$@"
|
| 688 |
+
fi
|
| 689 |
+
return $rc
|
| 690 |
+
;;
|
| 691 |
+
update)
|
| 692 |
+
if [ "$(id -u)" -eq 0 ]; then
|
| 693 |
+
command apt "$@"
|
| 694 |
+
elif _hc_can_sudo_apt; then
|
| 695 |
+
sudo apt "$@"
|
| 696 |
+
else
|
| 697 |
+
command apt "$@"
|
| 698 |
+
fi
|
| 699 |
+
return $?
|
| 700 |
+
;;
|
| 701 |
+
*)
|
| 702 |
+
command apt "$@"
|
| 703 |
+
return $?
|
| 704 |
+
;;
|
| 705 |
+
esac
|
| 706 |
+
}
|
| 707 |
+
pip() {
|
| 708 |
+
if [ "${1:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "$@" && ! _hc_has_arg --prefix "$@"; then
|
| 709 |
+
command pip install --user "${@:2}"
|
| 710 |
+
else
|
| 711 |
+
command pip "$@"
|
| 712 |
+
fi
|
| 713 |
+
local rc=$?
|
| 714 |
+
if [ $rc -eq 0 ] && [ "${1:-}" = "install" ]; then
|
| 715 |
+
_hc_append_cmd "python3 -m pip install --user" "${@:2}"
|
| 716 |
+
fi
|
| 717 |
+
return $rc
|
| 718 |
+
}
|
| 719 |
+
pip3() {
|
| 720 |
+
if [ "${1:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "$@" && ! _hc_has_arg --prefix "$@"; then
|
| 721 |
+
command pip3 install --user "${@:2}"
|
| 722 |
+
else
|
| 723 |
+
command pip3 "$@"
|
| 724 |
+
fi
|
| 725 |
+
local rc=$?
|
| 726 |
+
if [ $rc -eq 0 ] && [ "${1:-}" = "install" ]; then
|
| 727 |
+
_hc_append_cmd "python3 -m pip install --user" "${@:2}"
|
| 728 |
+
fi
|
| 729 |
+
return $rc
|
| 730 |
+
}
|
| 731 |
+
npm() {
|
| 732 |
+
command npm "$@"
|
| 733 |
+
local rc=$?
|
| 734 |
+
if [ $rc -eq 0 ] && [ "${1:-}" = "install" ] && { [ "${2:-}" = "-g" ] || [ "${2:-}" = "--global" ]; }; then
|
| 735 |
+
_hc_append_cmd "npm install -g" "${@:3}"
|
| 736 |
+
fi
|
| 737 |
+
return $rc
|
| 738 |
+
}
|
| 739 |
+
openclaw() {
|
| 740 |
+
command openclaw "$@"
|
| 741 |
+
local rc=$?
|
| 742 |
+
if [ $rc -eq 0 ] && [ "${1:-}" = "plugins" ] && [ "${2:-}" = "install" ]; then
|
| 743 |
+
_hc_allow_openclaw_plugins "${@:3}"
|
| 744 |
+
_hc_append_cmd "openclaw plugins install" "${@:3}"
|
| 745 |
+
fi
|
| 746 |
+
return $rc
|
| 747 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
BASHRC
|
| 749 |
+
cat > /home/node/.profile <<'PROFILE'
|
| 750 |
+
[ -f ~/.bashrc ] && . ~/.bashrc
|
| 751 |
+
PROFILE
|
| 752 |
echo "Shell capture wrappers ready."
|
| 753 |
|
| 754 |
# ββ Re-install previously installed plugins ββ
|
|
|
|
| 772 |
fi
|
| 773 |
fi
|
| 774 |
|
| 775 |
+
# ββ Startup command runner ββ
|
| 776 |
+
# Runs user-provided boot commands one by one so failures are visible in logs.
|
| 777 |
+
# By default failures are logged and boot continues; set
|
| 778 |
+
# HUGGINGCLAW_STARTUP_STRICT=true to fail the Space startup on any error.
|
| 779 |
+
HC_STARTUP_FAILURES=0
|
| 780 |
+
HC_STARTUP_STRICT_NORMALIZED=$(printf '%s' "${HUGGINGCLAW_STARTUP_STRICT:-false}" | tr '[:upper:]' '[:lower:]')
|
| 781 |
+
hc_run_startup_command() {
|
| 782 |
+
local source_label="$1"
|
| 783 |
+
local command_text="$2"
|
| 784 |
+
[ -n "$command_text" ] || return 0
|
| 785 |
+
|
| 786 |
+
echo "[startup:${source_label}] $command_text"
|
| 787 |
+
set +e
|
| 788 |
+
bash -lc "$command_text"
|
| 789 |
+
local rc=$?
|
| 790 |
+
set -e
|
| 791 |
+
if [ "$rc" -eq 0 ]; then
|
| 792 |
+
echo "[startup:${source_label}] ok"
|
| 793 |
+
return 0
|
| 794 |
+
fi
|
| 795 |
+
|
| 796 |
+
HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
|
| 797 |
+
echo "ERROR: startup command failed (${source_label}, exit ${rc}): $command_text" >&2
|
| 798 |
+
return "$rc"
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
hc_run_startup_script() {
|
| 802 |
+
local source_label="$1"
|
| 803 |
+
local script_text="$2"
|
| 804 |
+
[ -n "$script_text" ] || return 0
|
| 805 |
+
|
| 806 |
+
local script_file
|
| 807 |
+
script_file=$(mktemp "/tmp/huggingclaw-startup-${source_label//[^A-Za-z0-9_.-]/_}.XXXXXX.sh")
|
| 808 |
+
{
|
| 809 |
+
# Load HuggingClaw's install wrappers for env-provided scripts too, so
|
| 810 |
+
# `apt install`, `pip install`, `npm install -g`, and OpenClaw plugin
|
| 811 |
+
# installs behave the same way as they do in the interactive shell.
|
| 812 |
+
echo '[ -f /home/node/.bashrc ] && . /home/node/.bashrc'
|
| 813 |
+
printf '%s\n' "$script_text"
|
| 814 |
+
} > "$script_file"
|
| 815 |
+
chmod 700 "$script_file"
|
| 816 |
+
|
| 817 |
+
echo "[startup:${source_label}] running script (${script_file})"
|
| 818 |
+
set +e
|
| 819 |
+
bash "$script_file"
|
| 820 |
+
local rc=$?
|
| 821 |
+
set -e
|
| 822 |
+
rm -f "$script_file"
|
| 823 |
+
|
| 824 |
+
if [ "$rc" -eq 0 ]; then
|
| 825 |
+
echo "[startup:${source_label}] ok"
|
| 826 |
+
return 0
|
| 827 |
+
fi
|
| 828 |
+
|
| 829 |
+
HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
|
| 830 |
+
echo "ERROR: startup script failed (${source_label}, exit ${rc})" >&2
|
| 831 |
+
return "$rc"
|
| 832 |
+
}
|
| 833 |
+
hc_run_startup_script_b64() {
|
| 834 |
+
local source_label="$1"
|
| 835 |
+
local encoded_script="$2"
|
| 836 |
+
[ -n "$encoded_script" ] || return 0
|
| 837 |
+
|
| 838 |
+
local script_text
|
| 839 |
+
if ! script_text=$(printf '%s' "$encoded_script" | base64 -d 2>/dev/null); then
|
| 840 |
+
HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
|
| 841 |
+
echo "ERROR: startup script base64 decode failed (${source_label})" >&2
|
| 842 |
+
return 1
|
| 843 |
+
fi
|
| 844 |
+
|
| 845 |
+
hc_run_startup_script "$source_label" "$script_text"
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
|
| 849 |
+
hc_run_startup_auto() {
|
| 850 |
+
local source_label="$1"
|
| 851 |
+
local payload="$2"
|
| 852 |
+
[ -n "$payload" ] || return 0
|
| 853 |
+
|
| 854 |
+
if [[ "$payload" == base64:* ]]; then
|
| 855 |
+
hc_run_startup_script_b64 "$source_label" "${payload#base64:}"
|
| 856 |
+
elif [[ "$payload" == b64:* ]]; then
|
| 857 |
+
hc_run_startup_script_b64 "$source_label" "${payload#b64:}"
|
| 858 |
+
else
|
| 859 |
+
hc_run_startup_script "$source_label" "$payload"
|
| 860 |
+
fi
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
hc_run_command_block() {
|
| 864 |
+
local source_label="$1"
|
| 865 |
+
local command_block="$2"
|
| 866 |
+
local line
|
| 867 |
+
local index=0
|
| 868 |
+
|
| 869 |
+
while IFS= read -r line || [ -n "$line" ]; do
|
| 870 |
+
# Skip blank lines and comments so multi-line env vars can be documented.
|
| 871 |
+
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
|
| 872 |
+
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
| 873 |
+
|
| 874 |
+
index=$((index + 1))
|
| 875 |
+
hc_run_startup_command "${source_label}[${index}]" "$line" || true
|
| 876 |
+
done <<< "$command_block"
|
| 877 |
+
}
|
| 878 |
+
|
| 879 |
+
sync_installed_plugins_into_allow() {
|
| 880 |
+
local config="/home/node/.openclaw/openclaw.json"
|
| 881 |
+
[ -f "$config" ] || return 0
|
| 882 |
+
|
| 883 |
+
local patched
|
| 884 |
+
patched=$(jq '
|
| 885 |
+
(.plugins.installs // {}) as $installs
|
| 886 |
+
| ($installs | keys) as $installed
|
| 887 |
+
| ($installed | map(if startswith("@openclaw/") then sub("^@openclaw/"; "") else . end)) as $short
|
| 888 |
+
| .plugins.allow = (((.plugins.allow // []) + $installed + $short) | unique)
|
| 889 |
+
' "$config" 2>/dev/null) || {
|
| 890 |
+
echo "Warning: could not sync installed plugins into plugins.allow"
|
| 891 |
+
return 0
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
echo "$patched" > "$config.tmp" && mv "$config.tmp" "$config"
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
hc_finish_startup_commands() {
|
| 898 |
+
if [ "$HC_STARTUP_FAILURES" -gt 0 ]; then
|
| 899 |
+
echo "ERROR: ${HC_STARTUP_FAILURES} startup command(s) failed. Check the log lines above." >&2
|
| 900 |
+
if [ "$HC_STARTUP_STRICT_NORMALIZED" = "true" ] || [ "$HC_STARTUP_STRICT_NORMALIZED" = "1" ] || [ "$HC_STARTUP_STRICT_NORMALIZED" = "yes" ]; then
|
| 901 |
+
echo "ERROR: HUGGINGCLAW_STARTUP_STRICT=true, stopping startup." >&2
|
| 902 |
+
exit 1
|
| 903 |
+
fi
|
| 904 |
+
fi
|
| 905 |
+
return 0
|
| 906 |
+
}
|
| 907 |
+
|
| 908 |
+
# ββ Optional package install lists from HF Variables/Secrets ββ
|
| 909 |
+
# These install package names every boot without persisting package files.
|
| 910 |
+
# Use them when you prefer HF Variables over editing workspace/startup.sh.
|
| 911 |
+
if [ -n "${HUGGINGCLAW_APT_PACKAGES:-}" ]; then
|
| 912 |
+
echo "Installing apt packages from HUGGINGCLAW_APT_PACKAGES..."
|
| 913 |
+
read -r -a HC_APT_PACKAGES <<< "$HUGGINGCLAW_APT_PACKAGES"
|
| 914 |
+
if command -v sudo >/dev/null 2>&1; then
|
| 915 |
+
if sudo apt-get update && sudo apt-get install -y "${HC_APT_PACKAGES[@]}"; then
|
| 916 |
+
echo "HUGGINGCLAW_APT_PACKAGES install complete."
|
| 917 |
+
else
|
| 918 |
+
HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
|
| 919 |
+
echo "ERROR: HUGGINGCLAW_APT_PACKAGES install failed: ${HUGGINGCLAW_APT_PACKAGES}" >&2
|
| 920 |
+
fi
|
| 921 |
+
else
|
| 922 |
+
HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
|
| 923 |
+
echo "ERROR: sudo is unavailable; HUGGINGCLAW_APT_PACKAGES install skipped" >&2
|
| 924 |
+
fi
|
| 925 |
+
fi
|
| 926 |
+
if [ -n "${HUGGINGCLAW_PIP_PACKAGES:-}" ]; then
|
| 927 |
+
echo "Installing Python packages from HUGGINGCLAW_PIP_PACKAGES..."
|
| 928 |
+
read -r -a HC_PIP_PACKAGES <<< "$HUGGINGCLAW_PIP_PACKAGES"
|
| 929 |
+
if python3 -m pip install --user "${HC_PIP_PACKAGES[@]}"; then
|
| 930 |
+
echo "HUGGINGCLAW_PIP_PACKAGES install complete."
|
| 931 |
+
else
|
| 932 |
+
HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
|
| 933 |
+
echo "ERROR: HUGGINGCLAW_PIP_PACKAGES install failed: ${HUGGINGCLAW_PIP_PACKAGES}" >&2
|
| 934 |
+
fi
|
| 935 |
+
fi
|
| 936 |
+
if [ -n "${HUGGINGCLAW_NPM_PACKAGES:-}" ]; then
|
| 937 |
+
echo "Installing global npm packages from HUGGINGCLAW_NPM_PACKAGES..."
|
| 938 |
+
read -r -a HC_NPM_PACKAGES <<< "$HUGGINGCLAW_NPM_PACKAGES"
|
| 939 |
+
if npm install -g "${HC_NPM_PACKAGES[@]}"; then
|
| 940 |
+
echo "HUGGINGCLAW_NPM_PACKAGES install complete."
|
| 941 |
+
else
|
| 942 |
+
HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
|
| 943 |
+
echo "ERROR: HUGGINGCLAW_NPM_PACKAGES install failed: ${HUGGINGCLAW_NPM_PACKAGES}" >&2
|
| 944 |
+
fi
|
| 945 |
+
fi
|
| 946 |
+
if [ -n "${HUGGINGCLAW_OPENCLAW_PLUGINS:-}" ]; then
|
| 947 |
+
echo "Installing OpenClaw plugins from HUGGINGCLAW_OPENCLAW_PLUGINS..."
|
| 948 |
+
read -r -a HC_OPENCLAW_PLUGINS <<< "$HUGGINGCLAW_OPENCLAW_PLUGINS"
|
| 949 |
+
if openclaw plugins install "${HC_OPENCLAW_PLUGINS[@]}"; then
|
| 950 |
+
echo "HUGGINGCLAW_OPENCLAW_PLUGINS install complete."
|
| 951 |
+
else
|
| 952 |
+
HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
|
| 953 |
+
echo "ERROR: HUGGINGCLAW_OPENCLAW_PLUGINS install failed: ${HUGGINGCLAW_OPENCLAW_PLUGINS}" >&2
|
| 954 |
+
fi
|
| 955 |
+
fi
|
| 956 |
+
|
| 957 |
+
# ββ Arbitrary startup commands from HF Variables/Secrets ββ
|
| 958 |
+
# Recommended: use one variable, HUGGINGCLAW_RUN, as a full bash script. If the
|
| 959 |
+
# value starts with base64: or b64:, the rest is decoded and run as the script.
|
| 960 |
+
# Legacy granular HUGGINGCLAW_STARTUP_* variables are still supported below.
|
| 961 |
+
if [ -n "${HUGGINGCLAW_RUN:-}" ]; then
|
| 962 |
+
hc_run_startup_auto "HUGGINGCLAW_RUN" "$HUGGINGCLAW_RUN" || true
|
| 963 |
+
fi
|
| 964 |
+
if [ -n "${HUGGINGCLAW_STARTUP_COMMANDS:-}" ]; then
|
| 965 |
+
echo "Running commands from HUGGINGCLAW_STARTUP_COMMANDS..."
|
| 966 |
+
hc_run_command_block "HUGGINGCLAW_STARTUP_COMMANDS" "$HUGGINGCLAW_STARTUP_COMMANDS"
|
| 967 |
+
fi
|
| 968 |
+
for HC_STARTUP_INDEX in $(seq 1 100); do
|
| 969 |
+
HC_STARTUP_VAR="HUGGINGCLAW_STARTUP_COMMAND_${HC_STARTUP_INDEX}"
|
| 970 |
+
if [ -n "${!HC_STARTUP_VAR:-}" ]; then
|
| 971 |
+
hc_run_startup_command "$HC_STARTUP_VAR" "${!HC_STARTUP_VAR}" || true
|
| 972 |
+
fi
|
| 973 |
+
done
|
| 974 |
+
if [ -n "${HUGGINGCLAW_STARTUP_SCRIPT:-}" ]; then
|
| 975 |
+
hc_run_startup_script "HUGGINGCLAW_STARTUP_SCRIPT" "$HUGGINGCLAW_STARTUP_SCRIPT" || true
|
| 976 |
+
fi
|
| 977 |
+
if [ -n "${HUGGINGCLAW_STARTUP_SCRIPT_B64:-}" ]; then
|
| 978 |
+
hc_run_startup_script_b64 "HUGGINGCLAW_STARTUP_SCRIPT_B64" "$HUGGINGCLAW_STARTUP_SCRIPT_B64" || true
|
| 979 |
+
fi
|
| 980 |
+
for HC_STARTUP_INDEX in $(seq 1 20); do
|
| 981 |
+
HC_STARTUP_VAR="HUGGINGCLAW_STARTUP_SCRIPT_B64_${HC_STARTUP_INDEX}"
|
| 982 |
+
if [ -n "${!HC_STARTUP_VAR:-}" ]; then
|
| 983 |
+
hc_run_startup_script_b64 "$HC_STARTUP_VAR" "${!HC_STARTUP_VAR}" || true
|
| 984 |
+
fi
|
| 985 |
+
done
|
| 986 |
+
|
| 987 |
# ββ Run workspace startup script ββ
|
| 988 |
STARTUP_FILE="/home/node/.openclaw/workspace/startup.sh"
|
| 989 |
if [ ! -f "$STARTUP_FILE" ]; then
|
|
|
|
| 992 |
echo "Created workspace/startup.sh"
|
| 993 |
fi
|
| 994 |
if [ -s "$STARTUP_FILE" ]; then
|
| 995 |
+
echo "Running workspace/startup.sh script..."
|
| 996 |
+
hc_run_startup_script "workspace/startup.sh" "$(cat "$STARTUP_FILE")" || true
|
| 997 |
+
echo "Workspace startup script complete."
|
| 998 |
fi
|
| 999 |
+
hc_finish_startup_commands
|
| 1000 |
+
sync_installed_plugins_into_allow
|
| 1001 |
|
| 1002 |
# ββ Launch gateway ββ
|
| 1003 |
echo "Launching OpenClaw gateway on port 7860..."
|