Administration#
This app has no authentication. It is local-first: one container, one SQLite file, one machine. Everyone who can reach the port can do everything. That is a deliberate trade — it keeps the app simple and it is the right shape for something running on your own laptop or on a home LAN.
Do not put this on the public internet as-is
There is no login, no rate limiting and no CSRF token on the JSON API. If you need it reachable from outside your network, put it behind a reverse proxy that does the authentication (basic auth is enough) and terminate TLS there. See Installation.
Profiles#
A profile is a student. It owns assessments, attempts, solves, stars, focus items, drill sessions, plan progress and the cached scores — everything except the algorithm library, which is shared.
The first boot creates a default profile called Me with the slug me and
the goal sub_20. The active profile lives in the session cookie, so switching
profiles is per browser, not per machine.
Managing profiles#
From the profile chip in the header:
- Switch — pick another profile. Takes effect immediately.
- New — name and goal. The slug is derived from the name and de-duplicated
(
alex,alex-2, …). - Rename — changes the display name; the slug stays put so links and CLI commands keep working.
- Goal — one of the keys under
targets.splits. This selects the split targets every score is measured against, so a coach can keep a sub-30 and a sub-12 student on one machine and both see sensible colours. - Delete — removes the profile and everything it owns. The default profile cannot be deleted; promote another one first.
Using profiles for coaching#
One profile per student is the obvious use. Two less obvious ones:
- A scratch profile. Handy when you want to poke at the API console or try a plan without polluting real data.
- A "cold" profile. Some coaches keep a second profile per student with no assessments at all, to re-measure recognition from scratch every few months.
Resetting student data#
Two levels, both destructive, both irreversible without a backup.
Reset a profile keeps the profile and deletes every row it owns — assessments, attempts, solves, stars, focus, plan progress, cached scores and drill sessions.
docker compose run --rm web flask reset-profile me
There is no --force; the command is short and explicit on purpose. The same
action is available from the profile page with a confirmation dialog.
Reset everything means throwing away the database. Stop the app, remove the volume, start it again:
docker compose down
docker volume rm rubiks-trainer_rt-data
docker compose up -d
The library is re-imported and a fresh default profile is created on first boot.
Backups#
Everything a student owns is in one SQLite file on the rt-data volume, at
/data/rubiks-trainer.db by default.
Taking a backup#
Use SQLite's own backup command rather than copying the file, so you get a consistent snapshot even while the app is running:
docker compose exec web \
python -c "import sqlite3,sys; \
src=sqlite3.connect('/data/rubiks-trainer.db'); \
dst=sqlite3.connect('/data/backup.db'); \
src.backup(dst); dst.close(); src.close()"
docker compose cp web:/data/backup.db ./rubiks-trainer-$(date +%F).db
Restoring#
Stop the app first — restoring under a running server will corrupt the open connection's view of the file.
docker compose down
docker compose cp ./rubiks-trainer-2026-08-12.db web:/data/rubiks-trainer.db
docker compose up -d
What to keep#
| File | Why |
|---|---|
/data/rubiks-trainer.db |
All student data. This is the one that matters |
app/config/config.yml |
Your targets, scrambler settings and colours |
.env |
Port, secret key, database URL |
The algorithm library and the built-in plans are re-created from
app/data/algorithms.json on every boot, so they do not need backing up.
If you use Postgres
Point RT_DATABASE_URL at Postgres and back it up with pg_dump like any
other database. The schema is created automatically on first boot.
Seeding and updating the library#
The algorithm library is imported from app/data/algorithms.json at startup if
the database is empty. To re-import after the data file changes:
docker compose run --rm web flask seed
By default this is additive and idempotent — existing cases are matched by
key and updated, new ones are inserted. To wipe the library and rebuild it
from the file:
docker compose run --rm web flask seed --force
Student data is keyed on the case row, so --force will orphan attempts and
assessments if case keys change. Take a backup first, and prefer the plain
seed unless you know keys were renamed.
Regenerating the data file from the PDFs#
The library is extracted from the algorithm sheets in pdf/:
docker compose run --rm web python scripts/extract_algorithms.py
This rewrites app/data/algorithms.json and validates every case — the setup
moves followed by the primary algorithm must return the cube to the phase's
target state. Cases that fail are reported and the script exits non-zero. See
Contributing before you commit the result.
Upgrading#
See Installation → Upgrading. In short: pull,
rebuild, restart. The schema is created with create_all() and there is no
migration framework, so a release that changes a column requires a note in the
changelog and, in the worst case, a rebuild from a backup.
Health checks and monitoring#
GET /healthz returns {"status": "ok", "version": "..."} and is what the
Compose health check calls every 30 seconds:
curl -s http://localhost:8080/healthz
Application logs go to stdout and are visible with docker compose logs -f web.
Gunicorn writes both access and error logs there.
Configuration#
Every key in config.yml and every RT_* environment variable is documented
in Configuration.