refactor(flake): user registry, multi-user hosts, and portable home outputs (#49)
CI / flake (push) Successful in 3m26s
CI / flake (push) Successful in 3m26s
## Summary Separates user identity (data) from the reusable Nix modules and lets a host declare any number of users, replacing the previous one-user-per-host structure. Also restructures the tree and exposes the home config for use off these hosts. ## Changes - **User registry** (`users/registry.nix`): per-user identity (name, email, groups, authorized + signing keys) as the single source of truth; no user data hardcoded in modules. - **Multi-user `mkHost`**: a host declares a `users` set keyed by username; per-user identity is injected into each home config via the `identity` module arg. - **Restructured layout**: `users/`, `home/`, `modules/`, `hosts/`, `lib/` replace the former `lyrathorpe/` and `system/` trees. - **Portable outputs**: standalone `homeConfigurations."<user>@<system>"` (the portable subset — shell, git, editor, claude) plus an exported `homeModules` for use on machines not managed by this flake, or as an input to other flakes. - Docs (`README.md`, `home/README.md`) and `.gitignore` updated for the new paths. ## Fixes - Closes #46 — shared user module authorized one user's SSH key for every account. - Closes #47 — git committer identity hardcoded as defaults instead of per-user. - Closes #48 — EDaaS systemd linger hardcoded to a literal username. ## Verification - `nix flake check` passes: treefmt, deadnix, statix, pre-commit, and evaluation of all NixOS hosts + Darwin + homeConfigurations. - Derivation-path comparison vs `main`: `lyrathorpe-mbp` and `emmathorpe-edaas` are byte-identical; `lyrathorpe-t400`, `lyrathorpe-macpro31` and `lyrathorpe-rpi5` differ only by de-duplicating a repeated `authorized_keys` entry (confirmed with nix-diff — no other change). - Standalone `homeConfigurations."lyrathorpe@x86_64-linux".activationPackage` builds. ## Notes - `emmathorpe` has no personal authorized key yet (it previously inherited Lyra's key via the bug in #46); the registry entry is intentionally empty — add a real key if SSH login as `emmathorpe` is wanted (moot on the WSL host). - A two-repo (public dotfiles / private systems) split is deferred by design; this internal restructure is the prerequisite for it. --------- Co-authored-by: Emma Thorpe <emma.thorpe@citrix.com> Reviewed-on: #49
This commit was merged in pull request #49.
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# Default nix-darwin host. Minimal macOS baseline; the user environment
|
||||
# (shell, git, editor) is carried by the shared ./home modules,
|
||||
# the same ones used by the Linux hosts. nixpkgs.hostPlatform is set by
|
||||
# mkDarwinHost in flake.nix.
|
||||
{ pkgs, username, ... }:
|
||||
{
|
||||
programs.zsh.enable = true;
|
||||
|
||||
# Install the Nerd Font into /Library/Fonts so iTerm2 can use it (set it in
|
||||
# iTerm2 -> Settings -> Profiles -> Text -> Font: "JetBrainsMono Nerd Font").
|
||||
# Provides the powerline/Nerd glyphs the tmux statusline draws.
|
||||
fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ];
|
||||
|
||||
# CLI tooling sourced from nixpkgs instead of Homebrew formulae. Pure library
|
||||
# dependencies are omitted; nix pulls them into closures automatically.
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Build & toolchain
|
||||
autoconf
|
||||
automake
|
||||
cmake
|
||||
coreutils
|
||||
gcc
|
||||
gettext
|
||||
gnumake
|
||||
pkgconf
|
||||
ruby
|
||||
zig
|
||||
# Version control & dev workflow
|
||||
pre-commit
|
||||
deno
|
||||
opentofu
|
||||
# Compression
|
||||
lz4
|
||||
p7zip
|
||||
xz
|
||||
zstd
|
||||
# Crypto & networking
|
||||
gnupg
|
||||
gnutls
|
||||
openssl
|
||||
pinentry_mac
|
||||
unbound
|
||||
wget
|
||||
# Media
|
||||
ffmpeg
|
||||
svt-av1
|
||||
yt-dlp
|
||||
# Graphics / Vulkan / SDL
|
||||
glslang
|
||||
moltenvk
|
||||
spirv-tools
|
||||
vulkan-loader
|
||||
vulkan-tools
|
||||
SDL2
|
||||
sdl3
|
||||
# Embedded
|
||||
esptool
|
||||
picotool
|
||||
# Misc utilities
|
||||
f3
|
||||
gnused
|
||||
lua5_4
|
||||
magic-wormhole
|
||||
ncurses
|
||||
mas
|
||||
sqlite
|
||||
];
|
||||
|
||||
# Account that runs user-level activation and Homebrew.
|
||||
system.primaryUser = username;
|
||||
|
||||
# nix-homebrew owns and installs the Homebrew prefix declaratively, so brew
|
||||
# itself no longer needs a manual bootstrap. enableRosetta permits x86_64
|
||||
# formulae via Rosetta 2 on Apple Silicon.
|
||||
nix-homebrew = {
|
||||
autoMigrate = true;
|
||||
enable = true;
|
||||
enableRosetta = true;
|
||||
user = username;
|
||||
};
|
||||
|
||||
# Declarative Homebrew for packages with no nixpkgs equivalent or that must be
|
||||
# the vendor build (GUI casks).
|
||||
homebrew = {
|
||||
enable = true;
|
||||
onActivation = {
|
||||
autoUpdate = true;
|
||||
upgrade = true;
|
||||
# Lists below are authoritative: anything not declared is uninstalled.
|
||||
cleanup = "zap";
|
||||
};
|
||||
taps = [ ];
|
||||
# Formulae kept on brew: vendor PWA host and version-pinned toolchains that
|
||||
# are simpler to track via brew than to match exactly in nixpkgs.
|
||||
brews = [
|
||||
"firefoxpwa"
|
||||
"llvm@21"
|
||||
"lld@21"
|
||||
"python@3.14"
|
||||
"dosbox-staging"
|
||||
];
|
||||
# GUI applications. macOS app bundles are managed as casks; nixpkgs darwin
|
||||
# GUI support is unreliable, so these stay on brew for continuity.
|
||||
casks = [
|
||||
"alfred"
|
||||
"android-platform-tools"
|
||||
"angry-ip-scanner"
|
||||
"arduino-ide"
|
||||
"autodesk-fusion"
|
||||
"bambu-studio"
|
||||
"bitwarden"
|
||||
"citrix-workspace"
|
||||
"curseforge"
|
||||
"discord"
|
||||
"firefox"
|
||||
"freecad"
|
||||
"gcc-arm-embedded"
|
||||
"google-chrome"
|
||||
"istat-menus"
|
||||
"iterm2"
|
||||
"macfuse"
|
||||
"microsoft-teams"
|
||||
"nextcloud"
|
||||
"obs"
|
||||
"omnidisksweeper"
|
||||
"openscad@snapshot"
|
||||
"orcaslicer"
|
||||
"plex"
|
||||
"plexamp"
|
||||
"postman"
|
||||
"signal"
|
||||
"steam"
|
||||
"thunderbird"
|
||||
"virtualbox"
|
||||
"visual-studio-code"
|
||||
"vnc-viewer"
|
||||
"vscodium"
|
||||
"winbox"
|
||||
];
|
||||
# Mac App Store apps are not managed declaratively: nix-darwin 26.05 forces
|
||||
# activation to run as root, and `mas` cannot reach the App Store session
|
||||
# from root, so installs silently fail. Install them by hand with
|
||||
# `mas install <id>` from a GUI Terminal (the `mas` CLI is in
|
||||
# environment.systemPackages above).
|
||||
};
|
||||
|
||||
# Touch ID authorises sudo (and darwin-rebuild's sudo prompt) instead of a
|
||||
# typed password. sudo_local keeps the change in /etc/pam.d/sudo_local so it
|
||||
# survives macOS updates. reattach pulls in pam_reattach: pam_tid (Touch ID)
|
||||
# otherwise fails inside tmux/screen because the process is detached from the
|
||||
# GUI login session -- and terminals here auto-start tmux, so it is required.
|
||||
security.pam.services.sudo_local = {
|
||||
touchIdAuth = true;
|
||||
reattach = true;
|
||||
};
|
||||
|
||||
# Declarative macOS UI defaults -- the main reason to run nix-darwin beyond
|
||||
# package management. Applied on activation; all reversible.
|
||||
system.defaults = {
|
||||
dock = {
|
||||
show-recents = false;
|
||||
mru-spaces = false; # don't reorder spaces by use
|
||||
};
|
||||
finder = {
|
||||
AppleShowAllExtensions = true;
|
||||
ShowPathbar = true;
|
||||
FXPreferredViewStyle = "Nlsv"; # list view
|
||||
_FXShowPosixPathInTitle = true;
|
||||
};
|
||||
NSGlobalDomain = {
|
||||
AppleInterfaceStyle = "Dark";
|
||||
ApplePressAndHoldEnabled = false; # key-repeat instead of the accent popup
|
||||
InitialKeyRepeat = 15;
|
||||
KeyRepeat = 2;
|
||||
};
|
||||
trackpad = {
|
||||
Clicking = true; # tap to click
|
||||
TrackpadThreeFingerDrag = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Used for backwards compatibility; read `darwin-rebuild changelog` before changing.
|
||||
system.stateVersion = 5;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
# Edit this configuration file to define what should be installed on
|
||||
# your system. Help is available in the configuration.nix(5) man page, on
|
||||
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
|
||||
|
||||
# NixOS-WSL specific options are documented on the NixOS-WSL repository:
|
||||
# https://github.com/nix-community/NixOS-WSL
|
||||
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
|
||||
wsl = {
|
||||
enable = true;
|
||||
defaultUser = "emmathorpe";
|
||||
wslConf.automount.root = "/mnt";
|
||||
wslConf.interop.appendWindowsPath = true;
|
||||
wslConf.interop.enabled = true;
|
||||
wslConf.network.generateHosts = false;
|
||||
startMenuLaunchers = true;
|
||||
docker-desktop.enable = false;
|
||||
extraBin = with pkgs; [
|
||||
# Binaries for Docker Desktop wsl-distro-proxy
|
||||
{ src = "${coreutils}/bin/mkdir"; }
|
||||
{ src = "${coreutils}/bin/cat"; }
|
||||
{ src = "${coreutils}/bin/whoami"; }
|
||||
{ src = "${coreutils}/bin/ls"; }
|
||||
{ src = "${busybox}/bin/addgroup"; }
|
||||
{ src = "${su}/bin/groupadd"; }
|
||||
{ src = "${su}/bin/usermod"; }
|
||||
];
|
||||
};
|
||||
|
||||
virtualisation.docker = {
|
||||
enable = true;
|
||||
enableOnBoot = true;
|
||||
autoPrune.enable = true;
|
||||
};
|
||||
|
||||
# Match the flake's nixosConfigurations attribute name so `nh os switch`
|
||||
# (which selects by the local hostname) resolves without an explicit
|
||||
# -H/--hostname flag. The default would otherwise be the stock NixOS "nixos".
|
||||
networking.hostName = "emmathorpe-edaas";
|
||||
|
||||
networking.resolvconf.enable = false;
|
||||
|
||||
# Drop the systemd-ssh-proxy Include from the generated /etc/ssh/ssh_config.
|
||||
# The NixOS-WSL store is a read-only VHD whose files are owned by nobody
|
||||
# (65534), not root. OpenSSH permission-checks Include'd config files and
|
||||
# rejects any not owned by root or the caller, so the default include fails
|
||||
# with "Bad owner or permissions" and breaks ssh/git for every command. The
|
||||
# proxy plugin only matters for `ssh unix/…` / `vsock` to local machined VMs,
|
||||
# which WSL does not use.
|
||||
programs.ssh.systemd-ssh-proxy.enable = false;
|
||||
|
||||
## 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"'';
|
||||
|
||||
features.swayDesktop.enable = false;
|
||||
|
||||
# NOTE: this user's systemd --user lingering -- so the home-manager renovate
|
||||
# timer fires without an open login session -- is enabled from the host table
|
||||
# in flake.nix (users.emmathorpe.linger = true) and applied by
|
||||
# modules/users.nix.
|
||||
|
||||
# programs.nix-ld is enabled for all NixOS hosts in common-nixos.nix.
|
||||
# This value determines the NixOS release from which the default
|
||||
# settings for stateful data, like file locations and database versions
|
||||
# on your system were taken. It's perfectly fine and recommended to leave
|
||||
# this value at the release version of the first install of this system.
|
||||
# Before changing this value read the documentation for this option
|
||||
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
|
||||
system.stateVersion = "24.11"; # Did you read the comment?
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
# MacBook Pro (Apple Silicon, Asahi NixOS). Shared laptop options live in
|
||||
# ../../modules/laptop.nix; only host-specific settings are here.
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
# UEFI boot via systemd-boot. Asahi manages the EFI vars from macOS, so do not
|
||||
# touch them from NixOS.
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
|
||||
networking.hostName = "Lyra-Asahi";
|
||||
|
||||
# Audio (PipeWire) and the swaylock PAM stack are inherited from
|
||||
# workstation.nix. hardware.enableRedistributableFirmware is also set there;
|
||||
# it is harmless here since Asahi supplies its own peripheral firmware below.
|
||||
|
||||
# Binary cache for the Asahi kernel/build artifacts, so the MBP pulls prebuilt
|
||||
# outputs instead of compiling the Asahi kernel locally.
|
||||
nix.settings = {
|
||||
substituters = [ "https://nixos-apple-silicon.cachix.org" ];
|
||||
trusted-public-keys = [
|
||||
"nixos-apple-silicon.cachix.org-1:8psDu5SA5dAD7qA0zMy5UT292TxeEPzIz8VVEr2Js20="
|
||||
];
|
||||
};
|
||||
|
||||
# Apple peripheral firmware (Wi-Fi/Bluetooth). The directory is gitignored and
|
||||
# populated out-of-band -- see README.
|
||||
hardware.asahi.peripheralFirmwareDirectory = ../../modules/firmware;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
asahi-bless
|
||||
asahi-nvram
|
||||
asahi-btsync
|
||||
asahi-wifisync
|
||||
unzip
|
||||
ppp
|
||||
iptables
|
||||
];
|
||||
|
||||
# See `man configuration.nix` / the stateVersion docs before changing.
|
||||
system.stateVersion = "25.05";
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "uas" "sdhci_pci" ];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/disk/by-uuid/217a7427-d799-4e0d-af4e-378db3b27467";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
boot.initrd.luks.devices."cryptroot".device = "/dev/disk/by-uuid/4ec1a31b-a7ba-41bb-9bbc-4833cfc732b0";
|
||||
|
||||
fileSystems."/boot" =
|
||||
{ device = "/dev/disk/by-uuid/420E-1902";
|
||||
fsType = "vfat";
|
||||
options = [ "fmask=0022" "dmask=0022" ];
|
||||
};
|
||||
|
||||
swapDevices = [ {device = "/swapfile"; size = 8192;} ];
|
||||
|
||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
||||
# still possible to use this option, but it's recommended to use it in conjunction
|
||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.wlan0.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
# Mac Pro 3,1 (Early 2008) — install notes
|
||||
|
||||
Flake host: `lyrathorpe-macpro31`. Desktop (`portable = false`, imports
|
||||
`../../modules/desktop.nix`). Files: `configuration.nix`,
|
||||
`hardware-configuration.nix`.
|
||||
|
||||
## Hardware configuration
|
||||
|
||||
`hardware-configuration.nix` here is the real config generated by
|
||||
`nixos-generate-config` on the machine. Root is an **LVM** logical volume
|
||||
(`/dev/mapper/MacPro-Root`, ext4); the ESP (vfat) and swap are referenced by
|
||||
UUID. The initrd carries `dm-snapshot` for the LVM root. Regenerate and commit
|
||||
if the disk layout changes.
|
||||
|
||||
## Bootloader
|
||||
|
||||
The Mac Pro 3,1 has **64-bit EFI**, so it uses **systemd-boot** (no GRUB/CSM
|
||||
shim). `canTouchEfiVariables = false` because Apple's firmware does not reliably
|
||||
accept `efibootmgr` NVRAM writes.
|
||||
|
||||
Apple-EFI quirk: if the firmware boot picker does not show NixOS after install,
|
||||
either
|
||||
|
||||
- uncomment `boot.loader.efi.efiInstallAsRemovable = true;` in
|
||||
`configuration.nix` (installs the fallback `\EFI\BOOT\BOOTX64.EFI`), and/or
|
||||
- "bless" the ESP from macOS.
|
||||
|
||||
Partition the disk GPT with an ESP (vfat).
|
||||
|
||||
## Graphics
|
||||
|
||||
The stock card varies between units — **ATI Radeon HD 2600 XT** or **NVIDIA
|
||||
GeForce 8800 GT**. No proprietary driver is hardcoded; Sway relies on in-tree KMS:
|
||||
|
||||
- ATI Radeon HD 2600 XT → `radeon` (or `amdgpu`) KMS
|
||||
- NVIDIA GeForce 8800 GT → `nouveau` KMS
|
||||
|
||||
These come up automatically. If a card needs forcing, set
|
||||
`services.xserver.videoDrivers` and/or add the module to
|
||||
`boot.initrd.kernelModules` for early KMS (see the comment in
|
||||
`configuration.nix`).
|
||||
|
||||
## Networking
|
||||
|
||||
Wired Ethernet via NetworkManager (from `desktop.nix`) — the Mac Pro has two
|
||||
gigabit ports.
|
||||
|
||||
## Login
|
||||
|
||||
Graphical login via a Wayland greeter — `greetd` running ReGreet inside the
|
||||
`cage` kiosk compositor — configured centrally in `lyrathorpe/swaywm.nix` for
|
||||
every Sway host (gated on `features.swayDesktop.enable`). The greeter is forced
|
||||
to the Dvorak layout to match the console and Sway session. Set the user
|
||||
password (`passwd lyrathorpe`) after install, or the greeter cannot
|
||||
authenticate. Requires working KMS (radeon/nouveau — see Graphics).
|
||||
|
||||
## Apply
|
||||
|
||||
```sh
|
||||
sudo nixos-rebuild switch --flake .#lyrathorpe-macpro31
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
# Apple Mac Pro 3,1 (Early 2008, dual Xeon Harpertown, x86_64). Desktop host:
|
||||
# shared graphical/wired options live in ../../modules/desktop.nix; only
|
||||
# host-specific settings are here. Install notes (EFI booting, GPU, partitions):
|
||||
# see ./README.md.
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
# The Mac Pro 3,1 has 64-bit EFI (confirmed by the owner), so boot via
|
||||
# systemd-boot like the MBP -- no GRUB/BIOS shim needed.
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
# Apple's EFI does not reliably support efibootmgr NVRAM writes; leave the
|
||||
# firmware vars untouched.
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
# Apple-EFI quirk: if the Mac does not pick up the bootloader at the boot
|
||||
# picker, install it to the fallback path \EFI\BOOT\BOOTX64.EFI and/or
|
||||
# "bless" the ESP from macOS. Uncomment to write the removable fallback path:
|
||||
# boot.loader.efi.efiInstallAsRemovable = true;
|
||||
|
||||
networking.hostName = "MacPro31-NixOS";
|
||||
|
||||
# Elderly host: a compressed RAM swap softens memory pressure (earlyoom in
|
||||
# workstation.nix is the backstop).
|
||||
zramSwap.enable = true;
|
||||
|
||||
# This host accepts SSH, so open 22 (the firewall itself is enabled in
|
||||
# workstation.nix with a default-deny policy).
|
||||
services.openssh.enable = true;
|
||||
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||
|
||||
# Dual Harpertown Xeon microcode. Redistributable firmware (GPU/NIC blobs) is
|
||||
# enabled in workstation.nix.
|
||||
hardware.cpu.intel.updateMicrocode = true;
|
||||
|
||||
# GPU note: the stock card varies between units -- ATI Radeon HD 2600 XT or
|
||||
# NVIDIA GeForce 8800 GT. Sway needs a working KMS/modesetting driver; do NOT
|
||||
# install a proprietary blob here. Depending on the installed card, rely on
|
||||
# the open kernel driver:
|
||||
# - ATI Radeon HD 2600 XT -> "radeon" (older) or "amdgpu" KMS
|
||||
# - NVIDIA GeForce 8800 GT -> "nouveau" KMS
|
||||
# These come up automatically via the in-tree drivers + KMS, and the graphics
|
||||
# stack itself is enabled by modules/sway.nix. If a card needs to be forced, add it
|
||||
# here, e.g. `services.xserver.videoDrivers = [ "radeon" ];` (or "nouveau"),
|
||||
# and/or `boot.initrd.kernelModules = [ "radeon" ];` in
|
||||
# hardware-configuration.nix for early KMS.
|
||||
|
||||
# See `man configuration.nix` / the stateVersion docs before changing.
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ata_piix" "ahci" "firewire_ohci" "usb_storage" "usbhid" "sd_mod" ];
|
||||
boot.initrd.kernelModules = [ "dm-snapshot" ];
|
||||
boot.kernelModules = [ "kvm-intel" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/mapper/MacPro-Root";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" =
|
||||
{ device = "/dev/disk/by-uuid/0E9C-C099";
|
||||
fsType = "vfat";
|
||||
options = [ "fmask=0022" "dmask=0022" ];
|
||||
};
|
||||
|
||||
swapDevices =
|
||||
[ { device = "/dev/disk/by-uuid/dec138b4-320f-4b69-acbc-3014a1032cbd"; }
|
||||
];
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
# Raspberry Pi 5 (`lyrathorpe-rpi5`)
|
||||
|
||||
Headless `aarch64-linux` server with two roles:
|
||||
|
||||
- **Docker host** — daemon exposed over the network (`docker.nix`).
|
||||
- **nginx reverse proxy** — declarative `virtualHosts` (`reverse-proxy.nix`).
|
||||
|
||||
## Install
|
||||
|
||||
1. Flash a NixOS `aarch64` SD image (or USB) and boot the Pi. The
|
||||
`raspberry-pi-5` profile from `nixos-hardware` (wired in the flake host table)
|
||||
supplies the kernel, firmware and device tree; boot is U-Boot + extlinux.
|
||||
2. Partition/mount the target, then **regenerate the hardware config on the
|
||||
device** and replace the committed placeholder:
|
||||
```sh
|
||||
nixos-generate-config --root /mnt
|
||||
# copy /mnt/etc/nixos/hardware-configuration.nix over
|
||||
# system/machine/RPi5/hardware-configuration.nix in this repo, then commit
|
||||
```
|
||||
`hardware-configuration.nix` in this directory is a **placeholder** committed
|
||||
only so the host evaluates in CI. The machine will not boot correctly until it
|
||||
is replaced with the generated one.
|
||||
3. Set the host name to match the flake attribute (already done in
|
||||
`configuration.nix`: `lyrathorpe-rpi5`) and build:
|
||||
```sh
|
||||
sudo nixos-rebuild switch --flake .#lyrathorpe-rpi5
|
||||
# or, once the hostname is live:
|
||||
nh os switch
|
||||
```
|
||||
4. Give the login user a password (`passwd lyrathorpe`) and confirm the key in
|
||||
`system/modules/ssh.nix` is the one you will connect with.
|
||||
|
||||
## Docker socket (security)
|
||||
|
||||
The daemon listens on **plain TCP `2375`, no TLS, no auth**. Access is
|
||||
root-equivalent on this host. The only protection is the nftables rule in
|
||||
`docker.nix`, which accepts `2375` **only** from the trusted LAN subnet
|
||||
(`10.187.1.0/24` by default — change it to match your network). Do not widen
|
||||
that subnet to anything untrusted.
|
||||
|
||||
From a LAN client:
|
||||
|
||||
```sh
|
||||
export DOCKER_HOST=tcp://lyrathorpe-rpi5:2375
|
||||
docker info
|
||||
```
|
||||
|
||||
The secure upgrade path is mutual TLS on `2376` (`--tlsverify` with a CA and
|
||||
client certs); it needs out-of-band cert provisioning and is intentionally not
|
||||
wired here.
|
||||
|
||||
## Adding a reverse-proxy site
|
||||
|
||||
Each proxied service is a Nix entry in `reverse-proxy.nix`:
|
||||
|
||||
```nix
|
||||
services.nginx.virtualHosts."app.example.lan" = {
|
||||
# enableACME = true; forceSSL = true; # once a DNS name + cert exist
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8080"; # e.g. a local container
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The example vhost is HTTP-only by design. Turn on `enableACME`/`forceSSL`
|
||||
per-vhost once the host has a real DNS name and the ACME challenge can be met;
|
||||
`443` is already open in the firewall.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Raspberry Pi 5 (aarch64) headless server. Two roles, split into submodules:
|
||||
# ./docker.nix (Docker host with a network socket) and ./reverse-proxy.nix
|
||||
# (native nginx). The raspberry-pi-5 nixos-hardware profile (kernel, firmware,
|
||||
# device tree) and key-only sshd (../../modules/ssh.nix) are layered on in the
|
||||
# flake host table. Install notes: see ./README.md.
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./docker.nix
|
||||
./reverse-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-rpi5";
|
||||
|
||||
# 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
|
||||
# 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-5 nixos-hardware profile supplies the kernel, firmware and
|
||||
# device tree.
|
||||
boot.loader.grub.enable = false;
|
||||
boot.loader.generic-extlinux-compatible.enable = true;
|
||||
|
||||
# Remote administration. Key-only policy and the authorized key come from
|
||||
# ../../modules/ssh.nix; here we just enable the daemon and open the port.
|
||||
services.openssh.enable = true;
|
||||
|
||||
# Default-deny inbound. Open only SSH here; the Docker and nginx submodules
|
||||
# open their own ports (Docker via a source-restricted nftables rule, nginx
|
||||
# via 80/443). List-valued, so these merge with the submodule definitions.
|
||||
networking.firewall.enable = true;
|
||||
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||
|
||||
# See `man configuration.nix` / the stateVersion docs before changing.
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# Docker host with the daemon socket exposed over the network.
|
||||
#
|
||||
# SECURITY: the daemon listens on plain TCP 2375 with NO TLS and NO auth. Access
|
||||
# to that port is root-equivalent on this host (the Docker API can mount the
|
||||
# host filesystem and run privileged containers). The ONLY thing protecting it
|
||||
# is the nftables rule below, which accepts 2375 solely from the trusted LAN
|
||||
# subnet. Do not widen that subnet to anything you do not fully trust. The
|
||||
# secure upgrade path is mutual TLS on 2376 (--tlsverify with client certs);
|
||||
# that needs out-of-band cert provisioning and is intentionally not wired here.
|
||||
{ ... }:
|
||||
{
|
||||
virtualisation.docker.enable = true;
|
||||
|
||||
# Expose the daemon over TCP by extending systemd socket activation rather than
|
||||
# setting daemon.settings.hosts. The NixOS docker unit starts dockerd with
|
||||
# `-H fd://` and takes its listeners from this socket; putting `hosts` in
|
||||
# daemon.json as well would conflict with that and dockerd would refuse to
|
||||
# start. Adding the TCP listener here keeps a single source of truth.
|
||||
# The leading "" resets the unit's default (unix-socket-only) ListenStream list.
|
||||
systemd.sockets.docker.socketConfig.ListenStream = [
|
||||
""
|
||||
"/run/docker.sock"
|
||||
"0.0.0.0:2375"
|
||||
];
|
||||
|
||||
# Source-restricted firewall rule for the Docker TCP port. 2375 is deliberately
|
||||
# NOT added to networking.firewall.allowedTCPPorts (that would open it to every
|
||||
# source); instead nftables accepts it only from the trusted subnet. Adjust the
|
||||
# CIDR to match the LAN that should reach the Docker API.
|
||||
networking.nftables.enable = true;
|
||||
networking.firewall.extraInputRules = ''
|
||||
ip saddr 10.187.1.0/24 tcp dport 2375 accept
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# PLACEHOLDER hardware configuration for the Raspberry Pi 5.
|
||||
#
|
||||
# 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 ./README.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 Raspberry Pi 5 boots from an SD card / USB 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";
|
||||
};
|
||||
|
||||
swapDevices = [ ];
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Native nginx reverse proxy. The proxy configuration is declarative Nix:
|
||||
# every proxied service is an entry under services.nginx.virtualHosts, so the
|
||||
# whole routing table lives in this file and is built/version-controlled with
|
||||
# the rest of the system.
|
||||
#
|
||||
# To add a proxied service, add another virtualHosts."<host>" entry following
|
||||
# the example below. To serve it over HTTPS, uncomment enableACME + forceSSL on
|
||||
# that vhost once it has a real DNS name and the ACME HTTP-01/DNS-01 challenge
|
||||
# can be satisfied (see security.acme for the account/email and DNS settings).
|
||||
{ ... }:
|
||||
{
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedProxySettings = true; # sane proxy_set_header defaults (Host, X-Forwarded-*)
|
||||
recommendedTlsSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedGzipSettings = true;
|
||||
|
||||
virtualHosts = {
|
||||
# Example reverse-proxy vhost. Replace the name and upstream with a real
|
||||
# service (e.g. a container published by the Docker host on this machine).
|
||||
"example.lan" = {
|
||||
# enableACME = true; # request a Let's Encrypt cert for this host
|
||||
# forceSSL = true; # redirect HTTP -> HTTPS once the cert exists
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8080";
|
||||
proxyWebsockets = true; # forward Upgrade/Connection for WebSocket apps
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Public reverse-proxy ports. 443 is opened now so flipping a vhost to TLS
|
||||
# needs no firewall change.
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# ThinkPad T400 — install notes
|
||||
|
||||
Flake host: `lyrathorpe-t400`. Files: `configuration.nix`, the `boot-*.nix`
|
||||
variants, and `hardware-configuration.nix`.
|
||||
|
||||
## Hardware configuration
|
||||
|
||||
`hardware-configuration.nix` here is a hand-written **placeholder**. On the real
|
||||
machine, run `nixos-generate-config`, replace the file, and commit it. It assumes
|
||||
by-label partitions — root `nixos` (ext4) and `swap` — so either label them at
|
||||
install time or swap in the generated UUIDs.
|
||||
|
||||
## Bootloader — import the module matching the flashed firmware
|
||||
|
||||
`configuration.nix` imports exactly one boot module. Default is `boot-bios.nix`;
|
||||
switch by commenting it out and uncommenting the relevant alternative.
|
||||
|
||||
| Firmware | Module | Notes |
|
||||
| ---------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Stock Lenovo BIOS, or coreboot + **SeaBIOS** payload | `boot-bios.nix` | GRUB on the MBR. Set `device` to the real install disk (`/dev/sda` by default). MBR/legacy layout. |
|
||||
| coreboot + **GRUB** payload | `boot-coreboot-grub.nix` | GRUB is config-only (`device = "nodev"`); NixOS does **not** write to a disk. Your coreboot `grub.cfg` (in the flash chip) must `search` for and `configfile` the on-disk `/boot/grub/grub.cfg`, or chainload the disk's GRUB. |
|
||||
| coreboot + **Tianocore/edk2 (UEFI)** payload | `boot-coreboot-uefi.nix` | systemd-boot. `canTouchEfiVariables = true` (coreboot honours NVRAM writes). The module **declares its own ESP** (`/boot` vfat, label `ESP`) — when you regenerate `hardware-configuration.nix`, do **not** let it also define `/boot`. Create + label an `ESP` vfat partition (GPT). |
|
||||
|
||||
## Graphics
|
||||
|
||||
This unit has the optional **discrete ATI Mobility Radeon HD 3470 (RV620)**. The
|
||||
open `radeon` KMS driver is loaded in the initrd for early modesetting; firmware
|
||||
comes from `enableRedistributableFirmware`.
|
||||
|
||||
The T400 has switchable graphics (discrete ATI + Intel GMA 4500MHD). Select
|
||||
**Discrete** in the firmware's graphics setting so only the ATI is live. If you
|
||||
run **Integrated** instead, the Intel `i915` driver takes over with no config
|
||||
change and `radeon` stays idle.
|
||||
|
||||
## Login
|
||||
|
||||
Graphical login via a Wayland greeter — `greetd` running ReGreet inside the
|
||||
`cage` kiosk compositor — configured centrally in `lyrathorpe/swaywm.nix` for
|
||||
every Sway host (gated on `features.swayDesktop.enable`). The greeter is forced
|
||||
to the Dvorak layout to match the console and Sway session. Set the user
|
||||
password (`passwd lyrathorpe`) after install, or the greeter cannot
|
||||
authenticate. Requires working radeon/i915 KMS (see Graphics).
|
||||
|
||||
## Apply
|
||||
|
||||
```sh
|
||||
sudo nixos-rebuild switch --flake .#lyrathorpe-t400
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
# Boot via legacy BIOS -- the stock Lenovo BIOS, or coreboot with the SeaBIOS
|
||||
# payload (both present a legacy BIOS interface). GRUB is installed to the MBR of
|
||||
# the boot disk. This is the default.
|
||||
{ ... }:
|
||||
{
|
||||
boot.loader.grub = {
|
||||
enable = true;
|
||||
# Must point at the actual install disk -- adjust if it is not /dev/sda.
|
||||
device = "/dev/sda";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Boot via coreboot's GRUB payload (e.g. libreboot default). The GRUB in the
|
||||
# flash chip reads the grub.cfg that NixOS generates on disk, so GRUB here is
|
||||
# config-only -- it is NOT installed to any disk MBR (`device = "nodev"`).
|
||||
#
|
||||
# Your coreboot grub.cfg must locate and load the on-disk config, e.g. search
|
||||
# for and `configfile` /boot/grub/grub.cfg (or chainload the disk's GRUB).
|
||||
{ ... }:
|
||||
{
|
||||
boot.loader.grub = {
|
||||
enable = true;
|
||||
device = "nodev";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Boot via coreboot's Tianocore/edk2 (UEFI) payload. This turns the T400 into a
|
||||
# real UEFI machine, so use systemd-boot. Unlike Apple's firmware, coreboot's
|
||||
# UEFI honours EFI variable writes, so canTouchEfiVariables is on.
|
||||
#
|
||||
# Requires an EFI System Partition. It is declared here so it travels with this
|
||||
# boot mode; the generated hardware-configuration.nix should NOT also define
|
||||
# /boot. Label the ESP `ESP` at install, or replace with the generated UUID.
|
||||
{ ... }:
|
||||
{
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-label/ESP";
|
||||
fsType = "vfat";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
# ThinkPad T400 (NixOS). Shared laptop options live in ../../modules/laptop.nix;
|
||||
# only host-specific settings are here. Install notes (boot variants, GPU,
|
||||
# partitions): see ./README.md.
|
||||
{ config, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
# Boot: import exactly ONE, matching the firmware currently flashed.
|
||||
# Stock Lenovo BIOS and coreboot+SeaBIOS both use boot-bios.nix.
|
||||
./boot-bios.nix
|
||||
# ./boot-coreboot-grub.nix # coreboot with the GRUB payload (config-only GRUB)
|
||||
# ./boot-coreboot-uefi.nix # coreboot with the Tianocore/edk2 UEFI payload
|
||||
# # (systemd-boot; carries its own ESP mount)
|
||||
];
|
||||
|
||||
networking.hostName = "T400-NixOS";
|
||||
|
||||
console.font = "Lat2-Terminus16";
|
||||
|
||||
# Low-RAM host (4 GiB max): a compressed RAM swap reduces disk paging.
|
||||
zramSwap.enable = true;
|
||||
|
||||
# This host accepts SSH, so open 22 (the firewall itself is enabled in
|
||||
# laptop.nix with a default-deny policy).
|
||||
services.openssh.enable = true;
|
||||
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||
|
||||
# Intel Core 2 (Penryn) microcode. Redistributable firmware (enabled in
|
||||
# workstation.nix) supplies the iwlwifi blobs (Intel WiFi Link 5100/5300) and
|
||||
# the radeon firmware needed by the discrete GPU below.
|
||||
hardware.cpu.intel.updateMicrocode = true;
|
||||
|
||||
# Battery longevity: cap charging to 75-80%. tlp itself comes from the
|
||||
# nixos-hardware lenovo-thinkpad profile; tp_smapi supplies the threshold
|
||||
# sysfs on this 2008-era ThinkPad (kernel-native natacpi is too new for it).
|
||||
boot.kernelModules = [ "tp_smapi" ];
|
||||
boot.extraModulePackages = [ config.boot.kernelPackages.tp_smapi ];
|
||||
services.tlp.settings = {
|
||||
START_CHARGE_THRESH_BAT0 = 75;
|
||||
STOP_CHARGE_THRESH_BAT0 = 80;
|
||||
};
|
||||
|
||||
# This T400 has the optional discrete GPU fitted: an ATI Mobility Radeon HD
|
||||
# 3470 (RV620), driven by the open `radeon` KMS driver. Load it in the initrd
|
||||
# for early modesetting (clean Sway/Wayland start); firmware comes from
|
||||
# enableRedistributableFirmware above.
|
||||
#
|
||||
# The T400 has switchable graphics (this discrete GPU + the Intel GMA
|
||||
# 4500MHD). Select "Discrete" in the firmware's graphics setting so only the
|
||||
# ATI is live; if you instead run "Integrated", the Intel i915 driver takes
|
||||
# over with no extra config and `radeon` simply stays idle.
|
||||
boot.initrd.kernelModules = [ "radeon" ];
|
||||
|
||||
# See `man configuration.nix` / the stateVersion docs before changing.
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# PLACEHOLDER -- hand-written, not machine-generated. Regenerate on the real
|
||||
# T400 with `nixos-generate-config` and commit the result. The device labels
|
||||
# below are guesses; replace them with the generated UUIDs (or label the
|
||||
# partitions accordingly at install time).
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
imports = [
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [
|
||||
"ahci"
|
||||
"ata_piix"
|
||||
"ehci_pci"
|
||||
"uhci_hcd"
|
||||
"usb_storage"
|
||||
"sd_mod"
|
||||
"sr_mod"
|
||||
];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-intel" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
# Label your root partition `nixos` at install, or replace with the generated UUID.
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-label/nixos";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
swapDevices = [
|
||||
{ device = "/dev/disk/by-label/swap"; }
|
||||
];
|
||||
|
||||
networking.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
||||
Reference in New Issue
Block a user