diff --git a/.github/workflows/fly-docker.yml b/.github/workflows/fly-docker.yml new file mode 100644 index 00000000..52c3c1e7 --- /dev/null +++ b/.github/workflows/fly-docker.yml @@ -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 diff --git a/cli/package.json b/cli/package.json index 887482b6..61e88101 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.5.33", + "version": "0.5.34", "type": "module", "bin": { "spawn": "cli.js" diff --git a/cli/src/fly/agents.ts b/cli/src/fly/agents.ts index 829fbdc0..dd706d9a 100644 --- a/cli/src/fly/agents.ts +++ b/cli/src/fly/agents.ts @@ -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 = { 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}`, diff --git a/cli/src/fly/fly.ts b/cli/src/fly/fly.ts index 656c5d13..b73a83d4 100644 --- a/cli/src/fly/fly.ts +++ b/cli/src/fly/fly.ts @@ -567,11 +567,12 @@ async function createMachine( cpus: number, vmMemory: number, volumeId?: string, + image?: string, ): Promise { const kindLabel = cpuKind === "performance" ? "dedicated" : "shared"; logStep(`Creating Fly.io machine (region: ${region}, ${cpus} ${kindLabel} vCPU, ${vmMemory}MB)...`); const config: Record = { - 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 { +export async function createServer(name: string, opts: ServerOptions, image?: string): Promise { const region = process.env.FLY_REGION || "iad"; if (!validateRegionName(region)) { @@ -698,7 +699,7 @@ export async function createServer(name: string, opts: ServerOptions): Promise/dev/null || \ + echo 'export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"' >> "$rc"; \ + done + +CMD ["/bin/sleep", "inf"]