diff --git a/.gitignore b/.gitignore index b56b238..29cdfb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ modules/usb-devices.nix -secrets/ -.sops.yaml +# real secrets never enter the repo — only .example structure files are tracked +secrets/* +!secrets/*.example result \ No newline at end of file diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..9f6bae5 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,8 @@ +keys: + - &asxpi age19xvmm6j3h2u6mc3w6k65ufry4p5aa5lwu96p55h3e42p9wfzwc0qp2w7pk + +creation_rules: + - path_regex: secrets/.* + key_groups: + - age: + - *asxpi diff --git a/README.md b/README.md index d8d5ed3..4497776 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,35 @@ # NixOS +Flake-based NixOS configuration for a ThinkPad P14s Gen 6 (AMD), tracking `nixos-unstable`. + +## Highlights + +- **Secure Boot** via [lanzaboote](https://github.com/nix-community/lanzaboote) +- **Secrets**: [sops-nix](https://github.com/Mic92/sops-nix) with age — xray config, WireGuard, SSH keys, private routes/hosts; encrypted files stay out of the repo (only `.example` structure files are tracked), decrypted to `/run/secrets` at activation +- **Desktop**: GNOME on Wayland, AMD GPU +- **Local LLMs**: Ollama with ROCm (gfx1151) + Lemonade for XDNA2 NPU serving via [nix-amd-ai](https://github.com/noamsto/nix-amd-ai) +- **Networking**: Xray VLESS+Reality with sing-box TUN, WireGuard, WWAN modem +- **Gaming**: Steam with gamescope, declarative Flatpaks via [nix-flatpak](https://github.com/gmodena/nix-flatpak) +- **Litecoin Core (MWEB)**: vendored package build, revived after removal from nixpkgs +- **Power**: AMD P-State tuning, zram swap ahead of disk swap + +## Layout + +``` +flake.nix # inputs and host definition +configuration.nix # entry point, imports modules +modules/ # one module per concern (boot, desktop, ollama, xray, ...) +secrets/ # sops-encrypted (age) secrets, gitignored; .example files document structure +``` + +## Usage + +```sh +sudo nixos-rebuild switch --flake /etc/nixos#meow +``` + +Fresh install: restore `secrets/` and the age key (`/var/lib/sops-nix/key.txt`) from out-of-band backup first. + ## TODO -- [ ] Add secret manager (sops-nix) -- [ ] Check nix Home manager + +- [ ] Evaluate Home Manager diff --git a/configuration.nix b/configuration.nix index b8c445c..635b901 100644 --- a/configuration.nix +++ b/configuration.nix @@ -15,6 +15,7 @@ ./modules/programs.nix ./modules/wireguard.nix ./modules/security.nix + ./modules/sops.nix ./modules/power.nix ./modules/xray.nix ./modules/ollama.nix diff --git a/modules/networking.nix b/modules/networking.nix index 94be662..3a2c312 100644 --- a/modules/networking.nix +++ b/modules/networking.nix @@ -58,9 +58,9 @@ # A previous wwan-autoconnect oneshot service called `mmcli --simple-connect` # with a hardcoded APN and set up routing by hand. It fought NM for the modem # (endless "connecting") and its hardcoded APN broke when the SIM changed. - # Removed: connect via the GNOME network applet using the "narayana" GSM - # profile (apn=data.narayana). Re-add a service only if headless autoconnect - # is needed, and if so set the NM profile's autoconnect=no to avoid contention. + # Removed: connect via the GNOME network applet using the carrier's GSM + # profile instead. Re-add a service only if headless autoconnect is needed, + # and if so set the NM profile's autoconnect=no to avoid contention. # IVPN service — installed but not auto-started at boot. # Start manually: `sudo systemctl start ivpn-service` diff --git a/modules/sops.nix b/modules/sops.nix new file mode 100644 index 0000000..1934a7d --- /dev/null +++ b/modules/sops.nix @@ -0,0 +1,30 @@ +# sops-nix: decrypt secrets at activation using the root-side age key. +# Key installed once via: +# sudo install -d -m 700 /var/lib/sops-nix +# sudo install -m 600 ~/.config/sops/age/keys.txt /var/lib/sops-nix/key.txt +{ config, pkgs, ... }: + +{ + sops.age.keyFile = "/var/lib/sops-nix/key.txt"; + + # Secret files are gitignored (only .example structure files are tracked), + # so they are referenced by absolute path and read at activation, not eval. + sops.validateSopsFiles = false; + + # SSH keys travel as one encrypted tarball so filenames stay inside the + # encrypted payload. (Re)create with: + # tar -C ~/.ssh -cf secrets/ssh-keys.tar && sops -e -i secrets/ssh-keys.tar + sops.secrets."ssh-keys.tar" = { + sopsFile = "/etc/nixos/secrets/ssh-keys.tar"; + format = "binary"; + }; + system.activationScripts.ssh-keys = { + deps = [ "setupSecrets" "users" ]; + text = '' + mkdir -p /home/asxpi/.ssh + ${pkgs.gnutar}/bin/tar --unlink-first -C /home/asxpi/.ssh -xf ${config.sops.secrets."ssh-keys.tar".path} + chown -R asxpi:users /home/asxpi/.ssh + chmod 700 /home/asxpi/.ssh + ''; + }; +} diff --git a/secrets/network.yaml.example b/secrets/network.yaml.example new file mode 100644 index 0000000..5e68d05 --- /dev/null +++ b/secrets/network.yaml.example @@ -0,0 +1,14 @@ +# Encrypt with: sops -e -i secrets/network.yaml +# xray-endpoint: xray server IP (spliced into sing-box route rules at runtime) +# route-exclude: JSON array of CIDRs excluded from the TUN (sing-box quote=false splice) +# ssh-hosts: ssh_config fragment, pulled in via Include /run/secrets/ssh-hosts +xray-endpoint: 203.0.113.1 +route-exclude: '["203.0.113.1/32","198.51.100.7/32","10.20.0.0/24","fd69:69:69::/64"]' +ssh-hosts: | + Host jump + HostName 10.20.0.10 + AddressFamily inet + + Host myserver.example.com myserver + HostName myserver.example.com + ProxyJump jump diff --git a/secrets/wg0.conf.example b/secrets/wg0.conf.example new file mode 100644 index 0000000..b68d067 --- /dev/null +++ b/secrets/wg0.conf.example @@ -0,0 +1,12 @@ +# wg-quick config, sops-encrypted whole-file (format = "binary"). +# Same format for wg2.conf. Encrypt with: sops -e -i secrets/wg0.conf +[Interface] +PrivateKey = +Address = 10.20.0.2/24 +DNS = 10.20.0.1 + +[Peer] +PublicKey = +AllowedIPs = 10.20.0.0/24 +Endpoint = 198.51.100.7:51820 +PersistentKeepalive = 25 diff --git a/secrets/xray-config.json.example b/secrets/xray-config.json.example new file mode 100644 index 0000000..044a72f --- /dev/null +++ b/secrets/xray-config.json.example @@ -0,0 +1,46 @@ +{ + "inbounds": [ + { + "port": 10808, + "protocol": "socks", + "settings": { + "udp": true + } + }, + { + "port": 10809, + "protocol": "http" + } + ], + "outbounds": [ + { + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "203.0.113.1", + "port": 443, + "users": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "encryption": "mlkem768x25519plus.native.0rtt.", + "flow": "xtls-rprx-vision" + } + ] + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "fingerprint": "chrome", + "serverName": "www.example.com", + "publicKey": "", + "shortId": "", + "spiderX": "/" + } + } + } + ] +}