This is where my infrastructure lives.
All the instructions below assume running in nix develop shell from this flake, and that you have an SSH key named SSH key from my 1password in ~/.ssh/id_ed25519 (it's not enough to have an SSH Agent with that key, as age, and therefore agenix, requires access to the actual private key).
.github- contains CI configurations (note: the secrets are manually set through github's webinterface)apps- various applicationsdatahosts.nix- all the hostnames - the nixos ones are added automatically, windows machines must be added manuallypackages.nix- versions of language runtimes used by the rest of the flakepaths.nix- paths to various things, used throught the flakessh-keys.nix- all the SSH keyssynchting.nix- syncthing configuration and topology, add new nodes here (roles/private/syncthing.nixdefines syncthing configurations, if you are creating a new private machine, you can generate the secrets withgenerate-syncthing-keys ./secrets/ <HOSTNAME>)
libs- libraries used throughtmachine-templates- configurations for clustered machinesmachines- configurations for all the hostsmodules- nix modules to ease configuration of various thingsoutputs- outputs of the flake, imported inflake.nixroles- shared configuration for various roles that machines can have, these can be imported as modulesscripts- scripts for generating keys, etc. - those are loaded into the devshell in./outputs/dev-shells.nix- note: when a script is modified you need to run a new shell with
nix develop(ordirenv reloadif you're using that), so that the shell uses the new version
- note: when a script is modified you need to run a new shell with
secrets- agenix secrets and their configuration (insecrets/secrets.nix)terraform- terraform configurations for everythingusers- user-specific configurations (home-manager configs can also be used independently)
- Make changes as appropriate
- Create a PR and fix any build issues (you can also run
nix flake checklocally) - Once green, merge.
- Once the CI on the
mainbranch succeeds, the affected nodes will pull their new closures within about a minute. - In case of issues with automatic updates,
ssh root@<HOSTNAME>and check the logs:journalctl --unit updater --follow
- Install latest debian stable and ensure it's up to date
- On the host - enable the SSH Agent in 1password, and install npiperelay (
winget install albertony.npiperelay) - Install lix in multiuser mode (https://lix.systems/install/)
- Go to the summary of the latest
build-machinesCI job and find the closure forramona-wsl - Copy the closure:
nix-copy-closure --from root@hallewell <CLOSURE> - Activate it:
<CLOSURE>/bin/activate(this will, among other things, install an automatic updater job as a user systemd service calledupdater) - Switch your user to bash from the new home-managed profile:
chsh -s /home/ramona:/home/ramona/.nix-profile/bin/bash ramona - Restart the shell and have fun.
- Create a config in
machines/directory (probably easiest to just copy paste on of the existing machines), where the directory name is the hostname (remember to set it innetworking.hostNameas well)- there are some potentially useful modules that can be imported from
modules/as well as common configs inroles/ - there's also
machine-templates/directory that can be used for clusters, etc. -- those need to be manually added tooutputs/nixos-configurations-x86_64-linux.nix
- there are some potentially useful modules that can be imported from
- Add entries for host keys in
secrets/secrets.nix(<HOSTNAME>-ssh-host-key-ed25519.ageand<HOSTNAME>--ssh-host-key-rsa.age, with appropriate reciepients, includingci, so it can be deployed automatically) - Run
generate-host-keys ./secrets/ <HOSTNAME> - Paste the output of previous command into
data/ssh-keys.nix(under themachineskey) - Add a
<HOSTNAME> = ssh-keys.machines.<HOSTNAME>.rsavariable tosecrets/secrets.nix, and add topublicServers. - Inside the
secretsdirectory runagenix -rto rekey the secrets to include the new host keys.
- Create a file that instantiates the
nodemodule in the terraform directory. - Create a PR, wait for it to build (you can also run
nix flake checklocally beforehand) and fix any issues. - A terraform plan will be posted as a PR comment, verify that it does what it should and if so, merge the PR.
- Ensure the builds on the main branch succeed (sadly some terraform issues only happen during apply), once they do, the machine should be reachable over tailscale and running as configured in the repository.
- Create a PR, and merge once the builds succeed. When the build on the
mainbranch finishes, the configuration is ready for deployment. - Run
make-preinstall-bundle <HOSTNAME> - Download and boot
https://ramona.fun/public/nixos-latest.iso. Eithersshasroot, or use the local console for the rest of the setup. - Manually partition the disk as appropriate (ensure the configuration is in sync with what's in the repository), and mount in
/mnt/(if other partitions are present, ensure they are mounted in appropriate locations) - Copy the preinstall bundle from your machine (
scp <HOSTNAME>-rootfs.tar root@<IP_OF_THE_NODE>:/root/) and extract it into/mnt/(cd /mnt && tar zvf /root/<HOSTNAME>-rootfs.tar) - Go to the summary of the
build-machinesCI job, and find the closure for your new machine. - On your local machine run
nix-copy-closure --from root@hallewell <CLOSURE>, and thennix-copy-closure --to root@<IP_OF_THE_NODE> <CLOSURE>(if you need a non-standardsshport, prependNIX_SSHOPTS='-p <PORT>'to the command.- if the closure is too big to fit in memory, you can perform a standard nixos installation following the manual, and then copy the closure and switch with
<CLOSURE>/bin/switch-to-configuration switch
- if the closure is too big to fit in memory, you can perform a standard nixos installation following the manual, and then copy the closure and switch with
- Perform the installation using
nix-install --closure <CLOSURE>and reboot - Once the node is up,
ssh root@<IP_OF_THE_NODE>, and start tailscale (tailscale up, and then follow the prompts). - Create a PR adding tags to the nodes (you just need to add
data "tailscale_device" "<HOSTNAME>" { hostname = "<HOSTNAME>"; }, and then atailscale_device_tagsresource analogus to current settings for other machines. The tags are automatically generated inflake.nix).
- Create a directory in
apps/containg the app. If it's in rust, add it to the workspace at the root of the repo. - Create a new file in
packages/which exports an attrset with keyspackage(the derivation for the package),coverage(a derivation that should produce an lcov coverage file, where paths are relative to(toString ./.)in the root of the flake), andchecks, which is an attrset with arbitrary names as keys, and derivations as values that execute the checks (build should fail if the check fails), such as lints, formatting checks, etc.- look through currently existing files, rust is very abstracted for example
- the coverage will be automagically picked up by the CI
- Import the file in
packages/default.nix, with the package name as the key underapps - The app will be available as
pkgs.ramona.your-keythroughout the flake. Can be built standalone withnix build .#your-keyas well.
Automatic updates can be stopped:
- for home manager:
touch ~/.stop_updates - nodes:
touch /var/.stop_updates
Generally the flake should build anywhere, so the machines can be rebuilt by checking out the repo and doing nixos-rebuild --flake .#<HOSTNAME>. Terraform will work with the normal terraform validate/terraform plan/terraform apply worflow as long as you have the SSH key and are in the nix develop shell (there's a wrapper defined in outputs/dev-shells.nix that decrypts secrets/terraform-tokens.age and exports the secrets as environment variables).
Almost everything is managed declaratively, by a combination of terraform, argo-cd and nix.
The core componenets (kube-apiserver, kubelet, kube-controller-manager, kube-proxy) are ran as systemd services.
The only thing that needs to be managed manually is the certificates and kubeconfig files, which can be done with the r app (see apps/r). The CA certificate and key con be found on the control plane nodes in /etc/kubernetes/pki.
This is a useful reference about which certificates are needed where: https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/04-certificate-authority.md
In a disaster recovery scenario, careful use of terraform apply --target=... might be required, some things that are normally managed by argo-cd might need to be deployed manually (e.g. the roles for kube-proxy).
After the cluster is set up, update the secret in secrets/darkmore-kubeconfig.age with the contents of /etc/kubernetes/admin.conf from one of the nodes, with the server key updated to match the tailscale address of one of them (and port 6443).