- unset AGENT_ID/AGENT_WORKSPACE/AGENT_VERIFY so plugin installers don't refuse operations gated to human-only - --install-cli <branch>: shallow-clone HarborForge.Cli to tmp, symlink into plugins/ for the install script to find - --yonexus-client/--yonexus-protocol <branch>: same pattern for Yonexus.Client and Yonexus.Protocol (needed for tsc build) - build_yonexus step runs tsc before Yonexus.Server install - cloned repos and symlinks cleaned up on EXIT trap - --install-cli only passed to HarborForge when the flag is given
357 lines
11 KiB
Bash
Executable File
357 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
unset AGENT_ID AGENT_WORKSPACE AGENT_VERIFY
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
PLUGINS_DIR="$PROJECT_ROOT/plugins"
|
|
ENV_FILE="$PROJECT_ROOT/.env"
|
|
GIT_BASE="https://git.hangman-lab.top"
|
|
|
|
# ── 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 <<EOF
|
|
Usage: $(basename "$0") [options] [plugin ...]
|
|
|
|
Install and configure OpenClaw plugins from the plugins/ directory.
|
|
If no plugin names are given, all plugins are processed.
|
|
|
|
Options:
|
|
--env <path> Path to .env file (default: <project-root>/.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
|
|
--install-cli <branch> Clone & build HarborForge.Cli (default: main)
|
|
--yonexus-client <branch> Clone Yonexus.Client for build (default: main)
|
|
--yonexus-protocol <branch> Clone Yonexus.Protocol for build (default: main)
|
|
-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
|
|
INSTALL_CLI_BRANCH=""
|
|
YONEXUS_CLIENT_BRANCH=""
|
|
YONEXUS_PROTOCOL_BRANCH=""
|
|
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 ;;
|
|
--install-cli) INSTALL_CLI_BRANCH="${2:-main}"; shift 2 ;;
|
|
--yonexus-client) YONEXUS_CLIENT_BRANCH="${2:-main}"; shift 2 ;;
|
|
--yonexus-protocol) YONEXUS_PROTOCOL_BRANCH="${2:-main}"; shift 2 ;;
|
|
-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"
|
|
|
|
# ── Clone external deps to tmp ──────────────────────────────────────────────
|
|
|
|
TMPDIR_CLONES=""
|
|
SYMLINKS_TO_CLEAN=()
|
|
|
|
clone_dep() {
|
|
local org="$1" repo="$2" branch="$3" target_link="$4"
|
|
if [[ -z "$TMPDIR_CLONES" ]]; then
|
|
TMPDIR_CLONES="$(mktemp -d)"
|
|
fi
|
|
local dest="$TMPDIR_CLONES/$repo"
|
|
log "Cloning $org/$repo@$branch → $dest"
|
|
git clone --depth 1 --branch "$branch" "$GIT_BASE/$org/$repo.git" "$dest" 2>&1 | tail -2
|
|
ln -sfn "$dest" "$target_link"
|
|
SYMLINKS_TO_CLEAN+=("$target_link")
|
|
ok "$repo@$branch ready"
|
|
}
|
|
|
|
cleanup_clones() {
|
|
for link in "${SYMLINKS_TO_CLEAN[@]}"; do
|
|
rm -f "$link"
|
|
done
|
|
if [[ -n "$TMPDIR_CLONES" ]]; then
|
|
rm -rf "$TMPDIR_CLONES"
|
|
fi
|
|
}
|
|
trap cleanup_clones EXIT
|
|
|
|
if [[ -n "$INSTALL_CLI_BRANCH" ]]; then
|
|
clone_dep "zhi" "HarborForge.Cli" "$INSTALL_CLI_BRANCH" "$PLUGINS_DIR/HarborForge.Cli"
|
|
fi
|
|
|
|
if [[ -n "$YONEXUS_CLIENT_BRANCH" ]]; then
|
|
clone_dep "nav" "Yonexus.Client" "$YONEXUS_CLIENT_BRANCH" "$PLUGINS_DIR/Yonexus.Client"
|
|
fi
|
|
|
|
if [[ -n "$YONEXUS_PROTOCOL_BRANCH" ]]; then
|
|
clone_dep "nav" "Yonexus.Protocol" "$YONEXUS_PROTOCOL_BRANCH" "$PLUGINS_DIR/Yonexus.Protocol"
|
|
fi
|
|
|
|
# ── Resolve plugin list ─────────────────────────────────────────────────────
|
|
|
|
ALL_PLUGINS=()
|
|
for d in "$PLUGINS_DIR"/*/; do
|
|
name="$(basename "$d")"
|
|
case "$name" in
|
|
HarborForge.Cli|Yonexus.Client|Yonexus.Protocol) continue ;;
|
|
esac
|
|
ALL_PLUGINS+=("$name")
|
|
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
|
|
}
|
|
|
|
build_yonexus() {
|
|
local dir="$PLUGINS_DIR/Yonexus.Server"
|
|
log " Building Yonexus.Server (tsc)"
|
|
(cd "$dir" && npm run build 2>&1 | tail -5)
|
|
ok "Yonexus.Server built"
|
|
}
|
|
|
|
plugin_install_args() {
|
|
local name="$1" action="$2"
|
|
local args=("--$action")
|
|
if [[ "$action" == "install" ]]; then
|
|
case "$name" in
|
|
HarborForge.OpenclawPlugin)
|
|
if [[ -n "$INSTALL_CLI_BRANCH" ]]; then
|
|
args+=(--install-cli)
|
|
fi
|
|
args+=(--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.<id>.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"
|
|
if [[ "$name" == "Yonexus.Server" ]]; then
|
|
build_yonexus
|
|
fi
|
|
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
|