Download scripts/openclaw-post-restore.sh from SZFSPC/apple: direct link, hf CLI and curl.
- Browser
- Download file 23.2 kB
-
https://huggingface.co/spaces/SZFSPC/apple/resolve/main/scripts/openclaw-post-restore.sh
- Command line
-
hf download hf://spaces/SZFSPC/apple/scripts/openclaw-post-restore.sh
-
curl -L -o openclaw-post-restore.sh https://huggingface.co/spaces/SZFSPC/apple/resolve/main/scripts/openclaw-post-restore.sh
23.2 kB
| # | |
| # openclaw-post-restore.sh | |
| # | |
| # Container-startup hook invoked by openclaw-entrypoint.sh right | |
| # after the backup restore + config-write steps. Anything that | |
| # needs to run with a freshly-restored state should live here, not | |
| # in openclaw-restore.sh (which is generic data) and not in | |
| # openclaw-entrypoint.sh (which is process management). | |
| # | |
| # Why a dedicated script? | |
| # - Keeps openclaw-restore.sh generic: the restore script only | |
| # deals with data; it has no business knowing about openclaw. | |
| # - Keeps openclaw-entrypoint.sh focused on orchestration: the | |
| # entrypoint calls this hook, but the body of "what to do | |
| # after restore" lives in its own file, easy to swap or | |
| # extend without touching the entrypoint. | |
| # - Lets operators customize per-deployment (different fleets | |
| # can drop their own version in /usr/local/bin/) without | |
| # forking the rest of the repo. | |
| # | |
| # Default behavior (good baseline, easy to override): | |
| # 1. Verify openclaw CLI is reachable | |
| # 2. Verify openclaw --version works | |
| # 3. Verify `openclaw channels list --json` returns valid JSON | |
| # 4. Log results | |
| # | |
| # Customize by editing the run_check_* functions below or appending | |
| # your own. The entrypoint's run_step wrapper will pick up any new | |
| # command you add to the do_all_checks chain. | |
| # | |
| # Idempotent β safe to run on every container start, even if no | |
| # restore actually happened (the checks are read-only and cheap). | |
| # | |
| # Environment variables: | |
| # OPENCLAW_POST_RESTORE_ENABLED default: false (opt-in) | |
| # set to "true" to actually run | |
| # any of the operations below. | |
| # This script makes outbound | |
| # calls (npm update, plugin | |
| # install/uninstall, doctor | |
| # --fix) and is intentionally | |
| # NOT enabled by default β | |
| # operators must turn it on | |
| # per deployment. | |
| # OPENCLAW_POST_RESTORE_FAIL_ON_ERROR default: false | |
| # if "true", any check failure | |
| # returns exit 1, which | |
| # propagates through the | |
| # entrypoint's run_step and | |
| # aborts startup | |
| # OPENCLAW_BIN default: /usr/local/bin/openclaw | |
| # | |
| # OpenClaw 2026.6.x compatibility notes (as of 2026.6.1): | |
| # * `openclaw channels list --json` layout changed: | |
| # - 2026.6.x: top-level key per channel (e.g. ".feishu", | |
| # ".qqbot") with account-centric installed/configured/enabled. | |
| # Auth providers + model usage were moved out of channels list | |
| # (use `openclaw models auth list` / `openclaw status`). | |
| # - <2026.6: nested under ".chat.<name>" with channel-level | |
| # .installed + .accounts list. | |
| # The health probes below accept ALL three known layouts | |
| # (top-level / .chat.<name> / .channels[] array) so the script | |
| # works on either generation. | |
| # * `openclaw plugins install` source prefixes were introduced | |
| # (clawhub:, npm:, git:, npm-pack:). Bare scoped specs like | |
| # `@openclaw/qqbot@latest` still resolve to the bundled copy. | |
| # The reinstall flow uses `@openclaw/qqbot@latest` for a | |
| # deterministic latest-version pin. | |
| # * `openclaw plugins update --all` is unchanged and still safe to | |
| # invoke on every container start. | |
| # * `openclaw doctor --fix` gained a disk space probe and now | |
| # produces more stable JSON output post-upgrade. | |
| set -uo pipefail | |
| OPENCLAW_ENTRYPOINT_TAG="openclaw-post-restore" | |
| timestamp_utc() { | |
| date -u +"%Y-%m-%dT%H:%M:%SZ" | |
| } | |
| log_info() { printf '[%s] [INFO] [PID:%s] %s: %s\n' "$(timestamp_utc)" "$$" "$OPENCLAW_ENTRYPOINT_TAG" "$*"; } | |
| log_warn() { printf '[%s] [WARN] [PID:%s] %s: %s\n' "$(timestamp_utc)" "$$" "$OPENCLAW_ENTRYPOINT_TAG" "$*" >&2; } | |
| log_error() { printf '[%s] [ERROR] [PID:%s] %s: %s\n' "$(timestamp_utc)" "$$" "$OPENCLAW_ENTRYPOINT_TAG" "$*" >&2; } | |
| is_true() { | |
| local value | |
| value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" | |
| [[ "$value" == "1" || "$value" == "true" || "$value" == "yes" || "$value" == "on" ]] | |
| } | |
| # ---- OpenClaw 2026.6.x compatibility helpers ---- | |
| # OpenClaw 2026.6.1 changed the structure of `openclaw channels list --json`: | |
| # - 2026.6.x: top-level key per channel (".feishu", ".qqbot"), with | |
| # account-centric installed/configured/enabled status. The | |
| # `installed` flag is now per-account, not per-channel. | |
| # - <2026.6: nested under ".chat.<name>" with channel-level | |
| # .installed + .accounts list. | |
| # - Some builds also surface an array form ".channels[]". | |
| # The helpers below probe the JSON once, cache it, and resolve the | |
| # schema variant transparently so the rest of the script doesn't have | |
| # to care which generation is installed. | |
| OPENCLAW_DETECTED_VERSION="" | |
| OPENCLAW_CHANNELS_JSON_CACHE="" | |
| OPENCLAW_CHANNELS_JSON_LOADED=0 | |
| # Echo the OpenClaw version string (e.g. "2026.6.1") to stdout, or | |
| # empty string if it cannot be determined. Result is cached in | |
| # OPENCLAW_DETECTED_VERSION. | |
| detect_openclaw_version() { | |
| if [[ -n "$OPENCLAW_DETECTED_VERSION" ]]; then | |
| printf '%s' "$OPENCLAW_DETECTED_VERSION" | |
| return 0 | |
| fi | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| return 1 | |
| fi | |
| local ver | |
| ver=$(openclaw --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+([._-][0-9A-Za-z.-]+)?' | head -1) | |
| if [[ -z "$ver" ]]; then | |
| return 1 | |
| fi | |
| OPENCLAW_DETECTED_VERSION="$ver" | |
| printf '%s' "$ver" | |
| } | |
| # True if the detected version is >= the given X.Y.Z threshold. Used | |
| # to gate 2026.6.x-only behavior. Returns false when version can't | |
| # be detected (be conservative). | |
| openclaw_version_at_least() { | |
| local threshold="$1" | |
| local current | |
| current=$(detect_openclaw_version 2>/dev/null) || return 1 | |
| # compare major.minor.patch numerically after stripping suffixes | |
| local cur_major cur_minor cur_patch thr_major thr_minor thr_patch | |
| cur_major=$(printf '%s' "$current" | cut -d. -f1) | |
| cur_minor=$(printf '%s' "$current" | cut -d. -f2) | |
| cur_patch=$(printf '%s' "$current" | cut -d. -f3 | grep -oE '^[0-9]+') | |
| thr_major=$(printf '%s' "$threshold" | cut -d. -f1) | |
| thr_minor=$(printf '%s' "$threshold" | cut -d. -f2) | |
| thr_patch=$(printf '%s' "$threshold" | cut -d. -f3) | |
| cur_major=${cur_major:-0}; cur_minor=${cur_minor:-0}; cur_patch=${cur_patch:-0} | |
| thr_major=${thr_major:-0}; thr_minor=${thr_minor:-0}; thr_patch=${thr_patch:-0} | |
| if (( cur_major > thr_major )); then return 0; fi | |
| if (( cur_major < thr_major )); then return 1; fi | |
| if (( cur_minor > thr_minor )); then return 0; fi | |
| if (( cur_minor < thr_minor )); then return 1; fi | |
| if (( cur_patch >= thr_patch )); then return 0; fi | |
| return 1 | |
| } | |
| # Load `openclaw channels list --json` once and cache it. On | |
| # 2026.6.x the default view only shows configured accounts; pass | |
| # --all=1 to also include bundled-but-unconfigured channels (needed | |
| # for accurate "is the plugin on disk?" health checks). | |
| fetch_channels_json() { | |
| local with_all="${1:-}" | |
| if [[ -n "$with_all" && -n "$OPENCLAW_CHANNELS_JSON_CACHE_ALL" ]]; then | |
| printf '%s' "$OPENCLAW_CHANNELS_JSON_CACHE_ALL" | |
| return 0 | |
| fi | |
| if [[ -z "$with_all" && "$OPENCLAW_CHANNELS_JSON_LOADED" -eq 1 ]]; then | |
| printf '%s' "$OPENCLAW_CHANNELS_JSON_CACHE" | |
| return 0 | |
| fi | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| return 1 | |
| fi | |
| local out="" | |
| local args=(channels list --json) | |
| if [[ -n "$with_all" ]] && openclaw_version_at_least 2026.6.0; then | |
| args=(channels list --json --all) | |
| fi | |
| if ! out=$(openclaw "${args[@]}" 2>/dev/null); then | |
| return 1 | |
| fi | |
| if [[ -n "$with_all" ]]; then | |
| OPENCLAW_CHANNELS_JSON_CACHE_ALL="$out" | |
| else | |
| OPENCLAW_CHANNELS_JSON_CACHE="$out" | |
| OPENCLAW_CHANNELS_JSON_LOADED=1 | |
| fi | |
| printf '%s' "$out" | |
| } | |
| # Resolve a single channel's entry from cached JSON, accepting all | |
| # known layouts. Echoes the entry object (or empty string when | |
| # absent). Recognized layouts: | |
| # 1) 2026.6.x: { "<chan>": { installed, configured, enabled, accounts } } | |
| # 2) legacy: { chat: { "<chan>": { installed, accounts } } } | |
| # 3) array form: { channels: [ { name, installed, accounts } ] } | |
| resolve_channel_entry() { | |
| local channel_name="$1" | |
| local json="$2" | |
| if [[ -z "$json" ]] || ! command -v jq >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| printf '%s' "$json" | jq -c --arg chan "$channel_name" ' | |
| ( .[$chan] // null ) as $top | |
| | ( .chat[$chan] // null ) as $chatted | |
| | ( (.channels // []) | map(select(.name == $chan)) | first // null ) as $listed | |
| | ( $top // $chatted // $listed // null ) | |
| ' 2>/dev/null | |
| } | |
| # Map a resolved channel entry to a health string. Echoes one of: | |
| # missing - entry not present in channels list | |
| # not_installed - entry present but no installed flag/account | |
| # no_accounts - installed but no ready accounts | |
| # healthy - installed and at least one ready account | |
| # | |
| # "Ready account" = an account whose per-account `installed` flag is | |
| # true (2026.6.x) or, in legacy form, an entry in `.accounts`. | |
| channel_health_from_entry() { | |
| local entry_json="$1" | |
| if [[ -z "$entry_json" || "$entry_json" == "null" ]]; then | |
| printf 'missing' | |
| return 0 | |
| fi | |
| if ! command -v jq >/dev/null 2>&1; then | |
| # Without jq we can't be precise; assume healthy to avoid churn. | |
| printf 'healthy' | |
| return 0 | |
| fi | |
| printf '%s' "$entry_json" | jq -r ' | |
| ( .installed // false ) as $chan_installed | |
| | ( | |
| ( .accounts // null ) as $acc | |
| | if $acc == null then 0 | |
| elif ($acc | type) == "array" then | |
| ( [ $acc[] | select((.installed // true) == true) ] | length ) | |
| elif ($acc | type) == "object" then | |
| ( [ $acc | |
| | to_entries[] | |
| | select(.value != null and ((.value.installed // true) == true)) | |
| ] | length ) | |
| else 0 | |
| end | |
| ) as $ready | |
| | if ($chan_installed == true or $ready > 0) and $ready > 0 then "healthy" | |
| elif $chan_installed == true or $ready > 0 then "no_accounts" | |
| else "not_installed" | |
| end | |
| ' 2>/dev/null | |
| } | |
| # One-shot helper: cache + resolve + classify. Echoes a health | |
| # string. The second argument toggles --all (use 1 to include | |
| # bundled channels, useful when "missing" should mean "not even | |
| # bundled"). | |
| channel_health() { | |
| local channel_name="$1" | |
| local with_all="${2:-}" | |
| local json entry | |
| if ! json=$(fetch_channels_json "$with_all"); then | |
| return 1 | |
| fi | |
| entry=$(resolve_channel_entry "$channel_name" "$json") | |
| channel_health_from_entry "$entry" | |
| } | |
| # ---- Guard rails ---- | |
| if ! is_true "${OPENCLAW_POST_RESTORE_ENABLED:-false}"; then | |
| log_info "Skipped: OPENCLAW_POST_RESTORE_ENABLED is unset or false (opt-in; set OPENCLAW_POST_RESTORE_ENABLED=true to enable)" | |
| exit 0 | |
| fi | |
| OPENCLAW_BIN="${OPENCLAW_BIN:-/usr/local/bin/openclaw}" | |
| FAIL_ON_ERROR="${OPENCLAW_POST_RESTORE_FAIL_ON_ERROR:-false}" | |
| errors=0 | |
| record_error() { | |
| log_error "$1" | |
| errors=$((errors + 1)) | |
| } | |
| # Surface the detected OpenClaw version up-front so the operator | |
| # immediately sees which schema variant the rest of the script | |
| # will operate against. A "?" means the CLI was not reachable at | |
| # startup (we'll fail later in check_openclaw_cli_present if so). | |
| DETECTED_VERSION="$(detect_openclaw_version 2>/dev/null || true)" | |
| if [[ -n "$DETECTED_VERSION" ]]; then | |
| log_info "OpenClaw target: ${DETECTED_VERSION} (supports unified channels list --all, top-level channel keys, per-account installed flag)" | |
| else | |
| log_info "OpenClaw target: unknown (CLI not yet reachable β will recheck in check_openclaw_cli_present)" | |
| fi | |
| # ---- Checks ---- | |
| # Each check_* function should be self-contained and call | |
| # record_error on failure. Add new checks by writing a new | |
| # check_* function and calling it from do_all_checks. | |
| check_openclaw_cli_present() { | |
| if command -v openclaw >/dev/null 2>&1; then | |
| log_info "β openclaw CLI present: $(command -v openclaw)" | |
| else | |
| record_error "openclaw CLI not found in PATH (looked for: $OPENCLAW_BIN)" | |
| fi | |
| } | |
| check_openclaw_version() { | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| return 0 # already reported by check_openclaw_cli_present | |
| fi | |
| if version_output=$(openclaw --version 2>&1); then | |
| log_info "β openclaw version: $version_output" | |
| else | |
| record_error "openclaw --version failed: $version_output" | |
| fi | |
| } | |
| check_channels_list_json() { | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| if ! channels_json=$(openclaw channels list --json 2>&1); then | |
| record_error "openclaw channels list --json failed: $channels_json" | |
| return 0 | |
| fi | |
| if ! command -v jq >/dev/null 2>&1; then | |
| log_warn "jq not available, skipping JSON validation (channels list returned without error)" | |
| return 0 | |
| fi | |
| if ! echo "$channels_json" | jq -e . >/dev/null 2>&1; then | |
| record_error "channels list --json returned invalid JSON" | |
| return 0 | |
| fi | |
| # Detect which schema layout we're looking at so the operator | |
| # log line tells the truth on whatever version is installed. | |
| # 2026.6.x: top-level keys (or .channels[]); <2026.6: nested | |
| # under .chat. Both are valid. | |
| local schema_hint | |
| schema_hint=$(printf '%s' "$channels_json" | jq -r ' | |
| if (.channels | type) == "array" then "channels-array" | |
| elif (.chat | type) == "object" then "chat-nested" | |
| elif ([keys[] | select(. != "auth" and . != "usage" and . != "models" and . != "providers")] | length) > 0 then "top-level" | |
| else "empty" | |
| end | |
| ' 2>/dev/null) | |
| log_info "β channels list --json returned valid JSON (schema: ${schema_hint:-unknown})" | |
| } | |
| do_all_checks() { | |
| check_openclaw_cli_present | |
| check_openclaw_version | |
| check_channels_list_json | |
| } | |
| # ---- Operations ---- | |
| # Unlike check_* above, these have side effects (update, install, | |
| # fix). Each is wrapped in run_step which: | |
| # - logs the command before invoking it | |
| # - captures stdout+stderr | |
| # - measures wall time | |
| # - reports success/failure with the appropriate level | |
| # - re-emits the output line-by-line (info on success, warn on | |
| # failure) so the operator can grep the log | |
| # - counts failures in $errors (governed by FAIL_ON_ERROR) | |
| run_step() { | |
| local description="$1" | |
| local cmd="$2" | |
| local start_time end_time duration output rc=0 | |
| start_time=$(date +%s) | |
| log_info "ββ STEP: ${description} [STARTING]" | |
| log_info " Command: ${cmd}" | |
| output=$(bash -c "$cmd" 2>&1) || rc=$? | |
| end_time=$(date +%s) | |
| duration=$((end_time - start_time)) | |
| if [[ $rc -eq 0 ]]; then | |
| log_info "β STEP: ${description} [COMPLETED] (${duration}s)" | |
| else | |
| log_error "β STEP: ${description} [FAILED] (exit=$rc, ${duration}s)" | |
| record_error "${description} failed (exit=$rc)" | |
| fi | |
| if [[ -n "$output" ]]; then | |
| while IFS= read -r line; do | |
| [[ -z "$line" ]] && continue | |
| if [[ $rc -eq 0 ]]; then | |
| log_info " | $line" | |
| else | |
| log_warn " | $line" | |
| fi | |
| done <<< "$output" | |
| fi | |
| } | |
| # Each operation function pre-checks command availability so the | |
| # log shows "skipped: CLI not present" instead of a confusing | |
| # "command not found" coming out of run_step. | |
| run_openclaw_plugins_update() { | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| log_warn "openclaw CLI not available, skipping plugins update" | |
| return 0 | |
| fi | |
| run_step "openclaw plugins update --all" "openclaw plugins update --all" | |
| } | |
| # Healthy = the feishu entry exists in `openclaw channels list --json`, | |
| # is installed, AND has at least one ready account. "Ready" is | |
| # detected via the per-account `installed` flag (2026.6.x) or by | |
| # counting entries under `.accounts` (<2026.6). Anything else is | |
| # considered "unhealthy" and gates run_lark_cli_update to fire. | |
| check_feishu_plugin_healthy() { | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| log_warn "cannot check feishu health: openclaw CLI not available" | |
| return 1 | |
| fi | |
| if ! command -v jq >/dev/null 2>&1; then | |
| log_warn "cannot check feishu health: jq not available" | |
| return 1 | |
| fi | |
| # On 2026.6.x, the default `channels list` view only surfaces | |
| # configured accounts; pass --all so a missing-on-disk plugin | |
| # still shows up as `not_installed` rather than `missing`. On | |
| # older versions the --all flag is harmless (it was a no-op | |
| # alias at worst) and is dropped by the helper when not | |
| # supported. | |
| local status | |
| status=$(channel_health feishu 1) | |
| local rc=$? | |
| if (( rc != 0 )); then | |
| log_warn "cannot check feishu health: channels list failed" | |
| return 1 | |
| fi | |
| if [[ "$status" == "healthy" ]]; then | |
| log_info "β feishu plugin healthy (installed + has authenticated accounts)" | |
| return 0 | |
| fi | |
| log_info "feishu plugin status: $status (treats as unhealthy β will trigger lark-cli update)" | |
| return 1 | |
| } | |
| # Same shape as check_feishu_plugin_healthy, but for the qqbot | |
| # channel. Health = installed + at least one ready account. Anything | |
| # else is "unhealthy" and gates the reinstall step. | |
| check_qqbot_plugin_healthy() { | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| log_warn "cannot check qqbot health: openclaw CLI not available" | |
| return 1 | |
| fi | |
| if ! command -v jq >/dev/null 2>&1; then | |
| log_warn "cannot check qqbot health: jq not available" | |
| return 1 | |
| fi | |
| local status | |
| status=$(channel_health qqbot 1) | |
| local rc=$? | |
| if (( rc != 0 )); then | |
| log_warn "cannot check qqbot health: channels list failed" | |
| return 1 | |
| fi | |
| if [[ "$status" == "healthy" ]]; then | |
| log_info "β qqbot plugin healthy (installed + has authenticated accounts)" | |
| return 0 | |
| fi | |
| log_info "qqbot plugin status: $status (treats as unhealthy β will trigger reinstall)" | |
| return 1 | |
| } | |
| run_lark_cli_update() { | |
| if ! command -v npm >/dev/null 2>&1; then | |
| log_warn "npm not available, skipping lark-cli update" | |
| return 0 | |
| fi | |
| # Gate: only update lark-cli if the feishu plugin is broken. A | |
| # working feishu integration means the currently-installed lark-cli | |
| # version is good enough β running `npm update` would just be churn | |
| # (and might break the working setup if a major version lands). | |
| if check_feishu_plugin_healthy; then | |
| log_info "feishu plugin is healthy, skipping lark-cli update (don't touch what works)" | |
| return 0 | |
| fi | |
| log_info "feishu plugin is unhealthy or unconfigured, proceeding with lark-cli update" | |
| # Operator visibility: log which OpenClaw version we're targeting | |
| # and which command syntax we'll use. On 2026.6.x the recommended | |
| # skills source is the unscoped `larksuite/cli` (matches the new | |
| # skills hub manifest); on older versions some deployments still | |
| # used `@larksuite/cli`. We always pin to `@larksuite/cli` for the | |
| # binary (npm package, version-controlled) and pick the right | |
| # `skills add` source per detected version. | |
| local ver | |
| ver=$(detect_openclaw_version 2>/dev/null || true) | |
| log_info "Updating lark-cli for OpenClaw ${ver:-unknown}..." | |
| local skills_source="larksuite/cli" | |
| if [[ -n "$ver" ]] && ! openclaw_version_at_least 2026.6.0; then | |
| # On <2026.6 the skills registry still expects the @-scoped form | |
| skills_source="@larksuite/cli" | |
| fi | |
| local cmd="npm update -g @larksuite/cli && npx skills add ${skills_source} -g -y" | |
| run_step "$cmd" "$cmd" | |
| } | |
| run_lark_cli_version() { | |
| if ! command -v lark-cli >/dev/null 2>&1; then | |
| log_warn "lark-cli not available, skipping version check" | |
| return 0 | |
| fi | |
| run_step "lark-cli --version" "lark-cli --version" | |
| } | |
| run_lark_cli_auth_status() { | |
| if ! command -v lark-cli >/dev/null 2>&1; then | |
| log_warn "lark-cli not available, skipping auth status" | |
| return 0 | |
| fi | |
| run_step "lark-cli auth status" "lark-cli auth status" | |
| } | |
| run_openclaw_doctor_fix() { | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| log_warn "openclaw CLI not available, skipping doctor --fix" | |
| return 0 | |
| fi | |
| run_step "openclaw doctor --fix" "openclaw doctor --fix" | |
| } | |
| run_qqbot_plugin_reinstall() { | |
| if ! command -v openclaw >/dev/null 2>&1; then | |
| log_warn "openclaw CLI not available, skipping qqbot reinstall" | |
| return 0 | |
| fi | |
| # Same fix-on-broken pattern as run_lark_cli_update: only reinstall | |
| # the qqbot plugin if the channel is unhealthy. A working integration | |
| # doesn't need a churn reinstall. | |
| if check_qqbot_plugin_healthy; then | |
| log_info "qqbot plugin is healthy, skipping reinstall (don't touch what works)" | |
| return 0 | |
| fi | |
| log_info "qqbot plugin is unhealthy or unconfigured, proceeding with uninstall+reinstall" | |
| # 2026.6.1 plugin install syntax: | |
| # - bare `@openclaw/qqbot` resolves to the bundled copy of the | |
| # plugin that ships with this OpenClaw build (auto-detected | |
| # during install). Pinning to `@latest` keeps the upgrade | |
| # rolling even if the bundled copy was older. | |
| # - explicit `npm:@openclaw/qqbot@latest` would force the npm | |
| # registry path; not needed here, the bundled copy is the | |
| # canonical source for an official channel plugin. | |
| # - `uninstall || true` ensures we still reach the install step | |
| # when the plugin was never installed to begin with (the | |
| # "missing" health state). The original `&&` chain silently | |
| # skipped install in that case, which defeated the fix. | |
| run_step "openclaw plugins uninstall qqbot || true; openclaw plugins install @openclaw/qqbot@latest" \ | |
| "openclaw plugins uninstall qqbot || true; openclaw plugins install @openclaw/qqbot@latest" | |
| } | |
| run_feishu_doctor_check() { | |
| # Targeted feishu diagnostic β richer than the openclaw doctor --fix | |
| # catch-all, gives a per-section pass/warn/fail breakdown that | |
| # operators can grep. Always runs (not gated on health) so the | |
| # baseline check is captured on every container start. | |
| if ! command -v feishu-doctor >/dev/null 2>&1; then | |
| log_warn "feishu-doctor not on PATH, skipping" | |
| return 0 | |
| fi | |
| run_step "feishu-doctor check" "feishu-doctor check" | |
| } | |
| do_all_operations() { | |
| log_info "=== Post-restore openclaw operations START ===" | |
| run_openclaw_plugins_update | |
| run_lark_cli_update | |
| run_lark_cli_version | |
| run_lark_cli_auth_status | |
| run_qqbot_plugin_reinstall | |
| run_feishu_doctor_check | |
| run_openclaw_doctor_fix | |
| log_info "=== Post-restore openclaw operations END ===" | |
| } | |
| # ---- Main ---- | |
| log_info "=== Post-restore openclaw hook START ===" | |
| log_info "OPENCLAW_POST_RESTORE_ENABLED=${OPENCLAW_POST_RESTORE_ENABLED:-false}" | |
| log_info "OPENCLAW_POST_RESTORE_FAIL_ON_ERROR=$FAIL_ON_ERROR" | |
| log_info "OPENCLAW_BIN=$OPENCLAW_BIN" | |
| log_info "OPENCLAW_VERSION=${OPENCLAW_VERSION:-<not set, will be probed>}" | |
| log_info "Resolved target version: ${DETECTED_VERSION:-<not yet detectable>}" | |
| do_all_checks | |
| do_all_operations | |
| log_info "=== Post-restore openclaw hook END (errors=$errors) ===" | |
| if [[ "$errors" -gt 0 ]]; then | |
| if is_true "$FAIL_ON_ERROR"; then | |
| log_error "Aborting startup: $errors check(s) failed and OPENCLAW_POST_RESTORE_FAIL_ON_ERROR=true" | |
| exit 1 | |
| fi | |
| log_warn "Continuing startup with $errors check failure(s) (set OPENCLAW_POST_RESTORE_FAIL_ON_ERROR=true to fail-fast)" | |
| fi | |
| exit 0 | |