diff --git a/IDEAS.md b/IDEAS.md index 34241df..02594b6 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -121,8 +121,11 @@ Export an agent's complete state (workspace, config, rootfs diff) as a tarball. ### Multi-host agents Run agents on multiple machines (grogbox + odin). Overseer manages VMs across hosts via SSH. Agents on different hosts communicate via IRC federation. -### GPU passthrough -When/if grogbox gets a GPU: pass it through to a single agent VM for fast inference. That agent becomes the "smart" one, others stay on CPU. Or run Ollama with GPU on the host and all agents benefit. +### GPU deployment +Remote machine available: Xeon E5-1620 v4, 32GB RAM, Quadro P5000 (16GB VRAM). Enough for 14B-30B models at 2-5s inference. Standalone fireclaw deployment — its own ngircd, its own agents, completely independent from grogbox. + +### Install script +`scripts/install.sh` — one-command deployment to new machines. Installs firecracker, ollama (with GPU if available), ngircd, Node.js, builds rootfs, configures everything. `curl -fsSL .../install.sh | bash` or just `./scripts/install.sh`. No Ansible dependency — plain bash. ## Fun & Experimental diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..53830e7 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,362 @@ +#!/bin/bash +# Fireclaw install script +# Installs everything needed to run fireclaw on a fresh Linux machine. +# Requires: root or sudo access, KVM support, internet access. +# +# Usage: ./scripts/install.sh [--with-gpu] + +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; } + +# ─── Preflight checks ──────────────────────────────────────────────── + +log "Running preflight checks..." + +[[ $(uname) != "Linux" ]] && err "Linux required." +[[ ! -e /dev/kvm ]] && err "KVM not available. Enable virtualization in BIOS." + +if ! groups | grep -qw kvm; then + log "Adding $(whoami) to kvm group (re-login required after install)..." + sudo usermod -aG kvm "$(whoami)" +fi + +# ─── System packages ───────────────────────────────────────────────── + +log "Installing system packages..." + +if command -v apt-get &>/dev/null; then + sudo apt-get update -qq + sudo apt-get install -y -qq curl jq git ngircd >/dev/null +elif command -v dnf &>/dev/null; then + sudo dnf install -y -q curl jq git ngircd +elif command -v apk &>/dev/null; then + sudo apk add --no-cache curl jq git ngircd +else + err "Unsupported package manager. Install manually: curl, jq, git, ngircd" +fi + +# ─── Node.js ────────────────────────────────────────────────────────── + +if ! command -v node &>/dev/null; then + log "Installing Node.js 20..." + 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 +fi + +NODE_VER=$(node -v | cut -d. -f1 | tr -d v) +[[ $NODE_VER -lt 20 ]] && err "Node.js 20+ required, found $(node -v)" +log "Node.js $(node -v) OK" + +# ─── Firecracker ────────────────────────────────────────────────────── + +if ! command -v firecracker &>/dev/null; then + log "Installing Firecracker..." + ARCH=$(uname -m) + FC_VERSION=$(curl -fsSL https://api.github.com/repos/firecracker-microvm/firecracker/releases/latest | jq -r .tag_name) + curl -fSL -o /tmp/firecracker.tgz \ + "https://github.com/firecracker-microvm/firecracker/releases/download/${FC_VERSION}/firecracker-${FC_VERSION}-${ARCH}.tgz" + 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)" +fi + +# ─── Ollama ─────────────────────────────────────────────────────────── + +if ! command -v ollama &>/dev/null; then + log "Installing Ollama..." + curl -fsSL https://ollama.com/install.sh | sh >/dev/null 2>&1 || true +fi + +# Configure Ollama service +log "Configuring Ollama service..." +sudo tee /etc/systemd/system/ollama.service > /dev/null << EOF +[Unit] +Description=Ollama LLM Server +After=network-online.target +Wants=network-online.target + +[Service] +ExecStart=/usr/local/bin/ollama serve +User=$(whoami) +Group=$(id -gn) +Restart=always +RestartSec=3 +Environment="OLLAMA_HOST=0.0.0.0" + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable --now ollama >/dev/null 2>&1 +sleep 2 + +# Pull default model +log "Pulling default model (qwen2.5-coder:7b)..." +ollama pull qwen2.5-coder:7b 2>/dev/null || true + +if $WITH_GPU; then + log "GPU mode: pulling larger models..." + ollama pull qwen2.5:14b 2>/dev/null || true + ollama pull qwen2.5-coder:14b 2>/dev/null || true +fi + +# ─── ngircd ─────────────────────────────────────────────────────────── + +log "Configuring ngircd..." +HOSTNAME=$(hostname -s) + +sudo tee /etc/ngircd/ngircd.conf > /dev/null << EOF +[Global] + Name = nyx.fireclaw.local + AdminInfo1 = fireclaw + AdminEMail = admin@localhost + Info = nyx - fireclaw agent network + Listen = 127.0.0.1,172.16.0.1 + Network = FireclawNet + PidFile = /run/ngircd/ngircd.pid + ServerGID = irc + ServerUID = irc + +[Limits] + ConnectRetry = 60 + MaxConnections = 100 + MaxConnectionsIP = 20 + MaxJoins = 20 + PingTimeout = 120 + PongTimeout = 20 + +[Options] + DNS = no + Ident = no + PAM = no + OperCanUseMode = yes + DefaultUserModes = CiFo + SyslogFacility = daemon + +[Operator] + Name = admin + Password = fireclaw-oper + +[Channel] + Name = #control + Topic = Overseer command channel + Modes = +tn + +[Channel] + Name = #agents + Topic = Agent common room + Modes = +tnQN +EOF + +sudo systemctl enable --now ngircd >/dev/null 2>&1 +sudo systemctl restart ngircd +log "ngircd configured as nyx.fireclaw.local" + +# ─── Fireclaw ───────────────────────────────────────────────────────── + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +log "Installing fireclaw from ${SCRIPT_DIR}..." +cd "$SCRIPT_DIR" +npm install --silent 2>/dev/null +npm run build 2>/dev/null +sudo npm link 2>/dev/null + +# Run fireclaw setup (kernel, rootfs, bridge, SSH keys) +log "Running fireclaw setup..." +fireclaw setup + +# ─── Agent rootfs ───────────────────────────────────────────────────── + +FIRECLAW_DIR="$HOME/.fireclaw" + +if [[ ! -f "$FIRECLAW_DIR/agent-rootfs.ext4" ]]; then + log "Building agent rootfs..." + 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 + + mkdir -p /tmp/agent-build-mnt + sudo mount /tmp/agent-build.ext4 /tmp/agent-build-mnt + + # Install packages + 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 + rc-update add sshd default 2>/dev/null + rc-update add cgroups boot 2>/dev/null + ssh-keygen -A 2>/dev/null + echo "PermitRootLogin prohibit-password" >> /etc/ssh/sshd_config + adduser -D -h /home/agent -s /bin/bash agent + echo "root:100000:65536" > /etc/subuid + echo "root:100000:65536" > /etc/subgid + echo "agent:100000:65536" >> /etc/subuid + echo "agent:100000:65536" >> /etc/subgid + mkdir -p /etc/containers + echo "[containers]" > /etc/containers/containers.conf + echo "netns = \"host\"" >> /etc/containers/containers.conf + echo "[storage]" > /etc/containers/storage.conf + echo "driver = \"vfs\"" >> /etc/containers/storage.conf + ' + + # Install agent script + 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 + + # Inittab — auto-start agent as non-root + sudo tee /tmp/agent-build-mnt/etc/inittab > /dev/null << 'INITTAB' +::sysinit:/sbin/openrc sysinit +::sysinit:/sbin/openrc boot +::sysinit:/sbin/openrc default +ttyS0::respawn:/sbin/getty -L 115200 ttyS0 vt100 +::respawn:/bin/su -s /bin/sh agent -c "/usr/bin/python3 /opt/agent/agent.py" +::ctrlaltdel:/sbin/reboot +::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" +depend() { before sshd; after localmount; } +start() { + mkdir -p /sys/fs/cgroup /dev/shm + mount -t cgroup2 cgroup2 /sys/fs/cgroup 2>/dev/null + mount -t tmpfs tmpfs /dev/shm 2>/dev/null + return 0 +} +SVC + sudo chmod +x /tmp/agent-build-mnt/etc/init.d/podman-setup + sudo chroot /tmp/agent-build-mnt rc-update add podman-setup boot 2>/dev/null + + sudo tee /tmp/agent-build-mnt/etc/init.d/workspace > /dev/null << 'SVC' +#!/sbin/openrc-run +description="Mount agent workspace" +depend() { need localmount; before sshd; } +start() { + mkdir -p /workspace + if [ -b /dev/vdb ]; then + mount /dev/vdb /workspace + chown -R agent:agent /workspace + einfo "Workspace mounted at /workspace" + fi + return 0 +} +stop() { umount /workspace 2>/dev/null; return 0; } +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; } +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 + + 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" +fi + +# ─── Snapshot ───────────────────────────────────────────────────────── + +if [[ ! -f "$FIRECLAW_DIR/snapshot.state" ]]; then + log "Creating VM snapshot for fast restores..." + fireclaw snapshot create +fi + +# ─── Overseer service ──────────────────────────────────────────────── + +log "Configuring overseer service..." +sudo tee /etc/systemd/system/fireclaw-overseer.service > /dev/null << EOF +[Unit] +Description=Fireclaw Overseer — IRC agent lifecycle manager +After=network-online.target ngircd.service ollama.service +Wants=network-online.target + +[Service] +ExecStart=/usr/local/bin/fireclaw overseer +User=$(whoami) +Group=$(id -gn) +Restart=always +RestartSec=5 +KillMode=process +WorkingDirectory=$SCRIPT_DIR + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable --now fireclaw-overseer >/dev/null 2>&1 +log "Overseer service started" + +# ─── 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" + +# ─── 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 "" +if $WITH_GPU; then + log " GPU mode enabled — larger models available" +fi +log "═══════════════════════════════════════════════"