206 lines
11 KiB
Markdown
206 lines
11 KiB
Markdown
# Raspberry Pi Zero 2 W (`lyrathorpe-zero2w`)
|
|||
|
|
|
||
|
|
Headless `aarch64-linux` "Psion sidecar": an RS232 companion for a Psion 5MX,
|
||
|
|
after [Kian Ryan's PPP modem and terminal
|
||
|
|
write-up](https://www.kianryan.co.uk/2022-11-28-psion-sidecar-ppp-modem-and-terminal/).
|
||
|
|
Two roles, split into submodules:
|
||
|
|
|
||
|
|
- **PPP link + telnet** (`serial-ppp.nix`) — `pppd` on `/dev/ttyAMA0`, the Psion
|
||
|
|
on the far end of a null-modem cable, NAT out to Wi-Fi, and a telnet login for
|
||
|
|
the Psion's terminal client.
|
||
|
|
- **Legacy mail proxy** (`email-proxy.nix`) — cleartext POP3/SMTP for the
|
||
|
|
Psion's built-in mail client, forwarded to authenticated IMAPS/SMTPS by
|
||
|
|
[legacy-email-proxy](https://code.emmathe.dev/lyrathorpe/legacy-email-proxy).
|
||
|
|
That project ships its own package and NixOS module, so `email-proxy.nix`
|
||
|
|
here is only `services.legacy-email-proxy.enable` plus a path to the
|
||
|
|
credentials — nothing about the proxy is vendored into this flake.
|
||
|
|
|
||
|
|
`sd-image.nix` in the same directory is not part of the running system: it is
|
||
|
|
the one-shot install card, built as `packages.aarch64-linux.zero2w-sd-image`.
|
||
|
|
See "Install".
|
||
|
|
|
||
|
|
## Hardware and boot
|
||
|
|
|
||
|
|
The Zero 2 W is a BCM2837 — the Pi 3's SoC — so the host table uses
|
||
|
|
`nixos-hardware`'s `raspberry-pi-3` profile for the kernel, firmware and device
|
||
|
|
tree. Boot is the same U-Boot + extlinux path as the other Pi.
|
||
|
|
|
||
|
|
Unlike the Pi 5, this host owns the firmware partition declaratively
|
||
|
|
(`hardware.raspberry-pi.firmware.enable`): every `switch` rewrites
|
||
|
|
`/boot/firmware`, including `config.txt`. Two settings there matter:
|
||
|
|
|
||
|
|
| `config.txt` | Why |
|
||
|
|
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
|
|
| `dtoverlay=disable-bt` | Moves the PL011 UART off Bluetooth onto GPIO 14/15, so `/dev/ttyAMA0` is the RS232 header. The mini UART (`ttyS0`) drifts at 115200. |
|
||
|
|
| `dtoverlay=uart0,ctsrts` | RTS/CTS on GPIO 16/17. Both `pppd` and the Psion's modem profile use hardware flow control. |
|
||
|
|
| `kernel=u-boot.bin` | `hardware.raspberry-pi.firmware.uboot.enable`. Without it the rewritten `config.txt` would have no `kernel=` line and the board would stop booting. |
|
||
|
|
|
||
|
|
`gpu_mem=16`, `start_x=0`, `camera_auto_detect=0` and `display_auto_detect=0`
|
||
|
|
hand the VideoCore the minimum: the board has 512 MB total and no display.
|
||
|
|
|
||
|
|
## Never build on the Pi
|
||
|
|
|
||
|
|
512 MB of RAM and an SD card. It cannot compile its own system, and there is
|
||
|
|
deliberately no swap partition (SD cards wear out under swap writes) — zram
|
||
|
|
takes its place. Build somewhere else and push the result:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
# from a workstation, using another aarch64 machine as the builder
|
||
|
|
nixos-rebuild switch --flake .#lyrathorpe-zero2w \
|
||
|
|
--build-host lyrathorpe@lyrathorpe-rpi5 \
|
||
|
|
--target-host lyrathorpe@<pi-address> --use-remote-sudo
|
||
|
|
```
|
||
|
|
|
||
|
|
The `raspberry-pi-3` profile builds the vendor kernel from source and it is not
|
||
|
|
in the binary cache, so the first build is long (hours on the Pi 5, less on the
|
||
|
|
MacBook). Later builds reuse it. The same applies to the SD image below: it
|
||
|
|
contains that kernel, so it needs an `aarch64-linux` builder too. From an
|
||
|
|
`x86_64` box or a Mac, that means a remote builder (`nix.buildMachines`) or, on
|
||
|
|
Darwin, `nix.linux-builder.enable`.
|
||
|
|
|
||
|
|
## Install
|
||
|
|
|
||
|
|
The card is built from this flake, not downloaded. A generic NixOS image would
|
||
|
|
boot, but there would be no way into the machine afterwards: it has no Ethernet,
|
||
|
|
no wifi credentials, and this configuration hands the serial port to `pppd`, so
|
||
|
|
there is no console either. Building the host's own image sidesteps all three —
|
||
|
|
the first boot is already the real system, with the SSH key from the registry
|
||
|
|
in place.
|
||
|
|
|
||
|
|
1. **Set the SSID.** `networking.wireless.networks` in `configuration.nix` still
|
||
|
|
says `CHANGE-ME-SSID`. It is baked into the image at build time; only the PSK
|
||
|
|
is read at runtime.
|
||
|
|
2. **Build and write the card.** On an `aarch64-linux` machine (or with one
|
||
|
|
configured as a builder):
|
||
|
|
```sh
|
||
|
|
nix build .#packages.aarch64-linux.zero2w-sd-image
|
||
|
|
sudo dd if=result/sd-image/nixos-zero2w.img of=/dev/sdX bs=4M conv=fsync status=progress
|
||
|
|
```
|
||
|
|
Check `/dev/sdX` twice. `dd` does not ask.
|
||
|
|
3. **Seed the secrets before first boot.** They are not in the image. Mount the
|
||
|
|
card's second partition (the ext4 root) and write both files described under
|
||
|
|
"Secrets" below:
|
||
|
|
```sh
|
||
|
|
sudo mount /dev/sdX2 /mnt
|
||
|
|
sudo mkdir -p /mnt/var/lib/wpa_supplicant /mnt/var/lib/legacy-email-proxy
|
||
|
|
printf 'psk_home=%s\n' 'the-pre-shared-key' \
|
||
|
|
| sudo tee /mnt/var/lib/wpa_supplicant/secrets.conf > /dev/null
|
||
|
|
sudo chmod 600 /mnt/var/lib/wpa_supplicant/secrets.conf
|
||
|
|
# ... and /mnt/var/lib/legacy-email-proxy/backend.env, same permissions
|
||
|
|
sudo umount /mnt
|
||
|
|
```
|
||
|
|
Skip the PSK and the board boots with no network at all.
|
||
|
|
4. **Boot it.** Give it a few minutes on first boot — it resizes the root
|
||
|
|
partition and generates host keys on a slow card. Then:
|
||
|
|
```sh
|
||
|
|
ssh lyrathorpe@lyrathorpe-zero2w.local # mDNS; services.avahi publishes it
|
||
|
|
```
|
||
|
|
5. **Give the login user a password** (`passwd lyrathorpe`) if you want console
|
||
|
|
or telnet login; the SSH key from
|
||
|
|
[`users/registry.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/registry.nix)
|
||
|
|
already works without one.
|
||
|
|
6. Thereafter, rebuild from another machine as in the previous section.
|
||
|
|
|
||
|
|
`hosts/PiZero2W/hardware-configuration.nix` is a **placeholder** — but its
|
||
|
|
layout (`/` on label `NIXOS_SD`, `/boot/firmware` on label `FIRMWARE`) is
|
||
|
|
exactly what the SD image produces, so there is nothing to regenerate for a card
|
||
|
|
install. Run `nixos-generate-config` and replace it only if you deviate from
|
||
|
|
that layout.
|
||
|
|
|
||
|
|
If the board never appears on the network, it is almost always the PSK file.
|
||
|
|
Re-mount the card and check it. Failing that, a mini-HDMI monitor and a
|
||
|
|
micro-USB keyboard get you a console on `tty1` — the serial port will not,
|
||
|
|
because `pppd` holds it.
|
||
|
|
|
||
|
|
## Secrets (not in the Nix store)
|
||
|
|
|
||
|
|
Both files are created on the device, owned by root, mode `0600`. Neither is
|
||
|
|
managed by this flake; the units that read them fail loudly if they are absent.
|
||
|
|
|
||
|
|
**Wi-Fi PSK** — `/var/lib/wpa_supplicant/secrets.conf`:
|
||
|
|
|
||
|
|
```
|
||
|
|
psk_home=<the pre-shared key>
|
||
|
|
```
|
||
|
|
|
||
|
|
The SSID itself _is_ in `configuration.nix` and is currently the placeholder
|
||
|
|
`CHANGE-ME-SSID`; set it to the real network. `wpa_supplicant` resolves
|
||
|
|
`pskRaw = "ext:psk_home"` against this file at runtime.
|
||
|
|
|
||
|
|
**Mail backend** — `/var/lib/legacy-email-proxy/backend.env`, a systemd
|
||
|
|
`EnvironmentFile`:
|
||
|
|
|
||
|
|
```
|
||
|
|
BACKEND_IMAP_HOST=imap.example.com
|
||
|
|
BACKEND_IMAP_USER=someone@example.com
|
||
|
|
BACKEND_IMAP_PASS=<app password>
|
||
|
|
BACKEND_SMTP_HOST=smtp.example.com
|
||
|
|
BACKEND_SMTP_USER=someone@example.com
|
||
|
|
BACKEND_SMTP_PASS=<app password>
|
||
|
|
```
|
||
|
|
|
||
|
|
Ports and TLS default sensibly (IMAPS 993, SMTPS 465); the full variable list is
|
||
|
|
in the proxy's README.
|
||
|
|
|
||
|
|
### Why POP3 and not IMAP
|
||
|
|
|
||
|
|
The Psion's built-in mail client speaks POP only, so POP3 is what the proxy
|
||
|
|
exposes. If a third-party IMAP client is ever installed on the device, the
|
||
|
|
answer is **not** to add an IMAP frontend to the proxy: the backend is already
|
||
|
|
IMAP, so there is no protocol to translate, only TLS to remove. An `stunnel`
|
||
|
|
client (plaintext 143 on the PPP link, IMAPS 993 outbound) does that in a few
|
||
|
|
lines with no code, and credentials pass straight through — IMAP clients always
|
||
|
|
authenticate.
|
||
|
|
|
||
|
|
SMTP stays on the proxy either way. A client of this vintage cannot do SMTP
|
||
|
|
AUTH, which is exactly why the proxy injects the backend credentials.
|
||
|
|
|
||
|
|
## Psion configuration
|
||
|
|
|
||
|
|
Matches the addressing in `serial-ppp.nix` (`10.0.0.1` the Pi, `10.0.0.2` the
|
||
|
|
Psion):
|
||
|
|
|
||
|
|
- **Modem** control panel, a "Direct Cable Connection" profile: 115200 baud,
|
||
|
|
Hardware (RTS/CTS) flow control; on the Advanced tab, Terminal Detect and
|
||
|
|
Carrier Detect both **off**.
|
||
|
|
- **Internet** control panel, a new profile: Connection Type **Direct**, Manual
|
||
|
|
Login **True**. Addresses: get IP from server **False**, static **10.0.0.2**.
|
||
|
|
Get DNS from server **True** — `pppd` sends resolvers over the link
|
||
|
|
(`ms-dns`), so nothing is hard-coded on the Psion.
|
||
|
|
- Advanced: PPP extensions **False**, plain-text authentication **True**.
|
||
|
|
- Terminal client: telnet to **10.0.0.1 port 23**. It renders non-ANSI output
|
||
|
|
far better than the raw serial console does.
|
||
|
|
- Mail client: POP3 and SMTP server **10.0.0.1**, no encryption, no
|
||
|
|
authentication.
|
||
|
|
|
||
|
|
## Security
|
||
|
|
|
||
|
|
Everything on this host that the Psion talks to is unauthenticated and
|
||
|
|
unencrypted, because a 1999 palmtop speaks no TLS:
|
||
|
|
|
||
|
|
- **telnet on 23** — cleartext login, including the password.
|
||
|
|
- **POP3 on 110 / SMTP on 25** — full mailbox access and an open relay to anyone
|
||
|
|
who reaches them.
|
||
|
|
|
||
|
|
The confinement is the firewall, and it is the only thing standing there:
|
||
|
|
`ppp0` is a trusted interface, `wlan0` is not, and those ports are never opened
|
||
|
|
on it. The proxy binds `0.0.0.0` rather than `10.0.0.1` on purpose — the PPP
|
||
|
|
address only exists while the Psion is plugged in, and a bind-time dependency on
|
||
|
|
a serial cable is a restart loop waiting to happen. Do not add these ports to
|
||
|
|
`networking.firewall.allowedTCPPorts`, and do not put this board on an untrusted
|
||
|
|
network.
|
||
|
|
|
||
|
|
Only sshd (port 22, key-only, via
|
||
|
|
[`modules/ssh.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/modules/ssh.nix))
|
||
|
|
is reachable over Wi-Fi.
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
| Symptom | Check |
|
||
|
|
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
|
|
| No PPP at all | `systemctl status pppd-psion`, then `journalctl -u pppd-psion -f` while the Psion dials. `passive`/`persist` mean it waits, not fails. |
|
||
|
|
| PPP negotiates, then hangs | Flow control. Confirm `dtoverlay=uart0,ctsrts` is in `/boot/firmware/config.txt` and that the Psion's modem profile is set to Hardware. |
|
||
|
|
| `/dev/ttyAMA0` missing or is a Bluetooth device | `disable-bt` did not apply — the firmware partition was not rewritten. Confirm `/boot/firmware` is a mounted partition; the activation script skips with a warning if it is not. |
|
||
|
|
| Something else holds the port | `systemctl status serial-getty@ttyAMA0` — it is disabled in `serial-ppp.nix`, and must stay that way. |
|
||
|
|
| Mail proxy dead | `systemctl status legacy-email-proxy`. A missing `backend.env` fails the unit before it starts. |
|