# Setting up a new Pi > For a Pi with a **Waveshare 3.5" HDMI touchscreen**, do this base setup first, > then follow [`pi-desk-3.5-screen.md`](./pi-desk-3.5-screen.md) for the screen > and touch configuration. ## Hardware - Raspberry Pi 4B (dev) or 3B+ (works fine), any HDMI screen for now. - Raspberry Pi OS **with desktop** (not Lite — Chromium kiosk is required). - Set the hostname to `pi-` (e.g. `pi-aurel`, `pi-flo`) via `raspi-config` or the Imager pre-config — the owner name is derived from it automatically. ## Provisioning with Ansible (recommended) From a freshly flashed Pi (OS + Wi-Fi + SSH key) a single run from the Mac does everything below — installs Node via nvm, clones the repo, builds the native modules, and lays down the systemd service, kiosk autostart, screen blanking, and the USB Wi-Fi watchdog. It's idempotent, so re-runs only fix drift. ```bash # Add the Pi to ansible/inventory.ini (hostname, ansible_user, optional crema_screen), # then from the repo: cd ansible ansible-playbook playbook.yml --ask-become-pass --limit pi- sudo reboot # or: ssh @pi-.local sudo reboot — to land in the kiosk ``` `--ask-become-pass` is only needed the **first** time: that run installs a passwordless-sudo drop-in for the deploy user, so later runs (and CI) escalate without a prompt. See *sudo — passwordless (NOPASSWD)* in [`ansible/README.md`](../ansible/README.md), which also covers prerequisites, tags (`deploy` / `service` / `transport` / `display` / `watchdog` / `reload` / `reboot` / `sudoers`) and broker pinning. > **Addressing tip:** the inventory prefers a Pi's **static LAN IP** over > `pi-.local`. An IP resolves from anywhere on the network (including a CI > runner); `.local` needs mDNS, which only works from the Mac. Pin each Pi to a > DHCP reservation on the router and put that IP in `ansible_host`. The manual steps below are the fallback if you'd rather not use Ansible. ## Manual one-time install (alternative) ```bash # 1. SSH in and install Node 20 via nvm ssh @pi-.local curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash source ~/.bashrc nvm install 20 # 2. Install the native mDNS bridge to avahi sudo apt update sudo apt install -y libavahi-compat-libdnssd-dev # 3. Clone and install Crema git clone https://github.com/Aurel-Charles/crema.git cd crema npm install # 4. Wire up systemd + Chromium autostart + emoji font + disable blanking ./install-pi.sh # 5. Reboot to confirm full boot path works sudo reboot ``` `install-pi.sh` is idempotent and also installs `fonts-noto-color-emoji` (Raspberry Pi OS ships without any emoji font, which makes labels like `👍` render as empty boxes in Chromium). After reboot the Pi should land in the kiosk display showing the clock. The PWA is reachable from any device on the LAN at `http://pi-.local:3000`. ## Deploying code updates From your dev machine, push to `main`, then on each Pi: ```bash ssh @pi-.local "cd ~/crema && git pull && sudo systemctl restart crema" ``` Or, with Ansible: `ansible-playbook playbook.yml --ask-become-pass --tags deploy` (git pull + npm install, on every Pi or one via `--limit`). When the code actually changed, `deploy` then restarts the Node server **and** reloads the Chromium kiosk, so both backend and frontend edits take effect. To force those without a pull use `--tags reload`; to reboot the Pis one at a time use `--tags reboot` (opt-in — never runs on a normal deploy). If `package.json` changed, add `&& npm install` before the restart. On 32-bit Pi OS (armv7), `better-sqlite3` compiles from source on `npm install` — `install-pi.sh` already provisions `build-essential` and `python3` so this just works, but the first install takes a few minutes. To reload the display without rebooting (kiosk has an auto-restart loop): ```bash ssh @pi-.local pkill -f chromium ``` ## Continuous deploy (Semaphore CI) The same Ansible playbook also runs unattended from a **Semaphore** runner on the home LAN (template `Deploy Crema (pi-test)`), so a code update can be a one-click deploy instead of an SSH session. Semaphore clones the repo from GitHub each run, so the inventory/playbook changes must be on `main` to take effect. Two things make the runner work where a naive setup fails: - **Static IPs, not `.local`** — the runner has no mDNS, so the inventory addresses Pis by reserved LAN IP (see the addressing tip above). - **Passwordless sudo** — the runner can't type a become password, so the playbook's `sudoers` task grants the deploy user NOPASSWD sudo (bootstrap it once from the Mac with `--ask-become-pass`, then the runner needs no secret). With both in place the template needs **no secrets at all**: no SSH password, no sudo password, no vault. See [`ansible/README.md`](../ansible/README.md) for the NOPASSWD details. ## Docker Hub image Pushing to `main`, pushing a `v*` tag, or manually running the `Publish Docker image` GitHub Actions workflow builds and publishes `lowess/crema` to Docker Hub for `linux/amd64` and `linux/arm64`. The workflow logs in as Docker Hub user `Lowess` and expects a repository secret named `DOCKERHUB_TOKEN`.