Compare commits
8 Commits
5f4fd8d74e
...
1e49af53e7
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e49af53e7 | |||
| efa9aa93da | |||
| 277dfa4251 | |||
| 3470751c3e | |||
| b56641aaee | |||
| 108f7b9528 | |||
| 1cb8371775 | |||
| 2fc39a5f15 |
@@ -8,11 +8,12 @@ single flake.
|
|||||||
Defined in the host table in [`flake.nix`](./flake.nix):
|
Defined in the host table in [`flake.nix`](./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](./system/machine/T400/README.md) |
|
| `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](./system/machine/T400/README.md) |
|
||||||
| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](./system/machine/MacPro31/README.md) |
|
| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](./system/machine/MacPro31/README.md) |
|
||||||
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) |
|
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) |
|
||||||
|
| `lyrathorpe-rpi5` | `aarch64-linux` | Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — [install notes](./system/machine/RPi5/README.md) |
|
||||||
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) |
|
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) |
|
||||||
|
|
||||||
Shared layers: `lyrathorpe/home` (home-manager: shell, git, editor),
|
Shared layers: `lyrathorpe/home` (home-manager: shell, git, editor),
|
||||||
@@ -41,11 +42,13 @@ darwin-rebuild switch --flake .#lyrathorpe-mac
|
|||||||
## Login / greeter
|
## Login / greeter
|
||||||
|
|
||||||
Graphical (Sway) hosts log in through a Wayland greeter — `greetd` running
|
Graphical (Sway) hosts log in through a Wayland greeter — `greetd` running
|
||||||
ReGreet inside the `cage` kiosk compositor — configured centrally in
|
ReGreet inside the `cage` kiosk compositor — implemented in
|
||||||
[`lyrathorpe/swaywm.nix`](./lyrathorpe/swaywm.nix), gated on
|
[`lyrathorpe/swaywm.nix`](./lyrathorpe/swaywm.nix), gated on
|
||||||
`features.swayDesktop.enable`. The greeter is forced to Dvorak to match the
|
`features.swayDesktop.enable` (the option is declared in
|
||||||
console and Sway session. Hosts with `features.swayDesktop.enable = false` (the
|
[`system/modules/features.nix`](./system/modules/features.nix), so headless hosts
|
||||||
WSL work box) keep plain TTY login. The target account needs a password
|
can leave it off without importing `swaywm.nix`). The greeter is forced to Dvorak
|
||||||
|
to match the console and Sway session. Headless hosts (the WSL work box and the
|
||||||
|
Raspberry Pi server) keep plain TTY login. The target account needs a password
|
||||||
(`passwd <user>`) before it can log in.
|
(`passwd <user>`) before it can log in.
|
||||||
|
|
||||||
## MacBook (Asahi) firmware
|
## MacBook (Asahi) firmware
|
||||||
|
|||||||
@@ -114,6 +114,7 @@
|
|||||||
baseModules = [
|
baseModules = [
|
||||||
./lyrathorpe/user.nix
|
./lyrathorpe/user.nix
|
||||||
./system/modules/common-nixos.nix
|
./system/modules/common-nixos.nix
|
||||||
|
./system/modules/features.nix
|
||||||
commonModule
|
commonModule
|
||||||
home-manager.nixosModules.home-manager
|
home-manager.nixosModules.home-manager
|
||||||
{
|
{
|
||||||
@@ -287,6 +288,22 @@
|
|||||||
./lyrathorpe/home/work.nix
|
./lyrathorpe/home/work.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
lyrathorpe-rpi5 = {
|
||||||
|
system = "aarch64-linux";
|
||||||
|
username = "lyrathorpe";
|
||||||
|
fullName = "Lyra Thorpe";
|
||||||
|
portable = false;
|
||||||
|
# Headless server: Docker host + nginx reverse proxy. No swaywm.nix
|
||||||
|
# (no desktop); the raspberry-pi-5 profile supplies kernel/firmware,
|
||||||
|
# ssh.nix adds key-only sshd.
|
||||||
|
modules = [
|
||||||
|
./system/machine/RPi5/configuration.nix
|
||||||
|
inputs.nixos-hardware.nixosModules.raspberry-pi-5
|
||||||
|
./system/modules/ssh.nix
|
||||||
|
];
|
||||||
|
homeModules = [ ./lyrathorpe/home ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Darwin host table — macOS machines built via mkDarwinHost. The shared
|
# Darwin host table — macOS machines built via mkDarwinHost. The shared
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ let
|
|||||||
ctp = import ./catppuccin-mocha.nix;
|
ctp = import ./catppuccin-mocha.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
# The features.swayDesktop.enable option is declared in
|
||||||
features.swayDesktop.enable = lib.mkEnableOption "Enable Sway Desktop";
|
# system/modules/features.nix (so headless hosts can read/set it without
|
||||||
};
|
# importing this module). This module only provides its implementation.
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
programs.sway = {
|
programs.sway = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -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. swaywm.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,12 @@
|
|||||||
|
# Feature-flag option declarations shared by every NixOS host (imported via
|
||||||
|
# baseModules in flake.nix). Declaring the flags here -- rather than inside the
|
||||||
|
# module that implements them -- means a host can read or set a flag without
|
||||||
|
# importing the (often large) implementation module. In particular,
|
||||||
|
# features.swayDesktop.enable is read by lyrathorpe/user.nix on every host, but a
|
||||||
|
# headless host (e.g. the Pi) must be able to leave it at its default without
|
||||||
|
# pulling in lyrathorpe/swaywm.nix. The implementation lives in swaywm.nix,
|
||||||
|
# gated on this flag.
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
options.features.swayDesktop.enable = lib.mkEnableOption "the Sway desktop";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user