-
Notifications
You must be signed in to change notification settings - Fork 0
Per Machine Config
NullOS makes it easy to manage multiple machines with shared configurations while allowing per-machine customization.
NullOS supports multiple machines through:
-
variables.nix- Per-machine variables (gitignored) - Hardware configurations - Machine-specific hardware settings
- Conditional logic - Load configs based on hostname
- Shared home-manager - Same user config across machines
NullOS includes configurations for:
Hardware: Laptop with hybrid graphics (Intel + NVIDIA)
- Uses NVIDIA PRIME
- Power management optimizations
- Laptop-specific settings
File: modules/system/hardware_nslapt.nix
Hardware: Desktop workstation
- Dedicated NVIDIA GPU
- No power management restrictions
- Desktop-specific optimizations
File: modules/system/hardware_nspc.nix
On the new machine, generate hardware config:
nixos-generate-config --show-hardware-config > /tmp/hardware.nixCreate 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";
}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
];
};
};Copy and customize variables.nix:
cp variables.nix.example variables.nixEdit with machine-specific values:
{
hostname = "newmachine";
username = "yourusername";
# ... other settings
}sudo nixos-rebuild switch --flake .#newmachineKeep 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 .#machine2Keep multiple variables-*.nix files:
NullOS/
├── variables-laptop.nix
├── variables-desktop.nix
└── variables-server.nix
Symlink the active one:
ln -sf variables-laptop.nix variables.nixOr specify in flake:
let
vars = if hostname == "laptop"
then import ./variables-laptop.nix { inherit nixpkgs; }
else import ./variables-desktop.nix { inherit nixpkgs; };
inUse 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
];
}# 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");
};
}# 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 configuration is shared by default, but you can customize:
# 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
];
}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;# 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";
};
}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;
};
};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.
# 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# 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 desktopBuild for another machine without switching:
# On any machine, build config for nspc
nix build .#nixosConfigurations.nspc.config.system.build.toplevelUsing nixos-rebuild over SSH:
nixos-rebuild switch --flake .#nspc --target-host user@nspc --build-host localhostNullOS/
├── 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
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";
};
}-
Keep
variables.nixout of Git - Use.gitignore -
Provide example files -
variables-*.nix.example - Document machine differences - Comment your hardware configs
- Use conditional logic sparingly - Only for necessary differences
- Test before deploying - Build configs before switching
- Keep commits atomic - Separate machine-specific from shared changes
- Use branches for experiments - Test big changes on one machine first
Ensure variables.nix hostname matches:
- Flake configuration name
- Hardware config
networking.hostName
Check that hardware_${hostname}.nix exists and is imported in flake.
Ensure variables.nix exists in root directory:
ls -la variables.nix- Variables Reference - All available variables
- Customization Guide - How to customize configs
- File Structure - Understanding the layout