ci.yml runs tools/check.sh and the container gate on every push and pull request, with no secrets. Release 1 builds a version from its release commit on master or release/*, gates it, and keeps it in a Gitea pre-release. Release 2 gates that file again on the CI runner and publishes it to canary (Preview) from the release runner. Release 3 promotes the file Preview ran, on a person's approval. Import records a release from before Gitea kept the record. All of it is tools/release-ci.sh calling the existing scripts. docs/releasing.md: tracks, branches, each step, immutability, rollback, secrets, runners, Nightly and the hardware lab as designs, the Gitea administration still to do, and what was measured to make it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
254 lines
11 KiB
Markdown
254 lines
11 KiB
Markdown
# Provisioning
|
|
|
|
Some things a classroom needs cannot be installed by the classroom
|
|
package itself: they need the network, and they need the apt lock that
|
|
dpkg is holding for the whole of the package's `postinst`.
|
|
|
|
`ekklesia-classroom-provision.service` runs after every boot and does
|
|
them. It is a `oneshot` unit that nothing else waits on, so it never
|
|
holds the classroom up, and **it is not allowed to fail the boot**: a
|
|
machine with no DVD codecs is still a working classroom.
|
|
|
|
The package `postinst` enables the service and schedules a delayed start after
|
|
the outer apt transaction has had time to release its lock. It never runs
|
|
provisioning synchronously inside dpkg. If an OTA reboot happens before the
|
|
delayed job, the enabled service runs normally on the next boot.
|
|
|
|
To provision without waiting for a reboot:
|
|
|
|
```
|
|
sudo classroom-provision
|
|
```
|
|
|
|
Each step checks whether it is already done and returns immediately if
|
|
so. A provisioned machine costs nothing; a machine that was offline the
|
|
first time gets another go at the next boot.
|
|
|
|
```
|
|
journalctl -u ekklesia-classroom-provision
|
|
```
|
|
|
|
## Google Chrome
|
|
|
|
Chrome is not in Debian and a classroom is not usable without it, so a
|
|
fresh machine installs it itself rather than waiting for someone to do it
|
|
by hand on every box.
|
|
|
|
`classroom-chrome-setup` adds Google's signing key to
|
|
`/etc/apt/keyrings/google-chrome.gpg` and their repository to
|
|
`/etc/apt/sources.list.d/google-chrome.list` — the same path Google's own
|
|
package manages, so the two cannot become two copies of the same
|
|
repository — then installs `google-chrome-stable`. Using the repository
|
|
rather than a one-off `.deb` means Chrome keeps getting security updates.
|
|
|
|
Run it by hand with `sudo classroom-chrome-setup`. It returns
|
|
immediately if Chrome is already installed.
|
|
|
|
The Chrome policies in `/etc/opt/chrome/policies/` are shipped by the
|
|
classroom package and apply as soon as Chrome exists.
|
|
|
|
## DVD codecs
|
|
|
|
Only for rooms with `DVD_ENABLED=true`. See [dvd.md](dvd.md).
|
|
|
|
## Networking and printing
|
|
|
|
NetworkManager and CUPS are Debian package dependencies, not deferred network
|
|
provisioning steps. Their services are enabled by the package and neither
|
|
requires Internet access or hardware to be present.
|
|
|
|
- Wi-Fi administration: [networking.md](networking.md)
|
|
- Church color printer setup: [printing.md](printing.md)
|
|
|
|
The printer helper is part of every provisioning pass. It is capped at 30
|
|
seconds and only configures the local scheduler, so an offline Ricoh cannot
|
|
hold boot or provisioning open. Failures are logged and retried on the next
|
|
boot. A correctly configured queue is a quick no-op.
|
|
|
|
## What provisioning does not do
|
|
|
|
The boot provisioning command never upgrades or reconfigures anything already installed, and it
|
|
never touches a repository it did not add. Classroom software updates
|
|
are the OTA channel's job, not this.
|
|
|
|
## When a classroom updates
|
|
|
|
**Every day at 03:00**, local time, spread over half an hour so the rooms
|
|
do not all ask at once: `ekklesia-classroom-update.timer`,
|
|
`OnCalendar=*-*-* 03:00:00`, `RandomizedDelaySec=30m`. It has been so
|
|
since 0.1.5; the lab's installed stable machine logged its checks at 03:01
|
|
on 26 September and 03:04 on 27 September.
|
|
|
|
**A computer that was switched off at 03:00** checks once, soon after it
|
|
is next switched on (`Persistent=true`). Measured on the lab's systemd
|
|
with the machine really stopped across the time: the missed check ran 50 s
|
|
after it started, once, and not again. Nothing else starts the check —
|
|
no boot trigger, no repeat — so it cannot loop.
|
|
|
|
Each check follows only the device's own `CHANNEL` (a stable room stays
|
|
on stable), installs only a newer version, and only a package whose
|
|
SHA-256, name and version are what the manifest says.
|
|
|
|
**The reboot after an update waits for nobody to be using the computer**
|
|
(since 0.2.6.5). It used to happen the moment the update was installed:
|
|
harmless at 03:00, and a room restarting itself mid-morning after a night
|
|
switched off. Now `classroom-reboot-when-idle` reboots once no keyboard,
|
|
mouse, touchpad or touchscreen has been touched for 30 minutes *and* no
|
|
sound has played for as long — a video to the room is not an idle
|
|
computer. At 03:00 that is about 03:30. Switched off before then, nothing
|
|
is lost: the update is installed and the next start is on it. Until the
|
|
reboot the session carries on as it was; the only other thing an update
|
|
starts at once, provisioning, does nothing on a machine that is already
|
|
set up.
|
|
|
|
Input is read from the kernel's input devices, whatever the desktop is
|
|
doing; sound from the kernel's own playback state (`/proc/asound`). Both
|
|
measured in the lab — a mouse made with `/dev/uinput` kept it from
|
|
rebooting, and leaving it alone let it — except the sound card, which the
|
|
lab does not have: that is a check on real hardware.
|
|
|
|
The schedule is a file the package installs, not a configuration file,
|
|
so every upgrade brings the packaged schedule. The release gate checks it
|
|
after a fresh install and after each upgrade.
|
|
|
|
systemctl list-timers ekklesia-classroom-update.timer # next and last check
|
|
journalctl -u ekklesia-classroom-update # what each check did
|
|
journalctl -u ekklesia-classroom-reboot-when-idle # a reboot that is waiting
|
|
|
|
## Permanent download addresses
|
|
|
|
```
|
|
https://updates.riverofglorymn.org/stable/latest.deb
|
|
https://updates.riverofglorymn.org/canary/latest.deb
|
|
```
|
|
|
|
One address per channel that never changes. Each is a symlink beside that
|
|
channel's manifest, pointing at the versioned package the manifest names,
|
|
so there is still exactly one record of which version is current — the
|
|
manifest. Nothing here is a second version database.
|
|
|
|
The versioned files under `/packages/` are never rewritten. A release
|
|
uploads its package, then the manifest, then swaps the pointer by rename.
|
|
Renaming is atomic, so nothing downloads a half-written pointer, and
|
|
going last means `latest.deb` can lag the manifest by a moment but can
|
|
never lead it: it will never offer a package the channel has not
|
|
published.
|
|
|
|
`latest.deb` and `manifest.json` are served `no-store, no-cache,
|
|
must-revalidate`. That header matters more than it looks: Cloudflare sits
|
|
in front of this server and caches `.deb` by extension, so without it a
|
|
room could be handed the previous release from an edge for hours. The
|
|
versioned packages are deliberately left cacheable — they are immutable.
|
|
|
|
**Installing from a channel URL does not move a machine onto that
|
|
channel.** `CHANNEL` in device.conf is the only thing that decides which
|
|
manifest the updater follows, and the package's postinst only ever
|
|
appends settings that are missing. Downloading the canary package to fix
|
|
a machine by hand leaves it on stable, and the next automatic update
|
|
brings it back to stable.
|
|
|
|
Verify a download against the channel it came from:
|
|
|
|
```
|
|
curl -fsSL -o c.deb https://updates.riverofglorymn.org/canary/latest.deb
|
|
sha256sum c.deb
|
|
curl -fsS https://updates.riverofglorymn.org/canary/manifest.json
|
|
```
|
|
|
|
## Version numbers and releasing
|
|
|
|
```
|
|
0.2.6 a feature release
|
|
0.2.6.1 the first maintenance release of 0.2.6
|
|
0.2.6.2 the second
|
|
0.2.7 the next feature release
|
|
0.3.0 a larger milestone
|
|
```
|
|
|
|
Three parts for a feature release, four for a maintenance release of one.
|
|
The leading zero stays until Classroom is feature-complete; 1.0.0 belongs
|
|
to the wider Ekklesia platform's milestone. Nothing else is a version: no
|
|
leading zeros (dpkg reads `0.2.06` as `0.2.6`), no `0.2.6.0`, no suffixes.
|
|
The build, both publish scripts and every classroom's updater refuse
|
|
anything else, from one pattern held to one list of cases by
|
|
`tests/test-release-versions.sh`.
|
|
|
|
Which of two versions is newer is always `dpkg --compare-versions`, never
|
|
our own arithmetic, because dpkg is what installs them:
|
|
`0.2.2 < 0.2.6 < 0.2.6.1 < 0.2.6.2 < 0.2.6.10 < 0.2.7`.
|
|
|
|
### A published version is never used again
|
|
|
|
0.2.6 was rebuilt with later fixes and uploaded a second time under the
|
|
same number, replacing the first upload. A room that had installed the
|
|
first one could never be offered the second: the updater only installs a
|
|
version newer than its own. So now:
|
|
|
|
* `update_canary.sh VERSION` refuses a version that has ever been
|
|
published — on the server or in `repo/packages` — and one that is not
|
|
newer than every package ever published. It checks before building and
|
|
again after the Trixie gate.
|
|
* Packages are uploaded with `rsync --ignore-existing`, and the checksum on
|
|
the server is compared with the one built before anything points at it.
|
|
* A manifest is only written once the server holds exactly the package
|
|
and checksum it names.
|
|
* After publishing, the channel is read back over HTTPS the way a classroom
|
|
reads it: manifest, versioned package, `latest.deb` and the version dpkg
|
|
reads out of the file must all agree.
|
|
|
|
A publish that fails after the local copy into `repo/packages` has spent
|
|
that number. Use the next one.
|
|
|
|
### Canary (Preview)
|
|
|
|
Since 0.2.6.6 releases normally go through Gitea: built once, recorded as
|
|
a Gitea release, published and promoted by workflows that call these same
|
|
scripts. [releasing.md](releasing.md) has the whole path. By hand:
|
|
|
|
```
|
|
./update_canary.sh 0.2.6.7 dist/ekklesia-classroom_0.2.6.7_amd64.deb # gate and publish THAT file
|
|
./update_canary.sh 0.2.7 # build, gate and publish
|
|
```
|
|
|
|
The version is always given. With four parts there are two possible next
|
|
numbers, and which one a release is depends on what is in it. Run with no
|
|
argument it prints the current canary and both candidates, and builds
|
|
nothing.
|
|
|
|
Given a package, that file is what is gated and published; nothing is
|
|
rebuilt, because a second build is a second, untested file. The gate is
|
|
`tools/release-gate.sh`: a fresh install, and the upgrade from whatever
|
|
canary and stable serve, each on a room of that channel;
|
|
`RELEASE_GATE_FROM=0.2.2` adds an older release rooms are still on.
|
|
|
|
### Stable
|
|
|
|
```
|
|
./update_stable.sh 0.2.6.6 <sha256>
|
|
```
|
|
|
|
Only with the owner's written approval for that release. Stable is never
|
|
given a package of its own: it is pointed at one canary already published,
|
|
byte for byte, and refuses a version that is not newer than stable's
|
|
current one or whose package the server does not hold with the same
|
|
checksum. With the sha256 — always give it — it refuses any file but the
|
|
one that was approved.
|
|
|
|
### Checking a channel without changing it
|
|
|
|
```
|
|
ROOT=$PWD bash -c 'source tools/release-lib.sh; release_verify_channel stable; release_verify_channel canary'
|
|
```
|
|
|
|
### Rehearsing an upgrade
|
|
|
|
`tools/validate-trixie-release.sh NEW.deb [PREVIOUS.deb]` installs into a
|
|
clean Debian 13 container. `VALIDATION_CHANNEL=stable` rehearses a stable
|
|
room, for example the desktops' real path:
|
|
|
|
```
|
|
VALIDATION_CHANNEL=stable tools/validate-trixie-release.sh \
|
|
dist/ekklesia-classroom_0.2.6.1_amd64.deb \
|
|
repo/packages/ekklesia-classroom_0.2.2_amd64.deb
|
|
```
|