Repository navigation
Releases
TimeSync 0.1.7 — update check against GitHub releases
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'svar/runto/var/run/chronyd.pid. macOS empties/var/runat 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.shto 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-sideallowlist 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
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.initnow does the same two-candidate probe the helper does —/opt/homebrew/bin/chronycfirst, 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%
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 withps -o %cpusampling: post-launch with the popover never opened, three 5-second windows read 4.7% → 1.3% → 1.1%. The residual ~1% is the legitimateChronyMonitorpoll (every 5 s) + gpsd TCP stream — not the timer. formatUTC()no longer allocates a freshDateFormatteron every call — the formatter is now astatic 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
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
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
Two real bugs that surfaced after v0.1.0/v0.1.1 went out:
chronyc exit 1: 506 Cannot talk to daemonwhen clicking Step Clock — useless to anyone who doesn't already know what 506 means. It happens when chrony is installed butchronydisn't actually running (the most likely state for someone who downloaded the .app but never ranserver/install.sh).server/install.shwas 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 atserver/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
en0and 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
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
gpsdover TCP/2947 for live GPS state - Step Clock button → privileged helper →
chronyc makestepto 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
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
- Download
TimeSync-0.1.0.zip - Unzip, drag
TimeSync.appto/Applications/ - Open it — clock icon appears in menu bar
- 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:
chronyinstalled and running
Architecture (in brief)
- Pure-Swift NTP client (UDP/123, no daemon needed for this part)
- Reads GPS from
gpsdover TCP/2947 (JSON line protocol) — no direct serial port access - Polls
chronyc trackingevery 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