Last Bell 0.2.8
Three commands for the second week: status, upgrade, backup.
Once it runs, the questions change from "how do I set it up" to "is it
still working", "how do I update it", and "what if the SD card dies".
Each now has a one-word answer.
lastbell statusputs the install on one screen: the version
running and, if different, the version installed but not yet restarted;
the platform and whether the clock is on UTC; the settings file and
where the password lives (the keyring by name, or the owner-only
settings file — never the password); the poll interval, alert channel,
and Canvas mode; whether the service is installed and running (both
user units on Linux, the launchd agent on macOS); whether the dashboard
is listening and whether its network key exists; the log's size and
age; the last successful check, whether checking is failing and since
when, and when the next check is due (or that it is overdue, which is
how a stopped service looks); students as initials; every watcher with
their channels and who they are subscribed to; alerts sent and queued.
Home directories print as~, so the whole thing can be pasted into an
issue. It creates nothing, reads no secret, and touches no network.lastbell upgraderunspipx upgrade lastbelland then restarts
the poller and, where one is set up, the dashboard — the step people
forgot often enough to earn a footer badge in 0.2.5. It reports the
version before and after and what it restarted.--no-restartupgrades
the files only;--restart-onlyskips pipx. Without pipx on PATH it
says how to upgrade a venv or checkout instead.lastbell backup [path]writes one owner-only zip: the database
copied through SQLite's backup API (a copy taken mid-poll is still
whole, and the write-ahead log is folded in, which a plaincpcan
miss) and the settings file with every secret left out — passwords,
channel tokens, the dashboard key — each replaced by a line saying so.
A README inside says what it holds and how to restore it.lastbell restore <file>checks the archive, refuses to replace an
existing database unless--force(and then keeps the old one beside
it as.before-restore, with its log folded in first), merges the
settings without touching the secrets already on the machine, and
reminds you to store the password again.
It no longer fails silently, it can forget you, and three audits'
findings are fixed. A read of the credential path in 0.2.6 left six
things on the list; this release does all of them.
- Guardians are told when checking stops. A watcher that can't sign
in (the ParentVUE password changed) or can't reach the portal used to
fail every three hours forever, and the only sign was a stale footer
on a dashboard nobody opens. Now, after a rejected sign-in has lasted
two polls (a day for an unreachable portal), every guardian gets one
message on their own channels saying what is wrong and what to do; one
more arrives when checking resumes. The home page footer names the
failure meanwhile. While sign-in is being rejected the poller also
backs off to once a day, so a stale password can't trip a district's
lockout policy. lastbell forgetremoves everything Last Bell keeps on a machine:
the background service, the database with every snapshot and alert,
the settings file, and the keyring entries. It lists what it will do
and asks first;--yesis for scripts. (The program itself stays;
pipx uninstall lastbellremoves that.)- Preflight: the shareable report is redacted on the failure paths
too. A LoadControl error from the portal is post-login text that
could name anyone; it now stays local and the report carries a fixed
sentence. A request failure after login is reported by exception name
only (its text carries a URL, and the gradebook URL carries the
student's id). A pasted portal URL is reduced to its hostname before it
is printed anywhere. Table cells can't break the table. The command
never prints a traceback. - Dashboard: the alerts page's type filter was reflected unescaped.
A crafted link could run script in the dashboard's own origin. The
filter is now escaped and, before that, only ever one of the types
actually present. - Email addresses mean one mailbox. The validator accepted
kid@example.com, other@evil.comand display-name and group forms,
which SMTP would fan out to every address named. One bare address, or
a plain refusal. - Clocks. History buckets (the six-week trend, "this week") were
grouped by UTC date while everything around them used local days, so
an evening change didn't count until midnight. A daily summary whose
slot fell inside a poll that ran past midnight was skipped for the day;
a missed day is now caught up. Work in the grace window (due in the
last few days, still ungraded) had dropped out of the summary; it has
its own line now, matching the dashboard.run --loopwarns at start
when the host clock is on UTC. - Locks. The poller and the dashboard restart together after an
upgrade and both run migrations; the loser of that race died on a
duplicate-column error. A settings save while the poller held the
database past the busy timeout dropped the connection, and the page's
fallback re-submitted the form. Both now end well: the migration
tolerates the race, the save answers "busy, try again". WAL runs with
synchronous=NORMAL, the standard setting, so a poll's many small
commits aren't each a full fsync on the SD card. - Housekeeping for a public repo:
SECURITY.mdwith a private
reporting path, Dependabot for pip and Actions, and apip-auditjob
that also runs weekly against fresh advisories.