個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
A self-hosted PWA two caregivers use from their phones to track one baby,
from the newborn weeks (feeds, diapers, sleep, a push nudge when nothing has
been logged) through the first two years (growth percentiles, a photo
gallery, CDC milestone and vaccination checklists).
One Node.js process serves the app, owns a SQLite database, stores photos on
disk, and runs the notification timers. No accounts, no analytics, no
third-party services unless you opt into the AI extras below.檢視完整 README ↗A self-hosted PWA two caregivers use from their phones to track one baby, from the newborn weeks (feeds, diapers, sleep, a push nudge when nothing has been logged) through the first two years (growth percentiles, a photo gallery, CDC milestone and vaccination checklists).
One Node.js process serves the app, owns a SQLite database, stores photos on disk, and runs the notification timers. No accounts, no analytics, no third-party services unless you opt into the AI extras below.
Who it is for: technical parents who can run a Docker image or a Fly app and want their child's data on a machine they control. It is deliberately single-family: two named caregivers, one baby, one shared secret.
Not goals: accounts or multi-family hosting, a native app, non-US immunization or milestone schedules (the checklists are CDC/ACIP data files and can be swapped), and translated UI. Issues asking for these will be closed with a pointer here; forks are welcome.
NUDGE_HOURS=0 to switch it off once the newborn phase passes)
plus the photo nudges above. Web Push, no vendor account needed.A read-only demo runs at https://baby-tracker-demo.fly.dev (log in with
the secret demo as either Alex or Sam). Everything in it is synthetic and
regenerated on each deploy; logging, uploads and export are disabled there.
All screenshots are anonymized. Every name, date, measurement and photo shown is synthetic: they come from a throwaway database seeded by
scripts/seed-demo-data.jsplus placeholder images, never from a real family's data. See Demo data.
This repo is public; no personal data lives in it. Names, emails, and the
shared login secret are supplied via environment variables (.env locally -
gitignored - or Fly secrets in production). The database and photos live on a
private volume, and photos are only served behind login.
npm install
cp .env.example .env # optional - every key has a usable dev default
npm start # http://localhost:3000
npm test # API test suite, no config needed
A fresh clone boots with no other setup: the data directory and push keys
are created on first start, and the startup banner lists which optional
features are on. (npm run make-icons only regenerates the committed app
icon.)
Configuration is read from .env; .env.example documents every key, and the
table at the bottom of this file has the full reference. The default login
secret in dev is baby.
Every pull request runs npm test (GitHub Actions, Node 22) and gets one
automated comment from Claude listing any README, CLAUDE.md, .env.example,
fly.toml.example or docs/ statement the change made stale. The comment
job is read-only and never pushes. Neither job runs on draft PRs or on PRs
from forks; fork contributors should run npm test locally. Tagging v*
publishes the container image to GitHub Container Registry
(.github/workflows/publish-image.yml).
The screenshots above were produced from a disposable instance with an entirely synthetic database - no real data is involved at any point:
SCRATCH=/tmp/bt-demo && mkdir -p $SCRATCH
cat > $SCRATCH/demo.env <<EOF
USER_NAMES=Alex,Sam
BABY_NAME=Robin
BIRTH_DATE=2026-05-02
BABY_SEX=girl
APP_SECRET=demo
DATA_DIR=$SCRATCH/demo-data
PORT=3100
EOF
SEED_DIR=$SCRATCH/demo-data SEED_BIRTH=2026-05-02 \
SEED_NOW=2026-07-23T15:40:00-07:00 node scripts/seed-demo-data.js
node --env-file=$SCRATCH/demo.env server/index.js # http://localhost:3100
scripts/seed-demo-data.js generates about two months of plausible feeds,
diapers, weekly growth measurements and milestones from a seeded PRNG, so
re-running it reproduces the same screenshots. It refuses to write to a live
DATA_DIR, and --env-file= (rather than npm start) keeps your real
.env out of the demo process.
A prebuilt multi-arch image is published to GitHub Container Registry on every release, so a Raspberry Pi, a home server or any VPS with Docker can run the app without compiling anything.
git clone https://github.com/magratheazaphod/baby-tracker && cd baby-tracker
# (or just download docker-compose.yml and .env.example into an empty folder)
cp .env.example .env # set APP_SECRET, USER_NAMES, BABY_NAME, BIRTH_DATE,
# BABY_SEX and HOME_TZ at minimum
docker compose up -d
curl -s http://127.0.0.1:3000/api/health # {"ok":true}
The app binds to localhost only, because the "Add to Home Screen" install
flow and Web Push notifications both require HTTPS. Put a reverse proxy with
automatic certificates in front of it. A ready-made Caddy setup is included:
point DNS at the host, replace tracker.example.com in
deploy/caddy/Caddyfile, then
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
Traefik, nginx or a Cloudflare tunnel work just as well; proxy to port 3000.
Then on each phone: open the app URL in Safari → Share → Add to Home Screen → open it from the home screen → log in → tap 🔔 to enable nudges.
The named data volume holds the SQLite database and every photo. Back it up
the same way as any other install: GET /api/export or scripts/backup.sh
(see Backups). To upgrade, docker compose pull && docker compose up -d; schema migrations run automatically at startup.
cp fly.toml.example fly.toml # set your app name + region (stays local)
fly apps create your-app-name
fly volumes create data --size 1 --region <region> -a your-app-name
npx web-push generate-vapid-keys # for push notifications
fly secrets set -a your-app-name \
APP_SECRET="your-family-secret" \
USER_NAMES="Mom,Dad" \
BABY_NAME="..." \
BIRTH_DATE="YYYY-MM-DD" \
BABY_SEX="girl" \
VAPID_SUBJECT="mailto:[email protected]" \
VAPID_PUBLIC_KEY="..." \
VAPID_PRIVATE_KEY="..."
fly deploy --ha=false
Then on each iPhone: open the app URL in Safari → Share → Add to Home Screen → open it from the home screen → log in → tap 🔔 to enable nudges.
GET /api/health returns {"ok":true} when the app is up and its database is
readable, and 503 otherwise. It runs a one-row SQLite query rather than just
confirming the process is alive - a machine whose volume failed to mount will
happily serve pages while every real request fails. It requires no login and
returns nothing personal, so it is safe to point a public monitor at.
Two things should watch it:
fly.toml.example includes an [[http_service.checks]] block that
polls it every 30s and restarts the machine when it fails.https://your-app.fly.dev/api/health. This is the one that actually reaches
you: if the machine or the whole region is down, Fly's internal check has no
way to send you an alert.curl -s https://your-app.fly.dev/api/health # {"ok":true}
Three layers:
fly volumes update <vol-id> --snapshot-retention 30). Restore with
fly volumes create data --snapshot-id <id>.GET /api/export streams a tar.gz of the SQLite
database + all photos. Authenticated by login cookie or
Authorization: Bearer <APP_SECRET>.scripts/backup.sh downloads an export and keeps the
newest 30 locally:
APP_URL=https://your-app.fly.dev APP_SECRET=... ./scripts/backup.sh
Run it on a schedule (cron/launchd) for continuous off-site copies.Two ways to log by voice, both parsed by Claude and both off unless you set the keys:
ANTHROPIC_API_KEY plus
TRANSCRIBE_API_KEY for speech-to-text (any OpenAI-compatible
/v1/audio/transcriptions endpoint; Groq by default). The button appears
as soon as the transcription key is set, so set both or every attempt
fails. Handles English, Mandarin, and sentences that switch between them.POST /api/voice takes a dictated sentence, saves the
events, and returns a spoken confirmation, so "Hey Siri, Log Baby" works
hands-free. Needs ANTHROPIC_API_KEY plus its own VOICE_TOKEN
(fly secrets set -a your-app-name --stage VOICE_TOKEN="...", then
deploy). The token is deliberately separate from APP_SECRET and can only
create events, never read or export.See docs/siri-voice-logging.md for both.
| Var | Default | Purpose |
|---|---|---|
APP_SECRET | baby (dev only) | shared login secret - set a real one in prod |
USER_NAMES | Mom,Dad | comma-separated parent names shown at login |
BABY_NAME | Baby | baby's name (private, env-only) |
APP_NAME | falls back to BABY_NAME | app title + home-screen name |
BIRTH_DATE | unset | baby's birth date (YYYY-MM-DD); enables growth percentiles |
BABY_SEX | unset | boy or girl; selects the WHO growth-standards table |
HOME_TZ | America/Los_Angeles | day boundaries for reports |
NUDGE_HOURS | 6 | push a nudge after this many hours with no entries; 0 disables |
RENUDGE_MINUTES | 60 | re-nudge interval while still quiet |
PHOTO_NUDGE_DAYS | 3 | nudge for a photo after this many days with none; 0 disables |
MONTHLY_PHOTO_NUDGE | 1 | monthly-birthday photo nudge (needs BIRTH_DATE); 0 disables |
PORT | 3000 | port the server listens on |
DATA_DIR | ./data | where SQLite + photos live (/data on Fly) |
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT | auto-generated, persisted in DATA_DIR | web-push credentials; set only to survive a data wipe |
COOKIE_SECRET | derived from APP_SECRET | cookie signing key |
ANTHROPIC_API_KEY |
MIT - see LICENSE. The WHO growth standards, CDC milestone
checklist and CDC/ACIP immunization schedule bundled under public/ are
public reference data from their respective publishers, not covered by this
license. Screenshots under docs/screenshots/ are synthetic demo captures
and may be reused under the same MIT terms.
| unset |
| enables the auto-generated Claude analysis of diaper photos and voice logging; without it, photos still work and analysis is skipped |
TRANSCRIBE_API_KEY / TRANSCRIBE_URL / TRANSCRIBE_MODEL | unset / Groq / whisper-large-v3-turbo | speech-to-text for the in-app mic button; unset hides the button |
DEMO_MODE | unset | 1 makes the instance a read-only public demo: login and reads work, every write, upload, push and export is refused, and an empty database is filled with synthetic data at boot (needs BIRTH_DATE) |
DEMO_RESEED | unset | 1 regenerates the demo data on every start; only honoured with DEMO_MODE |
VOICE_TOKEN | unset | bearer token for POST /api/voice (hands-free Siri logging - see docs/siri-voice-logging.md); unset disables the endpoint |