NixOS Things
word of warning
I only just got started with NixOS, I have no idea what I'm doing. Stuff below might not be best practice, might not work, might be dangerous, idk.
I'd love to hear improvements though, i'm reachable on matrix as @f0x:pixie.town
, freenode f0x
, email nixos@cthu.lu
:)
Erase your darlings
My current setup is similar to https://grahamc.com/blog/erase-your-darlings, although the wipe-during-boot isn't currently actually active yet.
ZFS on LUKS with USB auto-decrypt
Boot order of these was kinda finnicky, otherwise it fails during boot.
- preLVM Mount USB with keyfile
1 2 3 4 5 6
preLVMCommands = pkgs.lib.mkBefore '' mkdir -m 0755 -p /key sleep 2 echo "mounting /key" mount -n -t ext4 -o ro `findfs UUID=<USB_UUID_HERE>` /key '';
- set ZFS disks to preLVM as well
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
luks.devices = { ssd1 = { name = "ssd1"; device = "/dev/disk/by-uuid/SSD_UUID_HERE"; allowDiscards = true; preLVM = true; keyFile = "/key/file"; fallbackToPassword = true; }; hdd1 = { name = "hdd1"; device = "/dev/disk/by-uuid/HDD_UUID_HERE"; preLVM = true; keyFile = "/key/file"; fallbackToPassword = true; }; ... etc };
Morph to localhost
As the server is my most powerful machine, it makes sense for that to do all the building. Remote builds didn't work (and executing morph from my not-nixos desktop isn't great anyways). So I edit my morph stuff on desktop, with a deploy script that does:
rsync -avP . cosmos:/persist/morph ssh cosmos morph deploy --upload-secrets /persist/morph/nodes.nix switch
So if something were to happen on cosmos making morph unworkable, I still have a local copy I can use to fix it, just have to change the localhost
directive.
The config for cosmos allows it's own ssh key access to root, to make the ssh localhost work.
Packaging simple things
Documentation: Nixpkgs
Packaging a simple utility, in this case galaxy, a combination of nodejs (no deps) and a bash script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
with import <nixpkgs> {}; stdenv.mkDerivation rec { pname = "galaxyMotd"; version = "0.0.1"; src = pkgs.fetchgit { url = "https://git.pixie.town/f0x/galaxy"; rev = "96de8613458ef06185f2cec371556cd0fac26f35"; sha256 = "1gcrinv14sbqybyvcfyq36mcfvq0lzqr3shmjw0cwl86s4ny6mm2"; }; buildInputs = [nodejs]; dontBuild = true; installPhase = '' mkdir -p $out/bin mv galaxy.js $out/bin/ mv motd $out/bin/galaxy_motd ''; }
You get the rev
and sha256
through nix-prefetch-git <repo_url>
. Basically just need to make sure ends up in $out/bin
and then it's good
Throwing my scrapped ZSH config in
Split in a separate file, so
1 2 3 4 5 6
{pkgs, ...}: { environment.etc.zshrc.text = pkgs.lib.mkAfter '' # ZSH config goes here '' }
Escaping for ZSH stuff
My config uses a bunch of ${}
or just $
in places, which have to be escaped inside the '' ''
block by prefixing them with ''
. With Vim: s/\$/''$$/g
turns then into ''$
.
Codimd / HedgeDoc
It's amazing how many services have Nix packages + service modules written for them, making them relatively easy to install. As a test I set up HedgeDoc;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
services.hedgedoc = { enable = true; configuration = { allowOrigin = ["cosmos"]; domain = "cosmos:3000"; host = "0.0.0.0"; #TODO: include this from .gitignore'd semi-secrets file # (still ends up in nix store) sessionSecret = "<keysmash here>"; db = { dialect = "postgres"; host = "/var/run/postgresql"; username = "codimd"; }; }; };
To keep things fully declarative, I added the Postgres info too. Doing it that way uses Peer Authentication. Unix user = access to the corresponding postgres user.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
services.postgresql = { enable = true; dataDir = "/persist/postgres"; #TODO: make wrapper function to set easily add entries # for database and user with same name ensureDatabases = ["codimd"]; ensureUsers = [ { name = "codimd"; ensurePermissions = { "DATABASE codimd" = "ALL PRIVILEGES"; }; } ]; };
And that's a fully working HedgeDoc installation.
Storing semi-secrets
Fixing the Hedgedoc TODO, I now have a semi-secrets.nix
for things I don't want in my (eventually published) git repo, but that do need to be passed straight into Nix stuff.
Hence also the term semi-secrets, because they end up in the /nix
store, which is readable system-wide.
The secure thing to do (like happens with ssh private keys for example) is have the config refer to a file on disk, with proper permissions, optionally managed through Morph secrets management for example.
semi-secrets.nix:
1 2 3
{ hedgedoc.sessionSecret = "<keysmash here>"; }
System config is now wrapped in a let/in
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
{ config, pkgs, ... }: let semiSecrets = (import ../../semi-secrets.nix); in { ... system config stuff services = { hedgedoc = { configuration = { sessionSecret = semiSecrets.hedgedoc.sessionSecret; }; }; }; ... more system config stuff }
and a .gitignore
that excludes semi-secrets.nix
Writing functions (for generating my zsh config)
I basically dumped my ZSH config in a string and called it a day, but I quite like using different color combinations for different hosts.
For that, I converted zshConfig.nix
into a function with 2 (optional) arguments;
1 2 3 4 5 6
{color1 ? "2", color2 ? "3"}: ('' # ZSH config # prompt PROMPT='%F{${color1}}[%m] %a%F{${color2}}%n [%3c] %F{15}' '')
Actually the only place in that whole file where I do want $ to be used as a $, substituting the color numbers nicely into the config. It's called like this from my machine specific configs:
1
environment.etc.zshrc.text = pkgs.lib.mkAfter (zshConfig {color1 = "4"; color2 = "5";});
Writing functions (for generating postgres users with databases)
Most of my TODO's are where I notice I'm copy-pasting, but don't quite know yet how to abstract the functionality. Here I am generating Postgres users that have full access to a database with the same name;
1 2 3 4 5 6 7 8 9
services = { postgresql = ({ enable = true; dataDir = "/persist/postgres"; } // (postgresUsersWithDatabases ["codimd" "synapse"] ) ); };
The //
is a new operator I learned, which merges sets: a // b
adds all the keys/values from b into a (overriding existing ones).
postgresUsersWithDatabases function:
1 2 3 4 5 6 7 8 9 10 11 12
databases: { ensureDatabases = databases; ensureUsers = (let userWithPermissions = user: { name = user; ensurePermissions = { "DATABASE ${user}" = "ALL PRIVILEGES"; }; }; in (map userWithPermissions databases)); }
Now I'm thinking with portals.
NixOS Installation on BuyVM
BuyVM needs static ip assignments, the details of which can all be found in the networking tab of the Stallion control panel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
networking = { useDHCP = false; defaultGateway = ""; defaultGateway6 = ""; interfaces.eth0 = { ipv4.addresses = [{ address = ""; prefixLength = 24; }]; ipv6.addresses = [{ address = ""; prefixLength = 48; }]; }; };
NixOS Installation on Hetzner Cloud VPS
Similar to BuyVM, it needs some manual settings (but only for ipv6), like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
networking = { usePredictableInterfaceNames = false; # if true/default, it's *probably* ens3 instead of eth0 defaultGateway6 = { address = "fe80::1"; interface = "eth0"; }; interfaces.eth0 = { useDHCP = true; # for ipv4 ipv6.addresses = [{ address = "YOUR:IPV6:ADDR::1"; prefixLength = 64; }]; }; };