Not a medical device. No warranty, no liability. Read the disclaimer before using any part of this project.
DiaLife relays a child's continuous glucose readings to the parents' phones and makes sure somebody actually reacts to an alarm.
Dexcom sensor → xDrip+ (the child's phone) → DiaLife server → parents' Android app
↘ read-only web viewer
↘ ntfy (optional push)
It is a small, self-hosted alternative to running a full Nightscout: one Go binary with SQLite behind your own HTTPS reverse proxy, an Android app for each parent and a read-only web page. The interface is in Ukrainian, English and Spanish.
The pictures are rendered from made-up data (android/…/ReadmeShots.kt for
the app; a demo server with a generated day for the web).
- Live glucose on every parent's phone: a home screen with trend, forecast cone and a pinch-to-zoom chart, a full-screen landscape chart, home-screen and lock-screen widgets, and the same data as a table.
- Alarm episodes, not just beeps. Low, urgent low, high, urgent high, predicted low and "no data". One parent taps "I'm on it" and the others go quiet. Everyone is woken again if it gets worse, if nobody answers a check-in, if it lasts too long or if the one handling it asks for help.
- Treatments. Rapid and long-acting insulin, food, glucometer checks, activity and notes, entered from any phone (also offline) or synced from xDrip+. A reminder an hour before a scheduled long-acting dose.
- No double insulin. When a parent opens the insulin form, the other phones show "Mom is giving insulin". Saving a dose close to another one asks first: the same injection or a new one (rapid within 20 minutes), a correction (rapid within 3 hours), a second long-acting dose (within 16 hours). A dose from xDrip+ that lands next to another asks every parent "one injection or two?", and "two" keeps a watch for a low.
- Meals as a list, with carb suggestions. Each dish is its own line on a food screen of its own, kept as a draft until it is saved. Suggestions come from the family's own earlier entries first, then from an imported carb table (a clinic's or a glucometer maker's handout, in Ukrainian, English and Spanish), and, if you configure one or more language models (Gemini, or any OpenAI-compatible API: OpenAI, OpenRouter, a local Ollama), from their estimate with a range. With a Gemini key a name is also found by meaning: "mashed potatoes" finds the table's potato, "rye bread" the family's own "black bread". The foods logged most are quick buttons; the whole table is one tap further. A suggestion is only ever a tap away, never filled in by itself. Without a model configured nothing about food leaves the server.
- The rapid dose, worked out but never recorded by itself. From the family's settings, shared by every phone: grams of carbs per unit, a correction table by glucometer, one of four clinic rounding rules (step 1 or 0.5 unit), and limits. The meal's part and the correction are added, then rounded once. The correction applies only to a glucometer reading under 15 minutes old; a low, a very high, rapid insulin in the last 3 hours, a dose over the limit, an unusual meal all show as cards, red ones needing an extra tap. A meal's later dishes join it, and what was already given is taken into account. The dose can go in as planned: every phone shows it, it counts as insulin only once someone taps "Given" with when, and an hour later everyone is asked whether it was given. Under the chart, a meal shows what it needed and what was given.
- "Did you forget to log?" A rise with no food or a fall with no insulin asks every parent what happened, and asks again until their answers agree. Unanswered questions become marks on the chart.
- Sensor vs glucometer. Pairs every glucometer check with the sensor at that moment and estimates, sensor by sensor, how far apart the two usually are. With enough checks an optional "≈ meter" line appears on the chart.
- Statistics. Time in range, mean, GMI, CV, the typical day (AGP), daily insulin and carbs, and an alarm log. The 30-minute forecast shows how often it came true on the child's own recent readings.
- A forecast that knows the food and insulin. Rapid insulin still working and carbs still coming in (the LoopKit/oref0 insulin curve, carbs absorbed evenly) give a second forecast for the next hour, with its own record of how often it came true. The sensitivity comes from the correction table unless you set it. Alarms do not use it.
- Read-only web viewer for a computer: the chart, any period, and every reading and entry as a table. A browser gets in only when a parent scans its QR code in the app and chooses how long it may stay; the parent can end the session from the phone at any time.
- Nightscout-compatible upload API, so xDrip+ uploads to it as it would to Nightscout.
| Path | What |
|---|---|
cmd/dialife, internal/ |
the server (Go, SQLite with migrations) |
android/ |
the parents' app (Kotlin, Jetpack Compose) |
deploy/ |
systemd units, Caddy snippets, deploy and backup scripts |
tools/dl |
the admin tool: runs every command on the server over SSH |
testdata/ |
sample xDrip+ uploads used by the tests |
- Server: a small Linux server with systemd and SSH access as root, and Caddy (or another reverse proxy) for HTTPS on your own domain. DiaLife uses about 20 MB of RAM.
- To build:
- Go 1.27 for the server.
- JDK 21 and the Android SDK for the app (API 36, runs on Android 12+).
python3andqrencodefortools/dl.
- xDrip+ on the phone that reads the sensor.
Nothing about your installation is stored in the repository. Two files hold it:
# on your computer: where the server is, domains, Caddy, ntfy topics
cp deploy/dialife.conf.example deploy/dialife.conf # git-ignored
$EDITOR deploy/dialife.conf
# on the server: secrets, public URL, time zone, the name of the xDrip+ phone
scp deploy/dialife.env.example root@your-server:/etc/dialife.env
ssh root@your-server 'chmod 600 /etc/dialife.env && $EDITOR /etc/dialife.env'Then put tools/dl on your PATH: ln -s "$PWD/tools/dl" ~/.local/bin/dl.
dl deploy # tests, builds, uploads the binary and systemd units, restarts
deploy/caddy-add.sh # one time: adds the site block to your Caddyfile, validates, reloadsdeploy/caddy-add.sh backs up the Caddyfile first and puts it back if the
result does not validate. Only /api/* and the web viewer (/, /web/*)
are exposed; everything else is refused.
Every phone gets its own token. A revoked token stops working at once.
dl device-add "xDrip" uploader # prints the token for xDrip+
dl device-add "Mom" caregiver --qr # a QR code to scan in the app
dl device-add "Dad" caregiver --qr
dl devices # list; dl revoke ID to cut one offIf one person uses several phones, dl device-person ID NAME makes them
answer questions as one person.
In xDrip+: Settings → Cloud Upload → Nightscout Sync (REST-API). Turn it on and set the Base URL to
https://<xDrip token>@dialife.example.com/api/v1/
Enable uploading treatments if you want entries made in xDrip+ to show up in the app, and downloading them if you want the parents' entries to show up in xDrip+.
Phone makers' battery savers are the most common reason readings stop arriving. On the xDrip+ phone, exempt xDrip+ from battery optimisation, allow it to run in the background, and turn off any "sleep" or "deep optimisation" mode.
cd android
./gradlew assembleRelease # or: dl app (tests, builds, installs on every phone on USB)Or download a ready APK from Releases.
Install the APK, open it and scan the QR code from dl device-add … --qr.
Then allow notifications, alarms over the lock screen and unrestricted battery
use, as the app asks. An APK you build yourself is signed with your local debug
key. The APKs on Releases are signed with the project's key
(the publish build type, ./gradlew assemblePublish, see android/app/build.gradle.kts), so
yours will not install over them, nor theirs over yours.
With a self-hosted ntfy, the server also sends alarms as
push notifications with action buttons. Set DIALIFE_NTFY_URL,
DIALIFE_NTFY_TOKEN and DIALIFE_NTFY_TARGETS in /etc/dialife.env. The
deploy/ntfy.Caddyfile.snippet exposes ntfy on its own domain:
deploy/caddy-add.sh deploy/ntfy.Caddyfile.snippet ntfy.
A carb table is a JSON file (source, title, categories and foods with
their portion, carbs and names in uk, en, es); dl foods FILE loads it
and replaces the table of the same source, dl foods lists what is loaded.
No table ships with DiaLife: printed ones belong to their publishers.
With a Gemini key, food names are also compared by meaning
(DIALIFE_GEMINI_EMBED_MODEL, none to switch it off; vectors are made once
and kept). DIALIFE_FOOD_SIMILAR (0–1, default 0.80) is how close two names
must be.
Add one or more models to /etc/dialife.env and redeploy; each comma-separated
model is asked at the same time, each with its own timeout, and their answers
are combined (the middle number, the widest range). An answer is cached per
food and portion. With none set, the app only suggests the family's own
earlier numbers.
DIALIFE_GEMINI_KEY=… # from aistudio.google.com (not the Gemini app subscription)
DIALIFE_GEMINI_MODEL=gemini-3.5-flash-lite
DIALIFE_OPENAI_URL=https://openrouter.ai/api/v1 # or OpenAI, or http://127.0.0.1:11434/v1 for Ollama
DIALIFE_OPENAI_KEY=…
DIALIFE_OPENAI_MODEL=…
DIALIFE_LLM_TIMEOUT=10sOnly the food names and portions are sent, never glucose or names. They are estimates of a language model: check them against the package or the scale.
Nothing to set up beyond the Caddy block above: open your domain in a browser, then in the app ⋮ → Web login and scan the code.
The server makes a daily SQLite backup (dialife-backup.timer). The user
units in deploy/laptop/ pull those backups to your computer every hour over
a read-only SSH key. Copy deploy/laptop/pull.env.example to
~/.config/dialife/pull.env before you enable them.
dl status # services, versions, memory, age of the newest reading, live alarms
dl last # newest readings
dl alarms # thresholds and live episodes
dl set low=70 high=180 # change thresholds (takes effect within a minute)
dl foods table.json # load a carb table (dl foods: what is loaded)
dl logs -f # follow the server log
dl help # everything elsemake test # server: go vet + tests
cd android && ./gradlew testDebugUnitTest # app: unit tests
./gradlew verifyRoborazziDebug # app: screenshots against src/test/screenshots
./gradlew recordRoborazziDebug # accept changed screenshots
./gradlew recordRoborazziDebug --tests '*ReadmeShots*' # re-render the README pictures (docs/screenshots)MIT. The bundled fonts (Onest, IBM Plex Mono) are under the SIL Open
Font License; see android/licenses/. The web viewer bundles
Mithril (MIT, internal/web/static/mithril.LICENSE.txt).
xDrip+, Dexcom, Nightscout and ntfy belong to their respective owners. DiaLife is not affiliated with or endorsed by any of them.







