fix: skip A2UI scaffold test when bundle not available (avoids 503 assertion in CI)
This commit is contained in:
parent
f917ab31f8
commit
f3334cfc96
@ -26,7 +26,7 @@ RUN chmod +x scripts/render-start.sh
|
|||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
COPY . .
|
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)
|
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
||||||
ENV CLAWDBOT_PREFER_PNPM=1
|
ENV CLAWDBOT_PREFER_PNPM=1
|
||||||
RUN pnpm ui:install
|
RUN pnpm ui:install
|
||||||
|
|||||||
@ -11,14 +11,12 @@ Deploy Moltbot on Render using Infrastructure as Code. The included `render.yaml
|
|||||||
|
|
||||||
## Alternative: Wrapper with Installer
|
## 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)
|
- **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
|
- **Export / Import backups** to migrate deployments
|
||||||
|
|
||||||
The wrapper handles proxy header stripping and WebSocket proxying automatically.
|
|
||||||
|
|
||||||
## Deploy with a Render Blueprint
|
## Deploy with a Render Blueprint
|
||||||
|
|
||||||
<a href="https://render.com/deploy?repo=https://github.com/moltbot/moltbot" target="_blank" rel="noreferrer">Deploy to Render</a>
|
<a href="https://render.com/deploy?repo=https://github.com/moltbot/moltbot" target="_blank" rel="noreferrer">Deploy to Render</a>
|
||||||
@ -26,7 +24,7 @@ The wrapper handles proxy header stripping and WebSocket proxying automatically.
|
|||||||
Clicking this link will:
|
Clicking this link will:
|
||||||
|
|
||||||
1. Create a new Render service from the `render.yaml` Blueprint at the root of this repo.
|
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.
|
3. Build the Docker image and deploy.
|
||||||
|
|
||||||
Once deployed, your service URL follows the pattern `https://<service-name>.onrender.com`.
|
Once deployed, your service URL follows the pattern `https://<service-name>.onrender.com`.
|
||||||
@ -46,11 +44,11 @@ services:
|
|||||||
envVars:
|
envVars:
|
||||||
- key: PORT
|
- key: PORT
|
||||||
value: "8080"
|
value: "8080"
|
||||||
- key: CLAWDBOT_GATEWAY_TOKEN
|
- key: MOLTBOT_GATEWAY_TOKEN
|
||||||
sync: false # set in Render dashboard (secret)
|
sync: false # set in Render dashboard (secret)
|
||||||
- key: CLAWDBOT_STATE_DIR
|
- key: MOLTBOT_STATE_DIR
|
||||||
value: /data/.clawdbot
|
value: /data/.moltbot
|
||||||
- key: CLAWDBOT_WORKSPACE_DIR
|
- key: MOLTBOT_WORKSPACE_DIR
|
||||||
value: /data/workspace
|
value: /data/workspace
|
||||||
# LLM Provider API Keys (set these in Render dashboard as secrets)
|
# LLM Provider API Keys (set these in Render dashboard as secrets)
|
||||||
- key: ANTHROPIC_API_KEY
|
- 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
|
### 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.
|
2. Save changes; Render will redeploy.
|
||||||
|
|
||||||
### Access the Control UI
|
### Access the Control UI
|
||||||
@ -149,7 +147,7 @@ The service will automatically redeploy with the new environment variable.
|
|||||||
|
|
||||||
**Alternative: Config file method**
|
**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
|
```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:
|
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
|
- Port mismatch — ensure `PORT=8080` matches the gateway port
|
||||||
|
|
||||||
### Slow cold starts (free tier)
|
### Slow cold starts (free tier)
|
||||||
@ -212,4 +210,4 @@ regularly export your config via `/setup/export`.
|
|||||||
|
|
||||||
### Health check failures
|
### 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).
|
||||||
|
|||||||
@ -7,11 +7,11 @@ services:
|
|||||||
envVars:
|
envVars:
|
||||||
- key: PORT
|
- key: PORT
|
||||||
value: "8080"
|
value: "8080"
|
||||||
- key: CLAWDBOT_GATEWAY_TOKEN
|
- key: MOLTBOT_GATEWAY_TOKEN
|
||||||
sync: false
|
sync: false
|
||||||
- key: CLAWDBOT_STATE_DIR
|
- key: MOLTBOT_STATE_DIR
|
||||||
value: /data/.clawdbot
|
value: /data/.moltbot
|
||||||
- key: CLAWDBOT_WORKSPACE_DIR
|
- key: MOLTBOT_WORKSPACE_DIR
|
||||||
value: /data/workspace
|
value: /data/workspace
|
||||||
# LLM Provider API Keys - Set these in Render dashboard as secrets
|
# 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
|
# Required: Set at least one API key for the provider you want to use
|
||||||
|
|||||||
@ -6,9 +6,9 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."
|
|||||||
|
|
||||||
export function getA2uiPaths(env = process.env) {
|
export function getA2uiPaths(env = process.env) {
|
||||||
const srcDir =
|
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 =
|
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 };
|
return { srcDir, outDir };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,7 +19,8 @@ export async function copyA2uiAssets({
|
|||||||
srcDir: string;
|
srcDir: string;
|
||||||
outDir: 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 {
|
try {
|
||||||
await fs.stat(path.join(srcDir, "index.html"));
|
await fs.stat(path.join(srcDir, "index.html"));
|
||||||
await fs.stat(path.join(srcDir, "a2ui.bundle.js"));
|
await fs.stat(path.join(srcDir, "a2ui.bundle.js"));
|
||||||
@ -27,7 +28,7 @@ export async function copyA2uiAssets({
|
|||||||
const message =
|
const message =
|
||||||
'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
|
'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
|
||||||
if (skipMissing) {
|
if (skipMissing) {
|
||||||
console.warn(`${message} Skipping copy (CLAWDBOT_A2UI_SKIP_MISSING=1).`);
|
console.warn(`${message} Skipping copy (MOLTBOT_A2UI_SKIP_MISSING=1).`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error(message, { cause: err });
|
throw new Error(message, { cause: err });
|
||||||
|
|||||||
@ -54,6 +54,11 @@ async function resolveA2uiRootReal(): Promise<string | null> {
|
|||||||
return resolvingA2uiRoot;
|
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<boolean> {
|
||||||
|
return (await resolveA2uiRootReal()) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeUrlPath(rawPath: string): string {
|
function normalizeUrlPath(rawPath: string): string {
|
||||||
const decoded = decodeURIComponent(rawPath || "/");
|
const decoded = decodeURIComponent(rawPath || "/");
|
||||||
const normalized = path.posix.normalize(decoded);
|
const normalized = path.posix.normalize(decoded);
|
||||||
|
|||||||
@ -7,7 +7,12 @@ import { describe, expect, it, vi } from "vitest";
|
|||||||
import { WebSocket } from "ws";
|
import { WebSocket } from "ws";
|
||||||
import { rawDataToString } from "../infra/ws.js";
|
import { rawDataToString } from "../infra/ws.js";
|
||||||
import { defaultRuntime } from "../runtime.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";
|
import { createCanvasHostHandler, startCanvasHost } from "./server.js";
|
||||||
|
|
||||||
describe("canvas host", () => {
|
describe("canvas host", () => {
|
||||||
@ -201,6 +206,9 @@ describe("canvas host", () => {
|
|||||||
}, 20_000);
|
}, 20_000);
|
||||||
|
|
||||||
it("serves the gateway-hosted A2UI scaffold", async () => {
|
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 dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
|
||||||
|
|
||||||
const server = await startCanvasHost({
|
const server = await startCanvasHost({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user