Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd97b694ad |
@@ -7,23 +7,23 @@ single flake.
|
||||
|
||||
Defined in the host table in [`flake.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/flake.nix):
|
||||
|
||||
| Configuration | System | Machine |
|
||||
| --------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) |
|
||||
| `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/t400/) |
|
||||
| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/macpro31/) |
|
||||
| `lyrathorpe-console` | `x86_64-linux` | Living-room games machine (Haswell i7 + GTX 1070) on a television — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/console/) |
|
||||
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) — [notes](https://docs.lyrapup.pet/nixfiles/hosts/edaas/) |
|
||||
| `lyrathorpe-rpi5` | `aarch64-linux` | Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/rpi5/) |
|
||||
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) — [notes](https://docs.lyrapup.pet/nixfiles/hosts/darwin/) |
|
||||
| Configuration | System | Machine |
|
||||
| --------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) |
|
||||
| `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/t400/) |
|
||||
| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/macpro31/) |
|
||||
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) — [notes](https://docs.lyrapup.pet/nixfiles/hosts/edaas/) |
|
||||
| `lyrathorpe-rpi5` | `aarch64-linux` | Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/rpi5/) |
|
||||
| `lyrathorpe-zero2w` | `aarch64-linux` | Raspberry Pi Zero 2 W "Psion sidecar": PPP over RS232 + legacy mail proxy — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/pizero2w/) |
|
||||
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) — [notes](https://docs.lyrapup.pet/nixfiles/hosts/darwin/) |
|
||||
|
||||
Shared layers: `home` (home-manager: shell, git, editor),
|
||||
`modules/common-nixos.nix` (all NixOS hosts: fonts, nix-ld, caches),
|
||||
`modules/workstation.nix` (physical graphical hosts: audio, thermald,
|
||||
earlyoom, fwupd), `modules/laptop.nix` (laptops: Wi-Fi, Bluetooth, power,
|
||||
lid), `modules/desktop.nix` (wired desktops: NetworkManager), and
|
||||
`modules/ssh.nix` (key-only sshd). The x86 hosts also pull `nixos-hardware`
|
||||
profiles. The full module catalogue is below.
|
||||
`modules/ssh.nix` (key-only sshd). The x86 hosts and both Raspberry Pis also
|
||||
pull `nixos-hardware` profiles. The full module catalogue is below.
|
||||
|
||||
## Repository layout
|
||||
|
||||
@@ -57,17 +57,17 @@ module reaches a host: **baseModules** (every NixOS host, via `flake.nix`),
|
||||
**host table** (listed explicitly per host in `flake.nix`), or **transitively**
|
||||
(pulled in by another module's `imports`).
|
||||
|
||||
| Module | Imported by | What it does / when to use it |
|
||||
| ------------------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `common-nixos.nix` | baseModules (all NixOS) | Timezone/locale, store hygiene (auto-optimise, big download buffer, **no** auto-GC), the nix-community binary cache, `nix-ld`, **sudo-rs** in place of sudo, base CLI (`git`, `fastfetch`), and the fleet-wide font stack. |
|
||||
| `users.nix` | baseModules (all NixOS) | Builds `users.users` from the registry for the host's `hostUsers`; enables zsh; enables Firefox + Thunderbird **only** when `features.swayDesktop.enable` is on. Applies per-user `linger`. |
|
||||
| `features.nix` | baseModules (all NixOS) | Declares the feature-flag options (`features.swayDesktop.enable`, `features.claudeCode.enable`) so any host can read/set them without importing the heavy implementation module, plus the CPU capability fact they derive from (`features.cpu.microarchLevel`) and the assertion that guards it. See "CPU capability gating". |
|
||||
| `workstation.nix` | transitively (via laptop/desktop) | Form-factor-agnostic base for physical graphical hosts: turns on `swayDesktop`, Dvorak console, PipeWire, firewall (default-deny), fstrim, earlyoom, fwupd, thermald (x86), redistributable fw. |
|
||||
| `laptop.nix` | host table (MBP, T400) | `imports` workstation.nix, then adds the portable bits: iwd Wi-Fi, lid suspend/lock, Bluetooth + blueman. |
|
||||
| `desktop.nix` | host table (Mac Pro) | `imports` workstation.nix, then swaps Wi-Fi for wired NetworkManager. Pair with `portable = false` in the host table. |
|
||||
| `sway.nix` | host table (graphical hosts) | Implementation of `features.swayDesktop`: the system Sway package, the greetd/ReGreet (cage) greeter forced to Dvorak, xdg-portal, Wayland utility packages. Home-side Sway config is in `home/sway.nix`. |
|
||||
| `ssh.nix` | host table (T400, Mac Pro, RPi5) | Enables sshd, opens port 22, enforces a key-only policy (no password / keyboard-interactive, no root). Authorized keys come from the registry via `users.nix`. |
|
||||
| `firmware/` | referenced by MBP host config | Committed Apple peripheral firmware blobs for the Asahi MBP (see "MacBook (Asahi) firmware"). |
|
||||
| Module | Imported by | What it does / when to use it |
|
||||
| ------------------ | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `common-nixos.nix` | baseModules (all NixOS) | Timezone/locale, store hygiene (auto-optimise, big download buffer, **no** auto-GC), the nix-community binary cache, `nix-ld`, **sudo-rs** in place of sudo, base CLI (`git`, `fastfetch`), and the fleet-wide font stack. |
|
||||
| `users.nix` | baseModules (all NixOS) | Builds `users.users` from the registry for the host's `hostUsers`; enables zsh; enables Firefox + Thunderbird **only** when `features.swayDesktop.enable` is on. Applies per-user `linger`. |
|
||||
| `features.nix` | baseModules (all NixOS) | Declares the feature-flag options (`features.swayDesktop.enable`, `features.claudeCode.enable`) so any host can read/set them without importing the heavy implementation module, plus the CPU capability fact they derive from (`features.cpu.microarchLevel`) and the assertion that guards it. See "CPU capability gating". |
|
||||
| `workstation.nix` | transitively (via laptop/desktop) | Form-factor-agnostic base for physical graphical hosts: turns on `swayDesktop`, Dvorak console, PipeWire, firewall (default-deny), fstrim, earlyoom, fwupd, thermald (x86), redistributable fw. |
|
||||
| `laptop.nix` | host table (MBP, T400) | `imports` workstation.nix, then adds the portable bits: iwd Wi-Fi, lid suspend/lock, Bluetooth + blueman. |
|
||||
| `desktop.nix` | host table (Mac Pro) | `imports` workstation.nix, then swaps Wi-Fi for wired NetworkManager. Pair with `portable = false` in the host table. |
|
||||
| `sway.nix` | host table (graphical hosts) | Implementation of `features.swayDesktop`: the system Sway package, the greetd/ReGreet (cage) greeter forced to Dvorak, xdg-portal, Wayland utility packages. Home-side Sway config is in `home/sway.nix`. |
|
||||
| `ssh.nix` | host table (T400, Mac Pro, both Pis) | Enables sshd, opens port 22, enforces a key-only policy (no password / keyboard-interactive, no root). Authorized keys come from the registry via `users.nix`. |
|
||||
| `firmware/` | referenced by MBP host config | Committed Apple peripheral firmware blobs for the Asahi MBP (see "MacBook (Asahi) firmware"). |
|
||||
|
||||
Form-factor decision: a **laptop** imports `laptop.nix` (default
|
||||
`portable = true`); a **wired desktop** imports `desktop.nix` and sets
|
||||
|
||||
@@ -1,340 +0,0 @@
|
||||
# Console — living-room games machine
|
||||
|
||||
Flake host: `lyrathorpe-console`. Desktop (`portable = false`, imports
|
||||
`../../modules/desktop.nix`). Files: `configuration.nix`, `nvidia.nix`,
|
||||
`gaming.nix`, `hardware-configuration.nix`.
|
||||
|
||||
A 4th-generation Core i7 (Haswell) on a UEFI board with an NVIDIA GeForce GTX
|
||||
1070 8 GB, wired to a television. It boots straight into Steam Big Picture and
|
||||
is driven from the sofa with a Bluetooth controller; keyboard and mouse are
|
||||
supported but secondary.
|
||||
|
||||
## Not installed yet
|
||||
|
||||
`hardware-configuration.nix` in this host directory is a **placeholder**, not a
|
||||
hardware scan. It exists so the flake evaluates in CI and assumes the install
|
||||
labels its partitions `nixos` (root, ext4) and `BOOT` (ESP, vfat). Replace the
|
||||
whole file with the output of `nixos-generate-config` run on the machine and
|
||||
commit that. If the labels do not match, the boot fails on a missing device
|
||||
rather than touching the wrong disk.
|
||||
|
||||
Partition the disk GPT with an ESP (vfat, 512 MB is comfortable). Nothing else
|
||||
in the host config depends on the disk layout.
|
||||
|
||||
## Bootloader
|
||||
|
||||
Ordinary PC UEFI firmware, so **systemd-boot** with
|
||||
`canTouchEfiVariables = true` — unlike the Mac Pro, this board is trusted with
|
||||
`efibootmgr` NVRAM writes.
|
||||
|
||||
`boot.loader.timeout = 0`: the boot menu is unreadable from a sofa and unusable
|
||||
without a keyboard, so the default entry boots immediately. **Hold space at
|
||||
power-on** to get the menu back and pick an older generation.
|
||||
`configurationLimit = 10` stops the ESP filling up.
|
||||
|
||||
## Graphics — GTX 1070
|
||||
|
||||
Everything driver-related is in [`nvidia.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/hosts/Console/nvidia.nix).
|
||||
The GTX 1070 is Pascal (GP104), so it is under the same driver constraint as the
|
||||
Mac Pro's Quadro P400:
|
||||
|
||||
- **Driver branch 580** (`nvidiaPackages.legacy_580`), _not_ the nixpkgs default
|
||||
(`production`, currently 595.x). 580 is the last branch supporting
|
||||
Maxwell/Pascal/Volta, maintained as an LTS branch until Aug 2028; a newer
|
||||
branch does not drive this card at all.
|
||||
- `modesetting.enable = true` — mandatory for Wayland. Without
|
||||
`nvidia-drm.modeset=1` neither gamescope nor wlroots gets a usable GBM device,
|
||||
and both the Steam session and Sway fail to start.
|
||||
- `open = false` — the open kernel modules require Turing or later.
|
||||
- Sway runs with `--unsupported-gpu`; wlroots refuses the proprietary driver
|
||||
otherwise. gamescope and cage/ReGreet need no such flag.
|
||||
- `hardware.graphics.enable32Bit` pulls in the lib32 NVIDIA userspace that
|
||||
32-bit Steam titles and Proton's 32-bit prefixes link against.
|
||||
|
||||
The driver is unfree, so it is **not in the binary cache**: the kernel module is
|
||||
compiled on the machine. On a Haswell i7 that is a few minutes, not the Mac
|
||||
Pro's ordeal, but it recurs on every kernel bump. The package names are
|
||||
allowlisted in `unfreePackages` in [`flake.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/flake.nix).
|
||||
|
||||
Verify after a rebuild:
|
||||
|
||||
```sh
|
||||
nvidia-smi
|
||||
lsmod | grep nvidia # nvidia, nvidia_modeset, nvidia_drm
|
||||
```
|
||||
|
||||
## Session model — autologin into Steam
|
||||
|
||||
[`gaming.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/hosts/Console/gaming.nix)
|
||||
sets `programs.steam.gamescopeSession.enable`, which registers a `steam.desktop`
|
||||
Wayland session (gamescope wrapping Steam in tenfoot mode) and installs a
|
||||
`steam-gamescope` launcher. greetd — already present on every Sway host for
|
||||
ReGreet, see [`../../modules/sway.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/modules/sway.nix)
|
||||
— gets an `initial_session` pointing at that launcher:
|
||||
|
||||
```
|
||||
boot
|
||||
└─ greetd initial_session (autologin as lyrathorpe)
|
||||
└─ gamescope --steam -- steam -tenfoot -pipewire-dmabuf
|
||||
├─ Steam library / Proton titles
|
||||
├─ [non-Steam] RetroArch
|
||||
└─ [non-Steam] Clone Hero
|
||||
|
||||
quit Steam → greetd default_session → ReGreet → pick Sway (keyboard + mouse)
|
||||
```
|
||||
|
||||
So there is no greeter at boot and no keyboard needed. Quitting Steam drops back
|
||||
to ReGreet, where the ordinary Sway session is available for everything else.
|
||||
`services.greetd.restart` defaults to `false` once `initial_session` is set,
|
||||
which is what stops a logout looping straight back into autologin.
|
||||
|
||||
Two details worth knowing:
|
||||
|
||||
- The launcher is referenced as `/run/current-system/sw/bin/steam-gamescope`.
|
||||
The steam module builds that script privately and only adds it to
|
||||
`environment.systemPackages`, so there is no package attribute to point at.
|
||||
- `programs.gamescope.capSysNice = true` installs gamescope as a setcap wrapper
|
||||
in `/run/wrappers/bin` instead of the system profile. That path precedes the
|
||||
system profile on `PATH`, so `steam-gamescope` still resolves it.
|
||||
|
||||
The greeter is **Dvorak**, like every other host here (`modules/sway.nix` forces
|
||||
`XKB_DEFAULT_VARIANT=dvorak` on cage). Only relevant if someone else has to type
|
||||
a password.
|
||||
|
||||
### Adding RetroArch and Clone Hero to Big Picture
|
||||
|
||||
Both are installed system-wide but are not Steam titles. Add each once, from a
|
||||
Sway session, via Steam's **Games → Add a Non-Steam Game**; they then appear in
|
||||
Big Picture and inherit Steam Input, so the controller works in them without
|
||||
further configuration.
|
||||
|
||||
## Emulation — RetroArch
|
||||
|
||||
`gaming.nix` builds RetroArch through `pkgs.retroarch-bare.wrapper`, which wires
|
||||
up the packaged assets, core info and joypad autoconfig profiles. Cores:
|
||||
|
||||
| System | Core |
|
||||
| -------------------------------------- | ------------------------ |
|
||||
| NES | `nestopia` |
|
||||
| SNES | `snes9x` |
|
||||
| Game Boy / Color | `gambatte` |
|
||||
| Game Boy Advance | `mgba` |
|
||||
| Nintendo 64 | `mupen64plus` |
|
||||
| Nintendo DS | `melonds` |
|
||||
| GameCube / Wii | `dolphin` |
|
||||
| Master System / Game Gear / Mega Drive | `genesis-plus-gx` |
|
||||
| 32X / Mega CD | `picodrive` |
|
||||
| Saturn | `beetle-saturn` |
|
||||
| Dreamcast / NAOMI | `flycast` |
|
||||
| PlayStation | `beetle-psx-hw` |
|
||||
| PlayStation 2 | `pcsx2` (LRPS2) |
|
||||
| PSP | `ppsspp` |
|
||||
| Arcade | `fbneo`, `mame2003-plus` |
|
||||
| DOS | `dosbox-pure` |
|
||||
|
||||
A handful of settings are applied on every launch via `--appendconfig`, so they
|
||||
are fixed policy rather than saved preferences — changing them in the UI will
|
||||
not stick. Everything else stays user-editable as normal.
|
||||
|
||||
- `menu_driver = ozone` — the controller-navigable menu.
|
||||
- `video_fullscreen = true`.
|
||||
- `input_menu_toggle_gamepad_combo = 2` — **L3+R3 opens the RetroArch menu**
|
||||
from inside a running core. Without a pad combo there is no way to exit a game
|
||||
without a keyboard. No retro system emulated here has L3/R3 on its own
|
||||
controller, so the binding cannot collide with a game.
|
||||
- `system_directory`, `savefile_directory`, `savestate_directory`,
|
||||
`playlist_directory`, `screenshot_directory`, `thumbnails_directory` and
|
||||
`rgui_browser_directory` — all pointed at the shared library described below,
|
||||
rather than scattered through `~/.config/retroarch`.
|
||||
|
||||
An unrecognised key in an appended config is ignored **silently**, so those key
|
||||
names are worth keeping in step with upstream if RetroArch is ever bumped
|
||||
across a major version.
|
||||
|
||||
### BIOS files and expectations
|
||||
|
||||
Several cores need BIOS or firmware images that are not redistributable and are
|
||||
therefore not packaged. Drop them in `/srv/games/bios`, which is RetroArch's
|
||||
system directory on this host:
|
||||
|
||||
- **PlayStation 2** (`pcsx2`) — a PS2 BIOS dump. The core will not boot anything
|
||||
without one.
|
||||
- **Saturn** (`beetle-saturn`) — region BIOS images.
|
||||
- **Dreamcast** (`flycast`) — `dc_boot.bin` / `dc_flash.bin` for most titles.
|
||||
- **Nintendo DS** (`melonds`) — optional, but DSi mode and some titles want the
|
||||
real BIOS/firmware.
|
||||
|
||||
Be honest about the two heaviest cores. `dolphin` and `pcsx2` are libretro ports
|
||||
of emulators whose upstream effort goes into their **standalone** builds; the
|
||||
cores lag on compatibility and are the first place to look when a GameCube, Wii
|
||||
or PS2 title misbehaves. If a game does not cooperate, add the standalone
|
||||
emulators to `environment.systemPackages` in `gaming.nix`:
|
||||
|
||||
```nix
|
||||
pkgs.dolphin-emu # GameCube / Wii
|
||||
pkgs.pcsx2 # PlayStation 2
|
||||
```
|
||||
|
||||
Both are controller-driven and can be added to Big Picture the same way as
|
||||
RetroArch. The hardware is not the limit here — a GTX 1070 and a Haswell i7 run
|
||||
PS2 and Wii comfortably.
|
||||
|
||||
Five cores (`snes9x`, `genesis-plus-gx`, `picodrive`, `fbneo`,
|
||||
`mame2003-plus`) carry upstream licences with non-commercial or
|
||||
no-redistribution-for-profit clauses, so their derivation names are in
|
||||
`unfreePackages` in `flake.nix`. Nothing else in the core set needs it.
|
||||
|
||||
## Games library layout
|
||||
|
||||
Content lives under `/srv/games`, deliberately outside any home directory: it is
|
||||
bulky, it is the thing most likely to move to its own disk, and it is shared
|
||||
between Steam, RetroArch and Clone Hero rather than owned by one of them.
|
||||
Mounting a second drive at `/srv/games` is the only change that move needs.
|
||||
|
||||
`gaming.nix` creates the tree with `systemd.tmpfiles.rules` at every boot:
|
||||
|
||||
```
|
||||
/srv/games/
|
||||
├── roms/ # RetroArch content browser opens here
|
||||
│ ├── nes/ snes/ gb/ gbc/ gba/ n64/ nds/ gc/ wii/
|
||||
│ ├── mastersystem/ gamegear/ megadrive/ sega32x/ segacd/
|
||||
│ ├── saturn/ dreamcast/
|
||||
│ ├── psx/ ps2/ psp/
|
||||
│ └── arcade/ dos/
|
||||
├── bios/ # RetroArch system dir: BIOS and firmware
|
||||
├── saves/ # in-game saves
|
||||
├── states/ # save states
|
||||
├── playlists/
|
||||
├── screenshots/
|
||||
├── thumbnails/
|
||||
├── steam/ # second Steam library folder
|
||||
└── clonehero/
|
||||
├── songs/
|
||||
└── backgrounds/
|
||||
```
|
||||
|
||||
Directory names under `roms/` follow the libretro/ES-DE convention, so a scraper
|
||||
or a second frontend recognises them without anything being renamed.
|
||||
|
||||
Everything is `lyrathorpe:users` mode **2775**. The setgid bit matters: the
|
||||
owning group is carried onto anything created inside, so a second account — or
|
||||
an `rsync` from another machine — does not leave behind files the TV user cannot
|
||||
write. The rules create directories if missing and otherwise leave them alone;
|
||||
nothing here removes or rewrites content.
|
||||
|
||||
RetroArch is pointed at these paths declaratively. The other two have to be told
|
||||
once, in their own UIs:
|
||||
|
||||
- **Steam** — Settings → Storage → the `+` control → add `/srv/games/steam` as a
|
||||
library folder. Games installed there survive a reinstall of the OS.
|
||||
- **Clone Hero** — set the song library path to `/srv/games/clonehero/songs` from
|
||||
its settings screen. Clone Hero keeps its own config in `~/.clonehero`.
|
||||
|
||||
## Steam and Proton
|
||||
|
||||
`programs.steam.enable` already arranges most of what Proton needs, and it is
|
||||
worth recording so it is not re-litigated:
|
||||
|
||||
- `hardware.graphics` with `enable32Bit` — the 32-bit GL/Vulkan userspace
|
||||
Proton's 32-bit prefixes link against.
|
||||
- Steam's udev rules (`hardware.steam-hardware.enable`) — controller and
|
||||
hidraw access, which is also what lets `dualsensectl` talk to a DualSense.
|
||||
- 32-bit PipeWire ALSA (`services.pipewire.alsa.support32Bit`), derived from
|
||||
`alsa.enable`, which `gaming.nix` turns on for the older native titles that
|
||||
talk to ALSA directly rather than through the Pulse shim.
|
||||
- Wine's fonts — Liberation, DejaVu, FreeFont and the Noto set arrive with
|
||||
`fonts.enableDefaultPackages` plus `modules/common-nixos.nix`. Liberation is
|
||||
metric-compatible with the Microsoft core fonts, so text lays out correctly
|
||||
without shipping unfree `corefonts`.
|
||||
- `vm.max_map_count` is **1048576** in the nixpkgs default sysctls, above what
|
||||
DX12 and Unreal titles need. No override required — this is the one people
|
||||
usually copy from Arch wiki posts and it is already handled.
|
||||
|
||||
What is **not** covered by default, and is set explicitly in `gaming.nix`:
|
||||
|
||||
- `systemd.settings.Manager.DefaultLimitNOFILE = "1024:1048576"`. esync opens
|
||||
one eventfd per Wine synchronisation object and runs out against systemd's
|
||||
default 524288 hard limit in the heaviest titles. Only the hard limit is
|
||||
raised; the soft limit stays at 1024, because lifting that breaks
|
||||
`select()`-based programs elsewhere on the system.
|
||||
- `extraCompatPackages = [ pkgs.proton-ge-bin ]`. The module puts its
|
||||
`steamcompattool` output on `STEAM_EXTRA_COMPAT_TOOLS_PATHS`, which is what
|
||||
makes **GE-Proton** appear in the client's compatibility list.
|
||||
- `protontricks.enable` — winetricks against a Proton prefix, the standard
|
||||
repair when a title needs a runtime (dotnet, vcrun, Media Foundation) Proton
|
||||
does not ship.
|
||||
- `programs.gamemode.enable` — applies the performance CPU governor around games
|
||||
that request it.
|
||||
|
||||
One thing is **not declarative**: Steam Play must be switched on in the client,
|
||||
once, per account — **Settings → Compatibility → Enable Steam Play for all other
|
||||
titles**. Nix cannot set this; it lives in Steam's own config.
|
||||
|
||||
Verify the Proton side after installing:
|
||||
|
||||
```sh
|
||||
vulkaninfo --summary # 64-bit ICD
|
||||
nvidia-smi # driver up
|
||||
ulimit -Hn # expect 1048576
|
||||
# in a game's launch options, to confirm esync/fsync are active:
|
||||
# PROTON_LOG=1 %command% → ~/steam-<appid>.log
|
||||
```
|
||||
|
||||
## Controllers
|
||||
|
||||
- **Xbox One / Series over Bluetooth** — `hardware.xpadneo.enable`. The
|
||||
out-of-tree driver; the in-kernel `xpad` handles these badly over Bluetooth
|
||||
(wrong button mapping, no rumble). The module enables bluez itself.
|
||||
- **Xbox 360, wired** — in-kernel `xpad`, autoloaded by udev on plug-in.
|
||||
Nothing to configure. The kernel is built with `CONFIG_JOYSTICK_XPAD=m`,
|
||||
`CONFIG_JOYSTICK_XPAD_FF=y` (rumble) and `CONFIG_JOYSTICK_XPAD_LEDS=y`. The
|
||||
360 wireless PC receiver uses the same driver and works the same way.
|
||||
- **DualSense / DualShock 4** — in-kernel `hid-playstation`, over both USB and
|
||||
Bluetooth. No driver config. `dualsensectl` is installed for LED, battery and
|
||||
microphone control; it works because Steam's udev rules grant hidraw access.
|
||||
- **Clone Hero guitars** — plain USB HID gamepads, handled in-kernel. Nothing to
|
||||
configure.
|
||||
|
||||
`hardware.bluetooth.powerOnBoot` is set so the adapter is up before the Steam
|
||||
session starts and a pad can reconnect unattended.
|
||||
`settings.General.Experimental = true` is what enables battery level reporting
|
||||
for Bluetooth gamepads — it is still behind bluez's experimental flag.
|
||||
|
||||
Pair a new controller from Big Picture (**Settings → Controller**), or from a
|
||||
Sway session with `bluetoothctl`. If a pad connects but no input arrives, check
|
||||
`journalctl -b -u bluetooth` and confirm `hid_xpadneo` is loaded
|
||||
(`lsmod | grep xpadneo`).
|
||||
|
||||
The Xbox One / Series USB **wireless dongle** is deliberately not configured. It
|
||||
needs `hardware.xone.enable`, which **blacklists `xpad`** — that would break the
|
||||
wired 360 pads — as well as `mt76x2u`, and pulls in proprietary dongle firmware.
|
||||
Not worth the side effects unless that dongle is actually in use, and if it ever
|
||||
is, the 360 pads have to be re-tested.
|
||||
|
||||
## Untested claims
|
||||
|
||||
This host has not been built or booted yet. Two things are worth watching on
|
||||
first boot:
|
||||
|
||||
- **gamescope on the proprietary NVIDIA driver.** `gaming.nix` sets
|
||||
`GBM_BACKEND=nvidia-drm` and `__GLX_VENDOR_LIBRARY_NAME=nvidia` for the
|
||||
session, which is the standard fix, but the combination has a history of
|
||||
needing tweaks. If the session dies at startup, switch the greeter back to
|
||||
interactive by commenting out `services.greetd.settings.initial_session`, log
|
||||
into Sway, and read `journalctl --user -b`.
|
||||
- **Television resolution and refresh.** gamescope takes the output's native
|
||||
mode by default. Add `gamescopeSession.args = [ "-W" "3840" "-H" "2160" "-r"
|
||||
"60" ]` if a specific mode is wanted.
|
||||
|
||||
There is no HDMI-CEC configuration here, so the TV remote will not drive the
|
||||
box; that needs a Pulse-Eight adapter or a working CEC bridge on the board.
|
||||
Nothing boots to a splash screen either — Plymouth was left out deliberately, as
|
||||
early KMS with the proprietary driver makes it unreliable.
|
||||
|
||||
## Networking
|
||||
|
||||
Wired NetworkManager from `../../modules/desktop.nix`; `modules/ssh.nix` adds
|
||||
key-only sshd, which is the practical way to administer a machine with no
|
||||
keyboard attached. The firewall is default-deny (`modules/workstation.nix`);
|
||||
Steam Remote Play and local network game transfers open their own ports through
|
||||
`programs.steam`.
|
||||
@@ -0,0 +1,205 @@
|
||||
# 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. |
|
||||
Generated
+17
@@ -185,6 +185,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"legacy-email-proxy": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1781718202,
|
||||
"narHash": "sha256-b+d/PqeGuQgdU/fYAla5dobmsDOsd6aYImzWRhAZ/RQ=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "4bde4f884db2150b1b5ae5d2ac3e3d7e82ab2567",
|
||||
"revCount": 12,
|
||||
"type": "git",
|
||||
"url": "https://code.emmathe.dev/lyrathorpe/legacy-email-proxy"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://code.emmathe.dev/lyrathorpe/legacy-email-proxy"
|
||||
}
|
||||
},
|
||||
"nix-darwin": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -368,6 +384,7 @@
|
||||
"git-hooks": "git-hooks",
|
||||
"home-manager": "home-manager",
|
||||
"kube-tmux": "kube-tmux",
|
||||
"legacy-email-proxy": "legacy-email-proxy",
|
||||
"nix-darwin": "nix-darwin",
|
||||
"nix-homebrew": "nix-homebrew",
|
||||
"nix-index-database": "nix-index-database",
|
||||
|
||||
@@ -67,6 +67,13 @@
|
||||
url = "github:jonmosco/kube-tmux";
|
||||
flake = false;
|
||||
};
|
||||
# legacy-email-proxy: cleartext POP3/SMTP front end for the Psion's mail
|
||||
# client, proxied to authenticated IMAPS/SMTPS. Ships its own package and
|
||||
# NixOS module; the Pi Zero 2 W host just enables the service.
|
||||
legacy-email-proxy = {
|
||||
url = "git+https://code.emmathe.dev/lyrathorpe/legacy-email-proxy";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
@@ -112,27 +119,13 @@
|
||||
|
||||
# Unfree packages permitted to be built (replaces blanket allowUnfree).
|
||||
# The NVIDIA entries are for the Mac Pro's Quadro P400 (hosts/MacPro31/
|
||||
# nvidia.nix) and the Console host's GTX 1070 (hosts/Console/nvidia.nix);
|
||||
# unfree packages are not in the binary cache, so the kernel module is
|
||||
# compiled on the host. The steam/clonehero entries are the Console
|
||||
# host's games stack (hosts/Console/gaming.nix).
|
||||
# nvidia.nix); unfree packages are not in the binary cache, so the
|
||||
# kernel module is compiled on the host.
|
||||
unfreePackages = [
|
||||
"claude-code"
|
||||
"nvidia-x11"
|
||||
"nvidia-kernel-modules"
|
||||
"nvidia-settings"
|
||||
"steam"
|
||||
"steam-unwrapped"
|
||||
"steam-run"
|
||||
"clonehero"
|
||||
# RetroArch cores whose upstream licences carry a non-commercial or
|
||||
# no-redistribution-for-profit clause. Everything else in the core set
|
||||
# is plain free software.
|
||||
"libretro-snes9x"
|
||||
"libretro-genesis-plus-gx"
|
||||
"libretro-picodrive"
|
||||
"libretro-fbneo"
|
||||
"libretro-mame2003-plus"
|
||||
];
|
||||
|
||||
# Per-user identity, keyed by username. See README "Users".
|
||||
@@ -307,28 +300,6 @@
|
||||
];
|
||||
};
|
||||
|
||||
lyrathorpe-console = {
|
||||
system = "x86_64-linux";
|
||||
portable = false;
|
||||
# Living-room games machine on a television: autologins into the
|
||||
# gamescope Steam session (hosts/Console/gaming.nix). sway.nix is
|
||||
# still imported -- greetd/ReGreet is what the Steam session falls
|
||||
# back to, and Sway is the keyboard-and-mouse session behind it.
|
||||
modules = [
|
||||
./hosts/Console/configuration.nix
|
||||
./modules/desktop.nix
|
||||
./modules/ssh.nix
|
||||
inputs.nixos-hardware.nixosModules.common-pc-ssd
|
||||
inputs.nixos-hardware.nixosModules.common-cpu-intel
|
||||
./modules/sway.nix
|
||||
];
|
||||
users.lyrathorpe.homeModules = [
|
||||
./home
|
||||
./users/lyrathorpe/home.nix
|
||||
./home/desktop.nix
|
||||
];
|
||||
};
|
||||
|
||||
emmathorpe-edaas = {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
@@ -363,6 +334,26 @@
|
||||
./users/lyrathorpe/home.nix
|
||||
];
|
||||
};
|
||||
|
||||
lyrathorpe-zero2w = {
|
||||
system = "aarch64-linux";
|
||||
portable = false;
|
||||
# Headless "Psion sidecar": PPP over RS232 plus a legacy mail proxy
|
||||
# (hosts/PiZero2W/). No sway.nix; the raspberry-pi-3 profile carries
|
||||
# the kernel/firmware/device tree (the Zero 2 W is the Pi 3's
|
||||
# BCM2837 SoC) and ssh.nix adds key-only sshd. This board has 512 MB
|
||||
# of RAM and never builds its own system -- see
|
||||
# docs/hosts/pizero2w.md.
|
||||
modules = [
|
||||
./hosts/PiZero2W/configuration.nix
|
||||
inputs.nixos-hardware.nixosModules.raspberry-pi-3
|
||||
./modules/ssh.nix
|
||||
];
|
||||
users.lyrathorpe.homeModules = [
|
||||
./home
|
||||
./users/lyrathorpe/home.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# Darwin host table — macOS machines built via mkDarwinHost. The shared
|
||||
@@ -401,8 +392,24 @@
|
||||
# nixpkgs instance for that system. Outputs here become per-system
|
||||
# attrsets automatically (e.g. devShells.<system>.default).
|
||||
perSystem =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
{
|
||||
# One-shot SD card for bringing the Pi Zero 2 W up: that host's own
|
||||
# configuration plus the sd-image module, so the first boot is
|
||||
# already the real system. aarch64-linux only -- building it needs
|
||||
# an aarch64 Linux builder. See docs/hosts/pizero2w.md.
|
||||
packages = lib.optionalAttrs (system == "aarch64-linux") {
|
||||
zero2w-sd-image =
|
||||
((mkHost hosts.lyrathorpe-zero2w).extendModules {
|
||||
modules = [ ./hosts/PiZero2W/sd-image.nix ];
|
||||
}).config.system.build.sdImage;
|
||||
};
|
||||
|
||||
# treefmt drives `nix fmt` and the formatting check below. nixfmt
|
||||
# stays the .nix formatter (the tree is already nixfmt-formatted);
|
||||
# shfmt covers shell and prettier covers markdown/yaml/json.
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# Living-room games machine: 4th-gen Core i7 (Haswell) on a UEFI board, wired to
|
||||
# a television and driven from the sofa with a Bluetooth controller. Desktop host
|
||||
# -- shared graphical/wired options live in ../../modules/desktop.nix; only
|
||||
# host-specific settings are here. The games stack (Steam session, RetroArch,
|
||||
# controllers) is in ./gaming.nix and the GPU in ./nvidia.nix. Install notes:
|
||||
# see ../../docs/hosts/console.md.
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./nvidia.nix
|
||||
./gaming.nix
|
||||
];
|
||||
|
||||
# Haswell: AVX2/FMA/BMI2, i.e. x86-64-v3. Above the fleet default (2), so this
|
||||
# only records the fact -- no feature flag currently keys off level 3.
|
||||
features.cpu.microarchLevel = 3;
|
||||
|
||||
# Ordinary PC UEFI firmware: systemd-boot, and NVRAM writes are safe here
|
||||
# (unlike the Mac Pro's Apple EFI, which cannot be trusted with efibootmgr).
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
# The boot menu is unreadable from a sofa and unusable without a keyboard.
|
||||
# Boot the default immediately; hold space at power-on to get the menu back.
|
||||
boot.loader.timeout = 0;
|
||||
# Bound the entry list so the ESP does not fill up with old generations.
|
||||
boot.loader.systemd-boot.configurationLimit = 10;
|
||||
|
||||
networking.hostName = "Console-NixOS";
|
||||
|
||||
hardware.cpu.intel.updateMicrocode = true;
|
||||
|
||||
# See `man configuration.nix` / the stateVersion docs before changing.
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
# The games stack for the living-room machine: the Steam session that the TV
|
||||
# boots into, RetroArch with its cores, Clone Hero, and the controller plumbing.
|
||||
#
|
||||
# Session model. greetd (from ../../modules/sway.nix, which enables it for
|
||||
# ReGreet) gets an `initial_session`, so the machine autologins into the
|
||||
# gamescope Steam session at boot -- no keyboard, no greeter, straight to Big
|
||||
# Picture. Quitting Steam drops back to greetd's `default_session`, i.e. ReGreet,
|
||||
# where the ordinary Sway session can be picked for keyboard-and-mouse work.
|
||||
{ pkgs, ... }:
|
||||
|
||||
let
|
||||
# The account the television autologins as. Must match the user declared for
|
||||
# this host in the flake host table.
|
||||
tvUser = "lyrathorpe";
|
||||
|
||||
# Games library root. Deliberately outside any home directory: content is
|
||||
# bulky, is the thing most likely to move to its own disk, and is shared
|
||||
# between Steam, RetroArch and Clone Hero rather than owned by one of them.
|
||||
# Mounting a second drive at this path is the only change that needs.
|
||||
gamesRoot = "/srv/games";
|
||||
|
||||
# One ROM directory per emulated system. Names follow the libretro/ES-DE
|
||||
# convention so a scraper or a second frontend recognises them without
|
||||
# renaming anything.
|
||||
romSystems = [
|
||||
"nes"
|
||||
"snes"
|
||||
"gb"
|
||||
"gbc"
|
||||
"gba"
|
||||
"n64"
|
||||
"nds"
|
||||
"gc"
|
||||
"wii"
|
||||
"mastersystem"
|
||||
"gamegear"
|
||||
"megadrive"
|
||||
"sega32x"
|
||||
"segacd"
|
||||
"saturn"
|
||||
"dreamcast"
|
||||
"psx"
|
||||
"ps2"
|
||||
"psp"
|
||||
"arcade"
|
||||
"dos"
|
||||
];
|
||||
|
||||
# Everything under the root that is not a ROM directory. RetroArch is pointed
|
||||
# at these below; Steam and Clone Hero have to be told about theirs in their
|
||||
# own UIs (see docs/hosts/console.md).
|
||||
libraryDirs = [
|
||||
"bios" # RetroArch system directory: BIOS and firmware images
|
||||
"saves" # in-game saves
|
||||
"states" # save states
|
||||
"playlists"
|
||||
"screenshots"
|
||||
"thumbnails"
|
||||
"steam" # add as a Steam library folder from the client
|
||||
"clonehero/songs"
|
||||
"clonehero/backgrounds"
|
||||
];
|
||||
|
||||
# RetroArch and the cores this machine is expected to run. The wrapper already
|
||||
# points RetroArch at the packaged assets, core info and joypad autoconfig
|
||||
# profiles; `settings` here is merged on top of those.
|
||||
retroarch = pkgs.retroarch-bare.wrapper {
|
||||
cores = with pkgs.libretro; [
|
||||
# Nintendo
|
||||
nestopia # NES
|
||||
snes9x # SNES
|
||||
gambatte # Game Boy / Color
|
||||
mgba # Game Boy Advance
|
||||
mupen64plus # Nintendo 64
|
||||
melonds # Nintendo DS
|
||||
dolphin # GameCube / Wii
|
||||
# Sega
|
||||
genesis-plus-gx # Master System / Game Gear / Mega Drive
|
||||
picodrive # 32X / Mega CD
|
||||
beetle-saturn # Saturn
|
||||
flycast # Dreamcast / NAOMI
|
||||
# Sony
|
||||
beetle-psx-hw # PlayStation, hardware renderer
|
||||
pcsx2 # PlayStation 2 (LRPS2); needs a PS2 BIOS in RetroArch's system dir
|
||||
ppsspp # PSP
|
||||
# Arcade and PC
|
||||
fbneo
|
||||
mame2003-plus
|
||||
dosbox-pure
|
||||
];
|
||||
settings = {
|
||||
# Applied on every launch via --appendconfig, so these three are fixed
|
||||
# policy rather than saved preferences: changing them in the UI will not
|
||||
# stick. Everything else stays user-editable as usual.
|
||||
#
|
||||
# Ozone is the controller-navigable menu; the TV has no keyboard.
|
||||
menu_driver = "ozone";
|
||||
video_fullscreen = "true";
|
||||
# L3+R3 opens the RetroArch menu from inside a running core
|
||||
# (INPUT_COMBO_L3_R3). Without a pad combo there is no way to exit a game
|
||||
# without a keyboard, and no retro system this box emulates has L3/R3 on
|
||||
# its own controller, so the binding cannot collide with a game.
|
||||
input_menu_toggle_gamepad_combo = "2";
|
||||
|
||||
# Point RetroArch at the shared library instead of scattering content and
|
||||
# state through ~/.config/retroarch. Key names are RetroArch's own; an
|
||||
# unrecognised key in an appended config is ignored silently, so they are
|
||||
# worth keeping in step with upstream.
|
||||
system_directory = "${gamesRoot}/bios";
|
||||
savefile_directory = "${gamesRoot}/saves";
|
||||
savestate_directory = "${gamesRoot}/states";
|
||||
playlist_directory = "${gamesRoot}/playlists";
|
||||
screenshot_directory = "${gamesRoot}/screenshots";
|
||||
thumbnails_directory = "${gamesRoot}/thumbnails";
|
||||
# Where the content browser opens, so loading a game is a couple of
|
||||
# D-pad presses rather than a walk up from the filesystem root.
|
||||
rgui_browser_directory = "${gamesRoot}/roms";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
programs.steam = {
|
||||
enable = true;
|
||||
|
||||
# Registers the "Steam" wayland session (gamescope wrapping Steam in tenfoot
|
||||
# mode) with the display manager and installs the steam-gamescope launcher.
|
||||
gamescopeSession.enable = true;
|
||||
gamescopeSession.env = {
|
||||
# gamescope has to be pointed at NVIDIA's GBM implementation and GLX
|
||||
# vendor explicitly; on the proprietary driver it otherwise fails to get a
|
||||
# usable device and the session dies at startup.
|
||||
GBM_BACKEND = "nvidia-drm";
|
||||
__GLX_VENDOR_LIBRARY_NAME = "nvidia";
|
||||
};
|
||||
|
||||
# Remote Play and local network game transfers are the point of a TV box on
|
||||
# the same LAN as a desktop; both need their ports open.
|
||||
remotePlay.openFirewall = true;
|
||||
localNetworkGameTransfers.openFirewall = true;
|
||||
|
||||
# Proton-GE, selectable per title in Steam's compatibility settings. Covers
|
||||
# the titles where Valve's Proton lags on codecs and anti-cheat shims. The
|
||||
# module puts its steamcompattool output on STEAM_EXTRA_COMPAT_TOOLS_PATHS,
|
||||
# which is what makes it appear in the client's Proton version list.
|
||||
extraCompatPackages = [ pkgs.proton-ge-bin ];
|
||||
|
||||
# Winetricks against a Proton prefix: the standard repair tool when a title
|
||||
# needs a runtime (dotnet, vcrun, Media Foundation) that Proton does not ship.
|
||||
protontricks.enable = true;
|
||||
};
|
||||
|
||||
# Proton prerequisites beyond what programs.steam already arranges.
|
||||
#
|
||||
# Already covered by the steam module, recorded here so it is not re-litigated:
|
||||
# hardware.graphics 32-bit (the lib32 NVIDIA userspace Proton's 32-bit prefixes
|
||||
# need), Steam's udev rules, 32-bit PipeWire, and the system fonts Wine renders
|
||||
# with (Liberation and DejaVu arrive with fonts.enableDefaultPackages).
|
||||
# vm.max_map_count is 1048576 in the nixpkgs default sysctls, which is above
|
||||
# what DX12/Unreal titles need -- no override required.
|
||||
#
|
||||
# What is not covered: esync opens one eventfd per Wine sync object and runs
|
||||
# out against systemd's default 524288 hard limit in the heaviest titles.
|
||||
# Raise the hard limit only; the soft limit stays at the default, because
|
||||
# lifting that breaks select()-based programs elsewhere on the system.
|
||||
systemd.settings.Manager.DefaultLimitNOFILE = "1024:1048576";
|
||||
|
||||
# capSysNice lets gamescope raise its own scheduling priority, which is what
|
||||
# keeps the compositor smooth while a game saturates the GPU. It installs
|
||||
# gamescope as a setcap wrapper instead of a plain systemPackages entry;
|
||||
# /run/wrappers/bin precedes the system profile on PATH, so steam-gamescope
|
||||
# still resolves it.
|
||||
programs.gamescope = {
|
||||
enable = true;
|
||||
capSysNice = true;
|
||||
};
|
||||
|
||||
# Applies the performance CPU governor (and drops it again) around games that
|
||||
# ask for it; Steam's Proton builds and most native titles do.
|
||||
programs.gamemode.enable = true;
|
||||
|
||||
# Autologin into the Steam session. The launcher is not exposed as a package
|
||||
# by the steam module -- it is built inside it and added to
|
||||
# environment.systemPackages -- so reference it through the system profile.
|
||||
# greetd's `restart` option defaults to false once initial_session is set,
|
||||
# which is what stops a logout from looping straight back into autologin.
|
||||
services.greetd.settings.initial_session = {
|
||||
command = "/run/current-system/sw/bin/steam-gamescope";
|
||||
user = tvUser;
|
||||
};
|
||||
|
||||
# Controllers.
|
||||
#
|
||||
# Xbox One/Series pads over Bluetooth need xpadneo: the in-kernel xpad driver
|
||||
# does not handle them well over BT (wrong button mapping, no rumble). The
|
||||
# module turns on bluez itself; powerOnBoot is set below so the adapter is up
|
||||
# before the Steam session starts and a pad can reconnect unattended.
|
||||
#
|
||||
# Everything else is in-kernel and needs no configuration: wired Xbox 360 pads
|
||||
# (and the 360 wireless receiver) via xpad, DualSense/DualShock 4 via
|
||||
# hid-playstation over USB and Bluetooth, and Clone Hero guitars as plain USB
|
||||
# HID gamepads. xpadneo does not contend with xpad -- it binds Bluetooth HID
|
||||
# devices, and the 360 pad is not HID-compliant. hardware.xone is deliberately
|
||||
# left off: it blacklists xpad, which would break the 360 pads.
|
||||
#
|
||||
# hidraw access for the PlayStation pads (LED, battery, dualsensectl) comes
|
||||
# from Steam's udev rules, which programs.steam enables via
|
||||
# hardware.steam-hardware.
|
||||
hardware.xpadneo.enable = true;
|
||||
hardware.bluetooth = {
|
||||
enable = true;
|
||||
powerOnBoot = true;
|
||||
# Battery level reporting for Bluetooth gamepads is still behind bluez's
|
||||
# experimental flag.
|
||||
settings.General.Experimental = true;
|
||||
};
|
||||
|
||||
# The games library, created at boot so the directories exist before anything
|
||||
# tries to write into them. Mode 2775 is setgid: the owning group is carried
|
||||
# onto anything created inside, so a second account (or an rsync from another
|
||||
# machine) does not end up with files the TV user cannot write. Directories
|
||||
# are created if missing and otherwise left alone -- nothing here removes or
|
||||
# rewrites content.
|
||||
systemd.tmpfiles.rules =
|
||||
let
|
||||
dir = path: "d ${path} 2775 ${tvUser} users -";
|
||||
in
|
||||
[
|
||||
(dir gamesRoot)
|
||||
(dir "${gamesRoot}/roms")
|
||||
]
|
||||
++ map (system: dir "${gamesRoot}/roms/${system}") romSystems
|
||||
++ map (sub: dir "${gamesRoot}/${sub}") libraryDirs;
|
||||
|
||||
# 32-bit ALSA for the older native titles that talk to ALSA directly rather
|
||||
# than through the PulseAudio shim; programs.steam derives
|
||||
# pipewire.alsa.support32Bit from this. PipeWire itself and the Pulse shim
|
||||
# come from ../../modules/workstation.nix.
|
||||
services.pipewire.alsa.enable = true;
|
||||
|
||||
environment.systemPackages = [
|
||||
retroarch
|
||||
pkgs.clonehero
|
||||
pkgs.dualsensectl # DualSense LED/battery/mic control from the shell
|
||||
pkgs.mangohud # FPS/frametime overlay; use `mangohud %command%` in Steam
|
||||
pkgs.vulkan-tools # vulkaninfo, for checking the 32/64-bit ICDs Proton needs
|
||||
];
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
# PLACEHOLDER -- not generated by nixos-generate-config.
|
||||
#
|
||||
# This host has not been installed yet, so there is no real hardware scan to
|
||||
# commit. The values below are the conventional defaults for a Haswell UEFI
|
||||
# desktop and assume the install labels its partitions `nixos` (root, ext4) and
|
||||
# `BOOT` (ESP, vfat) -- see docs/hosts/console.md. They exist so the flake
|
||||
# evaluates in CI; they are not a description of the actual machine.
|
||||
#
|
||||
# Replace this whole file with the output of `nixos-generate-config` run on the
|
||||
# machine, and commit that. If the labels do not match, the boot fails loudly on
|
||||
# a missing device rather than touching the wrong disk.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
imports = [
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [
|
||||
"xhci_pci"
|
||||
"ehci_pci"
|
||||
"ahci"
|
||||
"nvme"
|
||||
"usb_storage"
|
||||
"usbhid"
|
||||
"sd_mod"
|
||||
"sr_mod"
|
||||
];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-intel" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-label/nixos";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-label/BOOT";
|
||||
fsType = "vfat";
|
||||
options = [
|
||||
"fmask=0022"
|
||||
"dmask=0022"
|
||||
];
|
||||
};
|
||||
|
||||
swapDevices = [ ];
|
||||
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
# NVIDIA GeForce GTX 1070 8 GB (Pascal, GP104): proprietary driver for the
|
||||
# gamescope Steam session and the Sway desktop.
|
||||
#
|
||||
# Driver branch: 580 (nvidiaPackages.legacy_580), NOT the nixpkgs default
|
||||
# (`production`, currently 595.x). 580 is the last branch that supports
|
||||
# Maxwell/Pascal/Volta -- NVIDIA keeps it as an LTS branch to Aug 2028 -- and a
|
||||
# newer branch simply will not drive this card. Same constraint as the Mac Pro's
|
||||
# Quadro P400; see hosts/MacPro31/nvidia.nix.
|
||||
#
|
||||
# The driver is unfree, so it is not in the binary cache: the kernel module is
|
||||
# compiled locally on every kernel bump.
|
||||
{ config, ... }:
|
||||
|
||||
{
|
||||
# Selects the proprietary driver; the module blacklists nouveau/nvidiafb and
|
||||
# loads nvidia-uvm via a modprobe softdep. Naming is historical -- this option
|
||||
# drives the kernel/driver choice on Wayland hosts too, which is why it is set
|
||||
# on a machine that runs no X server.
|
||||
services.xserver.videoDrivers = [ "nvidia" ];
|
||||
|
||||
hardware.nvidia = {
|
||||
package = config.boot.kernelPackages.nvidiaPackages.legacy_580;
|
||||
# Required for Wayland: sets nvidia-drm.modeset=1 (and fbdev=1), without
|
||||
# which neither gamescope nor wlroots gets a usable GBM device and both the
|
||||
# Steam session and Sway fail to start.
|
||||
modesetting.enable = true;
|
||||
# The open kernel modules need Turing or later; Pascal must use the closed
|
||||
# ones. Explicit because the option has no default on driver >= 560.
|
||||
open = false;
|
||||
};
|
||||
|
||||
# The NVIDIA module only puts these in boot.kernelModules when
|
||||
# services.xserver.enable is true, which is false on this Wayland-only host --
|
||||
# so load them explicitly rather than relying on udev modalias autoloading.
|
||||
# nvidia_uvm is deliberately absent: the module's modprobe softdep pulls it in
|
||||
# after the GPU device exists, which is the supported ordering.
|
||||
boot.kernelModules = [
|
||||
"nvidia"
|
||||
"nvidia_modeset"
|
||||
"nvidia_drm"
|
||||
];
|
||||
|
||||
# wlroots refuses the proprietary NVIDIA driver unless told to proceed. The
|
||||
# greeter's compositor (cage) and gamescope have no such check; only Sway
|
||||
# needs the flag, which the module bakes into the wrapper the session's
|
||||
# .desktop file runs.
|
||||
programs.sway.extraOptions = [ "--unsupported-gpu" ];
|
||||
|
||||
# 32-bit driver libraries for 32-bit Steam titles and Proton's 32-bit
|
||||
# prefixes: hardware.graphics.enable32Bit pulls in the matching lib32 NVIDIA
|
||||
# userspace. programs.steam (./gaming.nix) sets it too; stated here as well so
|
||||
# the GPU's 32-bit story lives with the rest of the GPU config.
|
||||
hardware.graphics.enable32Bit = true;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
# Raspberry Pi Zero 2 W (aarch64) "Psion sidecar": an RS232 companion for a
|
||||
# Psion 5MX. Two roles, split into submodules: ./serial-ppp.nix (PPP over the
|
||||
# serial line, NAT out to wifi, telnet login) and ./email-proxy.nix (cleartext
|
||||
# POP3/SMTP front end for the Psion's mail client). The raspberry-pi-3
|
||||
# nixos-hardware profile (the Zero 2 W is the same BCM2837 SoC as the Pi 3) and
|
||||
# key-only sshd (../../modules/ssh.nix) are layered on in the flake host table.
|
||||
# Install notes: see ../../docs/hosts/pizero2w.md.
|
||||
{ lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./serial-ppp.nix
|
||||
./email-proxy.nix
|
||||
];
|
||||
|
||||
# Match the flake's nixosConfigurations attribute name so `nh os switch`
|
||||
# (which selects by the local hostname) resolves without an explicit -H flag.
|
||||
networking.hostName = "lyrathorpe-zero2w";
|
||||
|
||||
# Headless server: modules/sway.nix is not imported and
|
||||
# features.swayDesktop.enable defaults to false, so this host keeps plain
|
||||
# TTY/SSH login.
|
||||
|
||||
# Claude Code is a Node application. It runs on aarch64, but not usefully in
|
||||
# 512 MB of RAM, and its closure is unwelcome on an SD card.
|
||||
features.claudeCode.enable = false;
|
||||
|
||||
# 512 MB total and no swap partition -- SD cards wear out under swap writes.
|
||||
# Compressed RAM swap instead; zstd is the best ratio-per-cycle the SoC can
|
||||
# sustain.
|
||||
zramSwap = {
|
||||
enable = true;
|
||||
algorithm = "zstd";
|
||||
};
|
||||
|
||||
# The NixOS manual and man page index cost build time and a chunk of the card
|
||||
# for a box that is administered over SSH from elsewhere.
|
||||
documentation.nixos.enable = false;
|
||||
|
||||
# Own the firmware partition declaratively: every switch rewrites config.txt,
|
||||
# the vendor device trees and the overlays below. Without this the card keeps
|
||||
# whatever config.txt the flashed image wrote and the UART overlays never
|
||||
# load. uboot.enable keeps the GPU firmware chainloading U-Boot -> extlinux,
|
||||
# which is how the NixOS aarch64 SD image boots; leaving it off would rewrite
|
||||
# config.txt without a `kernel=` line and the board would stop booting.
|
||||
hardware.raspberry-pi.firmware = {
|
||||
enable = true;
|
||||
uboot.enable = true;
|
||||
};
|
||||
|
||||
hardware.raspberry-pi.configtxt = {
|
||||
settings.all = {
|
||||
# Headless: hand the VideoCore the minimum and leave the rest to Linux.
|
||||
# start_x/camera_auto_detect otherwise reserve VRAM for a camera stack
|
||||
# this board does not have.
|
||||
gpu_mem = 16;
|
||||
start_x = 0;
|
||||
camera_auto_detect = false;
|
||||
# Left on, the firmware auto-loads the KMS display overlay, which wants
|
||||
# more VRAM than this board can spare for a monitor it will never have.
|
||||
display_auto_detect = false;
|
||||
};
|
||||
|
||||
# Replaces the profile's default (vc4-kms-v3d), which is display hardware
|
||||
# this host never uses.
|
||||
deviceTreeOverlays.all = [
|
||||
# Move the PL011 UART off Bluetooth and onto GPIO 14/15, so /dev/ttyAMA0
|
||||
# is the RS232 header. The mini UART (ttyS0) derives its baud rate from
|
||||
# the core clock and drifts at 115200.
|
||||
{ disable-bt = { }; }
|
||||
# RTS/CTS on GPIO 16/17: the Psion's modem profile uses hardware flow
|
||||
# control, and so does pppd in ./serial-ppp.nix.
|
||||
{ uart0.ctsrts = true; }
|
||||
];
|
||||
};
|
||||
|
||||
# Wifi is the Pi's uplink and the route the Psion reaches the internet over
|
||||
# (./serial-ppp.nix masquerades onto it).
|
||||
networking.interfaces.wlan0.useDHCP = true;
|
||||
networking.wireless = {
|
||||
enable = true;
|
||||
interfaces = [ "wlan0" ];
|
||||
# PSKs stay out of the Nix store: wpa_supplicant reads them at runtime from
|
||||
# this file, which is created on the device (root-owned, 0600) and contains
|
||||
# psk_home=<the pre-shared key>
|
||||
# See ../../docs/hosts/pizero2w.md.
|
||||
secretsFile = "/var/lib/wpa_supplicant/secrets.conf";
|
||||
networks."CHANGE-ME-SSID".pskRaw = "ext:psk_home";
|
||||
};
|
||||
|
||||
# The board takes a DHCP lease over wifi, so its address moves. mDNS makes it
|
||||
# findable as lyrathorpe-zero2w.local instead of hunting through the router's
|
||||
# lease table -- which matters most on first boot, when it is the only way in.
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
publish = {
|
||||
enable = true;
|
||||
addresses = true;
|
||||
workstation = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Default-deny inbound. sshd opens 22 (../../modules/ssh.nix); everything the
|
||||
# Psion talks to is reached over the PPP link, which ./serial-ppp.nix marks
|
||||
# trusted.
|
||||
networking.firewall.enable = true;
|
||||
|
||||
# See `man configuration.nix` / the stateVersion docs before changing.
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# legacy-email-proxy: a cleartext POP3 (110) and SMTP (25) front end for the
|
||||
# Psion's built-in mail client, forwarded to authenticated IMAPS/SMTPS.
|
||||
#
|
||||
# The package, the systemd unit and its hardening all live upstream
|
||||
# (https://code.emmathe.dev/lyrathorpe/legacy-email-proxy); this host only
|
||||
# enables the service and points it at the credentials.
|
||||
{ inputs, ... }:
|
||||
{
|
||||
imports = [ inputs.legacy-email-proxy.nixosModules.default ];
|
||||
|
||||
services.legacy-email-proxy = {
|
||||
enable = true;
|
||||
|
||||
# The listeners are unauthenticated and unencrypted by design, so the
|
||||
# firewall is what confines them: ppp0 is trusted, wlan0 is not, and 110/25
|
||||
# are never opened there (./serial-ppp.nix). They stay on the default
|
||||
# 0.0.0.0 rather than the PPP address because 10.0.0.1 exists only while
|
||||
# the Psion is plugged in, and a bind-time dependency on a serial cable is
|
||||
# a restart loop waiting to happen.
|
||||
|
||||
# Backend hostnames and credentials. Kept out of the Nix store: created on
|
||||
# the device, root-owned 0600. See ../../docs/hosts/pizero2w.md.
|
||||
environmentFile = "/var/lib/legacy-email-proxy/backend.env";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# PLACEHOLDER hardware configuration for the Raspberry Pi Zero 2 W.
|
||||
#
|
||||
# This file is NOT the real generated config -- it exists only so the host
|
||||
# evaluates in CI before the Pi is provisioned. The machine will not boot from
|
||||
# it as-is. On first install, regenerate this file on the device with
|
||||
# nixos-generate-config --root /mnt
|
||||
# and replace this placeholder with the output (commit it). See ../../docs/hosts/pizero2w.md.
|
||||
#
|
||||
# Like every hardware-configuration.nix in this repo, this file is excluded from
|
||||
# the formatter and linters (see the pre-commit/treefmt excludes in flake.nix).
|
||||
{ modulesPath, ... }:
|
||||
{
|
||||
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
|
||||
|
||||
nixpkgs.hostPlatform = "aarch64-linux";
|
||||
|
||||
# The Zero 2 W boots from an SD card with a FAT firmware partition and an ext4
|
||||
# root. Labels match the conventional sd-image layout; the real generated
|
||||
# config will use by-uuid device paths instead.
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-label/NIXOS_SD";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot/firmware" = {
|
||||
device = "/dev/disk/by-label/FIRMWARE";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
# 512 MB of RAM and an SD card: no swap partition (SD cards wear out under
|
||||
# swap writes). zram takes its place; see ../../hosts/PiZero2W/configuration.nix.
|
||||
swapDevices = [ ];
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# SD-card image of this host, used exactly once: to bring the board up.
|
||||
#
|
||||
# Deliberately NOT imported by ./configuration.nix. The flake extends the host
|
||||
# with it (see packages.aarch64-linux.zero2w-sd-image in ../../flake.nix), so
|
||||
# the card carries the host's own kernel, config.txt and SSH keys rather than a
|
||||
# generic installer that then has to be reconfigured over a console this host
|
||||
# does not have -- pppd owns the serial port (./serial-ppp.nix).
|
||||
#
|
||||
# It does not carry the runtime secrets. Seed those into the card's root
|
||||
# partition before first boot; see ../../docs/hosts/pizero2w.md.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [ "${modulesPath}/installer/sd-card/sd-image.nix" ];
|
||||
|
||||
# sd-image.nix pulls in profiles/all-hardware.nix, which is every driver and
|
||||
# firmware blob NixOS knows about. The raspberry-pi-3 profile already carries
|
||||
# what this board has, and the card is small.
|
||||
hardware.enableAllHardware = lib.mkForce false;
|
||||
|
||||
image.baseName = "nixos-zero2w";
|
||||
|
||||
sdImage = {
|
||||
# Compressing costs a long single-threaded pass and buys nothing: the image
|
||||
# is written straight to a card with dd.
|
||||
compressImage = false;
|
||||
|
||||
# The default 30 MiB does not hold the vendor GPU firmware, U-Boot and the
|
||||
# BCM2837 device trees and overlays that nixos-hardware installs here.
|
||||
firmwareSize = 128;
|
||||
|
||||
# The firmware partition is populated by nixos-hardware's firmware module
|
||||
# (it takes over sdImage.populateFirmwareCommands); the root side is the
|
||||
# stock extlinux install, which no longer arrives with it.
|
||||
populateRootCommands = ''
|
||||
mkdir -p ./files/boot
|
||||
${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# The serial half of the Psion sidecar: a PPP link to a Psion 5MX over
|
||||
# /dev/ttyAMA0 (RS232 level shifter on the GPIO header, 115200 8N1 with
|
||||
# RTS/CTS), masqueraded out of wifi, plus a telnet login for the Psion's
|
||||
# terminal client.
|
||||
#
|
||||
# Cleartext telnet and unauthenticated PPP are safe *only* because the link is
|
||||
# a two-node cable: the peer is a machine from 1999 that speaks no TLS. Nothing
|
||||
# here is exposed to wlan0.
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
# Point-to-point addresses for the serial link; nothing else routes here.
|
||||
piAddress = "10.0.0.1";
|
||||
psionAddress = "10.0.0.2";
|
||||
in
|
||||
{
|
||||
# pppd needs exclusive use of the port. NixOS starts a getty on any serial
|
||||
# console named in boot.kernelParams; ttyAMA0 is not one today, but disable it
|
||||
# explicitly so a later kernel-param change cannot silently steal the line.
|
||||
systemd.services."serial-getty@ttyAMA0".enable = false;
|
||||
|
||||
services.pppd = {
|
||||
enable = true;
|
||||
peers.psion.config = ''
|
||||
/dev/ttyAMA0
|
||||
115200
|
||||
${piAddress}:${psionAddress}
|
||||
|
||||
# Hardware flow control, matching the Psion's modem profile.
|
||||
crtscts
|
||||
|
||||
# A null-modem cable has no carrier detect and no peer to authenticate.
|
||||
local
|
||||
noauth
|
||||
|
||||
# The systemd unit is Type=notify, so pppd must stay in the foreground.
|
||||
nodetach
|
||||
lock
|
||||
|
||||
# Wait for the Psion rather than failing when it is unplugged, and keep
|
||||
# waiting for the next time it is plugged back in.
|
||||
passive
|
||||
persist
|
||||
maxfail 0
|
||||
holdoff 1
|
||||
|
||||
# Hand the Psion resolvers over the link, so its Internet profile can set
|
||||
# "get DNS from server = True" instead of hard-coding them.
|
||||
ms-dns 1.1.1.1
|
||||
ms-dns 8.8.8.8
|
||||
'';
|
||||
};
|
||||
|
||||
# The Psion's route to the internet. The original write-up used pppd's
|
||||
# proxyarp instead; NAT keeps the Psion out of the LAN broadcast domain and
|
||||
# does not depend on what the wifi router tolerates.
|
||||
networking.nat = {
|
||||
enable = true;
|
||||
externalInterface = "wlan0";
|
||||
internalIPs = [ "${psionAddress}/32" ];
|
||||
};
|
||||
|
||||
# Everything the Psion connects to (telnet here, POP3/SMTP in
|
||||
# ./email-proxy.nix) is reachable over the PPP link and nowhere else.
|
||||
networking.firewall.trustedInterfaces = [ "ppp0" ];
|
||||
|
||||
# The Psion's terminal client speaks telnet over TCP, which it renders far
|
||||
# better than the raw serial console. Socket-activated, one process per
|
||||
# connection; busybox's telnetd in inetd mode hands straight over to login.
|
||||
systemd.sockets.telnetd = {
|
||||
description = "Telnet login socket for the Psion";
|
||||
wantedBy = [ "sockets.target" ];
|
||||
listenStreams = [ "${piAddress}:23" ];
|
||||
socketConfig = {
|
||||
Accept = true;
|
||||
# ppp0 (and with it 10.0.0.1) only exists while the Psion is connected;
|
||||
# FreeBind lets the socket be listening before that.
|
||||
FreeBind = true;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services."telnetd@" = {
|
||||
description = "Telnet login for the Psion";
|
||||
serviceConfig = {
|
||||
ExecStart = "-${pkgs.busybox}/bin/busybox telnetd -i -l ${pkgs.shadow}/bin/login";
|
||||
StandardInput = "socket";
|
||||
StandardError = "journal";
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user