Improve install script with verbose progress output
- Step headers, checkmarks, skip indicators for each component - Shows what's being installed vs already present - Progress messages for long operations (model pulls, rootfs build) - Banner at start and summary at end with disk usage and model count - Per-package install status on Debian/Ubuntu Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 ""
|
||||
|
||||
Reference in New Issue
Block a user