Compare commits
1
Commits
main
..
a94a749f29
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a94a749f29 |
@@ -8,12 +8,13 @@ single flake.
|
|||||||
Defined in the host table in [`flake.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/flake.nix):
|
Defined in the host table in [`flake.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/flake.nix):
|
||||||
|
|
||||||
| Configuration | System | Machine |
|
| Configuration | System | Machine |
|
||||||
| --------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) |
|
| `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-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-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/) |
|
| `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-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/) |
|
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) — [notes](https://docs.lyrapup.pet/nixfiles/hosts/darwin/) |
|
||||||
|
|
||||||
Shared layers: `home` (home-manager: shell, git, editor),
|
Shared layers: `home` (home-manager: shell, git, editor),
|
||||||
@@ -21,8 +22,8 @@ Shared layers: `home` (home-manager: shell, git, editor),
|
|||||||
`modules/workstation.nix` (physical graphical hosts: audio, thermald,
|
`modules/workstation.nix` (physical graphical hosts: audio, thermald,
|
||||||
earlyoom, fwupd), `modules/laptop.nix` (laptops: Wi-Fi, Bluetooth, power,
|
earlyoom, fwupd), `modules/laptop.nix` (laptops: Wi-Fi, Bluetooth, power,
|
||||||
lid), `modules/desktop.nix` (wired desktops: NetworkManager), and
|
lid), `modules/desktop.nix` (wired desktops: NetworkManager), and
|
||||||
`modules/ssh.nix` (key-only sshd). The x86 hosts also pull `nixos-hardware`
|
`modules/ssh.nix` (key-only sshd). The x86 hosts and both Raspberry Pis also
|
||||||
profiles. The full module catalogue is below.
|
pull `nixos-hardware` profiles. The full module catalogue is below.
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ module reaches a host: **baseModules** (every NixOS host, via `flake.nix`),
|
|||||||
(pulled in by another module's `imports`).
|
(pulled in by another module's `imports`).
|
||||||
|
|
||||||
| Module | Imported by | What it does / when to use it |
|
| 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. |
|
| `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`. |
|
| `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". |
|
| `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". |
|
||||||
@@ -65,7 +66,7 @@ module reaches a host: **baseModules** (every NixOS host, via `flake.nix`),
|
|||||||
| `laptop.nix` | host table (MBP, T400) | `imports` workstation.nix, then adds the portable bits: iwd Wi-Fi, lid suspend/lock, Bluetooth + blueman. |
|
| `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. |
|
| `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`. |
|
| `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`. |
|
| `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"). |
|
| `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
|
Form-factor decision: a **laptop** imports `laptop.nix` (default
|
||||||
|
|||||||
@@ -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. |
|
||||||
+2
-10
@@ -186,14 +186,6 @@ fleet's stock "wheel, with a password" policy. What it does **not** implement:
|
|||||||
host aliases, LDAP/SSSD sudoers, `sudoreplay`, and most `Defaults` settings.
|
host aliases, LDAP/SSSD sudoers, `sudoreplay`, and most `Defaults` settings.
|
||||||
Needing any of those means reverting to `security.sudo`.
|
Needing any of those means reverting to `security.sudo`.
|
||||||
|
|
||||||
One exception to the password: the EDaaS box sets
|
|
||||||
`security.sudo-rs.wheelNeedsPassword = false`. NixOS-WSL ships that default for
|
|
||||||
`security.sudo` — WSL has no console login, so the trust boundary is the Windows
|
|
||||||
session and the Linux account password is never one the user chose — and the
|
|
||||||
option does not carry across to the `security.sudo-rs` module, which defaults to
|
|
||||||
requiring one. Without the explicit setting, `sudo` on that host prompts for a
|
|
||||||
password nobody knows.
|
|
||||||
|
|
||||||
If a host ever refuses to escalate, get a root shell that does not go through
|
If a host ever refuses to escalate, get a root shell that does not go through
|
||||||
sudo (`wsl -u root -d NixOS` on the work box; the console or a serial/HDMI login
|
sudo (`wsl -u root -d NixOS` on the work box; the console or a serial/HDMI login
|
||||||
elsewhere) and roll back with `nixos-rebuild switch --rollback`, or pick the
|
elsewhere) and roll back with `nixos-rebuild switch --rollback`, or pick the
|
||||||
@@ -358,10 +350,10 @@ Claude to route new memories there.
|
|||||||
## Per-host differences
|
## Per-host differences
|
||||||
|
|
||||||
| | Personal Linux (sway) | macOS | Work WSL (EDaaS) |
|
| | Personal Linux (sway) | macOS | Work WSL (EDaaS) |
|
||||||
| --------------------------- | --------------------- | --------------------- | ---------------------------- |
|
| --------------------------- | --------------------- | --------------------- | --------------------------- |
|
||||||
| Auto-tmux | yes (foot/TTY) | yes (iTerm2) | yes (WSL shell) |
|
| Auto-tmux | yes (foot/TTY) | yes (iTerm2) | yes (WSL shell) |
|
||||||
| `kubectl` → kubecolor | no (no kubectl) | no | yes (work module) |
|
| `kubectl` → kubecolor | no (no kubectl) | no | yes (work module) |
|
||||||
| `sudo` implementation | sudo-rs (password) | Apple sudo + Touch ID | sudo-rs (passwordless wheel) |
|
| `sudo` implementation | sudo-rs | Apple sudo + Touch ID | sudo-rs |
|
||||||
| git email | `iam@emmathe.dev` | `iam@emmathe.dev` | `…@citrix.com` (work) |
|
| git email | `iam@emmathe.dev` | `iam@emmathe.dev` | `…@citrix.com` (work) |
|
||||||
| ssh config managed | yes | yes | no (keeps corporate config) |
|
| ssh config managed | yes | yes | no (keeps corporate config) |
|
||||||
| ssh-agent | yes | launchd | yes (work module) |
|
| ssh-agent | yes | launchd | yes (work module) |
|
||||||
|
|||||||
Generated
+52
-31
@@ -3,16 +3,16 @@
|
|||||||
"brew-src": {
|
"brew-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1786945682,
|
"lastModified": 1786348930,
|
||||||
"narHash": "sha256-VBESSoJccikdhxh3vp3SQeG7cZXTOulMvVkoSqNDEhs=",
|
"narHash": "sha256-bCQJkbgsAMDp5HQystZLCq11UHiyEuoWbxKulAPYrh8=",
|
||||||
"owner": "Homebrew",
|
"owner": "Homebrew",
|
||||||
"repo": "brew",
|
"repo": "brew",
|
||||||
"rev": "5b90e281d4e0c8fbd6ca4d8358276fb305b8d0bd",
|
"rev": "3ecc9eff23feebf1bc73846d74e14a122c93b66f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "Homebrew",
|
"owner": "Homebrew",
|
||||||
"ref": "6.0.18",
|
"ref": "6.0.16",
|
||||||
"repo": "brew",
|
"repo": "brew",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "pkgs/firefox-addons",
|
"dir": "pkgs/firefox-addons",
|
||||||
"lastModified": 1787457768,
|
"lastModified": 1786853140,
|
||||||
"narHash": "sha256-cbgeu5NTb6DtB+tNs4E6z6K/1XKKM90gVmlkWMJe+gY=",
|
"narHash": "sha256-O880FlUav75Q5aNlg9znyg/avf1X/W7o/cAtZFLtpWc=",
|
||||||
"owner": "rycee",
|
"owner": "rycee",
|
||||||
"repo": "nur-expressions",
|
"repo": "nur-expressions",
|
||||||
"rev": "25cfc8fdc413d73b3a47e3e86dafcad51cf5c9f9",
|
"rev": "ba9568c9c0df6290dc2f34b032ab4cb575e73788",
|
||||||
"type": "gitlab"
|
"type": "gitlab"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -135,11 +135,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787424939,
|
"lastModified": 1784288435,
|
||||||
"narHash": "sha256-O2tBn84NNuHrnqNVxx/XqsXwfYvS1YwBh+7CBnbCYsk=",
|
"narHash": "sha256-ReRHaLgr/uVqdD8afFSn+myXIfpHeOhP0yYe0TJqAA8=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "git-hooks.nix",
|
"repo": "git-hooks.nix",
|
||||||
"rev": "809414f0cdadf82cf11b06c2b29ba9b3168b3297",
|
"rev": "43b3c1ab9d40fb1dbb008f451988a91e375825e9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -155,11 +155,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787377438,
|
"lastModified": 1786924861,
|
||||||
"narHash": "sha256-Sxu1NLTD/Ern6hFGLlZmtKCSct3YQXZI/lls8RE1XeM=",
|
"narHash": "sha256-hftabkb+73OcGzvwFAjCiQorAhprs9TnU1+FkGO5CIw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "65258d5c65a250189fde2e35f490d15e064c4c62",
|
"rev": "09ae1b85a6db412d841d60f924b23f881f0d0a38",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -185,6 +185,26 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"legacy-email-proxy": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1787315211,
|
||||||
|
"narHash": "sha256-FuZ9nXMRtnMPO/wbjYkpsKn6K/FFc64P5XDmCyfyxGs=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "f1e1373fd350fd77f1848eddfa67ed9e00724c25",
|
||||||
|
"revCount": 13,
|
||||||
|
"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": {
|
"nix-darwin": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -211,11 +231,11 @@
|
|||||||
"brew-src": "brew-src"
|
"brew-src": "brew-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787330919,
|
"lastModified": 1786686423,
|
||||||
"narHash": "sha256-LslMncqN7uOOH5S88WZtO/EVt2HwD8ltUnfyANk+mC0=",
|
"narHash": "sha256-8q3WdB8o3VUI7rOz1OXfioXIaaWbFTAxRJAkWLlfc0s=",
|
||||||
"owner": "zhaofengli",
|
"owner": "zhaofengli",
|
||||||
"repo": "nix-homebrew",
|
"repo": "nix-homebrew",
|
||||||
"rev": "b00218e4aec0e5bf07d61a0bb13f842faa582d7b",
|
"rev": "ccabf79a6b9845eb72b51ea1d9c7ce3446350df3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -231,11 +251,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787457452,
|
"lastModified": 1786852476,
|
||||||
"narHash": "sha256-FJh4esFS3zqNNuKwvN3t6wrJGewqp1AUF9DAEvoKPD8=",
|
"narHash": "sha256-IM5CYtf86W4w8eUPpKcY/LpdHElmVBtJhaKnoTKxZEA=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nix-index-database",
|
"repo": "nix-index-database",
|
||||||
"rev": "c51d5c2ba69c907a34e90c9b6b80cd2b93811745",
|
"rev": "c7962dc97b45129df8d751bedaf37beb5a17706e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -272,11 +292,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787144466,
|
"lastModified": 1786867632,
|
||||||
"narHash": "sha256-HHfv2/HkNSKbbSyU9iD/g8lbP6r4tl33sSw1W4rXCk0=",
|
"narHash": "sha256-ez+ubZlA1RtdjCB18a6zJ9M4u8qoPDy08EcnsW5M3Xw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixos-hardware",
|
"repo": "nixos-hardware",
|
||||||
"rev": "0471accf8d0a8210b31d947497d179ecc99e0021",
|
"rev": "ff17823245ab9ff7bcae6acf950bd89cba82c38c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -308,11 +328,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787414105,
|
"lastModified": 1786711500,
|
||||||
"narHash": "sha256-WncT27+3BOkgTaJZLnCsf3LcYf9RXMuR9ONSN4rzQ7s=",
|
"narHash": "sha256-QvnceIGTBeDvDd9oCn+GvdsnkquliuwbVgpiRH68qaQ=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a9e6d84f9c2f9012f5fe7d964a7851352300e61a",
|
"rev": "02e08985a27c65ffd33d434eeb2e660a2e4dc84d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -324,11 +344,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787360063,
|
"lastModified": 1786862985,
|
||||||
"narHash": "sha256-dt4WdcvsA8/RCe+VZZwqU0X+XMM3wBbGCWA0/sFWzGo=",
|
"narHash": "sha256-FBJRXmbGXiSUDvYEbfLYRkckayyZ6SK1UEqhCrIZ2Cs=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2c423e03bbafcff28bfadc6781a4a8257f205cb5",
|
"rev": "e5bdc4a41d4c072fe1e3787eaa0320a384741d44",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -347,11 +367,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787536726,
|
"lastModified": 1786873773,
|
||||||
"narHash": "sha256-aBh5Yk9tX8ZV4k10BJr2fvTq0/+iWGegaCMUOU7YKas=",
|
"narHash": "sha256-Hj/nkhKDv0aJly1PAUstrhrgEYn1mVSkLIYMh90r/Pc=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixvim",
|
"repo": "nixvim",
|
||||||
"rev": "e2c3f9f36326d07340626847543c557e2b95fb50",
|
"rev": "b397fb9f6950d57355d62bb92457d223464e0115",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -368,6 +388,7 @@
|
|||||||
"git-hooks": "git-hooks",
|
"git-hooks": "git-hooks",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"kube-tmux": "kube-tmux",
|
"kube-tmux": "kube-tmux",
|
||||||
|
"legacy-email-proxy": "legacy-email-proxy",
|
||||||
"nix-darwin": "nix-darwin",
|
"nix-darwin": "nix-darwin",
|
||||||
"nix-homebrew": "nix-homebrew",
|
"nix-homebrew": "nix-homebrew",
|
||||||
"nix-index-database": "nix-index-database",
|
"nix-index-database": "nix-index-database",
|
||||||
|
|||||||
@@ -67,6 +67,13 @@
|
|||||||
url = "github:jonmosco/kube-tmux";
|
url = "github:jonmosco/kube-tmux";
|
||||||
flake = false;
|
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 =
|
outputs =
|
||||||
@@ -327,6 +334,26 @@
|
|||||||
./users/lyrathorpe/home.nix
|
./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
|
# Darwin host table — macOS machines built via mkDarwinHost. The shared
|
||||||
@@ -365,8 +392,24 @@
|
|||||||
# nixpkgs instance for that system. Outputs here become per-system
|
# nixpkgs instance for that system. Outputs here become per-system
|
||||||
# attrsets automatically (e.g. devShells.<system>.default).
|
# attrsets automatically (e.g. devShells.<system>.default).
|
||||||
perSystem =
|
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
|
# treefmt drives `nix fmt` and the formatting check below. nixfmt
|
||||||
# stays the .nix formatter (the tree is already nixfmt-formatted);
|
# stays the .nix formatter (the tree is already nixfmt-formatted);
|
||||||
# shfmt covers shell and prettier covers markdown/yaml/json.
|
# shfmt covers shell and prettier covers markdown/yaml/json.
|
||||||
|
|||||||
@@ -15,5 +15,3 @@
|
|||||||
- [Dev clusters disposable](dev_clusters_disposable.md) — Lyra's dev clusters are recreatable; mutate/break freely, no confirmation needed
|
- [Dev clusters disposable](dev_clusters_disposable.md) — Lyra's dev clusters are recreatable; mutate/break freely, no confirmation needed
|
||||||
- [Nix shell tooling](nix_shell_tooling.md) — any nixpkgs tool runs ad hoc via `nix run`/`nix shell nixpkgs#<pkg>`; a missing command is never a dead end
|
- [Nix shell tooling](nix_shell_tooling.md) — any nixpkgs tool runs ad hoc via `nix run`/`nix shell nixpkgs#<pkg>`; a missing command is never a dead end
|
||||||
- [WSP local build and test](wsp_local_build_and_test.md) — core-services-cloud on this box: dotnet via nix, artifactory creds from `~/.artifactoryenv` sourced per command, how to tell auth failure from a code failure
|
- [WSP local build and test](wsp_local_build_and_test.md) — core-services-cloud on this box: dotnet via nix, artifactory creds from `~/.artifactoryenv` sourced per command, how to tell auth failure from a code failure
|
||||||
- [WSP-32957 PIM migration](wsp_32957_pim_migration.md) — AKS RBAC to PIM + AutoPerm decommission; prod is in the Technical Preview subscription, three tenants; resume via `~/code/WSP-32957-CONTINUATION.md`
|
|
||||||
- [Entra group member reads](entra_group_member_reads.md) — `az ad group member list` hides service principal members; use the servicePrincipal cast or transitiveMemberOf, and trust the Terraform plan over it
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
name: entra-group-member-reads
|
|
||||||
description: az ad group member list and Graph /members silently omit service principal members — use the servicePrincipal cast or transitiveMemberOf when a group is expected to hold an SPN.
|
|
||||||
metadata:
|
|
||||||
node_type: memory
|
|
||||||
type: reference
|
|
||||||
---
|
|
||||||
|
|
||||||
`az ad group member list --group <id>` and `GET /groups/{id}/members` both return an **empty collection**, with no error, for a group whose only members are service principals — at least when called with Lyra's user account. The read looks authoritative and is not.
|
|
||||||
|
|
||||||
**Reliable reads instead:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# members, cast to the type that is being hidden
|
|
||||||
az rest --method get --url "https://graph.microsoft.com/v1.0/groups/<gid>/members/microsoft.graph.servicePrincipal?\$select=id,displayName"
|
|
||||||
|
|
||||||
# count, which does not filter
|
|
||||||
az rest --method get --url "https://graph.microsoft.com/v1.0/groups/<gid>/members/\$count" --headers ConsistencyLevel=eventual
|
|
||||||
|
|
||||||
# from the principal's side
|
|
||||||
az rest --method get --url "https://graph.microsoft.com/v1.0/servicePrincipals/<spid>/transitiveMemberOf?\$select=id,displayName"
|
|
||||||
az rest --method post --url "https://graph.microsoft.com/v1.0/servicePrincipals/<spid>/checkMemberGroups" \
|
|
||||||
--body '{"groupIds":["<gid>"]}' --headers "Content-Type=application/json"
|
|
||||||
```
|
|
||||||
|
|
||||||
**How to apply:** any group that deployment or automation identities belong to — `*-cluster-admins`, `*-keyvault`, anything created by `rg-prereqs` — must be checked with one of the above before concluding it is empty. Cross-check against Terraform: a plan reporting "No changes" against a `members` attribute is strong evidence the membership is present, and outranks the `az` read. On 2026-08-28 the plain read produced a Bug (WSP-33432, cancelled) claiming five `*-cluster-admins` groups across three tenants had been emptied; all five held their deployment principals the whole time.
|
|
||||||
|
|
||||||
Related: [[wsp-32957-pim-migration]].
|
|
||||||
@@ -9,9 +9,7 @@ Field map for the **WSP (Workspace Platform)** Jira project, to create tickets w
|
|||||||
|
|
||||||
**Issue-type IDs:** Epic `10000`, Story `10004`, Task `10008`, Bug `10123`, Sub-task `10009`.
|
**Issue-type IDs:** Epic `10000`, Story `10004`, Task `10008`, Bug `10123`, Sub-task `10009`.
|
||||||
|
|
||||||
**Fast path — use Task, not Bug.** A `Task` requires `summary` plus **Task Type** `customfield_15622` (added since this note was first written; the create validator enforces it even though `createmeta` omits it, same trap as Bug's `versions`). Options value=id: Dev Task=34065, CQE Task=34066, Investigation=34067, Security=34068, Maintenance=34069, Release=34070 — use `Maintenance` for refactors and tidy-ups, `Dev Task` for feature work. A `Bug` requires six extra fields (below), so only pick Bug when it must be a Bug. The sibling infra/remediation tickets in WSP are Tasks.
|
**Fast path — use Task, not Bug.** A `Task` requires only `summary` (project/issuetype auto, reporter defaults to caller). A `Bug` requires six extra fields (below), so only pick Bug when it must be a Bug. The sibling infra/remediation tickets in WSP are Tasks.
|
||||||
|
|
||||||
Example Task `additional_fields`: `{"customfield_15622":{"id":"34069"},"components":[{"name":"Multicluster Platform"}]}`
|
|
||||||
|
|
||||||
**Bug required fields** (enforced by the create validator; note `createmeta` omits `versions` but the API rejects without it):
|
**Bug required fields** (enforced by the create validator; note `createmeta` omits `versions` but the API rejects without it):
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
name: wsp-32957-pim-migration
|
|
||||||
description: State of the WSP AKS-RBAC-to-PIM migration and AutoPerm decommission, and how to resume it
|
|
||||||
metadata:
|
|
||||||
node_type: memory
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
Long-running epic (August 2026) moving WSP's AKS cluster RBAC off **AutoPerm
|
|
||||||
Manager**-maintained `wsp-*` groups onto the per-subscription **`CEO-*` Azure PIM**
|
|
||||||
groups, then retiring AutoPerm. Epic **WSP-32957**; on the critical path for Zensar
|
|
||||||
L1 on-call (WSP-32193). Work happens in **`~/code/multicluster`**
|
|
||||||
(`terraform/cluster`, `terraform/cluster-k8s-resources`, `products/*/environments/*`).
|
|
||||||
|
|
||||||
**Resume from `~/code/WSP-32957-CONTINUATION.md`** — full state, branch list,
|
|
||||||
verified object IDs, findings and next steps. Jira is the durable record; that file
|
|
||||||
is the index. Keep it updated as work lands ([[docs-keep-updated]]).
|
|
||||||
|
|
||||||
**Landed:** WSP-33141 (multicluster PR #1808, merged `b047163c`) added object-ID
|
|
||||||
inputs — `admin_group_oids` on `cluster`, and `cluster_user_group_oids` /
|
|
||||||
`cluster_viewer_group_oids` / `cluster_superuser_group_oids` on
|
|
||||||
`cluster-k8s-resources`. Supplying IDs _replaces_ the display-name lookup and leaves
|
|
||||||
the `data "azuread_group"` unread, which is what will let the legacy groups be
|
|
||||||
deleted. Nothing sets them yet, so behaviour is unchanged. Repoint branches for test
|
|
||||||
(WSP-33067) and staging (WSP-33068) are pushed but have **no PR** — both gated on
|
|
||||||
decisions, not code.
|
|
||||||
|
|
||||||
**Facts that cost real effort to establish, do not re-derive:**
|
|
||||||
|
|
||||||
- **Production runs in `fc7af6ae-…` (_Workspace Platform Technical Preview_), not
|
|
||||||
`d6d75d07-…` (_Workspace Platform Production_)**, which holds no clusters. The
|
|
||||||
epic was wrong about this for its whole life and every production `CEO-*` group
|
|
||||||
name and object ID had to change. Because `CEO-*` names embed the subscription
|
|
||||||
name, **always re-verify object IDs against live Entra rather than trusting the
|
|
||||||
epic table.**
|
|
||||||
- **Three tenants**, not two: `6f4fe054` (prod, prod JP), `335836de` (staging,
|
|
||||||
staging JP, test), `3eae2746` (dev). Each `wsp-*` name is a distinct object in
|
|
||||||
each tenant.
|
|
||||||
- `wsp-staging-cluster-admins` and `wsp-test-cluster-admins` are **empty**, so
|
|
||||||
`wsp-owner` is the _only_ path to `cluster-admin` in staging. Never drop it before
|
|
||||||
`SuperAdmin-*` is proven — hence the staging branch is split into an additive
|
|
||||||
commit and a cutover commit.
|
|
||||||
- **No break-glass exists**: `disableLocalAccounts = True` on every cluster; only the
|
|
||||||
deployment SPNs authenticate non-interactively.
|
|
||||||
- Graph **PIM-for-Groups is unreadable via `az`** (the CLI's first-party client lacks
|
|
||||||
`PrivilegedAccess.Read.AzureADGroup`, on both `v1.0` and `beta`). Use the portal or
|
|
||||||
`Connect-MgGraph -Scopes PrivilegedAccess.Read.AzureADGroup`.
|
|
||||||
|
|
||||||
**Watch for:** the epic gets rewritten by James Weldrake between sessions — re-read
|
|
||||||
the description before acting, and check which child tickets are still live
|
|
||||||
(WSP-33062/33063/33064/33066 were cancelled 2026-08-24, and dev was put out of
|
|
||||||
scope). Verified findings have repeatedly contradicted the epic text
|
|
||||||
([[copilot-review-false-positives]] is the same instinct: check against reality
|
|
||||||
first).
|
|
||||||
|
|
||||||
Queued Slack messages and the leaver report live as `~/code/*.txt` alongside the
|
|
||||||
continuation file; see the table in it for what has and has not been sent
|
|
||||||
([[workflow-review-and-comments]] — show them before they go out).
|
|
||||||
+1
-35
@@ -287,22 +287,7 @@ in
|
|||||||
plugins = with pkgs.tmuxPlugins; [
|
plugins = with pkgs.tmuxPlugins; [
|
||||||
sensible
|
sensible
|
||||||
vim-tmux-navigator # Ctrl-h/j/k/l across vim splits and tmux panes
|
vim-tmux-navigator # Ctrl-h/j/k/l across vim splits and tmux panes
|
||||||
{
|
yank
|
||||||
# On WSL, tmux-yank pipes the selection to clip.exe, which decodes its
|
|
||||||
# stdin as the OEM codepage instead of UTF-8 -- an em dash reaches the
|
|
||||||
# Windows clipboard as three characters. Route through tmux's own
|
|
||||||
# buffer instead: with set-clipboard on, tmux emits OSC 52 and the
|
|
||||||
# terminal takes the text as UTF-8. Windows Terminal honours OSC 52;
|
|
||||||
# iTerm2 does not by default, hence the runtime guard rather than
|
|
||||||
# overriding pbcopy/xsel on every host. yank.tmux bakes the command
|
|
||||||
# into its key bindings when it loads, so this must be set first, which
|
|
||||||
# is what plugin extraConfig gives us.
|
|
||||||
plugin = yank;
|
|
||||||
extraConfig = ''
|
|
||||||
if-shell 'grep -qi microsoft /proc/version 2>/dev/null' \
|
|
||||||
"set -g @override_copy_command 'tmux load-buffer -w -'"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
extrakto # prefix+Tab: fzf-grab paths/URLs/text from the pane into the prompt
|
extrakto # prefix+Tab: fzf-grab paths/URLs/text from the pane into the prompt
|
||||||
{
|
{
|
||||||
# Catppuccin Mocha statusline (v2 API: flavour + window options must be
|
# Catppuccin Mocha statusline (v2 API: flavour + window options must be
|
||||||
@@ -313,14 +298,6 @@ in
|
|||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
set -g @catppuccin_flavor 'mocha'
|
set -g @catppuccin_flavor 'mocha'
|
||||||
set -g @catppuccin_window_status_style 'rounded'
|
set -g @catppuccin_window_status_style 'rounded'
|
||||||
# Catppuccin's default window text is #T, the pane title, which every
|
|
||||||
# program in the pane is free to overwrite -- the shell writes the
|
|
||||||
# hostname, Claude Code writes its current task, and a hand-set window
|
|
||||||
# name never appears. Show the window name instead, falling back to the
|
|
||||||
# pane title when the window holds a single pane and the two carry the
|
|
||||||
# same information anyway.
|
|
||||||
set -g @catppuccin_window_text ' #{?#{==:#{window_panes},1},#T,#W}'
|
|
||||||
set -g @catppuccin_window_current_text ' #{?#{==:#{window_panes},1},#T,#W}'
|
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
resurrect # save/restore sessions
|
resurrect # save/restore sessions
|
||||||
@@ -365,17 +342,6 @@ in
|
|||||||
set -g renumber-windows on
|
set -g renumber-windows on
|
||||||
set -g set-clipboard on
|
set -g set-clipboard on
|
||||||
|
|
||||||
# Pane titles on the border, but only once a window is split -- a single
|
|
||||||
# pane's title is already in the status bar. pane-border-status takes no
|
|
||||||
# format, so the hook recomputes it whenever the layout changes, which
|
|
||||||
# covers both splitting and closing a pane.
|
|
||||||
set -g pane-border-format " #P #{pane_title} "
|
|
||||||
set -g pane-border-status off
|
|
||||||
set-hook -g window-layout-changed 'set -Fw pane-border-status "#{?#{>:#{window_panes},1},top,off}"'
|
|
||||||
|
|
||||||
# Pane titles have no default binding.
|
|
||||||
bind T command-prompt -p "pane title:" "select-pane -T '%%'"
|
|
||||||
|
|
||||||
# Catppuccin v2 statusline. Must run after the plugin has loaded;
|
# Catppuccin v2 statusline. Must run after the plugin has loaded;
|
||||||
# home-manager appends this extraConfig after the whole plugin list.
|
# home-manager appends this extraConfig after the whole plugin list.
|
||||||
set -g status-left-length 100
|
set -g status-left-length 100
|
||||||
|
|||||||
@@ -161,11 +161,6 @@
|
|||||||
dock = {
|
dock = {
|
||||||
show-recents = false;
|
show-recents = false;
|
||||||
mru-spaces = false; # don't reorder spaces by use
|
mru-spaces = false; # don't reorder spaces by use
|
||||||
# Disable hot-corners
|
|
||||||
wvous-tr-corner = 1;
|
|
||||||
wvous-tl-corner = 1;
|
|
||||||
wvous-bl-corner = 1;
|
|
||||||
wvous-br-corner = 1;
|
|
||||||
};
|
};
|
||||||
finder = {
|
finder = {
|
||||||
AppleShowAllExtensions = true;
|
AppleShowAllExtensions = true;
|
||||||
|
|||||||
@@ -60,11 +60,6 @@
|
|||||||
## patch the script
|
## patch the script
|
||||||
systemd.services.docker-desktop-proxy.script = lib.mkForce ''${config.wsl.wslConf.automount.root}/wsl/docker-desktop/docker-desktop-user-distro proxy --docker-desktop-root ${config.wsl.wslConf.automount.root}/wsl/docker-desktop "C:\Program Files\Docker\Docker\resources"'';
|
systemd.services.docker-desktop-proxy.script = lib.mkForce ''${config.wsl.wslConf.automount.root}/wsl/docker-desktop/docker-desktop-user-distro proxy --docker-desktop-root ${config.wsl.wslConf.automount.root}/wsl/docker-desktop "C:\Program Files\Docker\Docker\resources"'';
|
||||||
|
|
||||||
# NixOS-WSL's passwordless wheel default only covers `security.sudo`; the
|
|
||||||
# sudo-rs swap in common-nixos.nix needs it set again. No console login here,
|
|
||||||
# and no account password anyone knows.
|
|
||||||
security.sudo-rs.wheelNeedsPassword = false;
|
|
||||||
|
|
||||||
features.swayDesktop.enable = false;
|
features.swayDesktop.enable = false;
|
||||||
|
|
||||||
# NOTE: this user's systemd --user lingering -- so the home-manager renovate
|
# NOTE: this user's systemd --user lingering -- so the home-manager renovate
|
||||||
|
|||||||
@@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
# Headless server: the Sway desktop is intentionally not set up. modules/sway.nix is
|
# Headless server: the Sway desktop is intentionally not set up. modules/sway.nix is
|
||||||
# not imported and features.swayDesktop.enable defaults to false (declared in
|
# not imported and features.swayDesktop.enable defaults to false (declared in
|
||||||
# modules/features.nix), so this host keeps plain TTY/SSH login.
|
# system/modules/features.nix), so this host keeps plain TTY/SSH login.
|
||||||
|
|
||||||
# Raspberry Pi boots via U-Boot + extlinux, not GRUB/systemd-boot. The
|
# Raspberry Pi boots via U-Boot + extlinux, not GRUB/systemd-boot. The
|
||||||
# raspberry-pi-5 nixos-hardware profile supplies the kernel, firmware and
|
# raspberry-pi-5 nixos-hardware profile supplies the kernel, firmware and
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
# The features.swayDesktop.enable option is declared in
|
# The features.swayDesktop.enable option is declared in
|
||||||
# modules/features.nix (so headless hosts can read/set it without
|
# system/modules/features.nix (so headless hosts can read/set it without
|
||||||
# importing this module). This module only provides its implementation.
|
# importing this module). This module only provides its implementation.
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
programs.sway = {
|
programs.sway = {
|
||||||
|
|||||||
+2
-2
@@ -29,10 +29,10 @@
|
|||||||
// lib.optionalAttrs (spec ? linger) { inherit (spec) linger; }
|
// lib.optionalAttrs (spec ? linger) { inherit (spec) linger; }
|
||||||
) hostUsers;
|
) hostUsers;
|
||||||
|
|
||||||
programs.firefox = lib.mkIf config.features.swayDesktop.enable {
|
programs.firefox = lib.mkIf (config.features.swayDesktop.enable == true) {
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
programs.thunderbird = lib.mkIf config.features.swayDesktop.enable {
|
programs.thunderbird = lib.mkIf (config.features.swayDesktop.enable == true) {
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,23 +47,7 @@
|
|||||||
pkgs.terraform-docs # generate Terraform module docs
|
pkgs.terraform-docs # generate Terraform module docs
|
||||||
pkgs.yq-go # jq for YAML
|
pkgs.yq-go # jq for YAML
|
||||||
pkgs.gcx # Grafana Cloud CLI (dashboards, SLOs, synthetics, alerts)
|
pkgs.gcx # Grafana Cloud CLI (dashboards, SLOs, synthetics, alerts)
|
||||||
|
|
||||||
# WSL ships no xdg-open, so anything that shells out to a browser dies with
|
|
||||||
# `exec: "xdg-open,x-www-browser,www-browser": executable file not found`.
|
|
||||||
# kubelogin's interactive login is the one that bites: it is the login mode
|
|
||||||
# the shared cluster kubeconfig uses. Hand the URL to Windows instead.
|
|
||||||
# (wslu, the usual answer, is gone from nixpkgs -- upstream archived it.)
|
|
||||||
(pkgs.writeShellScriptBin "xdg-open" ''
|
|
||||||
url="$1"
|
|
||||||
if command -v powershell.exe >/dev/null 2>&1; then
|
|
||||||
exec powershell.exe -NoProfile -Command "Start-Process '$url'"
|
|
||||||
fi
|
|
||||||
exec explorer.exe "$url"
|
|
||||||
'')
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Honoured by tools that read $BROWSER rather than calling xdg-open.
|
|
||||||
home.sessionVariables.BROWSER = "xdg-open";
|
|
||||||
services.ssh-agent.enable = true;
|
services.ssh-agent.enable = true;
|
||||||
|
|
||||||
# Colourised kubectl. enableAlias points `kubectl` at kubecolor, which parses
|
# Colourised kubectl. enableAlias points `kubectl` at kubecolor, which parses
|
||||||
|
|||||||
Reference in New Issue
Block a user