A collection of scripts and modules that wrap great existing tools like NixOS Anywhere
and deploy-rs
, adding the necessary glue to make deployment and
maintenance of bare metal servers on Hetzner easier. This repository provides
reusable components that can be integrated into project-specific NixOS
configurations.
- Easy activation of rescue mode for Hetzner servers
- Hardware configuration generation
- Disk partitioning configuration using disko
- Initial deployment with NixOS Anywhere
- Updates with deploy-rs
- WireGuard key management and peer configuration
- Base system configuration including:
- Network setup with WireGuard VPN
- SSH server configuration
- Basic security settings
- User management
- Common system packages
-
Initial Setup
- Create a project-specific flake
- Configure SOPS encryption
- Set up Hetzner Robot credentials
-
Server Discovery
- Generate a nix config for each server using the Heztner Robot API.
- Configure network settings automatically
-
Hardware Setup
- Boot server into rescue mode
- Generate disk (disko) configuration
- Generate hardware configuration
-
Network Setup
- Configure SSH access with project keys
- Generate WireGuard keys for all servers
- Set up WireGuard mesh network
-
Deployment
- Initial deployment with nixos-anywhere
- Subsequent updates with deploy-rs
For detailed instructions, follow the setup phases below.
-
Prerequisites
- Nix with flakes enabled
- A Hetzner server
- SOPS for secrets management
- Basic understanding of NixOS configuration
-
Create Project Flake
Create a new flake.nix in your project directory:
{ description = "myproject description"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; flake-parts.url = "github:hercules-ci/flake-parts"; disko.url = "github:nix-community/disko"; sops-nix.url = "github:Mic92/sops-nix"; hetzner-deploy-scripts.url = "github:rochecompaan/hetzner-nixos-deploy"; deploy-rs.url = "github:serokell/deploy-rs"; }; outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" ]; perSystem = { config, pkgs, ... }: { devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ # your packages here wireguard-tools deploy-rs ] ++ (builtins.attrValues inputs.hetzner-deploy-scripts.packages); }; }; }; }
This flake:
- Imports the hetzner-nixos-deploy scripts as a dependency
- Makes all scripts available in your development shell
- Allows you to add your own project-specific packages
Enter the development shell:
nix develop
All hetzner-nixos-deploy scripts will be available in your PATH.
You can use direnv to automatically enter you development shell when changing into your project directory.
To automatically create the dev shell, you need a
.envrc
file in your project withuse flake
, e.g.:#!/usr/bin/env bash if type nix-shell >/dev/null 2>&1; then use flake fi
-
SOPS Configuration
Create a
.sops.yaml
file in your project root to configure SOPS encryption:# .sops.yaml keys: - &admin_alice age1... # Replace with your age public key - &admin_bob age1... # Add more team members as needed creation_rules: - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$ key_groups: - age: - *admin_alice - *admin_bob
Generate an age key pair for encrypting/decrypting secrets:
# Create directory for age keys mkdir -p ~/.config/sops/age # Generate new age key pair age-keygen -o ~/.config/sops/age/keys.txt # View your public key to add to .sops.yaml age-keygen -y ~/.config/sops/age/keys.txt
The age key pair is used by SOPS to encrypt/decrypt secrets. The public key goes in
.sops.yaml
while the private key stays in~/.config/sops/age/keys.txt
.For more details on using SOPS with NixOS, see the sops-nix documentation.
-
Create Hetzner Robot credentials file:
echo '{"hetzner_robot_username": "your-username", "hetzner_robot_password": "your-password"}' | sops -e > secrets/hetzner.json
-
Generate server configurations using the Hetzner Robot API:
# Generate for all servers generate-server-config # Filter servers by name pattern generate-server-config "myproject" # Overwrite existing configurations generate-server-config --overwrite "myproject" # Specify custom WireGuard subnet generate-server-config --subnet "10.0.0.0/16" "myproject"
This will create a NixOS configuration for each server in the
hosts/
directory. The script:- Fetches server details from the Hetzner Robot API
- Creates a directory structure for each server
- Generates network configuration based on server IP information
- Assigns sequential WireGuard IPs in the 172.16.0.0/24 range
- Sets up default interface names and gateway IPs
- Generate RSA and ED25519 SSH host keys for each server
- Convert the public ED25519 keys to age format using ssh-to-age
- Output the age public keys to add to .sops.yaml
Here is an example of a
default.nix
module generated by the script:{ imports = [ ./hardware-configuration.nix ./disko.nix ./wg0.nix ../../modules/base.nix ]; networking = { hostName = "server1"; useDHCP = false; # Primary network interface interfaces.enp0s31f6 = { ipv4.addresses = [{ address = "123.45.67.89"; prefixLength = 24; }]; }; defaultGateway = "123.45.67.1"; }; }
The script will automatically download
modules/base.nix
if it doesn't exist. You can customize both the generated configuration andmodules/base.nix
as needed for your specific requirements. -
Add the generated age keys to
.sops.yaml
:# .sops.yaml keys: - &admin_alice age1... # Replace with your age public key - &admin_bob age1... # Add more team members as needed - &server_server1 age1wy7nmyfsnkfzsl2txt0z4anqu56d6p4a4v0zan0357a5kv36zevqzeesnp - &server_server2 age1rgffpespcyjn0d8jglk7km9kfrfhdyev6camd3rck6pn8y47ze4sug23v3 creation_rules: - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$ key_groups: - age: - *admin_alice - *admin_bob - *server_server2 - *server_server2
Make sure to run
sops updatekeys secrets/wireguard.json
to re-encrypt secrets.
-
Activate rescue mode:
# Just activate rescue mode activate-rescue-mode <server-ip> # Or activate rescue mode and boot into NixOS installer activate-rescue-mode --boot-nixos <server-ip>
-
Generate disk configuration:
generate-disko-config <server-ip> <hostname>
-
Generate hardware configuration:
generate-hardware-config <server-ip> <hostname>
You can also run setup-servers
that will run the above scripts for all hosts
automatically.
The server configuration process handles both SSH and WireGuard setup automatically.
-
SSH Access Configuration:
SSH keys are used for direct server access and are managed through the
authorized_keys/
directory. The base system configuration (modules/base.nix
) automatically adds these keys to thenix
user's authorized keys.a. Using an Existing SSH Key:
# Copy your existing public key cp ~/.ssh/id_ed25519.pub authorized_keys/alice.pub
b. Generating a New Project-Specific Key:
# Generate a new ED25519 key pair ssh-keygen -t ed25519 -C "alice@project" -f ~/.ssh/project_ed25519 # Copy the public key to authorized_keys cp ~/.ssh/project_ed25519.pub authorized_keys/alice.pub # Update SSH config to use the project key cat >> ~/.ssh/config << EOF # Project-specific configuration Host <publicip> IdentityFile ~/.ssh/project_ed25519 User nix EOF
-
WireGuard Network Setup:
The
generate-server-config
script automatically handles WireGuard configuration during server setup:- Generates unique WireGuard keypairs for each server
- Assigns sequential IPs in the 172.16.0.0/16 range
- Creates
wg0.nix
modules in each server's directory - Maintains a shared peers configuration in
modules/wireguard-peers.nix
- Encrypts private keys in
secrets/wireguard.json
The generated configurations enable secure communication between servers and administrators.
-
Adding WireGuard Admin Access:
# Generate a new WireGuard key pair wg genkey | tee privatekey | wg pubkey > publickey # View your public key cat publickey
-
Add Admin to WireGuard Network:
add-wireguard-admin \ --name alice \ --endpoint alice.duckdns.org \ --public-key "$(cat publickey)" \ --private-ip 172.16.0.10
-
Configure Local WireGuard Client:
# Generate client configuration generate-wireguard-config \ --private-key "$(cat privatekey)" \ --address 172.16.0.10/24 > wireguard/wg0.conf # Start WireGuard interface sudo wg-quick up ./wireguard/wg0.conf
NB! Add and entry to
.gitignore
for wireguard configs:./wireguard/*.conf
-
Pre-deployment Checklist:
- Hardware configuration generated
- Disk configuration created
- Wireguard configuration generated
- Network connectivity verified
-
Initial Deployment:
deploy-nixos <hostname>
After deployment:
# Wait a minute for the server to finish rebooting ssh nix@<server-ip>
-
Subsequent Updates:
deploy .#<hostname>
-
System Updates:
# Update flake inputs nix flake update # Deploy updates deploy .#<hostname>
-
Key Rotation:
# Generate new WireGuard keys nix run .#generate-wireguard-keys -- <environment> <server> # Update admin keys nix run .#add-wireguard-admin -- --name <admin> --update-key
-
Backup Strategy:
- Keep encrypted copies of:
secrets/wireguard.json
- Any custom configurations
- Store backups in a secure location
- Test restoration procedures regularly
- Keep encrypted copies of:
-
Troubleshooting:
- WireGuard issues: Check
journalctl -u wireguard-wg0
- Deployment failures: Verify network and configurations
- Access issues: Check SSH keys and firewall rules
- WireGuard issues: Check
.
├── modules/
│ └── base.nix # Base system configuration
├── secrets/
│ ├── hetnzer.json # Hetzner username and password
│ ├── server-private-ssh-keys.json # Server private ssh keys
│ └── wireguard.json # WireGuard keys and configurations
└── hosts/ # Server-specific configurations
├── default.nix # Generates host nixos configurations
└── <hostname>/
├── default.nix
├── disko.nix
├── hardware-configuration.nix
└── wg0.nix
This project is licensed under the MIT License - see the LICENSE file for details.