diff --git a/Makefile b/Makefile index 1ed4e35..0ed4501 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,9 @@ web-build: ## Build the web UI into frontend/dist desktop: ## Run the native desktop app against localhost:8000 cd desktop && python3 -m kadence_desktop +desktop-test: ## Run the desktop app test suite (uses the backend venv) + cd desktop && $(CURDIR)/backend/.venv/bin/python -m pytest -q + desktop-install: ## Install the launcher entry and icon for your user ./desktop/packaging/install-desktop.sh @@ -61,4 +64,4 @@ deploy: ## Push master and redeploy kadence.timka.su on sw_pl_1 deploy-logs: ## Follow the production app log ssh sw_pl_1 'cd /opt/kadence && docker compose -f docker-compose.yml -f docker-compose.prod.yml logs -f --tail 100 app' -.PHONY: help up down logs dev test backend-venv web-build desktop desktop-install helper-install android-test android-test-core android-build android-install report set-password deploy deploy-logs +.PHONY: help up down logs dev test backend-venv web-build desktop desktop-test desktop-install helper-install android-test android-test-core android-build android-install report set-password deploy deploy-logs diff --git a/README.md b/README.md index 659791e..ca4eec4 100644 --- a/README.md +++ b/README.md @@ -58,13 +58,17 @@ Rules live on the server (edit them in the web app or the desktop app); the native desktop app enforces them on your machine: * **Websites** — `/etc/hosts` is rewritten through a tiny privileged helper under - polkit. No browser extension, no proxy, works for every app on the machine, and - you can audit it with `cat /etc/hosts`. + polkit, and the masks a hosts file cannot express are answered by a local DNS + stub the helper points `systemd-resolved` at, for the zones it can route. No + browser extension, no proxy, works for every app on the machine, and you can + audit the hosts region with `cat /etc/hosts`. * **Masks** — `youtube.com` blocks the site plus `www.` and `m.`; `*.google.com` - blocks every subdomain in your expansion list (a hosts file has no wildcards, - so the app expands them for you and tells you when a pattern like `*gram*` - cannot be expressed that way). Exceptions take masks too, so `*.google.com` - blocked plus `*.docs.google.com` allowed does what you would expect. + blocks the bare domain plus every subdomain in your expansion list — add a bare + `*` to that list and "every" means every, answered by the DNS stub rather than by + guessed hostnames. A hosts file has no wildcards at all, so `*gram*` is reported + honestly as unenforceable on the machine rather than silently ignored. + Exceptions take masks too, so `*.google.com` blocked plus `*.docs.google.com` + allowed does what you would expect. * **Applications** — `/proc` is watched and blocked apps are closed (or just reported, if you prefer a warning). Entries here are globs as well: `steam*`, `*discord*`, matched against the process name and its command line. @@ -196,14 +200,15 @@ and you can fill everything in from **Settings**. ┌──────────────┐ │ • reports + achievements │ └───────────────┘ │ Desktop app │──/api──│ • scheduler (briefing, │ │ Qt/QML, D-Bus│ │ report, nudges) │───────▶ Telegram ⇄ you -│ hosts + /proc│ └───────────────────────────────┘ +│ hosts+DNS+ │ └───────────────────────────────┘ +│ /proc │ │ └──────────────┘ │ enforces Postgres ``` * `backend/` — FastAPI app. `app/services/` holds the thinking (timer, tasks, stats, report, blocker policy, scheduler, bot); `app/sources/` holds one module - per integration; `app/api/` is a thin HTTP layer. 63 tests under `backend/tests`. + per integration; `app/api/` is a thin HTTP layer. 71 tests under `backend/tests`. * `frontend/` — React + TypeScript + MUI, built by Vite into the backend image and served from the same origin (so there is no CORS to configure). * **The look** — warm paper, ink type, hairline rules instead of boxes and @@ -214,7 +219,7 @@ and you can fill everything in from **Settings**. has to stay readable for colour-blind readers in both themes. * `desktop/` — the native app. See [`desktop/README.md`](desktop/README.md). * `android/` — the phone client. Everything with interesting behaviour lives in a - plain-JVM `:core` module, so 225 tests run on a laptop with nothing but a JDK. See + plain-JVM `:core` module, so 234 tests run on a laptop with nothing but a JDK. See [`android/README.md`](android/README.md). --- @@ -226,6 +231,7 @@ make up # docker compose up --build make dev # backend with reload + Vite dev server make test # backend test suite make desktop # run the native app from the checkout +make desktop-test # desktop app test suite (uses the backend venv) make desktop-install # menu entry + icon make helper-install # the privileged blocking helper make report # build and send today's report right now @@ -279,11 +285,13 @@ a login screen instead of a header the desktop app fills in for you. ## Security notes * The desktop app never runs as root. The only privileged code is - `desktop/helper/kadence-helper` (~200 lines): it validates every hostname, - rewrites only the region between its own markers in `/etc/hosts`, writes - atomically, keeps a backup, and refuses anything that does not look like a - hostname. It is authorised by polkit (`auth_admin_keep` by default — one prompt - per session; `install.sh --passwordless` drops that for one user). + `desktop/helper/kadence-helper` (~250 lines) and it has exactly two jobs: it + validates every hostname, rewrites only the region between its own markers in + `/etc/hosts`, writes atomically, keeps a backup, and refuses anything that does + not look like a hostname — and it writes one drop-in for `systemd-resolved` + pointing the routable zones at the unprivileged DNS stub, which it removes again + before the stub stops. It is authorised by polkit (`auth_admin_keep` by default + — one prompt per session; `install.sh --passwordless` drops that for one user). * Tokens live in the database and in `.env`, are masked in every API response, and are never sent to the browser in full. The password is never stored at all — only a salted PBKDF2 hash.