- Go 51.5%
- Swift 47.8%
- Dockerfile 0.4%
- Shell 0.3%
|
All checks were successful
server-ci / build-image (push) Successful in 52s
Apple Health Records is only available in a limited set of countries (US + a handful), and querying HKClinicalType in unsupported regions can cause HKSampleQuery to hang or requestAuthorization to stall mid-prompt — which manifests as "Sync now" never completing. Re-enable by flipping the flag back to true once both (a) the device is in a supported region AND (b) the App ID has the Apple-restricted health-records entitlement. |
||
|---|---|---|
| .forgejo/workflows | ||
| docs/superpowers | ||
| ios | ||
| server | ||
| .gitignore | ||
| CLAUDE.md | ||
| LICENSE | ||
| README.md | ||
health-sync
Self-hosted mirror of all your Apple Health data. An iOS app reads from HealthKit, ships deltas over HTTPS, and a Go server lands them in a Timescale-extended Postgres so you can query and graph your own body history without handing it to anyone else.
Architecture
┌────────────────────┐ HTTPS / gzip JSON ┌──────────────────────┐
│ HealthSync.app │ ───────────────────▶ │ health-sync-server │
│ (iOS 26, SwiftUI) │ ◀─────────────────── │ (Go 1.25) │
└─────────┬──────────┘ bearer-token auth └──────────┬───────────┘
│ │
│ HKAnchoredObjectQuery │ pgx
│ per HK type, persisted anchor │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ HealthKit │ │ Timescale │
│ (system) │ │ on Postgres │
└──────────────┘ └──────────────┘
- iOS walks every HK sample type, batches deltas, posts to
/v1/samples:batch,/v1/workouts:batch,/v1/ecg:batch,/v1/clinical:batch, and/v1/characteristics. Anchors live in a SQLite file in the app sandbox so each sync only sends new rows. - Server keys idempotency on
(source_uuid, type_id, time)— re-sending a batch ingests zero new rows. ABGAppRefreshTaskschedules a sync roughly every hour; a "Sync now" button drives it manually. - The HealthKit type catalog (
server/internal/types/types.yaml) is the single source of truth for both ends; iOS codegens its catalog from the same YAML at build time.
Repo layout
server/ Go module — API server + CLI
cmd/
health-sync-server/ main HTTP daemon
health-sync-cli/ mint-code, revoke, stats
codegen/ types.yaml → Swift catalog
internal/
api/ store/ auth/ metrics/ migrate/ types/
deploy/
Dockerfile
docker-compose.yml host-loopback proxy variant
docker-compose.caddy.yml standalone with auto-TLS
Caddyfile reverse-proxy config
prometheus.yml example scrape config
grafana-dashboard.json server-ops dashboard (Prometheus)
grafana-dashboard-body.json body-metrics dashboard (Postgres)
README.md deploy + observability notes
ios/ xcodegen project — HealthSync.app
project.yml single source of truth for the Xcode project
HealthSync/Sources/
App/ Auth/ Network/ HealthKit/ Persistence/ Sync/ Features/
HealthSync/HealthSync.entitlements
HealthSyncTests/Sources/
scripts/codegen.sh shells out to server/cmd/codegen
MANUAL_TESTS.md device-only checklist
.forgejo/workflows/ CI — server image build + push
Server quickstart
Requires Go 1.25 and Docker (the tests spin up real Postgres via testcontainers-go).
cd server
go test ./... # full test sweep
go build ./cmd/... # binaries in ./bin or $GOPATH/bin
For local end-to-end against the iOS app:
cd server/deploy
echo -n '<long random password>' > secrets/db_password
printf 'postgres://healthsync:%s@db:5432/healthsync?sslmode=disable' \
"$(cat secrets/db_password)" > secrets/db_url
docker compose up -d
docker compose run --rm server health-sync-cli mint-code --ttl 15m
Type the resulting 8-char code into the iOS app during onboarding.
For a production VPS deployment (Caddy-fronted, auto-HTTPS) see
server/deploy/README.md.
iOS quickstart
Requires Xcode 17+ and xcodegen (brew install xcodegen). The
catalog generation step calls go run so Go must be on PATH inside
Xcode's build environment too.
cd ios
bash scripts/codegen.sh # one-time: generate the HK catalog
xcodegen generate # produce HealthSync.xcodeproj
open HealthSync.xcodeproj
In Xcode, plug in your iPhone, pick it as the destination, hit ⌘R.
The bundle is me.devilreef.healthsync; signing is set to team
Y29665FRMG. HealthKit must be enabled on that App ID in Apple's
developer portal (Xcode "Automatic Signing" will register it on first
build).
CLI test sweep against the simulator:
cd ios
xcodegen generate
xcodebuild -scheme HealthSync \
-destination 'platform=iOS Simulator,name=iPhone 17' test
Observability
Metrics scrape at :9090/metrics on the server. Prometheus config and
two importable Grafana dashboards (one for server ops, one for body
metrics out of Postgres) live in server/deploy/. See
server/deploy/README.md for the read-only Postgres role recipe Grafana
should use.
License
Released into the public domain under The Unlicense.
See LICENSE for the full text. Do whatever you want with it.