My Nix environment. Once you have Nix installed, the first step is to clone this repo:
nix-shell -p git gh --run "gh auth login && gh repo clone samestep/env ~/github/samestep/env"There are separate configurations for the two different machines I use.
This machine has an x86 CPU and an NVIDIA GPU, and runs NixOS.
Run these commands to setup the NixOS configuration:
cp /etc/nixos/hardware-configuration.nix ~/github/samestep/env/nixos/nixos/
sudo rm /etc/nixos/*
sudo ln -s ~/github/samestep/env/flake.nix /etc/nixos/flake.nix
sudo nixos-rebuild switch
sudo nix-channel --remove nixosThen run these commands to setup the Home Manager configuration:
ln -fsT ~/github/samestep/env ~/.config/home-manager
nix run ~/github/samestep/env#home-manager switchYou may need to log out and back in to see everything installed in the GNOME applications launcher.
The configuration also runs derper, a Tailscale DERP relay, so that the VMs below can talk to each other over the LAN instead of over the Internet. Each VM sits behind its own NAT and the home connection is behind CGNAT, so without a STUN server on the LAN, Tailscale never learns the VMs' LAN-side endpoints and relays every packet through a cloud DERP, up the slow 5G uplink and back. derper is pinned to the static address 192.168.12.10 declared in configuration.nix; on first start it mints a self-signed certificate for that address and logs the DERP-map entry to paste into the tailnet policy (add "DERPPort": 8443 to it, since derper listens on an unprivileged port and its logged entry omits the port):
journalctl -u derper | grep -A1 'Configure it in DERPMap'Add that entry to the tailnet policy file under derpMap, wrapped in a custom region (IDs 900–999 are reserved for these), leaving OmitDefaultRegions as false:
"derpMap": {
"OmitDefaultRegions": false,
"Regions": {
"900": {
"RegionID": 900,
"RegionCode": "home",
"RegionName": "Home LAN",
"Nodes": [
{
"Name": "home1",
"RegionID": 900,
"HostName": "192.168.12.10",
"IPv4": "192.168.12.10",
"IPv6": "none",
"DERPPort": 8443,
"CertName": "sha256-raw:..."
}
]
}
}
}From a VM at home, tailscale netcheck should then list Home LAN as the nearest DERP, and tailscale ping to another VM should settle on a direct 192.168.12.x address. Because the relay is only reachable on the LAN, a VM that is away from home currently cannot reach the VMs that are at home, only the other way around. If the NixOS machine ever loses that address, the VMs lose every path to each other; the tell is ping 192.168.12.10 failing from a VM while openssl s_client -connect <new-ip>:8443 still answers with a CN=192.168.12.10 certificate. Changes to the lan-wired profile take effect on reboot or with sudo nmcli connection up lan-wired, and the Wi-Fi profile must not also claim .10 (it once did, from a one-off nmcli con mod).
This machine has an Apple Silicon chip and runs macOS.
Enable flakes by making sure this line is present in /etc/nix/nix.conf:
experimental-features = nix-command flakes
Then run these commands to setup the Home Manager configuration:
rm -rf ~/.config/home-manager
ln -s ~/github/samestep/env ~/.config/home-manager
nix run ~/github/samestep/env#home-manager switchThis config can also be used for x86 Linux virtual machines on Linux. First make sure you have virt-manager, virt-viewer, and the libvirt NSS module installed, as they are in this repo's NixOS config. Then make sure you've started the default network:
virsh -c qemu:///system net-start defaultYou can also run this command so the default network starts automatically in the future:
virsh -c qemu:///system net-autostart defaultDownload an OS ISO like Ubuntu 26.04 and run this command to create a VM, tweaking the CPU/RAM/disk parameters as appropriate:
virt-install --connect qemu:///system --name sandbox-amd64 --vcpus 32 --memory 65536 --disk size=1000 --network network=default --cdrom ubuntu-26.04-live-server-amd64.isoAs a heads up, at time of writing, the only reason Ubuntu 26.04 works for me here is because I'm using an unreleased osinfo-db patch that adds support for it. You may need to use an image of an older OS instead.
That aside, here's what all the flags mean:
- the
--connectsetting makes thedefaultnetwork visible - the explicit
--nameis used by thelibvirt_guestNSS module for SSH --vcpusallows the VM to use all the cores instead of just two--memoryis in MiB--disk sizeis in GB- the
--networksetting is necessary for SSH to work after installation
When installing Ubuntu, in the "Storage configuration" step, increase the size of the ubuntu-lv device from 100.000G to the maximum allowed, which will depend on how much disk space you gave it. Then use these options in the "Profile configuration" step:
- Your name:
Agent - Your servers name:
sandbox-amd64 - Pick a username:
agent-amd64 - Choose a password:
password - Confirm your password:
password
Check the "Install OpenSSH server" box in the "SSH configuration" step. Then once installation is finished, ignore the message saying to remove the installation medium, and just hit ENTER to reboot.
After rebooting, you can close the virt-viewer window; you won't need it again. Next, give the VM your public SSH key so you don't need to type the password when connecting:
ssh-copy-id agent-amd64@sandbox-amd64Now connect using SSH:
ssh agent-amd64@sandbox-amd64The only reason for choosing a password at all was because the Ubuntu installer forces you to; first step after installation is to enable passwordless sudo:
echo "agent-amd64 ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/agent && sudo chmod 0440 /etc/sudoers.d/agentThe next step is to reconfigure chrony so that it can readjust the VM's clock if it becomes wrong e.g. if the host machine reboots. Make this edit to /etc/chrony/chrony.conf:
-makestep 1 3
+makestep 1 -1Note that the above may be unnecessary if you configure libvirt to shut down VMs when you shut down the host machine. That may be a good idea in general, since if libvirt only suspends and resumes the VMs when the host machine reboots, their DHCP leases can disappear from the host, forcing you to use an explicit IP address for SSH.
Then install Nix:
sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemonEnable flakes:
echo 'experimental-features = nix-command flakes' | sudo tee -a /etc/nix/nix.confAfter installing Nix you'll need to log back out and back in. Then clone this repo:
git clone https://github.com/samestep/env.git ~/github/samestep/envAnd set up the Home Manager symlink:
mkdir ~/.config && ln -fsT ~/github/samestep/env ~/.config/home-managerAnd finally set up the Home Manager config itself:
nix run ~/github/samestep/env#home-manager -- switch -b backupAs an optional followup step, install Tailscale to be able to talk to other VMs; installing via Home Manager doesn't work properly on Ubuntu, so just use the official installer:
curl -fsSL https://tailscale.com/install.sh | shConnect to the tailnet:
sudo tailscale up --ssh --hostname=sandbox-amd64And run this repo's script to generate ~/.ssh/tailnet:
tailnetThis config can be used for ARM Linux virtual machines on macOS, via Tart which comes with the host-side macOS config in this repo. First, download an Ubuntu image:
tart clone ghcr.io/cirruslabs/ubuntu:latest ubuntuBy default, Tart doesn't give the VM all CPU cores, and only gives 8 GiB of RAM and 50 GB of disk space, so adjust those as appropriate:
tart set ubuntu --cpu 18 --memory 32768 --disk-size 2000Start up the VM, optionally using the --nested flag to enable KVM:
tart run --no-graphics --nested ubuntuLeave that running and, in a different terminal, give the VM your public SSH key give the VM your public SSH key so you don't need to type the password each time you connect:
ssh-copy-id admin@$(tart ip ubuntu)While adding the SSH key, you will need to type the password, which is admin. Then SSH into the VM:
ssh admin@$(tart ip ubuntu)Avoid ten-second hangs by enabling Ubuntu to resolve the local hostname:
echo "127.0.1.1 $(hostname)" | sudo tee -a /etc/hostsNext, install Nix:
sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemonEnable flakes:
echo 'experimental-features = nix-command flakes' | sudo tee -a /etc/nix/nix.confAfter installing Nix you'll need to log back out and back in. Then clone this repo:
git clone https://github.com/samestep/env.git ~/github/samestep/envMake a symlink for Home Manager:
mkdir -p ~/.config && ln -fsT ~/github/samestep/env ~/.config/home-managerAnd activate the config:
nix run ~/github/samestep/env#home-manager -- switch -b backupAs an optional followup step, install Tailscale to be able to talk to other VMs; installing via Home Manager doesn't work properly on Ubuntu, so just use the official installer:
curl -fsSL https://tailscale.com/install.sh | shConnect to the tailnet:
sudo tailscale up --ssh --hostname=ubuntuAnd run this repo's script to generate ~/.ssh/tailnet:
tailnetThis config can be used for macOS VMs, again using Tart. First, download a macOS image:
tart clone ghcr.io/cirruslabs/macos-tahoe-vanilla:latest tahoe-vanillaExpand the provided CPU cores, RAM, and disk space:
tart set tahoe-vanilla --cpu 18 --memory 16384 --disk-size 1000Next follow the steps to finish resizing the disk of a macOS Tart VM, starting by booting in recovery mode:
tart run --recovery tahoe-vanillaChoose Options, then open the Terminal under Utilities. Delete the preexisting recovery partition:
diskutil eraseVolume free free disk0s3Repair the disk:
yes | diskutil repairDisk disk0And resize the system Apple File System container to use the new disk space:
diskutil apfs resizeContainer disk0s2 0Shut down the VM, then reboot it:
tart run tahoe-vanillaSince we're using the vanilla image, we still need to install the Xcode Command Line Tools:
xcode-select --installThat should pop up a dialogue which you need to accept. You'll also want to allow Ghostty's environment forwarding over SSH:
echo AcceptEnv COLORTERM TERM_PROGRAM TERM_PROGRAM_VERSION | sudo tee /etc/ssh/sshd_config.d/101-color.confNow shut down the VM again and reboot it once more, this time without graphics:
tart run --no-graphics tahoe-vanillaIn a different terminal, give the VM your public SSH key, using the password admin:
ssh-copy-id admin@$(tart ip tahoe-vanilla)Then SSH into the VM:
ssh admin@$(tart ip tahoe-vanilla)curl -fsSL https://install.determinate.systems/nix | sh -s -- installYou may need to start a new shell. Clone this repo:
git clone https://github.com/samestep/env.git ~/github/samestep/envSet up the Home Manager symlink:
mkdir -p ~/.config && ln -s ~/github/samestep/env ~/.config/home-managerAnd finally activate the Home Manager config:
nix run ~/github/samestep/env#home-manager switchAs an optional followup step, activate Tailscale to let other VMs connect to this one; the Home Manager config provides the open-source tailscaled variant since that's the only macOS one with an SSH server, but it still needs to be registered with launchd:
sudo "$(command -v tailscaled)" install-system-daemonConnect to the tailnet:
sudo tailscale up --ssh --hostname=tahoe-vanillaNote that, without additional setup, this VM can only receive Tailscale SSH connections, and cannot SSH into other VMs on the tailnet.