https://updates.riverofglorymn.org/stable/latest.debhttps://updates.riverofglorymn.org/canary/latest.deb
A symlink beside each channel's manifest, pointing at the versioned
package the manifest names. Deliberately not a second record of which
version is current — the manifest stays the only answer to that, and the
publish scripts derive the pointer from it rather than from anything they
know themselves.
The versioned files are never rewritten. A release uploads its package,
then the manifest, then swaps the pointer by rename. Atomic, so nothing
downloads a half-written pointer; and last, so the pointer can lag the
manifest by a moment and can never lead it. A pointer that led the
manifest would offer a package the channel had not published.
Both latest.deb and manifest.json are served no-store on the update
server. Cloudflare sits in front of it and caches .deb by extension, so
without that header a room could be handed the previous release from an
edge for hours. The versioned packages keep no such header because they
are immutable and worth caching.
The server change is one matcher in the Caddyfile's updates block, and it
took three attempts to land for a reason worth recording: the Caddyfile is
a FILE bind mount, so `mv` over it replaces the inode and the container
goes on serving the old one. Two apparently successful reloads changed
nothing. It is written through the mount now, and the host copy is
identical so the next container restart is a no-op. Caddy was reloaded,
never restarted; the church's other sites were not interrupted.
Installing from a channel URL still does not move a machine onto that
channel. CHANNEL in device.conf decides which manifest the updater
follows, and the postinst only appends settings that are missing, so
fetching the canary package by hand to repair a machine leaves it on
stable.
Verified end to end against the live server: both URLs deliver bytes whose
sha256 matches their channel's manifest, both carry the no-store header,
and the versioned package does not.
Co-Authored-By: Claude Opus 5 <[email protected]>
Three things, none of them redesigns.
The close affordance for a secondary Chrome window was a separate
layer-shell surface anchored to that window's corner, repositioned by a
five-per-second poll. Any position change tore the surface down and
built another, so on real hardware it chased the window and flickered.
Removed entirely: the GTK button process, the geometry maths that placed
it, the --close-button mode that spawned it, and the poll loop that
drove it. The window helper no longer imports GTK at all.
The behaviour underneath it was right and is kept. The first Chrome
top-level is the classroom browser, tiled and borderless; later ones
float centred at about three quarters of the output. Creation order is
the only thing that can tell them apart — confirmed on a real session,
where every Chrome top-level shares app_id, shell, PID and a null
transient_for, and sway exposes no window properties for Wayland
toplevels at all. Titles are still not used. It now follows sway's own
window events instead of polling, so dragging a popup costs nothing.
Close moved into the dock, which was already on screen and does not
move, so it cannot chase anything. It appears only while a secondary
window is focused and runs kill on that one container. The helper
publishes which window that is; the dock watches the file rather than
polling. A stock sway title bar has no close button, so restoring the
border would not have solved this either.
Two bugs found by looking rather than by testing state. Chrome
renegotiates the size of a popup opened with explicit dimensions after
the resize returns, which left windows centred for a width they no
longer had; placement now settles and repeats once, at creation only, so
dragging is still free. And the dock's Close button came up empty
because show_all does not recurse into a no-show-all widget.
The printer's Location is now blank. CUPS prints it under the queue name
in every dialog, so "Ekklesia Classroom" was a second line the teacher
had to read past; the identity is one word. The already-configured check
includes Location, so a machine set up before this is corrected rather
than left as close enough.
Co-Authored-By: Claude Opus 5 <[email protected]>
A fresh classroom needed two things installed by hand on every box:
Google Chrome, which is not in Debian, and the DVD codecs, which Debian
cannot ship. Both now happen on their own.
ekklesia-classroom-provision.service runs after every boot and does the
work that needs the network and the apt lock, neither of which a package
postinst can have. Each step returns immediately if it is already done,
so a provisioned machine costs nothing and a machine that was offline
the first time gets another go at the next boot. Nothing there is
allowed to fail the boot: a classroom with no DVD codecs is still a
working classroom, and it says so in the log rather than silently.
The postinst enables the service but deliberately does not start it.
Every OTA update reboots the moment the install finishes, and an apt run
killed by that reboot would leave dpkg half-configured.
Chrome comes from Google's own repository rather than a one-off .deb, so
it keeps getting security updates. Google's package migrates whatever we
write into a deb822 file of its own, so the repository is looked for
across every source file instead of only the one we would write;
otherwise a machine that had Chrome removed would end up with two copies
of the same repository.
The DVD codec helper previously assumed deb822 sources and did nothing
on a machine using a classic /etc/apt/sources.list, which is what the
hardware actually runs. It now handles both formats, and only ever adds
a missing component to an existing Debian entry — so it cannot produce a
duplicate repository or a duplicate component however many times it
runs, and third-party repositories like Chrome's are never touched. It
also answers libdvd-pkg's debconf questions, so nothing blocks on a
prompt in a service with no terminal.
Tested against fixture APT trees for classic sources.list, deb822
.sources, contrib already enabled, repeated runs, third-party
repositories and the awkward real lines: bracketed options, indentation,
trailing comments and flat repositories.
Co-Authored-By: Claude Opus 5 <[email protected]>