Claude 🤖, evo ti "hetzner" server: Particioniraj, podigni zfs, pa instaliraj NixOS!
Naslov je manje-više ono što sam ukucao u terminal: “Claude, evo ti ‘hetzner’
host — particioniraj, podigni ZFS, pa instaliraj NixOS!”. Rezultat — potpuno nov
dedicirani server sa NixOS-om na ZFS-u, priključen na našu colmena flotu i
tailnet, instaliran skoro u potpunosti autonomno. Server u tekstu zovemo
hetzner-2; svi ostali identifikatori (IP adrese, serijski brojevi diskova,
imena susjednih hostova, domeni) su u ovom zapisu generalizovani radi
privatnosti.
Polazna tačka
Hetzner dedicirani server, butovan u Rescue System (Debian), dostupan preko SSH ključa:
- 2× NVMe SSD od 1.92 TB
- Intel Core i9-13900, 62 GB RAM
- UEFI boot (bitno — stariji
hetzner-1koristi legacy BIOS)
Cilj: ZFS mirror (RAID1) root, dedicirana swap particija od 32 GB, puna iskorištenost diskova, bez enkripcije.
Napomena: Hetzner-ov
installimagene podržava ZFS root, pa je sve rađeno ručno po OpenZFS / NixOS-on-ZFS receptu.
Korak 1 — skripta za wipe i instalaciju
Prvo je nastala skripta hetzner-2-dedicated-wipe-and-install-nixos-swap.sh,
izvedena iz našeg postojećeg recepta za hetzner-1, ali prilagođena novom
hardveru. Ključne razlike:
- ZFS native mirror preko sirovih particija (
zpool create ... root_pool mirror <disk1>-part3 <disk2>-part3) — ne ZFS-na-mdraid-u - Dedicirana swap particija od 32 GiB, kao
mdraid1mirror (/dev/md/swap) - UEFI: nema više
bios_grubparticije; GRUB se instalira kao EFI samirroredBootsna dvije nezavisne ESP particije (jedna po disku), pa svaki disk može samostalno butati - Particija za podatke ide do
100%— diskovi se koriste u cijelosti (~1.65 TB iskoristivo u mirroru)
Šema particija po disku:
part1 1 GiB ESP (FAT32) -> /boot/efi, /boot/efi2 (mirroredBoots)
part2 32 GiB swap -> mdraid1 mirror /dev/md/swap
part3 ostatak ZFS root_pool -> mirror (native ZFS)
NixOS configuration.nix koji skripta generiše bira GRUB-EFI umjesto
systemd-boot (jer /boot živi na ZFS-u, a systemd-boot ne zna čitati kernele
sa ZFS-a):
boot.loader.grub = {
enable = true;
efiSupport = true;
zfsSupport = true;
mirroredBoots = [
{ devices = [ "nodev" ]; path = "/boot/efi"; efiSysMountPoint = "/boot/efi"; }
{ devices = [ "nodev" ]; path = "/boot/efi2"; efiSysMountPoint = "/boot/efi2"; }
];
};
Cijela logika (zap diskova, particionisanje parted-om, kreiranje root_pool
mirrora i swap mdraid-a, formatiranje ESP-ova, nixos-install) živi u jednoj
skripti, koja se pokreće iz Rescue System-a:
# kopiraj skriptu na server i pokreni je u rescue-u (detached, sa logom)
scp hetzner-2-dedicated-wipe-and-install-nixos-swap.sh root@<SERVER_IP>:/root/
ssh root@<SERVER_IP> \
"nohup bash /root/hetzner-2-dedicated-wipe-and-install-nixos-swap.sh \
> /root/install.log 2>&1 &"
Skripta je idempotentna koliko može biti — na početku ruši postojeće
montiranja/pool-ove i nule mdraid superblokove, pa se može pokretati više puta
dok se sve ne slegne. Na kraju radi reboot u svježe instaliran NixOS.
Korak 2 — automatizacija rescue sesije (Robot API + pass)
Da bi instalacija tekla, server mora biti u Rescue System-u. Umjesto ručnog
klikanja po Hetzner Robot panelu, Claude je iskoristio Robot webservice
kredencijale koje već čuvamo u pass:
WSUSER=$(pass hetzner/ws-user)
WSPASS=$(pass hetzner/ws-password)
Provjera servera i SSH ključeva, aktivacija rescue-a i hardverski reset —
sve preko Robot API-ja (robot-ws.your-server.de):
# aktiviraj rescue (linux) sa našim ključem
curl -s -u "$WSUSER:$WSPASS" -X POST \
"https://robot-ws.your-server.de/boot/<SERVER_IP>/rescue" \
--data-urlencode "os=linux"
# hardverski reset -> server butuje u rescue
curl -s -u "$WSUSER:$WSPASS" -X POST \
"https://robot-ws.your-server.de/reset/<SERVER_IP>" \
--data-urlencode "type=hw"
Otisak našeg SSH ključa je provjeren spram ključeva registrovanih u Robot-u prije
reseta, da slučajno ne ostanemo zaključani van rescue sistema. Nakon ~25 sekundi
server je bio u rescue-u, ZFS kompajliran (openzfs_install), a skripta
pokrenuta detached uz logovanje.
Ova sitnica — čitanje kredencijala iz
pass— je ono što cijeli proces čini automatskim: nema ručne prijave na web panel, nema kopiranja lozinki.
Cijeli ciklus se ponavljao više puta dok se sve nije sleglo (jedan raniji pokušaj
je čak završio kao standardni installimage Ubuntu na mdraid-u — pa smo se vratili
u rescue i odradili ZFS kako treba).
Korak 3 — colmena konfiguracija (infra-hodi)
Server se ne ostavlja “ručno instaliran” — ulazi u našu deklarativnu flotu.
Dodali smo novi host u colmena hive:
hosts/hetzner/hetzner-2/default.nix— uvozi zajednički_common(isti set paketa kaohetzner-1), GRUB-EFImirroredBoots,boot.swraid.enableza swap mirror, te stvarnihardware-configuration.nixsa servera (“respect the hardware”)hive/hetzner-2.nix— colmena node,deployment.targetHost= javni IPhive.nix— registracija hosta i pin nanixpkgs-26.05(najnoviji stable)
Najnoviji NixOS (26.05) smo dodali kao novi pin:
# nixpkgs-26.05/default.nix
import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/<rev>.tar.gz";
sha256 = "sha256-...";
})
# hive.nix
nodeNixpkgs = {
hetzner-2 = (import ./nixpkgs-26.05); # prati najnoviji stable
...
};
Eval je odmah uhvatio jedan benigni warning (mdadm: Neither MAILADDR nor PROGRAM has been set ... mdmon ... crash) jer host ima md swap — riješeno sa
boot.swraid.mdadmConf = "MAILADDR root@localhost";.
Korak 4 — deploy preko colmena
Build prvo (sigurnosna provjera da se closure uopšte gradi), pa deploy:
nix-shell --run "colmena build --on hetzner-2"
nix-shell --run "colmena apply switch --on hetzner-2"
SSH agent je dobio naš ključ, a stari host-key zapis je osvježen prije konekcije. NixOS 26.05 donosi kernel 6.18 i GCC 15 — GRUB se rekompajlira iz izvora (sa ZFS podrškom), zatim se closure gura na server i aktivira. Rezultat:
$ nixos-version
26.05pre-git (Yarara)
$ zpool list -H -o name,health
root_pool ONLINE
$ swapon --show
/dev/md127 32G
(colmena na kraju prijavi exit 4 zbog reload-a dbus-broker user unita na
bezglavom serveru — kozmetički; sistemski dio aktivacije prolazi bez ijednog
neuspjelog unita.)
Korak 5 — priključenje na tailnet (headscale)
hetzner-1 je na tailscale-u, pa i hetzner-2 treba biti. Modul
services/tailscale samo podiže tailscaled; prijava na headscale ide
out-of-band preauth ključem. Ključ smo generisali na headscale serveru
(headscale-server) za korisnika tailuser:
# na headscale-server
headscale preauthkeys create -u tailuser -e 24h
# na hetzner-2
tailscale up \
--login-server https://headscale.example.com \
--authkey <preauth-key> \
--hostname hetzner-2
I server je na mreži:
100.64.0.x hetzner-2 tailuser linux -
Preauth ključ nikad nije ispisan u čitljivom obliku — prebačen je direktno iz
generisanja u tailscale up.
Rezultat
Za jednu sesiju, iz jedne rečenice:
- ✅ NixOS 26.05 na ZFS mirroru, UEFI GRUB sa dvije nezavisne ESP particije
- ✅ 32 GB swap kao mdraid1 mirror, puna iskorištenost diskova
- ✅ Host u
colmenafloti, deklarativno upravljan - ✅ Na
tailnet-u preko headscale-a - ✅ Sve commit-ovano u
infra-hodi
Najzanimljiviji dio nije ni ZFS ni colmena — nego to što je kombinacija
pass-a i Robot API-ja dozvolila agentu da sam aktivira rescue i resetuje
fizički server, bez ijednog klika u web panelu.
Napomena
Generisano od strane Claude 🤖
Ernad Husremović, hernad@bring.out.ba