- Deploy Music Assistant on Pi (10.0.0.199:8095) with host networking for Chromecast mDNS discovery, Spotify + SMB library support - Switch primary LLM from Ollama to Claude Sonnet 4 (Anthropic API), local models remain as fallback - Add model info tag under each assistant message in dashboard chat, persisted in conversation JSON - Rewrite homeai-agent/setup.sh: loads .env, injects API keys into plists, symlinks plists to ~/Library/LaunchAgents/, smoke tests services - Update install_service() in common.sh to use symlinks instead of copies - Open UFW ports on Pi for Music Assistant (8095, 8097, 8927) - Add ANTHROPIC_API_KEY to openclaw + bridge launchd plists Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
218 lines
8.6 KiB
Bash
Executable File
218 lines
8.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# homeai-agent/setup.sh — OpenClaw agent, HTTP bridge, skills, reminder daemon
|
|
#
|
|
# Components:
|
|
# - OpenClaw gateway — AI agent runtime (port 8080)
|
|
# - OpenClaw HTTP bridge — HA ↔ OpenClaw translator (port 8081)
|
|
# - 13 skills — home-assistant, image-generation, voice-assistant,
|
|
# vtube-studio, memory, service-monitor, character,
|
|
# routine, music, workflow, gitea, calendar, mode
|
|
# - Reminder daemon — fires TTS when reminders are due
|
|
#
|
|
# Prerequisites:
|
|
# - Ollama running (port 11434)
|
|
# - Home Assistant reachable (HA_TOKEN set in .env)
|
|
# - Wyoming TTS running (port 10301)
|
|
# - homeai-voice-env venv exists (for bridge + reminder daemon)
|
|
# - At least one character JSON in ~/homeai-data/characters/
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
source "${REPO_DIR}/scripts/common.sh"
|
|
|
|
log_section "P4: Agent (OpenClaw + HTTP Bridge + Skills)"
|
|
detect_platform
|
|
|
|
# ─── Load environment ────────────────────────────────────────────────────────
|
|
ENV_FILE="${REPO_DIR}/.env"
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
log_info "Loading .env..."
|
|
load_env "$ENV_FILE"
|
|
else
|
|
log_warn "No .env found at ${ENV_FILE} — API keys may be missing"
|
|
fi
|
|
|
|
# ─── Prerequisite checks ────────────────────────────────────────────────────
|
|
log_info "Checking prerequisites..."
|
|
|
|
require_command node "brew install node"
|
|
require_command openclaw "npm install -g openclaw"
|
|
|
|
VOICE_ENV="${HOME}/homeai-voice-env"
|
|
if [[ ! -d "$VOICE_ENV" ]]; then
|
|
die "homeai-voice-env not found at $VOICE_ENV — run homeai-voice/setup.sh first"
|
|
fi
|
|
|
|
# Check key services (non-fatal)
|
|
for check in "http://localhost:11434:Ollama" "http://localhost:10301:Wyoming-TTS"; do
|
|
url="${check%%:*}"; name="${check##*:}"
|
|
if curl -sf "$url" -o /dev/null 2>/dev/null; then
|
|
log_success "$name reachable"
|
|
else
|
|
log_warn "$name not reachable at $url"
|
|
fi
|
|
done
|
|
|
|
# Check required env vars
|
|
MISSING_KEYS=()
|
|
[[ -z "${HA_TOKEN:-}" ]] && MISSING_KEYS+=("HA_TOKEN")
|
|
[[ -z "${ANTHROPIC_API_KEY:-}" ]] && MISSING_KEYS+=("ANTHROPIC_API_KEY")
|
|
if [[ ${#MISSING_KEYS[@]} -gt 0 ]]; then
|
|
log_warn "Missing env vars: ${MISSING_KEYS[*]} — set these in ${ENV_FILE}"
|
|
fi
|
|
|
|
# ─── Ensure data directories ─────────────────────────────────────────────────
|
|
DATA_DIR="${HOME}/homeai-data"
|
|
for dir in characters memories memories/personal conversations routines; do
|
|
mkdir -p "${DATA_DIR}/${dir}"
|
|
done
|
|
log_success "Data directories verified"
|
|
|
|
# ─── OpenClaw config ─────────────────────────────────────────────────────────
|
|
OPENCLAW_DIR="${HOME}/.openclaw"
|
|
OPENCLAW_CONFIG="${OPENCLAW_DIR}/openclaw.json"
|
|
|
|
if [[ ! -f "$OPENCLAW_CONFIG" ]]; then
|
|
die "OpenClaw config not found at $OPENCLAW_CONFIG — run: openclaw doctor --fix"
|
|
fi
|
|
log_success "OpenClaw config exists at $OPENCLAW_CONFIG"
|
|
|
|
# Verify Anthropic provider is configured
|
|
if ! grep -q '"anthropic"' "$OPENCLAW_CONFIG" 2>/dev/null; then
|
|
log_warn "Anthropic provider not found in openclaw.json — add it for Claude support"
|
|
fi
|
|
|
|
# ─── Install skills ──────────────────────────────────────────────────────────
|
|
SKILLS_SRC="${SCRIPT_DIR}/skills"
|
|
SKILLS_DEST="${OPENCLAW_DIR}/skills"
|
|
|
|
if [[ -d "$SKILLS_SRC" ]]; then
|
|
log_info "Syncing skills..."
|
|
mkdir -p "$SKILLS_DEST"
|
|
for skill_dir in "$SKILLS_SRC"/*/; do
|
|
skill_name="$(basename "$skill_dir")"
|
|
dest="${SKILLS_DEST}/${skill_name}"
|
|
if [[ -L "$dest" ]]; then
|
|
log_info " ${skill_name} (symlinked)"
|
|
elif [[ -d "$dest" ]]; then
|
|
# Replace copy with symlink
|
|
rm -rf "$dest"
|
|
ln -s "$skill_dir" "$dest"
|
|
log_step "${skill_name} → symlinked"
|
|
else
|
|
ln -s "$skill_dir" "$dest"
|
|
log_step "${skill_name} → installed"
|
|
fi
|
|
done
|
|
log_success "Skills synced ($(ls -d "$SKILLS_DEST"/*/ 2>/dev/null | wc -l | tr -d ' ') total)"
|
|
else
|
|
log_warn "No skills directory at $SKILLS_SRC"
|
|
fi
|
|
|
|
# ─── Install launchd services (macOS) ────────────────────────────────────────
|
|
if [[ "$OS_TYPE" == "macos" ]]; then
|
|
log_info "Installing launchd agents..."
|
|
|
|
LAUNCHD_DIR="${SCRIPT_DIR}/launchd"
|
|
AGENTS_DIR="${HOME}/Library/LaunchAgents"
|
|
mkdir -p "$AGENTS_DIR"
|
|
|
|
# Inject API keys into plists that need them
|
|
_inject_plist_key() {
|
|
local plist="$1" key="$2" value="$3"
|
|
if [[ -n "$value" ]] && grep -q "<key>${key}</key>" "$plist" 2>/dev/null; then
|
|
# Use python for reliable XML-safe replacement
|
|
python3 -c "
|
|
import sys, re
|
|
with open('$plist') as f: content = f.read()
|
|
pattern = r'(<key>${key}</key>\s*<string>)[^<]*(</string>)'
|
|
content = re.sub(pattern, r'\g<1>${value}\g<2>', content)
|
|
with open('$plist', 'w') as f: f.write(content)
|
|
"
|
|
fi
|
|
}
|
|
|
|
# Update API keys in plist source files before linking
|
|
OPENCLAW_PLIST="${LAUNCHD_DIR}/com.homeai.openclaw.plist"
|
|
BRIDGE_PLIST="${LAUNCHD_DIR}/com.homeai.openclaw-bridge.plist"
|
|
|
|
if [[ -f "$OPENCLAW_PLIST" ]]; then
|
|
_inject_plist_key "$OPENCLAW_PLIST" "ANTHROPIC_API_KEY" "${ANTHROPIC_API_KEY:-}"
|
|
_inject_plist_key "$OPENCLAW_PLIST" "OPENAI_API_KEY" "${OPENAI_API_KEY:-}"
|
|
_inject_plist_key "$OPENCLAW_PLIST" "HA_TOKEN" "${HA_TOKEN:-}"
|
|
_inject_plist_key "$OPENCLAW_PLIST" "HASS_TOKEN" "${HA_TOKEN:-}"
|
|
_inject_plist_key "$OPENCLAW_PLIST" "GITEA_TOKEN" "${GITEA_TOKEN:-}"
|
|
_inject_plist_key "$OPENCLAW_PLIST" "N8N_API_KEY" "${N8N_API_KEY:-}"
|
|
fi
|
|
|
|
if [[ -f "$BRIDGE_PLIST" ]]; then
|
|
_inject_plist_key "$BRIDGE_PLIST" "ANTHROPIC_API_KEY" "${ANTHROPIC_API_KEY:-}"
|
|
_inject_plist_key "$BRIDGE_PLIST" "ELEVENLABS_API_KEY" "${ELEVENLABS_API_KEY:-}"
|
|
fi
|
|
|
|
# Symlink and load each plist
|
|
for plist in "$LAUNCHD_DIR"/*.plist; do
|
|
[[ ! -f "$plist" ]] && continue
|
|
plist_name="$(basename "$plist")"
|
|
plist_label="${plist_name%.plist}"
|
|
dest="${AGENTS_DIR}/${plist_name}"
|
|
|
|
# Unload if already running
|
|
launchctl bootout "gui/$(id -u)/${plist_label}" 2>/dev/null || true
|
|
|
|
# Symlink source → LaunchAgents
|
|
ln -sf "$(cd "$(dirname "$plist")" && pwd)/${plist_name}" "$dest"
|
|
|
|
# Load
|
|
launchctl bootstrap "gui/$(id -u)" "$dest" 2>/dev/null && \
|
|
log_success " ${plist_label} → loaded" || \
|
|
log_warn " ${plist_label} → failed to load (check: launchctl print gui/$(id -u)/${plist_label})"
|
|
done
|
|
fi
|
|
|
|
# ─── Smoke test ──────────────────────────────────────────────────────────────
|
|
log_info "Running smoke tests..."
|
|
|
|
sleep 2 # Give services a moment to start
|
|
|
|
# Check gateway
|
|
if curl -sf "http://localhost:8080" -o /dev/null 2>/dev/null; then
|
|
log_success "OpenClaw gateway responding on :8080"
|
|
else
|
|
log_warn "OpenClaw gateway not responding on :8080 — check: tail /tmp/homeai-openclaw.log"
|
|
fi
|
|
|
|
# Check bridge
|
|
if curl -sf "http://localhost:8081/status" -o /dev/null 2>/dev/null; then
|
|
log_success "HTTP bridge responding on :8081"
|
|
else
|
|
log_warn "HTTP bridge not responding on :8081 — check: tail /tmp/homeai-openclaw-bridge.log"
|
|
fi
|
|
|
|
# ─── Summary ─────────────────────────────────────────────────────────────────
|
|
print_summary "Agent Setup Complete" \
|
|
"OpenClaw gateway" "http://localhost:8080" \
|
|
"HTTP bridge" "http://localhost:8081" \
|
|
"OpenClaw config" "$OPENCLAW_CONFIG" \
|
|
"Skills directory" "$SKILLS_DEST" \
|
|
"Character data" "${DATA_DIR}/characters/" \
|
|
"Memory data" "${DATA_DIR}/memories/" \
|
|
"Reminder data" "${DATA_DIR}/reminders.json" \
|
|
"Gateway log" "/tmp/homeai-openclaw.log" \
|
|
"Bridge log" "/tmp/homeai-openclaw-bridge.log"
|
|
|
|
cat <<'EOF'
|
|
|
|
To reload a service after editing its plist:
|
|
launchctl bootout gui/$(id -u)/com.homeai.<service>
|
|
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.homeai.<service>.plist
|
|
|
|
To test the agent:
|
|
curl -X POST http://localhost:8081/api/agent/message \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"message":"say hello","agent":"main"}'
|
|
|
|
EOF
|