fix(docker): atomized volume setup and migration robustness
- src/infra/state-migrations.ts: Handle EBUSY errors on rename (Docker bind mount safe) - Dockerfile: Add global CLI symlink and fix volume permissions - docker-compose.yml: Use named volumes for isolation and explicit entrypoints
This commit is contained in:
parent
4583f88626
commit
c7bfcadf02
@ -32,6 +32,13 @@ RUN pnpm ui:build
|
|||||||
|
|
||||||
ENV NODE_ENV=production
|
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
|
# Security hardening: Run as non-root user
|
||||||
# The node:22-bookworm image includes a 'node' user (uid 1000)
|
# The node:22-bookworm image includes a 'node' user (uid 1000)
|
||||||
# This reduces the attack surface by preventing container escape via root privileges
|
# This reduces the attack surface by preventing container escape via root privileges
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
services:
|
services:
|
||||||
moltbot-gateway:
|
moltbot-gateway:
|
||||||
|
container_name: moltbot
|
||||||
image: ${CLAWDBOT_IMAGE:-moltbot:local}
|
image: ${CLAWDBOT_IMAGE:-moltbot:local}
|
||||||
environment:
|
environment:
|
||||||
HOME: /home/node
|
HOME: /home/node
|
||||||
|
MOLTBOT_STATE_DIR: /var/lib/moltbot
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
CLAWDBOT_GATEWAY_TOKEN: ${CLAWDBOT_GATEWAY_TOKEN}
|
CLAWDBOT_GATEWAY_TOKEN: ${CLAWDBOT_GATEWAY_TOKEN}
|
||||||
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
||||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
||||||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
|
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
|
||||||
volumes:
|
volumes:
|
||||||
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
|
- moltbot_data:/var/lib/moltbot
|
||||||
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
|
- moltbot_workspace:/home/node/clawd
|
||||||
ports:
|
ports:
|
||||||
- "${CLAWDBOT_GATEWAY_PORT:-18789}:18789"
|
- "${CLAWDBOT_GATEWAY_PORT:-18789}:18789"
|
||||||
- "${CLAWDBOT_BRIDGE_PORT:-18790}:18790"
|
- "${CLAWDBOT_BRIDGE_PORT:-18790}:18790"
|
||||||
@ -19,27 +21,15 @@ services:
|
|||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"node",
|
"node",
|
||||||
"dist/index.js",
|
"/app/moltbot.mjs",
|
||||||
"gateway",
|
"gateway",
|
||||||
"--bind",
|
"--bind",
|
||||||
"${CLAWDBOT_GATEWAY_BIND:-lan}",
|
"${CLAWDBOT_GATEWAY_BIND:-lan}",
|
||||||
"--port",
|
"--port",
|
||||||
"${CLAWDBOT_GATEWAY_PORT:-18789}"
|
"${CLAWDBOT_GATEWAY_PORT:-18789}",
|
||||||
|
"--allow-unconfigured"
|
||||||
]
|
]
|
||||||
|
|
||||||
moltbot-cli:
|
volumes:
|
||||||
image: ${CLAWDBOT_IMAGE:-moltbot:local}
|
moltbot_data:
|
||||||
environment:
|
moltbot_workspace:
|
||||||
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"]
|
|
||||||
|
|||||||
@ -21,11 +21,11 @@ if ! docker compose version >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "${CLAWDBOT_CONFIG_DIR:-$HOME/.clawdbot}"
|
mkdir -p "${CLAWDBOT_CONFIG_DIR:-$HOME/.moltbot}"
|
||||||
mkdir -p "${CLAWDBOT_WORKSPACE_DIR:-$HOME/clawd}"
|
mkdir -p "${CLAWDBOT_WORKSPACE_DIR:-$HOME/moltbot}"
|
||||||
|
|
||||||
export CLAWDBOT_CONFIG_DIR="${CLAWDBOT_CONFIG_DIR:-$HOME/.clawdbot}"
|
export CLAWDBOT_CONFIG_DIR="${CLAWDBOT_CONFIG_DIR:-$HOME/.moltbot}"
|
||||||
export CLAWDBOT_WORKSPACE_DIR="${CLAWDBOT_WORKSPACE_DIR:-$HOME/clawd}"
|
export CLAWDBOT_WORKSPACE_DIR="${CLAWDBOT_WORKSPACE_DIR:-$HOME/moltbot}"
|
||||||
export CLAWDBOT_GATEWAY_PORT="${CLAWDBOT_GATEWAY_PORT:-18789}"
|
export CLAWDBOT_GATEWAY_PORT="${CLAWDBOT_GATEWAY_PORT:-18789}"
|
||||||
export CLAWDBOT_BRIDGE_PORT="${CLAWDBOT_BRIDGE_PORT:-18790}"
|
export CLAWDBOT_BRIDGE_PORT="${CLAWDBOT_BRIDGE_PORT:-18790}"
|
||||||
export CLAWDBOT_GATEWAY_BIND="${CLAWDBOT_GATEWAY_BIND:-lan}"
|
export CLAWDBOT_GATEWAY_BIND="${CLAWDBOT_GATEWAY_BIND:-lan}"
|
||||||
@ -62,7 +62,7 @@ YAML
|
|||||||
|
|
||||||
if [[ -n "$home_volume" ]]; then
|
if [[ -n "$home_volume" ]]; then
|
||||||
printf ' - %s:/home/node\n' "$home_volume" >>"$EXTRA_COMPOSE_FILE"
|
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"
|
printf ' - %s:/home/node/clawd\n' "$CLAWDBOT_WORKSPACE_DIR" >>"$EXTRA_COMPOSE_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ YAML
|
|||||||
|
|
||||||
if [[ -n "$home_volume" ]]; then
|
if [[ -n "$home_volume" ]]; then
|
||||||
printf ' - %s:/home/node\n' "$home_volume" >>"$EXTRA_COMPOSE_FILE"
|
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"
|
printf ' - %s:/home/node/clawd\n' "$CLAWDBOT_WORKSPACE_DIR" >>"$EXTRA_COMPOSE_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -360,7 +360,13 @@ export async function autoMigrateLegacyStateDir(params: {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
fs.renameSync(legacyDir, targetDir);
|
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)}`);
|
warnings.push(`Failed to move legacy state dir (${legacyDir} → ${targetDir}): ${String(err)}`);
|
||||||
return { migrated: false, skipped: false, changes, warnings };
|
return { migrated: false, skipped: false, changes, warnings };
|
||||||
}
|
}
|
||||||
@ -430,11 +436,11 @@ export async function detectLegacyStateMigrations(params: {
|
|||||||
: { store: {}, ok: true };
|
: { store: {}, ok: true };
|
||||||
const legacyKeys = targetSessionParsed.ok
|
const legacyKeys = targetSessionParsed.ok
|
||||||
? listLegacySessionKeys({
|
? listLegacySessionKeys({
|
||||||
store: targetSessionParsed.store,
|
store: targetSessionParsed.store,
|
||||||
agentId: targetAgentId,
|
agentId: targetAgentId,
|
||||||
mainKey: targetMainKey,
|
mainKey: targetMainKey,
|
||||||
scope: targetScope,
|
scope: targetScope,
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const legacyAgentDir = path.join(stateDir, "agent");
|
const legacyAgentDir = path.join(stateDir, "agent");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user