mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-19 16:39:50 +00:00
feat: pre-built Docker image for OpenClaw on Fly.io (#1686)
Eliminates the slow waitForCloudInit() + bun install phase by booting a pre-built image with Node.js, bun, and openclaw already installed. The image is rebuilt daily via GitHub Actions to pick up new releases. Other agents are unaffected — they still use ubuntu:24.04 + cloud-init. Co-authored-by: spawn-bot <spawn-bot@openrouter.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e381ca2412
commit
0f4df7be71
6 changed files with 95 additions and 11 deletions
34
.github/workflows/fly-docker.yml
vendored
Normal file
34
.github/workflows/fly-docker.yml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
name: Build Fly Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "fly/docker/openclaw.Dockerfile"
|
||||
schedule:
|
||||
# Daily: pick up new openclaw releases
|
||||
- cron: "0 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-openclaw:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: fly/docker/openclaw.Dockerfile
|
||||
push: true
|
||||
tags: ghcr.io/openrouterteam/spawn-openclaw:latest
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.5.33",
|
||||
"version": "0.5.34",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"spawn": "cli.js"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import {
|
|||
|
||||
export interface AgentConfig {
|
||||
name: string;
|
||||
/** Custom Docker image for the Fly machine (default: ubuntu:24.04). */
|
||||
image?: string;
|
||||
/** If true, prompt for model selection before provisioning. */
|
||||
modelPrompt?: boolean;
|
||||
/** Default model ID when modelPrompt is true. */
|
||||
|
|
@ -342,13 +344,22 @@ export const agents: Record<string, AgentConfig> = {
|
|||
|
||||
openclaw: {
|
||||
name: "OpenClaw",
|
||||
image: "ghcr.io/openrouterteam/spawn-openclaw:latest",
|
||||
modelPrompt: true,
|
||||
modelDefault: "openrouter/auto",
|
||||
install: () =>
|
||||
installAgent(
|
||||
"openclaw",
|
||||
'export PATH="$HOME/.bun/bin:$HOME/.local/bin:$PATH" && bun install -g openclaw && command -v openclaw',
|
||||
),
|
||||
install: async () => {
|
||||
logStep("Verifying openclaw installation...");
|
||||
try {
|
||||
await runServer("command -v openclaw");
|
||||
logInfo("openclaw is pre-installed");
|
||||
} catch {
|
||||
logInfo("openclaw not found in image, installing from scratch...");
|
||||
await installAgent(
|
||||
"openclaw",
|
||||
'export PATH="$HOME/.bun/bin:$HOME/.local/bin:$PATH" && bun install -g openclaw && command -v openclaw',
|
||||
);
|
||||
}
|
||||
},
|
||||
envVars: (apiKey) => [
|
||||
`OPENROUTER_API_KEY=${apiKey}`,
|
||||
`ANTHROPIC_API_KEY=${apiKey}`,
|
||||
|
|
|
|||
|
|
@ -567,11 +567,12 @@ async function createMachine(
|
|||
cpus: number,
|
||||
vmMemory: number,
|
||||
volumeId?: string,
|
||||
image?: string,
|
||||
): Promise<string> {
|
||||
const kindLabel = cpuKind === "performance" ? "dedicated" : "shared";
|
||||
logStep(`Creating Fly.io machine (region: ${region}, ${cpus} ${kindLabel} vCPU, ${vmMemory}MB)...`);
|
||||
const config: Record<string, unknown> = {
|
||||
image: "ubuntu:24.04",
|
||||
image: image || "ubuntu:24.04",
|
||||
guest: { cpu_kind: cpuKind, cpus, memory_mb: vmMemory },
|
||||
init: { exec: ["/bin/sleep", "inf"] },
|
||||
auto_destroy: false,
|
||||
|
|
@ -675,7 +676,7 @@ export async function listVolumes(appName: string): Promise<Array<{ id: string;
|
|||
}));
|
||||
}
|
||||
|
||||
export async function createServer(name: string, opts: ServerOptions): Promise<void> {
|
||||
export async function createServer(name: string, opts: ServerOptions, image?: string): Promise<void> {
|
||||
const region = process.env.FLY_REGION || "iad";
|
||||
|
||||
if (!validateRegionName(region)) {
|
||||
|
|
@ -698,7 +699,7 @@ export async function createServer(name: string, opts: ServerOptions): Promise<v
|
|||
|
||||
let machineId: string;
|
||||
try {
|
||||
machineId = await createMachine(name, region, opts.cpuKind, opts.cpus, opts.memoryMb, volumeId);
|
||||
machineId = await createMachine(name, region, opts.cpuKind, opts.cpus, opts.memoryMb, volumeId, image);
|
||||
} catch (err) {
|
||||
await cleanupOnFailure(name);
|
||||
throw err;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
createServer,
|
||||
getServerName,
|
||||
waitForCloudInit,
|
||||
waitForSsh,
|
||||
runServer,
|
||||
interactiveSession,
|
||||
saveLaunchCmd,
|
||||
|
|
@ -89,10 +90,15 @@ async function main() {
|
|||
|
||||
// 6. Provision server
|
||||
const serverName = await getServerName();
|
||||
await createServer(serverName, serverOpts);
|
||||
await createServer(serverName, serverOpts, agent.image);
|
||||
|
||||
// 7. Wait for readiness
|
||||
await waitForCloudInit();
|
||||
if (agent.image) {
|
||||
// Custom image already has packages baked in — just wait for SSH
|
||||
await waitForSsh();
|
||||
} else {
|
||||
await waitForCloudInit();
|
||||
}
|
||||
|
||||
// 8. Install agent
|
||||
await agent.install();
|
||||
|
|
|
|||
32
fly/docker/openclaw.Dockerfile
Normal file
32
fly/docker/openclaw.Dockerfile
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Base packages (matches waitForCloudInit in fly.ts)
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl git ca-certificates build-essential unzip xz-utils zsh && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Node.js 22 via apt + n (matches fly.ts cloud-init strategy)
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y --no-install-recommends nodejs npm && \
|
||||
npm install -g n && n 22 && \
|
||||
ln -sf /usr/local/bin/node /usr/bin/node && \
|
||||
ln -sf /usr/local/bin/npm /usr/bin/npm && \
|
||||
ln -sf /usr/local/bin/npx /usr/bin/npx && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Bun
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
ENV PATH="/root/.bun/bin:/root/.local/bin:${PATH}"
|
||||
|
||||
# OpenClaw via bun (matches agents.ts install strategy)
|
||||
RUN bun install -g openclaw
|
||||
# Ensure tools are on PATH for all shells
|
||||
RUN for rc in /root/.bashrc /root/.zshrc; do \
|
||||
grep -q '.bun/bin' "$rc" 2>/dev/null || \
|
||||
echo 'export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"' >> "$rc"; \
|
||||
done
|
||||
|
||||
CMD ["/bin/sleep", "inf"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue