NixOS Impermanence ve SOPS-Nix: systemd-initrd Sonrası Boot Kilitlenmesini Çözmek

NixOS üzerinde Impermanence (tmpfs) kullanarak root dizinini her kapanışta siliyor ve sistem sırlarınızı (şifreler, API anahtarları) sops-nix ile yönetiyorsanız, muhtemelen “End-Game” seviyesinde bir setup kullanıyorsunuzdur. Ancak bu ikiliyi bir araya getirmek çoğu zaman bir tavuk-yumurta problemine dönüşür.

Özellikle NixOS 26.05 ile birlikte varsayılan hale gelen systemd-initrd yapısı, eski yapılandırmalarda çok can sıkıcı bir boot kilitlenmesine (race condition) yol açmaya başladı.


1. Problem: “neededForBoot” Kördüğümü

Sırları çözmek için SOPS’un bir anahtara (genelde SSH veya özel age anahtarı) ihtiyacı vardır. Geleneksel olarak bu anahtar /etc/ssh/ssh_host_ed25519_key altında durur. Ancak root dizininiz tmpfs ise (yani her boot’ta siliniyorsa), bu anahtarı bir şekilde /persist (kalıcı) diskinizden /etc/ssh altına bağlamanız (bind-mount) gerekir.

Eskiden Impermanence modülünde bunu şöyle çözüyorduk:

# ESKİ VE SORUNLU YÖNTEM
environment.persistence."/persist" = {
  directories = [
    "/etc/ssh" # <-- Sops'un aradığı anahtar bu dizinin altında
  ];
};

# Sops'un anahtarı okuyabilmesi için boot'un erken aşamasında bağlanmaya zorla
fileSystems."/etc/ssh".neededForBoot = true;

Neden patlıyor? Yeni systemd-initrd boot mimarisi, neededForBoot = true ile işaretlenmiş bind-mount işlemlerini çok daha agresif ve paralel ele alıyor. SOPS servisi, anahtarın mount edilmesini beklerken systemd mount döngüsü (cycle) karışıyor ve sistem siyah ekranda sessizce kilitlenip kalıyor. (nix-community/impermanence #294).


2. Çözüm: Aracıları Ortadan Kaldırmak

Sorunu çözmenin en zarif yolu, neededForBoot ile bind-mount dansına hiç girmemektir. SOPS’a, “Anahtarı /etc/ssh altında arama, doğrudan kalıcı diskin ham (raw) yolundan oku” dememiz gerekiyor.

/persist btrfs/zfs subvolume’unuz zaten donanım seviyesinde çok erkenden mount edildiği için, SOPS anahtara anında erişebilir ve hiçbir kilitlenme yaşanmaz:

{ config, ... }:

{
  sops = {
    defaultSopsFile = ../../secrets/secrets.yaml;
    
    # DOĞRU YÖNTEM:
    # /etc/ssh bind-mount'unu beklemek yerine doğrudan ham persist yolunu göster.
    # Bu sayede systemd-initrd race condition'ı tamamen bypass edilir.
    age.sshKeyPaths = [ "/persist/xmrah/.ssh/id_ed25519" ];
  };
}

Bu tek satırlık değişiklik sayesinde Impermanence mount kurallarınızı neededForBoot karmaşasından kurtarır ve boot sürecini pürüzsüz hale getirirsiniz.


3. Bilinçli Tercih ve Güvenlik Takası (Trade-off)

Yukarıdaki kod bloğunda dikkat ederseniz, decryption anahtarı olarak doğrudan kendi kişisel kimlik anahtarımı (/persist/xmrah/.ssh/id_ed25519) kullandım. Bu cihaz benim tek kullanıcılı fiziksel bilgisayarım olduğu için, operasyonel karmaşıklığı azaltmak adına yaptığım bilinçli bir tercihtir.

Ancak bunu kopyalarken şu riskleri bilerek kabul ettiğinizi unutmayın:

[!CAUTION] Blast Radius (Etki Alanı) Birleşmesi Kişisel SSH anahtarınızı SOPS decryption anahtarı olarak kullanırsanız, o anahtar sızdığında saldırgan sadece GitHub’ınıza erişmekle kalmaz; aynı makinedeki tüm sistem sırlarını (API keyler vb.) da anında çözer.

Rotasyon Kırılganlığı (Rotation Fragility) Güvenlik şüphesiyle kişisel SSH anahtarınızı yenilediğinizde, NixOS boot sırasında eski anahtarı bulamayacağı için sırları çözemez ve sistem açılmaz.

Eğer bir sunucu kuruyorsanız veya güvenlik tehdit modeliniz tek bir laptop’tan daha karmaşıksa, benim yaptığım gibi kişisel kimlik anahtarınızı kullanmayın. SOPS için varsayılan ssh_host_ed25519_key anahtarını kullanın veya tamamen bu işe özel dedike bir age anahtarı üretip (age-keygen -o /persist/var/lib/sops-nix/key.txt) güvenli bir şekilde saklayın.

Hangisini seçerseniz seçin, anahtarı /etc/ssh üzerinden değil, doğrudan /persist altındaki ham yoldan okutun (örn. /persist/etc/ssh/ssh_host_ed25519_key). Aksi halde 1. bölümdeki kilitlenme sorununa (race condition) geri dönersiniz.

EOF.