#!/usr/bin/env bash # start.sh — Start all HomeAI services on LINDBLUM # # Usage: # ./start.sh # start everything # ./start.sh status # show what's running # ./start.sh stop # stop everything # ./start.sh restart # stop then start everything set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${REPO_DIR}/scripts/common.sh" DOCKER_DIR="${HOME}/server/docker" # ─── Service definitions ────────────────────────────────────────────────────── LAUNCHD_SERVICES=( com.homeai.ollama com.homeai.wyoming-stt com.homeai.wyoming-tts com.homeai.wyoming-satellite com.homeai.openclaw com.homeai.openclaw-bridge com.homeai.preload-models com.homeai.dashboard ) DOCKER_COMPOSE_DIRS=( "${DOCKER_DIR}/open-webui" "${DOCKER_DIR}/uptime-kuma" "${DOCKER_DIR}/n8n" "${DOCKER_DIR}/code-server" ) # ─── Helpers ────────────────────────────────────────────────────────────────── launchd_running() { local label="$1" local pid pid=$(launchctl list "$label" 2>/dev/null | grep '"PID"' | sed 's/[^0-9]//g' || true) [[ -n "$pid" && "$pid" -gt 0 ]] 2>/dev/null } start_launchd() { local label="$1" local plist="${HOME}/Library/LaunchAgents/${label}.plist" if [[ ! -f "$plist" ]]; then log_warn "Plist not found: $plist — skipping" return fi if launchd_running "$label"; then log_info "Already running: $label" return fi log_step "Starting $label" # bootstrap loads + starts the agent launchctl bootstrap "gui/$(id -u)" "$plist" 2>/dev/null \ || launchctl kickstart -k "gui/$(id -u)/$label" 2>/dev/null \ || true sleep 0.5 if launchd_running "$label"; then log_success "$label started" else log_warn "$label may not have started — check logs" fi } stop_launchd() { local label="$1" local plist="${HOME}/Library/LaunchAgents/${label}.plist" if [[ ! -f "$plist" ]]; then return fi if ! launchd_running "$label"; then log_info "Already stopped: $label" return fi log_step "Stopping $label" launchctl bootout "gui/$(id -u)/$label" 2>/dev/null || true log_success "$label stopped" } start_docker_services() { ensure_docker_running for dir in "${DOCKER_COMPOSE_DIRS[@]}"; do if [[ ! -d "$dir" ]]; then log_warn "Docker compose dir not found: $dir — skipping" continue fi local name name=$(basename "$dir") log_step "Starting Docker stack: $name" docker_compose -f "${dir}/docker-compose.yml" up -d 2>/dev/null \ || docker_compose -f "${dir}/compose.yml" up -d 2>/dev/null \ || log_warn "No compose file found in $dir" done } stop_docker_services() { for dir in "${DOCKER_COMPOSE_DIRS[@]}"; do if [[ ! -d "$dir" ]]; then continue fi local name name=$(basename "$dir") log_step "Stopping Docker stack: $name" docker_compose -f "${dir}/docker-compose.yml" down 2>/dev/null \ || docker_compose -f "${dir}/compose.yml" down 2>/dev/null \ || true done } # ─── Commands ───────────────────────────────────────────────────────────────── do_start() { log_section "Starting HomeAI Services" echo "" log_info "── LaunchD services ──" for svc in "${LAUNCHD_SERVICES[@]}"; do start_launchd "$svc" done echo "" log_info "── Docker services ──" start_docker_services echo "" log_success "All services started." echo "" do_status } do_stop() { log_section "Stopping HomeAI Services" echo "" log_info "── LaunchD services ──" for svc in "${LAUNCHD_SERVICES[@]}"; do stop_launchd "$svc" done echo "" log_info "── Docker services ──" stop_docker_services echo "" log_success "All services stopped." } do_status() { log_section "HomeAI Service Status" # LaunchD services printf "\n ${BOLD}%-34s %-10s %s${RESET}\n" "SERVICE" "STATUS" "PID" printf " %-34s %-10s %s\n" "─────────────────────────────────" "────────" "─────" for svc in "${LAUNCHD_SERVICES[@]}"; do local pid pid=$(launchctl list "$svc" 2>/dev/null | grep '"PID"' | sed 's/[^0-9]//g' || true) if [[ -n "$pid" && "$pid" -gt 0 ]] 2>/dev/null; then printf " ${GREEN}●${RESET} %-32s ${GREEN}%-10s${RESET} %s\n" "$svc" "running" "$pid" else printf " ${RED}○${RESET} %-32s ${RED}%-10s${RESET} %s\n" "$svc" "stopped" "-" fi done # Docker services echo "" local containers=("homeai-open-webui" "homeai-uptime-kuma" "homeai-n8n" "homeai-code-server") for c in "${containers[@]}"; do local state state=$(docker inspect -f '{{.State.Status}}' "$c" 2>/dev/null || echo "not found") if [[ "$state" == "running" ]]; then printf " ${GREEN}●${RESET} %-32s ${GREEN}%-10s${RESET}\n" "$c" "$state" else printf " ${RED}○${RESET} %-32s ${RED}%-10s${RESET}\n" "$c" "$state" fi done echo "" } # ─── Main ───────────────────────────────────────────────────────────────────── main() { local cmd="${1:-start}" case "$cmd" in start) do_start ;; stop) do_stop ;; restart) do_stop; echo ""; do_start ;; status) do_status ;; -h|--help|help) echo "" echo " Usage: $0 [start|stop|restart|status]" echo "" echo " Commands:" echo " start Start all HomeAI services (default)" echo " stop Stop all HomeAI services" echo " restart Stop then start all services" echo " status Show current service status" echo "" ;; *) die "Unknown command: $cmd. Run '$0 help' for usage." ;; esac } main "$@"