Skip to content

the-kizz/homekeep

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

448 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

HomeKeep

A calm, self-hosted household maintenance PWA for couples and families. Every recurring chore has a frequency, and HomeKeep spreads the year's work evenly across weeks so nothing piles up and nothing rots.

HomeKeep dashboard — three-band view with load-smoothed horizon + shift badges

Latest release CI AGPL v3 License Next.js 16 PocketBase 0.37 Docker multi-arch


What it is

A small weekend project that grew into a full v1. Existing task apps (Apple Reminders, Todoist) treat a task due in 365 days the same as one due today — everything lives in the same list, so you either ignore it or get overwhelmed. HomeKeep separates what's due now from what's coming eventually, and turns the whole year of home maintenance into a steady rhythm instead of a guilt pile.

Built for people who self-host things and want ownership of their data. AGPL v3, public repo, no cloud dependencies, no telemetry, no paid APIs.

Guiding principles

  1. Calm over urgent. Reduces anxiety, not creates it. No red badges on things that aren't actually overdue.
  2. Shared, not competitive. Streaks and progress are "us vs. the house," never partner-vs-partner.
  3. Forgiveness built in. Miss a week? The app redistributes, doesn't scold.

What's new in v1.2.1 — Self-Host UX polish

Lightweight patch on top of v1.2.0. Addresses four small-but-noisy issues a fresh live-smoke test against a deployed instance turned up:

  • Tap-to-view, not tap-to-complete. Tapping a task row now opens the detail sheet. Completion lives behind the sheet's Complete button. iOS-style tap-to-view — fewer accidental completions, and "Reschedule" / "Edit" / "Archive" are right there.
  • "Keep me signed in for 14 days" checkbox on the login form. Default checked (14-day persistent cookie, prior behavior). Unchecked = session cookie that drops on browser close — useful on shared devices.
  • No more warning on brand-new tasks. The early-completion guard used to fire on any completion of a task created less than 25% of its frequency ago — so "I just added this 5 minutes ago and I'm doing it today" always triggered a confirm. Now the guard only protects against genuine double-tap / re-complete accidents.
  • Configurable password strength. New PASSWORD_POLICY=simple|strong env var. Default simple = 8-char floor everywhere (right for LAN / Tailscale / single-household self-hosts). Public-facing operators set strong for a 12-char floor on signup + reset. Login always accepts 8+ so pre-flip accounts never get locked out. See docs/deployment-hardening.md item 12b.

Plus: fixed a silent Secure cookie bug on LAN-HTTP deploys (signup succeeded but every authed page bounced to login), routed Next.js API paths correctly behind the built-in Caddy edge, and removed a dead "Learn more" link that was spamming 9+ console 404s per HTTP session.

What's new in v1.2 — Security milestone

No new user-facing features; every change tightens the attack surface or the operator-facing security posture. Ship-safe for public internet exposure (with docs/deployment-hardening.md).

  • Cosign-signed images. Every ghcr.io/the-kizz/homekeep:* tag is signed via GitHub OIDC keyless. No long-lived keys; the signature is bound to the exact workflow that built it. Verify with:
    cosign verify ghcr.io/the-kizz/homekeep:latest \
      --certificate-identity-regexp '^https://github.com/the-kizz/homekeep/.github/workflows/release.yml@.+' \
      --certificate-oidc-issuer https://token.actions.githubusercontent.com
  • SBOM + SLSA-3 provenance. Every image has a SPDX SBOM (currently ~552 packages) and a SLSA-3 provenance attestation embedded as OCI attestations. Inspect with docker buildx imagetools inspect ghcr.io/the-kizz/homekeep:latest.
  • Security headers + CSP. CSP-Report-Only during the soak window, HSTS on HTTPS deploys, X-Frame DENY, X-Content-Type nosniff, strict-origin-when-cross-origin referrer, permissions-policy all-off. Served at both Next.js (next.config.ts) and Caddy (docker/Caddyfile*) layers — strip-safe.
  • PocketBase admin blocked at the edge. /_/*, /api/_superusers, and related paths return 404 through Caddy by default. Operators with admin need a temporary ALLOW_PUBLIC_ADMIN_UI=true flip or a docker exec into the container.
  • Row quotas + rate limits. MAX_HOMES_PER_OWNER (5), MAX_TASKS_PER_HOME (500 active), MAX_AREAS_PER_HOME (10). Signup capped 10/60s per IP; password-reset 5/60s; auth-with-password 20/60s; invite-accept 5/60s per IP with a 3-strike per-token lockout.
  • SECURITY.md + disclosure process. Scope, safe-harbor, 7-day ack / 90-day fix SLA, docs/deployment-hardening.md checklist (15+ items).

See docs/deployment-hardening.md for the operator checklist before public exposure.

What's new in v1.1 — Scheduling & Flexibility

The big idea: spread the year's work evenly across weeks. v1.0 kept tasks separate by band; v1.1 makes the app actively smooth household load so you don't get six annual tasks all landing on the same Saturday.

Load-smoothed horizon

A per-month density tint on the Horizon strip shows you, at a glance, which months are heavy and which are light. Any task that the smoother shifted off its natural date wears a ⚖️ badge — tap the task to see Ideal: Apr 25 / Scheduled: Apr 28 / Shifted by 3 days. Nothing silent, nothing magic.

Dashboard with density tint + shift badges

Task detail sheet — Ideal vs Scheduled + shift explanation

Reschedule any task from any view

"Just this time" writes a one-off snooze that auto-consumes when you next complete the task. "From now on" permanently shifts the task's anchor / smoothed date and is preserved across future rebalances (it won't undo your intent).

Reschedule action sheet with date picker + just-this-time/from-now-on

One-off tasks

Not everything is recurring. Toggle the form to One-off for single-shot tasks with an explicit "Do by" date — they auto-archive on completion, contribute to the load map while they're pending, and never clog the cycle tasks list.

One-off toggle with Do by date

Seasonal tasks

Set active months on any task (e.g. October → March for winter tasks). Out of season, tasks render dimmed with "Sleeps until Mar 2027" — they don't nag, don't drag the coverage ring, and wake up automatically on the first day of their window.

Task form with Advanced section — Last done + Active months

Manual rebalance

When life drifts and your schedule gets lumpy, Settings → Scheduling → Rebalance schedule. Preview first ("Will update: 16 / Will preserve: 1 anchored") — Apply re-places everything eligible while respecting anchored tasks, active snoozes, and "From now on" intent.

Settings — Scheduling with Rebalance preview dialog

The rest of v1.0 still here

By Area, Person, History, coverage ring, early-completion guard, cascading assignment, seed library (now with 4 seasonal pairs), ntfy notifications, mobile PWA. Nothing removed. v1.0 data migrates forward with zero changes; anchored-mode tasks are byte-identical.

By Area with dormant tasks dimmed Mobile dashboard — density tiers + shift badges

What's in the box

  • Three-band dashboard (Overdue / This Week / Horizon) + household coverage ring
  • Load-smoothed scheduling (v1.1) — placeNextDue algorithm with min(0.15 × frequency, 5) tolerance window, forward-only, deterministic tiebreakers, <100ms budget for 100-task households
  • Six-branch computeNextDue: archived → override → smoothed → seasonal-dormant → seasonal-wakeup → one-off → cycle/anchored
  • Cycle vs. anchored scheduling per task (cleaning benches resets the cycle; annual smoke alarm test sticks to its fixed calendar — anchored bypasses smoothing by design)
  • One-off tasks (v1.1) with explicit due date, atomic archive-on-completion
  • Seasonal dormancy (v1.1) with cross-year-wrap support (Oct→Mar windows) and coverage-ring exclusion
  • Snooze + permanent reschedule (v1.1) — action sheet from any task row; history-preserving schedule_overrides collection; cross-season snooze warns with ExtendWindowDialog
  • Preferred days (v1.1) — optional any / weekend / weekday hard constraint; smoother narrows candidates before scoring by load
  • Manual rebalance (v1.1) — counts-only preview + 4-bucket preservation (anchored / active-snooze / from-now-on / rebalanceable); idempotent after first run
  • Early-completion guard — prompts "Are you sure?" under 25% of cycle
  • Collaboration — invite links, member management, cascading assignment (task-level > area-default > "Anyone")
  • First-run onboarding wizard — ~30 seed tasks including 4 seasonal pairs (warm/cool mowing, summer/winter HVAC)
  • Gentle gamification — household streak, per-area coverage %, area-100% celebration, "most neglected" nudge
  • Push notifications via ntfy — overdue, newly-assigned, partner-completed (opt-in), weekly summary (opt-in)
  • Installable PWA on HTTPS deployments; graceful HTTP degradation on LAN-only
  • Append-only completion history — nothing is ever deleted; dormant-task completions still show in History regardless of current season state

Stack

  • Next.js 16 (App Router, Server Components, Server Actions) + React 19
  • PocketBase 0.37 (SQLite + migrations-as-code + JSVM hooks) — single binary, lives in the same container
  • Tailwind 4 + shadcn/ui — soft neutrals, one warm accent (#D4A574 terracotta-sand)
  • Zod + react-hook-form, @dnd-kit for drag-to-reorder
  • node-cron for the hourly scheduler, ntfy for push
  • s6-overlay supervises Caddy + PocketBase + Next.js inside one container
  • Vitest (unit) + Playwright (E2E); 678 unit + 24 E2E tests (v1.2.1)
  • Cosign keyless image signing + SPDX SBOM + SLSA-3 provenance on every GHCR push

Quickstart

Option 1 — docker run (fastest)

docker run -d -p 3000:3000 \
  -v homekeep_data:/app/data \
  -e SITE_URL=http://localhost:3000 \
  -e NTFY_URL=https://ntfy.sh \
  --name homekeep --restart unless-stopped \
  ghcr.io/the-kizz/homekeep:latest

Open http://localhost:3000, sign up, create a home. The PocketBase admin UI lives at /_/ — on first boot check the container logs for an installer link.

Image tags (pick your channel)

Tag What it is Good for
:latest Most recent stable release. No RCs, no betas. Default — what you want unless you know otherwise.
:rc Most recent release candidate. Moving pointer. Early-access; might break.
:edge Auto-built from every push to master. Bleeding edge. Trying the unreleased HEAD; expect breakage.
:1 Latest patch of major version 1. Auto-updates within 1.x.x. Conservative auto-updates (bugfixes only).
:1.0 Latest patch of 1.0.x. Never crosses minor. Very conservative — only 1.0.<next> patches.
:1.0.0, :1.0.0-rc1, … Exact version pin. Never changes. Production; reproducibility.

Model borrowed from Plex, Nextcloud, Grafana, Postgres. :latest is stable, not nightly.

Option 2 — docker compose up (LAN)

Drop this file anywhere as docker-compose.yml — no clone required:

services:
  homekeep:
    image: ghcr.io/the-kizz/homekeep:latest
    container_name: homekeep
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - homekeep_data:/app/data
    environment:
      SITE_URL: http://localhost:3000
      NTFY_URL: https://ntfy.sh
      TZ: Australia/Perth
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/api/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s

volumes:
  homekeep_data:

Then docker compose up -d. Data lives in the homekeep_data named volume — survives restarts and upgrades.

Or clone the repo if you want the full LAN/Caddy/Tailscale overlay set:

git clone https://github.com/the-kizz/homekeep.git
cd homekeep
cp .env.example docker/.env   # edit as needed
docker compose -f docker/docker-compose.yml up -d

Option 3 — HTTPS via Caddy

Point a domain at your server and:

export DOMAIN=homekeep.example.com
docker compose \
  -f docker/docker-compose.yml \
  -f docker/docker-compose.caddy.yml \
  up -d

Caddy handles TLS automatically via Let's Encrypt. See docs/deployment.md for tuning, or docker/docker-compose.tailscale.yml for the Tailscale-funnel variant.

Environment variables

Minimum:

Var Required Default Notes
SITE_URL yes Absolute URL (e.g. https://homekeep.example.com). Used in invite links.
NTFY_URL no https://ntfy.sh Override for self-hosted ntfy.
PB_ADMIN_EMAIL yes for invites PocketBase superuser — the invite-acceptance path needs admin context.
PB_ADMIN_PASSWORD yes for invites Paired with above. Create with docker exec <container> pocketbase superuser upsert <email> <pass>.
ADMIN_SCHEDULER_TOKEN no 32+ char string. Lets you manually trigger the scheduler via POST /api/admin/run-scheduler.
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS no Enables password-reset + (future) email notifications. If unset, password reset no-ops gracefully.
HOST_PORT no 3000 Host-side port when using docker-compose.
TZ no Etc/UTC Host timezone. Per-home timezone still comes from the home record.

Full reference in .env.example.

Architecture

Single Docker image. Inside the container:

 ┌─────────────────────────────────────────────┐
 │  s6-overlay (PID 1)                         │
 │  ├── Caddy :3000   (path-based router)      │
 │  │     ├─  /api/health → Next.js            │
 │  │     ├─  /api/* + /_/* → PocketBase       │
 │  │     └─  everything else → Next.js        │
 │  ├── Next.js :3001 (standalone, loopback)   │
 │  └── PocketBase :8090 (loopback)            │
 │        ├─ /app/pb_migrations (schema)       │
 │        └─ /app/pb_hooks (JSVM lifecycle)    │
 │  /app/data → persistent volume               │
 └─────────────────────────────────────────────┘

Read more in docs/deployment.md and the per-phase summaries in .planning/phases/.

Development

npm install
npm run dev          # Next.js + PocketBase side-by-side
npm test             # Vitest
npm run test:e2e     # Playwright (boots a disposable PB)
npm run build        # production build (uses webpack for Serwist)
npm run lint && npm run type-check

PocketBase runs as a local binary under ./.pb/pocketbase via scripts/dev-pb.js. Migrations live in pocketbase/pb_migrations/, hooks in pocketbase/pb_hooks/.

Project status

v1.0.0-rc1. All 7 planned phases shipped:

Phase What it delivered
1 Docker + Next + PocketBase + Caddy + s6 + multi-arch CI
2 Signup, homes, areas, tasks, computed next-due
3 Three-band dashboard + one-tap complete + early-completion guard + coverage ring
4 Invite links + members + cascading assignment
5 By Area / Person / History views + onboarding wizard
6 ntfy notifications + scheduler + streaks + celebrations
7 PWA manifest + service worker + HTTP banner + Caddy/Tailscale compose overlays

Decimal phases (2.1, 3.1, …) are deploy checkpoints — build the image and stand it up on the VPS between features so you can actually look at the thing.

Known limits

  • Password-reset emails only work if you configure SMTP.
  • PWA install prompts only appear on HTTPS deployments (browser restriction — that's why the HTTP banner exists).
  • Multi-instance deploys aren't supported yet — the scheduler assumes one container.
  • Offline-writes is not in v1. You can read cached pages offline, but edits require a connection.

Contributing

This is a small fun project, not a startup. PRs welcome — keep it calm and read CONTRIBUTING.md first. Open an issue before any big change so we don't duplicate effort.

If the app helps you keep your house, let me know — no tracking, no analytics, so the only feedback loop I have is people telling me.

License

AGPL v3. Self-host freely. Modify freely. If you run a modified version of HomeKeep as a public service, the AGPL asks that you publish your modifications so users of that service can see what's running. Same spirit as the rest of the project: transparent, self-hostable, yours to change — just keep the changes visible to the people you serve.

Security

Found a vulnerability? See SECURITY.md for our threat model, disclosure policy, and response SLA. Operators running HomeKeep on a public domain should also work through docs/deployment-hardening.md before cut-over.

Provenance

Every HomeKeep build ships a small, static, zero-telemetry JSON probe at /.well-known/homekeep.json. It returns the app name, source repo URL, license, and the unique build UUID baked into that image at Docker build time. No phone-home, no analytics — the endpoint only responds to a request made directly to the serving host.

curl -fsS https://your-homekeep.example.com/.well-known/homekeep.json
# => {"app":"HomeKeep","repo":"https://github.com/the-kizz/homekeep","license":"AGPL-3.0-or-later","build":"hk-<uuid>"}

If you ever come across HomeKeep running on a domain that isn't yours or mine, that endpoint is the fastest way to see where it was built and to verify the AGPL license declaration. A build value of hk-dev-local means someone rebuilt the image without passing the HK_BUILD_ID build-arg — a signal (not proof) that the deploy isn't from an official release.

Credits

  • Written during a few long evenings with Claude Code driving the build (the whole thing was scaffolded, researched, planned, and implemented via GSD — see .planning/ for the phase-by-phase paper trail).
  • Inspired by every task app that nagged me about annual gutter cleaning in July.
  • Thanks to PocketBase, ntfy, shadcn/ui, and s6-overlay for doing the heavy lifting.

About

A calm, self-hosted household maintenance PWA — couples and families track recurring chores without the guilt pile. Next.js + PocketBase + Docker, one image, AGPL-3.0.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors