Short technical map of the current Gluco architecture, plus a brief note on where the project may go next.
Gluco is two processes communicating over local HTTP on
127.0.0.1:8765:
- Frontend (
macos_frontend/app.py) — AppKit/rumpsmenubar UI. - Daemon (
python -m daemon) — LibreView auth, polling, caching, status.
Process ownership:
- The frontend supervises the daemon as a child process in managed mode, respawning on crash (with a crash-rate limit) and tearing it down on Quit.
- If a daemon is already reachable at
DAEMON_URLon startup, the supervisor switches to external mode and leaves lifecycle to whoever already owns it (typically a dev runningpython -m daemonmanually). - A parent-death pipe (
daemon/parent_watch.py, wired up viaGLUCOBAR_PARENT_PIPE_FD) ensures the daemon exits even if the frontend dies without running signal handlers (e.g. SIGKILL, force-quit). No ghost daemons. Open at Loginis implemented as a macOS login item toggle for the running app bundle path (not via LaunchAgent enable/disable).
Owns all LibreView-facing behavior:
- authenticates with Keychain-provided credentials
- polls readings every 60s and caches current + ~12h of history
- classifies status:
starting,ok,stale,warming_up,no_network,auth_failed,tos_required,rate_limited - applies exponential backoff, separately for network errors and rate-limit responses
- exposes a stable local HTTP API
- enforces single-instance via
fcntl.flockon~/.glucobar/daemon.lock - persists a session token (
session.json, mode 0600) so short restarts don't force a fresh login
Missing credentials cause the daemon to exit with code 78
(EX_CONFIG). The supervisor treats that as sticky and stops
respawning until the frontend saves credentials and asks for a
restart.
Intentionally thin:
- polls the daemon every 30s
- renders the menubar title: value + trend arrow, or a status glyph
(
Connecting…,⚠ Offline,⚠ Sign In,⚠ Error) - renders a compact 12h graph in the dropdown
- provides
Refresh,Open at Login,Sign In/Out of LibreView Account,Quit
Credentials UX is driven by native AppKit NSPanel modals
(first-run prompt and the account menu share the same dialog). On
save, the frontend writes the Keychain entries and asks the
supervisor to restart the daemon so it re-authenticates immediately.
The frontend must launch from inside Gluco.app (either the
source wrapper bundle or the standalone py2app bundle). Running
python -m macos_frontend
directly is intentionally rejected — AppKit modals need a real
CFBundleIdentifier and LSUIElement to behave.
GET /health— 3-bucket status, uptime, last error, failure countGET /glucose/current— latest reading + status + agesGET /glucose/graph?hours=N— cached history, oldest firstGET /patient— first/last name + patient idPOST /auth/refresh— drop session, re-auth, poll once
This is the portability seam. A future Linux frontend consumes the same endpoints; the daemon does not change.
Default state dir: ~/.glucobar/ (override via DAEMON_STATE_DIR).
daemon.lock— single-instance guardsession.json— cached session token metadata (mode 0600)daemon.log(+ rotations) — daemon application log (1 MB × 3)daemon.stdout.log/daemon.stderr.log— supervisor-captured child stdiolaunchd.frontend.out.log/launchd.frontend.err.log— launchd capture when running viascripts/install_launchagent.sh
Credentials are never read from .env or environment variables.
Keychain (service = "glucobar") is the only source of truth, and
a partially-seeded Keychain is treated as absent.
Non-credential config comes from .env / environment (see
.env.example). The most relevant knobs:
LIBREVIEW_REGION— cluster (US,EU,EU2,DE,FR,JP, …) or a country alias (PT/ES/GB→EU)DAEMON_URL— override the local daemon URLDAEMON_STATE_DIR,LOG_LEVELERROR_BACKOFF_*,RATELIMIT_BACKOFF_*
Hardcoded runtime defaults:
- daemon poll: 60s
- frontend poll: 30s
- stale cutoff: 360s
- color thresholds (mg/dL):
<70,70-180,180-250,>250
tests/ (pytest) covers the pieces with real logic:
- backoff behavior
- Keychain credentials contract (partial state = absent)
- daemon service state transitions
- session store persistence
- supervisor lifecycle (external vs. managed, watchdog, EX_CONFIG stickiness)
- parent-death pipe — daemon self-terminates when the frontend dies without cleanup
UI rendering is deliberately lightweight and validated by running the app.
- Abbott/LibreView complexity stays in
daemon/. - The frontend is thin and API-driven.
- Keychain-only credentials; missing creds is a config state, not a retry loop.
- Reliability > feature breadth. Gluco is a personal utility, not a medical workflow.
Kept intentionally small:
- Preserve the daemon API and keep auth/backoff paths robust.
- Tighten release automation; revisit signing/notarization only if broad distribution becomes a goal.
- A Linux (Fedora KDE) frontend remains possible via Qt6/PySide6
consuming the same daemon API —
systemd --userwould replace the macOS supervisor. This is an explicit frontend port, not a daemon fork.
Explicit non-goals, for now:
- Multi-user / multi-patient support
- In-app threshold editing
- Desktop alerting as a headline feature
- Broad distro/package parity before the macOS path is solid
The release version is the version field in pyproject.toml (for example
1.0.0). The menubar About window and the daemon’s OpenAPI version read
that value via importlib.metadata after install. The thin .app bundle and
py2app dist bundle copy CFBundleVersion / CFBundleShortVersionString from
the same number in Info.plist templates / build_portable_app.sh — keep
those aligned when you cut a release.
User-facing copy, NOTICE, and ATTRIBUTIONS.md summarize licensing and
third-party material; the canonical Apache-2.0 text is LICENSE.