Skip to content

Releases

TimeSync 0.1.7 — update check against GitHub releases

Choose a tag to compare

@vu2cpl vu2cpl released this 08 Oct 23:46

Update check. TimeSync now tells you when a newer release is out — by asking GitHub, with nothing downloaded or installed on its own. Also in this release: a server/chrony.conf fix so a power cut can no longer leave chronyd refusing to start.

What changed

Update check against GitHub releases (new)

  • What it checks: about 10 s after launch, and then from an hourly timer for as long as TimeSync keeps running, it asks GitHub whether a release newer than the running version exists — at most once per 24 h after the last successful check. The only request is an anonymous GET https://api.github.com/repos/vu2cpl/timesync-mac/releases/latest: no account or token, nothing sent beyond the app's name and version in the User-Agent.
  • When there is a newer release: a dialog shows its version and release notes. Download opens the release page in your browser (nothing is downloaded or installed automatically), Skip This Version keeps the automatic check quiet about that release, Remind Me Later asks again at the next daily check.
  • Failures stay quiet: only a successful check counts towards the day. One that fails (offline, timeout, GitHub's rate limit, any other error) shows nothing and is tried again about an hour later, or at the next launch.
  • Development builds (a version containing "dev") never check on their own.
  • Turn it off: Settings… → General → untick Check for updates automatically (on by default). Check for Updates… beside it checks right away and always reports. TimeSync is a menu-bar app with no app menu, so both live in Settings.

Server stack (server/)

  • chrony.conf: the pidfile moved from Homebrew's var/run to /var/run/chronyd.pid. macOS empties /var/run at every boot, so a stale pidfile left by a power cut can no longer make chronyd exit with "Another chronyd may already be running" (launchd respawning it every 10 s while the clock free-runs about a second off). Re-run ./server/install.sh to pick this up.
  • chrony.conf + docs/CHRONY.md: a commented example for using a GPS NTP server on your LAN (e.g. a PPS-disciplined Raspberry Pi) as a source, and the server-side allow list trap that makes it silently ignore you.

No helper changes, nothing changed in how the drift is measured or shown.

Verifying

shasum -a 256 TimeSync-0.1.7.zip
# expect: 001c0865c703afd2782b0919b4ec1fde1338a31fbf73bdbe89b38d64fa77bdeb

ditto -x -k TimeSync-0.1.7.zip .
spctl -a -vvv TimeSync.app
# expect: source=Notarized Developer ID, accepted

lipo -info TimeSync.app/Contents/MacOS/TimeSync
# expect: Architectures in the fat file: ... are: x86_64 arm64

Upgrading

Quit TimeSync, drop the new TimeSync.app over the old one in /Applications/, launch. No helper reinstall needed.

License: MIT — see LICENSE.

73 de Manoj VU2CPL

TimeSync 0.1.6 — fixes chrony offset display on Intel Macs

Choose a tag to compare

@vu2cpl vu2cpl released this 26 May 04:53

Intel Mac fix. v0.1.5 and earlier shipped a universal binary (arm64 + x86_64) but one hardcoded path in ChronyMonitor broke the headline chrony offset display on Intel hosts. v0.1.6 fixes that.

Root cause

The privileged helper has always probed both /opt/homebrew/bin/chronyc (Apple Silicon brew) and /usr/local/bin/chronyc (Intel brew), so the Step Clock button worked everywhere. But ChronyMonitor in the main app — which polls chronyc tracking every 5 s to render the canonical filtered drift number — hardcoded the Apple Silicon path. On Intel Macs where brew lives at /usr/local, Process failed to spawn and the popover silently fell back to the noisy single-sample NTP+GPS numbers. No crash, just a quiet feature regression.

What changed

  • ChronyMonitor.init now does the same two-candidate probe the helper does — /opt/homebrew/bin/chronyc first, then /usr/local/bin/chronyc. Apple Silicon behaviour is unchanged; Intel hosts get a working chrony monitor.
  • README's requirements line dropped the "Apple Silicon" qualifier. The binary has always been universal — this one constant was the only thing blocking Intel.

No helper protocol changes, no behavioural changes on Apple Silicon.

Verifying

shasum -a 256 TimeSync-0.1.6.zip
# expect: d7aa026b083247bade30fe44c07ba437c3d4c2c6b9ed7242b79422e3380e1668

unzip TimeSync-0.1.6.zip
spctl -a -vvv TimeSync.app
# expect: source=Notarized Developer ID, accepted

lipo -info TimeSync.app/Contents/MacOS/TimeSync
# expect: Architectures in the fat file: ... are: x86_64 arm64

Upgrading

Quit TimeSync, drop the new TimeSync.app over the old one in /Applications/, launch. No helper reinstall needed.

License: MIT — see LICENSE.

73 de Manoj VU2CPL

TimeSync 0.1.5 — drop popover-closed CPU from 10% to ~1%

Choose a tag to compare

@vu2cpl vu2cpl released this 10 May 07:37

Performance fix. Anyone running v0.1.4 (or any earlier version) was burning ~10% CPU continuously, even with the popover closed. v0.1.5 brings that down to ~1% baseline.

Root cause

MenuBarContent had a 10 Hz Timer.publish(every: 0.1) autoconnected at view-construction time. With .menuBarExtraStyle(.window) (which we use for the popover), the SwiftUI view tree stays mounted even when the popover isn't visible — so the timer kept firing 10 times per second forever, forcing a full re-evaluation of the popover body each tick (re-resolving SF Symbol glyphs, recomputing format strings, the works).

The 10 Hz rate is there to render the .SSS millisecond field on the UTC clock display in the popover header. Useful when you're looking at it; pointless when you aren't.

What changed

  • The timer now lives behind .onAppear / .onDisappear. It only ticks while the popover is actually visible. Verified with ps -o %cpu sampling: post-launch with the popover never opened, three 5-second windows read 4.7% → 1.3% → 1.1%. The residual ~1% is the legitimate ChronyMonitor poll (every 5 s) + gpsd TCP stream — not the timer.
  • formatUTC() no longer allocates a fresh DateFormatter on every call — the formatter is now a static let, created once per process.

Both fixes are pure source-side. The notarized binary in v0.1.4 was built from the buggy source, so the drain affected everyone running it.

Verifying

shasum -a 256 TimeSync-0.1.5.zip
# expect: 3f174475e7138cba0428d8c463a26a98694d0e9d0467dd8fb2a9571e8becb59b

unzip TimeSync-0.1.5.zip
spctl -a -vvv TimeSync.app
# expect: source=Notarized Developer ID, accepted

Upgrading

Quit TimeSync, drop the new TimeSync.app over the old one in /Applications/, launch. Helper protocol unchanged in this release; no helper reinstall needed.

To confirm the fix is taking effect on your Mac, with the popover closed:

ps -o %cpu= -p $(pgrep -f TimeSync.app | head -1)

Should read under 2% pretty much always.

License: MIT — see LICENSE.

73 de Manoj VU2CPL

TimeSync 0.1.4 — Launch at Login toggle

Choose a tag to compare

@vu2cpl vu2cpl released this 09 May 09:37

Launch at Login. A single toggle in Settings → General that registers TimeSync.app to start automatically every time you log in. Closes the last gap in the auto-start story:

Auto-starts at boot?
chrony daemon ✅ — was already, via LaunchDaemon
gpsd daemon ✅ — was already, via LaunchDaemon
TimeSyncHelper ✅ — on-demand via launchd whenever the app makes its first XPC call
TimeSync.app menu bar UI ✅ now — when you flip the toggle

How it works: uses SMAppService.mainApp (modern macOS API, same family as the helper registration). The first time you turn it on, macOS shows a one-time notification "TimeSync was added to Login Items" — that's the standard prompt for any app that registers itself to launch at login. After that: every login, the menu bar icon appears automatically, no Dock icon (LSUIElement still set).

Caveat: requires TimeSync.app to live in /Applications/. If you launched from Downloads or anywhere transient, SMAppService refuses — move the app first.

Verifying

shasum -a 256 TimeSync-0.1.4.zip
# expect: 994fdcfe7b4dce1215fbd2438d2e60fe2eb2c375cba18e6dfb498bc60428f93a

unzip TimeSync-0.1.4.zip
spctl -a -vvv TimeSync.app
# expect: source=Notarized Developer ID, accepted

Upgrading

Quit TimeSync, drop the new TimeSync.app over the old one in /Applications/, launch. Open Settings → General to find the new toggle. Helper protocol is unchanged in this release, so no need to reinstall the helper.

License: MIT — see LICENSE.

73 de Manoj VU2CPL

TimeSync 0.1.3 — actually fix chronyc error message

Choose a tag to compare

@vu2cpl vu2cpl released this 09 May 09:27

Bug fix. v0.1.2 was supposed to translate chronyc exit 1: 506 Cannot talk to daemon into a helpful message — but the fix was checking the wrong stream. chronyc emits its protocol responses (506, 501, etc.) on stdout, and my code only looked at stderr. So the friendly-error path never fired and a fresh user saw the same cryptic error v0.1.2 was supposed to remove.

Reproduced locally:

$ launchctl bootout system/com.vu2cpl.chrony
$ chronyc makestep > /tmp/c.out 2> /tmp/c.err
  exit code: 1
  stdout:    "506 Cannot talk to daemon"
  stderr:    ""

v0.1.3 combines stdout+stderr before pattern-matching. The Step Clock button on a Mac with chrony installed but chronyd not running now correctly shows:

chronyd is not running. Run server/install.sh from the timesync-mac repo to install and start it.

Also added handling for 501 Not authorised (shouldn't happen in normal operation but worth surfacing if it does).

Verifying

shasum -a 256 TimeSync-0.1.3.zip
# expect: f566b7615bc5908fd906ddc110ba7ce2f597fc75d37646a158553f4f5060b1e8

unzip TimeSync-0.1.3.zip
spctl -a -vvv TimeSync.app
# expect: source=Notarized Developer ID, accepted

Upgrading

Quit TimeSync, drop new TimeSync.app over old in /Applications/, launch. Then force-reload the helper so the corrected error path takes effect:

sudo launchctl kickstart -k system/com.vu2cpl.TimeSync.Helper

(Or Settings → Helper → Uninstall, then Install Helper… again.)

73 de Manoj VU2CPL

TimeSync 0.1.2 — friendly chrony errors + interactive installer

Choose a tag to compare

@vu2cpl vu2cpl released this 09 May 09:22

Two real bugs that surfaced after v0.1.0/v0.1.1 went out:

  1. chronyc exit 1: 506 Cannot talk to daemon when clicking Step Clock — useless to anyone who doesn't already know what 506 means. It happens when chrony is installed but chronyd isn't actually running (the most likely state for someone who downloaded the .app but never ran server/install.sh).
  2. server/install.sh was hardcoded to my LAN, my GPS device's FTDI serial number, and 4800 baud. Anyone else had to edit shell scripts before installing — exactly the friction the script was supposed to remove.

What's fixed

Friendlier chrony errors

  • The privileged helper now searches both Homebrew prefixes for chronyc (/opt/homebrew/bin/ for Apple Silicon, /usr/local/bin/ for Intel)
  • When chronyc returns "Cannot talk to daemon" or 506, the helper translates that into a message pointing the user at server/install.sh, instead of forwarding the raw chronyc output

Setup banner in the menu bar

When chrony is unreachable (not installed, or installed but daemon down), the popover now shows a prominent orange banner at the top with a one-click button to open the setup instructions. The Step Clock button is also disabled while chrony is unreachable — it can't do anything useful in that state anyway.

Interactive server/install.sh

The installer now:

  • Detects your Homebrew prefix automatically (Apple Silicon vs Intel)
  • Scans /dev/cu.usb*, lists candidate GPS devices, asks which one
  • Asks for the baud rate (default 4800)
  • Sniffs the device for 3 s and confirms NMEA is actually present at that combination — catches wrong-device or wrong-baud before installing
  • Detects your LAN subnet from en0 and asks which CIDR to allow
  • Renders customized copies of chrony.conf, gpsd-wrapper.sh, and both LaunchDaemon plists, then installs them

No more editing shell scripts before installing.

Verifying

shasum -a 256 TimeSync-0.1.2.zip
# expect: 51dfac214b837f87ac2ff30b23f92dc2f1a075948b462abf8135c29af9b91b69

unzip TimeSync-0.1.2.zip
spctl -a -vvv TimeSync.app
# expect: source=Notarized Developer ID, accepted

Upgrading

Quit the running TimeSync, drop the new TimeSync.app over the old one in /Applications/, launch.

The helper protocol gained one new method (no breaking changes), but the helper binary changed — to pick up the friendlier error path, force a reload:

sudo launchctl kickstart -k system/com.vu2cpl.TimeSync.Helper

Or just toggle Settings → Helper → Uninstall, then Install again.

73 de Manoj VU2CPL

TimeSync 0.1.1 — app icon

Choose a tag to compare

@vu2cpl vu2cpl released this 09 May 08:44

App icon. That's the only user-facing change.

Visual changes

TimeSync.app now ships with a custom icon — a clock face on a blue squircle, with a GPS satellite (body + solar-panel wings) on a dashed orbit ring beaming time signals at the clock. Generated programmatically by scripts/make-icon.swift using CoreGraphics — fully reproducible, regenerable any time.

Verifying

shasum -a 256 TimeSync-0.1.1.zip
# expect: 2f006398710ac24911ff18d53712a648d1f54e70783e58e242e84428a7ef1255

unzip TimeSync-0.1.1.zip
spctl -a -vvv TimeSync.app
# expect: source=Notarized Developer ID, accepted

Same Developer ID signing chain as v0.1.0 (Manoj Ramawarrier — CHVNJ85C9F), hardened runtime, notarized by Apple, ticket stapled.

Upgrading from 0.1.0

Quit the running TimeSync, drop the new TimeSync.app over the old one in /Applications/, launch. The privileged helper from 0.1.0 keeps working — no re-install needed (the XPC protocol hasn't changed). To force-reload the helper anyway: sudo launchctl kickstart -k system/com.vu2cpl.TimeSync.Helper.

What 0.1.0 already does (in case you missed it)

  • Menu-bar app showing system clock drift via chronyc tracking (the canonical filtered offset)
  • Talks to gpsd over TCP/2947 for live GPS state
  • Step Clock button → privileged helper → chronyc makestep to recover when chrony loses quorum
  • Companion server/ install script for the chrony + gpsd LaunchDaemons that turn this Mac into a stratum-2 NTP server for your LAN

73 de Manoj VU2CPL

TimeSync 0.1.0 — first notarized release

Choose a tag to compare

@vu2cpl vu2cpl released this 09 May 08:26

First public, notarized release. Signed with Developer ID Manoj Ramawarrier (CHVNJ85C9F), hardened runtime, accepted by Apple Notary.

What you get

TimeSync.app — a macOS menu-bar app (no Dock icon) that shows your system clock's drift against NTP and a USB GPS, and lets you trigger chronyc makestep from the menu bar via a privileged helper.

Install

  1. Download TimeSync-0.1.0.zip
  2. Unzip, drag TimeSync.app to /Applications/
  3. Open it — clock icon appears in menu bar
  4. Click → Settings… → Helper → Install Helper… (one-time admin auth) to enable the Step Clock button

The app shows useful info on its own. For the Step Clock button to do anything, you need chrony running (this Mac's clock has to be managed by chrony, not the default macOS timed). The companion server/ directory in the repo has an installer that sets up chrony + gpsd as LaunchDaemons; see the README.

Requirements

  • macOS 14 (Sonoma) or later, Apple Silicon
  • USB GPS receiver (NMEA 4800 or 9600 baud) — only needed for GPS-disciplined operation
  • For the Step Clock button: chrony installed and running

Architecture (in brief)

  • Pure-Swift NTP client (UDP/123, no daemon needed for this part)
  • Reads GPS from gpsd over TCP/2947 (JSON line protocol) — no direct serial port access
  • Polls chronyc tracking every 5 s for the canonical drift number (chrony's filtered Allan-deviation Kalman view across all sources is much more stable than any single sample)
  • The privileged helper exposes one XPC method (runChronyMakestep); validates the calling app's code signature

See CLAUDE.md for the development-facing architecture deep dive.

Verifying the download

shasum -a 256 TimeSync-0.1.0.zip
spctl -a -vvv TimeSync.app

spctl should report source=Notarized Developer ID, accepted. SHA-256 of the ZIP is bd2190cae91e04b66e049845ed7dfcb3783289e559be926245ad8829bc814d89.

73 de Manoj VU2CPL