Add install script for one-command deployment

scripts/install.sh handles full fireclaw deployment on a fresh machine:
- System packages (curl, jq, git, ngircd)
- Node.js 20, Firecracker, Ollama
- ngircd config (nyx.fireclaw.local)
- Agent rootfs build (Alpine + Python + podman)
- VM snapshot, overseer systemd service, templates
- Optional --with-gpu flag for larger models

Usage: ./scripts/install.sh [--with-gpu]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 14:52:45 +00:00
parent 5cc6a38c96
commit 129fd4d869
2 changed files with 367 additions and 2 deletions
+5 -2
View File
@@ -121,8 +121,11 @@ Export an agent's complete state (workspace, config, rootfs diff) as a tarball.
### Multi-host agents ### 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. Run agents on multiple machines (grogbox + odin). Overseer manages VMs across hosts via SSH. Agents on different hosts communicate via IRC federation.
### GPU passthrough ### GPU deployment
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. 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 ## Fun & Experimental
+362
View File
@@ -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 "═══════════════════════════════════════════════"