Deploying the service on the Raspberry Pi¶
Verified on the maintainer's Raspberry Pi 5 (Raspberry Pi OS; hostname smokingpi, used
below only where an actual result is quoted) on 2026-09-18. Every command below was run
there before it was written down. Replace <server> with your machine's hostname.
What runs¶
paperwhite serve fetches weather every display.refresh_minutes, keeps the last good
snapshot, and answers on port 8765:
| Path | Response |
|---|---|
/health |
JSON: service, version, status (ok or no-data), provider, skin, default orientation, refresh interval, fetched_at, last_attempt_at, last_error, counters |
/dashboard.png |
Frame in the configured default orientation |
/dashboard/landscape.png, /dashboard/portrait.png |
Frame in that orientation |
/skins |
A page for a phone: the current frame and one button per skin (no scripts) |
POST /skin (form field name), POST /skin/<name>, POST /skin/next |
Switch the skin for every client from now on; /health reports it |
/ |
Plain-text list of the routes |
Frames are rendered when requested, with the request time on the clock, and memoized per
minute. Until the first successful fetch the frames say "No weather data yet" with the time
of the last fetch attempt (or "No fetch attempted yet"), not the request time, so a long
outage looks like one. A failed refresh keeps the previous snapshot and is reported in
/health as last_error.
The skin can be changed while the service runs, from any phone or laptop on the LAN:
open http://<server>.lan:8765/skins and tap a name. Every client shows the new skin at
its next refresh (a tap on the Kindle fetches a frame right away). display.skin in
config.yaml is the skin at startup; the runtime choice is persisted in
PAPERWHITE_STATE_DIR (the unit sets its StateDirectory, ~/.config/paperwhite-weather
on the maintainer's Pi: a user manager's %S is $XDG_CONFIG_HOME on systemd 252), so a
restart keeps it. Anyone on the LAN can switch it; there is no authentication, by design, as with
the frames themselves.
Configuration¶
The unit passes settings as environment variables, so it never needs editing:
| Variable | Default | Meaning |
|---|---|---|
PAPERWHITE_CONFIG |
<repo>/config.yaml |
Configuration file (git-ignored) |
PAPERWHITE_PROVIDER |
mock |
Weather provider name (paperwhite providers) |
PAPERWHITE_HOST |
0.0.0.0 |
Interface to listen on |
PAPERWHITE_PORT |
8765 |
TCP port |
PAPERWHITE_STATE_DIR |
the unit's StateDirectory (%S/paperwhite-weather: ~/.config/paperwhite-weather for a user manager on systemd 252); unset for a manual serve |
Keeps the skin chosen at runtime across restarts |
Override any of them in <repo>/.env (git-ignored; cp .env.example .env and edit) or
with systemctl --user edit paperwhite-weather.service. The same variables work for a
manual paperwhite serve; note that the shell does not read .env by itself, only the
unit does (EnvironmentFile).
Install¶
One command, from the checkout:
deploy/install.sh
It runs uv sync --locked, creates config.yaml and .env from the examples if they
do not exist (never overwriting them), writes the unit with the checkout's path, enables
and restarts it, warns if lingering is off, and waits for /health to answer. Re-run it
after editing .env or after git pull. Verified on the maintainer's Pi on 2026-09-20,
both on a fresh clone (mock provider, status ok, 1.4 s) and over the existing install
(config and .env kept, open-meteo, status ok).
Then edit config.yaml (coordinates, time zone, units, skin) and .env
(PAPERWHITE_PROVIDER=open-meteo for live weather), and
systemctl --user restart paperwhite-weather.service.
The same steps by hand:
cd ~/paperwhite-weather
uv sync
cp config.example.yaml config.yaml # then edit coordinates, time zone, units
cp .env.example .env # optional: provider, host, port
mkdir -p ~/.config/systemd/user
sed "s|%REPO%|$PWD|g" deploy/systemd/paperwhite-weather.service \
> ~/.config/systemd/user/paperwhite-weather.service
systemctl --user daemon-reload
systemctl --user enable --now paperwhite-weather.service
Requirements: uv on the path; loginctl show-user $USER -p Linger must print
Linger=yes so the user service starts at boot without a login
(sudo loginctl enable-linger $USER otherwise); port 8765 must be free
(ss -ltn | grep 8765), or set PAPERWHITE_PORT in .env.
Check¶
systemctl --user status paperwhite-weather.service
journalctl --user -u paperwhite-weather.service -f
curl -s http://<server>.lan:8765/health | python3 -m json.tool
curl -s -o /tmp/dashboard.png http://<server>.lan:8765/dashboard.png
On 2026-09-18 the service answered on http://smokingpi.lan:8765/ from the Pi itself
(getent hosts smokingpi.lan → 192.168.86.27, resolved by the router). Whether your
router resolves <hostname>.lan is router-specific; /health reports the server's
hostname, and the Kindle client falls back to a subnet scan (see docs/ARCHITECTURE.md).
Verified from the Kindle on 2026-09-19: wget of /health and /dashboard.png by that
name worked on the device (docs/DEVICE.md).
The unit defaults to the mock provider; put PAPERWHITE_PROVIDER=open-meteo in .env
for live weather (the maintainer's Pi runs it since the merge of PR #7, 2026-09-19).
Optional: mDNS advertisement¶
sudo cp deploy/avahi/paperwhite-weather.service /etc/avahi/services/
Installed on the maintainer's Pi on 2026-09-18. Unverified: avahi-utils is not installed
there, so avahi-browse -rt _paperwhite-weather._tcp could not be run. The Kindle client
does not depend on it.
Update¶
cd ~/paperwhite-weather && git pull && deploy/install.sh
(or uv sync and systemctl --user restart paperwhite-weather.service by hand).
Remove¶
systemctl --user disable --now paperwhite-weather.service
rm ~/.config/systemd/user/paperwhite-weather.service
sudo rm /etc/avahi/services/paperwhite-weather.service # if installed