Updating
One-click updates
A packaged Speculum can update itself from the web admin: it downloads the latest signed release, verifies it, installs it, and restarts the mirror — without you touching the Pi.
Using it
Open the web admin (http://<pi-ip>:8080) and find the Updates card:
- Up to date — you're on the latest release.
- Update available: v0.5.6 → v0.5.7 with an Update now button — click it.
- A muted message (e.g. "install it manually for now", "development build") — the in-app updater isn't available for this install; see Requirements.
After clicking, the card shows progress — Downloading… → Verifying… → Installing… — then "Speculum is restarting — this page will reconnect". When the new version answers, the version badge in the header flips to the new release.
How it works
The mirror runs unprivileged, so it can't install a system package on its own. The update is split across a privilege boundary:
[Updates card] ── POST /api/update/start ──▶ [config-server, in the mirror JVM]
1. find the latest GitHub release
2. pick the asset for this OS + CPU
3. download it + SHA256SUMS + .asc
4. verify GPG signature + SHA-256 (unprivileged)
5. stage it under ~/.speculum/update
6. systemctl start speculum-update.service
│ (authorized by a polkit rule)
▼
[speculum-update.service — runs as root]
7. re-verify signature + checksum AS ROOT
8. apt-get / dnf / pacman install the package
9. restart the mirror
The unprivileged side only prepares the update; the privileged helper re-verifies everything independently before installing. The boundary passes no arguments — the helper finds the staged package itself and trusts nothing the app asserts.
How the mirror restarts
After the package is installed, the running mirror has to be replaced. Speculum picks the right method for how you launched it:
| How you run the mirror | How it's replaced |
|---|---|
| Desktop-session autostart (the default kiosk) | The app relaunches the new binary in your session, then exits the old one. |
A --user systemd service | The app restarts its own user unit (systemctl --user restart …). |
| A custom system service you created | The root helper restarts it if it's active. |
8080 and the updater may restart the one that isn't on screen. Pick a single launch method — see Troubleshooting → two mirrors.Security model
- Signed, fail-closed. An update is applied only if
SHA256SUMScarries a valid GPG signature from the key bundled in the app and the package's SHA-256 matches. A missing or bad signature is a refusal — never a downgrade to checksum-only. - Verified twice. The mirror verifies before staging (a UX gate); the root helper re-verifies against its own root-owned copy of the key before installing (the security gate).
- Least privilege. The only standing privilege is a polkit rule letting a local session start one fixed unit (
speculum-update.service). That unit installs only genuine, signed Speculum packages. - Streamed to disk. Packages (~100 MB with a bundled JRE) never sit in memory — important on a 1 GB Pi.
See Verifying releases to check a signature yourself.
Requirements
- A packaged install —
.deb,.rpm, or Arch.pkg.tar.*. Development runs (./gradlew run) show "development build" and never self-update. - A signed release on GitHub (the in-app updater refuses unsigned releases). Releases are signed automatically when the project's signing key is configured in CI.
- polkit present (standard on Raspberry Pi OS) so the mirror can trigger the privileged install. A
sudoersfallback exists for systems without it. - Outbound HTTPS to
github.com/release-assets.githubusercontent.com.
Manual update (fallback)
You can always update by hand — and must, for the first install or an unsigned/old build:
# download the asset for your arch from the Releases page, then:
sudo apt install ./speculum_<version>_arm64.deb # Debian / Raspberry Pi OS
sudo dnf install ./speculum_<version>_arm64.rpm # Fedora / RHEL
sudo pacman -U ./speculum-<version>-1-aarch64.pkg.tar.zst # Arch
# then restart the mirror — relaunch the kiosk (log out/in),
# or just relaunch it however you started it