complete-computing-environment/morph.org

23 KiB
Raw Permalink Blame History

Morph for managing NixOS

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]
# target = "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 roam: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 roam:Framework Laptop.

[endpoints.hosts.virtuous-cassette]
stateVersion = "23.05"
target = "192.168.69.71"
user = "root"

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";

  # ===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]
# target = "tres-ebow"
target = "192.168.69.69"
user = "root"
# will probably reinstall soon
stateVersion = "23.05"

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;
}