diff --git a/Dockerfile b/Dockerfile index c9ee20717..4899f1b13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ RUN chmod +x scripts/render-start.sh RUN pnpm install --frozen-lockfile COPY . . -RUN CLAWDBOT_A2UI_SKIP_MISSING=1 pnpm build +RUN MOLTBOT_A2UI_SKIP_MISSING=1 pnpm build # Force pnpm for UI build (Bun may fail on ARM/Synology architectures) ENV CLAWDBOT_PREFER_PNPM=1 RUN pnpm ui:install diff --git a/docs/render.mdx b/docs/render.mdx index 8dfaed833..7806cadaa 100644 --- a/docs/render.mdx +++ b/docs/render.mdx @@ -11,14 +11,12 @@ Deploy Moltbot on Render using Infrastructure as Code. The included `render.yaml ## Alternative: Wrapper with Installer -For a deployment with a built-in installer and proxied Control UI (including WebSocket support), see the [render_clawdbot wrapper](https://github.com/ojusave/render_clawdbot). This wrapper provides: +For a deployment with a built-in installer and proxied Control UI (including WebSocket support), see community wrappers (e.g. in the ecosystem docs). Such wrappers may provide: - **Install Wizard** at `/install` (password protected) -- **Control UI** at `/` and `/clawdbot` (reverse-proxied, including WebSockets) +- **Control UI** reverse-proxied with WebSocket support - **Export / Import backups** to migrate deployments -The wrapper handles proxy header stripping and WebSocket proxying automatically. - ## Deploy with a Render Blueprint Deploy to Render @@ -26,7 +24,7 @@ The wrapper handles proxy header stripping and WebSocket proxying automatically. Clicking this link will: 1. Create a new Render service from the `render.yaml` Blueprint at the root of this repo. -2. Prompt you to set `CLAWDBOT_GATEWAY_TOKEN` (or set it in **Environment** after deploy). +2. Prompt you to set `MOLTBOT_GATEWAY_TOKEN` (or set it in **Environment** after deploy). 3. Build the Docker image and deploy. Once deployed, your service URL follows the pattern `https://.onrender.com`. @@ -46,11 +44,11 @@ services: envVars: - key: PORT value: "8080" - - key: CLAWDBOT_GATEWAY_TOKEN + - key: MOLTBOT_GATEWAY_TOKEN sync: false # set in Render dashboard (secret) - - key: CLAWDBOT_STATE_DIR - value: /data/.clawdbot - - key: CLAWDBOT_WORKSPACE_DIR + - key: MOLTBOT_STATE_DIR + value: /data/.moltbot + - key: MOLTBOT_WORKSPACE_DIR value: /data/workspace # LLM Provider API Keys (set these in Render dashboard as secrets) - key: ANTHROPIC_API_KEY @@ -94,7 +92,7 @@ The Blueprint defaults to `starter`. To use free tier, change `plan: free` in yo ### Set the gateway token -1. In Render **Dashboard → your service → Environment**, set `CLAWDBOT_GATEWAY_TOKEN` to a long random secret (or generate one with `openssl rand -hex 32`). +1. In Render **Dashboard → your service → Environment**, set `MOLTBOT_GATEWAY_TOKEN` to a long random secret (or generate one with `openssl rand -hex 32`). 2. Save changes; Render will redeploy. ### Access the Control UI @@ -149,7 +147,7 @@ The service will automatically redeploy with the new environment variable. **Alternative: Config file method** -You can also configure API keys in the `moltbot.json` config file (under `CLAWDBOT_STATE_DIR` or `~/.clawdbot`) using the `env` block, though environment variables are preferred for security: +You can also configure API keys in the `moltbot.json` config file (under `MOLTBOT_STATE_DIR` or `~/.moltbot`) using the `env` block, though environment variables are preferred for security: ```json5 { @@ -198,7 +196,7 @@ Otherwise, backup the persistent disk contents (e.g. under `/data/.moltbot`) via Check the deploy logs in the Render Dashboard. Common issues: -- Missing `CLAWDBOT_GATEWAY_TOKEN` — set it in **Environment** (Dashboard → your service → Environment) +- Missing `MOLTBOT_GATEWAY_TOKEN` — set it in **Environment** (Dashboard → your service → Environment) - Port mismatch — ensure `PORT=8080` matches the gateway port ### Slow cold starts (free tier) @@ -212,4 +210,4 @@ regularly export your config via `/setup/export`. ### Health check failures -If Render is configured with `healthCheckPath: /health`, it expects a 200 from `/health` within 30 seconds. This blueprint does not set a health check by default. If deploys fail, check deploy logs and that `scripts/render-start.sh` runs correctly (config written under `CLAWDBOT_STATE_DIR` or `~/.moltbot`/`~/.clawdbot`, then gateway started with the token). +If Render is configured with `healthCheckPath: /health`, it expects a 200 from `/health` within 30 seconds. This blueprint does not set a health check by default. If deploys fail, check deploy logs and that `scripts/render-start.sh` runs correctly (config written under `MOLTBOT_STATE_DIR` or `~/.moltbot`, then gateway started with the token). diff --git a/render.yaml b/render.yaml index dd0ff11f6..1805c6195 100644 --- a/render.yaml +++ b/render.yaml @@ -7,11 +7,11 @@ services: envVars: - key: PORT value: "8080" - - key: CLAWDBOT_GATEWAY_TOKEN + - key: MOLTBOT_GATEWAY_TOKEN sync: false - - key: CLAWDBOT_STATE_DIR - value: /data/.clawdbot - - key: CLAWDBOT_WORKSPACE_DIR + - key: MOLTBOT_STATE_DIR + value: /data/.moltbot + - key: MOLTBOT_WORKSPACE_DIR value: /data/workspace # LLM Provider API Keys - Set these in Render dashboard as secrets # Required: Set at least one API key for the provider you want to use diff --git a/scripts/canvas-a2ui-copy.ts b/scripts/canvas-a2ui-copy.ts index e95be5fdd..ca930ef06 100644 --- a/scripts/canvas-a2ui-copy.ts +++ b/scripts/canvas-a2ui-copy.ts @@ -6,9 +6,9 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".." export function getA2uiPaths(env = process.env) { const srcDir = - env.CLAWDBOT_A2UI_SRC_DIR ?? path.join(repoRoot, "src", "canvas-host", "a2ui"); + env.MOLTBOT_A2UI_SRC_DIR ?? env.CLAWDBOT_A2UI_SRC_DIR ?? path.join(repoRoot, "src", "canvas-host", "a2ui"); const outDir = - env.CLAWDBOT_A2UI_OUT_DIR ?? path.join(repoRoot, "dist", "canvas-host", "a2ui"); + env.MOLTBOT_A2UI_OUT_DIR ?? env.CLAWDBOT_A2UI_OUT_DIR ?? path.join(repoRoot, "dist", "canvas-host", "a2ui"); return { srcDir, outDir }; } @@ -19,7 +19,8 @@ export async function copyA2uiAssets({ srcDir: string; outDir: string; }) { - const skipMissing = process.env.CLAWDBOT_A2UI_SKIP_MISSING === "1"; + const skipMissing = + process.env.MOLTBOT_A2UI_SKIP_MISSING === "1" || process.env.CLAWDBOT_A2UI_SKIP_MISSING === "1"; try { await fs.stat(path.join(srcDir, "index.html")); await fs.stat(path.join(srcDir, "a2ui.bundle.js")); @@ -27,7 +28,7 @@ export async function copyA2uiAssets({ const message = 'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.'; if (skipMissing) { - console.warn(`${message} Skipping copy (CLAWDBOT_A2UI_SKIP_MISSING=1).`); + console.warn(`${message} Skipping copy (MOLTBOT_A2UI_SKIP_MISSING=1).`); return; } throw new Error(message, { cause: err }); diff --git a/src/canvas-host/a2ui.ts b/src/canvas-host/a2ui.ts index 2a19d03dc..a8052e733 100644 --- a/src/canvas-host/a2ui.ts +++ b/src/canvas-host/a2ui.ts @@ -54,6 +54,11 @@ async function resolveA2uiRootReal(): Promise { return resolvingA2uiRoot; } +/** Returns true if A2UI assets (index.html + a2ui.bundle.js) are available. Use in tests to skip when bundle not built. */ +export async function isA2uiAvailable(): Promise { + return (await resolveA2uiRootReal()) !== null; +} + function normalizeUrlPath(rawPath: string): string { const decoded = decodeURIComponent(rawPath || "/"); const normalized = path.posix.normalize(decoded); diff --git a/src/canvas-host/server.test.ts b/src/canvas-host/server.test.ts index e460b2630..f6a6cbd0c 100644 --- a/src/canvas-host/server.test.ts +++ b/src/canvas-host/server.test.ts @@ -7,7 +7,12 @@ import { describe, expect, it, vi } from "vitest"; import { WebSocket } from "ws"; import { rawDataToString } from "../infra/ws.js"; import { defaultRuntime } from "../runtime.js"; -import { CANVAS_HOST_PATH, CANVAS_WS_PATH, injectCanvasLiveReload } from "./a2ui.js"; +import { + CANVAS_HOST_PATH, + CANVAS_WS_PATH, + injectCanvasLiveReload, + isA2uiAvailable, +} from "./a2ui.js"; import { createCanvasHostHandler, startCanvasHost } from "./server.js"; describe("canvas host", () => { @@ -201,6 +206,9 @@ describe("canvas host", () => { }, 20_000); it("serves the gateway-hosted A2UI scaffold", async () => { + if (!(await isA2uiAvailable())) { + return; // Skip when A2UI bundle not built (e.g. CI before canvas:a2ui:bundle or path not found) + } const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-")); const server = await startCanvasHost({