699 lines
32 KiB
Bash
699 lines
32 KiB
Bash
#!/usr/bin/env nix-shell
|
|
#!nix-shell -i bash -p gum xkcdpass sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto
|
|
|
|
NECESSARY_VARIABLES_LIST=("TARGET_HOST" "SSH_PUBLIC_KEY" "DOMAIN_NAME" "EMAIL_ADDRESS" \
|
|
"CF_DNS_API_TOKEN" "SENDER_EMAIL_ADDRESS" "SENDER_EMAIL_ADDRESS_PASSWORD" \
|
|
"SENDER_EMAIL_DOMAIN" "SENDER_EMAIL_PORT" "HOME_ROUTER_SUBNET" "HOME_ROUTER_IP" \
|
|
"HOME_SERVER_IP")
|
|
|
|
INSTALLED_REMOTE_PASS="changeMe!"
|
|
|
|
necessary_credentials() {
|
|
#TARGET SETTINGS
|
|
echo -e "\n\n ➡️ Please provide the IP address of the target host :"
|
|
export TARGET_HOST="$(gum input --placeholder "192.168.1.100")"
|
|
echo -e "\n\n ➡️ Please provide the public SSH key of an authorized device :"
|
|
export SSH_PUBLIC_KEY="$(gum input --placeholder "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhcYDmjMo5YApLkk/3P3HZCnOSzm0uYewNAbxL8Fci8 user@your-pc")"
|
|
|
|
# TRAEFIK SETTINGS
|
|
echo -e "\n\n ➡️ Please provide the domain name (FQDN) your home server will use :"
|
|
export DOMAIN_NAME="$(gum input --placeholder "yourdomain.com")"
|
|
echo -e "\n\n ➡️ Please provide a valid email address (will be used for ACME, and your services) :"
|
|
export EMAIL_ADDRESS="$(gum input --placeholder "myemail@gmail.com")"
|
|
echo -e "\n\n ➡️ Please provide a cloudflare API token with DNS zone permission :"
|
|
export CF_DNS_API_TOKEN="$(gum input --placeholder "bA7hdvCOuXGytlNKohi3ZGtlVpf5CHpLuCMiJrE")"
|
|
|
|
# SMTP SETTINGS
|
|
echo -e "\n\n ➡️ Some services will be able to send you emails. For that you need an email that supports sending emails.\n Please provide a valid sender email address :"
|
|
export SENDER_EMAIL_ADDRESS="$(gum input --placeholder "myemail@gmail.com")"
|
|
echo -e "\n\n ➡️ Please provide the password of this email address :"
|
|
export SENDER_EMAIL_ADDRESS_PASSWORD="$(gum input --placeholder "abcd efgh ijkl mnop")"
|
|
echo -e "\n\n ➡️ Please provide the SMTP server endpoint :"
|
|
export SENDER_EMAIL_DOMAIN="$(gum input --placeholder "smtp.gmail.com")"
|
|
echo -e "\n\n ➡️ Please provide the smtp TLS port (for gmail : 587) :"
|
|
export SENDER_EMAIL_PORT="$(gum input --placeholder "587")"
|
|
|
|
# NETWORK SETTINGS
|
|
echo -e "\n\n ➡️ Please provide your home network subnet :"
|
|
export HOME_ROUTER_SUBNET="$(gum input --placeholder "192.168.1.1/24")"
|
|
echo -e "\n\n ➡️ Please provide the ip address of your router :"
|
|
export HOME_ROUTER_IP="$(gum input --placeholder "192.168.1.1")"
|
|
echo -e "\n\n ➡️ Please choose the ip address that your server will use (i.e. any address in the 192.168.1.1/24 range that is not in use.) :"
|
|
export HOME_SERVER_IP="$(gum input --placeholder "192.168.1.5")"
|
|
}
|
|
|
|
necessary_credentials_with_config() {
|
|
echo -e "\n\n ➡️ Please choose your configuration file :"
|
|
CONFIG_PATH="$(gum file)"
|
|
|
|
source "$CONFIG_PATH"
|
|
MISSING=0
|
|
for VAR in "${NECESSARY_VARIABLES_LIST[@]}"; do
|
|
if [[ -v $VAR && -n ${!VAR} ]]; then
|
|
echo -e "\n ✅ $VAR imported successfully from the config file"
|
|
export $VAR
|
|
else
|
|
echo "\n ❌ $VAR is missing or empty"
|
|
MISSING=1
|
|
fi
|
|
done
|
|
|
|
if [[ "$MISSING" -eq "1" ]]; then
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
setup_ssh() {
|
|
echo -e "\n\n ✅ Generating new SSH for numbus-admin..."
|
|
mkdir -p extra-files/home/numbus-admin/.ssh/
|
|
chmod 700 extra-files/home/numbus-admin/.ssh/
|
|
ssh-keygen -t "ed25519" -C "numbus-admin@numbus-server" -f "extra-files/home/numbus-admin/.ssh/id_ed25519" -N "" -q
|
|
|
|
LIVE_REMOTE_PASS=$(gum input --password --placeholder "Enter password for 'nixos@$TARGET_HOST'")
|
|
if [ -z "$LIVE_REMOTE_PASS" ]; then
|
|
echo " ❌ Password is required to proceed. Aborting."
|
|
exit 1
|
|
fi
|
|
echo -e "\n\n ➡️ Copying SSH key to target host 'nixos@$TARGET_HOST'..."
|
|
if sshpass -p "$LIVE_REMOTE_PASS" ssh-copy-id -o StrictHostKeyChecking=no -i "extra-files/home/numbus-admin/.ssh/id_ed25519" "nixos@$TARGET_HOST"; then
|
|
echo " ✅ SSH key copied successfully."
|
|
else
|
|
echo " ❌ Failed to copy SSH key. Please check the host IP and password."
|
|
exit 1
|
|
fi
|
|
export LIVE_REMOTE_PASS
|
|
}
|
|
|
|
ssh_to_live_host() {
|
|
ARG="$1"
|
|
ssh -i "extra-files/home/numbus-admin/.ssh/id_ed25519" "nixos@$TARGET_HOST" $ARG
|
|
}
|
|
|
|
ssh_to_installed_host() {
|
|
ARG="$1"
|
|
ssh -i "extra-files/home/numbus-admin/.ssh/id_ed25519" "numbus-admin@$TARGET_HOST" $ARG
|
|
}
|
|
|
|
hardware_detection() {
|
|
echo -e "\n\n 🔎 Detecting graphics card on target host..."
|
|
VGA_INFO=$(ssh_to_live_host "lspci -nn | grep -i 'vga'")
|
|
if echo "$VGA_INFO" | grep -iq "intel" 2>/dev/null; then
|
|
echo -e " ✅ Intel graphics card detected."
|
|
export TARGET_GRAPHICS="true"
|
|
elif echo "$VGA_INFO" | grep -iq "amd" 2>/dev/null; then
|
|
echo -e " ✅ AMD graphics card detected."
|
|
export TARGET_GRAPHICS="true"
|
|
elif echo "$VGA_INFO" | grep -iq "nvidia" 2>/dev/null; then
|
|
echo -e " ✅ NVIDIA graphics card detected."
|
|
export TARGET_GRAPHICS="true"
|
|
else
|
|
echo -e " ⚠️ No dedicated graphics card detected."
|
|
export TARGET_GRAPHICS="false"
|
|
fi
|
|
echo -e "\n\n 🔎 Detecting transconding acceleration on target host..."
|
|
if ssh_to_live_host "ls /dev/dri/ | grep -iq 'renderD128'" 2>/dev/null; then
|
|
echo -e " ✅ Transcoding capable card detected."
|
|
TARGET_GRAPHICS_RENDERER="true"
|
|
else
|
|
echo -e " ⚠️ No transcoding capable card detected."
|
|
TARGET_GRAPHICS_RENDERER="false"
|
|
fi
|
|
echo -e "\n\n 🔎 Detecting USB Google Coral TPU on target host..."
|
|
if ssh_to_live_host "lsusb | grep -iq 'google'" 2>/dev/null; then
|
|
echo -e " ✅ USB Google Coral TPU detected."
|
|
TARGET_USB_CORAL="true"
|
|
else
|
|
echo -e " ⚠️ No USB Google Coral TPU detected."
|
|
TARGET_USB_CORAL="false"
|
|
fi
|
|
echo -e "\n\n 🔎 Detecting Zigbee coordinator on target host..."
|
|
if ssh_to_live_host "ls /dev/serial/by-id/ | grep -i 'zigbee'" 2>/dev/null; then
|
|
echo -e " ✅ Zigbee device found in /dev/serial/by-id/."
|
|
TARGET_ZIGBEE_DEVICE=$(ssh_to_live_host "ls /dev/serial/by-id/ | grep -i 'zigbee'")
|
|
else
|
|
echo -e " ⚠️ No Zigbee device found."
|
|
TARGET_ZIGBEE_DEVICE=""
|
|
fi
|
|
}
|
|
|
|
services_selection() {
|
|
echo -e "\n\n ➡️ You will now select the services you want installed on your server:"
|
|
|
|
AVAILABLE_SERVICES=( "frigate" "gitea" "home-assistant" "immich" "it-tools" \
|
|
"nextcloud" "passbolt" "pi-hole" )
|
|
AVAILABLE_SERVICES_NUMBER=${#AVAILABLE_SERVICES[@]}
|
|
|
|
SERVICES_DESCRIPTION=( "Pi-Hole : Block ads on all your devices" \
|
|
"Immich : Pictures and videos backup with local machine-learning" \
|
|
"Nextcloud : No fuss Office 365 replacement" \
|
|
"Passbolt: Security-first password manager with collaboration features" \
|
|
"Home-Assistant : Manage your smart home and security cameras" \
|
|
"Frigate [Home Assistant required] : Secure your house with security cameras" \
|
|
"Gitea : Your own git platform" \
|
|
"IT-tools : A set of useful tools when doing IT" \
|
|
)
|
|
|
|
SELECTED_SERVICES_DESCRIPTION=$(gum choose --no-limit --header "Homelab services:" "${SERVICES_DESCRIPTION[@]}")
|
|
|
|
for i in $(seq 0 $((${#AVAILABLE_SERVICES[@]} - 1))); do
|
|
if printf '%s' "$SELECTED_SERVICES_DESCRIPTION" | grep -iq "${AVAILABLE_SERVICES[$i]}"; then
|
|
SELECTED_SERVICES+=(${AVAILABLE_SERVICES[$i]})
|
|
fi
|
|
done
|
|
}
|
|
|
|
files_generation() {
|
|
echo -e "\n ✅ Writing configuration files for the selected homelab services..."
|
|
# Traefik
|
|
mkdir -p extra-files/mnt/config-storage/traefik/config/conf/
|
|
envsubst < config-files/docker/config/traefik/traefik.yaml > extra-files/mnt/config-storage/traefik/config/traefik.yaml
|
|
|
|
for service in "${SELECTED_SERVICES[@]}"; do
|
|
# Frigate
|
|
if [[ "$service" -eq "frigate" ]]; then
|
|
echo -e "\n ✅ Adapting the docker configuration to your hardware..."
|
|
FRIGATE_DEVICES_BLOCK=""
|
|
if [[ "$TARGET_GRAPHICS_RENDERER" -eq "true" ]]; then
|
|
FRIGATE_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
|
|
fi
|
|
if [[ "$TARGET_USB_CORAL" -eq "true" ]]; then
|
|
FRIGATE_DEVICES_BLOCK+=" - /dev/bus/usb:/dev/bus/usb\n"
|
|
fi
|
|
if [[ -n "$FRIGATE_DEVICES_BLOCK" ]]; then
|
|
REPLACEMENT="devices:\n${FRIGATE_DEVICES_BLOCK%\\n}"
|
|
sed -i.bak "s|# --- frigate devices --- #|$REPLACEMENT|" ./config-files/docker/compose/frigate.nix
|
|
else
|
|
sed -i.bak "/# --- frigate devices --- #/d" ./config-files/docker/compose/frigate.nix
|
|
fi
|
|
# Home-Assistant
|
|
elif [[ "$service" -eq "home-assistant" ]]; then
|
|
if [[ -n "$TARGET_ZIGBEE_DEVICE" ]]; then
|
|
REPLACEMENT="devices:\n - /dev/serial/by-id/${TARGET_ZIGBEE_DEVICE}:/dev/ttyUSB0"
|
|
sed -i.bak "s|# --- hass devices --- #|$REPLACEMENT|" ./config-files/docker/compose/home-assistant.nix
|
|
else
|
|
sed -i.bak "/# --- hass devices --- #/d" ./config-files/docker/compose/home-assistant.nix
|
|
fi
|
|
export HOME_ASSISTANT_MQTT_USER="$(xkcdpass -d "-" -n 2)"
|
|
export HOME_ASSISTANT_MQTT_PASSWORD="$(xkcdpass -d "-")"
|
|
mkdir -p extra-files/mnt/config-storage/hass/mqtt/config/
|
|
mkdir -p extra-files/mnt/config-storage/hass/mqtt/data/
|
|
envsubst < config-files/docker/config/hass/mosquitto.conf > extra-files/mnt/config-storage/hass/mqtt/config/mosquitto.conf
|
|
touch extra-files/mnt/config-storage/hass/mqtt/config/password.txt
|
|
chmod 0700 extra-files/mnt/config-storage/hass/mqtt/config/password.txt
|
|
mosquitto_passwd -b extra-files/mnt/config-storage/hass/mqtt/config/password.txt $HOME_ASSISTANT_MQTT_USER $HOME_ASSISTANT_MQTT_PASSWORD
|
|
# Passbolt
|
|
elif [[ "$service" -eq "passbolt" ]]; then
|
|
export PASSBOLT_DB_NAME="$(xkcdpass -d "-" -n 2)"
|
|
export PASSBOLT_DB_USERNAME="$(xkcdpass -d "-" -n 2)"
|
|
export PASSBOLT_DB_PASSWORD="$(xkcdpass -d "-")"
|
|
envsubst < config-files/docker/config/traefik/headers.yaml > extra-files/mnt/config-storage/traefik/config/conf/headers.yaml
|
|
envsubst < config-files/docker/config/traefik/tls.yaml > extra-files/mnt/config-storage/traefik/config/conf/tls.yaml
|
|
# Pi-Hole
|
|
elif [[ "$service" -eq "pi-hole" ]]; then
|
|
export FTLCONF_WEBSERVER_PASSWORD="$(xkcdpass -d "-")"
|
|
# Immich
|
|
elif [[ "$service" -eq "immich" ]]; then
|
|
IMMICH_DEVICES_BLOCK=""
|
|
if [[ "$TARGET_GRAPHICS_RENDERER" -eq "true" ]]; then
|
|
IMMICH_DEVICES_BLOCK+=" - /dev/dri:/dev/dri\n"
|
|
fi
|
|
if [[ -n "$IMMICH_DEVICES_BLOCK" ]]; then
|
|
REPLACEMENT="devices:\n${IMMICH_DEVICES_BLOCK%\\n}"
|
|
sed -i.bak "s|# --- immich devices --- #|$REPLACEMENT|" ./config-files/docker/compose/immich.nix
|
|
else
|
|
sed -i.bak "/# --- immich devices --- #/d" ./config-files/docker/compose/immich.nix
|
|
fi
|
|
export IMMICH_DB_NAME="$(xkcdpass -d "-" -n 2)"
|
|
export IMMICH_DB_USERNAME="$(xkcdpass -d "-" -n 2)"
|
|
export IMMICH_DB_PASSWORD="$(xkcdpass -d "-")"
|
|
mkdir -p extra-files/mnt/data-storage/immich/
|
|
elif [[ "$service" -eq "gitea" ]]; then
|
|
export GITEA_DB_NAME="$(xkcdpass -d "-" -n 2)"
|
|
export GITEA_DB_USERNAME="$(xkcdpass -d "-" -n 2)"
|
|
export GITEA_DB_PASSWORD="$(xkcdpass -d "-")"
|
|
elif [[ "$service" -eq "nextcloud" ]]; then
|
|
envsubst < config-files/docker/config/traefik/nextcloud.yaml > extra-files/mnt/config-storage/traefik/config/conf/nextcloud.yaml
|
|
mkdir -p extra-files/mnt/data-storage/nextcloud/
|
|
fi
|
|
cp ./config-files/docker/compose/${service}.nix ./nix-config/docker/${service}.nix
|
|
done
|
|
|
|
echo -e "\n ✅ Generating sops-nix keys..."
|
|
mkdir -p extra-files/etc/secrets/disks/
|
|
mkdir -p extra-files/var/lib/sops-nix/
|
|
mkdir -p extra-files/etc/nixos/secrets/
|
|
|
|
ssh-to-age -private-key -i extra-files/home/numbus-admin/.ssh/id_ed25519 > extra-files/var/lib/sops-nix/key.txt
|
|
export SOPS_PUBLIC_KEY=$(age-keygen -y extra-files/var/lib/sops-nix/key.txt)
|
|
|
|
echo -e "\n ✅ Generating sops-nix configuration files..."
|
|
envsubst < config-files/sops-nix/.sops.yaml > extra-files/etc/nixos/.sops.yaml
|
|
|
|
echo -e "\n ✅ Encrypting secrets in the correct file..."
|
|
envsubst < "config-files/sops-nix/secrets.yaml" | sops encrypt --filename-override secrets.yaml \
|
|
--input-type yaml --output-type yaml \
|
|
--age $SOPS_PUBLIC_KEY \
|
|
--output extra-files/etc/nixos/secrets/secrets.yaml
|
|
|
|
echo -e "\n ✅ Writing correct ips to configuration.nix..."
|
|
sed -i s+HOME_SERVER_IP+$HOME_SERVER_IP+g ./nix-config/misc/networking.nix
|
|
sed -i s+HOME_ROUTER_IP+$HOME_ROUTER_IP+g ./nix-config/misc/networking.nix
|
|
|
|
echo -e "\n ✅ Copying the configuration to the new machine..."
|
|
cp -ravu ./nix-config/* extra-files/etc/nixos/
|
|
}
|
|
|
|
disk_config_generation() {
|
|
### --> Disk wiping warning
|
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "
|
|
⚠️ $(gum style --foreground 212 'WARNING:') You will choose the disks to install NixOS on.
|
|
!! PLEASE MAKE SURE YOU BACKED UP ANY IMPORTANT DATA !!
|
|
!! ALL DATA WILL BE WIPED ON THE DISKS YOU CHOOSE !!
|
|
Please press CTRL+C to abort.
|
|
"
|
|
gum confirm "Do you understand and wish to proceed?" || { echo -e "\n\n ❌ Aborting as requested."; exit 1; }
|
|
|
|
echo -e "\n\n 🔎 Fetching and analyzing disks from target host... (This may take a moment)"
|
|
### Disk wiping warning <--
|
|
|
|
TMPFILE="/tmp/nixos-deployment-temp-file"
|
|
|
|
### --> Get disk information
|
|
DISK_DETAILS=$(ssh_to_live_host 'bash -s' <<EOF
|
|
HDD=1
|
|
|
|
DISK_DEVPATH=()
|
|
DISK_NAME=()
|
|
DISK_TYPE=()
|
|
DISK_HEALTH=()
|
|
DISK_ID=()
|
|
DISK_SIZE=()
|
|
|
|
for DISK in \$(lsblk -x SIZE -d -n -e 7,11 -o NAME); do
|
|
# Disk name and simple path
|
|
DISK_DEVPATH+=("/dev/\$DISK")
|
|
DISK_NAME+=("\$DISK")
|
|
# Disk type
|
|
HDD=\$(cat /sys/block/\$DISK/queue/rotational)
|
|
TRANSPORT_PROTOCOL=\$(lsblk -x SIZE -d -n -e 7,11 -o TRAN /dev/\$DISK)
|
|
if [[ "\$DISK" -eq "nvme*" ]]; then DISK_TYPE+=("NVMe");
|
|
elif [[ "\$TRANSPORT_PROTOCOL" -eq "usb" ]]; then DISK_TYPE+=("USB");
|
|
elif [[ "\$HDD" -eq "1" ]]; then DISK_TYPE+=("HDD");
|
|
elif [[ "\$HDD" -eq "0" ]]; then DISK_TYPE+=("SSD");
|
|
else DISK_TYPE+=("Other")
|
|
fi
|
|
# Disk health
|
|
if [[ \$(echo "$LIVE_REMOTE_PASS" | sudo -S smartctl -H /dev/\$DISK 2>/dev/null | grep 'self-assessment' | awk '{print \$6}') -eq "PASSED" ]]; then
|
|
DISK_HEALTH+=("PASSED")
|
|
else
|
|
DISK_HEALTH+=("N/A")
|
|
fi
|
|
# Disk ID
|
|
DISK_ID+=("\$(ls -l /dev/disk/by-id | grep -m1 "../../\$DISK" | awk '{print "/dev/disk/by-id/" \$9}')")
|
|
# Disk size
|
|
DISK_SIZE+=("\$(lsblk -x SIZE -d -n -e 7,11 -o SIZE /dev/\$DISK)")
|
|
done
|
|
|
|
echo "DISK_DEVPATH=(\${DISK_DEVPATH[@]})" > $TMPFILE
|
|
echo "DISK_NAME=(\${DISK_NAME[@]})" >> $TMPFILE
|
|
echo "DISK_TYPE=(\${DISK_TYPE[@]})" >> $TMPFILE
|
|
echo "DISK_HEALTH=(\${DISK_HEALTH[@]})" >> $TMPFILE
|
|
echo "DISK_ID=(\${DISK_ID[@]})" >> $TMPFILE
|
|
echo "DISK_SIZE=(\${DISK_SIZE[@]})" >> $TMPFILE
|
|
EOF
|
|
)
|
|
|
|
scp -i "extra-files/home/numbus-admin/.ssh/id_ed25519" nixos@$TARGET_HOST:$TMPFILE $TMPFILE &> /dev/null
|
|
source $TMPFILE && rm $TMPFILE
|
|
|
|
### --> Disk selection
|
|
if [[ "${#DISK_NAME[@]}" -eq 0 ]]; then
|
|
echo -e "\n\n ❌ No disks found on the target host. Aborting."
|
|
exit 1
|
|
fi
|
|
|
|
HEADER=$(printf " %-12s %-12s %-12s %-12s %s" "Device" "Type" "Size" "SMART" "Path")
|
|
|
|
for i in ${!DISK_NAME[@]}; do
|
|
GUM_PRINTED_ELEMENT=$(printf "%-12s %-12s %-12s %-12s %s" \
|
|
"${DISK_NAME[${i}]}" "${DISK_TYPE[${i}]}" "${DISK_SIZE[${i}]}" \
|
|
"${DISK_HEALTH[${i}]}" "${DISK_DEVPATH[${i}]}")
|
|
GUM_PRINTED_ELEMENTS+=("$GUM_PRINTED_ELEMENT")
|
|
done
|
|
|
|
gum style --foreground 212 " ➡️ Please choose one (stripe) or two (mirror) disks for your NixOS boot installation :"
|
|
|
|
SELECTED_BOOT_DISK=$(gum choose --limit 2 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}")
|
|
|
|
for i in ${!DISK_NAME[@]}; do
|
|
if printf '%s' "$SELECTED_BOOT_DISK" | grep -iqw "${DISK_NAME[${i}]}"; then
|
|
if [[ -n "${DISK_ID[${i}]}" ]]; then
|
|
export BOOT_DISKS_ID+=("${DISK_NAME[${i}]}")
|
|
else
|
|
export BOOT_DISKS_ID+=("${DISK_ID[${i}]}")
|
|
fi
|
|
unset "GUM_PRINTED_ELEMENTS[${i}]"
|
|
fi
|
|
done
|
|
|
|
if [[ "${#BOOT_DISKS_ID[@]}" -eq 0 ]]; then
|
|
echo -e "\n\n ❌ No boot disk selected. Aborting."
|
|
exit 1
|
|
elif [[ "${#BOOT_DISKS_ID[@]}" -eq 1 ]]; then
|
|
echo -e "\n\n ⚠️ One boot disk selected, continuing with striped boot disk configuration."
|
|
echo -e " Consider using 2 boot disks instead to get data protection features on the boot disks."
|
|
export BOOT_DISK_1_ID=${BOOT_DISKS_ID[0]}
|
|
elif [[ "${#BOOT_DISKS_ID[@]}" -eq 2 ]]; then
|
|
echo -e "\n\n ✅ Two boot disks selected, continuing with mirrored boot disks configuration."
|
|
echo -e "\n\n ⚠️ If the two disks are different sizes, the resulting usable space size will be \
|
|
the one of the smallest disk."
|
|
export BOOT_DISK_1_ID=${BOOT_DISKS_ID[0]}
|
|
export BOOT_DISK_2_ID=${BOOT_DISKS_ID[1]}
|
|
else
|
|
echo -e "\n\n ❌ Unexpected bug. Please contact the developer. Aborting."
|
|
exit 1
|
|
fi
|
|
|
|
gum style --foreground 212 " ➡️ Please choose data and parity disks (up to 9 total) :"
|
|
|
|
SELECTED_DATA_DISK=$(gum choose --limit 9 --header "$HEADER" "${GUM_PRINTED_ELEMENTS[@]}")
|
|
|
|
for i in ${!DISK_NAME[@]}; do
|
|
if printf '%s' "$SELECTED_DATA_DISK" | grep -iq "${DISK_NAME[${i}]}"; then
|
|
if [[ -n ${DISK_ID[${i}]} ]]; then
|
|
export DATA_DISKS_ID+=("${DISK_NAME[${i}]}")
|
|
export DATA_DISKS_TYPE+=("${DISK_TYPE[${i}]}")
|
|
else
|
|
export DATA_DISKS_ID+=("${DISK_ID[${i}]}")
|
|
export DATA_DISKS_TYPE+=("${DISK_TYPE[${i}]}")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
PARITY_DISK_NUMBER=$(((${#DATA_DISKS_ID[@]} + 2) / 3))
|
|
CONTENT_DISK_NUMBER=$((${#DATA_DISKS_ID[@]} - PARITY_DISK_NUMBER))
|
|
### Disk selection <--
|
|
|
|
### --> Selection recap
|
|
RECAP_CONTENT=$(cat <<EOF
|
|
### Disk Configuration Summary
|
|
|
|
Please review the selected disk layout before proceeding.
|
|
|
|
**Boot Disks (${#BOOT_DISKS_ID[@]}):**
|
|
* **Boot 1:** \`${BOOT_DISKS_ID[0]}\`
|
|
$( [[ -n "${BOOT_DISKS_ID[1]:-}" ]] && echo "* **Boot 2:** \`${BOOT_DISKS_ID[1]}\`" || echo "* **Boot 2:** *Not configured*")
|
|
|
|
**Parity Disks ($PARITY_DISK_NUMBER):**
|
|
$(for i in $(seq 0 $((${#DATA_DISKS_ID[@]} - CONTENT_DISK_NUMBER))); do echo "* **Parity ${i}:** `${DATA_DISKS_ID[${i}]}`"; done)
|
|
$( [[ $PARITY_DISK_NUMBER -eq 0 ]] && echo "* *Not configured*")
|
|
|
|
**Data Disks ($CONTENT_DISK_NUMBER):**
|
|
$(for i in $(seq $PARITY_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do echo "* **Data ${i}:** `${DATA_DISKS_ID[${i}]}`"; done)
|
|
$( [[ $CONTENT_DISK_NUMBER -eq 0 ]] && echo "* *Not configured*")
|
|
EOF
|
|
)
|
|
|
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "$RECAP_CONTENT")"
|
|
gum confirm "Proceed with this disk configuration?" || { echo -e "\n\n ❌ Aborting as requested."; exit 1; }
|
|
### Selection recap <--
|
|
|
|
### --> Config generation
|
|
echo -e "\n\n ✅ Generating disko configuration from templates..."
|
|
TEMPLATE_FILE="config-files/disks/templates/boot-${#BOOT_DISKS_ID[@]}.nix"
|
|
(envsubst < "$TEMPLATE_FILE") > ./nix-config/disks/disko.nix
|
|
echo -e "\n ✅ Generated boot disk configuration."
|
|
|
|
# Striped configuration
|
|
if [[ "$CONTENT_DISK_NUMBER" -eq 1 && "$PARITY_DISK_NUMBER" -eq 0 ]]; then
|
|
export j="1"
|
|
export CONTENT_DISK_ID="${DATA_DISKS_ID[0]}"
|
|
(envsubst < "config-files/disks/templates/content.nix") >> ./nix-config/disks/disko.nix
|
|
sed -i "s|/mnt/content-1|/mnt/data-storage|" ./nix-config/disks/disko.nix
|
|
# Mirror configuration
|
|
elif [[ "$CONTENT_DISK_NUMBER" -eq 1 && "$PARITY_DISK_NUMBER" -eq 1 ]]; then
|
|
export CONTENT_DISK_ID="${DATA_DISKS_ID[0]}"
|
|
export PARITY_DISK_ID="${DATA_DISKS_ID[1]}"
|
|
(envsubst < "config-files/disks/templates/mirror.nix") >> ./nix-config/disks/disko.nix
|
|
# SnapRAID configuration
|
|
elif [[ "$CONTENT_DISK_NUMBER" -gt 1 ]]; then
|
|
# Enable SnapRAID
|
|
sed -i "s|# ./disks/snapraid.nix| ./disks/snapraid.nix|" ./nix-config/configuration.nix
|
|
sed -i '$ d' ./config-files/disks/snapraid.nix
|
|
cat <<EOF >> ./config-files/disks/snapraid.nix
|
|
# --> Automatic data disks unlock, generated by deploy.sh on $(date)
|
|
boot.initrd.luks.devices = {
|
|
EOF
|
|
j=0
|
|
for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do
|
|
export ((j++))
|
|
LOOP_DISK="${DATA_DISKS_ID[${i}]}"
|
|
export CONTENT_DISK_ID=${!LOOP_DISK}
|
|
(envsubst < "config-files/disks/templates/content.nix") >> ./nix-config/disks/disko.nix
|
|
cat <<EOF >> ./config-files/disks/snapraid.nix
|
|
"crypted-content-disk-${j}" = {
|
|
device = "${!LOOP_DISK}";
|
|
keyFile = "/etc/secrets/disks/content-disk-${j}";
|
|
};
|
|
EOF
|
|
done
|
|
echo -e "\n ✅ Generated $CONTENT_DISK_NUMBER data disk configuration(s)."
|
|
j=0
|
|
for i in $(seq $PARITY_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do
|
|
export ((j++))
|
|
LOOP_DISK="${DATA_DISKS_ID[${i}]}"
|
|
export PARITY_DISK_ID=${!LOOP_DISK}
|
|
(envsubst < "config-files/disks/templates/parity.nix") >> ./nix-config/disks/disko.nix
|
|
cat <<EOF >> ./config-files/disks/snapraid.nix
|
|
"crypted-parity-disk-${j}" = {
|
|
device = "${!LOOP_DISK}";
|
|
keyFile = "/etc/secrets/disks/parity-disk-${j}}";
|
|
};
|
|
EOF
|
|
done
|
|
echo -e "\n ✅ Generated $PARITY_DISK_NUMBER parity disk configuration(s)."
|
|
# Close the snapraid.nix block
|
|
cat <<'EOF' >> ./config-files/disks/snapraid.nix
|
|
# Automatic data disks unlock <--
|
|
};
|
|
}
|
|
EOF
|
|
cp -avu ./config-files/disks/snapraid.nix ./nix-config/disks/
|
|
fi
|
|
|
|
# Close the disko.nix block
|
|
cat <<'EOF' >> ./nix-config/disks/disko.nix
|
|
};
|
|
};
|
|
}
|
|
EOF
|
|
|
|
echo -e "\n ✅ Final disko configuration created."
|
|
|
|
if [[ -n "${DATA_DISKS_ID[@]}" ]]; then
|
|
for i in ${!DATA_DISKS_ID[@]}; do
|
|
if [[ "${DATA_DISKS_TYPE[${i}]}" -eq "HDD" ]]; then
|
|
DISK_ID_LIST+=("${DATA_DISKS_ID[${i}]}")
|
|
fi
|
|
done
|
|
if [[ -n "${DISK_ID_LIST[@]}" ]]; then
|
|
sed -i "s|DISK_ID_LIST|${DISK_ID_LIST[@]}|" ./config-files/disks/spindown.nix
|
|
cp -avu ./config-files/disks/spindown.nix ./nix-config/disks/spindown.nix
|
|
echo -e "\n ✅ Disk spindown configuration created."
|
|
fi
|
|
fi
|
|
### Config generation <--
|
|
|
|
### --> Generate unlock keys
|
|
for i in ${#BOOT_DISKS_ID[@]}; do
|
|
declare "/etc/secrets/disks/boot-disk-${i}=$(xkcdpass -d "-")"
|
|
done
|
|
for i in $CONTENT_DISK_NUMBER; do
|
|
declare "/etc/secrets/disks/content-disk-${i}=$(xkcdpass -d "-")"
|
|
done
|
|
for i in $PARITY_DISK_NUMBER; do
|
|
declare "/etc/secrets/disks/parity-disk-${i}=$(xkcdpass -d "-")"
|
|
done
|
|
### Generate unlock keys <--
|
|
}
|
|
|
|
deploy() {
|
|
echo -e "\n\n 🔄 Deploying to the remote server..."
|
|
nix run github:nix-community/nixos-anywhere -- \
|
|
--generate-hardware-config nixos-generate-config ./nix-config/hardware-configuration.nix \
|
|
--flake ./nix-config#numbus-server \
|
|
--extra-files extra-files \
|
|
--chown "/home/numbusing a us-admin/" 1000:1000 \
|
|
--target-host nixos@$TARGET_HOST
|
|
|
|
echo -e "\n\n ✅ Installation successfull !"
|
|
sleep 1
|
|
}
|
|
|
|
sum_up() {
|
|
RECAP_CONTENT=$(cat <<EOF
|
|
### Generated Secrets Summary
|
|
|
|
Please save these secrets in a secure location (e.g., a password manager).
|
|
|
|
**Service Credentials:**
|
|
* **Home Assistant MQTT User:** \`$HOME_ASSISTANT_MQTT_USER\`
|
|
* **Home Assistant MQTT Password:** \`$HOME_ASSISTANT_MQTT_PASSWORD\`
|
|
* **Passbolt DB Name:** \`$PASSBOLT_MYSQL_DATABASE\`
|
|
* **Passbolt DB User:** \`$PASSBOLT_MYSQL_USER\`
|
|
* **Passbolt DB Password:** \`$PASSBOLT_MYSQL_PASSWORD\`
|
|
* **Pi-hole Web Password:** \`$FTLCONF_WEBSERVER_PASSWORD\`
|
|
* **Immich DB Name:** \`$IMMICH_DB_DATABASE_NAME\`
|
|
* **Immich DB User:** \`$IMMICH_DB_USERNAME\`
|
|
* **Immich DB Password:** \`$IMMICH_DB_PASSWORD\`
|
|
|
|
**Disk Encryption Keys:**
|
|
$(for i in {1..2}; do key_var="BOOT_DISK_${i}_KEY"; [[ -n "${!key_var}" ]] && echo "* **Boot Disk $i Key:** \`${!key_var}\`"; done)
|
|
$(for i in {1..6}; do key_var="CONTENT_DISK_${i}_KEY"; [[ -n "${!key_var}" ]] && echo "* **Content Disk $i Key:** \`${!key_var}\`"; done)
|
|
$(for i in {1..3}; do key_var="PARITY_DISK_${i}_KEY"; [[ -n "${!key_var}" ]] && echo "* **Parity Disk $i Key:** \`${!key_var}\`"; done)
|
|
EOF
|
|
)
|
|
|
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "$RECAP_CONTENT")"
|
|
}
|
|
|
|
postrun_action() {
|
|
echo -e "\n\n Now the remote machine will reboot. You will need to input the boot disk(s) passphrase.
|
|
This will be the only time you will have to do so, it will be automatic in the future."
|
|
|
|
gum spin --spinner dot --title "Rebooting the remote..." -- sleep 120
|
|
|
|
gum confirm " ➡️ Select 'yes' once the machine rebooted and you unlocked the disks." || { echo -e "\n\n ❌ Aborting as requested."; exit 1; }
|
|
|
|
gum spin --spinner dot --title "\n\n 🔄 Waiting for the server to boot up..." --auto <<EOF
|
|
while FOUND="false"; do
|
|
if ping -c1 -W1 $HOME_SERVER_IP >/dev/null 2>&1; then
|
|
FOUND="true"
|
|
exit 0
|
|
(i++)
|
|
if [[ "\${i}" -gt 150 ]]; then
|
|
echo -e "\n\n ❌ Could not connect to the server after 150 retries. \
|
|
This is most likely due to a networking issue. Please double check your network settings. Aborting."
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
EOF
|
|
|
|
ssh_to_installed_host 'bash -s' <<EOF
|
|
sed -i "s|# ./disks/pcr-check.nix| ./disks/pcr-check.nix|" /etc/nixos/configuration.nix
|
|
|
|
if [[ ${#BOOT_DISKS_ID[@]} -eq 1 ]]; then
|
|
echo $INSTALLED_REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-1
|
|
elif [[ ${#BOOT_DISKS_ID[@]} -eq 2 ]]; then
|
|
echo $INSTALLED_REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-1
|
|
echo $INSTALLED_REMOTE_PASS | sudo -S systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/mapper/crypted-boot-2
|
|
fi
|
|
|
|
PCR_HASH=\$(echo $INSTALLED_REMOTE_PASS | sudo -S systemd-analyze pcrs 15 --json=short)
|
|
|
|
sed -i "s|# systemIdentity.enable = true;| systemIdentity.enable = true;|" /etc/nixos/configuration.nix
|
|
sed -i "s|# systemIdentity.pcr15 = "PCR_HASH";| systemIdentity.pcr15 = "PCR_HASH";|" /etc/nixos/configuration.nix
|
|
sed -i "s|PCR_HASH|\${PCR_HASH}|" /etc/nixos/configuration.nix
|
|
EOF
|
|
|
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "
|
|
⚠️ $(gum style --foreground 212 'WARNING:') You will now set the password of the numbus-admin user. \
|
|
You will almost never user it. Consider using a very strong password : you can write it down \
|
|
securely on a hidden sheet of paper or add it to your password manager (local with Passbolt \
|
|
any other online password manager provider.)."
|
|
|
|
gum confirm " ➡️ I understand, 'yes' to proceed." || { echo -e "\n\n ❌ Aborting as requested."; exit 1; }
|
|
|
|
echo $INSTALLED_REMOTE_PASS | sudo -S passwd numbus-admin
|
|
}
|
|
|
|
congrats() {
|
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "
|
|
⚠️ $(gum style --foreground 212 'CONGRATULATIONS !!:') You now have a working home server. \
|
|
Data stored on there will be fully yours and protected. Keep in my mind this comes with the \
|
|
responsability of managing it and keeping it secure. Now, you have to log in the webpages of \
|
|
the services you installed. Create an admin account for all of them and configure them (or keep \
|
|
it simple and use defaults) and take care to note down all the passwords. Change all default passwords \
|
|
and create user accounts for your family or friends that will use the server.
|
|
|
|
Cheers !!"
|
|
}
|
|
|
|
nixos_update() {
|
|
echo -e "\n\n 🔄 Updating NixOS on the remote server..."
|
|
echo "coming soon !"
|
|
}
|
|
|
|
set -euo pipefail
|
|
|
|
cat <<EOF
|
|
██████ █████ ███ ███████ █████████
|
|
░░██████ ░░███ ░░░ ███░░░░░███ ███░░░░░███
|
|
░███░███ ░███ ████ █████ █████ ███ ░░███░███ ░░░
|
|
░███░░███░███ ░░███ ░░███ ░░███ ░███ ░███░░█████████
|
|
░███ ░░██████ ░███ ░░░█████░ ░███ ░███ ░░░░░░░░███
|
|
░███ ░░█████ ░███ ███░░░███ ░░███ ███ ███ ░███
|
|
█████ ░░█████ █████ █████ █████ ░░░███████░ ░░█████████
|
|
░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░░
|
|
|
|
|
|
|
|
█████████ █████
|
|
███░░░░░███ ░░███
|
|
░███ ░███ ████████ █████ ████ █████ ███ █████ ░███████ ██████ ████████ ██████
|
|
░███████████ ░░███░░███ ░░███ ░███ ░░███ ░███░░███ ░███░░███ ███░░███░░███░░███ ███░░███
|
|
░███░░░░░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███████ ░███ ░░░ ░███████
|
|
░███ ░███ ░███ ░███ ░███ ░███ ░░███████████ ░███ ░███ ░███░░░ ░███ ░███░░░
|
|
█████ █████ ████ █████ ░░███████ ░░████░████ ████ █████░░██████ █████ ░░██████
|
|
░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░███ ░░░░ ░░░░ ░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░░
|
|
███ ░███
|
|
░░██████
|
|
░░░░░░
|
|
EOF
|
|
|
|
sleep 1
|
|
|
|
# Choose the action
|
|
ACTION_ANSWER=$(gum choose "[1] 🌐 Deploy NixOS on a remote machine" "[2] 💽 Deploy NixOS on a remote machine with a file configuration" "[3] 🛠️ Update a NixOS remote machine")
|
|
echo $ACTION_ANSWER
|
|
|
|
if [[ "$ACTION_ANSWER" -eq "[1] 🌐 Deploy NixOS on a remote machine" ]]; then
|
|
echo -e "\n ➡️ Proceeding with deployment…"
|
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : start the computer and boot into the NixOS iso.
|
|
Launch a console and set up a new user password."
|
|
gum confirm "Do you understand and wish to proceed?" || { echo " ❌ Aborting as requested."; exit 1; }
|
|
necessary_credentials
|
|
setup_ssh
|
|
hardware_detection
|
|
services_selection
|
|
files_generation
|
|
disk_config_generation
|
|
deploy
|
|
postrun_action
|
|
congrats
|
|
elif [[ "$ACTION_ANSWER" -eq "[2] 💽 Deploy NixOS on a remote machine with a file configuration" ]]; then
|
|
echo -e "\n ➡️ Proceeding with deployment using a config file…"
|
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : start the computer and boot into the NixOS iso.
|
|
Launch a console and set up a new user password."
|
|
gum confirm "Do you understand and wish to proceed?" || { echo " ❌ Aborting as requested."; exit 1; }
|
|
necessary_credentials_with_config
|
|
setup_ssh
|
|
hardware_detection
|
|
services_selection
|
|
files_generation
|
|
disk_config_generation
|
|
deploy
|
|
sum_up
|
|
postrun_action
|
|
congrats
|
|
elif [[ "$ACTION_ANSWER" -eq "[3] 🛠️ Update a NixOS remote machine" ]]; then
|
|
echo -e "\n ➡️ Proceeding with update…"
|
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "➡️ On the target host : make sure the NixOS installation you want
|
|
to update is up-and-running, accessible with SSH."
|
|
gum confirm "Do you understand and wish to proceed?" || { echo " ❌ Aborting as requested."; exit 1; }
|
|
nixos_update
|
|
else
|
|
echo "Aborting - you did not type '1, 2 or 3'."
|
|
exit 1
|
|
fi |