Skip to content

Commit 81c0e4f

Browse files
egebeseclaude
andcommitted
Initial open-source release
Self-hosted personal life tracker: workouts, nutrition, Whoop sync, AI photo calories, AI diet planner, weekly insights. All AI features run through a single fal.ai key (openrouter/router + wizper + fal.storage). Next.js 15, Postgres 16, Drizzle, iron-session, MIT. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 parents  commit 81c0e4f

147 files changed

Lines changed: 24006 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
node_modules
2+
.next
3+
.git
4+
.env
5+
.env.*
6+
!.env.example
7+
.DS_Store
8+
*.log
9+
uploads
10+
/data

.env.example

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# ============================================================================
2+
# LifeOS — environment configuration
3+
# ============================================================================
4+
# Copy this file to `.env` and fill in the values. `.env` is gitignored.
5+
#
6+
# Required for the app to boot: DATABASE_URL, SESSION_SECRET, ADMIN_EMAIL,
7+
# ADMIN_PASSWORD
8+
# Required for any AI feature: FAL_KEY
9+
# Optional: everything else
10+
# ============================================================================
11+
12+
13+
# --- Database --------------------------------------------------------------
14+
# Postgres 16 connection string. The bundled docker-compose.yml spins up a
15+
# local instance at the URL below.
16+
DATABASE_URL=postgres://lifetracker:lifetracker@localhost:5432/lifetracker
17+
18+
19+
# --- Auth ------------------------------------------------------------------
20+
# 64-byte random base64 string used by iron-session to seal the cookie.
21+
# Generate one with: openssl rand -base64 64
22+
SESSION_SECRET=replace_with_openssl_rand_base64_64
23+
24+
# Bootstraps the single admin account on first boot.
25+
ADMIN_EMAIL=you@example.com
26+
27+
# First-boot password ONLY. Change it from /profile after logging in.
28+
ADMIN_PASSWORD=changeme
29+
30+
31+
# --- AI (fal.ai) -----------------------------------------------------------
32+
# Every AI feature in LifeOS — food photo vision, meal parsing, voice
33+
# transcription, meal planner, workout program generator, weekly insights,
34+
# and file storage — runs through this single key.
35+
#
36+
# Get one at: https://fal.ai/dashboard/keys
37+
# Top up credits at: https://fal.ai/dashboard/billing
38+
#
39+
# Treat this like a billing credential: anyone with the key can spend your
40+
# fal credits.
41+
FAL_KEY=
42+
43+
44+
# --- Whoop OAuth (optional) ------------------------------------------------
45+
# Register an app at https://developer.whoop.com to sync recovery, sleep,
46+
# strain, workouts, and body measurements. Leave blank to disable.
47+
WHOOP_CLIENT_ID=
48+
WHOOP_CLIENT_SECRET=
49+
WHOOP_REDIRECT_URI=http://localhost:3000/api/whoop/callback
50+
51+
# Optional — Whoop signs webhooks with the OAuth client_secret by default.
52+
# Only set this if you've configured a separate webhook secret in the portal.
53+
WHOOP_WEBHOOK_SECRET=
54+
55+
56+
# --- App -------------------------------------------------------------------
57+
# Public origin (used in OAuth callbacks and absolute links).
58+
NEXT_PUBLIC_APP_URL=http://localhost:3000
59+
60+
# Set to 1 to enable in-process cron jobs (Whoop daily safety-net sync).
61+
# Leave 0 if you run sync from an external scheduler instead.
62+
ENABLE_CRON=0
63+
64+
# IANA timezone. Affects daily rollovers (e.g. "kcal today").
65+
TZ=Europe/Istanbul
66+
67+
NODE_ENV=development
68+
69+
70+
# --- Uploads ---------------------------------------------------------------
71+
# Local filesystem path for uploaded photos before they go to fal.storage.
72+
# In Docker this is overridden to /data/uploads (mounted as a volume).
73+
UPLOADS_DIR=./uploads
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
name: Bug report
3+
about: Something is broken
4+
title: "[bug] "
5+
labels: bug
6+
---
7+
8+
## What happened
9+
10+
<!-- One sentence. -->
11+
12+
## Steps to reproduce
13+
14+
1.
15+
2.
16+
3.
17+
18+
## Expected
19+
20+
## Actual
21+
22+
## Environment
23+
24+
- LifeOS commit SHA:
25+
- Deploy target: <!-- local dev / docker compose / Coolify / other -->
26+
- Node version: `node --version`
27+
- Browser (if UI bug):
28+
29+
## Logs
30+
31+
<!--
32+
Paste relevant logs (`docker compose logs web` or browser console).
33+
REDACT FAL_KEY, session cookies, DB connection strings, and any personal data.
34+
-->
35+
36+
```
37+
```
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
name: Feature request
3+
about: Suggest an enhancement
4+
title: "[feat] "
5+
labels: enhancement
6+
---
7+
8+
## Problem
9+
10+
<!-- What can't you do today, or what's painful? -->
11+
12+
## Proposed solution
13+
14+
<!-- Optional — what would you like to see? -->
15+
16+
## Alternatives considered
17+
18+
## Scope check
19+
20+
LifeOS is intentionally single-admin and fal.ai-only. Please confirm:
21+
22+
- [ ] This does not require multi-tenant auth
23+
- [ ] This does not require replacing fal.ai as the AI provider
24+
- [ ] This is reasonable for a self-hosted single-user app
25+
26+
(See [CONTRIBUTING.md → Scope](../../CONTRIBUTING.md) if unsure.)

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!--
2+
Thanks for sending a PR! Please read CONTRIBUTING.md if you haven't.
3+
-->
4+
5+
## What
6+
7+
<!-- One-line summary of the change. -->
8+
9+
## Why
10+
11+
<!-- Linked issue, or a sentence on the problem this solves. -->
12+
13+
## How tested
14+
15+
<!--
16+
- For UI: screenshots / screen recording at 375px and 1280px.
17+
- For API: curl example or test output.
18+
- For AI prompt changes: before/after example response + rough cost delta.
19+
-->
20+
21+
## Checklist
22+
23+
- [ ] `pnpm lint` passes
24+
- [ ] `pnpm typecheck` passes
25+
- [ ] `pnpm build` passes
26+
- [ ] Updated `.env.example` if I added/changed an env var
27+
- [ ] Updated `README.md` / docs if user-facing behaviour changed
28+
- [ ] No `FAL_KEY` / secrets / personal data in commits or screenshots

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: CI
2+
3+
# This workflow only runs lint/typecheck/build. It does not handle any
4+
# untrusted input from issues, PRs, or commits (no github.event.*.title /
5+
# .body / commit messages used in run: blocks), so it is not vulnerable to
6+
# workflow injection.
7+
8+
on:
9+
push:
10+
branches: [main]
11+
pull_request:
12+
branches: [main]
13+
14+
jobs:
15+
lint-typecheck-build:
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 10
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- uses: pnpm/action-setup@v4
23+
with:
24+
version: 9.15.2
25+
26+
- uses: actions/setup-node@v4
27+
with:
28+
node-version: 20
29+
cache: pnpm
30+
31+
- run: pnpm install --frozen-lockfile
32+
33+
- run: pnpm lint
34+
35+
- run: pnpm typecheck
36+
37+
- name: Build
38+
run: pnpm build
39+
env:
40+
DATABASE_URL: postgres://placeholder:placeholder@localhost:5432/placeholder
41+
SESSION_SECRET: ci_placeholder_secret_thats_at_least_64_bytes_long_for_iron_session_xx
42+
FAL_KEY: placeholder
43+
NEXT_TELEMETRY_DISABLED: "1"

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
node_modules/
2+
.next/
3+
.turbo/
4+
out/
5+
dist/
6+
*.tsbuildinfo
7+
8+
.env
9+
.env.local
10+
.env.production
11+
.env.*.local
12+
13+
*.log
14+
.DS_Store
15+
16+
uploads/
17+
/data/
18+
.pnpm-store/
19+
20+
# Exercise media (synced from hasaneyldrm/exercises-dataset via pnpm sync:exercises)
21+
/public/exercises/
22+
23+
# IDE
24+
.vscode/
25+
.idea/

CONTRIBUTING.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Contributing to LifeOS
2+
3+
Thanks for your interest! LifeOS is a self-hosted, single-admin personal tracker — small in scope on purpose. PRs and issues are welcome; please read this first to save us both time.
4+
5+
## Scope — what's in / what's out
6+
7+
**In scope** (PRs likely to be accepted):
8+
9+
- Bug fixes (auth, sync, parsing, UI regressions)
10+
- Performance & a11y improvements
11+
- New exercise dataset translations / corrections
12+
- New `lib/ai/prompts.ts` improvements (better food/plan/program prompts)
13+
- Documentation, screenshots, deployment guides for other PaaS (Railway, Fly, Render, Hetzner Coolify, etc.)
14+
- Additional fal.ai endpoint integrations (e.g. swapping the default model, adding a new vision pipeline)
15+
- New chart types in `/analysis`
16+
- Whoop sync edge cases
17+
18+
**Out of scope** (please open an issue / discussion first; will likely be declined as a PR):
19+
20+
- Multi-tenant / multi-user auth — LifeOS is intentionally single-admin. If you need this, fork it.
21+
- Replacing fal.ai with another AI provider at the framework level. (Adding a *configurable* alternative behind the same `chat()` / `vision()` interface is fine; ripping fal out isn't.)
22+
- Switching the database away from Postgres.
23+
- Mobile native apps (iOS/Android shells). The PWA is intentionally web-first.
24+
- Major UI redesigns away from the Nothing-design aesthetic.
25+
26+
If unsure, **open an issue before writing code**. Saves rejection cycles.
27+
28+
## Dev setup
29+
30+
```bash
31+
git clone https://github.com/egebese/lifeos.git
32+
cd lifeos
33+
34+
cp .env.example .env
35+
# Fill in SESSION_SECRET, ADMIN_EMAIL, ADMIN_PASSWORD, FAL_KEY
36+
37+
docker compose up -d db
38+
pnpm install
39+
pnpm db:migrate
40+
pnpm bootstrap:admin
41+
pnpm seed:exercises
42+
pnpm dev
43+
```
44+
45+
You'll need a [fal.ai](https://fal.ai/dashboard/keys) key to test any AI feature. The free trial credits are enough for development — every call gets logged to the `ai_messages` table so you can audit usage.
46+
47+
## Code conventions
48+
49+
- **TypeScript strict.** No `any` unless absolutely necessary (and comment why).
50+
- **Server Components by default.** Drop to `"use client"` only when you need state, effects, or browser APIs.
51+
- **Drizzle for all DB access.** No raw SQL except in migrations.
52+
- **Zod schemas at API boundaries.** Look at `lib/ai/schemas.ts` for the pattern.
53+
- **No new env vars without a default + `.env.example` update.**
54+
- **AI calls go through `lib/ai/client.ts`.** Don't call `@fal-ai/client` directly from route handlers — the wrapper logs cost and errors to `ai_messages`.
55+
- **Mobile-first.** Test at 375px width before desktop. Use the existing `components/nothing/*` primitives where possible.
56+
57+
## Before you push
58+
59+
```bash
60+
pnpm lint
61+
pnpm typecheck
62+
pnpm build
63+
```
64+
65+
All three must pass. CI runs the same on PRs.
66+
67+
## Commit / PR style
68+
69+
- One concern per PR. A bug fix + a refactor + a new feature = three PRs.
70+
- Commit messages: imperative present tense — `add voice transcription endpoint`, not `added` / `adds`.
71+
- PR description: what changed, why, and **how you tested it** (screenshots for UI, curl/log snippets for API).
72+
- If you touched a fal.ai endpoint, mention the cost impact (rough $/call) in the PR.
73+
74+
## Reporting bugs
75+
76+
Open an issue with:
77+
78+
1. What you did (step-by-step).
79+
2. What you expected.
80+
3. What actually happened.
81+
4. Environment: Node version, deploy target (Docker/Coolify/local), browser if UI.
82+
5. Relevant logs — `docker compose logs web` is your friend.
83+
84+
**Do not include `FAL_KEY`, session cookies, or DB connection strings** in issues. Redact them.
85+
86+
## Reporting security issues
87+
88+
See [SECURITY.md](SECURITY.md). **Do not** open a public issue for security.
89+
90+
## License
91+
92+
By contributing you agree your contributions are MIT-licensed under the same terms as the rest of the project.

Dockerfile

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
### --- deps ---
2+
FROM node:20-alpine AS deps
3+
WORKDIR /app
4+
RUN apk add --no-cache libc6-compat
5+
COPY package.json pnpm-lock.yaml* ./
6+
RUN corepack enable && \
7+
(pnpm install --frozen-lockfile || pnpm install --no-frozen-lockfile)
8+
9+
### --- builder ---
10+
FROM node:20-alpine AS builder
11+
WORKDIR /app
12+
ENV NEXT_TELEMETRY_DISABLED=1
13+
COPY --from=deps /app/node_modules ./node_modules
14+
COPY . .
15+
RUN corepack enable && \
16+
pnpm drizzle-kit generate && \
17+
pnpm build
18+
19+
### --- runner ---
20+
FROM node:20-alpine AS runner
21+
WORKDIR /app
22+
ENV NODE_ENV=production
23+
ENV NEXT_TELEMETRY_DISABLED=1
24+
ENV PORT=3000
25+
ENV HOSTNAME=0.0.0.0
26+
27+
RUN apk add --no-cache bash && \
28+
addgroup -S nodejs && adduser -S nextjs -G nodejs && \
29+
mkdir -p /data/uploads && chown -R nextjs:nodejs /data
30+
31+
# Next.js standalone output
32+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
33+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
34+
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
35+
36+
# Migrations + setup scripts (we still need tsx at runtime → keep node_modules of scripts deps)
37+
COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle
38+
COPY --from=builder --chown=nextjs:nodejs /app/scripts ./scripts
39+
COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib
40+
COPY --from=builder --chown=nextjs:nodejs /app/drizzle.config.ts ./drizzle.config.ts
41+
COPY --from=builder --chown=nextjs:nodejs /app/tsconfig.json ./tsconfig.json
42+
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules-full
43+
44+
# Replace standalone's slim node_modules with the build's full one (needed for tsx-based scripts)
45+
RUN rm -rf node_modules && mv node_modules-full node_modules
46+
47+
ENV UPLOADS_DIR=/data/uploads
48+
49+
USER nextjs
50+
EXPOSE 3000
51+
52+
CMD ["sh", "-c", "node --import tsx scripts/migrate.ts && node --import tsx scripts/bootstrap-admin.ts && node --import tsx scripts/seed-exercises.ts && node --import tsx scripts/seed-templates.ts && node server.js"]

0 commit comments

Comments
 (0)