Skip to content

Releases: noestudios/lastbell

Last Bell 0.3.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 18:51

Last Bell runs without Python now, keeps its first household setting
inside the app, and only restarts what an upgrade actually changed.

  • Runs anywhere. Every release is also a container image,
    ghcr.io/noestudios/lastbell (tagged X.Y.Z and latest), for
    64-bit Intel/AMD machines and 64-bit Raspberry Pis, built from the
    same wheel the release sends to PyPI. No Python, no pipx: put
    docker-compose.yml in an empty folder and run three commands —
    docker compose run --rm lastbell setup, docker compose up -d,
    docker compose exec dashboard lastbell dashboard --show-key. One
    folder, data/, holds everything: the database, the snapshots, and
    the settings file the wizard writes. The README's new "Run it as a
    container" section walks through it; packaged apps for Umbrel, Home
    Assistant and NAS app stores come later and will wrap this image.
  • The CLI knows when it's in a container. There lastbell upgrade
    prints docker compose pull and docker compose up -d instead of
    looking for pipx; lastbell status says "container image";
    install-service says Docker already keeps it running; and the
    footer's "restart to use it" badge never appears, because the image
    is the only copy.
  • The score tint is a setting inside the app. Settings has a new
    Display card: "Tint scores below" decides which graded
    assignments are tinted on the student pages (70 by default, a C on
    the MCPS scale; 0 turns it off). Save it there and it stays across
    restarts. LASTBELL_SCORE_CUTOFF only seeds it now: an existing
    install picks up its shell value the first time Settings opens, and
    once you save on the page the variable is ignored. lastbell status
    says which one is in charge. The tint is household-wide for now (no
    per-student value), and nothing alerts on it.
  • lastbell upgrade restarts only when there is something to
    restart.
    When pipx says the installed release is already the latest
    and nothing newer is waiting on disk, the poller and dashboard are
    left alone ("nothing to restart"). --restart-only still restarts
    them on request.

For existing installs nothing changes on disk: upgrade as usual with
lastbell upgrade. Contributors: docker compose build still builds
from the working tree (uncomment build: .), and CI now builds the
image without pushing on every change.

Last Bell 0.2.14

Choose a tag to compare

@github-actions github-actions released this 05 Sep 15:25

The student page holds still, the Settings page keeps your edits, and
a course grade shows wherever the portal put it.

  • View cards and course filters switch in place. The four cards
    (Needs attention, Due soon, Recent grades, Everything) and the All
    Courses filters were links that loaded a new page, which started at
    the top. Now a click swaps the strip, cards and panel where they
    stand; the URL, tab title and back button follow as before, and the
    strip stays open or closed as you had it. With JavaScript off they
    are ordinary links, as they were.
  • The cards tell the filtered course's story. Under a course filter
    every number on the cards was the whole student's while the panel
    below was one course's. Now the counts, the weekly change, the
    next-due lines and the last ten scores are the course's, with the
    whole-student figure alongside ("3 of 19 overall"), and the
    Everything card becomes the course grade with the course's own
    trajectory.
  • Settings: one Save / Discard per table. Each row had its own
    Update button, and pressing one threw away every other row's unsaved
    edits. Now a Save changes / Discard bar appears under the Watchers
    and Subscriptions tables once anything differs; Save writes every
    changed row in one go and leaves untouched rows alone.
  • Course grades read the portal's two slots as one. At interim time
    MCPS can put a number in the letter-grade slot with no percent, or
    "N/A" with a 0 percent for nothing graded yet. The first showed as a
    dash and the second as "0.0 N/A", a failing grade that isn't one. Now
    a numeric mark is the percent, a placeholder is no grade, and the
    strip, the overview, the Everything cards, the daily summary and the
    grade-drop check all read them the same way. A flip back to the
    placeholder no longer fires a grade drop. Storage is unchanged.
  • The CLI explains itself. lastbell alone prints the help instead
    of an error; --help groups the commands the way a parent reaches
    for them; the dashboard line names --show-key, and lastbell status on a network-bound install says that command prints the
    phone link.

Last Bell 0.2.13

Choose a tag to compare

@github-actions github-actions released this 05 Sep 14:19

Two small follow-ups to 0.2.12's History page. Its assignment and course
links now wear the same soft underline as every other in-app link, and
the Course grades rows link to the student's Everything view, as the
assignment rows already did.

Last Bell 0.2.12

Choose a tag to compare

@github-actions github-actions released this 05 Sep 14:10

History reads one line per event. A grade used to land on the
History page as three rows — status due → graded, score — → 8.0, points
— → 10.0 — and the status row said nothing about the grade. Now the rows
one check writes about one assignment fold into a single line: "graded,
— → 8/10", or "score, 8/10 → 9/10" on a regrade. A status change on its
own reads in the badge's words ("due → ungraded past due"), and a due
date or name change stays its own row. The audit trail underneath is
unchanged; only the page groups. The Change chips still count what was
recorded, so "score 12" and "points 12" can both sit above a table that
shows twelve grades.

Every assignment and course on the page now links to the student's page
scoped to that class — as far as a link can land today.

Last Bell 0.2.11

Choose a tag to compare

@github-actions github-actions released this 04 Sep 21:16

0.2.10's self-restart is removed. For one release the poller and the
dashboard watched the version on disk and restarted themselves when it
changed. That made a running service act on something other than a
person's decision, and the owner would rather it didn't: an upgrade is a
choice, and the restart belongs to that choice. So it is gone, entirely.
pipx upgrade lastbell moves the files and nothing more; lastbell upgrade moves them and restarts the poller and the dashboard, which is
the path to use. The footer badge and lastbell status say "restart to
use it" again, as they did in 0.2.9. Nothing else changed.

Last Bell 0.2.10

Choose a tag to compare

@github-actions github-actions released this 04 Sep 21:08

pipx upgrade lastbell is now enough. The poller and the dashboard
each look at the version on disk once a minute and, when it is newer than
the one they loaded, replace themselves with a fresh process running the
same command line: the poller between polls, never mid-poll; the
dashboard after the last response has gone out. The process id doesn't
change, so systemd and launchd see nothing happen, and a copy started by
hand in a terminal just carries on as the new version. The footer badge
and lastbell status now say "restarting within a minute" instead of
asking you to restart; lastbell upgrade still restarts at once.
LASTBELL_SELF_RESTART=0 switches it off. A checkout whose dist-info
runs ahead of its code restarts once, not every minute.

Last Bell 0.2.9

Choose a tag to compare

@github-actions github-actions released this 04 Sep 18:15

A heartbeat, a demo you can click, and the rest of the public-repo
list.

  • Heartbeat URL. A stopped machine can't email anyone. Set
    LASTBELL_HEARTBEAT_URL to a healthchecks.io check or an Uptime Kuma
    push monitor and Last Bell fetches it once after every successful poll,
    never otherwise; that service raises the alarm when the pings stop. A
    ping that fails is one warning in the log, never a failed poll. It is
    off unless set, listed in the README's outbound-HTTP inventory, and
    lastbell status names the host it pings (never the URL, which is a
    secret).
  • The watcher-health notices wear the email frame. "Can't check the
    gradebook" and "Checking again" now carry the same HTML twin the alerts
    and summaries do, with commands as code; the plain text every other
    channel gets is unchanged.
  • A static demo. scripts/build_demo_site.py renders every dashboard
    page from seed-demo data, and a workflow publishes it to
    https://noestudios.github.io/lastbell/ whenever the dashboard changes.
  • README. Badges; a link to the demo; the district question now leads
    with pipx run lastbell preflight …, which installs nothing; screenshots
    of the three emails (digest, daily summary, the health notice), made by
    scripts/render_emails.py; a half-page threat model; a Contributing
    section. SECURITY.md documents the PEP 740 attestations each release
    carries, and CONTRIBUTING.md is new.

Last Bell 0.2.8

Choose a tag to compare

@github-actions github-actions released this 04 Sep 17:55

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.

Last Bell 0.2.7

Choose a tag to compare

@github-actions github-actions released this 04 Sep 17:19

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.

Last Bell 0.2.6

Choose a tag to compare

@github-actions github-actions released this 04 Sep 16:03

A hardening pass over the parts a careful reader checks first. Seven
changes; none alters what Last Bell watches or sends, and each closes a
gap between what the README promised and what the code guaranteed.

  • Dashboard: beyond loopback, other devices need a key. --host 0.0.0.0 used to put full names and the watcher list on the network
    with no login at all. Now requests from the machine itself still need
    nothing, and every other device needs the dashboard key once: a long
    random string generated on first widened start, kept in the settings
    file, printed as a link (http://raspberrypi.local:8321/?key=…) that
    sets a cookie. lastbell dashboard --show-key prints it again. Binding
    to a public address is refused (the key would cross the internet in the
    clear) unless LASTBELL_DASHBOARD_PUBLIC=1; the right shape there is
    loopback behind a TLS proxy with its own login.

  • Email: the mail server's certificate is now verified. starttls()
    with no context uses Python's unverified default, so anyone between
    the box and the mail server could have presented any certificate and
    read the SMTP password. Both STARTTLS and, new, implicit TLS on port
    465 now verify against the OS trust store.

  • Dashboard: DNS rebinding is refused. Binding to 127.0.0.1 kept the
    network out but not the reader's own browser: a web page could point a
    hostname it controls at 127.0.0.1 and read the dashboard, and the
    Origin check couldn't tell because Origin and Host then agree. The
    dashboard now answers only to loopback, IP literals, .local names,
    the bound address, and LASTBELL_DASHBOARD_HOSTNAMES; anything else
    gets a 421 page saying why and how to allow a name of your own.

  • Settings file: passwords with #, $, quotes, or edge spaces now
    survive the round trip.
    The wizard wrote values bare; python-dotenv
    reads abc #def as abc and expands ${VAR}. The wizard's own check
    used the in-memory value, so it said "go" and the boot-time service
    then failed to sign in. Values that need it are now quoted, the loader
    no longer interpolates, and the wizard reads the password back through
    dotenv before calling it saved.

  • Settings file: owner-only from the first byte. It was written under
    the umask and chmod-ed to 0600 afterwards. It is now written to a
    0600 temp file and moved into place, so a crash mid-write can't leave
    a truncated settings file either.

  • Docker: the compose file works. It mounted a secret that nothing
    read. LASTBELL_PASSWORD_FILE (and _SMTP_FILE, LASTBELL_CANVAS_TOKEN_FILE)
    now name a secret file, and the compose file runs the poller plus a
    loopback-only dashboard.

  • Preflight: the legacy SOAP probe no longer carries your password.
    The deprecation code districts return (UPD5304-00 and friends) comes
    back before any login, so a placeholder learns the same thing. The
    README's "sent to one destination" is now literally true.