From 43e13334c077e37e4b4e382056cf7054e8d69587 Mon Sep 17 00:00:00 2001 From: Swarsel Date: Tue, 24 Dec 2024 12:38:23 +0100 Subject: [PATCH] feat: option for unencrypted Impermanence --- SwarselSystems.org | 232 +++++++++++++------------ hosts/nixos/nbl-imba-2/default.nix | 1 + hosts/nixos/toto/default.nix | 11 +- hosts/nixos/toto/disk-config.nix | 130 +++++++------- modules/nixos/setup.nix | 1 + profiles/common/nixos/impermanence.nix | 32 ++-- profiles/common/nixos/sops.nix | 2 +- scripts/bootstrap.sh | 3 +- 8 files changed, 216 insertions(+), 196 deletions(-) diff --git a/SwarselSystems.org b/SwarselSystems.org index 839c968..ab2f82f 100644 --- a/SwarselSystems.org +++ b/SwarselSystems.org @@ -1285,6 +1285,7 @@ My work machine. Built for more security, this is the gold standard of my config hasFingerprint = true; impermanence = false; isBtrfs = true; + isCrypted = true; }; home-manager.users.swarsel.swarselsystems = { @@ -1612,102 +1613,6 @@ My server setup was originally built on Proxmox VE; back when I started, I creat I have removed most of the machines from this section. What remains are some hosts that I have deployed on OCI (mostly sync for medium-important data) and one other machine that I left for now as a reference. -**** Toto (QEMU VM) - -#+begin_src nix :tangle hosts/nixos/toto/default.nix - { self, inputs, outputs, config, pkgs, lib, ... }: - let - profilesPath = "${self}/profiles"; - in - { - - imports = [ - inputs.disko.nixosModules.disko - "${self}/hosts/nixos/toto/disk-config.nix" - { - _module.args = { - withSwap = false; - withImpermanence = true; - withEncryption = false; - }; - } - ./hardware-configuration.nix - - inputs.sops-nix.nixosModules.sops - - "${profilesPath}/optional/nixos/autologin.nix" - "${profilesPath}/common/nixos/settings.nix" - "${profilesPath}/common/nixos/home-manager.nix" - "${profilesPath}/common/nixos/xserver.nix" - "${profilesPath}/common/nixos/users.nix" - "${profilesPath}/common/nixos/sops.nix" - "${profilesPath}/server/nixos/ssh.nix" - - inputs.home-manager.nixosModules.home-manager - { - home-manager.users.swarsel.imports = [ - inputs.sops-nix.homeManagerModules.sops - "${profilesPath}/common/home/settings.nix" - "${profilesPath}/common/home/sops.nix" - "${profilesPath}/common/home/ssh.nix" - - ] ++ (builtins.attrValues outputs.homeManagerModules); - } - ] ++ (builtins.attrValues outputs.nixosModules); - - - nixpkgs = { - overlays = [ outputs.overlays.default ]; - config = { - allowUnfree = true; - }; - }; - - environment.systemPackages = with pkgs; [ - curl - git - gnupg - rsync - ssh-to-age - sops - vim - just - ]; - - system.stateVersion = lib.mkForce "23.05"; - - boot = { - loader.systemd-boot.enable = lib.mkForce true; - loader.efi.canTouchEfiVariables = true; - supportedFilesystems = [ "btrfs" ]; - kernelPackages = lib.mkDefault pkgs.linuxPackages_latest; - }; - - - networking = { - hostName = "toto"; - firewall.enable = false; - }; - - swarselsystems = { - wallpaper = self + /wallpaper/lenovowp.png; - impermanence = true; - isBtrfs = false; - initialSetup = true; - }; - - home-manager.users.swarsel.swarselsystems = { - isLaptop = false; - isNixos = true; - isBtrfs = false; - flakePath = "/home/swarsel/.dotfiles"; - }; - - } - - -#+end_src - **** Sync (OCI) :PROPERTIES: :CUSTOM_ID: h:4c5febb0-fdf6-44c5-8d51-7ea0f8930abf @@ -1822,6 +1727,109 @@ This machine mainly acts as an external sync helper. It manages the following th #+end_src *** Utility hosts +**** Toto (Physical/VM) + +This is a slim setup for developing base configuration. + +#+begin_src nix :tangle hosts/nixos/toto/default.nix + { self, inputs, outputs, config, pkgs, lib, ... }: + let + profilesPath = "${self}/profiles"; + in + { + + imports = [ + inputs.disko.nixosModules.disko + "${self}/hosts/nixos/toto/disk-config.nix" + { + _module.args = { + withSwap = true; + swapSize = "8"; + rootDisk = "/dev/vda"; + withImpermanence = true; + withEncryption = false; + }; + } + ./hardware-configuration.nix + + inputs.sops-nix.nixosModules.sops + inputs.impermanence.nixosModules.impermanence + + "${profilesPath}/optional/nixos/autologin.nix" + "${profilesPath}/common/nixos/settings.nix" + "${profilesPath}/common/nixos/home-manager.nix" + "${profilesPath}/common/nixos/xserver.nix" + "${profilesPath}/common/nixos/users.nix" + "${profilesPath}/common/nixos/impermanence.nix" + "${profilesPath}/common/nixos/sops.nix" + "${profilesPath}/server/nixos/ssh.nix" + + inputs.home-manager.nixosModules.home-manager + { + home-manager.users.swarsel.imports = [ + inputs.sops-nix.homeManagerModules.sops + "${profilesPath}/common/home/settings.nix" + "${profilesPath}/common/home/sops.nix" + "${profilesPath}/common/home/ssh.nix" + + ] ++ (builtins.attrValues outputs.homeManagerModules); + } + ] ++ (builtins.attrValues outputs.nixosModules); + + + nixpkgs = { + overlays = [ outputs.overlays.default ]; + config = { + allowUnfree = true; + }; + }; + + environment.systemPackages = with pkgs; [ + curl + git + gnupg + rsync + ssh-to-age + sops + vim + just + ]; + + system.stateVersion = lib.mkForce "23.05"; + + boot = { + loader.systemd-boot.enable = lib.mkForce true; + loader.efi.canTouchEfiVariables = true; + supportedFilesystems = [ "btrfs" ]; + kernelPackages = lib.mkDefault pkgs.linuxPackages_latest; + }; + + + networking = { + hostName = "toto"; + firewall.enable = false; + }; + + swarselsystems = { + wallpaper = self + /wallpaper/lenovowp.png; + impermanence = true; + isBtrfs = true; + isCrypted = false; + initialSetup = true; + }; + + home-manager.users.swarsel.swarselsystems = { + isLaptop = false; + isNixos = true; + isBtrfs = true; + flakePath = "/home/swarsel/.dotfiles"; + }; + + } + + +#+end_src + **** drugstore (ISO) :PROPERTIES: :CUSTOM_ID: h:8583371d-5d47-468b-84ba-210aad7e2c90 @@ -1990,7 +1998,7 @@ This is the "reference implementation" of a setup that runs without NixOS, only #+end_src -**** ChaosTheatre (Demo) +**** ChaosTheatre (Demo Physical/VM) This is just a demo host. It applies all the configuration found in the common parts of the flake, but disables all secrets-related features (as they would not work without the proper SSH keys). @@ -2840,11 +2848,9 @@ This program sets up a new NixOS host. temp=$1 ;; --impermanence) - shift persist_dir="/persist" ;; --encryption) - shift disk_encryption=1 ;; --debug) @@ -2984,6 +2990,7 @@ This program sets up a new NixOS host. if [ -n "$persist_dir" ]; then $ssh_root_cmd "cp -r /home/$target_user/.dotfiles $persist_dir/.dotfiles || true" + $ssh_root_cmd "cp -r /home/$target_user/.ssh $persist_dir/.ssh || true" fi if yes_or_no "Do you want to rebuild immediately?"; then @@ -3566,6 +3573,7 @@ I usually use =mutableUsers = false= in my NixOS configuration. However, on a ne type = types.bool; default = true; }; + options.swarselsystems.isCrypted = lib.mkEnableOption "uses full disk encryption"; options.swarselsystems.isPublic = lib.mkEnableOption "is a public machine (no secrets)"; options.swarselsystems.initialSetup = lib.mkEnableOption "initial setup (no sops keys available)"; options.swarselsystems.server.enable = lib.mkEnableOption "is a server machine"; @@ -4957,7 +4965,7 @@ I use sops-nix to handle secrets that I want to have available on my machines at { sops = lib.mkIf (!config.swarselsystems.isPublic) { - age.sshKeyPaths = mkIfElse config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" ] [ "${config.users.users.swarsel.home}/.ssh/sops" "/etc/ssh/ssh_host_ed25519_key" ]; + age.sshKeyPaths = mkIfElse config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" "/persist/.ssh/ssh_host_ed25519_key" ] [ "${config.users.users.swarsel.home}/.ssh/sops" "/etc/ssh/ssh_host_ed25519_key" ]; defaultSopsFile = mkIfElse config.swarselsystems.isBtrfs "/persist/.dotfiles/secrets/general/secrets.yaml" "${config.users.users.swarsel.home}/.dotfiles/secrets/general/secrets.yaml"; validateSopsFiles = false; @@ -5488,6 +5496,15 @@ Normally, doing that also resets the lecture that happens on the first use of =s #+begin_src nix :tangle profiles/common/nixos/impermanence.nix { config, lib, ... }: + let + mkIfElse = p: yes: no: if p then yes else no; + mkIfElseList = p: yes: no: lib.mkMerge [ + (lib.mkIf p yes) + (lib.mkIf (!p) no) + ]; + mapperTarget = mkIfElse config.swarselsystems.isCrypted "/dev/mapper/cryptroot" "/dev/disk/by-label/nixos"; + in + { security.sudo.extraConfig = lib.mkIf config.swarselsystems.impermanence '' @@ -5506,7 +5523,8 @@ Normally, doing that also resets the lecture that happens on the first use of =s wantedBy = [ "initrd.target" ]; # make sure it's done after encryption # i.e. LUKS/TPM process - after = [ "systemd-cryptsetup@enc.service" ]; + after = mkIfElseList config.swarselsystems.isCrypted [ "systemd-cryptsetup@cryptroot.service" ] [ "dev-disk-by\\x2dlabel-nixos.device" ]; + requires = lib.mkIf (!config.swarselsystems.isCrypted) [ "dev-disk-by\\x2dlabel-nixos.device" ]; # mount the root fs before clearing before = [ "sysroot.mount" ]; unitConfig.DefaultDependencies = "no"; @@ -5516,7 +5534,7 @@ Normally, doing that also resets the lecture that happens on the first use of =s # We first mount the btrfs root to /mnt # so we can manipulate btrfs subvolumes. - mount -o subvol=/ /dev/mapper/cryptroot /mnt + mount -o subvolid=5 -t btrfs ${mapperTarget} /mnt btrfs subvolume list -o /mnt/root # While we're tempted to just delete /root and create @@ -5533,13 +5551,13 @@ Normally, doing that also resets the lecture that happens on the first use of =s cut -f9 -d' ' | while read subvolume; do echo "deleting /$subvolume subvolume..." - # btrfs subvolume delete "/mnt/$subvolume" + btrfs subvolume delete "/mnt/$subvolume" done && echo "deleting /root subvolume..." && - # btrfs subvolume delete /mnt/root + btrfs subvolume delete /mnt/root echo "restoring blank /root subvolume..." - # btrfs subvolume snapshot /mnt/root-blank /mnt/root + btrfs subvolume snapshot /mnt/root-blank /mnt/root # Once we're done rolling back to a blank snapshot, # we can unmount /mnt and continue on the boot process. @@ -5556,7 +5574,6 @@ Normally, doing that also resets the lecture that happens on the first use of =s "/srv" "/etc/nixos" "/etc/nix" - "/home/swarsel/.dotfiles" "/etc/NetworkManager/system-connections" "/etc/secureboot" "/var/db/sudo" @@ -5565,13 +5582,10 @@ Normally, doing that also resets the lecture that happens on the first use of =s ]; files = [ - # ssh stuff - /* "/etc/ssh/ssh_host_ed25519_key" "/etc/ssh/ssh_host_ed25519_key.pub" "/etc/ssh/ssh_host_rsa_key" "/etc/ssh/ssh_host_rsa_key.pub" - ,*/ ]; }; diff --git a/hosts/nixos/nbl-imba-2/default.nix b/hosts/nixos/nbl-imba-2/default.nix index 4c83811..c6515d5 100644 --- a/hosts/nixos/nbl-imba-2/default.nix +++ b/hosts/nixos/nbl-imba-2/default.nix @@ -84,6 +84,7 @@ in hasFingerprint = true; impermanence = false; isBtrfs = true; + isCrypted = true; }; home-manager.users.swarsel.swarselsystems = { diff --git a/hosts/nixos/toto/default.nix b/hosts/nixos/toto/default.nix index 46f9fd9..7047a42 100644 --- a/hosts/nixos/toto/default.nix +++ b/hosts/nixos/toto/default.nix @@ -9,7 +9,9 @@ in "${self}/hosts/nixos/toto/disk-config.nix" { _module.args = { - withSwap = false; + withSwap = true; + swapSize = "8"; + rootDisk = "/dev/vda"; withImpermanence = true; withEncryption = false; }; @@ -17,12 +19,14 @@ in ./hardware-configuration.nix inputs.sops-nix.nixosModules.sops + inputs.impermanence.nixosModules.impermanence "${profilesPath}/optional/nixos/autologin.nix" "${profilesPath}/common/nixos/settings.nix" "${profilesPath}/common/nixos/home-manager.nix" "${profilesPath}/common/nixos/xserver.nix" "${profilesPath}/common/nixos/users.nix" + "${profilesPath}/common/nixos/impermanence.nix" "${profilesPath}/common/nixos/sops.nix" "${profilesPath}/server/nixos/ssh.nix" @@ -75,14 +79,15 @@ in swarselsystems = { wallpaper = self + /wallpaper/lenovowp.png; impermanence = true; - isBtrfs = false; + isBtrfs = true; + isCrypted = false; initialSetup = true; }; home-manager.users.swarsel.swarselsystems = { isLaptop = false; isNixos = true; - isBtrfs = false; + isBtrfs = true; flakePath = "/home/swarsel/.dotfiles"; }; diff --git a/hosts/nixos/toto/disk-config.nix b/hosts/nixos/toto/disk-config.nix index a073b16..d0e7e06 100644 --- a/hosts/nixos/toto/disk-config.nix +++ b/hosts/nixos/toto/disk-config.nix @@ -1,18 +1,69 @@ # NOTE: ... is needed because dikso passes diskoFile { lib , pkgs -, swapSize +, rootDisk +, swapSize ? "8" , withSwap ? true , withEncryption ? true , withImpermanence ? true , ... }: +let + type = "btrfs"; + extraArgs = [ "-L" "nixos" "-f" ]; # force overwrite + subvolumes = { + "/root" = { + mountpoint = "/"; + mountOptions = [ + "subvol=root" + "compress=zstd" + "noatime" + ]; + }; + "/home" = lib.mkIf withImpermanence { + mountpoint = "/home"; + mountOptions = [ + "subvol=home" + "compress=zstd" + "noatime" + ]; + }; + "/persist" = lib.mkIf withImpermanence { + mountpoint = "/persist"; + mountOptions = [ + "subvol=persist" + "compress=zstd" + "noatime" + ]; + }; + "/log" = lib.mkIf withImpermanence { + mountpoint = "/var/log"; + mountOptions = [ + "subvol=log" + "compress=zstd" + "noatime" + ]; + }; + "/nix" = { + mountpoint = "/nix"; + mountOptions = [ + "subvol=nix" + "compress=zstd" + "noatime" + ]; + }; + "/swap" = lib.mkIf withSwap { + mountpoint = "/.swapvol"; + swap.swapfile.size = "${swapSize}G"; + }; + }; +in { disko.devices = { disk = { disk0 = { type = "disk"; - device = "/dev/vda"; + device = rootDisk; content = { type = "gpt"; partitions = { @@ -31,41 +82,13 @@ root = lib.mkIf (!withEncryption) { size = "100%"; content = { - type = "btrfs"; - extraArgs = [ "-f" ]; # force overwrite + inherit type subvolumes extraArgs; postCreateHook = lib.mkIf withImpermanence '' - MNTPOINT=$(mktemp -d) - mount "/dev/mapper/root" "$MNTPOINT" -o subvol=/ + MNTPOINT=$(mktemp -d) + mount "/dev/disk/by-label/nixos" "$MNTPOINT" -o subvolid=5 trap 'umount $MNTPOINT; rm -rf $MNTPOINT' EXIT btrfs subvolume snapshot -r $MNTPOINT/root $MNTPOINT/root-blank ''; - subvolumes = { - "@root" = { - mountpoint = "/"; - mountOptions = [ - "compress=zstd" - "noatime" - ]; - }; - "@persist" = lib.mkIf withImpermanence { - mountpoint = "/persist"; - mountOptions = [ - "compress=zstd" - "noatime" - ]; - }; - "@nix" = { - mountpoint = "/nix"; - mountOptions = [ - "compress=zstd" - "noatime" - ]; - }; - "@swap" = lib.mkIf withSwap { - mountpoint = "/.swapvol"; - swap.swapfile.size = "${swapSize}G"; - }; - }; }; }; luks = lib.mkIf withEncryption { @@ -73,7 +96,7 @@ content = { type = "luks"; name = "cryptroot"; - passwordFile = "/tmp/disko-password"; # this is populated by bootstrap-nixos.sh + passwordFile = "/tmp/disko-password"; # this is populated by bootstrap.sh settings = { allowDiscards = true; # https://github.com/hmajid2301/dotfiles/blob/a0b511c79b11d9b4afe2a5e2b7eedb2af23e288f/systems/x86_64-linux/framework/disks.nix#L36 @@ -82,44 +105,14 @@ "token-timeout=10" ]; }; - # Subvolumes must set a mountpoint in order to be mounted, - # unless their parent is mounted content = { - type = "btrfs"; - extraArgs = [ "-f" ]; # force overwrite + inherit type subvolumes extraArgs; postCreateHook = lib.mkIf withImpermanence '' - MNTPOINT=$(mktemp -d) - mount "/dev/mapper/cryptroot" "$MNTPOINT" -o subvol=/ + MNTPOINT=$(mktemp -d) + mount "/dev/mapper/cryptroot" "$MNTPOINT" -o subvolid=5 trap 'umount $MNTPOINT; rm -rf $MNTPOINT' EXIT btrfs subvolume snapshot -r $MNTPOINT/root $MNTPOINT/root-blank ''; - subvolumes = { - "@root" = { - mountpoint = "/"; - mountOptions = [ - "compress=zstd" - "noatime" - ]; - }; - "@persist" = lib.mkIf withImpermanence { - mountpoint = "/persist"; - mountOptions = [ - "compress=zstd" - "noatime" - ]; - }; - "@nix" = { - mountpoint = "/nix"; - mountOptions = [ - "compress=zstd" - "noatime" - ]; - }; - "@swap" = lib.mkIf withSwap { - mountpoint = "/.swapvol"; - swap.swapfile.size = "${swapSize}G"; - }; - }; }; }; }; @@ -130,8 +123,9 @@ }; fileSystems."/persist".neededForBoot = lib.mkIf withImpermanence true; + fileSystems."/home".neededForBoot = lib.mkIf withImpermanence true; environment.systemPackages = [ - pkgs.yubikey-manager # For luks fido2 enrollment before full install + pkgs.yubikey-manager ]; } diff --git a/modules/nixos/setup.nix b/modules/nixos/setup.nix index 758f9c9..0a4f10b 100644 --- a/modules/nixos/setup.nix +++ b/modules/nixos/setup.nix @@ -12,6 +12,7 @@ in type = types.bool; default = true; }; + options.swarselsystems.isCrypted = lib.mkEnableOption "uses full disk encryption"; options.swarselsystems.isPublic = lib.mkEnableOption "is a public machine (no secrets)"; options.swarselsystems.initialSetup = lib.mkEnableOption "initial setup (no sops keys available)"; options.swarselsystems.server.enable = lib.mkEnableOption "is a server machine"; diff --git a/profiles/common/nixos/impermanence.nix b/profiles/common/nixos/impermanence.nix index 963178b..b961366 100644 --- a/profiles/common/nixos/impermanence.nix +++ b/profiles/common/nixos/impermanence.nix @@ -1,4 +1,13 @@ { config, lib, ... }: +let + mkIfElse = p: yes: no: if p then yes else no; + mkIfElseList = p: yes: no: lib.mkMerge [ + (lib.mkIf p yes) + (lib.mkIf (!p) no) + ]; + mapperTarget = mkIfElse config.swarselsystems.isCrypted "/dev/mapper/cryptroot" "/dev/disk/by-label/nixos"; +in + { security.sudo.extraConfig = lib.mkIf config.swarselsystems.impermanence '' @@ -17,7 +26,8 @@ wantedBy = [ "initrd.target" ]; # make sure it's done after encryption # i.e. LUKS/TPM process - after = [ "systemd-cryptsetup@enc.service" ]; + after = mkIfElseList config.swarselsystems.isCrypted [ "systemd-cryptsetup@cryptroot.service" ] [ "dev-disk-by\\x2dlabel-nixos.device" ]; + requires = lib.mkIf (!config.swarselsystems.isCrypted) [ "dev-disk-by\\x2dlabel-nixos.device" ]; # mount the root fs before clearing before = [ "sysroot.mount" ]; unitConfig.DefaultDependencies = "no"; @@ -27,7 +37,7 @@ # We first mount the btrfs root to /mnt # so we can manipulate btrfs subvolumes. - mount -o subvol=/ /dev/mapper/cryptroot /mnt + mount -o subvolid=5 -t btrfs ${mapperTarget} /mnt btrfs subvolume list -o /mnt/root # While we're tempted to just delete /root and create @@ -44,13 +54,13 @@ cut -f9 -d' ' | while read subvolume; do echo "deleting /$subvolume subvolume..." - # btrfs subvolume delete "/mnt/$subvolume" + btrfs subvolume delete "/mnt/$subvolume" done && echo "deleting /root subvolume..." && - # btrfs subvolume delete /mnt/root + btrfs subvolume delete /mnt/root echo "restoring blank /root subvolume..." - # btrfs subvolume snapshot /mnt/root-blank /mnt/root + btrfs subvolume snapshot /mnt/root-blank /mnt/root # Once we're done rolling back to a blank snapshot, # we can unmount /mnt and continue on the boot process. @@ -67,7 +77,6 @@ "/srv" "/etc/nixos" "/etc/nix" - "/home/swarsel/.dotfiles" "/etc/NetworkManager/system-connections" "/etc/secureboot" "/var/db/sudo" @@ -76,13 +85,10 @@ ]; files = [ - # ssh stuff - /* - "/etc/ssh/ssh_host_ed25519_key" - "/etc/ssh/ssh_host_ed25519_key.pub" - "/etc/ssh/ssh_host_rsa_key" - "/etc/ssh/ssh_host_rsa_key.pub" - */ + "/etc/ssh/ssh_host_ed25519_key" + "/etc/ssh/ssh_host_ed25519_key.pub" + "/etc/ssh/ssh_host_rsa_key" + "/etc/ssh/ssh_host_rsa_key.pub" ]; }; diff --git a/profiles/common/nixos/sops.nix b/profiles/common/nixos/sops.nix index 92f0305..dd69e1e 100644 --- a/profiles/common/nixos/sops.nix +++ b/profiles/common/nixos/sops.nix @@ -8,7 +8,7 @@ in { sops = lib.mkIf (!config.swarselsystems.isPublic) { - age.sshKeyPaths = mkIfElse config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" ] [ "${config.users.users.swarsel.home}/.ssh/sops" "/etc/ssh/ssh_host_ed25519_key" ]; + age.sshKeyPaths = mkIfElse config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" "/persist/.ssh/ssh_host_ed25519_key" ] [ "${config.users.users.swarsel.home}/.ssh/sops" "/etc/ssh/ssh_host_ed25519_key" ]; defaultSopsFile = mkIfElse config.swarselsystems.isBtrfs "/persist/.dotfiles/secrets/general/secrets.yaml" "${config.users.users.swarsel.home}/.dotfiles/secrets/general/secrets.yaml"; validateSopsFiles = false; diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 68b68bb..015f999 100644 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -115,11 +115,9 @@ while [[ $# -gt 0 ]]; do temp=$1 ;; --impermanence) - shift persist_dir="/persist" ;; --encryption) - shift disk_encryption=1 ;; --debug) @@ -259,6 +257,7 @@ if yes_or_no "Do you want to copy your full nix-config and nix-secrets to $targe if [ -n "$persist_dir" ]; then $ssh_root_cmd "cp -r /home/$target_user/.dotfiles $persist_dir/.dotfiles || true" + $ssh_root_cmd "cp -r /home/$target_user/.ssh $persist_dir/.ssh || true" fi if yes_or_no "Do you want to rebuild immediately?"; then