Skip to content

Last Bell 0.2.8

Choose a tag to compare

@github-actions github-actions released this 04 Sep 17:55
· 27 commits to main since this release

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 status puts 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 upgrade runs pipx upgrade lastbell and 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-restart upgrades
    the files only; --restart-only skips 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 plain cp can
    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 forget removes 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; --yes is for scripts. (The program itself stays;
    pipx uninstall lastbell removes 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.com and 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 --loop warns 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.md with a private
    reporting path, Dependabot for pip and Actions, and a pip-audit job
    that also runs weekly against fresh advisories.