mirror of https://github.com/openclaw/openclaw.git
Docker: add OPENCLAW_TZ timezone support (#34119)
* Docker: add OPENCLAW_TZ timezone support * fix: validate docker timezone names * fix: support Docker timezone override (#34119) (thanks @Lanfei) --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
parent
a3eed2b70f
commit
4d3a2f674b
|
|
@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus.
|
- Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus.
|
||||||
|
- Docker/timezone override: add `OPENCLAW_TZ` so `docker-setup.sh` can pin gateway and CLI containers to a chosen IANA timezone instead of inheriting the daemon default. (#34119) Thanks @Lanfei.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ services:
|
||||||
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:-}
|
||||||
|
TZ: ${OPENCLAW_TZ:-UTC}
|
||||||
volumes:
|
volumes:
|
||||||
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
||||||
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
||||||
|
|
@ -65,6 +66,7 @@ services:
|
||||||
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:-}
|
||||||
|
TZ: ${OPENCLAW_TZ:-UTC}
|
||||||
volumes:
|
volumes:
|
||||||
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
||||||
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ HOME_VOLUME_NAME="${OPENCLAW_HOME_VOLUME:-}"
|
||||||
RAW_SANDBOX_SETTING="${OPENCLAW_SANDBOX:-}"
|
RAW_SANDBOX_SETTING="${OPENCLAW_SANDBOX:-}"
|
||||||
SANDBOX_ENABLED=""
|
SANDBOX_ENABLED=""
|
||||||
DOCKER_SOCKET_PATH="${OPENCLAW_DOCKER_SOCKET:-}"
|
DOCKER_SOCKET_PATH="${OPENCLAW_DOCKER_SOCKET:-}"
|
||||||
|
TIMEZONE="${OPENCLAW_TZ:-}"
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
echo "ERROR: $*" >&2
|
echo "ERROR: $*" >&2
|
||||||
|
|
@ -135,6 +136,11 @@ contains_disallowed_chars() {
|
||||||
[[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]]
|
[[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_valid_timezone() {
|
||||||
|
local value="$1"
|
||||||
|
[[ -e "/usr/share/zoneinfo/$value" && ! -d "/usr/share/zoneinfo/$value" ]]
|
||||||
|
}
|
||||||
|
|
||||||
validate_mount_path_value() {
|
validate_mount_path_value() {
|
||||||
local label="$1"
|
local label="$1"
|
||||||
local value="$2"
|
local value="$2"
|
||||||
|
|
@ -202,6 +208,17 @@ fi
|
||||||
if [[ -n "$SANDBOX_ENABLED" ]]; then
|
if [[ -n "$SANDBOX_ENABLED" ]]; then
|
||||||
validate_mount_path_value "OPENCLAW_DOCKER_SOCKET" "$DOCKER_SOCKET_PATH"
|
validate_mount_path_value "OPENCLAW_DOCKER_SOCKET" "$DOCKER_SOCKET_PATH"
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "$TIMEZONE" ]]; then
|
||||||
|
if contains_disallowed_chars "$TIMEZONE"; then
|
||||||
|
fail "OPENCLAW_TZ contains unsupported control characters."
|
||||||
|
fi
|
||||||
|
if [[ ! "$TIMEZONE" =~ ^[A-Za-z0-9/_+\-]+$ ]]; then
|
||||||
|
fail "OPENCLAW_TZ must be a valid IANA timezone string (e.g. Asia/Shanghai)."
|
||||||
|
fi
|
||||||
|
if ! is_valid_timezone "$TIMEZONE"; then
|
||||||
|
fail "OPENCLAW_TZ must match a timezone in /usr/share/zoneinfo (e.g. Asia/Shanghai)."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -p "$OPENCLAW_CONFIG_DIR"
|
mkdir -p "$OPENCLAW_CONFIG_DIR"
|
||||||
mkdir -p "$OPENCLAW_WORKSPACE_DIR"
|
mkdir -p "$OPENCLAW_WORKSPACE_DIR"
|
||||||
|
|
@ -224,6 +241,7 @@ export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME"
|
||||||
export OPENCLAW_ALLOW_INSECURE_PRIVATE_WS="${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}"
|
export OPENCLAW_ALLOW_INSECURE_PRIVATE_WS="${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}"
|
||||||
export OPENCLAW_SANDBOX="$SANDBOX_ENABLED"
|
export OPENCLAW_SANDBOX="$SANDBOX_ENABLED"
|
||||||
export OPENCLAW_DOCKER_SOCKET="$DOCKER_SOCKET_PATH"
|
export OPENCLAW_DOCKER_SOCKET="$DOCKER_SOCKET_PATH"
|
||||||
|
export OPENCLAW_TZ="$TIMEZONE"
|
||||||
|
|
||||||
# Detect Docker socket GID for sandbox group_add.
|
# Detect Docker socket GID for sandbox group_add.
|
||||||
DOCKER_GID=""
|
DOCKER_GID=""
|
||||||
|
|
@ -408,7 +426,8 @@ upsert_env "$ENV_FILE" \
|
||||||
OPENCLAW_DOCKER_SOCKET \
|
OPENCLAW_DOCKER_SOCKET \
|
||||||
DOCKER_GID \
|
DOCKER_GID \
|
||||||
OPENCLAW_INSTALL_DOCKER_CLI \
|
OPENCLAW_INSTALL_DOCKER_CLI \
|
||||||
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS
|
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS \
|
||||||
|
OPENCLAW_TZ
|
||||||
|
|
||||||
if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then
|
if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then
|
||||||
echo "==> Building Docker image: $IMAGE_NAME"
|
echo "==> Building Docker image: $IMAGE_NAME"
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,18 @@ describe("docker-setup.sh", () => {
|
||||||
expect(identityDirStat.isDirectory()).toBe(true);
|
expect(identityDirStat.isDirectory()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("writes OPENCLAW_TZ into .env when given a real IANA timezone", async () => {
|
||||||
|
const activeSandbox = requireSandbox(sandbox);
|
||||||
|
|
||||||
|
const result = runDockerSetup(activeSandbox, {
|
||||||
|
OPENCLAW_TZ: "Asia/Shanghai",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
|
||||||
|
expect(envFile).toContain("OPENCLAW_TZ=Asia/Shanghai");
|
||||||
|
});
|
||||||
|
|
||||||
it("precreates agent data dirs to avoid EACCES in container", async () => {
|
it("precreates agent data dirs to avoid EACCES in container", async () => {
|
||||||
const activeSandbox = requireSandbox(sandbox);
|
const activeSandbox = requireSandbox(sandbox);
|
||||||
const configDir = join(activeSandbox.rootDir, "config-agent-dirs");
|
const configDir = join(activeSandbox.rootDir, "config-agent-dirs");
|
||||||
|
|
@ -411,6 +423,17 @@ describe("docker-setup.sh", () => {
|
||||||
expect(result.stderr).toContain("OPENCLAW_HOME_VOLUME must match");
|
expect(result.stderr).toContain("OPENCLAW_HOME_VOLUME must match");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects OPENCLAW_TZ values that are not present in zoneinfo", async () => {
|
||||||
|
const activeSandbox = requireSandbox(sandbox);
|
||||||
|
|
||||||
|
const result = runDockerSetup(activeSandbox, {
|
||||||
|
OPENCLAW_TZ: "Nope/Bad",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.status).not.toBe(0);
|
||||||
|
expect(result.stderr).toContain("OPENCLAW_TZ must match a timezone in /usr/share/zoneinfo");
|
||||||
|
});
|
||||||
|
|
||||||
it("avoids associative arrays so the script remains Bash 3.2-compatible", async () => {
|
it("avoids associative arrays so the script remains Bash 3.2-compatible", async () => {
|
||||||
const script = await readFile(join(repoRoot, "docker-setup.sh"), "utf8");
|
const script = await readFile(join(repoRoot, "docker-setup.sh"), "utf8");
|
||||||
expect(script).not.toMatch(/^\s*declare -A\b/m);
|
expect(script).not.toMatch(/^\s*declare -A\b/m);
|
||||||
|
|
@ -455,4 +478,9 @@ describe("docker-setup.sh", () => {
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps docker-compose timezone env defaults aligned across services", async () => {
|
||||||
|
const compose = await readFile(join(repoRoot, "docker-compose.yml"), "utf8");
|
||||||
|
expect(compose.match(/TZ: \$\{OPENCLAW_TZ:-UTC\}/g)).toHaveLength(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue