diff --git a/README.md b/README.md index f1f781e..da054e5 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ NixOS dotfiles and configuration for various hosts and users. - [`remove-host.sh`](./lib/scripts/remove-host.sh): Remove references to a host. - [`update-keys.sh`](./lib/scripts/update-keys.sh): Update the encryption keys in all relevant files using `sops.yaml` configurations. - [`update.sh`](./lib/scripts/update.sh): Update flake and all git submodules. - - [`install.sh`](./lib/scripts/install.sh): Install or repair a selected NixOS host. - [`submodules/`](./submodules): Flake forks used in the repository, such as [`nixpkgs`](https://github.com/NixOS/nixpkgs) and [`home-manager`](https://github.com/nix-community/home-manager). diff --git a/hosts/common/configs/system/backup/backup.completion.zsh b/hosts/common/configs/system/backup/backup.completion.zsh index 4600624..abfed1b 100644 --- a/hosts/common/configs/system/backup/backup.completion.zsh +++ b/hosts/common/configs/system/backup/backup.completion.zsh @@ -1,7 +1,7 @@ _backup_completion() { local options=( - '-m[specify partition to mount for backup]:partition:($(_partitions))' - '-b[specify backup directory]:backup directory:_files -/' + '-m[Partition to mount for backup]:partition:($(_partitions))' + '-b[Backup directory]:backup directory:_files -/' ) local curcontext="$curcontext" state line typeset -A opt_args diff --git a/hosts/common/configs/system/backup/backup.sh b/hosts/common/configs/system/backup/backup.sh index 77d75e5..ac73d73 100644 --- a/hosts/common/configs/system/backup/backup.sh +++ b/hosts/common/configs/system/backup/backup.sh @@ -10,7 +10,7 @@ usage() { cleanup() { if [ -d "/persist.bak" ]; then btrfs -q subvolume delete "/persist.bak"; fi - if [ -n "${backup_location}" ] && [ -f "${backup_location}.tmp" ]; then rm "${backup_location}.tmp"; fi + if [ -n "${backup_location}" ]; then rm -f "${backup_location}.tmp"; fi if [ -n "${mount_location}" ]; then if mount | grep -q "${mount_location}"; then umount "${mount_location}"; fi diff --git a/hosts/common/configs/system/nix-install/default.nix b/hosts/common/configs/system/nix-install/default.nix new file mode 100644 index 0000000..9e32c80 --- /dev/null +++ b/hosts/common/configs/system/nix-install/default.nix @@ -0,0 +1,22 @@ +{ pkgs, inputs, ... }: +{ + environment.systemPackages = [ + (pkgs.writeShellApplication { + name = "nix-install"; + runtimeInputs = with pkgs; [ + coreutils + iputils + jq + nix + inputs.disko.packages.${system}.disko + ]; + text = builtins.readFile ./install.sh; + }) + ]; + + home-manager.sharedModules = [ + { + programs.zsh.initExtra = builtins.readFile ./install.completion.zsh; + } + ]; +} diff --git a/hosts/common/configs/system/nix-install/install.completion.zsh b/hosts/common/configs/system/nix-install/install.completion.zsh new file mode 100644 index 0000000..c4faa12 --- /dev/null +++ b/hosts/common/configs/system/nix-install/install.completion.zsh @@ -0,0 +1,30 @@ +_nix-install_completion() { + local -a options + options=( + '1:flake:_directories' + '-m[Mode: 'install' or 'repair']:mode:(install repair)' + '-h[Host to configure]:host:($(_list_hosts))' + '-k[Key file to copy to user config]:key:($(_list_keys))' + '-p[LUKS password file to use for encryption]:password_file:_files' + '-c[Copy configuration to target]' + '-r[Reboot after completion]' + ) + + _list_hosts() { + flake="$(realpath ${words[2]})" + if [[ -f "${flake}/flake.nix" ]]; then + nix flake show --quiet --json "${flake}" 2>/dev/null | jq -r '.nixosConfigurations | keys[]' + fi + } + + _list_keys() { + flake="$(realpath ${words[2]})" + if [[ -d "${flake}/secrets" ]]; then + find "${flake}/secrets" -type f -name 'key.txt' | sed -E 's|^.*/secrets/([^/]+)/key.txt$|\1|' | sort -u + fi + } + + _arguments -s $options +} + +compdef _nix-install_completion nix-install diff --git a/hosts/common/configs/system/nix-install/install.sh b/hosts/common/configs/system/nix-install/install.sh new file mode 100644 index 0000000..51c4c07 --- /dev/null +++ b/hosts/common/configs/system/nix-install/install.sh @@ -0,0 +1,173 @@ +usage() { + echo "Usage: $0 flake -m install|repair -h host [-k key] [-p password_file] [-c] [-r]" + echo + echo "Options:" + echo " flake Directory containing the flake.nix file." + echo " -m mode Mode: 'install' or 'repair'." + echo " -h host Host to configure." + echo " -k key Key file to copy to user config." + echo " -p password_file LUKS password file to use for encryption." + echo " -c Copy configuration to target." + echo " -r Reboot after completion." + exit 1 +} + +check_root() { + if [[ "${EUID}" -ne 0 ]]; then + echo "Please run the script as root." + exit 1 + fi +} + +check_network() { + if ! ping -c 1 google.com &>/dev/null; then + echo "Connect to a network before proceeding." + exit 1 + fi +} + +check_flake() { + if [[ ! -f "${flake}/flake.nix" ]]; then + echo "flake.nix not found in ${flake}." + exit 1 + fi +} + +check_host() { + if ! nix flake show --quiet --json "${flake}" 2>/dev/null | jq -e ".nixosConfigurations[\"${host}\"]" &>/dev/null; then + echo "Host '${host}' not found in flake." + exit 1 + fi +} + +check_key() { + if [[ -n "${key}" ]] && [[ ! -f "${flake}/secrets/${key}/key.txt" ]]; then + echo "Key '${key}' not found." + exit 1 + fi +} + +set_password_file() { + if [[ -n "${password_file}" ]]; then + if [[ ! -f "${password_file}" ]]; then + echo "LUKS key file '${password_file}' not found." + exit 1 + fi + + ln -sf "${password_file}" /tmp/installer.key + else + echo "Enter password for LUKS encryption:" + IFS= read -r -s password + echo "Enter password again to confirm: " + IFS= read -r -s password_check + [ "${password}" != "${password_check}" ] + echo -n "${password}" > /tmp/installer.key + unset password password_check + fi +} + +prepare_disk() { + local disko_mode="$1" + root=$(mktemp -d /mnt/install.XXXXXX) + disko -m "${disko_mode}" --yes-wipe-all-disks --root-mountpoint "${root}" "${flake}/hosts/${host}/format.nix" --arg device "\"${device}\"" +} + +copy_keys() { + mkdir -p "${root}/persist/etc/ssh" + cp "${flake}/hosts/${host}/secrets/ssh_host_ed25519_key" "${root}/persist/etc/ssh/ssh_host_ed25519_key" + + for path in "${flake}/hosts/${host}/users"/*; do + if [[ -z "${key}" ]]; then + continue + fi + + user=$(basename "${path}") + mkdir -p "${root}/persist/home/${user}/.config/sops-nix" + cp "${flake}/secrets/${key}/key.txt" "${root}/persist/home/${user}/.config/sops-nix/key.txt" + uid=$(cat "${flake}/hosts/${host}/users/${user}/uid") + gid=100 + chown -R "${uid}:${gid}" "${root}/persist/home/${user}" + done +} + +install() { + nixos-install --root "${root}" --flake "${flake}#${host}" --no-root-passwd +} + +copy_config() { + echo "Copying configuration..." + rm -rf "${root}/persist/etc/nixos" + cp -r "${flake}" "${root}/persist/etc/nixos" +} + +finish() { + echo "Rebooting system..." + trap - EXIT + cleanup + reboot +} + +cleanup() { + rm -f /tmp/installer.key + if [[ -n "${host}" && -n "${device}" ]]; then disko -m "unmount" "${flake}/hosts/${host}/format.nix" --arg device "\"${device}\""; fi + if [[ -d "${root}" ]]; then rmdir "${root}"; fi +} + +check_root +check_network + +if [[ "$#" -lt 1 ]]; then + usage +fi + +flake="$(realpath "$1")" +check_flake +shift + +mode="" +host="" +key="" +password_file="" +copy_config_flag="false" +reboot_flag="false" + +while getopts "m:h:k:p:cr" opt; do + case "${opt}" in + m) mode="${OPTARG}" ;; + h) host="${OPTARG}" ;; + k) key="${OPTARG}" ;; + p) password_file="${OPTARG}" ;; + c) copy_config_flag="true" ;; + r) reboot_flag="true" ;; + *) usage ;; + esac +done + +if [[ -z "${mode}" || -z "${host}" ]]; then + usage +fi + +check_host +check_key +until set_password_file; do echo "Passwords did not match, please try again."; done + +device=$(grep -oP '(?<=device = ")[^"]+' "${flake}/hosts/${host}/default.nix") + +case "${mode}" in + install) + prepare_disk "destroy,format,mount" + copy_keys + install + if [[ "${copy_config_flag}" == "true" ]]; then copy_config; fi + if [[ "${reboot_flag}" == "true" ]]; then finish; fi + ;; + repair) + prepare_disk "mount" + install + if [[ "${reboot_flag}" == "true" ]]; then finish; fi + ;; + *) + echo "Invalid mode: ${mode}" + usage + ;; +esac diff --git a/hosts/common/configs/user/gui/theme/theme.completion.zsh b/hosts/common/configs/user/gui/theme/theme.completion.zsh index ae2caec..2e6bee2 100644 --- a/hosts/common/configs/user/gui/theme/theme.completion.zsh +++ b/hosts/common/configs/user/gui/theme/theme.completion.zsh @@ -1,7 +1,7 @@ _theme_completion() { local options=( - '-m[set mode: light, dark, or toggle]:mode:(light dark toggle)' - '-w[set wallpaper: specify file path]:file:_files' + '-m[Set mode: 'light', 'dark', or 'toggle']:mode:(light dark toggle)' + '-w[Set wallpaper file]:file:_files' ) local curcontext="$curcontext" state line typeset -A opt_args diff --git a/hosts/common/configs/user/gui/theme/theme.sh b/hosts/common/configs/user/gui/theme/theme.sh index 4874380..fd1a280 100644 --- a/hosts/common/configs/user/gui/theme/theme.sh +++ b/hosts/common/configs/user/gui/theme/theme.sh @@ -1,9 +1,9 @@ -WALLPAPER="" -MODE="" +wallpaper="" +mode="" set_wallpaper() { if [[ -f "$1" ]]; then - WALLPAPER="$1" + wallpaper="$1" else echo "Invalid wallpaper path: $1" exit 1 @@ -12,9 +12,9 @@ set_wallpaper() { toggle_mode() { if [[ "$(cat "${CONFIG}"/mode)" = "light" ]]; then - MODE="dark" + mode="dark" else - MODE="light" + mode="light" fi } @@ -24,8 +24,8 @@ usage() { } finish() { - [[ -n "${WALLPAPER}" ]] && ln -sf "${WALLPAPER}" "${CONFIG}"/wallpaper - [[ -n "${MODE}" ]] && echo "${MODE}" > "${CONFIG}"/mode + [[ -n "${wallpaper}" ]] && ln -sf "${wallpaper}" "${CONFIG}"/wallpaper + [[ -n "${mode}" ]] && echo "${mode}" > "${CONFIG}"/mode "${INIT}" > /dev/null "${RELOAD}" > /dev/null @@ -37,7 +37,7 @@ while getopts "m:w:" opt; do m) case "$OPTARG" in light|dark) - MODE="$OPTARG" + mode="$OPTARG" ;; toggle) toggle_mode diff --git a/hosts/eirene/default.nix b/hosts/eirene/default.nix index 03f50b7..4da57d9 100644 --- a/hosts/eirene/default.nix +++ b/hosts/eirene/default.nix @@ -23,6 +23,7 @@ ../common/configs/system/networking ../common/configs/system/nix ../common/configs/system/nix-cleanup + ../common/configs/system/nix-install ../common/configs/system/nix-ld ../common/configs/system/nixpkgs ../common/configs/system/ntp diff --git a/hosts/eirene/format.nix b/hosts/eirene/format.nix index 7ce4f97..cf1818f 100644 --- a/hosts/eirene/format.nix +++ b/hosts/eirene/format.nix @@ -40,6 +40,7 @@ content = { name = "main"; type = "luks"; + passwordFile = "/tmp/installer.key"; settings = { allowDiscards = true; }; diff --git a/hosts/elara/default.nix b/hosts/elara/default.nix index e899ddb..ab79f24 100644 --- a/hosts/elara/default.nix +++ b/hosts/elara/default.nix @@ -27,6 +27,7 @@ ../common/configs/system/networking ../common/configs/system/nix ../common/configs/system/nix-cleanup + ../common/configs/system/nix-install ../common/configs/system/nix-ld ../common/configs/system/nixpkgs ../common/configs/system/ntp diff --git a/hosts/elara/format.nix b/hosts/elara/format.nix index 5775f2a..d1bba3d 100644 --- a/hosts/elara/format.nix +++ b/hosts/elara/format.nix @@ -40,6 +40,7 @@ content = { name = "usb"; type = "luks"; + passwordFile = "/tmp/installer.key"; settings = { allowDiscards = true; }; diff --git a/hosts/installer/README.md b/hosts/installer/README.md index bc7bb8f..1add57d 100644 --- a/hosts/installer/README.md +++ b/hosts/installer/README.md @@ -3,11 +3,83 @@ I have automated myself out of a job. How to use: 1. Boot into installer -2. Unlock luks partition -3. Run the following commands: +2. Unlock luks partition + +3. Connect to the internet with `nmcli` + + - Scan for available networks: + + ```bash + nmcli device wifi list + ``` + - For an open network: + + ```bash + nmcli device wifi connect "" + ``` + - For a secured network: + + ```bash + nmcli device wifi connect "" password "" + ``` + +4. Run `sudo nix-install /etc/nixos -m install|repair -h host [-k key] [-c] [-r]"` + +## Reinstalling the Installer + +1. Download a Minimal Live Nix ISO + + - Visit the official NixOS website: [https://nixos.org/download.html](https://nixos.org/download.html). + - Download the "Minimal ISO image". + +2. Burn the ISO + + - On Windows, use [Rufus](https://rufus.ie) to burn the ISO to a USB drive. + + - On Linux, use the `dd` command: + + ```bash + sudo dd if= of=/dev/sdX bs=4M status=progress + ``` + +3. Boot into USB + +4. Connect to the Internet with `wpa_supplicant` + + - Identify your network interface: + + ```bash + ip link show | grep -E '^[0-9]+:' | awk '{print $2}' | tr -d ':' + ``` + - For an open network: + + ```bash + wpa_supplicant -i "${interface}" -c <(wpa_passphrase "${ssid}") -B + ``` + + - For a secured network: + + ```bash + config=$(mktemp) + wpa_passphrase "${ssid}" "${passphrase}" > "${config}" + wpa_supplicant -i "${interface}" -c "${config}" -B + rm "${config}" + ``` + + - Obtain an IP address: + + ```bash + dhcpcd + ``` + +5. Clone the flake repository + + ```bash + git clone git.karaolidis.com/karaolidis/nix + cd nix ``` - cd /etc/nixos - direnv allow - sudo ./lib/scripts/install.sh - ``` + +6. I really hope you had a backup of the keys, because you must copy them to the repository before the next step. + +7. Run `nix --experimental-features "nix-command flakes" shell nixpkgs#disko nixpkgs#jq -c bash hosts/common/configs/system/nix-install/install.sh nix -m install -h installer -k personal -c` diff --git a/hosts/installer/default.nix b/hosts/installer/default.nix index 8f39e31..16805a1 100644 --- a/hosts/installer/default.nix +++ b/hosts/installer/default.nix @@ -19,6 +19,7 @@ ../common/configs/system/networking ../common/configs/system/nix ../common/configs/system/nix-cleanup + ../common/configs/system/nix-install ../common/configs/system/nix-ld ../common/configs/system/nixpkgs ../common/configs/system/ntp diff --git a/hosts/installer/format.nix b/hosts/installer/format.nix index 3895ded..a808984 100644 --- a/hosts/installer/format.nix +++ b/hosts/installer/format.nix @@ -32,6 +32,7 @@ content = { name = "usb"; type = "luks"; + passwordFile = "/tmp/installer.key"; settings = { allowDiscards = true; }; diff --git a/lib/scripts/add-host.sh b/lib/scripts/add-host.sh index a078905..31e6f19 100755 --- a/lib/scripts/add-host.sh +++ b/lib/scripts/add-host.sh @@ -28,6 +28,5 @@ sed -i "/userKnownHostsFile = lib.strings.concatStringsSep \" \" \[/a\ .. "$(dirname "$0")/update-keys.sh" "$2" echo "Host ${HOST} has been successfully added." -echo "Please generate SSH key pairs for any users that need to connect to user@host." -echo "Use the following command:" +echo "You can generate SSH key pairs for any users that need to connect to user@host using the following command:" echo "ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_${HOST}_" diff --git a/lib/scripts/install.sh b/lib/scripts/install.sh deleted file mode 100755 index 215d6e7..0000000 --- a/lib/scripts/install.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -check_root() { - if [[ "${EUID}" -ne 0 ]]; then - echo "Please run the script as root." - exit 1 - fi -} - -check_network() { - rfkill unblock all - - if ping -c 1 google.com &>/dev/null; then - echo "Network connection detected, skipping Wi-Fi setup." - return - fi - - echo "No network connection detected." - echo "Would you like to connect to a Wi-Fi network? [y/N]" - read -r connect_wifi - - if ! [[ "${connect_wifi}" =~ ^([yY][eE][sS]|[yY])$ ]]; then - echo "Connect to a network before proceeding." - exit 1 - fi - - setup_wifi -} - -setup_wifi() { - echo "Available Wi-Fi interfaces:" - nmcli device status | awk '$2 == "wifi" {print $1}' - - echo "Enter the Wi-Fi interface you want to use:" - read -r interface - - echo "Scanning for Wi-Fi networks..." - nmcli device wifi rescan - - echo "Available Wi-Fi networks:" - nmcli device wifi list - - echo "Enter the SSID of the network:" - read -r ssid - - echo "Is this network open? [y/N]" - read -r open_network - - if [[ "${open_network}" =~ ^([yY][eE][sS]|[yY])$ ]]; then - nmcli device wifi connect "${ssid}" ifname "${interface}" - else - echo "Enter the passphrase:" - read -rs passphrase - nmcli device wifi connect "${ssid}" password "${passphrase}" ifname "${interface}" - fi - - echo "Waiting for a network connection..." - for _ in {1..10}; do - if ping -c 1 google.com &>/dev/null; then - echo "Connected to the network successfully." - return - fi - sleep 1 - done - - echo "Failed to establish a connection within the timeout period." - exit 1 -} - -select_host() { - echo "Available hosts:" - hosts=$(nix --experimental-features "nix-command flakes" flake show --json \ - | nix --experimental-features "nix-command flakes" shell nixpkgs#jq --command jq -r '.nixosConfigurations | keys[]') - echo "${hosts}" - - echo "Enter host:" - read -r host -} - -prepare_disk() { - local mode="$1" - device=$(grep -oP '(?<=device = ")[^"]+' "./hosts/${host}/default.nix") - nix --experimental-features "nix-command flakes" run github:nix-community/disko -- --mode "${mode}" "./hosts/${host}/format.nix" --arg device "\"${device}\"" -} - -copy_keys() { - mkdir -p /mnt/persist/etc/ssh - cp "./hosts/${host}/secrets/ssh_host_ed25519_key" /mnt/persist/etc/ssh/ssh_host_ed25519_key - - for path in "./hosts/${host}/users"/*; do - user=$(basename "${path}") - echo "User detected: ${user}" - - echo "Available keys for ${user}:" - ls ./secrets/*/key.txt - - echo "Enter the key file to copy (or press Enter to skip this user):" - read -r key - - if [[ -z "${key}" ]]; then - echo "Skipping ${user}" - continue - fi - - mkdir -p "/mnt/persist/home/${user}/.config/sops-nix" - cp "${key}" "/mnt/persist/home/${user}/.config/sops-nix/key.txt" - - uid=$(cat "./hosts/${host}/users/${user}/uid") - gid=100 - - chown -R "${uid}:${gid}" "/mnt/persist/home/${user}" - done -} - -copy_config() { - echo "Would you like to copy the current configuration (including keys) to the target system? [y/N]" - read -r copy_config - - if [[ "${copy_config}" =~ ^([yY][eE][sS]|[yY])$ ]]; then - rm -rf /mnt/persist/etc/nixos - cp -r . /mnt/persist/etc/nixos - echo "Configuration copied successfully." - fi -} - -install() { - nixos-install --root /mnt --flake ".#${host}" -} - -main() { - check_root - check_network - select_host - - echo "What would you like to do with ${host}?" - echo "1) Install" - echo "2) Repair" - read -r choice - - case ${choice} in - 1) - prepare_disk "disko" - copy_keys - install - copy_config - echo "Installation complete. Reboot your system." - ;; - 2) - prepare_disk "mount" - install - echo "Repair complete. Reboot your system." - ;; - *) - echo "Invalid choice." - ;; - esac -} - -main