A headless aarch64 companion for a Psion 5MX: PPP over RS232 with NAT out to wifi and a telnet login, plus a cleartext POP3/SMTP proxy for the Psion's mail client. - hosts/PiZero2W/: host config, serial-ppp.nix, email-proxy.nix, an SD-image variant, and a hardware-configuration.nix placeholder. - Host table entry on nixos-hardware's raspberry-pi-3 profile; the Zero 2 W is the Pi 3's BCM2837 SoC. nixpkgs' linuxPackages_rpi02w is deprecated and warns that the linux-rpi series is being removed in favour of nixos-hardware. - The host owns its firmware partition (hardware.raspberry-pi.firmware), which is what puts the disable-bt and uart0/ctsrts overlays in config.txt so /dev/ttyAMA0 is the RS232 header rather than Bluetooth. uboot.enable keeps the U-Boot -> extlinux boot path the rewritten config.txt would otherwise lose. - packages.aarch64-linux.zero2w-sd-image: the host's own configuration as an installable card. The board has no Ethernet and no free serial port, so a generic image would leave no way in. - The mail proxy comes from the legacy-email-proxy flake, which provides the package and the NixOS module; nothing about it is vendored here. - docs/hosts/pizero2w.md, plus README host table and shared-layer notes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
11 KiB
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.
Two roles, split into submodules:
- PPP link + telnet (
serial-ppp.nix) —pppdon/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. That project ships its own package and NixOS module, soemail-proxy.nixhere is onlyservices.legacy-email-proxy.enableplus 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:
# 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.
- Set the SSID.
networking.wireless.networksinconfiguration.nixstill saysCHANGE-ME-SSID. It is baked into the image at build time; only the PSK is read at runtime. - Build and write the card. On an
aarch64-linuxmachine (or with one configured as a builder):Checknix 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/dev/sdXtwice.dddoes not ask. - 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:
Skip the PSK and the board boots with no network at all.
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 - Boot it. Give it a few minutes on first boot — it resizes the root
partition and generates host keys on a slow card. Then:
ssh lyrathorpe@lyrathorpe-zero2w.local # mDNS; services.avahi publishes it - Give the login user a password (
passwd lyrathorpe) if you want console or telnet login; the SSH key fromusers/registry.nixalready works without one. - 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 —
pppdsends 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)
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. |