Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.7 KiB
WhatToDo
A small, self-hosted to-do app for one person. It works in a desktop browser and on a phone, and you can install it to your home screen.
- Tasks have an optional plan date (when you'll do it), an optional deadline (when it must be done), and an optional time range (e.g. 10:00–12:00). Each task also has a priority, category, tags, notes, and a checklist of steps.
- Postpone any task from the ⏭ button: today, tomorrow, the weekend, next week, in 2 weeks, next month, or a date you pick. You can move either the plan date or the deadline.
- Unfinished planned tasks roll over: they stay in Today until you do them or postpone them, and they never turn red. Only missed deadlines count as overdue.
- Backlog lists tasks with no date at all. Today suggests a couple of them so they don't get forgotten.
- Projects group the tasks of a bigger goal and have their own deadline, notes and progress bar. Today shows each active project's next step, how much time is left, and a warning if it's falling behind or nothing was done on it in over a week.
- Recurring tasks (every N days/weeks/months/years): completing one creates the next occurrence.
- When you pick a date and time, the task form lists what else is planned that day and warns about overlaps.
- Views: Today, Upcoming, Calendar (day timeline with week strip; tap an hour to add a task there), Backlog, All open, Done, plus one view per project and per category.
- You can rename, recolor, reorder, add, or delete categories in Settings. It starts with Work, Life, Personal, YouTube.
- Export / import of all data as JSON, from Settings.
- Login with a single password.
Stack: Python (FastAPI) + SQLite with a plain HTML/CSS/JS frontend and no build step. Everything runs in Docker.
Run
Requirements: Docker with the Compose plugin. You don't need the source code: only docker-compose.yaml and a .env.
mkdir whattodo && cd whattodo
# copy docker-compose.yaml and .env.example from this repo into this folder
cp .env.example .env # then edit .env: set APP_PASSWORD and SECRET_KEY
docker compose up -d
Open http://localhost:8080 and log in with APP_PASSWORD.
The database lives in ./data/whattodo.db.
Useful commands:
docker compose logs -f # follow logs
docker compose down # stop
docker compose pull && docker compose up -d # update to the newest published version
To pin a version, set WHATTODO_TAG=<version> in .env, e.g. v1.0 or a commit (default: latest).
Develop (build from source)
docker-compose.build.yaml builds the image from this checkout. Add this line to your .env so plain docker compose uses it:
COMPOSE_FILE=docker-compose.build.yaml
docker compose up -d --build # build and start
docker compose run --rm test # run the test suite (in Docker)
Publishing the image
./publish.sh builds the last commit (uncommitted changes are left out) and pushes it to
git.goyban.com/goyban/whattodo as :latest and :<commit>. ./publish.sh <tag|branch|commit> publishes another version.
To release a numbered version, tag the commit first: git tag v1.1 && ./publish.sh v1.1 also pushes the image as :v1.1.
Log in once first with docker login git.goyban.com, using a Gitea token with package write access as the password.
Testing on your phone (same Wi-Fi)
Set BIND_ADDR=0.0.0.0 in .env, run docker compose up -d, then open http://<this-computer's-LAN-IP>:8080 on the phone.
Browsers only allow "Install app" / "Add to Home Screen" with offline support over HTTPS (or on localhost). Over plain LAN HTTP the app still works, but it won't install as a full app.
Deploy to production
-
On the server, put
docker-compose.yamlin a folder and create.envfrom.env.example. Use a new strongAPP_PASSWORDandSECRET_KEY, and setCOOKIE_SECURE=true. -
docker compose up -d. The app listens on127.0.0.1:${PORT}. -
Put it behind a reverse proxy with HTTPS. For example, with Caddy:
todo.example.com { reverse_proxy 127.0.0.1:8080 }If the proxy runs in another container instead of on the host, set
BIND_ADDR/networking to match and setFORWARDED_ALLOW_IPSto the proxy's IP. The app then sees the real client IP, which login rate limiting uses.
Moving your data from this PC to production
- Local app → Settings → Export data. The export includes tasks, projects and categories. This downloads
whattodo-export-YYYYMMDD-HHMM.json. - Production app → Settings → Import, pick the file:
- Replace deletes everything on that install and loads the file. Use this for the first move to prod.
- Merge adds the file's tasks to what is already there. Categories with the same name are reused.
The export file contains your tasks. Keep it out of the repo (whattodo-export-*.json is git-ignored).
Backups
Export regularly from Settings, or copy ./data/whattodo.db while the app is stopped.
Security notes
.envanddata/are git-ignored. Never commit them; the repository is public.- Changing
APP_PASSWORDorSECRET_KEYlogs out all sessions. - Failed logins are rate limited: 10 failures per 15 minutes per IP.
- Write requests require an
X-WhatToDoheader (CSRF protection). The app also sends a strict Content-Security-Policy.
Project layout
app/main.py API: auth, tasks, categories, export/import
app/db.py SQLite schema and setup
app/static/ frontend (index.html, app.js, style.css, PWA files, icons)
tests/ API tests (run with: docker compose -f docker-compose.build.yaml run --rm test)
tools/make_icons.py regenerates the PNG icons