Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e1cb8857d | ||
|
|
63bbe5c2ab |
@@ -65,14 +65,16 @@ if ! $TEST_MODE; then
|
|||||||
start_sec=$((sunrise_sec - SUNRISE_PRE_MIN * 60))
|
start_sec=$((sunrise_sec - SUNRISE_PRE_MIN * 60))
|
||||||
end_sec=$((sunrise_sec + SUNRISE_POST_MIN * 60))
|
end_sec=$((sunrise_sec + SUNRISE_POST_MIN * 60))
|
||||||
echo "Window: $((start_sec/3600)):$(printf '%02d' $(((start_sec%3600)/60))) → $((end_sec/3600)):$(printf '%02d' $(((end_sec%3600)/60)))"
|
echo "Window: $((start_sec/3600)):$(printf '%02d' $(((start_sec%3600)/60))) → $((end_sec/3600)):$(printf '%02d' $(((end_sec%3600)/60)))"
|
||||||
midnight=$(date -d "today 00:00:00" +%s)
|
# Resolve wall-clock targets through `date -d` instead of adding seconds-since-
|
||||||
now_day_sec=$(( $(date +%s) - midnight ))
|
# midnight to a midnight epoch: on a DST day (23h/25h) the two differ by 3600s.
|
||||||
if [ "$now_day_sec" -lt "$end_sec" ]; then
|
end_epoch=$(date -d "today $(printf '%02d:%02d:%02d' $((end_sec/3600)) $(((end_sec%3600)/60)) $((end_sec%60)))" +%s)
|
||||||
wait_sec=$(( end_sec - now_day_sec + 30 ))
|
now_epoch=$(date +%s)
|
||||||
echo "Waiting ${wait_sec}s for capture window to finish (ends $(date -d "@$(( midnight + end_sec ))" '+%H:%M:%S'))..."
|
if [ "$now_epoch" -lt "$end_epoch" ]; then
|
||||||
|
wait_sec=$(( end_epoch - now_epoch + 30 ))
|
||||||
|
echo "Waiting ${wait_sec}s for capture window to finish (ends $(date -d "@$end_epoch" '+%H:%M:%S'))..."
|
||||||
sleep "$wait_sec"
|
sleep "$wait_sec"
|
||||||
else
|
else
|
||||||
echo "Capture window already closed at $(date -d "@$(( midnight + end_sec ))" '+%H:%M:%S') — processing available images"
|
echo "Capture window already closed at $(date -d "@$end_epoch" '+%H:%M:%S') — processing available images"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+19
@@ -296,6 +296,25 @@ for cam in "${CAMERAS[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# ── Timezone drift check ─────────────────────────────────────────────────────
|
||||||
|
# Daily + shortly after boot: notify if TIMEZONE no longer matches the
|
||||||
|
# machine's timezone (systemd timers follow the machine, scripts follow TIMEZONE).
|
||||||
|
write_service "sky-cam-tzcheck" "timezone drift check" "tz-drift-check.sh"
|
||||||
|
cat > "$UNIT_DIR/sky-cam-tzcheck.timer" <<EOF2
|
||||||
|
[Unit]
|
||||||
|
Description=Sky-Cam — timezone drift check
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=3min
|
||||||
|
OnCalendar=*-*-* 12:00:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF2
|
||||||
|
echo " wrote sky-cam-tzcheck.timer"
|
||||||
|
timers+=("sky-cam-tzcheck")
|
||||||
|
|
||||||
# ── Generate .env.example ────────────────────────────────────────────────────
|
# ── Generate .env.example ────────────────────────────────────────────────────
|
||||||
env_example="$HERE/.env.example"
|
env_example="$HERE/.env.example"
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,12 +54,13 @@ record_dur_sec=$SUNRISE_TARGET_SECS
|
|||||||
|
|
||||||
echo "Audio window: ${half_pre}s before → ${half_post}s after sunrise (${record_dur_sec}s total)"
|
echo "Audio window: ${half_pre}s before → ${half_post}s after sunrise (${record_dur_sec}s total)"
|
||||||
|
|
||||||
midnight=$(date -d "today 00:00:00" +%s)
|
# Resolve the wall-clock start via `date -d` (not midnight epoch + seconds):
|
||||||
now_day_sec=$(( $(date +%s) - midnight ))
|
# on a DST day (23h/25h) those differ by 3600s.
|
||||||
wait_sec=$(( record_start_sec - now_day_sec ))
|
start_epoch=$(date -d "today $(printf '%02d:%02d:%02d' $((record_start_sec/3600)) $(((record_start_sec%3600)/60)) $((record_start_sec%60)))" +%s)
|
||||||
|
wait_sec=$(( start_epoch - $(date +%s) ))
|
||||||
|
|
||||||
if [ "$wait_sec" -gt 0 ]; then
|
if [ "$wait_sec" -gt 0 ]; then
|
||||||
echo "Waiting ${wait_sec}s until audio window ($(date -d "@$(( midnight + record_start_sec ))" '+%H:%M:%S'))..."
|
echo "Waiting ${wait_sec}s until audio window ($(date -d "@$start_epoch" '+%H:%M:%S'))..."
|
||||||
sleep "$wait_sec"
|
sleep "$wait_sec"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -58,7 +58,9 @@ tz = pytz.timezone(conf['TIMEZONE'])
|
|||||||
# This bracket is timezone-correct and handles any UTC offset, including large
|
# This bracket is timezone-correct and handles any UTC offset, including large
|
||||||
# positive offsets (UTC+12/+14) where local midnight falls on the previous UTC day.
|
# positive offsets (UTC+12/+14) where local midnight falls on the previous UTC day.
|
||||||
now_local = datetime.now(tz)
|
now_local = datetime.now(tz)
|
||||||
midnight_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0)
|
# tz.localize (not .replace) so the UTC offset is re-resolved for midnight —
|
||||||
|
# on a DST-change day now_local's offset differs from midnight's.
|
||||||
|
midnight_local = tz.localize(datetime(now_local.year, now_local.month, now_local.day))
|
||||||
t0_utc = midnight_local.astimezone(timezone.utc)
|
t0_utc = midnight_local.astimezone(timezone.utc)
|
||||||
t1_utc = t0_utc + timedelta(hours=25) # 25 h covers any timezone's full day
|
t1_utc = t0_utc + timedelta(hours=25) # 25 h covers any timezone's full day
|
||||||
|
|
||||||
|
|||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# tz-drift-check.sh — warn if TIMEZONE (sky-cam.conf / .env) no longer matches
|
||||||
|
# the machine's own timezone.
|
||||||
|
#
|
||||||
|
# systemd timers fire on the machine's clock, but sunrise.py, the capture
|
||||||
|
# scripts and the per-day folder names use TIMEZONE. If the two drift apart
|
||||||
|
# (someone ran `timedatectl set-timezone`, a cloud-init reset, a restored
|
||||||
|
# image, a typo in .env...) every scheduled job runs at the wrong local time
|
||||||
|
# and the sunrise capture window is off by the zone difference.
|
||||||
|
#
|
||||||
|
# Run daily by sky-cam-tzcheck.timer (and shortly after boot). Sends one
|
||||||
|
# notify.sh message per run while the mismatch persists; silent when fine.
|
||||||
|
# Exit 0 either way — a mismatch is a notification, not a job failure.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
|
||||||
|
source "$SCRIPT_DIR/sky-cam.conf"
|
||||||
|
source "$SCRIPT_DIR/.env" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Machine's zone — read WITHOUT any TZ override so we see the real system value.
|
||||||
|
sys_tz="$(env -u TZ timedatectl show -p Timezone --value 2>/dev/null)"
|
||||||
|
[ -z "$sys_tz" ] && sys_tz="$(cat /etc/timezone 2>/dev/null)"
|
||||||
|
[ -z "$sys_tz" ] && sys_tz="$(readlink /etc/localtime 2>/dev/null | sed 's|.*/zoneinfo/||')"
|
||||||
|
if [ -z "$sys_tz" ]; then
|
||||||
|
echo "tz-drift-check: could not determine the machine's timezone — skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Compare by real UTC offsets across the year so aliases (US/Eastern vs
|
||||||
|
# America/New_York) and identical-rule zones are not reported as drift.
|
||||||
|
same=true
|
||||||
|
if [ "$sys_tz" != "$TIMEZONE" ]; then
|
||||||
|
for m in 01 04 07 10; do
|
||||||
|
a="$(TZ="$sys_tz" date -d "2026-$m-15 12:00 UTC" +%z)"
|
||||||
|
b="$(TZ="$TIMEZONE" date -d "2026-$m-15 12:00 UTC" +%z)"
|
||||||
|
[ "$a" = "$b" ] || { same=false; break; }
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $same; then
|
||||||
|
echo "Timezone OK: sky-cam TIMEZONE=$TIMEZONE, machine=$sys_tz"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg="sky-cam TIMEZONE is $TIMEZONE but this machine's timezone is $sys_tz. Scheduled jobs run on the machine clock, so sunrise/seasons/moon jobs will fire at the wrong local time. Fix: set TIMEZONE=$sys_tz in $SCRIPT_DIR/.env and re-run ./install.sh, or run: sudo timedatectl set-timezone $TIMEZONE"
|
||||||
|
echo "WARNING: $msg"
|
||||||
|
"$SCRIPT_DIR/notify.sh" "WARNING: sky-cam timezone mismatch" "$msg" || true
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user