mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 00:11:19 +00:00
fix(docker): allow spaces in setup mount paths
Allow ordinary spaces in Docker setup host persistence paths while preserving control-character and comma mount guards. Quote generated and base Compose volume scalars so OPENCLAW_CONFIG_DIR, OPENCLAW_WORKSPACE_DIR, and auth-profile secret paths can be parsed when host directories contain spaces.
This commit is contained in:
parent
2a48a2655b
commit
f02750d162
3 changed files with 64 additions and 23 deletions
|
|
@ -39,9 +39,9 @@ services:
|
|||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
|
||||
TZ: ${OPENCLAW_TZ:-UTC}
|
||||
volumes:
|
||||
- ${OPENCLAW_CONFIG_DIR:-${HOME:-/tmp}/.openclaw}:/home/node/.openclaw
|
||||
- ${OPENCLAW_WORKSPACE_DIR:-${HOME:-/tmp}/.openclaw/workspace}:/home/node/.openclaw/workspace
|
||||
- ${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-${HOME:-/tmp}/.openclaw-auth-profile-secrets}:/home/node/.config/openclaw
|
||||
- "${OPENCLAW_CONFIG_DIR:-${HOME:-/tmp}/.openclaw}:/home/node/.openclaw"
|
||||
- "${OPENCLAW_WORKSPACE_DIR:-${HOME:-/tmp}/.openclaw/workspace}:/home/node/.openclaw/workspace"
|
||||
- "${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-${HOME:-/tmp}/.openclaw-auth-profile-secrets}:/home/node/.config/openclaw"
|
||||
## Uncomment the lines below to enable sandbox isolation
|
||||
## (agents.defaults.sandbox). Requires Docker CLI in the image
|
||||
## (build with --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1) or use
|
||||
|
|
@ -118,9 +118,9 @@ services:
|
|||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
|
||||
TZ: ${OPENCLAW_TZ:-UTC}
|
||||
volumes:
|
||||
- ${OPENCLAW_CONFIG_DIR:-${HOME:-/tmp}/.openclaw}:/home/node/.openclaw
|
||||
- ${OPENCLAW_WORKSPACE_DIR:-${HOME:-/tmp}/.openclaw/workspace}:/home/node/.openclaw/workspace
|
||||
- ${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-${HOME:-/tmp}/.openclaw-auth-profile-secrets}:/home/node/.config/openclaw
|
||||
- "${OPENCLAW_CONFIG_DIR:-${HOME:-/tmp}/.openclaw}:/home/node/.openclaw"
|
||||
- "${OPENCLAW_WORKSPACE_DIR:-${HOME:-/tmp}/.openclaw/workspace}:/home/node/.openclaw/workspace"
|
||||
- "${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-${HOME:-/tmp}/.openclaw-auth-profile-secrets}:/home/node/.config/openclaw"
|
||||
stdin_open: true
|
||||
tty: true
|
||||
init: true
|
||||
|
|
|
|||
|
|
@ -218,9 +218,6 @@ validate_mount_path_value() {
|
|||
if contains_disallowed_chars "$value"; then
|
||||
fail "$label contains unsupported control characters."
|
||||
fi
|
||||
if [[ "$value" =~ [[:space:]] ]]; then
|
||||
fail "$label cannot contain whitespace."
|
||||
fi
|
||||
}
|
||||
|
||||
validate_named_volume() {
|
||||
|
|
@ -237,11 +234,18 @@ validate_mount_spec() {
|
|||
fi
|
||||
# Keep mount specs strict to avoid YAML structure injection.
|
||||
# Expected format: source:target[:options]
|
||||
if [[ ! "$mount" =~ ^[^[:space:],:]+:[^[:space:],:]+(:[^[:space:],:]+)?$ ]]; then
|
||||
fail "Invalid mount format '$mount'. Expected source:target[:options] without spaces."
|
||||
if [[ ! "$mount" =~ ^[^,:]+:[^,:]+(:[^,:]+)?$ ]]; then
|
||||
fail "Invalid mount format '$mount'. Expected source:target[:options] without commas or control characters."
|
||||
fi
|
||||
}
|
||||
|
||||
quote_yaml_string() {
|
||||
local value="$1"
|
||||
value="${value//\\/\\\\}"
|
||||
value="${value//\"/\\\"}"
|
||||
printf '"%s"' "$value"
|
||||
}
|
||||
|
||||
require_cmd docker
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
echo "Docker Compose not available (try: docker compose version)" >&2
|
||||
|
|
@ -388,15 +392,15 @@ YAML
|
|||
validate_mount_spec "$gateway_config_mount"
|
||||
validate_mount_spec "$gateway_workspace_mount"
|
||||
validate_mount_spec "$gateway_auth_profile_secret_mount"
|
||||
printf ' - %s\n' "$gateway_home_mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$gateway_config_mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$gateway_workspace_mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$gateway_auth_profile_secret_mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$gateway_home_mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$gateway_config_mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$gateway_workspace_mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$gateway_auth_profile_secret_mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
fi
|
||||
|
||||
for mount in "$@"; do
|
||||
validate_mount_spec "$mount"
|
||||
printf ' - %s\n' "$mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
done
|
||||
|
||||
cat >>"$EXTRA_COMPOSE_FILE" <<'YAML'
|
||||
|
|
@ -405,15 +409,15 @@ YAML
|
|||
YAML
|
||||
|
||||
if [[ -n "$home_volume" ]]; then
|
||||
printf ' - %s\n' "$gateway_home_mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$gateway_config_mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$gateway_workspace_mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$gateway_auth_profile_secret_mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$gateway_home_mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$gateway_config_mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$gateway_workspace_mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$gateway_auth_profile_secret_mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
fi
|
||||
|
||||
for mount in "$@"; do
|
||||
validate_mount_spec "$mount"
|
||||
printf ' - %s\n' "$mount" >>"$EXTRA_COMPOSE_FILE"
|
||||
printf ' - %s\n' "$(quote_yaml_string "$mount")" >>"$EXTRA_COMPOSE_FILE"
|
||||
done
|
||||
|
||||
if [[ -n "$home_volume" && "$home_volume" != *"/"* ]]; then
|
||||
|
|
@ -658,7 +662,7 @@ if [[ -n "$SANDBOX_ENABLED" ]]; then
|
|||
services:
|
||||
openclaw-gateway:
|
||||
volumes:
|
||||
- ${DOCKER_SOCKET_PATH}:/var/run/docker.sock
|
||||
- $(quote_yaml_string "${DOCKER_SOCKET_PATH}:/var/run/docker.sock")
|
||||
YAML
|
||||
if [[ -n "${DOCKER_GID:-}" ]]; then
|
||||
cat >>"$SANDBOX_COMPOSE_FILE" <<YAML
|
||||
|
|
|
|||
|
|
@ -303,6 +303,43 @@ describe("scripts/docker/setup.sh", () => {
|
|||
expect(log).not.toContain("run --rm openclaw-cli onboard --mode local --no-install-daemon");
|
||||
});
|
||||
|
||||
it("allows ordinary spaces in host persistence paths and quotes generated mounts", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
await resetDockerLog(activeSandbox);
|
||||
const configDir = join(activeSandbox.rootDir, "config with spaces");
|
||||
const workspaceDir = join(activeSandbox.rootDir, "workspace with spaces");
|
||||
const authProfileSecretDir = join(activeSandbox.rootDir, "auth secrets with spaces");
|
||||
const homeVolumeDir = join(activeSandbox.rootDir, "home volume with spaces");
|
||||
const extraMountSource = join(activeSandbox.rootDir, "extra data");
|
||||
|
||||
const result = runDockerSetup(activeSandbox, {
|
||||
OPENCLAW_CONFIG_DIR: configDir,
|
||||
OPENCLAW_WORKSPACE_DIR: workspaceDir,
|
||||
OPENCLAW_AUTH_PROFILE_SECRET_DIR: authProfileSecretDir,
|
||||
OPENCLAW_HOME_VOLUME: homeVolumeDir,
|
||||
OPENCLAW_EXTRA_MOUNTS: `${extraMountSource}:/mnt/extra data:ro`,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).not.toContain("cannot contain whitespace");
|
||||
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
|
||||
expect(envFile).toContain(`OPENCLAW_CONFIG_DIR=${configDir}`);
|
||||
expect(envFile).toContain(`OPENCLAW_WORKSPACE_DIR=${workspaceDir}`);
|
||||
expect(envFile).toContain(`OPENCLAW_AUTH_PROFILE_SECRET_DIR=${authProfileSecretDir}`);
|
||||
|
||||
const extraCompose = await readFile(
|
||||
join(activeSandbox.rootDir, "docker-compose.extra.yml"),
|
||||
"utf8",
|
||||
);
|
||||
expect(extraCompose).toContain(`"${homeVolumeDir}:/home/node"`);
|
||||
expect(extraCompose).toContain(`"${configDir}:/home/node/.openclaw"`);
|
||||
expect(extraCompose).toContain(`"${workspaceDir}:/home/node/.openclaw/workspace"`);
|
||||
expect(extraCompose).toContain(
|
||||
`"${authProfileSecretDir}:/home/node/.config/openclaw"`,
|
||||
);
|
||||
expect(extraCompose).toContain(`"${extraMountSource}:/mnt/extra data:ro"`);
|
||||
});
|
||||
|
||||
it("persists explicit Docker Bonjour opt-in overrides", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
|
||||
|
|
@ -781,7 +818,7 @@ describe("scripts/docker/setup.sh", () => {
|
|||
const compose = await readFile(join(repoRoot, "docker-compose.yml"), "utf8");
|
||||
expect(
|
||||
compose.split(
|
||||
"${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-${HOME:-/tmp}/.openclaw-auth-profile-secrets}:/home/node/.config/openclaw",
|
||||
'"${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-${HOME:-/tmp}/.openclaw-auth-profile-secrets}:/home/node/.config/openclaw"',
|
||||
),
|
||||
).toHaveLength(3);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue