NixOS - Das Multi-Host-Setup individualisieren

  Reto   Lesezeit: 11 Minuten  🗪 1 Kommentar Auf Mastodon ansehen

In meinem letzten Beitrag der NixOS-Reihe zeige ich, wie man einen einzelnen Rechner aus dem gemeinsamen Setup herauslöst und individuell konfiguriert – ohne die anderen Rechner auch nur zu berühren.

nixos - das multi-host-setup individualisieren

Wer meiner Reihe gefolgt ist, hat mittlerweile vielleicht mehrere Rechner mit identischer Konfiguration am Laufen. Eine configuration.nix für alle, eine home.nix für alle – sauber, simpel, reproduzierbar. Das ist bereits beeindruckend und für viele Anwendungsfälle völlig ausreichend.

Aber die Realität ist eben manchmal: drei Rechner, drei unterschiedliche Charaktere.

Es gibt viele Gründe und Szenarien für eine abweichende Konfiguration - als Beispiel nehme ich hier einfach mal meine Situation - das Vorgehen ist aber exemplarisch und vollständig auf andere Szenarien übertragbar.

Rücksicht nehmen muss ich auf einen geliebten alten Wegbegleiter, ein alter Lenovo mit nur 8 GB und einem schwachen Pentium Prozessor. Er ist das schwächste Glied in meiner kleinen Flotte und leidet unter der identischen Konfiguration, die meine leistungsfähigen Dell-Rechner problemlos stemmen. Fotoverwaltung mit tausenden RAW-Dateien - oder gar deren Entwicklung? Auf dem Lenovo undenkbar. Synchronisation mit mehreren Cloud-Speichern im Hintergrund? Verschwendete Ressourcen. Und der LanguageTool-Server mit seiner Java-Implementierung, die gerne mal fast 2 GB RAM schluckt – den will ich auf dem schwächsten Rechner erst recht nicht.

Dazu kommt: Der Lenovo ist ein Detachable, also ein Gerät das ich meist als Tablet weitab einer Steckdose nutze. Da möchte ich einen Energieverwaltungs-Button der wirklich funktioniert - manchmal muss ich die volle Power nämlich auch im Akkubetrieb zuschalten können. Mit TLP ist der Energieverwaltungs-Button bekanntlich wirkungslos, weil absichtlich lahm gelegt. TLP übernimmt nämlich die Kontrolle vollautomatisch über die CPU-Taktung und ignoriert was ich z.B. über GNOME einstellen möchte.

Jetzt könnte man auf die Idee kommen, die configuration.nix mit Bedingungen zu pflastern: "wenn Lenovo, dann dies, sonst das". Das ist aber der Weg in die Unübersichtlichkeit - und Unübersichtlichkeit ist nicht "Nix-like", wie du dir anhand des bisher Dargestellten sicher denken kannst. NixOS hat eine elegantere Antwort parat.


Das Prinzip: additive Module

In NixOS lassen sich mehrere Konfigurationsdateien zu einem System zusammensetzen. Statt eine einzige große configuration.nix mit Ausnahmen vollzustopfen, addiert man für den Lenovo einfach eine weitere kleine Datei obendrauf. Die configuration.nix bleibt vollständig unangetastet. Die Dell-Rechner bemerken von alldem nichts.

Das Ergebnis sieht so aus:

/etc/nixos/
├── flake.nix                          ← angepasst
├── configuration.nix                  ← unverändert (gilt für alle)
├── home.nix                           ← unverändert (gilt für meine zwei Dell)
├── lenovo-system.nix                  ← NEU: Lenovo-spezifisch (System)
├── home-lenovo.nix                    ← NEU: Lenovo-spezifisch (Benutzer)
├── hardware-configuration-dell5320.nix
├── hardware-configuration-dellpro.nix
└── hardware-configuration-lenovo.nix

Zwei neue Dateien. Mehr nicht.


Schritt 1: lenovo-system.nix erstellen

Diese Datei enthält ausschließlich die Dinge, die auf dem Lenovo anders sein sollen als in der configuration.nix. Alles andere – GNOME, Drucker, Sprache, Pakete – bleibt unverändert aus der gemeinsamen Konfiguration.

Die folgenden Dateien spiegeln meine eigene Konfiguration wider, sind also nur als Beispiele zu sehen. Das Grundprinzip sollte dir dadurch aber klar werden und dich zu einer individuellen Umsetzung auf deine Rahmenbedingungen befähigen.

Ich habe mich entschlossen meine Konfigurationsdateien weitgehend ungekürzt hier wiederzugeben. Das ist für die Lesbarkeit des Artikels zwar ungünstig, aber hilfreich für alle, welche damals meine Setup-Dateien als Ausgangspunkt für die eigene Konfiguration genutzt haben.

sudo nano /etc/nixos/lenovo-system.nix
# ============================================================
# Lenovo-spezifische Systemkonfiguration
# Ergänzt und überschreibt configuration.nix nur dort,
# wo der Lenovo andere Einstellungen benötigt.
# ============================================================

{ config, pkgs, lib, ... }:

{
  # ─────────────────────────────────────────
  # TLP deaktivieren – es überschreibt den GNOME-Energieprofil-Button.
  # lib.mkForce ist nötig um den Wert aus configuration.nix zu überschreiben.
  # ─────────────────────────────────────────
  services.tlp.enable = lib.mkForce false;

  # ─────────────────────────────────────────
  # Power Profiles Daemon aktivieren – er steuert die CPU-Leistungsstufen
  # und integriert sich vollständig in GNOME. Der Energieprofil-Button
  # oben rechts in GNOME funktioniert damit wieder.
  # ─────────────────────────────────────────
  services.power-profiles-daemon.enable = lib.mkForce true;

  # ─────────────────────────────────────────
  # Intel Thermaldienst
  # Schützt die CPU vor Überhitzung auf schwacher Hardware.
  # ─────────────────────────────────────────
  services.thermald.enable = true;
}

Wie wirkt hier lib.mkForce? Wenn zwei NixOS-Module dieselbe Option setzen, gibt es normalerweise einen Konflikt. lib.mkForce sagt NixOS: "Diese Datei gewinnt – egal was andere Module sagen." Das ist genau das richtige Werkzeug um Einstellungen aus der gemeinsamen configuration.nix für einen einzelnen Rechner zu überschreiben.


Schritt 2: home-lenovo.nix erstellen

Hier liegt der eigentliche Hebel für die Ressourcenschonung. Statt die gemeinsame home.nix mit Ausnahmen zu belasten, erstellen wir eine eigene, schlanke Variante.

Wie gesagt, hier nochmal meine (fast) vollständige originale home.nix mit allen Skripten - jetzt ergänzt mit Markierungen zu den Stellen, die sich geändert haben und den entsprechenden Auskommentierungen.

sudo nano /etc/nixos/home-lenovo.nix
# ============================================================
# Home Manager Konfiguration – Lenovo)
# Schlanke Version ohne LanguageTool, Fotosoftware und Clouds.
# ============================================================

{ config, pkgs, pkgs-unstable, ... }:

{
  home.username      = "dein-username";    # anpassen!
  home.homeDirectory = "/home/dein-username";    # anpassen!

  # Nie die Erstinstallations-Version ändern, auch nicht nach Versionssprung!
  home.stateVersion = "25.11";

  programs.home-manager.enable = true;

  # ─────────────────────────────────────────
  # Benutzerprogramme – schlanke Auswahl
  # ─────────────────────────────────────────
  home.packages = [

    # ── Büro (unstable) ───────────────────
    pkgs-unstable.libreoffice-fresh    # Office-Suite
    pkgs-unstable.hunspell             # Rechtschreibprüfung
    pkgs-unstable.hunspellDicts.de_DE  # Deutsches Wörterbuch
    pkgs-unstable.mythes               # Thesaurus
    # languagetool  ← bewusst weggelassen: Java-Server, ~2 GB RAM

    # ── Kommunikation (unstable) ──────────
    pkgs-unstable.thunderbird          # E-Mail
    pkgs-unstable.nextcloud-client
    # weitere Clouddienste         ← nicht benötigt

    # ── Notizen (unstable) ────────────────
    pkgs-unstable.obsidian             # Markdown-Notizen (unfree)

    # ── Datensicherung (unstable) ─────────
    # pkgs-unstable.pika-backup    ← bewusst weggelassen

    # ── Fotografie ────────────────────────
    # pkgs-unstable.rapidraw  ← nicht benötigt
    # pkgs-unstable.digikam   ← nicht benötigt

    # ── Shell & Tools (stable) ────────────
    pkgs.starship
    pkgs.nix-output-monitor
    pkgs.nvd

  ];

  # LanguageTool-Dienst bewusst nicht gestartet
  # (spart ~2 GB RAM auf schwacher Hardware)

  # ─────────────────────────────────────────
  # Git – Benutzerkonfiguration
  # ─────────────────────────────────────────
  programs.git = {
    enable = true;
    settings = {
      user.email = "deine.mail@für.codeberg";    # anpassen!
      user.name  = "dein-username";    # anpassen!
    };
  };

  # ─────────────────────────────────────────
  # SSH – Konfiguration für Codeberg
  # ─────────────────────────────────────────
  programs.ssh = {
    enable              = true;
    enableDefaultConfig = false;
    matchBlocks."codeberg.org" = {
      hostname     = "codeberg.org";
      user         = "git";
      identityFile = "~/.ssh/id_ed25519";
    };
  };

  # ─────────────────────────────────────────
  # GTK-Theme (Arc)
  # ─────────────────────────────────────────
  gtk = {
    enable = true;
    theme       = { name = "Arc";     package = pkgs.arc-theme; };
    iconTheme   = { name = "Papirus"; package = pkgs.papirus-icon-theme; };
    cursorTheme = { name = "Adwaita"; size = 24; };
  };

  # ─────────────────────────────────────────
  # XDG User Directories – Ordner auf Deutsch
  # ─────────────────────────────────────────
  xdg.userDirs = {
    enable            = true;
    createDirectories = true;
    desktop     = "${config.home.homeDirectory}/Schreibtisch";
    documents   = "${config.home.homeDirectory}/Dokumente";
    download    = "${config.home.homeDirectory}/Downloads";
    music       = "${config.home.homeDirectory}/Musik";
    pictures    = "${config.home.homeDirectory}/Bilder";
    publicShare = "${config.home.homeDirectory}/Öffentlich";
    templates   = "${config.home.homeDirectory}/Vorlagen";
    videos      = "${config.home.homeDirectory}/Videos";
  };

  # ─────────────────────────────────────────
  # Fish Shell
  # ─────────────────────────────────────────
  programs.fish = {
    enable = true;

    interactiveShellInit = ''
      starship init fish | source
    '';

    shellAliases = {
      ".."          = "cd ..";
      "..."         = "cd ../..";
      ll            = "ls -lah";
      nix-clean-all = "sudo nix-collect-garbage -d";
    };

    functions = {

      rebuild = {
        description = "System auf diesem Rechner neu bauen";
        body = ''
          sudo nixos-rebuild switch --flake /etc/nixos#(hostname) 2>&1 | nom
        '';
      };

      update-push = {
        description = "NixOS aktualisieren und auf Codeberg sichern";
        body = ''
          echo "────────────────────────────────────────────"
          echo "  NixOS Update + Store bereinigen + Git Push"
          echo "────────────────────────────────────────────"

          cd /etc/nixos

          if test -e result
            sudo rm -f result
          end
          if test -f .gitignore
            grep -q '^result$' .gitignore
            or echo "result" | sudo tee -a .gitignore >/dev/null
          else
            echo "result" | sudo tee .gitignore >/dev/null
          end
          sudo git rm -r --cached result 2>/dev/null
          or true

          echo ""
          echo "→ Schritt 1/5: flake.lock aktualisieren..."
          sudo nix flake update
          or return 1

          echo ""
          echo "→ Schritt 2/5: System bauen und aktivieren..."
          set old_system (readlink -f /run/current-system)
          sudo nixos-rebuild switch --flake /etc/nixos#(hostname) 2>&1 | nom
          if test $pipestatus[1] -ne 0
            echo "✗ Fehler beim Rebuild."
            return 1
          end

          echo ""
          echo "→ Schritt 3/5: Paketänderungen anzeigen..."
          nvd diff $old_system /run/current-system

          echo ""
          echo "→ Schritt 4/5: Nix-Store bereinigen..."
          sudo nix-collect-garbage --delete-older-than 30d
          or return 1

          echo ""
          echo "→ Schritt 5/5: Auf Codeberg sichern..."
          sudo git add -A
          sudo git commit -m "System aktualisiert "(date +%Y-%m-%d)
          or true
          sudo git push origin master
          and begin
            echo "✓ Fertig!"
            return 0
          end

          echo "✗ Push fehlgeschlagen. Bitte später erneut versuchen."
          return 1
        '';
      };

      pull-update = {
        description = "Konfiguration von Codeberg holen und System bauen";
        body = ''
          echo "──────────────────────────────────────"
          echo "  Git Pull + NixOS Rebuild + Cleanup  "
          echo "──────────────────────────────────────"

          cd /etc/nixos

          if test -e result
            sudo rm -f result
          end
          if test -f .gitignore
            grep -q '^result$' .gitignore
            or echo "result" | sudo tee -a .gitignore >/dev/null
          else
            echo "result" | sudo tee .gitignore >/dev/null
          end
          sudo git rm -r --cached result 2>/dev/null
          or true

          echo ""
          echo "→ Schritt 1/4: Konfiguration von Codeberg holen..."
          sudo git fetch origin
          or return 1
          sudo git reset --hard origin/master
          or return 1

          echo ""
          echo "→ Schritt 2/4: System bauen und aktivieren..."
          set old_system (readlink -f /run/current-system)
          sudo nixos-rebuild switch --flake /etc/nixos#(hostname) 2>&1 | nom
          if test $pipestatus[1] -ne 0
            echo "✗ Fehler beim Rebuild."
            return 1
          end

          echo ""
          echo "→ Schritt 3/4: Paketänderungen anzeigen..."
          nvd diff $old_system /run/current-system

          echo ""
          echo "→ Schritt 4/4: Nix-Store bereinigen..."
          sudo nix-collect-garbage --delete-older-than 30d
          or return 1

          echo ""
          echo "✓ Fertig!"
        '';
      };

    };
  };

  # ─────────────────────────────────────────
  # GNOME-Einstellungen via dconf
  # ─────────────────────────────────────────
  dconf.settings = {

    "org/gnome/desktop/interface" = {
      color-scheme = "default";
      cursor-theme = "Adwaita";
    };

    "org/gnome/desktop/wm/preferences" = {
      button-layout = "appmenu:minimize,maximize,close";
    };

    "org/gnome/settings-daemon/plugins/color" = {
      night-light-enabled            = false;
      night-light-schedule-automatic = false;
      night-light-schedule-from      = 22.0;
      night-light-schedule-to        = 7.0;
      night-light-temperature        = 4000;
    };

    "org/gnome/desktop/peripherals/touchpad" = {
      tap-to-click   = true;
      natural-scroll = true;
    };

    "org/gnome/mutter" = {
      experimental-features = [ "scale-monitor-framebuffer" ];
    };

    "org/gnome/shell" = {
      disable-user-extensions = false;
      enabled-extensions = [
        pkgs.gnomeExtensions.appindicator.extensionUuid
        "dash-to-dock@micxgx.gmail.com"
        "no-overview@fthx"
      ];
    };

  };
}

Schritt 3: flake.nix anpassen

Das ist der entscheidende Schritt. Bisher zeigt die flake.nix für alle Rechner auf dieselbe home.nix. Das muss für den Lenovo geändert werden – und gleichzeitig muss lenovo-system.nix als zusätzliches Modul eingebunden werden.

Dazu wandert home-manager.users.dein-username aus den gemeinsamen Modulen heraus und wird stattdessen je Rechner einzeln eingetragen. Bitte lies alle Kommentierungen in der Datei, damit müsste es klar werden.

Die flake.nix hier nur als Ausschnitt. Bitte beachten: ich habe überall meinen Usernamen und die Hostnamen meiner Rechner depersonalisiert - das sind viele Stellen an denen nun entweder "dein-username" oder "dein-hostname" steht - bitte detektivisch genau aufspüren.

sudo nano /etc/nixos/flake.nix

{
  outputs = { self, nixpkgs, nixpkgs-unstable, home-manager, ... }:
  let
    system = "x86_64-linux";
    pkgs-unstable = import nixpkgs-unstable {
      inherit system;
      config.allowUnfree = true;
    };

    # Gemeinsame Basis – gilt für alle Rechner.
    # home-manager.users.dein-username ist hier bewusst NICHT mehr enthalten,
    # da jeder Rechner seine eigene home.nix bekommt.
    common-modules = [
      ./configuration.nix
      home-manager.nixosModules.home-manager
      {
        home-manager.useGlobalPkgs            = true;
        home-manager.useUserPackages          = true;
        home-manager.backupFileExtension      = "backup";
        home-manager.extraSpecialArgs         = { inherit pkgs-unstable; };
        # ← home-manager.users.dein-username wurde hier entfernt
      }
    ];
  in {
    nixosConfigurations = {

      # Dell 5320 – unverändert, nutzt die gemeinsame home.nix
      dein-hostname = nixpkgs.lib.nixosSystem {
        inherit system;
        specialArgs = { inherit pkgs-unstable; };
        modules = common-modules ++ [
          ./hardware-configuration.nix
          { networking.hostName = "dein-hostname"; }
          { home-manager.users.dein-username = import ./home.nix; }  # gemeinsame home.nix
        ];
      };

      # Dell Pro Plus – unverändert, nutzt die gemeinsame home.nix
      dein-hostname = nixpkgs.lib.nixosSystem {
        inherit system;
        specialArgs = { inherit pkgs-unstable; };
        modules = common-modules ++ [
          ./hardware-configuration-dein-hostname.nix
          { networking.hostName = "dein-hostname"; }
          { home-manager.users.dein-username = import ./home.nix; }  # gemeinsame home.nix
        ];
      };

      # Lenovo – individuelle Konfiguration:
      # lenovo-system.nix überschreibt ausgewählte Systemeinstellungen,
      # home-lenovo.nix ersetzt die Benutzerumgebung komplett.
      lenovo = nixpkgs.lib.nixosSystem {
        inherit system;
        specialArgs = { inherit pkgs-unstable; };
        modules = common-modules ++ [
          ./hardware-configuration-lenovo.nix
          { networking.hostName = "dein-hostname"; }
          ./lenovo-system.nix                                        # ← Systemoverrides
          { home-manager.users.dein-username = import ./home-lenovo.nix; } # ← schlanke home.nix
        ];
      };

    };
  };
}


Schritt 4: Alle Dateien Git bekannt machen und bauen

Flakes ignorieren Dateien die Git nicht kennt – das kennen wir bereits. Daher erst registrieren:

cd /etc/nixos
sudo git add lenovo-system.nix home-lenovo.nix flake.nix
sudo git commit -m "Lenovo individualisiert: schlanke home.nix, Power Profiles Daemon"

Dann auf dem Lenovo einmalig manuell bauen:

sudo nixos-rebuild switch --flake /etc/nixos#lenovo

Und auf Codeberg sichern:

sudo git push origin master

Ab jetzt läuft auf dem Lenovo update-push und pull-update (direkt einmal danach ausführen) wie gewohnt.


Meine deklarativ gesetzte Wunsch-Konfiguration

Dell 5320 Dell Pro Plus Lenovo
Systemkonfiguration configuration.nix configuration.nix configuration.nix + lenovo-system.nix
Benutzerumgebung home.nix home.nix home-lenovo.nix
Energieverwaltung TLP TLP Power Profiles Daemon ✅
GNOME-Energieprofil-Button wirkungslos wirkungslos funktioniert ✅
LanguageTool (~2 GB RAM) ✅ läuft ✅ läuft ❌ gespart
Fotosoftware ❌ nicht nötig
mehrere Cloud-Dienste synchronisieren ❌ nicht nötig

Die Dell-Rechner laufen weiterhin völlig unverändert. In den gemeinsamen configuration.nix und home.nix wurde kein einziger Buchstabe angefasst. Alles was der Lenovo anders macht, steht in seinen eigenen zwei Dateien.

Dazu, cool wie immer: Änderungen pflege ich für meine ganze Flotte an dem Rechner, an dem ich zufällig gerade sitze - die flake.nix (bzw. flake.lock) im Zusammenspiel mit Codeberg verteilt es dann an alle anderen. Und Systempflege weiterhin mit nur einem einzigen Befehl.

Ich finde gerade heute zeigte sich NixOS mal wieder von seiner besten Seite. Es war zum Abschluss nochmal ein schönes Beispiel für das Potenzial des Paradigmenwechsels durch den konsequent und logisch durchdachten deklarativen Ansatz.

Abgesehen vom geringen Aufwand für die Systempflege gefällt mir nach den Monaten mit NixOS am besten, dass ich erstmals das Gefühl habe wirklich Herr meines Systems zu sein. Nichts versteckt sich hinter Eingabemasken oder Linux-Befehlen welche ich nicht genau entschlüsseln kann. Bei NixOS weiß ich ganz genau, an welcher Stelle in den wenigen Konfigurationsdatein welche Systemeinstellung beschrieben ist - in normalen Textdateien und in einer einheitlichen Sprache, welche über die Dokumentation (zentral, an einer Stelle - siehe Teil 2) gelernt und entschlüsselt werden kann.

So, das war's von meiner Seite. Aber das muss nicht alles gewesen sein. Wenn du spannende und hilfreiche Aspekte oder News zu NixOS weitergeben möchtest, dann setz dich gerne an die Tastatur und reiche deinen Artikelvorschlag direkt hier auf der Page unter "Mitschreiben" ein.

Ich würde mich sehr über konstruktive Kritik zu der NixOS-Reihe in den Kommentaren freuen! Gebt dort gerne auch eure eigenen Erfahrungen mit NixOS weiter.

Tschüss einstweilen!

Artikel der NixOS-Reihe

Tags

NixOS, Nix, Flakes, Nix-Store

Thomas
Geschrieben von Thomas am 12. Juni 2026 um 22:13

Lieber Reto, vielen Dank für diese NixOS-Reihe! Ich hoffe, dass ich bald die Zeit habe zwei alte Laptops (2011 und 2014) und zwei PC (2011 und 2020) so parallel einzurichten und zu administrieren. Nutzer und Anwendungsfälle gibt es in meiner jungen Familie reichlich für diese meist familiäre second Hand Technik) Nun sehe ich auch die Möglichkeit den Aufwand der Administration zu stemmen. Liebe Grüße