Simple light Trello-like
- JavaScript 42.1%
- Python 34.3%
- CSS 18.8%
- HTML 4.8%
| static | ||
| templates | ||
| .gitignore | ||
| app.py | ||
| instructions.md | ||
| README.md | ||
| requirements.txt | ||
| twilight.service | ||
🌒 Twilight Task System
A tiny single-writer Trello-ish task board. One person writes; everyone else gets read-only share links. Live updates over websocket, dark UI, drag & drop.
Run
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
TWILIGHT_PASSWORD=yourpassword .venv/bin/python app.py
# -> http://localhost:5000
Binds to 127.0.0.1:5000 by default (intended to sit behind a reverse proxy
like Caddy). Override with HOST / PORT env vars, e.g. HOST=0.0.0.0 to
expose it directly on your LAN.
Password
Resolved in this order:
$TWILIGHT_PASSWORD- File at
$TWILIGHT_PASSWORD_FILE instance/password.txt— auto-generated on first run (printed to stdout)
Run as a service (systemd)
A unit file is included at twilight.service. Adjust the paths at the top
(WorkingDirectory, ExecStart, ReadWritePaths) to wherever you deploy,
then:
sudo useradd -r -s /usr/sbin/nologin twilight # once
sudo chown -R twilight:twilight /opt/twilight/instance
sudo cp twilight.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now twilight
Set the password via the commented Environment= / EnvironmentFile= lines
in the unit, or let the app generate one into instance/password.txt
(journalctl -u twilight will show it on first start).
How it works
- Backend: Flask + flask-sock (websocket) + sqlite (
instance/twilight.db, created automatically). All mutations are plain HTTP POSTs; the websocket is only used to broadcast "something changed, refetch" to viewers of the same project (best effort). - Frontend: plain JS (no build step). State is held client-side; UI updates
are applied optimistically and synced lazily to the backend. SortableJS
powers drag & drop, marked + DOMPurify render task markdown. Vendored libs
live in
static/vendor/.
Auth model
- Writer: logs in via the password page; gets a session cookie. Full CRUD, project switcher, share-link management.
- Reader: visits
/?token=...with an active access token. Read-only view of that one project — no switching, no editing. Tokens are managed from the "Share" button in the banner and can be disabled or deleted at any time.
Layout
app.py # the whole backend
templates/ # index.html (app shell) + login.html
static/app.js # all frontend logic
static/style.css # dark theme
static/vendor/ # Sortable.min.js, marked.min.js, purify.min.js
instance/ # sqlite db, secret key, generated password (gitignored)
twilight.service # systemd unit (edit paths, copy to /etc/systemd/system)
API sketch
| Endpoint | Verb | Who | Notes |
|---|---|---|---|
/api/state/<pid> |
GET | writer or token | full project snapshot |
/api/projects[/<id>][/delete] |
POST | writer | create / update / delete |
/api/lists[/<id>][/delete] |
POST | writer | create / update / delete |
/api/tasks[/<id>][/delete] |
POST | writer | create / update / delete |
/api/reorder |
POST | writer | bulk task list/order after drag |
/api/tokens[/<id>][/delete] |
POST | writer | share-link management |
/ws?project_id=N[&token=…] |
WS | writer or token | change notifications |