From 67884944a3d90a068bbe0c00265db7c1d1f1d832 Mon Sep 17 00:00:00 2001 From: Swarsel Date: Tue, 17 Dec 2024 01:10:04 +0100 Subject: [PATCH] feat: full bootstrapping --- .sops.yaml | 12 +- SwarselSystems.org | 306 ++++++++++++++++++-- flake.nix | 8 + hosts/nixos/iso/default.nix | 14 +- hosts/nixos/toto/default.nix | 87 ++++++ hosts/nixos/toto/disk-config.nix | 87 ++++++ hosts/nixos/toto/hardware-configuration.nix | 25 ++ profiles/common/home/custom-packages.nix | 2 + profiles/common/home/emacs.nix | 6 +- profiles/common/home/settings.nix | 4 +- profiles/common/home/sops.nix | 3 +- profiles/common/nixos/sops.nix | 2 +- profiles/common/nixos/users.nix | 1 + profiles/iso/minimal.nix | 7 +- profiles/server/nixos/syncthing.nix | 10 +- scripts/bootstrap.sh | 154 +++++++++- 16 files changed, 677 insertions(+), 51 deletions(-) create mode 100644 hosts/nixos/toto/default.nix create mode 100644 hosts/nixos/toto/disk-config.nix create mode 100644 hosts/nixos/toto/hardware-configuration.nix diff --git a/.sops.yaml b/.sops.yaml index aa6e51e..b03523b 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -3,11 +3,13 @@ # Also see https://github.com/Mic92/dotfiles/blob/master/nixos/.sops.yaml # for a more complex example. keys: - - &admin_swarsel 4BE7925262289B476DBBC17B76FD3810215AE097 - - &server_winters age1h72072slm2pthn9m2qwjsyy2dsazc6hz97kpzh4gksvv0r2jqecqul8w63 - - &server_surface age1zlnxraee6tddr07xn59mx5rdexw8qxryd53eqlsajasfhfy78fkq705dfg - - &server_nbl age16lnmuuxfuxxtty3atnhut8wseppwnhp7rdhmxqd5tdvs9qnjffjq42sqyy - - &server_sync age1glge4e97vgqzh332mqs5990vteezu2m8k4wq3z35jk0q8czw3gks2d7a3h + - &users + - &admin_swarsel 4BE7925262289B476DBBC17B76FD3810215AE097 + - &hosts + - &server_winters age1h72072slm2pthn9m2qwjsyy2dsazc6hz97kpzh4gksvv0r2jqecqul8w63 + - &server_surface age1zlnxraee6tddr07xn59mx5rdexw8qxryd53eqlsajasfhfy78fkq705dfg + - &server_nbl age16lnmuuxfuxxtty3atnhut8wseppwnhp7rdhmxqd5tdvs9qnjffjq42sqyy + - &server_sync age1glge4e97vgqzh332mqs5990vteezu2m8k4wq3z35jk0q8czw3gks2d7a3h creation_rules: - path_regex: secrets/general/[^/]+\.(yaml|json|env|ini)$ key_groups: diff --git a/SwarselSystems.org b/SwarselSystems.org index 021ae68..f7c3324 100644 --- a/SwarselSystems.org +++ b/SwarselSystems.org @@ -1050,6 +1050,14 @@ In this section I am creating some attributes that define general concepts of my homeManagerModules = import ./modules/home; packages = forEachSystem (pkgs: import ./pkgs { inherit pkgs; }); + apps = forAllSystems (system: { + default = self.apps.${system}.bootstrap; + + bootstrap = { + type = "app"; + program = "${self.packages.${system}.bootstrap}/bin/bootstrap"; + }; + }); devShells = forAllSystems ( system: let @@ -1180,7 +1188,7 @@ This is a list of all physical machines that I maintain. This is a live environment ISO that I use to bootstrap new systems. It only loads a minimal configuration and no graphical interface. After booting this image on a host, find out its IP and bootstrap the system using the =bootstrap= utility. #+begin_src nix :tangle hosts/nixos/iso/default.nix - { self, inputs, config, pkgs, lib, modulesPath, ... }: + { self, inputs, config, lib, modulesPath, ... }: let pubKeys = lib.filesystem.listFilesRecursive "${self}/secrets/keys/ssh"; in @@ -1195,7 +1203,7 @@ This is a live environment ISO that I use to bootstrap new systems. It only load "${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix" "${modulesPath}/installer/cd-dvd/channel.nix" - "${self}/profiles/iso//minimal.nix" + "${self}/profiles/iso/minimal.nix" ]; @@ -1220,18 +1228,22 @@ This is a live environment ISO that I use to bootstrap new systems. It only load name = "swarsel"; group = "swarsel"; isNormalUser = true; - shell = pkgs.zsh; password = "setup"; # this is overwritten after install openssh.authorizedKeys.keys = lib.lists.forEach pubKeys (key: builtins.readFile key); + extraGroups = [ "wheel" ]; }; root = { - shell = pkgs.zsh; - password = lib.mkForce config.users.users.swarsel.password; # this is overwritten after install + # password = lib.mkForce config.users.users.swarsel.password; # this is overwritten after install openssh.authorizedKeys.keys = config.users.users.swarsel.openssh.authorizedKeys.keys; }; }; }; + boot = { + loader.systemd-boot.enable = lib.mkForce true; + loader.efi.canTouchEfiVariables = true; + }; + systemd = { services.sshd.wantedBy = lib.mkForce [ "multi-user.target" ]; targets = { @@ -1319,6 +1331,100 @@ This is the "reference implementation" of a setup that runs without NixOS, only } +#+end_src + +**** 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; + }; + } + ./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 = false; + isBtrfs = false; + initialSetup = true; + }; + + home-manager.users.swarsel.swarselsystems = { + isLaptop = false; + isNixos = true; + isBtrfs = false; + flakePath = "/home/swarsel/.dotfiles"; + }; + + } + + #+end_src **** nbl-imba-2 (Framework Laptop 16) @@ -2581,6 +2687,31 @@ This program sets up a new NixOS host. done } + function update_sops_file() { + key_name=$1 + key_type=$2 + key=$3 + + if [ ! "$key_type" == "hosts" ] && [ ! "$key_type" == "users" ]; then + red "Invalid key type passed to update_sops_file. Must be either 'hosts' or 'users'." + exit 1 + fi + cd "${git_root}" + + SOPS_FILE=".sops.yaml" + sed -i "{ + # Remove any * and & entries for this host + /[*&]$key_name/ d; + # Inject a new age: entry + # n matches the first line following age: and p prints it, then we transform it while reusing the spacing + /age:/{n; p; s/\(.*- \*\).*/\1$key_name/}; + # Inject a new hosts or user: entry + /&$key_type/{n; p; s/\(.*- &\).*/\1$key_name $key/} + }" $SOPS_FILE + green "Updating .sops.yaml" + cd - + } + while [[ $# -gt 0 ]]; do case "$1" in -n) @@ -2618,21 +2749,139 @@ This program sets up a new NixOS host. ssh_cmd="ssh -oport=${ssh_port} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t $target_user@$target_destination" # ssh_root_cmd=$(echo "$ssh_cmd" | sed "s|${target_user}@|root@|") # uses @ in the sed switch to avoid it triggering on the $ssh_key value ssh_root_cmd=${ssh_cmd/${target_user}@/root@} - scp_cmd="scp -oport=${ssh_port} -o StrictHostKeyChecking=no" - git_root=$(git rev-parse --show-toplevel) + # ------------------------ green "Wiping known_hosts of $target_destination" sed -i "/$target_hostname/d; /$target_destination/d" ~/.ssh/known_hosts - + # ------------------------ + green "Preparing a new ssh_host_ed25519_key pair for $target_hostname." + # Create the directory where sshd expects to find the host keys + install -d -m755 "$temp/etc/ssh" + # Generate host ssh key pair without a passphrase + ssh-keygen -t ed25519 -f "$temp/etc/ssh/ssh_host_ed25519_key" -C root@"$target_hostname" -N "" + # Set the correct permissions so sshd will accept the key + chmod 600 "$temp/etc/ssh/ssh_host_ed25519_key" + echo "Adding ssh host fingerprint at $target_destination to ~/.ssh/known_hosts" + # This will fail if we already know the host, but that's fine + ssh-keyscan -p "$ssh_port" "$target_destination" >> ~/.ssh/known_hosts || true + # ------------------------ + # when using luks, disko expects a passphrase on /tmp/disko-password, so we set it for now and will update the passphrase later + # via the config + green "Preparing a temporary password for disko." + green "[Optional] Set disk encryption passphrase:" + read -rs luks_passphrase + if [ -n "$luks_passphrase" ]; then + $ssh_root_cmd "/bin/sh -c 'echo $luks_passphrase > /tmp/disko-password'" + else + $ssh_root_cmd "/bin/sh -c 'echo passphrase > /tmp/disko-password'" + fi + # ------------------------ green "Generating hardware-config.nix for $target_hostname and adding it to the nix-config." - $ssh_root_cmd "nixos-generate-config --no-filesystems --root /mnt" - mkdir profiles/"$target_hostname" - $scp_cmd root@"$target_destination":/mnt/etc/nixos/hardware-configuration.nix "${git_root}"/profiles/"$target_hostname"/hardware-configuration.nix + $ssh_root_cmd "nixos-generate-config --force --no-filesystems --root /mnt" + mkdir -p "$FLAKE"/hosts/nixos/"$target_hostname" + $scp_cmd root@"$target_destination":/mnt/etc/nixos/hardware-configuration.nix "${git_root}"/hosts/nixos/"$target_hostname"/hardware-configuration.nix + # ------------------------ + green "Deploying minimal NixOS installation on $target_destination" + SHELL=/bin/sh nix run github:nix-community/nixos-anywhere -- --ssh-port "$ssh_port" --extra-files "$temp" --flake .#"$target_hostname" root@"$target_destination" + + echo "Updating ssh host fingerprint at $target_destination to ~/.ssh/known_hosts" + ssh-keyscan -p "$ssh_port" "$target_destination" >> ~/.ssh/known_hosts || true + # ------------------------ + + while true; do + read -rp "Press Enter to continue once the remote host has finished booting." + if nc -z "$target_destination" "${ssh_port}" 2> /dev/null; then + green "$target_destination is booted. Continuing..." + break + else + yellow "$target_destination is not yet ready." + fi + done + # ------------------------ + green "Generating an age key based on the new ssh_host_ed25519_key." + target_key=$( + ssh-keyscan -p "$ssh_port" -t ssh-ed25519 "$target_destination" 2>&1 | + grep ssh-ed25519 | + cut -f2- -d" " || + ( + red "Failed to get ssh key. Host down?" + exit 1 + ) + ) + host_age_key=$(nix shell nixpkgs#ssh-to-age.out -c sh -c "echo $target_key | ssh-to-age") + + if grep -qv '^age1' <<< "$host_age_key"; then + red "The result from generated age key does not match the expected format." + yellow "Result: $host_age_key" + yellow "Expected format: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + exit 1 + else + echo "$host_age_key" + fi + + green "Updating nix-secrets/.sops.yaml" + update_sops_file "$target_hostname" "hosts" "$host_age_key" + yellow ".sops.yaml has been updated. There may be superfluous entries, you might need to edit manually." + if yes_or_no "Do you want to manually edit .sops.yaml now?"; then + vim "${git_root}"/.sops.yaml + fi + green "Updating all secrets files to reflect updates .sops.yaml" + sops updatekeys --yes --enable-local-keyservice "${git_root}"/secrets/*/secrets.yaml + # -------------------------- + green "Making ssh_host_ed25519_key available to home-manager for user $target_user" + $scp_cmd root@"$target_destination":/etc/ssh/ssh_host_ed25519_key root@"$target_destination":/home/"$target_user"/.ssh/ssh_host_ed25519_key + $ssh_root_cmd "chown $target_user:users /home/swarsel/.ssh/ssh_host_ed25519_key" + # __________________________ + + if yes_or_no "Add ssh host fingerprints for git upstream repositories? (This is needed for building the full config)"; then + if [ "$target_user" == "root" ]; then + home_path="/root" + else + home_path="/home/$target_user" + fi + green "Adding ssh host fingerprints for git{lab,hub}" + $ssh_cmd "mkdir -p $home_path/.ssh/; ssh-keyscan -t ssh-ed25519 gitlab.com github.com swagit.swarsel.win >>$home_path/.ssh/known_hosts" + fi + # -------------------------- + + if yes_or_no "Do you want to copy your full nix-config and nix-secrets to $target_hostname?"; then + green "Adding ssh host fingerprint at $target_destination to ~/.ssh/known_hosts" + ssh-keyscan -p "$ssh_port" "$target_destination" >> ~/.ssh/known_hosts || true + green "Copying full nix-config to $target_hostname" + cd "${git_root}" + just sync "$target_user" "$target_destination" + + if yes_or_no "Do you want to rebuild immediately?"; then + green "Rebuilding nix-config on $target_hostname" + #FIXME:(bootstrap) there are still a gitlab fingerprint request happening during the rebuild + $ssh_cmd -oForwardAgent=yes "cd .dotfiles && sudo nixos-rebuild --show-trace --flake .#$target_hostname switch" + fi + else + echo + green "NixOS was successfully installed!" + echo "Post-install config build instructions:" + echo "To copy nix-config from this machine to the $target_hostname, run the following command from ~/nix-config" + echo "just sync $target_user $target_destination" + echo "To rebuild, sign into $target_hostname and run the following command from ~/nix-config" + echo "cd nix-config" + # see above FIXME:(bootstrap) + echo "sudo nixos-rebuild --show-trace --flake .#$target_hostname switch" + # echo "just rebuild" + echo + fi + + if yes_or_no "You can now commit and push the nix-config, which includes the hardware-configuration.nix for $target_hostname?"; then + cd "${git_root}" + deadnix hosts/nixos/"$target_hostname"/hardware-configuration.nix -qe + (pre-commit run --all-files 2> /dev/null || true) && + git add "$git_root/hosts/$target_hostname/hardware-configuration.nix" && (git commit -m "feat: hardware-configuration.nix for $target_hostname" || true) && git push + fi #+end_src + #+begin_src nix :tangle pkgs/bootstrap/default.nix { writeShellApplication, openssh }: @@ -3757,6 +4006,7 @@ For that reason, make sure that =sops-nix= is properly working before setting th users.swarsel = { isNormalUser = true; description = "Leon S"; + password = lib.mkIf config.swarselsystems.initialSetup "setup"; hashedPasswordFile = lib.mkIf (!config.swarselsystems.initialSetup) config.sops.secrets.swarseluser.path; extraGroups = [ "networkmanager" "syncthing" "docker" "wheel" "lp" "audio" "video" "vboxusers" "libvirtd" "scanner" ]; packages = with pkgs; [ ]; @@ -4257,7 +4507,7 @@ I use sops-nix to handle secrets that I want to have available on my machines at { sops = { - age.sshKeyPaths = mkIfElse config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" ] [ "${config.users.users.swarsel.home}/.ssh/sops" ]; + age.sshKeyPaths = mkIfElse config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" ] [ "${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; @@ -6393,11 +6643,11 @@ Here we just define some aliases for rebuilding the system, and we allow some in devices = [ "magicant" "nbl-imba-2" ]; id = "hgr3d-pfu3w"; }; - ".elfeed" = { - path = "/Vault/data/syncthing/.elfeed"; - devices = [ "sync (@oracle)" "magicant" "nbl-imba-2" ]; - id = "h7xbs-fs9v1"; - }; + # ".elfeed" = { + # path = "/Vault/data/syncthing/.elfeed"; + # devices = [ "sync (@oracle)" "magicant" "nbl-imba-2" ]; + # id = "h7xbs-fs9v1"; + # }; }; }; }; @@ -7191,6 +7441,10 @@ Options that I need specifically at work. There are more options at [[#h:f0b2ea9 }; }; + security.sudo.extraConfig = '' + Defaults env_keep+=SSH_AUTH_SOCK + ''; + security.pam = { sshAgentAuth.enable = true; services = { @@ -7200,6 +7454,8 @@ Options that I need specifically at work. There are more options at [[#h:f0b2ea9 environment.systemPackages = with pkgs; [ curl + git + gnupg rsync ssh-to-age sops @@ -7209,7 +7465,6 @@ Options that I need specifically at work. There are more options at [[#h:f0b2ea9 programs = { git.enable = true; - zsh.enable = lib.mkDefault true; }; fileSystems."/boot".options = [ "umask=0077" ]; @@ -7287,7 +7542,7 @@ This section sets up all the imports that are used in the home-manager section. Again, we adapt =nix= to our needs, enable the home-manager command for non-NixOS machines (NixOS machines are using it as a module) and setting user information that I always keep the same. #+begin_src nix :tangle profiles/common/home/settings.nix - { self, lib, config, pkgs, ... }: + { lib, config, pkgs, ... }: { nix = { package = lib.mkDefault pkgs.nix; @@ -7309,7 +7564,7 @@ This section sets up all the imports that are used in the home-manager section. stateVersion = lib.mkDefault "23.05"; keyboard.layout = "us"; sessionVariables = { - FLAKE = "${self}"; + FLAKE = "${config.home.homeDirectory}/.dotfiles"; }; }; } @@ -7528,6 +7783,8 @@ This is just a separate container for derivations defined in [[#h:64a5cc16-6b16- ts2t vershell + bootstrap + (pkgs.writeScriptBin "project" '' #! ${pkgs.bash}/bin/bash if [ "$1" == "rust" ]; then @@ -7589,7 +7846,7 @@ I use sops-nix to handle secrets that I want to have available on my machines at in { sops = { - age.sshKeyPaths = [ "${config.home.homeDirectory}/.ssh/sops" ]; + age.sshKeyPaths = [ "${config.home.homeDirectory}/.ssh/sops" "${config.home.homeDirectory}/.ssh/ssh_host_ed25519_key" ]; defaultSopsFile = mkIfElse config.swarselsystems.isBtrfs "/persist/.dotfiles/secrets/general/secrets.yaml" "${config.home.homeDirectory}/.dotfiles/secrets/general/secrets.yaml"; validateSopsFiles = false; @@ -7599,7 +7856,6 @@ I use sops-nix to handle secrets that I want to have available on my machines at leon = { path = "/run/user/1000/secrets/leon"; }; swarselmail = { path = "/run/user/1000/secrets/swarselmail"; }; github_notif = { path = "/run/user/1000/secrets/github_notif"; }; - fever = { path = "${config.home.homeDirectory}/.emacs.d/.fever"; }; }; }; } @@ -8778,8 +9034,12 @@ By using the emacs-overlay NixOS module, I can install all Emacs packages that I Lastly, I am defining some more packages here that the parser has problems finding. Also there are some packages that are not in ELPA or MELPA that I still want to use, like =calfw= and =fast-scroll=, so I build them here. #+begin_src nix :tangle profiles/common/home/emacs.nix - { self, pkgs, ... }: + { self, config, pkgs, ... }: { + + # needed for elfeed + sops.secrets.fever = { path = "${config.home.homeDirectory}/.emacs.d/.fever"; }; + # enable emacs overlay for bleeding edge features # also read init.el file and install use-package packages programs.emacs = { diff --git a/flake.nix b/flake.nix index 5d762b8..e0aee93 100644 --- a/flake.nix +++ b/flake.nix @@ -199,6 +199,14 @@ homeManagerModules = import ./modules/home; packages = forEachSystem (pkgs: import ./pkgs { inherit pkgs; }); + apps = forAllSystems (system: { + default = self.apps.${system}.bootstrap; + + bootstrap = { + type = "app"; + program = "${self.packages.${system}.bootstrap}/bin/bootstrap"; + }; + }); devShells = forAllSystems ( system: let diff --git a/hosts/nixos/iso/default.nix b/hosts/nixos/iso/default.nix index 28f35d9..dd5c798 100644 --- a/hosts/nixos/iso/default.nix +++ b/hosts/nixos/iso/default.nix @@ -1,4 +1,4 @@ -{ self, inputs, config, pkgs, lib, modulesPath, ... }: +{ self, inputs, config, lib, modulesPath, ... }: let pubKeys = lib.filesystem.listFilesRecursive "${self}/secrets/keys/ssh"; in @@ -13,7 +13,7 @@ in "${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix" "${modulesPath}/installer/cd-dvd/channel.nix" - "${self}/profiles/iso//minimal.nix" + "${self}/profiles/iso/minimal.nix" ]; @@ -38,18 +38,22 @@ in name = "swarsel"; group = "swarsel"; isNormalUser = true; - shell = pkgs.zsh; password = "setup"; # this is overwritten after install openssh.authorizedKeys.keys = lib.lists.forEach pubKeys (key: builtins.readFile key); + extraGroups = [ "wheel" ]; }; root = { - shell = pkgs.zsh; - password = lib.mkForce config.users.users.swarsel.password; # this is overwritten after install + # password = lib.mkForce config.users.users.swarsel.password; # this is overwritten after install openssh.authorizedKeys.keys = config.users.users.swarsel.openssh.authorizedKeys.keys; }; }; }; + boot = { + loader.systemd-boot.enable = lib.mkForce true; + loader.efi.canTouchEfiVariables = true; + }; + systemd = { services.sshd.wantedBy = lib.mkForce [ "multi-user.target" ]; targets = { diff --git a/hosts/nixos/toto/default.nix b/hosts/nixos/toto/default.nix new file mode 100644 index 0000000..742b06b --- /dev/null +++ b/hosts/nixos/toto/default.nix @@ -0,0 +1,87 @@ +{ 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; + }; + } + ./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 = false; + isBtrfs = false; + initialSetup = true; + }; + + home-manager.users.swarsel.swarselsystems = { + isLaptop = false; + isNixos = true; + isBtrfs = false; + flakePath = "/home/swarsel/.dotfiles"; + }; + +} diff --git a/hosts/nixos/toto/disk-config.nix b/hosts/nixos/toto/disk-config.nix new file mode 100644 index 0000000..7e61397 --- /dev/null +++ b/hosts/nixos/toto/disk-config.nix @@ -0,0 +1,87 @@ +# NOTE: ... is needed because dikso passes diskoFile +{ lib +, pkgs +, withSwap ? false +, swapSize +, ... +}: +{ + disko.devices = { + disk = { + disk0 = { + type = "disk"; + device = "/dev/vda"; + content = { + type = "gpt"; + partitions = { + ESP = { + priority = 1; + name = "ESP"; + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "defaults" ]; + }; + }; + luks = { + size = "100%"; + content = { + type = "luks"; + name = "cryptroot"; + passwordFile = "/tmp/disko-password"; # this is populated by bootstrap-nixos.sh + settings = { + allowDiscards = true; + # https://github.com/hmajid2301/dotfiles/blob/a0b511c79b11d9b4afe2a5e2b7eedb2af23e288f/systems/x86_64-linux/framework/disks.nix#L36 + crypttabExtraOpts = [ + "fido2-device=auto" + "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 + subvolumes = { + "@root" = { + mountpoint = "/"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + # "@persist" = { + # mountpoint = "${config.hostSpec.persistFolder}"; + # mountOptions = [ + # "compress=zstd" + # "noatime" + # ]; + # }; + "@nix" = { + mountpoint = "/nix"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + "@swap" = lib.mkIf withSwap { + mountpoint = "/.swapvol"; + swap.swapfile.size = "${swapSize}G"; + }; + }; + }; + }; + }; + }; + }; + }; + }; + }; + + environment.systemPackages = [ + pkgs.yubikey-manager # For luks fido2 enrollment before full install + ]; +} diff --git a/hosts/nixos/toto/hardware-configuration.nix b/hosts/nixos/toto/hardware-configuration.nix new file mode 100644 index 0000000..8f857fc --- /dev/null +++ b/hosts/nixos/toto/hardware-configuration.nix @@ -0,0 +1,25 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ lib, modulesPath, ... }: + +{ + imports = + [ + (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-amd" ]; + boot.extraModulePackages = [ ]; + + # 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..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/profiles/common/home/custom-packages.nix b/profiles/common/home/custom-packages.nix index bc00ff0..294d7d2 100644 --- a/profiles/common/home/custom-packages.nix +++ b/profiles/common/home/custom-packages.nix @@ -21,6 +21,8 @@ ts2t vershell + bootstrap + (pkgs.writeScriptBin "project" '' #! ${pkgs.bash}/bin/bash if [ "$1" == "rust" ]; then diff --git a/profiles/common/home/emacs.nix b/profiles/common/home/emacs.nix index c7230f6..3c138ef 100644 --- a/profiles/common/home/emacs.nix +++ b/profiles/common/home/emacs.nix @@ -1,5 +1,9 @@ -{ self, pkgs, ... }: +{ self, config, pkgs, ... }: { + + # needed for elfeed + sops.secrets.fever = { path = "${config.home.homeDirectory}/.emacs.d/.fever"; }; + # enable emacs overlay for bleeding edge features # also read init.el file and install use-package packages programs.emacs = { diff --git a/profiles/common/home/settings.nix b/profiles/common/home/settings.nix index fd44d71..0a79218 100644 --- a/profiles/common/home/settings.nix +++ b/profiles/common/home/settings.nix @@ -1,4 +1,4 @@ -{ self, lib, config, pkgs, ... }: +{ lib, config, pkgs, ... }: { nix = { package = lib.mkDefault pkgs.nix; @@ -20,7 +20,7 @@ stateVersion = lib.mkDefault "23.05"; keyboard.layout = "us"; sessionVariables = { - FLAKE = "${self}"; + FLAKE = "${config.home.homeDirectory}/.dotfiles"; }; }; } diff --git a/profiles/common/home/sops.nix b/profiles/common/home/sops.nix index c02bd14..aef84c0 100644 --- a/profiles/common/home/sops.nix +++ b/profiles/common/home/sops.nix @@ -7,7 +7,7 @@ let in { sops = { - age.sshKeyPaths = [ "${config.home.homeDirectory}/.ssh/sops" ]; + age.sshKeyPaths = [ "${config.home.homeDirectory}/.ssh/sops" "${config.home.homeDirectory}/.ssh/ssh_host_ed25519_key" ]; defaultSopsFile = mkIfElse config.swarselsystems.isBtrfs "/persist/.dotfiles/secrets/general/secrets.yaml" "${config.home.homeDirectory}/.dotfiles/secrets/general/secrets.yaml"; validateSopsFiles = false; @@ -17,7 +17,6 @@ in leon = { path = "/run/user/1000/secrets/leon"; }; swarselmail = { path = "/run/user/1000/secrets/swarselmail"; }; github_notif = { path = "/run/user/1000/secrets/github_notif"; }; - fever = { path = "${config.home.homeDirectory}/.emacs.d/.fever"; }; }; }; } diff --git a/profiles/common/nixos/sops.nix b/profiles/common/nixos/sops.nix index b6fba1a..b9ada15 100644 --- a/profiles/common/nixos/sops.nix +++ b/profiles/common/nixos/sops.nix @@ -8,7 +8,7 @@ in { sops = { - age.sshKeyPaths = mkIfElse config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" ] [ "${config.users.users.swarsel.home}/.ssh/sops" ]; + age.sshKeyPaths = mkIfElse config.swarselsystems.isBtrfs [ "/persist/.ssh/sops" ] [ "${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/profiles/common/nixos/users.nix b/profiles/common/nixos/users.nix index d99e5de..ed97913 100644 --- a/profiles/common/nixos/users.nix +++ b/profiles/common/nixos/users.nix @@ -7,6 +7,7 @@ users.swarsel = { isNormalUser = true; description = "Leon S"; + password = lib.mkIf config.swarselsystems.initialSetup "setup"; hashedPasswordFile = lib.mkIf (!config.swarselsystems.initialSetup) config.sops.secrets.swarseluser.path; extraGroups = [ "networkmanager" "syncthing" "docker" "wheel" "lp" "audio" "video" "vboxusers" "libvirtd" "scanner" ]; packages = with pkgs; [ ]; diff --git a/profiles/iso/minimal.nix b/profiles/iso/minimal.nix index 4d8aa54..3b4d62c 100644 --- a/profiles/iso/minimal.nix +++ b/profiles/iso/minimal.nix @@ -32,6 +32,10 @@ }; }; + security.sudo.extraConfig = '' + Defaults env_keep+=SSH_AUTH_SOCK + ''; + security.pam = { sshAgentAuth.enable = true; services = { @@ -41,6 +45,8 @@ environment.systemPackages = with pkgs; [ curl + git + gnupg rsync ssh-to-age sops @@ -50,7 +56,6 @@ programs = { git.enable = true; - zsh.enable = lib.mkDefault true; }; fileSystems."/boot".options = [ "umask=0077" ]; diff --git a/profiles/server/nixos/syncthing.nix b/profiles/server/nixos/syncthing.nix index 201260c..29be453 100644 --- a/profiles/server/nixos/syncthing.nix +++ b/profiles/server/nixos/syncthing.nix @@ -79,11 +79,11 @@ devices = [ "magicant" "nbl-imba-2" ]; id = "hgr3d-pfu3w"; }; - ".elfeed" = { - path = "/Vault/data/syncthing/.elfeed"; - devices = [ "sync (@oracle)" "magicant" "nbl-imba-2" ]; - id = "h7xbs-fs9v1"; - }; + # ".elfeed" = { + # path = "/Vault/data/syncthing/.elfeed"; + # devices = [ "sync (@oracle)" "magicant" "nbl-imba-2" ]; + # id = "h7xbs-fs9v1"; + # }; }; }; }; diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 7a82b53..44ab973 100644 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -64,6 +64,31 @@ function yes_or_no() { done } +function update_sops_file() { + key_name=$1 + key_type=$2 + key=$3 + + if [ ! "$key_type" == "hosts" ] && [ ! "$key_type" == "users" ]; then + red "Invalid key type passed to update_sops_file. Must be either 'hosts' or 'users'." + exit 1 + fi + cd "${git_root}" + + SOPS_FILE=".sops.yaml" + sed -i "{ + # Remove any * and & entries for this host + /[*&]$key_name/ d; + # Inject a new age: entry + # n matches the first line following age: and p prints it, then we transform it while reusing the spacing + /age:/{n; p; s/\(.*- \*\).*/\1$key_name/}; + # Inject a new hosts or user: entry + /&$key_type/{n; p; s/\(.*- &\).*/\1$key_name $key/} + }" $SOPS_FILE + green "Updating .sops.yaml" + cd - +} + while [[ $# -gt 0 ]]; do case "$1" in -n) @@ -101,15 +126,132 @@ done ssh_cmd="ssh -oport=${ssh_port} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t $target_user@$target_destination" # ssh_root_cmd=$(echo "$ssh_cmd" | sed "s|${target_user}@|root@|") # uses @ in the sed switch to avoid it triggering on the $ssh_key value ssh_root_cmd=${ssh_cmd/${target_user}@/root@} - scp_cmd="scp -oport=${ssh_port} -o StrictHostKeyChecking=no" - git_root=$(git rev-parse --show-toplevel) +# ------------------------ green "Wiping known_hosts of $target_destination" sed -i "/$target_hostname/d; /$target_destination/d" ~/.ssh/known_hosts - +# ------------------------ +green "Preparing a new ssh_host_ed25519_key pair for $target_hostname." +# Create the directory where sshd expects to find the host keys +install -d -m755 "$temp/etc/ssh" +# Generate host ssh key pair without a passphrase +ssh-keygen -t ed25519 -f "$temp/etc/ssh/ssh_host_ed25519_key" -C root@"$target_hostname" -N "" +# Set the correct permissions so sshd will accept the key +chmod 600 "$temp/etc/ssh/ssh_host_ed25519_key" +echo "Adding ssh host fingerprint at $target_destination to ~/.ssh/known_hosts" +# This will fail if we already know the host, but that's fine +ssh-keyscan -p "$ssh_port" "$target_destination" >> ~/.ssh/known_hosts || true +# ------------------------ +# when using luks, disko expects a passphrase on /tmp/disko-password, so we set it for now and will update the passphrase later +# via the config +green "Preparing a temporary password for disko." +green "[Optional] Set disk encryption passphrase:" +read -rs luks_passphrase +if [ -n "$luks_passphrase" ]; then + $ssh_root_cmd "/bin/sh -c 'echo $luks_passphrase > /tmp/disko-password'" +else + $ssh_root_cmd "/bin/sh -c 'echo passphrase > /tmp/disko-password'" +fi +# ------------------------ green "Generating hardware-config.nix for $target_hostname and adding it to the nix-config." -$ssh_root_cmd "nixos-generate-config --no-filesystems --root /mnt" -mkdir profiles/"$target_hostname" -$scp_cmd root@"$target_destination":/mnt/etc/nixos/hardware-configuration.nix "${git_root}"/profiles/"$target_hostname"/hardware-configuration.nix +$ssh_root_cmd "nixos-generate-config --force --no-filesystems --root /mnt" +mkdir -p "$FLAKE"/hosts/nixos/"$target_hostname" +$scp_cmd root@"$target_destination":/mnt/etc/nixos/hardware-configuration.nix "${git_root}"/hosts/nixos/"$target_hostname"/hardware-configuration.nix +# ------------------------ +green "Deploying minimal NixOS installation on $target_destination" +SHELL=/bin/sh nix run github:nix-community/nixos-anywhere -- --ssh-port "$ssh_port" --extra-files "$temp" --flake .#"$target_hostname" root@"$target_destination" + +echo "Updating ssh host fingerprint at $target_destination to ~/.ssh/known_hosts" +ssh-keyscan -p "$ssh_port" "$target_destination" >> ~/.ssh/known_hosts || true +# ------------------------ + +while true; do + read -rp "Press Enter to continue once the remote host has finished booting." + if nc -z "$target_destination" "${ssh_port}" 2> /dev/null; then + green "$target_destination is booted. Continuing..." + break + else + yellow "$target_destination is not yet ready." + fi +done +# ------------------------ +green "Generating an age key based on the new ssh_host_ed25519_key." +target_key=$( + ssh-keyscan -p "$ssh_port" -t ssh-ed25519 "$target_destination" 2>&1 | + grep ssh-ed25519 | + cut -f2- -d" " || + ( + red "Failed to get ssh key. Host down?" + exit 1 + ) +) +host_age_key=$(nix shell nixpkgs#ssh-to-age.out -c sh -c "echo $target_key | ssh-to-age") + +if grep -qv '^age1' <<< "$host_age_key"; then + red "The result from generated age key does not match the expected format." + yellow "Result: $host_age_key" + yellow "Expected format: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + exit 1 +else + echo "$host_age_key" +fi + +green "Updating nix-secrets/.sops.yaml" +update_sops_file "$target_hostname" "hosts" "$host_age_key" +yellow ".sops.yaml has been updated. There may be superfluous entries, you might need to edit manually." +if yes_or_no "Do you want to manually edit .sops.yaml now?"; then + vim "${git_root}"/.sops.yaml +fi +green "Updating all secrets files to reflect updates .sops.yaml" +sops updatekeys --yes --enable-local-keyservice "${git_root}"/secrets/*/secrets.yaml +# -------------------------- +green "Making ssh_host_ed25519_key available to home-manager for user $target_user" +$scp_cmd root@"$target_destination":/etc/ssh/ssh_host_ed25519_key root@"$target_destination":/home/"$target_user"/.ssh/ssh_host_ed25519_key +$ssh_root_cmd "chown $target_user:users /home/swarsel/.ssh/ssh_host_ed25519_key" +# __________________________ + +if yes_or_no "Add ssh host fingerprints for git upstream repositories? (This is needed for building the full config)"; then + if [ "$target_user" == "root" ]; then + home_path="/root" + else + home_path="/home/$target_user" + fi + green "Adding ssh host fingerprints for git{lab,hub}" + $ssh_cmd "mkdir -p $home_path/.ssh/; ssh-keyscan -t ssh-ed25519 gitlab.com github.com swagit.swarsel.win >>$home_path/.ssh/known_hosts" +fi +# -------------------------- + +if yes_or_no "Do you want to copy your full nix-config and nix-secrets to $target_hostname?"; then + green "Adding ssh host fingerprint at $target_destination to ~/.ssh/known_hosts" + ssh-keyscan -p "$ssh_port" "$target_destination" >> ~/.ssh/known_hosts || true + green "Copying full nix-config to $target_hostname" + cd "${git_root}" + just sync "$target_user" "$target_destination" + + if yes_or_no "Do you want to rebuild immediately?"; then + green "Rebuilding nix-config on $target_hostname" + #FIXME:(bootstrap) there are still a gitlab fingerprint request happening during the rebuild + $ssh_cmd -oForwardAgent=yes "cd .dotfiles && sudo nixos-rebuild --show-trace --flake .#$target_hostname switch" + fi +else + echo + green "NixOS was successfully installed!" + echo "Post-install config build instructions:" + echo "To copy nix-config from this machine to the $target_hostname, run the following command from ~/nix-config" + echo "just sync $target_user $target_destination" + echo "To rebuild, sign into $target_hostname and run the following command from ~/nix-config" + echo "cd nix-config" + # see above FIXME:(bootstrap) + echo "sudo nixos-rebuild --show-trace --flake .#$target_hostname switch" + # echo "just rebuild" + echo +fi + +if yes_or_no "You can now commit and push the nix-config, which includes the hardware-configuration.nix for $target_hostname?"; then + cd "${git_root}" + deadnix hosts/nixos/"$target_hostname"/hardware-configuration.nix -qe + (pre-commit run --all-files 2> /dev/null || true) && + git add "$git_root/hosts/$target_hostname/hardware-configuration.nix" && (git commit -m "feat: hardware-configuration.nix for $target_hostname" || true) && git push +fi