Ctrl K
ComunidadGuías…

INSTALL.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# INSTALL.md — AI-assisted deployment guide

> **For Claude / GPT / Gemini / Cursor / any AI coding assistant**: this file is written specifically for you to read and execute. Walk the user through it step-by-step. Verify each step before moving on. The user may not know docker — explain what you're doing in plain English when prompted.

---

## What you're deploying

A self-hosted dashboard that ingests Apple Health data from the **Health Auto Export** iOS app and serves a web UI on port 8880. Single-container Bun + SQLite stack. ~140 MB image, <50 MB RAM at rest.

## Prerequisites checklist (verify ALL before continuing)

```bash
# 1. Docker installed and running
docker --version          # need 20.10+
docker compose version     # need v2 (NOT docker-compose v1)

# 2. Network reachability — the iPhone must be able to POST to the host
#    Find the host's LAN IP (the one the iPhone can reach):
ip -4 addr show | grep -oE 'inet [0-9.]+' | grep -v '127\.'
#    On macOS:  ipconfig getifaddr en0

# 3. Port 8880 is free OR pick a different host port
ss -tln | grep 8880     # should print nothing
#    If something is on 8880, edit docker-compose.yml port mapping later

# 4. ~200 MB free disk for image + initial DB growth
df -h .

# 5. Git installed (for cloning)
git --version
```

If any of the above fail, **stop and fix it before continuing**. Don't try to "make it work" with workarounds — the user should have a clean base.

## Step 1 — Clone

```bash
cd ~        # or wherever the user wants the repo
git clone https://github.com/<USERNAME>/apple-health-dashboard.git
cd apple-health-dashboard
```

Replace `<USERNAME>` with the actual GitHub username/org. The user knows this.

## Step 2 — Configure docker-compose.yml

```bash
cp docker-compose.example.yml docker-compose.yml
```

Open `docker-compose.yml` and decide:

- **Direct port exposure (simplest)**: leave `ports: ["8880:8880"]`. The dashboard will be at `http://<host-ip>:8880`.
- **Different host port**: change to `"9090:8880"` (or whatever).
- **Reverse proxy with HTTPS**: uncomment the Traefik labels block, set `Host(\`health.example.com\`)` to a domain you control, ensure your Traefik instance and DNS are configured. The user should already have Traefik working if they're choosing this path — if they don't, default to direct port exposure.

## Step 3 — Build and start

```bash
docker compose up -d --build
```

Expected output: `Container apple-health-dashboard Started`. Wait ~10 seconds, then verify:

```bash
curl -sf http://localhost:8880/health
# expected: {"status":"ok","uptime":...}

curl -s http://localhost:8880/api/stats
# expected on a fresh install: {"metrics":0,"hr_readings":0,"workouts":0,"sleep":0,...}
```

Check container logs:

```bash
docker logs apple-health-dashboard --tail 5
# expected:
#   🏥 Health server running on http://localhost:8880
#   📊 SQLite database: /app/data/health.db
```

If you see anything else, capture the full log (`docker logs apple-health-dashboard`) and stop. Most failures here are port conflicts or volume permission issues.

## Step 4 — Configure Health Auto Export on the iPhone

The user needs to do this part on their phone — guide them.

1. **Buy and install** [Health Auto Export](https://apps.apple.com/us/app/health-auto-export-json-csv/id1115567069) from the App Store ($4.99 one-time).

2. **Open HAE → grant Apple Health permissions**. Allow ALL categories — the app needs broad access to export everything.

3. **Create automation #1: "Workouts"**
   - Type: REST API
   - URL: `http://<host-lan-ip>:8880/health` (the IP you found in prereq #2)
   - Data Type: **Workouts**
   - Include Route Data: **ON**
   - Include Workout Metrics: **ON**
   - Format: JSON v2
   - Date Range: Since Last Sync
   - Schedule: Every **5 minutes**

4. **Create automation #2: "Health Metrics"**
   - Type: REST API
   - URL: same as above
   - Data Type: **Health Metrics**
   - Select Health Metrics: **All**
   - Summarize Data: ON
   - Batch Requests: ON
   - Format: JSON v2
   - Date Range: Since Last Sync
   - Schedule: Every **1 hour**

5. **Tap "Run Now"** on each automation to seed the database.

## Step 5 — Verify data is flowing

```bash
# After ~30 seconds:
curl -s http://localhost:8880/api/stats
# expected: row counts > 0 across metrics, hr_readings, workouts, sleep

docker logs apple-health-dashboard --tail 5
# expected lines like:
#   [2026-05-10T23:15:00.000Z] Ingested health data: 84233 bytes
```

If row counts are still 0 after several "Run Now" taps:
- **Network**: from the iPhone, in Safari, visit `http://<host-lan-ip>:8880/health` — should return JSON. If not, the iPhone can't reach the host (firewall, wrong IP, etc.).
- **Logs**: `docker logs apple-health-dashboard --tail 30` — look for errors like "attempt to write a readonly database" (file permissions on `data/` dir) or "Payload too large" (rare; HAE can push >25 MB on initial sync — bump `MAX_POST_BYTES` in `server.ts` if needed).

## Step 6 — Visit the dashboard

Open a browser to `http://<host-lan-ip>:8880`. Click each tab — Briefing, Today, Trends, Workouts, Sleep, Report, Calendar — and confirm data renders.

The first few hours of data will be sparse. After a full day of HAE syncs, the briefing will populate (recovery score needs at least a few days of baseline). After a week, the 30-day trends and rolling charts come alive.

## Common follow-ups

### Add HTTPS via Traefik

If the user runs Traefik with a wildcard cert:

1. Uncomment the Traefik labels in `docker-compose.yml`
2. Replace `health.example.com` with an actual domain whose DNS points at the Traefik host
3. Set `traefik.tls.certresolver` to whatever resolver name they configured (commonly `letsencrypt`, `cloudflare`, etc.)
4. Add the container to the Traefik external network (uncomment `networks:` block)
5. `docker compose up -d` to apply

### Migrating existing data

If the user already has months of Apple Health data, HAE's "Date Range: All" mode on a one-time export will push it all in one go. Bump `MAX_POST_BYTES` in `server.ts` from 25 MB to 200 MB temporarily, rebuild, then revert after the import.

### Backups

```bash
# data/health.db is the only thing you need to back up
tar czf health-backup-$(date +%F).tar.gz data/
```

For automated backups, add a systemd timer or cron job that does the above and uploads to S3 / B2 / wherever. The container itself doesn't manage backups.

### Updating

```bash
cd apple-health-dashboard
git pull
docker compose up -d --build
```

The container is recreated; the bind-mounted `data/` and `public/` survive untouched.

### Resetting the database (start fresh)

```bash
docker compose down
rm data/health.db data/raw-payloads.jsonl
docker compose up -d --build
# HAE's next push will populate the new DB
```

## Troubleshooting matrix

| Symptom | Cause | Fix |
|---------|-------|-----|
| `curl localhost:8880/health` fails | Container didn't start | `docker logs apple-health-dashboard` — usually a port conflict or volume perm |
| Container starts but `/api/stats` shows zeros after HAE pushes | Phone can't reach host, OR iPhone-host network mismatch | From phone, visit `http://<host-ip>:8880/health` in Safari; if it loads, HAE just hasn't run yet |
| Logs show "attempt to write a readonly database" | Docker volume perms broken | `chmod -R 666 data/` then `docker restart apple-health-dashboard` |
| Sleep shows fragments instead of full nights | HAE didn't export the early-evening "before bed" sleep | Trigger a manual "Run Now" on the Health Metrics automation |
| 5/10 says 4h, iPhone says 10h | Day with multiple sleep events (overnight + naps) | This dashboard already sums them — confirmed against iPhone Health Sleep overview |
| Sleep duration off by 1-2 minutes | Rounding (we round to 0.01h, iPhone rounds to whole minutes) | Working as intended |

## File map (for AI assistants making changes)

```
apple-health-dashboard/
├── server.ts                      # all backend logic — ingest, SQLite, API routes, graceful shutdown
├── public/index.html              # the entire dashboard — inline CSS, inline JS, no build step
├── Dockerfile                     # bun:latest, COPY server.ts, expose 8880
├── docker-compose.example.yml     # users copy this to docker-compose.yml and customize
├── .gitignore                     # prevents data/ from being committed
├── data/.gitkeep                  # placeholder; SQLite + JSONL go here at runtime
├── docs/screenshots/              # PNG screenshots embedded in README
├── README.md                      # human-facing
└── INSTALL.md                     # this file (AI-facing)
```

The two files you'll actually edit are `server.ts` and `public/index.html`. Everything else is config.

## When in doubt

- Re-read the README's architecture diagram. The flow is always: iPhone HAE → POST `/health` → SQLite → GET `/api/*` → browser.
- Every new endpoint goes in `server.ts` near the bottom (`if (url.pathname === "/api/...")`).
- Every new chart goes in `public/index.html` — find a `render*()` pattern that matches your need.
- The `aggregateSleepForDate(day)` helper is the single source of truth for per-day sleep totals. Don't bypass it; HAE's data is messier than you'd think.