sops: introduce sops-nix (age), ssh keys from encrypted tarball, .example secrets

This commit is contained in:
Sergei Poljanski 2026-07-14 12:24:21 +04:00
commit a7ec82579c
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
9 changed files with 149 additions and 7 deletions

5
.gitignore vendored
View file

@ -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

8
.sops.yaml Normal file
View file

@ -0,0 +1,8 @@
keys:
- &asxpi age19xvmm6j3h2u6mc3w6k65ufry4p5aa5lwu96p55h3e42p9wfzwc0qp2w7pk
creation_rules:
- path_regex: secrets/.*
key_groups:
- age:
- *asxpi

View file

@ -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

View file

@ -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

View file

@ -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`

30
modules/sops.nix Normal file
View file

@ -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 <keys...> && 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
'';
};
}

View file

@ -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

12
secrets/wg0.conf.example Normal file
View file

@ -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 = <client-private-key>
Address = 10.20.0.2/24
DNS = 10.20.0.1
[Peer]
PublicKey = <server-public-key>
AllowedIPs = 10.20.0.0/24
Endpoint = 198.51.100.7:51820
PersistentKeepalive = 25

View file

@ -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.<base64-client-key>",
"flow": "xtls-rprx-vision"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.example.com",
"publicKey": "<reality-public-key>",
"shortId": "<short-id>",
"spiderX": "/"
}
}
}
]
}