Ulas 6a860f2c13 feat(calendar): expand recurring events in GET /calendar and /upcoming
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>
2026-03-31 10:17:39 +02:00
2026-03-24 13:46:15 +01:00

Oikos

Oikos

Self-hosted family planner — tasks, calendars, shopping, meals, budget.
Your data stays on your server. No subscriptions. No tracking. No cloud lock-in.

MIT License Node.js ≥20 Docker Ready SQLCipher Encrypted PWA Offline GitHub Stars Last Commit

Features · Quick Start · Configuration · Calendar Sync · Security


Oikos is a self-hosted family organizer for 26 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

Dashboard

Tasks
Tasks
Shopping
Shopping
Budget
Budget
Notes
Notes
Contacts
Contacts
Dashboard Mobile
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 (MonSun) · 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

Express SQLite Vanilla JS Docker PWA

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 23 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.example for a production-ready config. If you use Nginx Proxy Manager, paste the contents into the "Advanced" tab. Make sure X-Forwarded-Proto is 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

  1. Create a project at console.cloud.google.com
  2. Enable the Google Calendar API
  3. Create an OAuth 2.0 Client ID (type: Web application)
  4. Add your redirect URI:
    https://your-domain.com/api/v1/calendar/google/callback
    
  5. Add credentials to .env:
    GOOGLE_CLIENT_ID=...
    GOOGLE_CLIENT_SECRET=...
    GOOGLE_REDIRECT_URI=https://your-domain.com/api/v1/calendar/google/callback
    
  6. Restart: docker compose up -d
  7. 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)

  1. Go to appleid.apple.com → Sign-In and Security → App-Specific Passwords
  2. Generate a new password for "Oikos"
  3. Add to .env:
    APPLE_CALDAV_URL=https://caldav.icloud.com
    APPLE_USERNAME=your@apple-id.com
    APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx
    
  4. 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.

  • SessionshttpOnly, SameSite=Strict, Secure in 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

S
Description
Friborg-maintained Oikos core mirror/branch stack for upstreamable modular home-planning work
Readme 112 MiB
Languages
JavaScript 76.9%
CSS 17.5%
HTML 4.9%
Shell 0.7%