Morph is a tool for managing existing NixOS hosts - basically a fancy wrapper around nix-build, nix copy, nix-env, nix/store…/bin/switch-to-configuration, scp and more. Morph supports updating multiple hosts in a row, and with support for health checks makes it fairly safe to do so.
Interestingly, it seems like I can just use my NixOps laptop profile…? stealin' it! that's nice.
In the embeds you'll learn how I pick the hostnames for my computers.
Deploying from my hosts.toml
Morph is fine to use, but it's a little bit unergonomic, especially
if i want to blast out builds to a bunch of hosts. I am taking a cue
from Xe Iaso and moving toward
defining my host configurations in a hosts.toml
file. For now, it only has the bare
necessities to generate deployment networks for each of my roles, but it
could be extended with other things like SSH host keys, or encrypted
secrets in the near future. I'm also planning to write a little python
script so that I can type to my computer deploy virtuous-cassette
and have that roll out
rather than the complicated Shell
Spell morph deploy --on=virtuous-cassette --passwd ~/arroyo-nix/networks/laptops.nix switch
.
{ pkgs, networks }:
let
mkHost = rollConfig: hostname: config:
let hostConfig = ./. + "/../hosts/${hostname}";
in
{
imports = [ rollConfig hostConfig ];
deployment.targetHost = (if config ? target then config.target else hostname);
deployment.targetUser = (if config ? user then config.user else "rrix");
} // (if config ? stateVersion then {
system.stateVersion = config.stateVersion;
} else {});
mkNetwork = subnet:
let
network = networks."${subnet}";
roleConfig = ./. + "/${network.config}";
mkHost' = mkHost roleConfig;
in
{
network.pkgs = pkgs;
network.description = network.description;
network.enableRollback = (if network ? enableRollback then network.enableRollback else true);
} // builtins.mapAttrs mkHost' network.hosts;
in mkNetwork
this mkNetwork
function is easy to
operate as you can see below; it provides reasonable defaults so that my
Tailscale-backed hosts can just be added to the network with a single
line. Bootstrapping hosts is as simple as adding the local DHCP address
as the target
key and setting the user
for the first SSH.
Deploying My Laptops and Desktop
My laptops are installed through my NixOS Automatic Partitioning Installer and carry My NixOS configuration for laptops, the "endpoint configuration".
let
pkgs = import <nixpkgs> { allowUnfree = true; };
allNetworks = pkgs.lib.importTOML ./hosts.toml;
mkNetwork = import ./mkNetwork.nix { inherit pkgs; networks = allNetworks; };
in mkNetwork "endpoints"
[endpoints]
description = "my laptops and desktop"
enableRollback = true
config = "../roles/endpoint"
Rose Quine
Rose Quine is my GPD Pocket 3.
[endpoints.hosts.rose-quine]
stateVersion = "23.05"
{ config, pkgs, lib, ... }:
{
imports = [ <arroyo/nixos/gpd-pocket-3.nix> ];
networking.hostName = "rose-quine";
system.stateVersion = "23.05";
services.xserver.dpi = 280;
services.tailscale.authKey = "tskey-auth-knLBN35CNTRL-ignbakuis45bC5m5mrvX95o4DW9JHoRV8";
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.grub.efiSupport = true;
boot.loader.grub.device = "nodev";
boot.loader.grub.enable = true;
boot.loader.grub.gfxmodeBios = "1200x1920x32";
boot.loader.systemd-boot.enable = lib.mkForce false;
# boot.loader.systemd-boot.consoleMode = "max";
networking.hostId = "3f5dbbf9"; # required for zfs use
boot.zfs.devNodes = "/dev/mapper"; # (ref:devNodes)
boot.initrd.luks.devices = {
"swap" = { name = "swap"; device = "/dev/nvme0n1p2"; preLVM = true; };
"root" = { name = "root"; device = "/dev/nvme0n1p3"; preLVM = true; };
};
# === from hardware-configuration.nix
boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" "usbhid" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "tank/root";
fsType = "zfs";
};
fileSystems."/home" =
{ device = "tank/home";
fsType = "zfs";
};
fileSystems."/nix" =
{ device = "tank/nix";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/9FDC-2C40";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/455bbc40-e01c-4137-b593-a05b6220ce6b"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.enp175s0.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp174s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
}
NEXT derivation for umpc-display-rotate.c
NEXT split and document all this out in to an import on GPD Pocket 3 Support page
Window Smoke
[endpoints.hosts.window-smoke]
# target = "window-smoke"
# stateVersion = "22.11"
# user = "rrix"
Window Smoke is my desktop. It runs my Endpoint Configuration and some of My NixOS Tower Customizations
{ lib, config, ... }:
{
imports = [ ./hardware-configuration.nix ../../roles/desktop ];
boot.enableVFIO = false;
networking.hostName = "window-smoke";
system.stateVersion = "22.11"; #
# boot.loader.grub.efiInstallAsRemovable = true;
# boot.loader.grub.efiSupport = true;
# boot.loader.grub.device = "nodev";
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.systemd-boot.enable = true;
services.xserver.dpi = 110;
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" "wl" ];
boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ];
services.tailscale.authKey = "tskey-auth-k38z9b3CNTRL-DeWdeU2Zt4ccxM2RqHduzbu9h2D7mmP74";
networking.hostId = "141e1b4f"; # required for zfs use
boot.zfs.devNodes = lib.mkForce "/dev/disk/by-id/";
boot.initrd.luks.devices = {
"swap" = { name = "swap"; device = "/dev/nvme0n1p2"; preLVM = true; };
"root" = { name = "root"; device = "/dev/nvme0n1p3"; preLVM = true; };
};
fileSystems."/" =
{ device = "window-smoke/root";
fsType = "zfs";
};
fileSystems."/home" =
{ device = "window-smoke/home";
fsType = "zfs";
};
fileSystems."/nix" =
{ device = "window-smoke/nix";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/12CA-451F";
fsType = "vfat";
};
fileSystems."/media" =
{ device = "tank/media";
fsType = "zfs";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/26776a6d-4e53-4e39-b0e5-5a540ce78406"; }
];
}
Virtuous Cassette
Virtuous Cassette is my Framework Laptop.
[endpoints.hosts.virtuous-cassette]
stateVersion = "23.05"
hosts/tres-ebow/default.nix
replaces
the generated.nix
, basically, for my GPD Pocket:
{
imports = [ <arroyo/nixos/framework-laptop.nix> ];
networking.hostName = "virtuous-cassette";
boot.loader.grub.efiSupport = true;
boot.loader.grub.device = "nodev";
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.grub.enable = true;
boot.loader.systemd-boot.enable = false;
boot.initrd.luks.devices = {
"swap" = { name = "swap"; device = "/dev/nvme0n1p2"; preLVM = true; };
"root" = { name = "root"; device = "/dev/nvme0n1p3"; preLVM = true; };
};
boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
powerManagement.cpuFreqGovernor = "powersave";
hardware.enableRedistributableFirmware = true;
services.udev.extraRules = ''
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="18d1|096e", ATTRS{idProduct}=="5026|0858|085b", TAG+="uaccess", GROUP="plugdev"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0407", TAG+="uaccess", GROUP="plugdev", MODE="0660"
'';
# ===8<--- everything below here will change if i ever reinitialize the host!
services.tailscale.authKey = "tskey-auth-k1WxJ97CNTRL-6Rp5sqDZxM1yAH7mvKp9T1dj1Ps4iKYDY";
networking.hostId = "291fe33d"; # required for zfs use
fileSystems."/" =
{ device = "host/root";
fsType = "zfs";
};
fileSystems."/home" =
{ device = "host/home";
fsType = "zfs";
};
fileSystems."/media" =
{ device = "host/landfill";
fsType = "zfs";
};
fileSystems."/nix" =
{ device = "host/nix";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/CD54-B840";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/ddfce221-2d29-4882-9c66-1669ea60bc49"; }
];
}
Meadow Crush
Meadow Crush is my GPD Pocket 2; I don't use this right now but it's still running a viable NixOS if I need it in a Situation.
# [endpoints.hosts.meadow-crush]
# target = "meadow-crush"
# stateVersion = "22.05"
{
imports = [ ./hardware-configuration.nix ../../nixos/gpd-pocket.nix ];
networking.hostName = "meadow-crush";
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
services.tailscale.authKey = "tskey-kqvV5P3CNTRL-K3bdvSJcUreG8nrGcDKXCh";
networking.hostId = "c9ec7cad"; # required for zfs use
boot.initrd.luks.devices = {
"swap" = { name = "swap"; device = "/dev/mmcblk0p2"; preLVM = true; };
"root" = { name = "root"; device = "/dev/mmcblk0p3"; preLVM = true; };
};
fileSystems."/mnt/music" =
{ device = "/dev/disk/by-label/muzak";
fsType = "ext4";
noCheck = true;
};
}
NEXT implement nixos encrypted secrets and make these safe! maybe hosts.toml for a lot of this too…
Deploying My NixOS Set Top Box
let
pkgs = import <nixpkgs> { allowUnfree = true; };
allNetworks = pkgs.lib.importTOML ./hosts.toml;
mkNetwork = import ./mkNetwork.nix { inherit pkgs; networks = allNetworks; };
in mkNetwork "settop"
[settop]
description = "my kodi box"
enableRollback = true
config = "../roles/settop"
Tres Ebow
[settop.hosts.tres-ebow]
Tres Ebow is my Thinkpad Yoga gen 3 – a decent 2-in-1 with very un-Lenovo serviceability, and due to ordering error and soldered RAM, only 4 GiB of RAM. awkward. it'll be a fine kodi box.
{ config, lib, ... }:
{
networking.hostName = "tres-ebow";
boot.loader.grub.efiSupport = true;
boot.loader.grub.device = "nodev";
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.grub.enable = true;
boot.loader.systemd-boot.enable = false;
services.xserver.dpi = 207;
networking.hostId = "389acda5"; # required for zfs use
boot.zfs.devNodes = lib.mkForce "/dev/disk/by-uuid"; # (ref:devNodes)
services.tailscale.authKey = "tskey-auth-kjuYea5CNTRL-YApNAAdxe5aucWNb823g1aNCwTK11pVTA";
boot.initrd.availableKernelModules = [ "xhci_pci" "nvme" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "host/root";
fsType = "zfs";
};
fileSystems."/home" =
{ device = "host/home";
fsType = "zfs";
};
fileSystems."/nix" =
{ device = "host/nix";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/CB62-8263";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/4f1751ef-0ddd-4005-b69c-daafc518e9df"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.enp0s31f6.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp2s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
powerManagement.cpuFreqGovernor = lib.mkDefault "balanced";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
hardware.enableRedistributableFirmware = true;
}
Deploying The Wobserver
let
pkgs = import <nixpkgs> { allowUnfree = true; };
allNetworks = pkgs.lib.importTOML ./hosts.toml;
mkNetwork = import ./mkNetwork.nix { inherit pkgs; networks = allNetworks; };
in mkNetwork "wobserver"
[wobserver]
description = "the wobserver and friends"
enableRollback = true
config = "../roles/server"
Terra Firma
Terra Firma is my Wobserver hosted by Wobscale Technologies in Seattle, WA.
{
imports = [ ./hardware-configuration.nix ];
system.stateVersion = "22.11";
networking.hostName = "terra-firma";
boot.loader.grub.enable = true;
# boot.loader.grub.device = "/dev/sde";
boot.loader.grub.device = "/dev/sdf";
networking.hostId = "628c9fc3"; # required for zfs use
services.tailscale.authKey = "tskey-auth-kc6ULA7CNTRL-DwkDu5vJo2RrekxqbUHNxQP4LmMDnRjS3";
}
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "ehci_pci" "ata_piix" "uhci_hcd" "xhci_pci" "usb_storage" "usbhid" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "terra-firma/root";
fsType = "zfs";
};
fileSystems."/home" =
{ device = "tank/home";
fsType = "zfs";
};
fileSystems."/media" =
{ device = "tank/media";
fsType = "zfs";
};
fileSystems."/srv" =
{ device = "tank/srv";
fsType = "zfs";
};
fileSystems."/nix" =
{ device = "terra-firma/nix";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/2C1E-582F";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/1ee46640-6164-4882-a59d-aa260c7780a2"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.eno1.useDHCP = lib.mkDefault true;
# networking.interfaces.eno2.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
Last Bank
Last Bank is my New Homelab Build, a living-room server that will be proxied through Wobscale Technologies in Seattle, WA. It's going to replace terra-firma.
[wobserver.hosts.last-bank]
{ lib, config, ... }:
{
imports = [ ../../roles/desktop ];
boot.enableVFIO = true;
networking.hostName = "last-bank";
system.stateVersion = "23.05";
boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/disk/by-id/wwn-0x5000c5005d11c7e4";
services.tailscale.authKey = "tskey-auth-kzWZMt1CNTRL-48JC1bwTin5b1crXxBcti5Qru3zf8wC3";
networking.hostId = "56c334f2"; # required for zfs use
boot.zfs.devNodes = "/dev/disk/by-uuid"; # (ref:devNodes)
boot.initrd.availableKernelModules = [ "ehci_pci" "ahci" "isci" "usbhid" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "host/root";
fsType = "zfs";
};
fileSystems."/nix" =
{ device = "host/nix";
fsType = "zfs";
};
fileSystems."/home" =
{ device = "tank/home";
fsType = "zfs";
};
fileSystems."/media" =
{ device = "tank/media";
fsType = "zfs";
};
fileSystems."/srv" =
{ device = "tank/srv";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/19C9-747A";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/554d8e90-f4ea-49dc-b057-c69d0385bbc6"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.eno1.useDHCP = lib.mkDefault true;
# networking.interfaces.eno2.useDHCP = lib.mkDefault true;
# networking.interfaces.eno3.useDHCP = lib.mkDefault true;
# networking.interfaces.eno4.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}