expandRecurringEvents() iterates from the event's original start date, generating all occurrences within the requested window using the existing nextOccurrence() service (max 1000 iterations). The SQL query is extended to also fetch recurring events that started before the window. Event duration is preserved across instances. Virtual instances carry is_recurring_instance=1 and are shown with a repeat icon in the agenda view. /upcoming expands across a 90-day forward window. Closes BL-01. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Oikos
Self-hosted family planner — tasks, calendars, shopping, meals, budget.
Your data stays on your server. No subscriptions. No tracking. No cloud lock-in.
Features · Quick Start · Configuration · Calendar Sync · Security
Oikos is a self-hosted family organizer for 2–6 people. Tasks, calendars, shopping lists, meal plans, budget tracking, notes, and contacts — all running on your own server inside a single Docker container. No cloud dependency, no telemetry, no data leaves your network.
Built with Express.js, SQLite (optionally encrypted via SQLCipher), and vanilla JavaScript — no frontend framework, no build step. Works offline as a PWA on phones and tablets.
Oikos is not a SaaS product, not a team collaboration tool, and not designed for public multi-tenant use. It is a private tool for one family on one server.
Screenshots
Tasks |
Shopping |
Budget |
Notes |
Contacts |
Dashboard — Mobile |
Screenshots adapt to your GitHub theme — switch between light and dark mode to see both variants.
Features
| Module | What it does | Highlights | |
|---|---|---|---|
| 📋 | Dashboard | At-a-glance overview of your family's day | Weather widget · upcoming events · urgent tasks · today's meals · pinned notes |
| ✅ | Tasks | Shared to-do lists with accountability | List + Kanban views · subtasks · recurring tasks (RRULE) · swipe gestures · priority levels |
| 🛒 | Shopping | Collaborative grocery lists | Multiple lists · aisle-grouped categories · auto-import from meal plan |
| 🍽️ | Meals | Weekly meal planning with ingredients | Week view (Mon–Sun) · ingredient management · one-click export to shopping list |
| 📅 | Calendar | Family calendar with external sync | Month/week/day/agenda views · Google Calendar & Apple iCloud two-way sync |
| 📌 | Notes | Shared family pinboard | Colored sticky notes · pinning · lightweight Markdown (bold, italic, lists) |
| 👥 | Contacts | Important family contacts | Category filters · tap-to-call · tap-to-email · map links for addresses |
| 💰 | Budget | Income & expense tracking | Category breakdown · month-over-month comparison · CSV export |
| ⚙️ | Settings | User & sync management | Password changes · calendar sync config · family member admin |
Tech Stack
| Layer | Technology |
|---|---|
| Server | Node.js ≥ 20 · Express · better-sqlite3 · bcrypt · Helmet |
| Database | SQLite with optional SQLCipher encryption (AES-256) |
| Frontend | Vanilla JavaScript ES modules — no framework, no build step. Web Components (oikos-*). Lucide Icons (self-hosted SVG sprite) |
| Auth | Session-based · httpOnly cookies · CSRF double-submit · express-session |
| Deployment | Docker + Docker Compose · Nginx reverse proxy · Let's Encrypt SSL |
| Integrations | Google Calendar API v3 (OAuth 2.0) · Apple iCloud CalDAV (tsdav) · OpenWeatherMap |
Quick Start
Prerequisites: Docker + Docker Compose on a Linux server.
1. Clone
git clone https://github.com/ulsklyc/oikos.git
cd oikos
2. Configure
cp .env.example .env
Edit .env and set the two required variables:
SESSION_SECRET=your-random-string-at-least-32-chars
DB_ENCRYPTION_KEY=your-sqlcipher-aes256-key
3. Start
docker compose up -d
First build takes 2–3 minutes (compiles SQLCipher against better-sqlite3).
4. Create admin account
docker compose exec oikos node setup.js
Interactive script — sets up username, display name, and password. This admin can create additional family members from the settings page.
5. Open
Navigate to http://localhost:3000 — or your configured domain after Nginx setup.
Nginx: See
nginx.conf.examplefor a production-ready config. If you use Nginx Proxy Manager, paste the contents into the "Advanced" tab. Make sureX-Forwarded-Protois set so session cookies work correctly in production.
Configuration
Required
| Variable | Description |
|---|---|
SESSION_SECRET |
Random string ≥ 32 characters for session signing |
DB_ENCRYPTION_KEY |
SQLCipher AES-256 key. Leave empty to disable encryption |
Optional
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
NODE_ENV |
development |
Set to production for deployment |
DB_PATH |
./oikos.db |
Path to SQLite database file |
SYNC_INTERVAL_MINUTES |
15 |
Automatic calendar sync interval |
RATE_LIMIT_MAX_ATTEMPTS |
5 |
Max login attempts per minute per IP |
Weather Widget
Register a free API key at openweathermap.org:
| Variable | Default | Description |
|---|---|---|
OPENWEATHER_API_KEY |
— | Your API key |
OPENWEATHER_CITY |
Berlin |
City name |
OPENWEATHER_UNITS |
metric |
metric (°C) or imperial (°F) |
OPENWEATHER_LANG |
de |
Language code |
Integrations
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID |
Google OAuth 2.0 Client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth 2.0 Client Secret |
GOOGLE_REDIRECT_URI |
https://your-domain/api/v1/calendar/google/callback |
APPLE_CALDAV_URL |
https://caldav.icloud.com |
APPLE_USERNAME |
Your Apple ID email |
APPLE_APP_SPECIFIC_PASSWORD |
App-specific password from Apple ID settings |
Full template: .env.example
Calendar Sync
Oikos syncs bidirectionally with Google Calendar and Apple iCloud. External events are visually distinguished in the UI. On conflict, the external source wins.
Google Calendar
- Create a project at console.cloud.google.com
- Enable the Google Calendar API
- Create an OAuth 2.0 Client ID (type: Web application)
- Add your redirect URI:
https://your-domain.com/api/v1/calendar/google/callback - Add credentials to
.env:GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... GOOGLE_REDIRECT_URI=https://your-domain.com/api/v1/calendar/google/callback - Restart:
docker compose up -d - In Oikos: Settings → Calendar Sync → Connect Google
Sync behavior: Initial sync pulls events from 3 months ago to 12 months ahead. Subsequent syncs use Google's syncToken for incremental updates. Local events push to Google automatically. Conflicts: Google wins on simultaneous edits.
Apple Calendar (iCloud CalDAV)
- Go to appleid.apple.com → Sign-In and Security → App-Specific Passwords
- Generate a new password for "Oikos"
- Add to
.env:APPLE_CALDAV_URL=https://caldav.icloud.com APPLE_USERNAME=your@apple-id.com APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx - Restart:
docker compose up -d
The sync button appears automatically in Settings.
Security
Oikos is designed to run on a private server behind SSL. No public endpoints exist except the login page.
- Sessions —
httpOnly,SameSite=Strict,Securein production, 7-day TTL - CSRF — Double-submit cookie pattern on all state-changing requests
- Passwords — bcrypt with cost factor 12
- Rate limiting — 5 login attempts/min, 300 API requests/min per IP
- Headers — Strict Content Security Policy via Helmet (
self-only) - Encryption — Optional SQLCipher AES-256 database encryption at rest
- Access control — No API endpoint accessible without session auth (except
/api/v1/auth/login) - No public registration — Only admins can create user accounts
Development
Local Setup
npm install
cp .env.example .env
# Set SESSION_SECRET — skip DB_ENCRYPTION_KEY (no SQLCipher needed locally)
npm run dev # Starts server with --watch (auto-reload)
Tests
npm test # 146 tests across 7 suites
Tests use Node.js built-in test runner with --experimental-sqlite for in-memory SQLite. No running server required.
Architecture
server/
index.js # Express entry, middleware, static serving
db.js # SQLite connection, migration runner
auth.js # Session auth + user management routes
routes/ # One file per module
services/ # Calendar sync, recurrence engine
public/
index.html # SPA shell
router.js # History API router (~50 lines)
api.js # Fetch wrapper with auth + CSRF
styles/ # Design tokens, reset, layout, per-module CSS
components/ # Web Components (oikos-* prefix)
pages/ # Page modules with render() export
sw.js # Service worker
Request flow: Client → Express static or /api/v1/* → session auth middleware → route handler → better-sqlite3 (sync) → JSON response.
Database migrations run automatically on startup. Each migration is an idempotent SQL block in server/db.js. Append new migrations — never modify existing ones.
Backup & Restore
Backup
docker run --rm \
-v oikos_oikos_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/oikos-backup-$(date +%Y%m%d).tar.gz /data
Restore
docker compose down
docker run --rm \
-v oikos_oikos_data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/oikos-backup-YYYYMMDD.tar.gz -C /
docker compose up -d
Database migrations run automatically on startup. Data in the oikos_data volume is preserved across container rebuilds.
Updates & Family Members
Updating Oikos
git pull
docker compose up -d --build
Migrations run automatically. Your data volume stays intact.
Adding Family Members
Only admins can create new accounts — there is no public registration endpoint.
In the browser: Settings → Family Members → Add Member
Via CLI: docker compose exec oikos node setup.js
Contributing
Contributions are welcome. If you find a bug or have a feature idea, open an issue. Pull requests are appreciated — please keep the vanilla JS constraint in mind (no frameworks, no build tools).
A CONTRIBUTING.md with detailed guidelines is coming soon.
License
MIT © 2025 ulsklyc
Made with ☕ by ulsklyc






