diff --git a/README.md b/README.md index 4fb1e01..00f5522 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Configure via environment: | `QZ_HR_BELT` | `TRACKR HR 7DC2` | BLE name of your HR strap | | `TPV_GPU` | `auto` | `intel`, `nvidia`, `none` | | `STATE` | `~/.local/share/tpv-full` | Where the Wine prefix and game data live | +| `TZ` | host zone (`timedatectl`) | Time zone for TPV's HUD clock; set to override | `verify.sh` checks the five things that actually matter: the Bluetooth backend chosen, the trainer connection, the DIRCON listeners, whether the mDNS advert diff --git a/docs/adr/0003-pass-the-host-timezone-into-the-container.md b/docs/adr/0003-pass-the-host-timezone-into-the-container.md new file mode 100644 index 0000000..54a8104 --- /dev/null +++ b/docs/adr/0003-pass-the-host-timezone-into-the-container.md @@ -0,0 +1,53 @@ +# 0003. Pass the host timezone into the container and refresh the Wine prefix when it changes + +- **Status:** Accepted +- **Date:** 2026-09-17 +- **Deciders:** Ben Atkinson +- **Feature / area:** container-timezone +- **Builds on:** none — first ADR for container-timezone +- **Supersedes / Superseded by:** none + +## What problem were we trying to solve? + +TPV's HUD clock showed UTC instead of local time: an hour behind on a +`Europe/London` host during BST (#2). The container never received the host's +zone (`TZ` unset, `/etc/localtime` → `Etc/UTC`), and Wine derives the Windows +time zone from the Linux one. The fix had to follow the host automatically, +handle DST, still allow an explicit `TZ=` override, and work for existing users +whose Wine prefix was already created under UTC. + +## What did we try? + +### Option 1: pass `TZ` only ❌ considered, not enough on its own + +`TZ` alone relies on the image's zoneinfo and a zone name `timedatectl` can +report. Some hosts don't have `timedatectl`, so there has to be a fallback. + +### Option 2: bind-mount `/etc/localtime` only ❌ considered, not enough on its own + +This gives the right offset, including DST, from the host's own zone file, but +no zone *name* for anything that reads `TZ`, and no simple override. + +### Attempt: fix the environment and leave the prefix alone ❌ failed in practice + +Even with the container's clock correct, a prefix created under UTC kept an +empty `TimeZoneKeyName`. Wine only rewrites it when the prefix is updated, so +existing installs would still show UTC. + +## What did we land on, and why? + +- **`run-tpv.sh`:** passes `TZ` (an explicit `TZ` override, else `timedatectl`'s + zone) **and** bind-mounts `/etc/localtime` read-only. Together they cover + zone-name and zone-file consumers. +- **`scripts/entrypoint.sh`:** `sync_timezone` keeps a marker file in the + prefix (`TZ` plus a checksum of `/etc/localtime`). When the marker changes, it + runs `wineboot -u` once and waits for `wineserver`. Existing UTC-era prefixes + pick up the zone on the next start, without a `wineboot` on every launch. + +## What does this cost us? + +- **Slower start after a zone change:** the first start after a zone change (or + on an old prefix) runs a `wineboot -u` prefix update. +- **One more host file mounted** into the container: `/etc/localtime`, read-only. +- **Not yet verified here:** that the HUD shows local time including the + BST/GMT switch, and that `TZ=...` overrides the detected zone. diff --git a/run-tpv.sh b/run-tpv.sh index 21098d1..9e9af13 100755 --- a/run-tpv.sh +++ b/run-tpv.sh @@ -62,6 +62,12 @@ if [ ${#DISPLAY_ARGS[@]} -eq 0 ]; then exit 1 fi +TIMEZONE=() +HOST_TZ="${TZ:-$(timedatectl show -p Timezone --value 2>/dev/null || true)}" +[ -n "$HOST_TZ" ] && TIMEZONE+=(-e "TZ=${HOST_TZ}") +[ -e /etc/localtime ] && TIMEZONE+=(-v /etc/localtime:/etc/localtime:ro) +echo "==> timezone: ${HOST_TZ:-host /etc/localtime}" + INHIBIT=() if [ "$MODE" != qz ] && command -v gnome-session-inhibit >/dev/null; then INHIBIT=(gnome-session-inhibit --inhibit idle:suspend @@ -76,6 +82,7 @@ exec "${INHIBIT[@]}" podman run --rm --name tpv \ --network=host \ "${GPU[@]}" "${AUDIO[@]}" \ "${DISPLAY_ARGS[@]}" \ + "${TIMEZONE[@]}" \ -e "QZ_TRAINER=${TRAINER}" \ -e "QZ_HR_BELT=${HR_BELT}" \ -e "TPV_DIRECT=${TPV_DIRECT:-0}" \ diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 2aa8e53..7583d3a 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -68,9 +68,22 @@ wait_for_dircon() { echo " WARNING: DIRCON never came up; TPV will find no trainer." >&2 } +sync_timezone() { + # Wine rewrites TimeZoneKeyName only on a prefix update, so a UTC-era prefix needs wineboot -u. + local marker="$WINEPREFIX/.tpv-timezone" + local current + current="${TZ:-}:$(cksum < /etc/localtime 2>/dev/null || true)" + [ -f "$marker" ] && [ "$(cat "$marker")" = "$current" ] && return 0 + echo "==> timezone changed (${TZ:-/etc/localtime}); updating the Wine prefix" + wineboot -u >/dev/null 2>&1 || true + wineserver -w + printf '%s' "$current" > "$marker" +} + run_tpv() { seed_prefix ensure_tpv + sync_timezone # TPV refuses a direct launch of the game exe ("must be launched using the # TPVirtual-Launcher"), so the launcher is the only supported path. Do not # exec it: it hands off to the game and exits, which would take the