Moving to a web based configurator. Huge changes. Surely a lot of bugfixing to do.

This commit is contained in:
Raphaël Numbus
2026-03-28 21:49:24 +01:00
parent e67dc12f42
commit 29d7eac981
19 changed files with 1469 additions and 563 deletions
+96 -421
View File
@@ -1,310 +1,63 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash nano coreutils gnused gum fastfetch xkcdpass sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto curl jq
#!nix-shell -i bash -p bash nano coreutils gnused gum fastfetch xkcdpass sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto curl jq yq python3
### --> Default settings
export GUM_SPIN_SPINNER="minidot"
export GUM_SPIN_SPINNER_BOLD=true
export GUM_SPIN_SHOW_ERROR=true
export GUM_SPIN_TITLE_BOLD=true
launch_configurator() {
local PORT=8088
local CONFIG_FILE="numbus.yaml"
local BRIDGE_SCRIPT="configurator/bridge.py"
NECESSARY_BACKUP_SERVER_VARIABLES_LIST=(
#LIVE TARGET SETTINGS
LIVE_TARGET_IP
LIVE_TARGET_PASSWD
#SERVER SETTINGS
SERVER_LANGUAGE
SERVER_LOCALE
SERVER_TIMEZONE
SERVER_OWNER_NAME
SERVER_USER_EMAIL
SERVER_ADMIN_EMAIL
SERVER_AUTHORIZED_SSH_PUBKEYS
# TRAEFIK SETTINGS
TRAEFIK_CLOUDFLARE_TOKEN
# SMTP SETTINGS
SMTP_SERVER_USERNAME
SMTP_SERVER_PASSWORD
SMTP_SERVER_HOST
SMTP_SERVER_PORT
#NETWORK SETTINGS
NETWORK_SUBNET
NETWORK_ROUTER_IP
NETWORK_HOME_SERVER_IP
)
# Create a more robust Python Bridge
cat << EOF > "${BRIDGE_SCRIPT}"
import http.server
import json
import os
OPTIONAL_BACKUP_SERVER_VARIABLES_LIST=(
# SERVICES SETTINGS
SERVICES_DOMAIN_NAME
SERVICES_SELECTED_SYSTEM_PACKAGES
SERVICES_SELECTED_SYSTEM_SERVICES
SERVICES_SELECTED_WEB_APPLICATIONS
SERVIVCES_SELECTED_WEB_APPLICATIONS_SUBDOMAIN
)
class BridgeHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/logs':
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
if os.path.exists('deploy.log'):
with open('deploy.log', 'r') as f:
lines = f.readlines()
self.wfile.write("".join(lines[-20:]).encode())
return
return http.server.SimpleHTTPRequestHandler.do_GET(self)
NECESSARY_COMPUTER_VARIABLES_LIST=(
# LIVE TARGET SETTINGS
LIVE_TARGET_IP
LIVE_TARGET_PASSWD
# COMPUTER SETTINGS
COMPUTER_LANGUAGE
COMPUTER_LOCALE
COMPUTER_TIMEZONE
COMPUTER_OWNER_NAME
COMPUTER_USER_EMAIL
COMPUTER_ADMIN_EMAIL
COMPUTER_AUTHORIZED_SSH_PUBKEYS
# USER SETTINGS
USER_ADMINISTRATORS
USER_NORMAL_USERS
)
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
if self.path == '/discovery':
with open("live_settings.json", "wb") as f:
f.write(post_data)
self.send_response(200)
self.end_headers()
# Signal Bash that discovery data is ready
with open(".discovery_ready", "w") as f: f.write("1")
OPTIONAL_COMPUTER_VARIABLES_LIST=(
# NETWORK SETTINGS
NETWORK_SUBNET
NETWORK_ROUTER_IP
NETWORK_HOME_COMPUTER_IP
# SERVICES SETTINGS
SERVICES_SELECTED_SYSTEM_PACKAGES
SERVICES_SELECTED_DESKTOP_ENVIRONMENT
SERVICE_SELECTED_GNOME_EXTENSIONS
SERVICES_SELECTED_FLATPAK_APPLICATIONS
SERVICES_SELECTED_WEB_APPLICATIONS
)
elif self.path == '/deploy':
with open("${CONFIG_FILE}", "wb") as f:
f.write(post_data)
self.send_response(200)
self.end_headers()
with open(".deploy_signal", "w") as f: f.write("1")
NECESSARY_SERVER_VARIABLES_LIST=(
#LIVE TARGET SETTINGS
LIVE_TARGET_IP
LIVE_TARGET_PASSWD
#SERVER SETTINGS
SERVER_LANGUAGE
SERVER_LOCALE
SERVER_TIMEZONE
SERVER_OWNER_NAME
SERVER_USER_EMAIL
SERVER_ADMIN_EMAIL
SERVER_AUTHORIZED_SSH_PUBKEYS
# TRAEFIK SETTINGS
TRAEFIK_CLOUDFLARE_TOKEN
# SMTP SETTINGS
SMTP_SERVER_USERNAME
SMTP_SERVER_PASSWORD
SMTP_SERVER_HOST
SMTP_SERVER_PORT
#NETWORK SETTINGS
NETWORK_SUBNET
NETWORK_ROUTER_IP
NETWORK_HOME_SERVER_IP
# SERVICES SETTINGS
SERVICES_DOMAIN_NAME
SERVICES_SELECTED_DNS
SERVICES_SELECTED_SYSTEM
SERVICES_SELECTED_WEB_APPLICATIONS
)
os.chdir("configurator")
http.server.HTTPServer(('localhost', ${PORT}), BridgeHandler).serve_forever()
EOF
OPTIONAL_SERVER_VARIABLES_LIST=(
# SERVICES SETTINGS
SELECTED_DNS_SERVICE_SUBDOMAIN
SELECTED_WEB_APPLICATIONS_SUBDOMAIN
)
# Cleanup old signals
rm -f configurator/.discovery_ready configurator/.deploy_signal configurator/live_settings.json configurator/hardware.json
NECESSARY_TV_VARIABLES_LIST=(
#LIVE TARGET SETTINGS
LIVE_TARGET_IP
LIVE_TARGET_PASSWD
#TV SETTINGS
TV_LANGUAGE
TV_LOCALE
TV_TIMEZONE
TV_OWNER_NAME
TV_USER_EMAIL
TV_ADMIN_EMAIL
TV_AUTHORIZED_SSH_PUBKEYS
#NETWORK SETTINGS
NETWORK_SUBNET
NETWORK_ROUTER_IP
NETWORK_HOME_TV_IP
)
echo -e "🚀 Launching Numbus Configurator..."
python3 "${BRIDGE_SCRIPT}" > /dev/null 2>&1 &
BRIDGE_PID=$!
OPTIONAL_TV_VARIABLES_LIST=(
# SERVICES SETTINGS
SERVICES_SELECTED_SYSTEM_PACKAGES
SERVICES_SELECTED_FLATPAK_APPLICATIONS
SERVICES_SELECTED_WEB_APPLICATIONS
)
# Available DNS services
DNS_SERVICES_LIST=(
"pi-hole"
"adguard"
)
# Available services
WEB_APPLICATIONS_LIST=(
"crafty"
"frigate"
"gitea"
"home-assistant"
"homepage"
"immich"
"it-tools"
"jellyfin"
"n8n"
"netbootxyz"
"nextcloud"
"ntfy"
"odoo"
"passbolt"
"uptime-kuma"
"vscodium"
)
# Available system services
SYSTEM_SERVICES_LIST=(
"clamav"
"virtualization"
)
# Services descriptions
DNS_SERVICES_DESCRIPTION=(
"Pi-hole : Simple, fully open network-wide Ad Blocker"
"AdGuard : Feature-rich network-wide Ad Blocker"
)
WEB_APPLICATIONS_DESCRIPTION=(
"Crafty : A web-based control panel for Minecraft servers"
"Frigate [Home Assistant required] : NVR with real-time local object detection for IP cameras"
"Gitea : Painless self-hosted Git service"
"Home-Assistant : Open source home automation that puts local control and privacy first"
"Homepage : A modern, secure, highly customizable application dashboard"
"Immich : High performance self-hosted photo and video management solution"
"IT-tools : Handy collection of online tools for developers"
"Jellyfin : The Free Software Media System"
"N8n : Workflow automation for technical people"
"netboot.xyz : Network boot various operating system installers and utilities"
"Nextcloud : The most popular self-hosted collaboration platform"
"Ntfy : Send push notifications to your phone or desktop via PUT/POST"
"Odoo : Open Source ERP and CRM"
"Passbolt : Open source password manager for teams"
"Uptime-Kuma : A fancy self-hosted monitoring tool"
"VSCodium : Free/Libre Open Source Software Binaries of VS Code"
)
SYSTEM_SERVICES_DESCRIPTION=(
"ClamAV : An open-source anti-virus"
"Virtualization : Run Virtual Machines (KVM/QEMU) with Libvirt"
)
### Default settings <--
user_input() {
local VAR_NAME="${1}"
local HEADER="${2}"
local PLACEHOLDER="${3}"
local REGEX="${4}"
local ERROR_MSG="${5}"
local SENSITIVE="${6:-false}"
while true; do
[[ "${SENSITIVE}" == "false" ]] && INPUT_VALUE=$(gum input --placeholder "${PLACEHOLDER}" --header "${HEADER}")
[[ "${SENSITIVE}" == "true" ]] && INPUT_VALUE=$(gum input --password --placeholder "${PLACEHOLDER}" --header "${HEADER}")
if [[ -z "${INPUT_VALUE}" ]]; then
echo "❌ Error: Input cannot be empty. Please provide the necessary information."
continue
fi
if [[ -n "${REGEX}" ]]; then
if [[ ! "${INPUT_VALUE}" =~ ${REGEX} ]]; then
echo "❌ Error: ${ERROR_MSG}"
continue
fi
fi
export "${VAR_NAME}"="${INPUT_VALUE}"
break
done
}
strictly_necessary_information() {
export IP_REGEX='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
user_input "LIVE_TARGET_IP" " Please provide the IP address of the target host :" "For example : 192.168.1.100" "${IP_REGEX}" "Invalid IP address format."
user_input "LIVE_TARGET_PASSWD" " Please enter the password for '${TARGET_USER}@${LIVE_TARGET_IP}' :" "${LIVE_TARGET_IP}'s password" "" "" "true"
}
necessary_information() {
# Regex Definitions
local SUBNET_REGEX='^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$'
local DOMAIN_REGEX='^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
local EMAIL_REGEX='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
local PORT_REGEX='^[0-9]{1,5}$'
local SSH_KEY_REGEX='^ssh-[a-z0-9]+ [A-Za-z0-9+/]+.*'
echo -e "\n\n➡️ This script needs information about the target you want to install NixOS on\n"
# LIVE TARGET SETTINGS
user_input "LIVE_TARGET_IP" " Please provide the IP address of the target host :" "For example : 192.168.1.100" "${IP_REGEX}" "Invalid IP address format."
user_input "LIVE_TARGET_PASSWD" " Please enter the password for '${TARGET_USER}@${LIVE_TARGET_IP}' :" "${LIVE_TARGET_IP}'s password" "" "" "true"
echo -e "\n\n➡️ Now provide some information about the server you are deploying\n"
# SERVER SETTINGS
user_input "TIMEZONE" " Please provide the wanted timezone :" "For example : Europe/Paris, Europe/Berlin" "" ""
user_input "LANGUAGE" " Please provide the wanted language :" "For example : FR (for french), EN (for english), DE, IT, etc" "" ""
user_input "LOCALE" " Please provide your locale :" "For example : fr_FR for France, de_DE for Germany, en_US for USA or en_GB for Great-Britain, etc" "" ""
user_input "SERVER_OWNER_NAME" " Please provide the name of the owner of this server :" "For example : Steve" "" ""
user_input "SERVER_USER_EMAIL" " Please provide a valid user email address (to stay informed about your server's health) :" "For example : myemail@gmail.com" "${EMAIL_REGEX}" "Invalid email address format."
user_input "SERVER_ADMIN_EMAIL" " Please provide a valid admin email address (will be used for ACME, and system failures notifications) :" "For example : myemail@gmail.com" "${EMAIL_REGEX}" "Invalid email address format."
user_input "AUTHORIZED_SSH_PUBLIC_KEY" " Please provide a list of SSH public keys of authorized devices :" "For example : ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGhcYDmjMo5YApLkk/3P3HZCnOSzm0uYewNAbxL8Fci8 user@your-pc" "${SSH_KEY_REGEX}" "Invalid SSH key format (must start with ssh-...)." "true"
echo -e "\n\n➡️ You will access your services via a domain name (e.g. cloud.mydomain.com) and containers need credentials to create those subdomains\n"
# TRAEFIK SETTINGS
user_input "DOMAIN_NAME" " Please provide the domain name (FQDN) your home server will use :" "For example : yourdomain.com" "${DOMAIN_REGEX}" "Invalid domain name format."
user_input "CLOUDFLARE_DNS_API_TOKEN" " Please provide a cloudflare API token with DNS zone permission :" "For example : bA7hdvCOuXGytlNKohi3ZGtlVpf5CHpLuCMiJrE" "" "" "true"
echo -e "\n\n➡️ Some services will be able to send you emails. For that you need an email that supports sending emails (like Gmail for example)\n"
# SMTP SETTINGS
user_input "SMTP_SERVER_USERNAME" " Please provide a valid sender email address :" "For example : myemail@gmail.com" "${EMAIL_REGEX}" "Invalid email address format."
user_input "SMTP_SERVER_PASSWORD" " Please provide the password of this email address :" "abcd efgh ijkl mnop" "" "" "true"
user_input "SMTP_SERVER_HOST" " Please provide the SMTP server endpoint :" "For Gmail : smtp.gmail.com" "${DOMAIN_REGEX}" "Invalid domain name format."
user_input "SMTP_SERVER_PORT" " Please provide the smtp TLS port :" "For Gmail : 587" "${PORT_REGEX}" "Invalid port number."
echo -e "\n\n➡️ This server will connect to your local network and you will configure its IP address\n"
# NETWORK SETTINGS
user_input "NETWORK_SUBNET" " Please provide your network subnet :" "For example 192.168.1.0/24" "${SUBNET_REGEX}" "Invalid subnet format (e.g. 192.168.1.1/24)."
user_input "NETWORK_ROUTER_IP" " Please provide the ip address of your router :" "Most likely 192.168.1.1 or 192.168.1.254" "${IP_REGEX}" "Invalid IP address format."
user_input "HOME_SERVER_IP" " 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.) :" "For example 192.168.1.5" "${IP_REGEX}" "Invalid IP address format."
}
import_variables() {
VARIABLES_LIST="${1}"
NECESSARY="${2:-false}"
echo -e "\n\n➡️ Please choose your configuration file :"
local CONFIG_PATH="$(gum file)"
source "${CONFIG_PATH}"
local MISSING=false
for VAR in "${VARIABLES_LIST[@]}"; do
if [[ -v "${VAR}" && -n "${!VAR}" ]]; then
gum style "✅ "${VAR}" imported successfully from the config file"
else
gum style "❌ "${VAR}" is missing or empty"
MISSING=true
fi
done
if [[ "${MISSING}" == "true" ]]; then
if [[ "${NECESSARY}" = "true" ]]; then
echo -e "\n❌ Please check your configuration file to include all necessary variables"
exit 1
fi
fi
if [[ "${DEBUG:-false}" == "true" ]]; then
echo -e "\n✅ Debugging enabled."
export DIR_COPY_FLAGS="ravu"
export FILES_COPY_FLAGS="avu"
else
export DIR_COPY_FLAGS="rau"
export FILES_COPY_FLAGS="au"
fi
echo -e "➡️ Open your browser at: $(gum style --foreground 212 "http://localhost:${PORT}")"
xdg-open "http://localhost:${PORT}" 2>/dev/null || open "http://localhost:${PORT}" 2>/dev/null || true
}
hierarchy_preparation() {
@@ -359,15 +112,15 @@ ssh_to_host() {
hardware_detection() {
### --> Get hardware information
local TMPFILE="/tmp/nixos-installation-hardware-detection-temp-file"
local TMPFILE="/tmp/hw_detection.json"
ssh_to_host 'bash -s' << SSHEND
TARGET_GRAPHICS="false"
TARGET_GRAPHICS_BRAND=()
for brand in Intel AMD NVIDIA; do
if lspci -nn 2>/dev/null | grep -i "vga" | grep -iq "\${brand}"; then
TARGET_GRAPHICS="true"
TARGET_GRAPHICS_BRAND+=("\${brand}")
else
TARGET_GRAPHICS="false"
fi
done
@@ -420,34 +173,47 @@ for DISK in \$(lsblk -x SIZE -d -n -e 7,11 -o NAME); do
done
echo "# Hardware detection results on \$(date)" > "${TMPFILE}"
for var in \
TARGET_GRAPHICS \
TARGET_GRAPHICS_BRAND \
TARGET_GRAPHICS_RENDERER \
TARGET_USB_CORAL \
TARGET_PCIE_CORAL \
TARGET_ZIGBEE_DEVICE \
TARGET_INTERFACE \
TARGET_TPM \
TARGET_TPM_VERSION; do
echo "export \${var}=\${!var}" >> "${TMPFILE}"
done
for var in \
DISK_DEVPATH \
DISK_NAME \
DISK_TYPE \
DISK_HEALTH \
DISK_ID \
DISK_SIZE; do
declare -p \${var} | sed 's/^declare /declare -g /' >> "${TMPFILE}"
done
# Build organized JSON output for yq
cat << EOF > "\${TMPFILE}"
{
"graphics": {
"enabled": \${TARGET_GRAPHICS},
"brands": [ \$(printf '"%s",' "\${TARGET_GRAPHICS_BRAND[@]}" | sed 's/,\$//') ],
"renderer": \${TARGET_GRAPHICS_RENDERER}
},
"tpu": {
"usb": \${TARGET_USB_CORAL},
"pcie": \${TARGET_PCIE_CORAL}
},
"tpm": {
"enabled": \${TARGET_TPM},
"version": "\${TARGET_TPM_VERSION}"
},
"zigbee": {
"device": "\${TARGET_ZIGBEE_DEVICE}"
},
"network": {
"interface": "\${TARGET_INTERFACE}"
},
"disks": [
\$(
count=\${#DISK_NAME[@]}
for i in "\${!DISK_NAME[@]}"; do
echo " {\"name\": \"\${DISK_NAME[\$i]}\", \"path\": \"\${DISK_DEVPATH[\$i]}\", \"type\": \"\${DISK_TYPE[\$i]}\", \"health\": \"\${DISK_HEALTH[\$i]}\", \"id\": \"\${DISK_ID[\$i]}\", \"size\": \"\${DISK_SIZE[\$i]}\"}\$( [[ \$i -lt \$((count-1)) ]] && echo ',' )"
done
)
]
}
EOF
SSHEND
### Get hardware information <--
scp -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}":"${TMPFILE}" "${TMPFILE}" &> /dev/null
source "${TMPFILE}" && rm -rf "${TMPFILE}"
scp -i "final-nix-config/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}":"${TMPFILE}" "hardware.json" &> /dev/null
# Create YAML for NixOS and JSON for the Configurator Website
yq -P '.' hardware.json > hardware.yaml
yq -o=json '.' hardware.yaml > configurator/hardware.json
rm hardware.json
### --> Generate hardware-configuration.nix
if ssh_to_host "sudo nixos-generate-config --no-filesystems --show-hardware-config" > final-nix-config/etc/nixos/hardware-configuration.nix; then
@@ -1026,32 +792,19 @@ nix_update() {
--use-remote-sudo switch --flake final-nix-config/etc/nixos#numbus-server
}
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 !!"
}
set -euo pipefail
clear
fastfetch --logo nixos --logo-padding-left 4 --structure ' '
gum style --align center --width 80 --foreground 212 "
██████ █████ █████
▒▒██████ ▒▒███ ▒▒███
▒███▒███ ▒███ █████ ████ █████████████ ▒███████ █████ ████ █████
▒███▒███ ▒███ █████ ████ █████████████ ▒███████ █████ ████ █████
▒███▒▒███▒███ ▒▒███ ▒▒███ ▒▒███▒▒███▒▒███ ▒███▒▒███▒▒███ ▒▒███ ███▒▒
▒███ ▒▒██████ ▒███ ▒▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒███ ▒▒█████
▒███ ▒▒█████ ▒███ ▒▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒███ ▒▒▒▒███
█████ ▒▒█████ ▒▒████████ █████▒███ █████ ████████ ▒▒████████ ██████
▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒
█████ ▒▒█████ ▒▒████████ █████▒███ █████ ████████ ▒▒████████ ██████
▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒
█████████
███▒▒▒▒▒███
@@ -1062,85 +815,7 @@ gum style --align center --width 80 --foreground 212 "
▒▒█████████ ▒▒██████ █████ ▒▒█████ ▒▒██████ █████
▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒
"
sleep 1
SELECTED_DEVICE=$(gum choose --header "📦 Select the device type to deploy:" \
"numbus-server" \
"numbus-backup-server" \
"numbus-computer" \
"numbus-tv" \
)
SELECTED_MODE=$(gum choose --header "🛠️ Select the deployment strategy for ${SELECTED_DEVICE}:" \
"Semi-interactive (recommended - use a config file)" \
"Interactive (manual input)" \
"Update and Maintain (existing installation)" \
)
if [[ "${SELECTED_MODE}" == "Update and Maintain"* ]]; then
TARGET_USER="numbus-admin"
echo -e "\n➡️ Proceeding with maintenance/update for ${SELECTED_DEVICE}..."
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 \
"➡️ Ensure the remote device is powered on and accessible via SSH."
gum confirm "Ready to proceed?" || { echo "❌ Aborted."; exit 1; }
strictly_necessary_information
setup_ssh
# Maintain legacy update sequence
more_information_config
folder_tree_generation
nix_generation
nix_update
congrats
else
TARGET_USER="nixos"
echo -e "\n➡️ Proceeding with new deployment for ${SELECTED_DEVICE}..."
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 \
"➡️ On the target host: Boot into the NixOS ISO, launch a console, and set a temporary user password."
gum confirm "Ready to proceed?" || { echo "❌ Aborted."; exit 1; }
if [[ "${SELECTED_MODE}" == "Semi-interactive"* ]]; then
import_variables "${VARS_LIST[@]}" "true"
else
strictly_necessary_information
necessary_information
fi
# Standard Deployment Pipeline
hierarchy_preparation
setup_ssh
hardware_detection
# Server-specific logic
if [[ "${SELECTED_DEVICE}" == "numbus-server" ]]; then
services_selection
fi
disks_selection
server_config_generation
network_config_generation
if [[ "${SELECTED_DEVICE}" == "numbus-server" ]]; then
services_config_generation
fi
# Mail setup for server-grade devices
if [[ "${SELECTED_DEVICE}" == *"server"* ]]; then
mail_config_generation
fi
disk_config_generation
keys_generation
sum_up
if [[ "${SELECTED_DEVICE}" == "numbus-server" ]]; then
cloudflare_dns_setup
fi
export_configuration
deploy
postrun_action
fi
launch_configurator