Troubleshooting
Common problems & fixes
Real issues seen setting up the Raspberry Pi kiosk and the in-app updater, with the exact cause and fix for each.
Kiosk doesn't start on login
Symptom. After speculum-kiosk-enable and a reboot, the mirror doesn't come up fullscreen.
- Not logged into the desktop. The autostart fires only when your desktop session starts. Enable desktop autologin:
raspi-config→ System Options → Boot / Auto Login → Desktop Autologin. A console-only boot won't run XDG autostart. - Enabled as the wrong user.
speculum-kiosk-enablewrites~/.config/autostart/for whoever runs it — run it as your desktop user, not root. Check it exists:cat ~/.config/autostart/speculum.desktop. - Didn't re-login. Autostart only runs at session start — log out/in or reboot after enabling.
- Compositor ignores XDG autostart. LXDE, labwc and wayfire on Pi OS honour it. On a minimal custom Wayland setup, add the launch to that compositor's own autostart, pointing at
/opt/speculum/libexec/speculum-kiosk-run.
Run the launcher directly from a desktop terminal to surface any error:
/opt/speculum/libexec/speculum-kiosk-run
Cursor shows / screen blanks under Wayland
The kiosk wrapper disables blanking and hides the cursor with xset / unclutter, which are X11-only. On the Wayland Pi desktops (labwc / wayfire) they don't take effect. Fixes:
- Blanking: turn off screen blanking in
raspi-config→ Display Options, or in the compositor (wayfire's[idle], orswayidle). - Cursor: install
unclutter(sudo apt install -y unclutter); under Xwayland it can still hide the pointer over the mirror.
Two mirrors / port 8080 in use
Symptom. The admin UI / curl localhost:8080/api/version reports an old version even after a successful update; pgrep -af speculum shows a process that predates the update.
Cause. Two mirrors are running at once — e.g. the kiosk autostart and a copy you launched by hand (or a custom systemd unit). They both try to bind port 8080; whichever started first holds it, and the updater may restart/relaunch the one that isn't on screen.
Fix — run exactly one launcher. Stop the extras, then start a single mirror:
pgrep -af speculum # see every instance
pkill -f /opt/speculum/bin/speculum # stop them all
# then start ONE — relaunch the kiosk (log out/in) or run `speculum` once
curl -s localhost:8080/api/version # confirm one instance, right version
If you made your own systemd unit for the mirror, disable it (sudo systemctl disable --now <your-unit>) and use speculum-kiosk-enable instead.
Updater says "install manually" / "release is unsigned"
Cause. The in-app updater is fail-closed: it only installs a release whose SHA256SUMS carries a valid GPG signature from the key bundled in the app. If the release has no SHA256SUMS.asc (signing not configured in CI), it refuses and points you at the releases page. Development builds also show this.
Fix. Update manually for now. For maintainers: configure the GPG_PRIVATE_KEY / GPG_PASSPHRASE repo secrets (matching KEYS) and cut a release — the release workflow then signs every asset and the updater goes live automatically.
"Couldn't check GitHub for updates"
- No network / rate-limited. The check uses the unauthenticated GitHub API. Verify outbound HTTPS to
api.github.com. - Wrong repo. The default repo is the canonical
pierrejochem/SpeculumSmartMirror. If you set a customrepoin theupdatenotifiermodule config, make sure it points at a repo that publishes signed releases.
Update fails: permission denied / read-only / timeout
These were bugs fixed in v0.5.6; if you hit them, update to v0.5.6+ (manually once) and they go away:
| Journal line | Cause & fix |
|---|---|
staged.pkg: Permission denied | Old builds staged into a root-owned dir tied to user pi. Now staged under the mirror user's own ~/.speculum/update. Fixed in v0.5.6. |
Read-only file system creating the package | The updater unit set ProtectHome=read-only, blocking the home-staging write. Removed in v0.5.6. |
Request timeout has expired mid-download | The HTTP client's default 15 s request cap aborted the ~100 MB download. The downloader now has no whole-request timeout (only a stall timeout). Fixed in v0.5.6. |
| "could not authorize (polkit)" | polkit isn't installed or the rule didn't load. Confirm polkit is present, or use the sudoers fallback. The rule authorizes any active local session to start speculum-update.service. |
Inspect the privileged side any time with:
journalctl -u speculum-update.service -n 40 --no-pager
Updated, but the screen still shows the old version
Cause. The package installed, but the running mirror process wasn't the one that got restarted — usually the two-mirrors situation, or the on-screen mirror runs a different launcher than the one the helper restarted.
Fix. Settle on one launch method (above). The helper restarts a system service only if one is active; a --user service or a desktop-session launch is restarted by the app itself. The installed files are already the new version, so simply relaunching the on-screen mirror once brings it to the new release.