#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PLUGINS_DIR="$PROJECT_ROOT/plugins" ENV_FILE="$PROJECT_ROOT/.env" # ── Helpers ────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m' CYAN='\033[0;36m'; NC='\033[0m' log() { printf "${CYAN}[setup]${NC} %s\n" "$*"; } ok() { printf "${GREEN} ✓${NC} %s\n" "$*"; } warn() { printf "${YELLOW} ⚠${NC} %s\n" "$*"; } err() { printf "${RED} ✗${NC} %s\n" "$*"; } oc_set_str() { local key="$1" val="$2" if [[ -n "$val" ]]; then openclaw config set "$key" "\"$val\"" --json ok "config: $key" fi } oc_set_num() { local key="$1" val="$2" if [[ -n "$val" ]]; then openclaw config set "$key" "$val" --json ok "config: $key = $val" fi } oc_set_bool() { local key="$1" val="$2" if [[ -n "$val" ]]; then openclaw config set "$key" "$val" --json ok "config: $key = $val" fi } usage() { cat < Path to .env file (default: /.env) --list List available plugins and exit --uninstall Uninstall plugins instead of installing --skip-deps Skip npm install step --skip-config Skip openclaw config set step -h, --help Show this help Plugins: PaddedCell, ContractorAgent, Dirigent, HarborForge.OpenclawPlugin, Yonexus.Server Sensitive configuration is read from .env (see .env.example for reference) and written to openclaw config via 'openclaw config set'. EOF exit 0 } # ── Parse args ─────────────────────────────────────────────────────────────── UNINSTALL=false SKIP_DEPS=false SKIP_CONFIG=false SELECTED_PLUGINS=() while [[ $# -gt 0 ]]; do case "$1" in --env) ENV_FILE="$2"; shift 2 ;; --list) ls "$PLUGINS_DIR"; exit 0 ;; --uninstall) UNINSTALL=true; shift ;; --skip-deps) SKIP_DEPS=true; shift ;; --skip-config) SKIP_CONFIG=true; shift ;; -h|--help) usage ;; -*) err "Unknown option: $1"; usage ;; *) SELECTED_PLUGINS+=("$1"); shift ;; esac done # ── Load .env ──────────────────────────────────────────────────────────────── if [[ -f "$ENV_FILE" ]]; then log "Loading env from $ENV_FILE" set -a # shellcheck disable=SC1090 source "$ENV_FILE" set +a ok "Loaded $(grep -cE '^[A-Z_]+=' "$ENV_FILE") variables" else warn "No .env file found at $ENV_FILE — proceeding with current environment" fi # ── Submodules ─────────────────────────────────────────────────────────────── log "Ensuring git submodules are initialised" cd "$PROJECT_ROOT" git submodule update --init --recursive ok "Submodules ready" # ── Resolve plugin list ───────────────────────────────────────────────────── ALL_PLUGINS=() for d in "$PLUGINS_DIR"/*/; do ALL_PLUGINS+=("$(basename "$d")") done if [[ ${#SELECTED_PLUGINS[@]} -eq 0 ]]; then SELECTED_PLUGINS=("${ALL_PLUGINS[@]}") fi for p in "${SELECTED_PLUGINS[@]}"; do if [[ ! -d "$PLUGINS_DIR/$p" ]]; then err "Plugin not found: $p" exit 1 fi done # ── Per-plugin helpers ─────────────────────────────────────────────────────── install_deps() { local dir="$1" if [[ "$SKIP_DEPS" == true ]]; then return; fi if [[ -f "$dir/package.json" ]]; then log " npm install (root)" (cd "$dir" && npm install --no-audit --no-fund 2>&1 | tail -3) fi if [[ -f "$dir/plugin/package.json" ]]; then log " npm install (plugin/)" (cd "$dir/plugin" && npm install --no-audit --no-fund 2>&1 | tail -3) fi } find_install_script() { local dir="$1" for candidate in \ "$dir/scripts/install.mjs" \ "$dir/install.mjs" \ "$dir/scripts/install.js" \ "$dir/install.js"; do if [[ -f "$candidate" ]]; then echo "$candidate" return fi done } plugin_install_args() { local name="$1" action="$2" local args=("--$action") if [[ "$action" == "install" ]]; then case "$name" in HarborForge.OpenclawPlugin) args+=(--install-cli --install-monitor yes --openclaw-profile-path ~/.openclaw) ;; PaddedCell|Dirigent|Yonexus.Server) args+=(--openclaw-profile-path ~/.openclaw) ;; esac fi echo "${args[@]}" } run_install_script() { local name="$1" dir="$2" action="$3" local script script=$(find_install_script "$dir") if [[ -n "$script" ]]; then local -a args read -ra args <<< "$(plugin_install_args "$name" "$action")" log " Running $(basename "$script") ${args[*]}" node "$script" "${args[@]}" || { err "$name script failed" return 1 } return 0 fi if [[ "$action" == "install" ]] && [[ -f "$dir/package.json" ]]; then if grep -q "\"install-plugin\"" "$dir/package.json" 2>/dev/null; then log " Running npm run install-plugin" (cd "$dir" && npm run install-plugin) return $? elif grep -q "\"postinstall\"" "$dir/package.json" 2>/dev/null; then log " Running npm run postinstall" (cd "$dir" && npm run postinstall) return $? fi fi warn "No install script found for $name" return 0 } # ── Plugin config mapping ──────────────────────────────────────────────────── # Maps .env variables → openclaw config keys via 'openclaw config set'. # Each plugin's config lives under plugins.entries..config.* configure_plugin() { local name="$1" if [[ "$SKIP_CONFIG" == true ]]; then return; fi log " Writing openclaw config" case "$name" in HarborForge.OpenclawPlugin) local P="plugins.entries.harbor-forge.config" oc_set_str "$P.backendUrl" "${HF_BACKEND_URL:-}" oc_set_str "$P.apiKey" "${HF_API_KEY:-}" oc_set_str "$P.identifier" "${HF_MONITOR_IDENTIFIER:-}" oc_set_num "$P.monitor_port" "${HF_MONITOR_PORT:-}" oc_set_num "$P.reportIntervalSec" "${HF_REPORT_INTERVAL:-}" oc_set_str "$P.calendarApiKey" "${HF_CALENDAR_API_KEY:-}" oc_set_bool "$P.enabled" "true" ;; ContractorAgent) local P="plugins.entries.contractor-agent.config" oc_set_num "$P.bridgePort" "${CONTRACTOR_BRIDGE_PORT:-}" oc_set_str "$P.bridgeApiKey" "${CONTRACTOR_BRIDGE_API_KEY:-}" ;; Dirigent) local P="plugins.entries.dirigent.config" oc_set_str "$P.moderatorBotToken" "${DIRIGENT_MODERATOR_BOT_TOKEN:-}" oc_set_num "$P.sideCarPort" "${DIRIGENT_SIDECAR_PORT:-}" oc_set_str "$P.noReplyProvider" "${DIRIGENT_NO_REPLY_PROVIDER:-}" oc_set_str "$P.noReplyModel" "${DIRIGENT_NO_REPLY_MODEL:-}" oc_set_str "$P.scheduleIdentifier" "${DIRIGENT_SCHEDULE_IDENTIFIER:-}" ;; PaddedCell) local P="plugins.entries.padded-cell.config" oc_set_str "$P.secretMgrPath" "${PADDEDCELL_SECRET_MGR_PATH:-}" oc_set_str "$P.openclawProfilePath" "${PADDEDCELL_OPENCLAW_PATH:-}" oc_set_bool "$P.enabled" "true" ;; Yonexus.Server) local P="plugins.entries.yonexus-server.config" oc_set_str "$P.notifyBotToken" "${YONEXUS_NOTIFY_BOT_TOKEN:-}" oc_set_str "$P.adminUserId" "${YONEXUS_ADMIN_USER_ID:-}" oc_set_num "$P.listenPort" "${YONEXUS_PORT:-}" oc_set_str "$P.publicWsUrl" "${YONEXUS_PUBLIC_WS_URL:-}" ;; *) warn "No config mapping for $name" ;; esac } # ── Main loop ──────────────────────────────────────────────────────────────── run_plugin() { local name="$1" local dir="$PLUGINS_DIR/$name" echo if [[ "$UNINSTALL" == true ]]; then log "[uninstall] $name" run_install_script "$name" "$dir" "uninstall" return $? fi log "[install] $name" install_deps "$dir" run_install_script "$name" "$dir" "install" || return $? configure_plugin "$name" ok "$name done" } FAILURES=0 for plugin in "${SELECTED_PLUGINS[@]}"; do run_plugin "$plugin" || ((FAILURES++)) || true done echo if [[ $FAILURES -gt 0 ]]; then err "$FAILURES plugin(s) had errors" exit 1 else ok "All plugins processed successfully" fi