diff --git a/scripts/install.sh b/scripts/install.sh index 565d829..7a5c74e 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -10,45 +10,83 @@ set -euo pipefail WITH_GPU=false [[ "${1:-}" == "--with-gpu" ]] && WITH_GPU=true -log() { echo -e "\033[1;34m[fireclaw]\033[0m $*"; } -err() { echo -e "\033[1;31m[error]\033[0m $*" >&2; exit 1; } +log() { echo -e "\033[1;34m[fireclaw]\033[0m $*"; } +step() { echo -e "\n\033[1;32m━━━ $* ━━━\033[0m"; } +ok() { echo -e " \033[0;32m✓\033[0m $*"; } +skip() { echo -e " \033[0;33m→\033[0m $* (already installed)"; } +err() { echo -e "\033[1;31m[error]\033[0m $*" >&2; exit 1; } + +echo "" +echo " ╔═══════════════════════════════════════╗" +echo " ║ Fireclaw Installer v0.1.2 ║" +echo " ║ Multi-agent Firecracker system ║" +echo " ╚═══════════════════════════════════════╝" +echo "" # ─── Preflight checks ──────────────────────────────────────────────── -log "Running preflight checks..." +step "Preflight checks" [[ $(uname) != "Linux" ]] && err "Linux required." -[[ ! -e /dev/kvm ]] && err "KVM not available. Enable virtualization in BIOS." +ok "Linux detected: $(uname -r)" -if ! groups | grep -qw kvm; then +[[ ! -e /dev/kvm ]] && err "KVM not available. Enable virtualization in BIOS." +ok "KVM available" + +if groups | grep -qw kvm; then + ok "User $(whoami) in kvm group" +else log "Adding $(whoami) to kvm group (re-login required after install)..." sudo usermod -aG kvm "$(whoami)" + ok "Added to kvm group" fi # ─── System packages ───────────────────────────────────────────────── -log "Installing system packages..." +step "System packages" if command -v apt-get &>/dev/null; then + log "Updating apt..." sudo apt-get update -qq - sudo apt-get install -y -qq curl jq git ngircd >/dev/null + for pkg in curl jq git ngircd e2fsprogs; do + if dpkg -l "$pkg" &>/dev/null; then + skip "$pkg" + else + log "Installing $pkg..." + sudo apt-get install -y -qq "$pkg" >/dev/null + ok "$pkg installed" + fi + done elif command -v dnf &>/dev/null; then - sudo dnf install -y -q curl jq git ngircd + for pkg in curl jq git ngircd e2fsprogs; do + if rpm -q "$pkg" &>/dev/null; then + skip "$pkg" + else + log "Installing $pkg..." + sudo dnf install -y -q "$pkg" + ok "$pkg installed" + fi + done elif command -v apk &>/dev/null; then - sudo apk add --no-cache curl jq git ngircd + sudo apk add --no-cache curl jq git ngircd e2fsprogs + ok "Packages installed" else - err "Unsupported package manager. Install manually: curl, jq, git, ngircd" + err "Unsupported package manager. Install manually: curl, jq, git, ngircd, e2fsprogs" fi # ─── Node.js ────────────────────────────────────────────────────────── +step "Node.js" + NODE_VER=0 if command -v node &>/dev/null; then NODE_VER=$(node -v | cut -d. -f1 | tr -d v) fi -if [[ $NODE_VER -lt 20 ]]; then - log "Installing Node.js 20 (found: ${NODE_VER:-none})..." +if [[ $NODE_VER -ge 20 ]]; then + skip "Node.js $(node -v)" +else + log "Installing Node.js 20 (current: ${NODE_VER:-not installed})..." if command -v apt-get &>/dev/null; then curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - >/dev/null 2>&1 sudo apt-get install -y -qq nodejs >/dev/null @@ -60,38 +98,45 @@ if [[ $NODE_VER -lt 20 ]]; then else err "Cannot install Node.js 20+. Install it manually." fi + NODE_VER=$(node -v | cut -d. -f1 | tr -d v) + [[ $NODE_VER -lt 20 ]] && err "Node.js 20+ required, found $(node -v). Install manually." + ok "Node.js $(node -v) installed" fi -NODE_VER=$(node -v | cut -d. -f1 | tr -d v) -[[ $NODE_VER -lt 20 ]] && err "Node.js 20+ required, found $(node -v). Install manually." -log "Node.js $(node -v) OK" - # ─── Firecracker ────────────────────────────────────────────────────── -if ! command -v firecracker &>/dev/null; then - log "Installing Firecracker..." +step "Firecracker" + +if command -v firecracker &>/dev/null; then + skip "Firecracker $(firecracker --version 2>&1 | head -1)" +else ARCH=$(uname -m) + log "Fetching latest Firecracker release..." FC_VERSION=$(curl -fsSL https://api.github.com/repos/firecracker-microvm/firecracker/releases/latest | jq -r .tag_name) + log "Downloading Firecracker ${FC_VERSION} (${ARCH})..." curl -fSL -o /tmp/firecracker.tgz \ "https://github.com/firecracker-microvm/firecracker/releases/download/${FC_VERSION}/firecracker-${FC_VERSION}-${ARCH}.tgz" + log "Extracting..." tar xzf /tmp/firecracker.tgz -C /tmp sudo cp "/tmp/release-${FC_VERSION}-${ARCH}/firecracker-${FC_VERSION}-${ARCH}" /usr/local/bin/firecracker sudo cp "/tmp/release-${FC_VERSION}-${ARCH}/jailer-${FC_VERSION}-${ARCH}" /usr/local/bin/jailer rm -rf /tmp/firecracker.tgz "/tmp/release-${FC_VERSION}-${ARCH}" - log "Firecracker $(firecracker --version 2>&1 | head -1) installed" -else - log "Firecracker already installed: $(firecracker --version 2>&1 | head -1)" + ok "Firecracker $(firecracker --version 2>&1 | head -1) installed" fi # ─── Ollama ─────────────────────────────────────────────────────────── -if ! command -v ollama &>/dev/null; then +step "Ollama" + +if command -v ollama &>/dev/null; then + skip "Ollama $(ollama --version 2>&1 | grep -oP '[\d.]+' | head -1)" +else log "Installing Ollama..." - curl -fsSL https://ollama.com/install.sh | sh >/dev/null 2>&1 || true + curl -fsSL https://ollama.com/install.sh | sh 2>&1 | tail -3 || true + ok "Ollama installed" fi -# Configure Ollama service -log "Configuring Ollama service..." +log "Configuring Ollama systemd service..." sudo tee /etc/systemd/system/ollama.service > /dev/null << EOF [Unit] Description=Ollama LLM Server @@ -113,22 +158,30 @@ EOF sudo systemctl daemon-reload sudo systemctl enable --now ollama >/dev/null 2>&1 sleep 2 +ok "Ollama service running on 0.0.0.0:11434" -# Pull default model -log "Pulling default model (qwen2.5-coder:7b)..." +log "Pulling default model: qwen2.5-coder:7b (this may take a few minutes)..." ollama pull qwen2.5-coder:7b 2>/dev/null || true +ok "qwen2.5-coder:7b ready" if $WITH_GPU; then log "GPU mode: pulling larger models..." + log " Pulling qwen2.5:14b..." ollama pull qwen2.5:14b 2>/dev/null || true + ok "qwen2.5:14b ready" + log " Pulling qwen2.5-coder:14b..." ollama pull qwen2.5-coder:14b 2>/dev/null || true + ok "qwen2.5-coder:14b ready" fi # ─── ngircd ─────────────────────────────────────────────────────────── -log "Configuring ngircd..." -HOSTNAME=$(hostname -s) +step "IRC Server (ngircd)" +log "Backing up existing config..." +[[ -f /etc/ngircd/ngircd.conf ]] && sudo cp /etc/ngircd/ngircd.conf /etc/ngircd/ngircd.conf.bak 2>/dev/null || true + +log "Writing fireclaw IRC config..." sudo tee /etc/ngircd/ngircd.conf > /dev/null << EOF [Global] Name = nyx.fireclaw.local @@ -174,37 +227,51 @@ EOF sudo systemctl enable --now ngircd >/dev/null 2>&1 sudo systemctl restart ngircd -log "ngircd configured as nyx.fireclaw.local" +ok "ngircd running as nyx.fireclaw.local" # ─── Fireclaw ───────────────────────────────────────────────────────── +step "Fireclaw" + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -log "Installing fireclaw from ${SCRIPT_DIR}..." +log "Installing npm dependencies..." cd "$SCRIPT_DIR" npm install --silent 2>/dev/null -npm run build 2>/dev/null -sudo npm link 2>/dev/null +ok "Dependencies installed" -# Run fireclaw setup (kernel, rootfs, bridge, SSH keys) -log "Running fireclaw setup..." +log "Building TypeScript..." +npm run build 2>/dev/null +ok "Build complete" + +log "Linking global command..." +sudo npm link 2>/dev/null +ok "fireclaw command available globally" + +log "Running fireclaw setup (kernel + base rootfs + bridge + SSH keys)..." fireclaw setup +ok "Base setup complete" # ─── Agent rootfs ───────────────────────────────────────────────────── +step "Agent rootfs" + FIRECLAW_DIR="$HOME/.fireclaw" -if [[ ! -f "$FIRECLAW_DIR/agent-rootfs.ext4" ]]; then - log "Building agent rootfs..." +if [[ -f "$FIRECLAW_DIR/agent-rootfs.ext4" ]]; then + skip "Agent rootfs" +else + log "Creating 1G agent rootfs from Alpine base..." cp "$FIRECLAW_DIR/base-rootfs.ext4" /tmp/agent-build.ext4 truncate -s 1G /tmp/agent-build.ext4 sudo /usr/sbin/e2fsck -fy /tmp/agent-build.ext4 >/dev/null 2>&1 sudo /usr/sbin/resize2fs /tmp/agent-build.ext4 >/dev/null 2>&1 + ok "Image resized to 1G" mkdir -p /tmp/agent-build-mnt sudo mount /tmp/agent-build.ext4 /tmp/agent-build-mnt - # Install packages + log "Installing packages (openssh, python3, podman, curl, jq, bash)..." sudo chroot /tmp/agent-build-mnt sh -c ' apk update >/dev/null 2>&1 apk add --no-cache openssh-server ca-certificates curl jq python3 bash openrc podman iptables >/dev/null 2>&1 @@ -223,19 +290,20 @@ if [[ ! -f "$FIRECLAW_DIR/agent-rootfs.ext4" ]]; then echo "[storage]" > /etc/containers/storage.conf echo "driver = \"vfs\"" >> /etc/containers/storage.conf ' + ok "Alpine packages installed" - # Install agent script + log "Installing agent script and config..." sudo mkdir -p /tmp/agent-build-mnt/opt/agent /tmp/agent-build-mnt/etc/agent sudo cp "$SCRIPT_DIR/agent/agent.py" /tmp/agent-build-mnt/opt/agent/agent.py sudo chmod +x /tmp/agent-build-mnt/opt/agent/agent.py - # Default config echo '{"nick":"agent","model":"qwen2.5-coder:7b","trigger":"mention","server":"172.16.0.1","port":6667,"ollama_url":"http://172.16.0.1:11434"}' | \ sudo tee /tmp/agent-build-mnt/etc/agent/config.json > /dev/null echo "You are a helpful assistant on IRC." | \ sudo tee /tmp/agent-build-mnt/etc/agent/persona.md > /dev/null + ok "Agent script installed" - # Inittab — auto-start agent as non-root + log "Configuring init system..." sudo tee /tmp/agent-build-mnt/etc/inittab > /dev/null << 'INITTAB' ::sysinit:/sbin/openrc sysinit ::sysinit:/sbin/openrc boot @@ -246,7 +314,6 @@ ttyS0::respawn:/sbin/getty -L 115200 ttyS0 vt100 ::shutdown:/sbin/openrc shutdown INITTAB - # Boot services for podman + workspace sudo tee /tmp/agent-build-mnt/etc/init.d/podman-setup > /dev/null << 'SVC' #!/sbin/openrc-run description="Set up podman prerequisites" @@ -279,7 +346,6 @@ SVC sudo chmod +x /tmp/agent-build-mnt/etc/init.d/workspace sudo chroot /tmp/agent-build-mnt rc-update add workspace boot 2>/dev/null - # Networking init sudo tee /tmp/agent-build-mnt/etc/init.d/networking > /dev/null << 'SVC' #!/sbin/openrc-run depend() { need localmount; } @@ -287,25 +353,31 @@ start() { ip link set lo up; return 0; } SVC sudo chmod +x /tmp/agent-build-mnt/etc/init.d/networking sudo chroot /tmp/agent-build-mnt rc-update add networking boot 2>/dev/null + ok "Init services configured" sudo umount /tmp/agent-build-mnt rmdir /tmp/agent-build-mnt sudo mv /tmp/agent-build.ext4 "$FIRECLAW_DIR/agent-rootfs.ext4" - log "Agent rootfs built" -else - log "Agent rootfs already exists" + ok "Agent rootfs built ($(du -sh "$FIRECLAW_DIR/agent-rootfs.ext4" | cut -f1) on disk)" fi # ─── Snapshot ───────────────────────────────────────────────────────── -if [[ ! -f "$FIRECLAW_DIR/snapshot.state" ]]; then +step "VM Snapshot" + +if [[ -f "$FIRECLAW_DIR/snapshot.state" ]]; then + skip "Snapshot" +else log "Creating VM snapshot for fast restores..." fireclaw snapshot create + ok "Snapshot created" fi # ─── Overseer service ──────────────────────────────────────────────── -log "Configuring overseer service..." +step "Overseer service" + +log "Configuring systemd service..." sudo tee /etc/systemd/system/fireclaw-overseer.service > /dev/null << EOF [Unit] Description=Fireclaw Overseer — IRC agent lifecycle manager @@ -327,50 +399,53 @@ EOF sudo systemctl daemon-reload sudo systemctl enable --now fireclaw-overseer >/dev/null 2>&1 -log "Overseer service started" +ok "Overseer running in #control" # ─── Templates ──────────────────────────────────────────────────────── +step "Agent templates" + TMPL_DIR="$FIRECLAW_DIR/templates" mkdir -p "$TMPL_DIR" -[[ ! -f "$TMPL_DIR/worker.json" ]] && cat > "$TMPL_DIR/worker.json" << 'EOF' -{"name":"worker","nick":"worker","model":"qwen2.5-coder:7b","trigger":"mention","persona":"You are a general-purpose assistant on IRC. Keep responses concise."} -EOF - -[[ ! -f "$TMPL_DIR/coder.json" ]] && cat > "$TMPL_DIR/coder.json" << 'EOF' -{"name":"coder","nick":"coder","model":"qwen2.5-coder:7b","trigger":"mention","persona":"You are a code-focused assistant on IRC. Be direct and technical."} -EOF - -[[ ! -f "$TMPL_DIR/quick.json" ]] && cat > "$TMPL_DIR/quick.json" << 'EOF' -{"name":"quick","nick":"quick","model":"phi4-mini","trigger":"mention","tools":false,"network":"none","persona":"You are a fast assistant on IRC. One sentence answers."} -EOF - -log "Templates installed" +for tmpl in worker coder quick; do + if [[ -f "$TMPL_DIR/$tmpl.json" ]]; then + skip "$tmpl" + else + case $tmpl in + worker) echo '{"name":"worker","nick":"worker","model":"qwen2.5-coder:7b","trigger":"mention","persona":"You are a general-purpose assistant on IRC. Keep responses concise."}' > "$TMPL_DIR/$tmpl.json" ;; + coder) echo '{"name":"coder","nick":"coder","model":"qwen2.5-coder:7b","trigger":"mention","persona":"You are a code-focused assistant on IRC. Be direct and technical."}' > "$TMPL_DIR/$tmpl.json" ;; + quick) echo '{"name":"quick","nick":"quick","model":"phi4-mini","trigger":"mention","tools":false,"network":"none","persona":"You are a fast assistant on IRC. One sentence answers."}' > "$TMPL_DIR/$tmpl.json" ;; + esac + ok "$tmpl template created" + fi +done # ─── Done ───────────────────────────────────────────────────────────── echo "" -log "═══════════════════════════════════════════════" -log " Fireclaw installed successfully!" -log "═══════════════════════════════════════════════" -log "" -log " Services running:" -log " ngircd nyx.fireclaw.local :6667" -log " ollama 0.0.0.0:11434" -log " overseer IRC #control" -log "" -log " Connect to IRC:" -log " irssi -c localhost -n human" -log " /join #control" -log " !invoke worker" -log "" -log " Commands:" -log " fireclaw run \"uname -a\" Run in ephemeral VM" -log " fireclaw agent list List running agents" -log " fireclaw --help Full help" -log "" +echo " ╔═══════════════════════════════════════╗" +echo " ║ Installation complete! ║" +echo " ╚═══════════════════════════════════════╝" +echo "" +log "Services:" +log " ngircd nyx.fireclaw.local :6667" +log " ollama 0.0.0.0:11434" +log " overseer IRC #control" +echo "" +log "Quick start:" +log " irssi -c localhost -n human" +log " /join #control" +log " !invoke worker" +echo "" +log "CLI:" +log " fireclaw run \"uname -a\"" +log " fireclaw agent list" +log " fireclaw --help" +echo "" if $WITH_GPU; then - log " GPU mode enabled — larger models available" + log "GPU mode: larger models pulled (14b)" fi -log "═══════════════════════════════════════════════" +log "Disk usage: $(du -sh "$FIRECLAW_DIR" | cut -f1) in $FIRECLAW_DIR" +log "Models: $(ollama list 2>/dev/null | tail -n +2 | wc -l) available" +echo ""