Kyle Pope 1e225145c9 Fix Todo search alignment: wrap CategoryFilterBar in flex-1 container
CategoryFilterBar's internal flex-1 spacer needs room to push search
right. Wrapping in flex-1 min-w-0 matches the Locations/People pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:25:41 +08:00
2026-02-15 21:04:42 +08:00

UMBRA

A self-hosted personal life administration app with a dark-themed UI. Manage your todos, calendar events, projects, reminders, contacts, and locations from a single dashboard.

Features

  • Dashboard - Contextual greeting, week timeline, stat cards, upcoming events, weather widget, day briefing
  • Todos - Task management with priorities, due dates, recurrence, and grouped sections (overdue/today/upcoming)
  • Calendar - Multi-calendar system with month/week/day views, recurring events, drag-and-drop, event templates
  • Projects - Project boards with kanban view, nested tasks/subtasks, comments, progress tracking
  • Reminders - Time-based reminders with snooze, dismiss, recurrence, and real-time alert notifications (dashboard banner + toasts)
  • People - Contact directory with avatar initials, favourites, birthday tracking, category filtering
  • Locations - Location management with OSM search integration, category filtering, frequent locations
  • Weather - Dashboard weather widget with temperature, conditions, and contextual rain warnings
  • Settings - Accent color picker (5 presets), first day of week, weather city, ntfy push notifications, TOTP two-factor auth, auto-lock, password management
  • Notifications - ntfy push notifications for reminders (configurable per-user)

Tech Stack

Layer Technology
Frontend React 18, TypeScript, Vite 6, Tailwind CSS 3
UI Components Custom shadcn/ui-style components, FullCalendar 6, Lucide icons, Sonner toasts
Fonts Sora (headings), DM Sans (body) via Google Fonts
State TanStack Query v5, React Router v6
Backend FastAPI, Python 3.12, Pydantic v2
Database PostgreSQL 16, SQLAlchemy 2.0 (async), Alembic (25 migrations)
Auth Username/password with Argon2id hashing, DB-backed sessions (signed cookies), optional TOTP MFA
Scheduler APScheduler (async) for ntfy notification dispatch
Deployment Docker Compose (3 services), Nginx reverse proxy

Quick Start

Prerequisites

Setup

  1. Clone the repository

    git clone https://your-gitea-instance/youruser/umbra.git
    cd umbra
    
  2. Configure environment variables

    cp .env.example .env
    

    Edit .env and set secure values (see Production Hardening below for generation commands):

    POSTGRES_USER=umbra
    POSTGRES_PASSWORD=your-secure-password
    POSTGRES_DB=umbra
    DATABASE_URL=postgresql+asyncpg://umbra:your-secure-password@db:5432/umbra
    SECRET_KEY=your-random-secret-key
    OPENWEATHERMAP_API_KEY=your-openweathermap-api-key
    

    Weather widget: The dashboard weather widget requires a free OpenWeatherMap API key. Set OPENWEATHERMAP_API_KEY in .env, then configure your city in Settings.

  3. Build and run

    docker-compose up --build
    
  4. Open the app

    Navigate to http://localhost in your browser. On first launch you'll be prompted to create a username and password.

Architecture

                    +-----------+
                    |  Browser  |
                    +-----+-----+
                          |
                    port 80 (HTTP)
                          |
                  +-------+-------+
                  |    Nginx      |
                  | (frontend)    |
                  | non-root:8080 |
                  +---+-------+---+
                      |       |
            static    |       |  /api/*
            files     |       |  (rate-limited auth)
                      v       v
                  +---+-------+---+
                  |   FastAPI     |
                  |  (backend)    |
                  |  non-root     |
                  +-------+-------+
                          |
                  +-------+-------+
                  |  PostgreSQL   |
                  |    (db)       |
                  |  port 5432    |
                  +---------------+
  • Frontend is built as static files and served by nginxinc/nginx-unprivileged. Nginx also reverse-proxies API requests to the backend with rate limiting on auth endpoints.
  • Backend runs Alembic migrations on startup as a non-root user (appuser), then serves the FastAPI application with --no-server-header.
  • Database uses a named Docker volume (postgres_data) for persistence.
  • Backend port 8000 is not exposed externally — only accessible via the internal Docker network.

Security

Hardened by default

  • Non-root containers — both backend (appuser:1000) and frontend (nginx-unprivileged) run as non-root
  • No external backend port — port 8000 is internal-only; all traffic flows through nginx
  • Server version suppressionserver_tokens off (nginx) and --no-server-header (uvicorn)
  • Rate limiting — nginx limit_req_zone (10 req/min, burst=5) on /api/auth/login, /verify-password, /change-password, /totp-verify
  • Application-level rate limiting — in-memory IP-based rate limit (5 attempts / 5 min) + DB-backed account lockout (10 failures = 30-min lock)
  • Dotfile blocking/.env, /.git/config, etc. return 404 (.well-known preserved for ACME)
  • CSP headers — Content-Security-Policy on all responses, scoped for Google Fonts
  • CORS — configurable origins with explicit method/header allowlists
  • API docs disabled in production — Swagger/ReDoc/OpenAPI only available when ENVIRONMENT=development
  • Argon2id password hashing with transparent bcrypt migration on first login
  • DB-backed sessions — revocable, with signed itsdangerous cookies
  • Optional TOTP MFA — authenticator app support with backup codes

Production Hardening

Before deploying to production, generate secure values for your .env:

# Generate a secure SECRET_KEY (64-char hex string)
python3 -c "import secrets; print(secrets.token_hex(32))"
# or: openssl rand -hex 32

# Generate a secure database password
python3 -c "import secrets; print(secrets.token_urlsafe(24))"
# or: openssl rand -base64 24

# Set ENVIRONMENT to disable Swagger/ReDoc
ENVIRONMENT=production

# Enable secure cookies (requires HTTPS)
COOKIE_SECURE=true

Additionally for production:

  • Place behind a reverse proxy with TLS termination (e.g., Caddy, Traefik, or nginx with Let's Encrypt)
  • Set COOKIE_SECURE=true to enforce HTTPS-only session cookies
  • Set ENVIRONMENT=production to disable API documentation endpoints
  • Set CORS_ORIGINS to your actual domain (e.g., https://umbra.example.com)
  • Consider adding HSTS headers at the TLS-terminating proxy layer

API Overview

All endpoints require authentication (signed session cookie) except auth routes and the health check.

Endpoint Description
GET /health Health check
/api/auth/* Login, logout, setup, status, password change, TOTP MFA
/api/todos/* Todos CRUD + toggle completion
/api/events/* Calendar events CRUD (incl. recurring)
/api/event-templates/* Event templates CRUD
/api/calendars/* User calendars CRUD + visibility
/api/reminders/* Reminders CRUD + dismiss + snooze + due alerts
/api/projects/* Projects + nested tasks + comments CRUD
/api/people/* People CRUD
/api/locations/* Locations CRUD + search
/api/settings/* App settings + password change + ntfy config
/api/dashboard Dashboard aggregation
/api/upcoming Unified upcoming items feed
/api/weather/* Weather data proxy

API documentation is available at /api/docs (Swagger UI) when ENVIRONMENT=development.

Development

Rebuild a single service

docker-compose up --build backend    # Backend only
docker-compose up --build frontend   # Frontend only

View logs

docker-compose logs -f               # All services
docker-compose logs -f backend       # Backend only

Reset database

docker-compose down -v && docker-compose up --build

Stop all services

docker-compose down

Project Structure

umbra/
├── docker-compose.yaml
├── .env / .env.example
├── backend/
│   ├── Dockerfile
│   ├── requirements.txt
│   ├── alembic.ini
│   ├── alembic/versions/        # 25 migrations (001025)
│   └── app/
│       ├── main.py              # FastAPI app, router registration, health endpoint
│       ├── config.py            # Pydantic BaseSettings (DATABASE_URL, SECRET_KEY, CORS, etc.)
│       ├── database.py          # Async SQLAlchemy engine + session factory
│       ├── models/              # 17 SQLAlchemy ORM models
│       ├── schemas/             # Pydantic v2 request/response schemas
│       ├── routers/             # 14 API route handlers
│       ├── services/            # Auth (Argon2id), recurrence, TOTP, ntfy
│       └── jobs/                # APScheduler notification dispatch
└── frontend/
    ├── Dockerfile
    ├── nginx.conf
    ├── proxy-params.conf        # Shared proxy settings (DRY include)
    ├── package.json
    └── src/
        ├── App.tsx              # Routes and auth guard
        ├── lib/                 # api.ts, date-utils.ts, utils.ts
        ├── hooks/               # useAuth, useSettings, useTheme, useCalendars, useConfirmAction, useCategoryOrder, useTableVisibility
        ├── types/               # TypeScript interfaces
        └── components/
            ├── ui/              # 18 base components (Button, Dialog, Sheet, Card, Input, Select, Switch, etc.)
            ├── shared/          # EntityTable, EntityDetailPanel, CategoryFilterBar, CategoryAutocomplete, CopyableField
            ├── layout/          # AppLayout, Sidebar, LockOverlay
            ├── auth/            # LockScreen, AmbientBackground
            ├── dashboard/       # DashboardPage + 8 widgets
            ├── calendar/        # CalendarPage, CalendarSidebar, CalendarForm, EventForm, TemplateForm
            ├── todos/           # TodosPage, TodoList, TodoItem, TodoForm
            ├── reminders/       # RemindersPage, ReminderList, ReminderItem, ReminderForm, SnoozeDropdown, AlertBanner
            ├── projects/        # ProjectsPage, ProjectCard, ProjectDetail, ProjectForm, KanbanBoard, TaskRow, TaskForm, TaskDetailPanel
            ├── people/          # PeoplePage, PersonForm
            ├── locations/       # LocationsPage, LocationForm
            └── settings/        # SettingsPage, NtfySettingsSection, TotpSetupSection

License

This project is for personal use. Feel free to fork and adapt for your own needs.

Description
A self-hosted life administration app.
Readme 5.9 MiB
Languages
TypeScript 59.6%
Python 39.2%
CSS 0.9%
HTML 0.1%
Dockerfile 0.1%