Skip to content

Per Machine Config

NullString1 edited this page Jan 13, 2026 · 1 revision

Per-Machine Configuration

NullOS makes it easy to manage multiple machines with shared configurations while allowing per-machine customization.

Overview

NullOS supports multiple machines through:

  1. variables.nix - Per-machine variables (gitignored)
  2. Hardware configurations - Machine-specific hardware settings
  3. Conditional logic - Load configs based on hostname
  4. Shared home-manager - Same user config across machines

Current Machine Configurations

NullOS includes configurations for:

nslapt - Laptop Configuration

Hardware: Laptop with hybrid graphics (Intel + NVIDIA)

  • Uses NVIDIA PRIME
  • Power management optimizations
  • Laptop-specific settings

File: modules/system/hardware_nslapt.nix

nspc - Desktop Configuration

Hardware: Desktop workstation

  • Dedicated NVIDIA GPU
  • No power management restrictions
  • Desktop-specific optimizations

File: modules/system/hardware_nspc.nix


Setting Up a New Machine

Step 1: Generate Hardware Configuration

On the new machine, generate hardware config:

nixos-generate-config --show-hardware-config > /tmp/hardware.nix

Step 2: Create Hardware Module

Create modules/system/hardware_newmachine.nix:

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

{
  imports = [
    (modulesPath + "/installer/scan/not-detected.nix")
  ];

  # Copy from generated hardware.nix
  boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "sd_mod" ];
  boot.kernelModules = [ "kvm-intel" ];
  
  # Filesystems - adjust to your setup
  fileSystems."/" = {
    device = "/dev/disk/by-uuid/your-uuid";
    fsType = "ext4";
  };
  
  fileSystems."/boot" = {
    device = "/dev/disk/by-uuid/your-boot-uuid";
    fsType = "vfat";
  };
  
  swapDevices = [{
    device = "/dev/disk/by-uuid/your-swap-uuid";
  }];
  
  # Machine-specific settings
  networking.hostName = "newmachine";
  
  nixpkgs.hostPlatform = "x86_64-linux";
}

Step 3: Add to Flake

Edit flake.nix and add new configuration:

nixosConfigurations = {
  nslapt = nixpkgs.lib.nixosSystem {
    # existing config...
  };
  
  nspc = nixpkgs.lib.nixosSystem {
    # existing config...
  };
  
  # Add your new machine
  newmachine = nixpkgs.lib.nixosSystem {
    inherit system;
    specialArgs = {
      inherit vars;
      inherit inputs;
      inherit home-manager;
    };
    pkgs = import nixpkgs {
      inherit system;
      overlays = overlays;
      config = {
        allowUnfree = true;
        android_sdk.accept_license = true;
      };
    };
    modules = [
      inputs.home-manager.nixosModules.home-manager
      {
        home-manager.useUserPackages = true;
        home-manager.useGlobalPkgs = true;
        home-manager.extraSpecialArgs = {
          inherit vars inputs;
        };
        home-manager.users.${vars.username} = import ./home/default.nix;
      }
      inputs.stylix.nixosModules.stylix
      ./modules/system/hardware_newmachine.nix  # Your hardware config
      ./modules/default.nix
    ];
  };
};

Step 4: Create Variables

Copy and customize variables.nix:

cp variables.nix.example variables.nix

Edit with machine-specific values:

{
  hostname = "newmachine";
  username = "yourusername";
  # ... other settings
}

Step 5: Build and Switch

sudo nixos-rebuild switch --flake .#newmachine

Sharing Configurations Across Machines

Method 1: Git Repository

Keep your NullOS configuration in Git:

# On Machine 1
cd ~/NullOS
git init
git add .
git commit -m "Initial NullOS config"
git remote add origin git@github.com:yourusername/NullOS.git
git push -u origin main
# On Machine 2
git clone git@github.com:yourusername/NullOS.git
cd NullOS
cp variables.nix.example variables.nix
# Edit variables.nix for this machine
sudo nixos-rebuild switch --flake .#machine2

Method 2: Separate Variables Per Machine

Keep multiple variables-*.nix files:

NullOS/
├── variables-laptop.nix
├── variables-desktop.nix
└── variables-server.nix

Symlink the active one:

ln -sf variables-laptop.nix variables.nix

Or specify in flake:

let
  vars = if hostname == "laptop" 
         then import ./variables-laptop.nix { inherit nixpkgs; }
         else import ./variables-desktop.nix { inherit nixpkgs; };
in

Machine-Specific Customization

Conditional Configuration

Use hostname to conditionally enable features:

# modules/software/packages.nix
{ config, pkgs, vars, ... }:

{
  environment.systemPackages = with pkgs; [
    # Common packages
    vim
    git
    htop
  ] ++ lib.optionals (vars.hostname == "nslapt") [
    # Laptop-only packages
    powertop
    tlp
  ] ++ lib.optionals (vars.hostname == "nspc") [
    # Desktop-only packages
    blender
    davinci-resolve
  ];
}

Hardware-Specific Settings

# modules/system/power.nix
{ config, vars, ... }:

{
  # Only enable power management on laptops
  services.tlp.enable = (vars.hostname == "nslapt");
  
  powerManagement = {
    enable = true;
    powertop.enable = (vars.hostname == "nslapt");
  };
}

Per-Machine Services

# modules/services/services.nix
{ config, vars, ... }:

{
  services = {
    # Enable on all machines
    tailscale.enable = true;
    
    # Desktop only
    sunshine.enable = (vars.hostname == "nspc");
    
    # Laptop only
    thermald.enable = (vars.hostname == "nslapt");
  };
}

Home Manager Per-Machine

Home Manager configuration is shared by default, but you can customize:

Method 1: Conditional in Home Config

# home/default.nix
{ config, pkgs, vars, ... }:

{
  home.packages = with pkgs; [
    # Common packages
    firefox
    vscode
  ] ++ lib.optionals (vars.hostname == "nslapt") [
    # Laptop packages
    battery-notifier
  ] ++ lib.optionals (vars.hostname == "nspc") [
    # Desktop packages
    obs-studio
  ];
}

Method 2: Separate Home Configs

Create machine-specific home configs:

home/
├── common.nix       # Shared config
├── nslapt.nix      # Laptop-specific
└── nspc.nix        # Desktop-specific
# home/nslapt.nix
{ config, pkgs, ... }:

{
  imports = [ ./common.nix ];
  
  # Laptop-specific overrides
  home.packages = with pkgs; [
    powertop
  ];
}

Update flake to use machine-specific home config:

home-manager.users.${vars.username} = 
  if vars.hostname == "nslapt"
  then import ./home/nslapt.nix
  else import ./home/nspc.nix;

Managing Secrets Per-Machine

Using Environment Variables

# modules/services/backup.nix
{ config, vars, ... }:

let
  # Different repos per machine
  backupRepo = {
    nslapt = "sftp:user@server:/backups/laptop";
    nspc = "sftp:user@server:/backups/desktop";
  }.${vars.hostname};
in
{
  services.restic.backups.daily = {
    repository = backupRepo;
    passwordFile = "/etc/nixos/secrets/restic-password";
  };
}

Using agenix

For advanced secret management, consider agenix:

# In flake inputs
inputs.agenix.url = "github:ryantm/agenix";

# Per-machine secrets
secrets = {
  laptop-vpn = {
    file = ./secrets/laptop-vpn.age;
    owner = vars.username;
  };
  desktop-ssh = {
    file = ./secrets/desktop-ssh.age;
  };
};

Specialisations

Create boot-time variants for the same machine:

# modules/system/hardware_nslapt.nix
{ config, pkgs, ... }:

{
  # Default: Hybrid graphics
  
  specialisation = {
    nvidia-only.configuration = {
      # NVIDIA dedicated mode
      hardware.nvidia.prime.offload.enable = false;
      services.xserver.videoDrivers = [ "nvidia" ];
    };
    
    intel-only.configuration = {
      # Integrated graphics only
      hardware.nvidia.prime.offload.enable = false;
      boot.blacklistedKernelModules = [ "nvidia" "nvidia_drm" "nvidia_modeset" ];
    };
  };
}

Select at boot from GRUB menu.


Syncing Configuration Changes

Pull Updates on All Machines

# Machine 1: Make changes
cd ~/NullOS
# ... edit configs ...
git add .
git commit -m "Update waybar config"
git push

# Machine 2: Pull changes
cd ~/NullOS
git pull
sudo nixos-rebuild switch --flake .#machine2

Different Branches Per Machine

# Laptop-specific changes
git checkout -b laptop
# ... make laptop changes ...
git commit -m "Laptop-specific tweaks"

# Desktop-specific changes
git checkout -b desktop
# ... make desktop changes ...
git commit -m "Desktop optimizations"

# Shared changes - merge to main
git checkout main
git merge laptop
git merge desktop

Testing on Multiple Machines

Build Remotely

Build for another machine without switching:

# On any machine, build config for nspc
nix build .#nixosConfigurations.nspc.config.system.build.toplevel

Deploy to Remote Machine

Using nixos-rebuild over SSH:

nixos-rebuild switch --flake .#nspc --target-host user@nspc --build-host localhost

Example Multi-Machine Setup

Directory Structure

NullOS/
├── flake.nix
├── variables.nix (gitignored, machine-specific)
├── variables-laptop.nix.example
├── variables-desktop.nix.example
├── variables-server.nix.example
├── home/
│   ├── common/          # Shared configs
│   ├── laptop/          # Laptop-specific
│   └── desktop/         # Desktop-specific
└── modules/
    └── system/
        ├── hardware_laptop.nix
        ├── hardware_desktop.nix
        └── hardware_server.nix

Flake Configuration

let
  mkSystem = hostname: nixpkgs.lib.nixosSystem {
    inherit system;
    specialArgs = {
      vars = import ./variables.nix { inherit nixpkgs; };
      inherit inputs home-manager;
    };
    modules = [
      ./modules/system/hardware_${hostname}.nix
      ./modules/default.nix
      # ... rest of config
    ];
  };
in
{
  nixosConfigurations = {
    laptop = mkSystem "laptop";
    desktop = mkSystem "desktop";
    server = mkSystem "server";
  };
}

Best Practices

  1. Keep variables.nix out of Git - Use .gitignore
  2. Provide example files - variables-*.nix.example
  3. Document machine differences - Comment your hardware configs
  4. Use conditional logic sparingly - Only for necessary differences
  5. Test before deploying - Build configs before switching
  6. Keep commits atomic - Separate machine-specific from shared changes
  7. Use branches for experiments - Test big changes on one machine first

Troubleshooting

"Hostname mismatch"

Ensure variables.nix hostname matches:

  • Flake configuration name
  • Hardware config networking.hostName

"Cannot find hardware configuration"

Check that hardware_${hostname}.nix exists and is imported in flake.

"Variables not found"

Ensure variables.nix exists in root directory:

ls -la variables.nix

Next Steps

Clone this wiki locally