diff --git a/Dockerfile b/Dockerfile index 9c6aa7036..6eaf5170c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,13 @@ RUN pnpm ui:build ENV NODE_ENV=production +# Create global CLI symlink +RUN ln -s /app/moltbot.mjs /usr/local/bin/moltbot + +# Ensure state directories exist and are owned by node +RUN mkdir -p /var/lib/moltbot /home/node/clawd && \ + chown -R node:node /var/lib/moltbot /home/node/clawd + # Security hardening: Run as non-root user # The node:22-bookworm image includes a 'node' user (uid 1000) # This reduces the attack surface by preventing container escape via root privileges diff --git a/docker-compose.yml b/docker-compose.yml index 8ce610d6a..68412a5a2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,18 @@ services: moltbot-gateway: + container_name: moltbot image: ${CLAWDBOT_IMAGE:-moltbot:local} environment: HOME: /home/node + MOLTBOT_STATE_DIR: /var/lib/moltbot TERM: xterm-256color CLAWDBOT_GATEWAY_TOKEN: ${CLAWDBOT_GATEWAY_TOKEN} CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY} CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY} CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE} volumes: - - ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot - - ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd + - moltbot_data:/var/lib/moltbot + - moltbot_workspace:/home/node/clawd ports: - "${CLAWDBOT_GATEWAY_PORT:-18789}:18789" - "${CLAWDBOT_BRIDGE_PORT:-18790}:18790" @@ -19,27 +21,15 @@ services: command: [ "node", - "dist/index.js", + "/app/moltbot.mjs", "gateway", "--bind", "${CLAWDBOT_GATEWAY_BIND:-lan}", "--port", - "${CLAWDBOT_GATEWAY_PORT:-18789}" + "${CLAWDBOT_GATEWAY_PORT:-18789}", + "--allow-unconfigured" ] - moltbot-cli: - image: ${CLAWDBOT_IMAGE:-moltbot:local} - environment: - HOME: /home/node - TERM: xterm-256color - BROWSER: echo - CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY} - CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY} - CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE} - volumes: - - ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot - - ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd - stdin_open: true - tty: true - init: true - entrypoint: ["node", "dist/index.js"] +volumes: + moltbot_data: + moltbot_workspace: diff --git a/docker-setup.sh b/docker-setup.sh index 0f7571e96..56545aaa9 100755 --- a/docker-setup.sh +++ b/docker-setup.sh @@ -21,11 +21,11 @@ if ! docker compose version >/dev/null 2>&1; then exit 1 fi -mkdir -p "${CLAWDBOT_CONFIG_DIR:-$HOME/.clawdbot}" -mkdir -p "${CLAWDBOT_WORKSPACE_DIR:-$HOME/clawd}" +mkdir -p "${CLAWDBOT_CONFIG_DIR:-$HOME/.moltbot}" +mkdir -p "${CLAWDBOT_WORKSPACE_DIR:-$HOME/moltbot}" -export CLAWDBOT_CONFIG_DIR="${CLAWDBOT_CONFIG_DIR:-$HOME/.clawdbot}" -export CLAWDBOT_WORKSPACE_DIR="${CLAWDBOT_WORKSPACE_DIR:-$HOME/clawd}" +export CLAWDBOT_CONFIG_DIR="${CLAWDBOT_CONFIG_DIR:-$HOME/.moltbot}" +export CLAWDBOT_WORKSPACE_DIR="${CLAWDBOT_WORKSPACE_DIR:-$HOME/moltbot}" export CLAWDBOT_GATEWAY_PORT="${CLAWDBOT_GATEWAY_PORT:-18789}" export CLAWDBOT_BRIDGE_PORT="${CLAWDBOT_BRIDGE_PORT:-18790}" export CLAWDBOT_GATEWAY_BIND="${CLAWDBOT_GATEWAY_BIND:-lan}" @@ -62,7 +62,7 @@ YAML if [[ -n "$home_volume" ]]; then printf ' - %s:/home/node\n' "$home_volume" >>"$EXTRA_COMPOSE_FILE" - printf ' - %s:/home/node/.clawdbot\n' "$CLAWDBOT_CONFIG_DIR" >>"$EXTRA_COMPOSE_FILE" + printf ' - %s:/home/node/.moltbot\n' "$CLAWDBOT_CONFIG_DIR" >>"$EXTRA_COMPOSE_FILE" printf ' - %s:/home/node/clawd\n' "$CLAWDBOT_WORKSPACE_DIR" >>"$EXTRA_COMPOSE_FILE" fi @@ -77,7 +77,7 @@ YAML if [[ -n "$home_volume" ]]; then printf ' - %s:/home/node\n' "$home_volume" >>"$EXTRA_COMPOSE_FILE" - printf ' - %s:/home/node/.clawdbot\n' "$CLAWDBOT_CONFIG_DIR" >>"$EXTRA_COMPOSE_FILE" + printf ' - %s:/home/node/.moltbot\n' "$CLAWDBOT_CONFIG_DIR" >>"$EXTRA_COMPOSE_FILE" printf ' - %s:/home/node/clawd\n' "$CLAWDBOT_WORKSPACE_DIR" >>"$EXTRA_COMPOSE_FILE" fi diff --git a/src/infra/state-migrations.ts b/src/infra/state-migrations.ts index f5e50740e..cc0ed5721 100644 --- a/src/infra/state-migrations.ts +++ b/src/infra/state-migrations.ts @@ -360,7 +360,13 @@ export async function autoMigrateLegacyStateDir(params: { try { fs.renameSync(legacyDir, targetDir); - } catch (err) { + } catch (err: any) { + if (err.code === "EBUSY") { + warnings.push( + `Legacy state dir (${legacyDir}) could not be renamed (EBUSY). This is common in Docker with bind mounts. Skipping automatic migration.`, + ); + return { migrated: false, skipped: true, changes: [], warnings }; + } warnings.push(`Failed to move legacy state dir (${legacyDir} → ${targetDir}): ${String(err)}`); return { migrated: false, skipped: false, changes, warnings }; } @@ -430,11 +436,11 @@ export async function detectLegacyStateMigrations(params: { : { store: {}, ok: true }; const legacyKeys = targetSessionParsed.ok ? listLegacySessionKeys({ - store: targetSessionParsed.store, - agentId: targetAgentId, - mainKey: targetMainKey, - scope: targetScope, - }) + store: targetSessionParsed.store, + agentId: targetAgentId, + mainKey: targetMainKey, + scope: targetScope, + }) : []; const legacyAgentDir = path.join(stateDir, "agent");