anurag008w commited on
Commit
b1059ef
Β·
1 Parent(s): 49cc5fe

Align startup scripts with shell wrappers

Browse files
Files changed (3) hide show
  1. Dockerfile +11 -2
  2. README.md +44 -0
  3. start.sh +358 -12
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,50 @@ 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. 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.
202
+ 4. Package files are not persisted; commands are replayed to reconstruct them after restart.
203
+
204
+ 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.
205
+
206
+ 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`.
207
+
208
+ > [!IMPORTANT]
209
+ > `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`.
210
+
211
  ## πŸ’“ Staying Alive *(Recommended on Free HF Spaces)*
212
 
213
  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
@@ -537,26 +548,167 @@ if [ -n "${CLOUDFLARE_WORKERS_TOKEN:-}" ]; then
537
  fi
538
 
539
  # ── Write shell capture wrappers to .bashrc ──
540
- STARTUP_FILE="/home/node/.openclaw/workspace/startup.sh"
 
 
 
 
 
 
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
- command apt-get "$@"
549
- [[ "$1" == "install" ]] && _hc_append "apt-get install -y ${@:2}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  }
551
  apt() {
552
- command apt "$@"
553
- [[ "$1" == "install" ]] && _hc_append "apt-get install -y ${@:2}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 +732,199 @@ 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 +933,11 @@ 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
- bash "$STARTUP_FILE" || echo "Warning: startup.sh had errors, continuing..."
593
- echo "Startup script complete."
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
 
548
  fi
549
 
550
  # ── Write shell capture wrappers to .bashrc ──
551
+ # The wrappers persist only install commands, not downloaded package files.
552
+ # On the next boot the synced workspace/startup.sh replays those commands.
553
+ if [ ! -f "$STARTUP_FILE" ]; then
554
+ touch "$STARTUP_FILE"
555
+ chmod +x "$STARTUP_FILE"
556
+ echo "Created workspace/startup.sh"
557
+ fi
558
  cat > /home/node/.bashrc << 'BASHRC'
559
+ export PATH="/home/node/.local/bin:$PATH"
560
+ export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/home/node/.local}"
561
+ export npm_config_prefix="$NPM_CONFIG_PREFIX"
562
+ export PYTHONUSERBASE="${PYTHONUSERBASE:-/home/node/.local}"
563
+ export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
564
  STARTUP_FILE="/home/node/.openclaw/workspace/startup.sh"
565
  _hc_append() {
566
  local line="$*"
567
+ mkdir -p "$(dirname "$STARTUP_FILE")"
568
+ touch "$STARTUP_FILE"
569
+ chmod +x "$STARTUP_FILE" 2>/dev/null || true
570
  grep -qxF "$line" "$STARTUP_FILE" 2>/dev/null || echo "$line" >> "$STARTUP_FILE"
571
  }
572
+ _hc_quote_args() {
573
+ local quoted=()
574
+ local arg
575
+ for arg in "$@"; do
576
+ printf -v arg '%q' "$arg"
577
+ quoted+=("$arg")
578
+ done
579
+ printf '%s' "${quoted[*]}"
580
+ }
581
+ _hc_append_cmd() {
582
+ local cmd="$1"
583
+ shift
584
+ local args
585
+ args=$(_hc_quote_args "$@")
586
+ if [ -n "$args" ]; then
587
+ _hc_append "$cmd $args"
588
+ else
589
+ _hc_append "$cmd"
590
+ fi
591
+ }
592
+ _hc_has_arg() {
593
+ local needle="$1"
594
+ shift
595
+ local arg
596
+ for arg in "$@"; do
597
+ [ "$arg" = "$needle" ] && return 0
598
+ done
599
+ return 1
600
+ }
601
+ _hc_can_sudo_apt() {
602
+ command -v sudo >/dev/null 2>&1 && sudo -n apt-get --version >/dev/null 2>&1
603
+ }
604
+ _hc_apt_install() {
605
+ if [ "$(id -u)" -eq 0 ]; then
606
+ command apt-get update && command apt-get install -y "$@"
607
+ elif _hc_can_sudo_apt; then
608
+ sudo apt-get update && sudo apt-get install -y "$@"
609
+ else
610
+ echo "Error: apt install needs root. Rebuild with the latest HuggingClaw image or add packages to Dockerfile." >&2
611
+ return 1
612
+ fi
613
+ }
614
  apt-get() {
615
+ case "${1:-}" in
616
+ install)
617
+ shift
618
+ _hc_apt_install "$@"
619
+ local rc=$?
620
+ if [ $rc -eq 0 ]; then
621
+ _hc_append_cmd "sudo apt-get update && sudo apt-get install -y" "$@"
622
+ fi
623
+ return $rc
624
+ ;;
625
+ update)
626
+ if [ "$(id -u)" -eq 0 ]; then
627
+ command apt-get "$@"
628
+ elif _hc_can_sudo_apt; then
629
+ sudo apt-get "$@"
630
+ else
631
+ command apt-get "$@"
632
+ fi
633
+ return $?
634
+ ;;
635
+ *)
636
+ command apt-get "$@"
637
+ return $?
638
+ ;;
639
+ esac
640
  }
641
  apt() {
642
+ case "${1:-}" in
643
+ install)
644
+ shift
645
+ _hc_apt_install "$@"
646
+ local rc=$?
647
+ if [ $rc -eq 0 ]; then
648
+ _hc_append_cmd "sudo apt-get update && sudo apt-get install -y" "$@"
649
+ fi
650
+ return $rc
651
+ ;;
652
+ update)
653
+ if [ "$(id -u)" -eq 0 ]; then
654
+ command apt "$@"
655
+ elif _hc_can_sudo_apt; then
656
+ sudo apt "$@"
657
+ else
658
+ command apt "$@"
659
+ fi
660
+ return $?
661
+ ;;
662
+ *)
663
+ command apt "$@"
664
+ return $?
665
+ ;;
666
+ esac
667
+ }
668
+ pip() {
669
+ if [ "${1:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "$@" && ! _hc_has_arg --prefix "$@"; then
670
+ command pip install --user "${@:2}"
671
+ else
672
+ command pip "$@"
673
+ fi
674
+ local rc=$?
675
+ if [ $rc -eq 0 ] && [ "${1:-}" = "install" ]; then
676
+ _hc_append_cmd "python3 -m pip install --user" "${@:2}"
677
+ fi
678
+ return $rc
679
+ }
680
+ pip3() {
681
+ if [ "${1:-}" = "install" ] && [ -z "${VIRTUAL_ENV:-}" ] && ! _hc_has_arg --user "$@" && ! _hc_has_arg --prefix "$@"; then
682
+ command pip3 install --user "${@:2}"
683
+ else
684
+ command pip3 "$@"
685
+ fi
686
+ local rc=$?
687
+ if [ $rc -eq 0 ] && [ "${1:-}" = "install" ]; then
688
+ _hc_append_cmd "python3 -m pip install --user" "${@:2}"
689
+ fi
690
+ return $rc
691
+ }
692
+ npm() {
693
+ command npm "$@"
694
+ local rc=$?
695
+ if [ $rc -eq 0 ] && [ "${1:-}" = "install" ] && { [ "${2:-}" = "-g" ] || [ "${2:-}" = "--global" ]; }; then
696
+ _hc_append_cmd "npm install -g" "${@:3}"
697
+ fi
698
+ return $rc
699
+ }
700
+ openclaw() {
701
+ command openclaw "$@"
702
+ local rc=$?
703
+ if [ $rc -eq 0 ] && [ "${1:-}" = "plugins" ] && [ "${2:-}" = "install" ]; then
704
+ _hc_append_cmd "openclaw plugins install" "${@:3}"
705
+ fi
706
+ return $rc
707
  }
 
 
 
 
708
  BASHRC
709
+ cat > /home/node/.profile <<'PROFILE'
710
+ [ -f ~/.bashrc ] && . ~/.bashrc
711
+ PROFILE
712
  echo "Shell capture wrappers ready."
713
 
714
  # ── Re-install previously installed plugins ──
 
732
  fi
733
  fi
734
 
735
+ # ── Startup command runner ──
736
+ # Runs user-provided boot commands one by one so failures are visible in logs.
737
+ # By default failures are logged and boot continues; set
738
+ # HUGGINGCLAW_STARTUP_STRICT=true to fail the Space startup on any error.
739
+ HC_STARTUP_FAILURES=0
740
+ HC_STARTUP_STRICT_NORMALIZED=$(printf '%s' "${HUGGINGCLAW_STARTUP_STRICT:-false}" | tr '[:upper:]' '[:lower:]')
741
+ hc_run_startup_command() {
742
+ local source_label="$1"
743
+ local command_text="$2"
744
+ [ -n "$command_text" ] || return 0
745
+
746
+ echo "[startup:${source_label}] $command_text"
747
+ set +e
748
+ bash -lc "$command_text"
749
+ local rc=$?
750
+ set -e
751
+ if [ "$rc" -eq 0 ]; then
752
+ echo "[startup:${source_label}] ok"
753
+ return 0
754
+ fi
755
+
756
+ HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
757
+ echo "ERROR: startup command failed (${source_label}, exit ${rc}): $command_text" >&2
758
+ return "$rc"
759
+ }
760
+
761
+ hc_run_startup_script() {
762
+ local source_label="$1"
763
+ local script_text="$2"
764
+ [ -n "$script_text" ] || return 0
765
+
766
+ local script_file
767
+ script_file=$(mktemp "/tmp/huggingclaw-startup-${source_label//[^A-Za-z0-9_.-]/_}.XXXXXX.sh")
768
+ {
769
+ # Load HuggingClaw's install wrappers for env-provided scripts too, so
770
+ # `apt install`, `pip install`, `npm install -g`, and OpenClaw plugin
771
+ # installs behave the same way as they do in the interactive shell.
772
+ echo '[ -f /home/node/.bashrc ] && . /home/node/.bashrc'
773
+ printf '%s\n' "$script_text"
774
+ } > "$script_file"
775
+ chmod 700 "$script_file"
776
+
777
+ echo "[startup:${source_label}] running script (${script_file})"
778
+ set +e
779
+ bash "$script_file"
780
+ local rc=$?
781
+ set -e
782
+ rm -f "$script_file"
783
+
784
+ if [ "$rc" -eq 0 ]; then
785
+ echo "[startup:${source_label}] ok"
786
+ return 0
787
+ fi
788
+
789
+ HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
790
+ echo "ERROR: startup script failed (${source_label}, exit ${rc})" >&2
791
+ return "$rc"
792
+ }
793
+ hc_run_startup_script_b64() {
794
+ local source_label="$1"
795
+ local encoded_script="$2"
796
+ [ -n "$encoded_script" ] || return 0
797
+
798
+ local script_text
799
+ if ! script_text=$(printf '%s' "$encoded_script" | base64 -d 2>/dev/null); then
800
+ HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
801
+ echo "ERROR: startup script base64 decode failed (${source_label})" >&2
802
+ return 1
803
+ fi
804
+
805
+ hc_run_startup_script "$source_label" "$script_text"
806
+ }
807
+
808
+
809
+ hc_run_startup_auto() {
810
+ local source_label="$1"
811
+ local payload="$2"
812
+ [ -n "$payload" ] || return 0
813
+
814
+ if [[ "$payload" == base64:* ]]; then
815
+ hc_run_startup_script_b64 "$source_label" "${payload#base64:}"
816
+ elif [[ "$payload" == b64:* ]]; then
817
+ hc_run_startup_script_b64 "$source_label" "${payload#b64:}"
818
+ else
819
+ hc_run_startup_script "$source_label" "$payload"
820
+ fi
821
+ }
822
+
823
+ hc_run_command_block() {
824
+ local source_label="$1"
825
+ local command_block="$2"
826
+ local line
827
+ local index=0
828
+
829
+ while IFS= read -r line || [ -n "$line" ]; do
830
+ # Skip blank lines and comments so multi-line env vars can be documented.
831
+ [[ "$line" =~ ^[[:space:]]*$ ]] && continue
832
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
833
+
834
+ index=$((index + 1))
835
+ hc_run_startup_command "${source_label}[${index}]" "$line" || true
836
+ done <<< "$command_block"
837
+ }
838
+ hc_finish_startup_commands() {
839
+ if [ "$HC_STARTUP_FAILURES" -gt 0 ]; then
840
+ echo "ERROR: ${HC_STARTUP_FAILURES} startup command(s) failed. Check the log lines above." >&2
841
+ if [ "$HC_STARTUP_STRICT_NORMALIZED" = "true" ] || [ "$HC_STARTUP_STRICT_NORMALIZED" = "1" ] || [ "$HC_STARTUP_STRICT_NORMALIZED" = "yes" ]; then
842
+ echo "ERROR: HUGGINGCLAW_STARTUP_STRICT=true, stopping startup." >&2
843
+ exit 1
844
+ fi
845
+ fi
846
+ return 0
847
+ }
848
+
849
+ # ── Optional package install lists from HF Variables/Secrets ──
850
+ # These install package names every boot without persisting package files.
851
+ # Use them when you prefer HF Variables over editing workspace/startup.sh.
852
+ if [ -n "${HUGGINGCLAW_APT_PACKAGES:-}" ]; then
853
+ echo "Installing apt packages from HUGGINGCLAW_APT_PACKAGES..."
854
+ read -r -a HC_APT_PACKAGES <<< "$HUGGINGCLAW_APT_PACKAGES"
855
+ if command -v sudo >/dev/null 2>&1; then
856
+ if sudo apt-get update && sudo apt-get install -y "${HC_APT_PACKAGES[@]}"; then
857
+ echo "HUGGINGCLAW_APT_PACKAGES install complete."
858
+ else
859
+ HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
860
+ echo "ERROR: HUGGINGCLAW_APT_PACKAGES install failed: ${HUGGINGCLAW_APT_PACKAGES}" >&2
861
+ fi
862
+ else
863
+ HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
864
+ echo "ERROR: sudo is unavailable; HUGGINGCLAW_APT_PACKAGES install skipped" >&2
865
+ fi
866
+ fi
867
+ if [ -n "${HUGGINGCLAW_PIP_PACKAGES:-}" ]; then
868
+ echo "Installing Python packages from HUGGINGCLAW_PIP_PACKAGES..."
869
+ read -r -a HC_PIP_PACKAGES <<< "$HUGGINGCLAW_PIP_PACKAGES"
870
+ if python3 -m pip install --user "${HC_PIP_PACKAGES[@]}"; then
871
+ echo "HUGGINGCLAW_PIP_PACKAGES install complete."
872
+ else
873
+ HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
874
+ echo "ERROR: HUGGINGCLAW_PIP_PACKAGES install failed: ${HUGGINGCLAW_PIP_PACKAGES}" >&2
875
+ fi
876
+ fi
877
+ if [ -n "${HUGGINGCLAW_NPM_PACKAGES:-}" ]; then
878
+ echo "Installing global npm packages from HUGGINGCLAW_NPM_PACKAGES..."
879
+ read -r -a HC_NPM_PACKAGES <<< "$HUGGINGCLAW_NPM_PACKAGES"
880
+ if npm install -g "${HC_NPM_PACKAGES[@]}"; then
881
+ echo "HUGGINGCLAW_NPM_PACKAGES install complete."
882
+ else
883
+ HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
884
+ echo "ERROR: HUGGINGCLAW_NPM_PACKAGES install failed: ${HUGGINGCLAW_NPM_PACKAGES}" >&2
885
+ fi
886
+ fi
887
+ if [ -n "${HUGGINGCLAW_OPENCLAW_PLUGINS:-}" ]; then
888
+ echo "Installing OpenClaw plugins from HUGGINGCLAW_OPENCLAW_PLUGINS..."
889
+ read -r -a HC_OPENCLAW_PLUGINS <<< "$HUGGINGCLAW_OPENCLAW_PLUGINS"
890
+ if openclaw plugins install "${HC_OPENCLAW_PLUGINS[@]}"; then
891
+ echo "HUGGINGCLAW_OPENCLAW_PLUGINS install complete."
892
+ else
893
+ HC_STARTUP_FAILURES=$((HC_STARTUP_FAILURES + 1))
894
+ echo "ERROR: HUGGINGCLAW_OPENCLAW_PLUGINS install failed: ${HUGGINGCLAW_OPENCLAW_PLUGINS}" >&2
895
+ fi
896
+ fi
897
+
898
+ # ── Arbitrary startup commands from HF Variables/Secrets ──
899
+ # Recommended: use one variable, HUGGINGCLAW_RUN, as a full bash script. If the
900
+ # value starts with base64: or b64:, the rest is decoded and run as the script.
901
+ # Legacy granular HUGGINGCLAW_STARTUP_* variables are still supported below.
902
+ if [ -n "${HUGGINGCLAW_RUN:-}" ]; then
903
+ hc_run_startup_auto "HUGGINGCLAW_RUN" "$HUGGINGCLAW_RUN" || true
904
+ fi
905
+ if [ -n "${HUGGINGCLAW_STARTUP_COMMANDS:-}" ]; then
906
+ echo "Running commands from HUGGINGCLAW_STARTUP_COMMANDS..."
907
+ hc_run_command_block "HUGGINGCLAW_STARTUP_COMMANDS" "$HUGGINGCLAW_STARTUP_COMMANDS"
908
+ fi
909
+ for HC_STARTUP_INDEX in $(seq 1 100); do
910
+ HC_STARTUP_VAR="HUGGINGCLAW_STARTUP_COMMAND_${HC_STARTUP_INDEX}"
911
+ if [ -n "${!HC_STARTUP_VAR:-}" ]; then
912
+ hc_run_startup_command "$HC_STARTUP_VAR" "${!HC_STARTUP_VAR}" || true
913
+ fi
914
+ done
915
+ if [ -n "${HUGGINGCLAW_STARTUP_SCRIPT:-}" ]; then
916
+ hc_run_startup_script "HUGGINGCLAW_STARTUP_SCRIPT" "$HUGGINGCLAW_STARTUP_SCRIPT" || true
917
+ fi
918
+ if [ -n "${HUGGINGCLAW_STARTUP_SCRIPT_B64:-}" ]; then
919
+ hc_run_startup_script_b64 "HUGGINGCLAW_STARTUP_SCRIPT_B64" "$HUGGINGCLAW_STARTUP_SCRIPT_B64" || true
920
+ fi
921
+ for HC_STARTUP_INDEX in $(seq 1 20); do
922
+ HC_STARTUP_VAR="HUGGINGCLAW_STARTUP_SCRIPT_B64_${HC_STARTUP_INDEX}"
923
+ if [ -n "${!HC_STARTUP_VAR:-}" ]; then
924
+ hc_run_startup_script_b64 "$HC_STARTUP_VAR" "${!HC_STARTUP_VAR}" || true
925
+ fi
926
+ done
927
+
928
  # ── Run workspace startup script ──
929
  STARTUP_FILE="/home/node/.openclaw/workspace/startup.sh"
930
  if [ ! -f "$STARTUP_FILE" ]; then
 
933
  echo "Created workspace/startup.sh"
934
  fi
935
  if [ -s "$STARTUP_FILE" ]; then
936
+ echo "Running workspace/startup.sh script..."
937
+ hc_run_startup_script "workspace/startup.sh" "$(cat "$STARTUP_FILE")" || true
938
+ echo "Workspace startup script complete."
939
  fi
940
+ hc_finish_startup_commands
941
 
942
  # ── Launch gateway ──
943
  echo "Launching OpenClaw gateway on port 7860..."