Files
homeai/homeai-agent/setup.sh
Aodhan Collins 117254d560 feat: Music Assistant, Claude primary LLM, model tag in chat, setup.sh rewrite
- 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>
2026-03-18 22:21:28 +00:00

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