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:
Jealous 2026-03-13 02:21:55 -07:00 committed by GitHub
parent a3eed2b70f
commit 4d3a2f674b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 1 deletions

View File

@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
### 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.
- 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

View File

@ -9,6 +9,7 @@ services:
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-}
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-}
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
TZ: ${OPENCLAW_TZ:-UTC}
volumes:
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
@ -65,6 +66,7 @@ services:
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-}
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-}
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
TZ: ${OPENCLAW_TZ:-UTC}
volumes:
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace

View File

@ -10,6 +10,7 @@ HOME_VOLUME_NAME="${OPENCLAW_HOME_VOLUME:-}"
RAW_SANDBOX_SETTING="${OPENCLAW_SANDBOX:-}"
SANDBOX_ENABLED=""
DOCKER_SOCKET_PATH="${OPENCLAW_DOCKER_SOCKET:-}"
TIMEZONE="${OPENCLAW_TZ:-}"
fail() {
echo "ERROR: $*" >&2
@ -135,6 +136,11 @@ contains_disallowed_chars() {
[[ "$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() {
local label="$1"
local value="$2"
@ -202,6 +208,17 @@ fi
if [[ -n "$SANDBOX_ENABLED" ]]; then
validate_mount_path_value "OPENCLAW_DOCKER_SOCKET" "$DOCKER_SOCKET_PATH"
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_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_SANDBOX="$SANDBOX_ENABLED"
export OPENCLAW_DOCKER_SOCKET="$DOCKER_SOCKET_PATH"
export OPENCLAW_TZ="$TIMEZONE"
# Detect Docker socket GID for sandbox group_add.
DOCKER_GID=""
@ -408,7 +426,8 @@ upsert_env "$ENV_FILE" \
OPENCLAW_DOCKER_SOCKET \
DOCKER_GID \
OPENCLAW_INSTALL_DOCKER_CLI \
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS \
OPENCLAW_TZ
if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then
echo "==> Building Docker image: $IMAGE_NAME"

View File

@ -205,6 +205,18 @@ describe("docker-setup.sh", () => {
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 () => {
const activeSandbox = requireSandbox(sandbox);
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");
});
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 () => {
const script = await readFile(join(repoRoot, "docker-setup.sh"), "utf8");
expect(script).not.toMatch(/^\s*declare -A\b/m);
@ -455,4 +478,9 @@ describe("docker-setup.sh", () => {
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);
});
});