diff --git a/script/deploy.sh b/script/deploy.sh index f9da35e..5811124 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -1,5 +1,5 @@ #!/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 yq python3 +#!nix-shell -i bash -p bash coreutils gnused gum xkcdpass sops ssh-to-age age sshpass envsubst pciutils usbutils mosquitto curl jq yq python3 @@ -14,7 +14,7 @@ echod() { ssh_to_host() { local COMMAND="${1}" - ssh -i "${EXTRA_FILES_PATH}/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}" "${COMMAND}" + ssh -i "${TMP_EXTRA_PATH}/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}" "${COMMAND}" } get_valid_input() { @@ -67,7 +67,7 @@ get_valid_input() { cleanup() { echo -e "\n āœ… Cleaning up..." - rm -${DIR_RM_FLAGS} ${TMP_FILES_PATH}/ + rm -${DIR_RM_FLAGS} "/run/user/$(id -u)/numbus-installer" if [[ -n "${BRIDGE_PID:-}" ]] && ps -p ${BRIDGE_PID} > /dev/null; then kill ${BRIDGE_PID} @@ -78,136 +78,247 @@ hierarchy_preparation() { echod "\n šŸ”„ Preparing the folder hierarchy for the final configuration..." # Extra files folders - mkdir -${MKDIR_FLAGS} ${EXTRA_FILES_PATH}/home/numbus-admin/.ssh/ - mkdir -${MKDIR_FLAGS} ${EXTRA_FILES_PATH}/var/lib/sops-nix/ - mkdir -${MKDIR_FLAGS} ${EXTRA_FILES_PATH}/etc/nixos/secrets/disks - mkdir -${MKDIR_FLAGS} ${EXTRA_FILES_PATH}/etc/nixos/secrets/system - mkdir -${MKDIR_FLAGS} ${EXTRA_FILES_PATH}/etc/nixos/secrets/podman + mkdir -${MKDIR_FLAGS} ${TMP_EXTRA_PATH}/home/numbus-admin/.ssh/ + mkdir -${MKDIR_FLAGS} ${TMP_EXTRA_PATH}/var/lib/sops-nix/ + mkdir -${MKDIR_FLAGS} ${TMP_EXTRA_PATH}/etc/nixos/secrets/disks + mkdir -${MKDIR_FLAGS} ${TMP_EXTRA_PATH}/etc/nixos/secrets/system + mkdir -${MKDIR_FLAGS} ${TMP_EXTRA_PATH}/etc/nixos/secrets/podman echod "\n āœ… Folder hierarchy ready" } hardware_detection() { - local TMPFILE="/tmp/nixos-installation-hw-detection" + local TMPFILE="/tmp/nixos-installation-hw-detection.json" - ssh_to_host 'bash -s' << SSHEND -TARGET_GRAPHICS_BRAND=() + ssh_to_host "nix-shell -p jq pciutils usbutils smartmontools iproute2 --run 'bash -s'" << SSHEND +set -euo pipefail -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 +# --- Initialize Global JSON Output --- +HW_REPORT=\$(jq -n '{}') -ls /dev/dri/ > /dev/null 2>&1 | grep -iq "renderD128" && TARGET_GRAPHICS_RENDERER="true" || TARGET_GRAPHICS_RENDERER="false" -lsusb > /dev/null 2>&1 | grep -iq "google" && TARGET_USB_CORAL="true" || TARGET_USB_CORAL="false" -lspci -nn > /dev/null 2>&1 | grep -iq "089a" && TARGET_PCIE_CORAL="true" || TARGET_PCIE_CORAL="false" -ls /dev/serial/by-id/ > /dev/null 2>&1 | grep -i "zigbee" && TARGET_ZIGBEE_DEVICE=\$(ls /dev/serial/by-id/ > /dev/null 2>&1 | grep -i "zigbee" | head -n 1) || TARGET_ZIGBEE_DEVICE="" +# --- Helper: Add JSON array to the main report --- +append_to_report() { + local key="\$1" + local json_array="\$2" + HW_REPORT=\$(echo "\$HW_REPORT" | jq --argjson arr "\$json_array" --arg k "\$key" '.[\$k] = \$arr') +} -TARGET_INTERFACE=\$(ip -4 route show default | awk '{print \$5}' | head -n1) +# --- 1. Detect Graphics --- +detect_graphics() { + local gpus="[]" + + # Iterate over all VGA and 3D controllers + while read -r pci_addr; do + local brand="Unknown" + local integrated="false" + local renderer="" -if ls -l /sys/class/tpm/tpm0/ > /dev/null 2>&1; then - TARGET_TPM="true" - TARGET_TPM_VERSION=\$(cat /sys/class/tpm/tpm0/tpm_version_major) -else - TARGET_TPM="false" - TARGET_TPM_VERSION="N/A" -fi + # Determine Brand + local vendor_info + vendor_info=\$(lspci -vms "\$pci_addr" | grep -i "^Vendor:" || true) + if echo "\$vendor_info" | grep -iq "intel"; then + brand="Intel" + integrated="true" # General heuristic for Intel + elif echo "\$vendor_info" | grep -iq "amd"; then + brand="AMD" + # If boot_vga is 1, it's likely the primary/integrated APU + if [[ -f "/sys/bus/pci/devices/0000:\$pci_addr/boot_vga" ]] && grep -q "1" "/sys/bus/pci/devices/0000:\$pci_addr/boot_vga"; then + integrated="true" + fi + elif echo "\$vendor_info" | grep -iq "nvidia"; then + brand="NVIDIA" + integrated="false" # NVIDIA is almost always discrete in these contexts + fi -HDD=1 -DISK_DEVPATH=() -DISK_NAME=() -DISK_TYPE=() -DISK_HEALTH=() -DISK_ID=() + # Find Renderer (e.g., renderD128) + if [[ -d "/sys/bus/pci/devices/0000:\$pci_addr/drm" ]]; then + renderer=\$(find "/sys/bus/pci/devices/0000:\$pci_addr/drm" -maxdepth 1 -name "renderD*" -exec basename {} \; | head -n 1) + [[ -n "\$renderer" ]] && renderer="/dev/dri/\$renderer" + fi -for DISK in \$(lsblk -x SIZE -d -n -e 7,11 -o NAME); do + # Append to array + local gpu_obj + gpu_obj=\$(jq -n --arg b "\$brand" --arg r "\${renderer:-N/A}" --argjson i "\$integrated" \ + '{brand: \$b, renderer: \$r, integrated: \$i}') + gpus=\$(echo "\$gpus" | jq --argjson obj "\$gpu_obj" '. += [\$obj]') + + done < <(lspci -D | grep -iE 'VGA|3D' | awk '{print $1}') - # 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" == "nvme*" ]]; then DISK_TYPE+=("NVMe"); - elif [[ "\$TRANSPORT_PROTOCOL" == "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") + append_to_report "graphics" "\$gpus" +} + +# --- 2. Detect Coral TPUs --- +detect_corals() { + local corals="[]" + + # Check PCIe Coral (Google ID 1ac1:089a) + if lspci -nn 2>/dev/null | grep -iq "1ac1:089a"; then + local pcie_count + pcie_count=\$(lspci -nn | grep -ic "1ac1:089a") + for ((i=1; i<=pcie_count; i++)); do + local obj=\$(jq -n --arg i "PCIe" '{interface: \$i, type: "Edge TPU"}') + corals=\$(echo "\$corals" | jq --argjson obj "\$obj" '. += [\$obj]') + done fi - # Disk health - if [[ \$(echo "${LIVE_TARGET_PASSWORD}" | sudo -S smartctl -H /dev/\$DISK 2>/dev/null | grep 'self-assessment' | awk '{print \$6}') == "PASSED" ]]; then - DISK_HEALTH+=("PASSED") - else - DISK_HEALTH+=("N/A") + # Check USB Coral (Google ID 18d1:9302) + if lsusb 2>/dev/null | grep -iq "18d1:9302"; then + local usb_count + usb_count=\$(lsusb | grep -ic "18d1:9302") + for ((i=1; i<=usb_count; i++)); do + local obj=\$(jq -n --arg i "USB" '{interface: \$i, type: "Edge TPU"}') + corals=\$(echo "\$corals" | jq --argjson obj "\$obj" '. += [\$obj]') + done fi - # Disk ID - DISK_ID+=("\$(ls -l /dev/disk/by-id | grep -m1 "../../\$DISK" | awk '{print "/dev/disk/by-id/" \$9}')") - DISK_SIZE+=("\$(lsblk -x SIZE -d -n -e 7,11 -o SIZE /dev/\$DISK)") -done -echo "# Hardware detection results on \$(date)" > "${TMPFILE}" -for var in \ - TARGET_GRAPHICS \ - 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 + append_to_report "coral_devices" "\$corals" +} -for var in \ - TARGET_GRAPHICS_BRAND \ - DISK_DEVPATH \ - DISK_NAME \ - DISK_TYPE \ - DISK_HEALTH \ - DISK_ID \ - DISK_SIZE; do - declare -p \${var} | sed 's/^declare /declare -g /' >> "${TMPFILE}" -done -SSHEND +# --- 3. Detect Zigbee Coordinators --- +detect_zigbee() { + local zigbees="[]" + local serial_dir="/dev/serial/by-id" - scp -i "${EXTRA_FILES_PATH}/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}":"${TMPFILE}" "${TMPFILE}" &> /dev/null - source "${TMPFILE}" + if [[ -d "\$serial_dir" ]]; then + for dev in "\$serial_dir"/*; do + [[ -e "\$dev" ]] || continue # skip if empty directory pattern matched + # Match common Zigbee adapter names (Sonoff, ConBee, ITead, CC2531, etc.) + if echo "\$dev" | grep -iE 'zigbee|conbee|sonoff|cc2531|efr32|itead'; then + local obj=\$(jq -n --arg p "\$dev" '{device_path: \$p}') + zigbees=\$(echo "\$zigbees" | jq --argjson obj "\$obj" '. += [\$obj]') + fi + done + fi - local DISK_FLAT_ARRAY=() - for i in "${!DISK_NAME[@]}"; do - DISK_FLAT_ARRAY+=("${DISK_NAME[$i]}" "${DISK_DEVPATH[$i]}" "${DISK_TYPE[$i]}" "${DISK_HEALTH[$i]}" "${DISK_ID[$i]}" "${DISK_SIZE[$i]}") + append_to_report "zigbee_devices" "\$zigbees" +} + +# --- 4. Detect Network Interfaces --- +detect_network() { + local networks="[]" + local default_iface + default_iface=\$(ip -4 route show default 2>/dev/null | awk '{print \$5}' | head -n1) + + for iface_path in /sys/class/net/*; do + [[ -e "\$iface_path" ]] || continue + local iface + iface=\$(basename "\$iface_path") + + # Skip loopback and virtual interfaces + [[ "\$iface" == "lo" ]] && continue + [[ -L "\$iface_path" && \$(readlink "\$iface_path") == *"virtual"* ]] && continue + + local type="wired" + [[ -d "\$iface_path/wireless" ]] && type="wireless" + + local is_default="false" + [[ "\$iface" == "\$default_iface" ]] && is_default="true" + + local obj=\$(jq -n --arg n "\$iface" --arg t "\$type" --argjson d "\$is_default" \ + '{name: \$n, type: \$t, default: \$d}') + networks=\$(echo "\$networks" | jq --argjson obj "\$obj" '. += [\$obj]') done - jq -n \ - --argjson graphics_enabled "${TARGET_GRAPHICS:-false}" \ - --argjson graphics_renderer "${TARGET_GRAPHICS_RENDERER:-false}" \ - --argjson tpu_usb "${TARGET_USB_CORAL:-false}" \ - --argjson tpu_pcie "${TARGET_PCIE_CORAL:-false}" \ - --argjson tpm_enabled "${TARGET_TPM:-false}" \ - --arg tpm_version "${TARGET_TPM_VERSION:-N/A}" \ - --arg zigbee_device "${TARGET_ZIGBEE_DEVICE:-}" \ - --arg interface "${TARGET_INTERFACE:-}" \ - --argjson brands "$(jq -n '$ARGS.positional' --args ${TARGET_GRAPHICS_BRAND[@]:-})" \ - ' - { - graphics: { enabled: $graphics_enabled, brands: $brands, renderer: $graphics_renderer }, - tpu: { usb: $tpu_usb, pcie: $tpu_pcie }, - tpm: { enabled: $tpm_enabled, version: $tpm_version }, - zigbee: { device: $zigbee_device }, - network: { interface: $interface }, - disks: [ - $ARGS.positional | range(0; length; 6) as $i | { - name: .[$i], path: .[$i+1], type: .[$i+2], health: .[$i+3], id: .[$i+4], size: .[$i+5] - } - ] - }' --args "${DISK_FLAT_ARRAY[@]:-}" > ${HARDWARE_DATA_PATH} + append_to_report "network_interfaces" "\$networks" +} - if ssh_to_host "sudo nixos-generate-config --no-filesystems --show-hardware-config" > ${EXTRA_FILES_PATH}/etc/nixos/hardware-configuration.nix; then +# --- 5. Detect TPM --- +detect_tpm() { + local tpms="[]" + + for tpm_dir in /sys/class/tpm/tpm*; do + [[ -e "\$tpm_dir" ]] || continue + + local name + name=\$(basename "\$tpm_dir") + local version="Unknown" + + if [[ -f "\$tpm_dir/tpm_version_major" ]]; then + version=\$(cat "\$tpm_dir/tpm_version_major") + fi + + local obj=\$(jq -n --arg n "\$name" --arg v "\$version" '{name: \$n, version: \$v}') + tpms=\$(echo "\$tpms" | jq --argjson obj "\$obj" '. += [\$obj]') + done + + append_to_report "tpm" "\$tpms" +} + +# --- 6. Detect Disks --- +detect_disks() { + local disks="[]" + + # lsblk to loop through block devices (ignoring loops, rams, and cdroms) + while read -r disk; do + local dev_path="/dev/\$disk" + + # Disk Type Mapping + local disk_type="Other" + local transport + transport=\$(lsblk -d -n -o TRAN "\$dev_path" 2>/dev/null || echo "") + local rotational + rotational=\$(lsblk -d -n -o ROTA "\$dev_path" 2>/dev/null || echo "1") + + if [[ "\$disk" == nvme* ]]; then + disk_type="NVMe" + elif [[ "\$transport" == "usb" ]]; then + disk_type="USB" + elif [[ "\$rotational" == "1" ]]; then + disk_type="HDD" + elif [[ "\$rotational" == "0" ]]; then + disk_type="SSD" + fi + + # Size in GB + local size_bytes + size_bytes=\$(lsblk -d -n -b -o SIZE "\$dev_path" 2>/dev/null || echo "0") + local size_gb + size_gb=\$(awk "BEGIN {printf \"%.2f\", \$size_bytes/1073741824}") + + # ID via by-id + local disk_id="N/A" + if [[ -d "/dev/disk/by-id" ]]; then + local id_match + id_match=\$(find /dev/disk/by-id/ -type l -not -name "wwn-*" -not -name "nvme-eui*" -printf "%p %l\n" | grep -m1 "/\$disk\$" | awk '{print \$1}') + [[ -n "\$id_match" ]] && disk_id="\$id_match" + fi + + # Health Assessment (Requires smartctl) + local health="N/A" + if echo "${LIVE_TARGET_PASSWORD}" | sudo -S smartctl -H "\$dev_path" >/dev/null 2>&1; then + if smartctl -H "\$dev_path" 2>/dev/null | grep -iq "PASSED\|OK"; then + health="PASSED" + else + health="FAILED" + fi + fi + + local obj=\$(jq -n --arg n "\$disk" --arg dp "\$dev_path" --arg t "\$disk_type" \ + --arg h "\$health" --arg id "\$disk_id" --arg s "\$size_gb" \ + '{name: \$n, device_path: \$dp, type: \$t, health: \$h, id: \$id, size_gb: \$s}') + + disks=\$(echo "\$disks" | jq --argjson obj "\$obj" '. += [\$obj]') + + done < <(lsblk -d -n -o NAME -e 7,11,252) # Exclude loop(7), sr(11), zram(252) + + append_to_report "disks" "\$disks" +} + +# --- Execution --- +detect_graphics +detect_corals +detect_zigbee +detect_network +detect_tpm +detect_disks + +# --- Output --- +echo "\$HW_REPORT" | jq '.' > "$TMPFILE" +SSHEND + + scp -i "${TMP_EXTRA_PATH}/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}":"${TMPFILE}" "${HW_DATA_FILE}" &> /dev/null + + if ssh_to_host "sudo nixos-generate-config --no-filesystems --show-hardware-config" > ${TMP_EXTRA_PATH}/etc/nixos/hardware-configuration.nix; then echo -e "\nāœ… Hardware configuration generated" else echo -e "\nāŒ Failed to generate hardware configuration" @@ -235,18 +346,18 @@ launch_gui() { -# --- MAIN TUI FUNCTIONS ---> +# --- MAIN SCRIPT FUNCTIONS ---> setup_ssh() { echod "\n āœ… Generating new SSH key for numbus-admin..." - chmod 700 ${EXTRA_FILES_PATH}/home/numbus-admin/.ssh/ - ssh-keygen -t "ed25519" -C "numbus-admin@numbus-server" -f "${EXTRA_FILES_PATH}/home/numbus-admin/.ssh/id_ed25519" -N "" -q + chmod 700 ${TMP_EXTRA_PATH}/home/numbus-admin/.ssh/ + ssh-keygen -t "ed25519" -C "numbus-admin@numbus-server" -f "${TMP_EXTRA_PATH}/home/numbus-admin/.ssh/id_ed25519" -N "" -q if [[ ${DEBUG} -eq 1 ]]; then echo -e "\n āž”ļø Copying SSH key to target host '${TARGET_USER}@${LIVE_TARGET_IP}'..." fi - if sshpass -p "${LIVE_TARGET_PASSWORD}" ssh-copy-id -o StrictHostKeyChecking=no -i "${EXTRA_FILES_PATH}/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}"; then + if sshpass -p "${LIVE_TARGET_PASSWORD}" ssh-copy-id -o StrictHostKeyChecking=no -i "${TMP_EXTRA_PATH}/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}"; then if [[ ${DEBUG} -eq 1 ]]; then echo -e "\n āœ… SSH key copied successfully" fi @@ -289,13 +400,13 @@ server_config_generation() { enabledApps: $apps, managementConsole: $cockpit_enabled } - }' > "${EXTRA_FILES_PATH}/etc/nixos/settings.json" + }' > "${TMP_EXTRA_PATH}/etc/nixos/settings.json" echo -e "{\n numbus.settings = builtins.fromJSON (builtins.readFile ./settings.json);\n}" > "${CONFIGURATION_PATH}" # Ensure the settings file is writable by the management service # and that the directory is prepared for local git tracking - chmod 664 "${EXTRA_FILES_PATH}/etc/nixos/settings.json" + chmod 664 "${TMP_EXTRA_PATH}/etc/nixos/settings.json" } # The existing network_config_generation and services_config_generation functions @@ -331,8 +442,8 @@ disk_config_generation() { keys_generation() { for i in $(seq 1 "${#BOOT_DISKS_ID_LIST[@]}"); do PASS="$(xkcdpass)" - echo -n "$PASS" > "${EXTRA_FILES_PATH}/etc/secrets/disks/boot-${i}" - chmod 600 "${EXTRA_FILES_PATH}/etc/secrets/disks/boot-${i}" + echo -n "$PASS" > "${TMP_EXTRA_PATH}/etc/secrets/disks/boot-${i}" + chmod 600 "${TMP_EXTRA_PATH}/etc/secrets/disks/boot-${i}" ssh_to_host 'bash -s' << EOF echo "$LIVE_TARGET_PASSWORD" | sudo -S mkdir -p /etc/secrets/disks/ echo "$LIVE_TARGET_PASSWORD" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/boot-${i}" @@ -341,8 +452,8 @@ EOF done for i in $(seq 1 "$CONTENT_DISK_NUMBER"); do PASS="$(xkcdpass)" - echo -n "$PASS" > "${EXTRA_FILES_PATH}/etc/secrets/disks/content-${i}" - chmod 600 "${EXTRA_FILES_PATH}/etc/secrets/disks/content-${i}" + echo -n "$PASS" > "${TMP_EXTRA_PATH}/etc/secrets/disks/content-${i}" + chmod 600 "${TMP_EXTRA_PATH}/etc/secrets/disks/content-${i}" ssh_to_host 'bash -s' << EOF echo "$LIVE_TARGET_PASSWORD" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/content-${i}" echo "$LIVE_TARGET_PASSWORD" | sudo -S chmod 600 /etc/secrets/disks/content-${i} @@ -350,8 +461,8 @@ EOF done for i in $(seq 1 "$PARITY_DISK_NUMBER"); do PASS="$(xkcdpass)" - echo -n "$PASS" > "${EXTRA_FILES_PATH}/etc/secrets/disks/parity-${i}" - chmod 600 "${EXTRA_FILES_PATH}/etc/secrets/disks/parity-${i}" + echo -n "$PASS" > "${TMP_EXTRA_PATH}/etc/secrets/disks/parity-${i}" + chmod 600 "${TMP_EXTRA_PATH}/etc/secrets/disks/parity-${i}" ssh_to_host 'bash -s' << EOF echo "$LIVE_TARGET_PASSWORD" | sudo -S bash -c "printf '%s' '$PASS' > /etc/secrets/disks/parity-${i}" echo "$LIVE_TARGET_PASSWORD" | sudo -S chmod 600 /etc/secrets/disks/parity-${i} @@ -369,18 +480,18 @@ EOF export SSH_KEYS_FORMATTED echo -e "\n āœ… Generating sops-nix keys..." - ssh-to-age -private-key -i ${EXTRA_FILES_PATH}/home/numbus-admin/.ssh/id_ed25519 > ${EXTRA_FILES_PATH}/var/lib/sops-nix/key.txt - export SOPS_PUBLIC_KEY=$(age-keygen -y ${EXTRA_FILES_PATH}/var/lib/sops-nix/key.txt) + ssh-to-age -private-key -i ${TMP_EXTRA_PATH}/home/numbus-admin/.ssh/id_ed25519 > ${TMP_EXTRA_PATH}/var/lib/sops-nix/key.txt + export SOPS_PUBLIC_KEY=$(age-keygen -y ${TMP_EXTRA_PATH}/var/lib/sops-nix/key.txt) echo -e "\n āœ… Generating sops-nix configuration files..." - envsubst < templates/nix-config/sops-nix/.sops.yaml > ${EXTRA_FILES_PATH}/etc/nixos/.sops.yaml + envsubst < templates/nix-config/sops-nix/.sops.yaml > ${TMP_EXTRA_PATH}/etc/nixos/.sops.yaml echo -e "\n āœ… Encrypting secrets in the correct file..." envsubst < "templates/nix-config/sops-nix/secrets.yaml" \ | sops encrypt --filename-override secrets.yaml \ --input-type yaml --output-type yaml \ --age $SOPS_PUBLIC_KEY \ - --output ${EXTRA_FILES_PATH}/etc/nixos/secrets/secrets.yaml + --output ${TMP_EXTRA_PATH}/etc/nixos/secrets/secrets.yaml } cloudflare_dns_setup() { @@ -485,24 +596,24 @@ cloudflare_dns_setup() { } deploy() { - git -C . add -f "${EXTRA_FILES_PATH}/" + git -C . add -f "${TMP_EXTRA_PATH}/" git -C . add -f "templates/" git -C . add -f "deploy.conf" # Initialize a git repo in the configuration to be deployed # This allows the Management UI on the appliance to commit changes # and provide a local history/rollback UI to the user. - if [ ! -d "${EXTRA_FILES_PATH}/etc/nixos/.git" ]; then - git -C "${EXTRA_FILES_PATH}/etc/nixos" init -q - git -C "${EXTRA_FILES_PATH}/etc/nixos" add . - git -C "${EXTRA_FILES_PATH}/etc/nixos" commit -m "Initial bootstrap via Numbus Deploy" -q + if [ ! -d "${TMP_EXTRA_PATH}/etc/nixos/.git" ]; then + git -C "${TMP_EXTRA_PATH}/etc/nixos" init -q + git -C "${TMP_EXTRA_PATH}/etc/nixos" add . + git -C "${TMP_EXTRA_PATH}/etc/nixos" commit -m "Initial bootstrap via Numbus Deploy" -q fi echo -e "\n\nšŸ”„ Deploying to the remote server..." - nix flake update --flake ./${EXTRA_FILES_PATH}/etc/nixos + nix flake update --flake ./${TMP_EXTRA_PATH}/etc/nixos nix run github:nix-community/nixos-anywhere -- \ - --flake ${EXTRA_FILES_PATH}/etc/nixos#numbus-server \ - --extra-files ${EXTRA_FILES_PATH} \ + --flake ${TMP_EXTRA_PATH}/etc/nixos#numbus-server \ + --extra-files ${TMP_EXTRA_PATH} \ --chown "/home/numbus-admin/" 1000:1000 \ --target-host ${TARGET_USER}@${LIVE_TARGET_IP} @@ -550,7 +661,7 @@ postrun_action() { Do you want to enable automatic disk decryption on boot ?" if gum confirm "āž”ļø I understand, 'yes' to proceed."; then - sshpass -p "${LIVE_TARGET_PASSWORD}" ssh -i "${EXTRA_FILES_PATH}/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}" 'bash -s' << EOF + sshpass -p "${LIVE_TARGET_PASSWORD}" ssh -i "${TMP_EXTRA_PATH}/home/numbus-admin/.ssh/id_ed25519" "${TARGET_USER}@${LIVE_TARGET_IP}" 'bash -s' << EOF echo "Enrolling boot disk key to TPM..." BOOT_DISKS_NAME=(${BOOT_DISKS_NAME[@]}) @@ -593,7 +704,7 @@ securely on a hidden sheet of paper or add it to your password manager (locally echo $LIVE_TARGET_PASSWORD | sudo -S passwd numbus-admin } -# --- MAIN FUNCTIONS ---< +# --- MAIN SCRIPT FUNCTIONS ---< @@ -604,10 +715,11 @@ LIVE_DATA_FILE="../config/live.yaml" HW_DATA_FILE="../config/hardware.yaml" CONFIG_FILE="../config/numbus.yaml" +BRIDGE_SCRIPT="../web/logic/interactive.py" + TARGET_USER="nixos" -TMP_FILES_PATH="/run/user/$(id -u)/numbus-$(date +"%Y-%m-%d-%Hh%M")" -EXTRA_FILES_PATH="${TMP_FILES_PATH}/config" +TMP_EXTRA_PATH="../extra" if [[ ${DEBUG-0} -eq 1 ]]; then FILES_CP_FLAGS="vau" @@ -656,37 +768,17 @@ echo """ /_/|_/\____/_/ /_/____/\____/___/ """ -DEPLOYMENT_STRATEGY=$(gum choose --header "Choose your preferred deployment strategy :" \ -"I don't have a configuration" \ -"I have a valid configuration hosted on a Git platform") - -if [[ "${DEPLOYMENT_STRATEGY}" == "I don't have a configuration" ]]; then - BRIDGE_SCRIPT="../web/logic/interactive.py" - launch_gui - hierarchy_preparation - until [[ -e ../web/signals/hw_detection_ready ]]; do - sleep 5 - done - LIVE_TARGET_IP="$(yq -r '.live_target_ip' ${LIVE_DATA_FILE})" - LIVE_TARGET_PASSWORD="$(yq -r '.live_target_password' ${LIVE_DATA_FILE})" - until [[ -e ../web/signals/configuration_ready ]]; do - sleep 5 - done - until [[ -e ../web/signals/deployment_ready ]]; do - sleep 5 - done -else - BRIDGE_SCRIPT="../web/logic/non-interactive.py" - launch_gui - hierarchy_preparation - until [[ -e ../web/signals/hw_detection_ready ]]; do - sleep 5 - done - until [[ -e ../web/signals/hw_detection_ready ]]; do - sleep 5 - done - until [[ -e ../web/signals/hw_detection_ready ]]; do - sleep 5 - done -fi +launch_gui +hierarchy_preparation +until [[ -e ../web/signals/hw_detection_ready ]]; do + sleep 5 +done +LIVE_TARGET_IP="$(yq -r '.live_target_ip' ${LIVE_DATA_FILE})" +LIVE_TARGET_PASSWORD="$(yq -r '.live_target_password' ${LIVE_DATA_FILE})" +until [[ -e ../web/signals/configuration_ready ]]; do + sleep 5 +done +until [[ -e ../web/signals/deployment_ready ]]; do + sleep 5 +done # --- MAIN LOGIC ---< \ No newline at end of file diff --git a/web/logic/interactive.py b/web/logic/interactive.py index 3d9774e..188074e 100644 --- a/web/logic/interactive.py +++ b/web/logic/interactive.py @@ -6,10 +6,10 @@ import sys ### Variables --> SECRET_PATH = "/run/user/{}/numbus".format(os.getuid()) if os.path.exists("/run/user/{}".format(os.getuid())) else "../secrets" os.makedirs(SECRET_PATH, exist_ok=True) -LOGS_DIR = "../web/logs/" -PAGES_DIR = "../web/pages/" -CONFIG_DIR = "../web/config/" -SIGNALS_DIR = "../web/signal/" +LOGS_DIR = "../logs/" +PAGES_DIR = "../pages/" +CONFIG_DIR = "../config/" +SIGNALS_DIR = "../signal/" ### <-- Variables class BridgeHandler(http.server.SimpleHTTPRequestHandler): @@ -37,7 +37,7 @@ class BridgeHandler(http.server.SimpleHTTPRequestHandler): if self.path == '/discovery': # Store secrets in memory-backed filesystem - with open(os.path.join(SECRET_PATH, "live_settings.json"), "wb") as f: + with open(os.path.join(SECRET_PATH, "live.yaml"), "wb") as f: f.write(post_data) self.send_response(200) self.end_headers() diff --git a/web/logic/non-interactive.py b/web/logic/non-interactive.py deleted file mode 100644 index 3d9774e..0000000 --- a/web/logic/non-interactive.py +++ /dev/null @@ -1,55 +0,0 @@ -import http.server -import json -import os -import sys - -### Variables --> -SECRET_PATH = "/run/user/{}/numbus".format(os.getuid()) if os.path.exists("/run/user/{}".format(os.getuid())) else "../secrets" -os.makedirs(SECRET_PATH, exist_ok=True) -LOGS_DIR = "../web/logs/" -PAGES_DIR = "../web/pages/" -CONFIG_DIR = "../web/config/" -SIGNALS_DIR = "../web/signal/" -### <-- Variables - -class BridgeHandler(http.server.SimpleHTTPRequestHandler): - def do_GET(self): - # Route for logs: /logs?type=out or /logs?type=err - if self.path.startswith('/logs'): - log_type = "out" if "type=err" not in self.path else "err" - log_path = os.path.join(LOGS_DIR, f'deploy-{log_type}.log') - - 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(log_path): - with open(log_path, 'r') as f: - # Read last 50 lines for better context during errors - self.wfile.write("".join(f.readlines()[-50:]).encode()) - return - return http.server.SimpleHTTPRequestHandler.do_GET(self) - - def do_POST(self): - content_length = int(self.headers['Content-Length']) - post_data = self.rfile.read(content_length) - - if self.path == '/discovery': - # Store secrets in memory-backed filesystem - with open(os.path.join(SECRET_PATH, "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(os.path.join(SIGNALS_DIR, ".discovery_ready"), "w") as f: f.write("1") - - elif self.path == '/deploy': - with open(os.path.join(CONFIG_DIR, "numbus.yaml"), "wb") as f: - f.write(post_data) - self.send_response(200) - self.end_headers() - with open(os.path.join(SIGNALS_DIR, ".deploy_signal"), "w") as f: f.write("1") - -os.chdir(PAGES_DIR) -http.server.HTTPServer(('localhost', 8088), BridgeHandler).serve_forever() \ No newline at end of file