From 7978eaea14a5857f7dd9bd991bff63c4dcdd1b75 Mon Sep 17 00:00:00 2001 From: Aodhan Collins Date: Wed, 4 Mar 2026 21:10:53 +0000 Subject: [PATCH] Add self-deploying setup scripts for all sub-projects (P1-P8) - Root setup.sh orchestrator with per-phase dispatch (./setup.sh p1..p8 | all | status) - Makefile convenience targets (make infra, make llm, make status, etc.) - scripts/common.sh: shared bash library for OS detection, Docker helpers, service management (launchd/systemd), package install, env management - .env.example + .gitignore: shared config template and secret exclusions P1 (homeai-infra): full implementation - docker-compose.yml: Uptime Kuma, code-server, n8n - Note: Home Assistant, Portainer, Gitea are pre-existing instances - setup.sh: Docker install, homeai network, container health checks P2 (homeai-llm): full implementation - Ollama native install with CUDA/ROCm/Metal auto-detection - launchd plist (macOS) + systemd service (Linux) for auto-start - scripts/pull-models.sh: idempotent model puller from manifest - scripts/benchmark.sh: tokens/sec measurement per model - Open WebUI on port 3030 (avoids Gitea :3000 conflict) P3-P8: working stubs with prerequisite checks and TODO sections Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 46 ++ .gitignore | 47 ++ Makefile | 86 +++ homeai-agent/setup.sh | 65 ++ homeai-character/character-manager.jsx | 686 +++++++++++++++++++++ homeai-character/setup.sh | 55 ++ homeai-esp32/setup.sh | 76 +++ homeai-images/setup.sh | 65 ++ homeai-infra/docker/.env.example | 12 + homeai-infra/docker/docker-compose.yml | 91 +++ homeai-infra/setup.sh | 135 ++++ homeai-llm/docker/.env.example | 7 + homeai-llm/docker/docker-compose.yml | 45 ++ homeai-llm/launchd/com.homeai.ollama.plist | 37 ++ homeai-llm/ollama-models.txt | 21 + homeai-llm/scripts/benchmark.sh | 88 +++ homeai-llm/scripts/pull-models.sh | 86 +++ homeai-llm/setup.sh | 227 +++++++ homeai-llm/systemd/homeai-ollama.service | 26 + homeai-visual/setup.sh | 60 ++ homeai-voice/setup.sh | 93 +++ scripts/common.sh | 331 ++++++++++ setup.sh | 140 +++++ 23 files changed, 2525 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 homeai-agent/setup.sh create mode 100644 homeai-character/character-manager.jsx create mode 100644 homeai-character/setup.sh create mode 100644 homeai-esp32/setup.sh create mode 100644 homeai-images/setup.sh create mode 100644 homeai-infra/docker/.env.example create mode 100644 homeai-infra/docker/docker-compose.yml create mode 100644 homeai-infra/setup.sh create mode 100644 homeai-llm/docker/.env.example create mode 100644 homeai-llm/docker/docker-compose.yml create mode 100644 homeai-llm/launchd/com.homeai.ollama.plist create mode 100644 homeai-llm/ollama-models.txt create mode 100644 homeai-llm/scripts/benchmark.sh create mode 100644 homeai-llm/scripts/pull-models.sh create mode 100644 homeai-llm/setup.sh create mode 100644 homeai-llm/systemd/homeai-ollama.service create mode 100644 homeai-visual/setup.sh create mode 100644 homeai-voice/setup.sh create mode 100644 scripts/common.sh create mode 100644 setup.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..395fd2d --- /dev/null +++ b/.env.example @@ -0,0 +1,46 @@ +# HomeAI — Shared Configuration Template +# Copy to .env and fill in your values. +# .env is gitignored — never commit it. + +# ─── Data & Paths ────────────────────────────────────────────────────────────── +DATA_DIR=${HOME}/homeai-data +REPO_DIR=${HOME}/Projects/HomeAI + +# ─── Network ─────────────────────────────────────────────────────────────────── +# Set to your machine's local IP (not 127.0.0.1) +HOST_IP=192.168.1.100 + +# ─── P1: Infrastructure ──────────────────────────────────────────────────────── +# Pre-existing instances — set these to your actual URLs +HA_URL=http://localhost:8123 +HA_TOKEN= # Generated in Home Assistant UI → Profile → Security +PORTAINER_URL=https://localhost:9443 +GITEA_URL=http://localhost:3000 + +# Managed by homeai-infra docker-compose +UPTIME_KUMA_URL=http://localhost:3001 +CODE_SERVER_URL=http://localhost:8090 +CODE_SERVER_PASS= # Set in homeai-infra/docker/.env +N8N_URL=http://localhost:5678 +N8N_USER=admin +N8N_PASS= # Set in homeai-infra/docker/.env + +# ─── P2: LLM ─────────────────────────────────────────────────────────────────── +OLLAMA_URL=http://localhost:11434 +OLLAMA_API_URL=http://localhost:11434/v1 +OPEN_WEBUI_URL=http://localhost:3030 +OLLAMA_PRIMARY_MODEL=llama3.3:70b +OLLAMA_FAST_MODEL=qwen2.5:7b + +# ─── P3: Voice ───────────────────────────────────────────────────────────────── +WYOMING_STT_URL=tcp://localhost:10300 +WYOMING_TTS_URL=tcp://localhost:10301 + +# ─── P4: Agent ───────────────────────────────────────────────────────────────── +OPENCLAW_URL=http://localhost:8080 + +# ─── P7: Visual ──────────────────────────────────────────────────────────────── +VTUBE_WS_URL=ws://localhost:8001 + +# ─── P8: Images ──────────────────────────────────────────────────────────────── +COMFYUI_URL=http://localhost:8188 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..919de4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Secrets — never commit +.env +.env.secrets +.env.services +**/secrets.yaml +**/secrets.yml + +# Docker volumes / data +homeai-data/ +**/data/ + +# Python +__pycache__/ +*.pyc +*.pyo +.venv/ +venv/ +*.egg-info/ + +# Node +node_modules/ +dist/ +.cache/ + +# macOS +.DS_Store +*.localized + +# Editor +.vscode/ +*.swp +*.swo + +# Ollama model cache (large binaries) +*.gguf +*.safetensors +*.bin +*.ckpt +*.pt + +# ESPHome secrets +homeai-esp32/esphome/secrets.yaml + +# Generated +homeai-llm/benchmark-results.md +homeai-character/characters/*.json +!homeai-character/characters/.gitkeep diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..409b9f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,86 @@ +# HomeAI Makefile — convenience wrapper around setup.sh +# Run `make help` to see all targets. + +SHELL := /bin/bash +REPO_DIR := $(shell pwd) + +.PHONY: help all status infra llm voice agent character esp32 visual images \ + up down restart logs ps clean + +help: + @echo "" + @echo " HomeAI — Make targets" + @echo "" + @echo " Setup:" + @echo " make all Run full setup (all phases)" + @echo " make infra P1: Docker infra stack" + @echo " make llm P2: Ollama + Open WebUI" + @echo " make voice P3: STT / TTS / Wyoming" + @echo " make agent P4: OpenClaw + skills" + @echo " make character P5: Character Manager" + @echo " make esp32 P6: ESPHome firmware" + @echo " make visual P7: VTube Studio bridge" + @echo " make images P8: ComfyUI" + @echo "" + @echo " Operations:" + @echo " make status Show service health" + @echo " make up Start all Docker services" + @echo " make down Stop all Docker services" + @echo " make restart Restart all Docker services" + @echo " make logs Tail logs (all services)" + @echo " make ps Show running containers" + @echo " make clean Remove stopped containers" + @echo "" + +all: + bash setup.sh all + +status: + bash setup.sh status + +infra: + bash setup.sh p1 + +llm: + bash setup.sh p2 + +voice: + bash setup.sh p3 + +agent: + bash setup.sh p4 + +character: + bash setup.sh p5 + +esp32: + bash setup.sh p6 + +visual: + bash setup.sh p7 + +images: + bash setup.sh p8 + +# ─── Docker operations ───────────────────────────────────────────────────────── +up: + @cd homeai-infra && docker compose -f docker/docker-compose.yml up -d + @cd homeai-llm && docker compose -f docker/docker-compose.yml up -d + +down: + @cd homeai-infra && docker compose -f docker/docker-compose.yml down || true + @cd homeai-llm && docker compose -f docker/docker-compose.yml down || true + +restart: + @cd homeai-infra && docker compose -f docker/docker-compose.yml restart + @cd homeai-llm && docker compose -f docker/docker-compose.yml restart + +logs: + @cd homeai-infra && docker compose -f docker/docker-compose.yml logs -f --tail=50 + +ps: + @docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep homeai || \ + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + +clean: + @docker container prune -f diff --git a/homeai-agent/setup.sh b/homeai-agent/setup.sh new file mode 100644 index 0000000..be8ac23 --- /dev/null +++ b/homeai-agent/setup.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# homeai-agent/setup.sh — P4: OpenClaw agent + skills + mem0 +# +# Components: +# - OpenClaw — AI agent runtime (port 8080) +# - skills/ — home_assistant, memory, weather, timer, music stubs +# - mem0 — long-term memory (Chroma backend) +# - n8n workflows — morning briefing, notification router, memory backup +# +# Prerequisites: +# - P1 (homeai-infra) — Home Assistant running, HA_TOKEN set +# - P2 (homeai-llm) — Ollama running with llama3.3:70b + nomic-embed-text +# - P3 (homeai-voice) — Wyoming TTS running (for voice output) +# - P5 (homeai-character) — aria.json character config exists + +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 + skills + mem0)" +detect_platform + +# ─── Prerequisite check ──────────────────────────────────────────────────────── +log_info "Checking prerequisites..." + +for service in "http://localhost:11434:Ollama(P2)" "http://localhost:8123:HomeAssistant(P1)"; do + url="${service%%:*}"; name="${service##*:}" + if ! curl -sf "$url" -o /dev/null 2>/dev/null; then + log_warn "$name not reachable at $url" + fi +done + +load_env_services +if [[ -z "${HA_TOKEN:-}" ]]; then + log_warn "HA_TOKEN not set in ~/.env.services — needed for home_assistant skill" +fi + +# ─── TODO: Implementation ────────────────────────────────────────────────────── +cat <<'EOF' + + ┌─────────────────────────────────────────────────────────────────┐ + │ P4: homeai-agent — NOT YET IMPLEMENTED │ + │ │ + │ OPEN QUESTION: Which OpenClaw version/fork to use? │ + │ Decide before implementing. See homeai-agent/PLAN.md. │ + │ │ + │ Implementation steps: │ + │ 1. Install OpenClaw (pip install or git clone) │ + │ 2. Create ~/.openclaw/config.yaml from config/config.yaml.example │ + │ 3. Create skills: home_assistant, memory, weather, timer, music│ + │ 4. Install mem0 + Chroma backend │ + │ 5. Create systemd/launchd service for OpenClaw (port 8080) │ + │ 6. Import n8n workflows from workflows/ │ + │ 7. Smoke test: POST /chat "turn on living room lights" │ + │ │ + │ Interface contracts: │ + │ OPENCLAW_URL=http://localhost:8080 │ + └─────────────────────────────────────────────────────────────────┘ + +EOF + +log_info "P4 is not yet implemented. See homeai-agent/PLAN.md for details." +exit 0 diff --git a/homeai-character/character-manager.jsx b/homeai-character/character-manager.jsx new file mode 100644 index 0000000..33e063d --- /dev/null +++ b/homeai-character/character-manager.jsx @@ -0,0 +1,686 @@ +import { useState, useEffect, useCallback } from "react"; + +const STORAGE_KEY = "ai-character-profiles"; + +const DEFAULT_MODELS = [ + "llama3.3:70b", "qwen2.5:72b", "mistral-large", "llama3.1:8b", + "qwen2.5:14b", "gemma3:27b", "deepseek-r1:14b", "phi4:14b" +]; + +const TTS_MODELS = ["Kokoro", "Chatterbox", "F5-TTS", "Qwen3-TTS", "Piper"]; +const STT_MODELS = ["Whisper Large-v3", "Whisper Medium", "Whisper Small", "Whisper Turbo"]; +const IMAGE_MODELS = ["SDXL", "Flux.1-dev", "Flux.1-schnell", "SD 1.5", "Pony Diffusion"]; + +const PERSONALITY_TRAITS = [ + "Warm", "Witty", "Calm", "Energetic", "Sarcastic", "Nurturing", + "Curious", "Playful", "Formal", "Casual", "Empathetic", "Direct", + "Creative", "Analytical", "Protective", "Mischievous" +]; + +const SPEAKING_STYLES = [ + "Conversational", "Poetic", "Concise", "Verbose", "Academic", + "Informal", "Dramatic", "Deadpan", "Enthusiastic", "Measured" +]; + +const EMPTY_CHARACTER = { + id: null, + name: "", + tagline: "", + avatar: "", + accentColor: "#7c6fff", + personality: { + traits: [], + speakingStyle: "", + coreValues: "", + quirks: "", + backstory: "", + motivation: "", + }, + prompts: { + systemPrompt: "", + wakeWordResponse: "", + fallbackResponse: "", + errorResponse: "", + customPrompts: [], + }, + models: { + llm: "", + tts: "", + stt: "", + imageGen: "", + voiceCloneRef: "", + ttsSpeed: 1.0, + temperature: 0.7, + }, + liveRepresentation: { + live2dModel: "", + idleExpression: "", + speakingExpression: "", + thinkingExpression: "", + happyExpression: "", + vtsTriggers: "", + }, + userNotes: "", + createdAt: null, + updatedAt: null, +}; + +const TABS = ["Identity", "Personality", "Prompts", "Models", "Live2D", "Notes"]; + +const TAB_ICONS = { + Identity: "◈", + Personality: "◉", + Prompts: "◎", + Models: "⬡", + Live2D: "◇", + Notes: "▣", +}; + +function generateId() { + return Date.now().toString(36) + Math.random().toString(36).slice(2); +} + +function ColorPicker({ value, onChange }) { + const presets = [ + "#7c6fff","#ff6b9d","#00d4aa","#ff9f43","#48dbfb", + "#ff6348","#a29bfe","#fd79a8","#55efc4","#fdcb6e" + ]; + return ( +
+ {presets.map(c => ( +
+ ); +} + +function TagSelector({ options, selected, onChange, max = 6 }) { + return ( +
+ {options.map(opt => { + const active = selected.includes(opt); + return ( + + ); + })} +
+ ); +} + +function Field({ label, hint, children }) { + return ( +
+ + {hint &&

{hint}

} + {children} +
+ ); +} + +function Input({ value, onChange, placeholder, type = "text" }) { + return ( + onChange(e.target.value)} placeholder={placeholder} + style={{ + width: "100%", background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", + borderRadius: 8, padding: "10px 14px", color: "#fff", fontSize: 14, fontFamily: "inherit", + outline: "none", boxSizing: "border-box", transition: "border-color 0.2s", + }} + onFocus={e => e.target.style.borderColor = "var(--accent)"} + onBlur={e => e.target.style.borderColor = "rgba(255,255,255,0.1)"} + /> + ); +} + +function Textarea({ value, onChange, placeholder, rows = 4 }) { + return ( +