From 7aae0c5ab54a66aea1b160e1a2a46eee95970163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 10:58:41 +0200 Subject: [PATCH 01/91] Added a bootstrap script --- script/start.sh | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 script/start.sh diff --git a/script/start.sh b/script/start.sh new file mode 100644 index 0000000..1af64b6 --- /dev/null +++ b/script/start.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Numbus Installer Bootstrap +# This script clones the repository and launches the NixOS deployment script. + +REPO_URL="https://gittea.dev/numbus/numbus.git" +INSTALL_DIR="/tmp/numbus-installer" + +echo "\n ☁️ Initializing Numbus Installer..." + +# 1. Check for Nix +TEST_FAIL=0 + +if [[ -r /etc/os-release ]] && grep -qi '^ID=nixos\b' /etc/os-release; then + echo -e "\n ✅ NixOS system detected." +else + TEST_FAIL=$((TEST_FAIL + 1)) + echo -e "\n ❌ You are not on a NixOS based system. This is required to continue." +fi + +if [[ "$(uname -m)" == "x86_64" ]]; then + echo -e "\n ✅ x86_64 system detected." +else + TEST_FAIL=$((TEST_FAIL + 1)) + echo -e "\n ❌ You are not on a x86_64 based system. This is required to continue." +fi + +if [[ ${TEST_FAIL} -gt 0 ]]; then + COMPATIBILITY_OVERRIDE=$(gum choose --header "Some compatibility checks failed. The installation will VERY LIKELY fail. Continue ?" \ + "No" \ + "Yes, I know what I am doing") + [[ "${COMPATIBILITY_OVERRIDE}" == "No" ]] && exit 1 + echo -e "\n ⚠️ Continuing anyways, this is not supported by Numbus." +fi + +# 2. Clone/Update the repository +if [[ -e ${INSTALL_DIR}/config/* ]]; then + echo " ⚠️ It seems you have already run this script. Previously generated files need to be cleaned up." + OLD_CONFIG_PATH="${INSTALL_DIR}/trash/$(date +"%Y-%m-%d-%Hh%M")" + mkdir -p ${OLD_CONFIG_PATH} + mv ${INSTALL_DIR} ${OLD_CONFIG_PATH} + echo " ✅ Your files have been moved to the ${OLD_CONFIG_PATH} directory. You can retrieve them there if needed." +fi + +git clone "${REPO_URL}" "${INSTALL_DIR}" -q + +# 3. Launch the deployment script +cd "${INSTALL_DIR}/script" +chmod +x deploy.sh +./deploy.sh \ No newline at end of file -- 2.52.0 From e9628a112c6662c001fa627ecb7122524ded0b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 11:38:52 +0200 Subject: [PATCH 02/91] Moved the compatibility check from the deploy script to the deploy script to the start script. Moved install files and folders to /run/user for better security. --- script/deploy.sh | 35 ----------------------------------- script/start.sh | 2 +- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/script/deploy.sh b/script/deploy.sh index 6e644c3..2817543 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -74,34 +74,6 @@ cleanup() { fi } -compatibility_check() { - TEST_FAIL=0 - - if [[ -r /etc/os-release ]] && grep -qi '^ID=nixos\b' /etc/os-release; then - echo -e "\n ✅ NixOS system detected." - else - TEST_FAIL=$((TEST_FAIL + 1)) - echo -e "\n ❌ You are not on a NixOS based system. This is required to continue." - fi - - if [[ "$(uname -m)" == "x86_64" ]]; then - echo -e "\n ✅ x86_64 system detected." - else - TEST_FAIL=$((TEST_FAIL + 1)) - echo -e "\n ❌ You are not on a x86_64 based system. This is required to continue." - fi - - if [[ ${TEST_FAIL} -gt 0 ]]; then - COMPATIBILITY_OVERRIDE=$(gum choose --header "Some compatibility checks failed. The installation will very likely fail. Continue ?" \ - "No" \ - "Yes, I know what I am doing") - [[ "${COMPATIBILITY_OVERRIDE}" == "No" ]] && exit 1 - [[ "${COMPATIBILITY_OVERRIDE}" != "No" ]] && echo -e "\n ⚠️ Continuing anyways, this is not supported by Numbus." - fi - - return 0 -} - hierarchy_preparation() { echod "\n 🔄 Preparing the folder hierarchy for the final configuration..." @@ -943,13 +915,6 @@ securely on a hidden sheet of paper or add it to your password manager (locally echo $LIVE_TARGET_PASSWORD | sudo -S passwd numbus-admin } - -nix_update() { - echo -e "\n\n🔄 Updating NixOS on the remote server..." - - nixos-rebuild --target-host numbus-admin@${LIVE_TARGET_IP} \ - --use-remote-sudo switch --flake ${EXTRA_FILES_PATH}/etc/nixos#numbus-server -} # --- MAIN FUNCTIONS ---< diff --git a/script/start.sh b/script/start.sh index 1af64b6..d1b8197 100644 --- a/script/start.sh +++ b/script/start.sh @@ -5,7 +5,7 @@ set -euo pipefail # This script clones the repository and launches the NixOS deployment script. REPO_URL="https://gittea.dev/numbus/numbus.git" -INSTALL_DIR="/tmp/numbus-installer" +INSTALL_DIR="/run/user/$(id -u)/numbus-installer" echo "\n ☁️ Initializing Numbus Installer..." -- 2.52.0 From e7e354b0e73cbbdf40a53d2643dc8c1aa3fed31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 11:43:51 +0200 Subject: [PATCH 03/91] Removed compatibility check from deploy script and other now useless stuff. --- script/deploy.sh | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/script/deploy.sh b/script/deploy.sh index 2817543..0b74b93 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -37,7 +37,7 @@ get_valid_input() { # Handle empty input if [[ -z "${INPUT}" ]]; then if [[ "${MANDATORY}" == true ]]; then - gum style --foreground "#ff0000" -- "✖ This field is mandatory." + gum style --foreground "#ff0000" -- " ❌ This field is mandatory." continue else INPUT="" @@ -51,7 +51,7 @@ get_valid_input() { export "${VAR_NAME}"="${INPUT}" break else - gum style --foreground "#ff0000" -- "✖ Invalid format. Please try again." + gum style --foreground "#ff0000" -- " ❌ Invalid format. Please try again." fi else export "${VAR_NAME}"="${INPUT}" @@ -69,7 +69,7 @@ cleanup() { rm -${DIR_RM_FLAGS} ${TMP_FILES_PATH}/ - if ps -p ${BRIDGE_PID:-} > /dev/null; then + if [[ -n "${BRIDGE_PID:-}" ]] && ps -p ${BRIDGE_PID} > /dev/null; then kill ${BRIDGE_PID} fi } @@ -77,14 +77,6 @@ cleanup() { hierarchy_preparation() { echod "\n 🔄 Preparing the folder hierarchy for the final configuration..." - if [[ -e config/* ]]; then - echo " ⚠️ It seems you have already run this script. Previously generated files need to be cleaned up." - OLD_CONFIG_PATH="trash/$(date +"%Y-%m-%d-%Hh%M")/" - mkdir -${MKDIR_FLAGS} ${OLD_CONFIG_PATH} - mv -${MV_FLAGS} config/ ${OLD_CONFIG_PATH} - echo " ✅ Your files have been moved to the ${OLD_CONFIG_PATH} directory. You can retrieve them there if needed." - fi - # Script folders mkdir -${MKDIR_FLAGS} ${TMP_FILES_PATH}/config mkdir -${MKDIR_FLAGS} ${TMP_FILES_PATH}/logs @@ -968,7 +960,6 @@ set -euo pipefail clear trap cleanup EXIT -compatibility_check # --- PRE MAIN LOGIC ---< @@ -998,4 +989,5 @@ else else launch_tui fi -fi \ No newline at end of file +fi +# --- MAIN LOGIC ---< \ No newline at end of file -- 2.52.0 From f55e12c039f69e61c06d720c8632f3e5e7faf65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 11:44:56 +0200 Subject: [PATCH 04/91] Added missing -e. --- script/start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/start.sh b/script/start.sh index d1b8197..41516f0 100644 --- a/script/start.sh +++ b/script/start.sh @@ -7,7 +7,7 @@ set -euo pipefail REPO_URL="https://gittea.dev/numbus/numbus.git" INSTALL_DIR="/run/user/$(id -u)/numbus-installer" -echo "\n ☁️ Initializing Numbus Installer..." +echo -e "\n ☁️ Initializing Numbus Installer..." # 1. Check for Nix TEST_FAIL=0 -- 2.52.0 From 17a6affa85ae2d144ec05d101d98f29e71185190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 11:47:48 +0200 Subject: [PATCH 05/91] Moved clear command from child to parent process. --- script/deploy.sh | 1 - script/start.sh | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/script/deploy.sh b/script/deploy.sh index 0b74b93..40c7832 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -957,7 +957,6 @@ GUM_INPUT_TIMEOUT="3600" # --- PRE MAIN LOGIC ---> set -euo pipefail -clear trap cleanup EXIT # --- PRE MAIN LOGIC ---< diff --git a/script/start.sh b/script/start.sh index 41516f0..9683097 100644 --- a/script/start.sh +++ b/script/start.sh @@ -48,4 +48,7 @@ git clone "${REPO_URL}" "${INSTALL_DIR}" -q # 3. Launch the deployment script cd "${INSTALL_DIR}/script" chmod +x deploy.sh + +clear + ./deploy.sh \ No newline at end of file -- 2.52.0 From 96be47824f964c144273b517f0a9935766b46fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 11:49:57 +0200 Subject: [PATCH 06/91] Moved clear command from parent to child process. --- script/deploy.sh | 2 +- script/start.sh | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/script/deploy.sh b/script/deploy.sh index 40c7832..6c9cc6d 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -957,7 +957,7 @@ GUM_INPUT_TIMEOUT="3600" # --- PRE MAIN LOGIC ---> set -euo pipefail - +clear trap cleanup EXIT # --- PRE MAIN LOGIC ---< diff --git a/script/start.sh b/script/start.sh index 9683097..41516f0 100644 --- a/script/start.sh +++ b/script/start.sh @@ -48,7 +48,4 @@ git clone "${REPO_URL}" "${INSTALL_DIR}" -q # 3. Launch the deployment script cd "${INSTALL_DIR}/script" chmod +x deploy.sh - -clear - ./deploy.sh \ No newline at end of file -- 2.52.0 From 3cfc72221d647ef388d85817d118e16bed403d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 11:52:48 +0200 Subject: [PATCH 07/91] Added a branch selector for testing purposes. --- script/start.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/start.sh b/script/start.sh index 41516f0..0874c76 100644 --- a/script/start.sh +++ b/script/start.sh @@ -6,6 +6,7 @@ set -euo pipefail REPO_URL="https://gittea.dev/numbus/numbus.git" INSTALL_DIR="/run/user/$(id -u)/numbus-installer" +BRANCH="${BRANCH:-production}" echo -e "\n ☁️ Initializing Numbus Installer..." @@ -43,7 +44,7 @@ if [[ -e ${INSTALL_DIR}/config/* ]]; then echo " ✅ Your files have been moved to the ${OLD_CONFIG_PATH} directory. You can retrieve them there if needed." fi -git clone "${REPO_URL}" "${INSTALL_DIR}" -q +git clone -b "${BRANCH}" "${REPO_URL}" "${INSTALL_DIR}" -q # 3. Launch the deployment script cd "${INSTALL_DIR}/script" -- 2.52.0 From 15ba7797b0aaa9a53150a1b23317f57092751932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 12:09:28 +0200 Subject: [PATCH 08/91] Added a remove command to prevent directory already exists error. --- script/start.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/start.sh b/script/start.sh index 0874c76..618fd70 100644 --- a/script/start.sh +++ b/script/start.sh @@ -42,6 +42,8 @@ if [[ -e ${INSTALL_DIR}/config/* ]]; then mkdir -p ${OLD_CONFIG_PATH} mv ${INSTALL_DIR} ${OLD_CONFIG_PATH} echo " ✅ Your files have been moved to the ${OLD_CONFIG_PATH} directory. You can retrieve them there if needed." +else + rm -rf "${INSTALL_DIR}" fi git clone -b "${BRANCH}" "${REPO_URL}" "${INSTALL_DIR}" -q -- 2.52.0 From 4811714b0a54d8b4ee61ac257801742696851142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 13:24:31 +0200 Subject: [PATCH 09/91] Moved preparation file to index.html --- web/pages/{preparation.html => index.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web/pages/{preparation.html => index.html} (100%) diff --git a/web/pages/preparation.html b/web/pages/index.html similarity index 100% rename from web/pages/preparation.html rename to web/pages/index.html -- 2.52.0 From b53f3be190d0e88ecc95b7b329e6bc66fda59b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 13:27:15 +0200 Subject: [PATCH 10/91] Added the index.html file. --- web/index.html | 49 ----- web/pages/index.html | 431 +++---------------------------------- web/pages/preparation.html | 430 ++++++++++++++++++++++++++++++++++++ 3 files changed, 455 insertions(+), 455 deletions(-) delete mode 100644 web/index.html create mode 100644 web/pages/preparation.html diff --git a/web/index.html b/web/index.html deleted file mode 100644 index e49f7c0..0000000 --- a/web/index.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - Numbus Configurator - - - - - - - - - - - - - - - - -
- Numbus Logo -

Welcome to NUMBUS

-

Transform your device into a secure, reliable and private appliance
using the power of open-soure software.

-

You will be guided through the configuration process.

- -
- - - -

Privacy First: No data entered here ever leaves your device.
This configurator runs entirely locally in your browser and is fully private. -

- - - Get Started - -
- - - \ No newline at end of file diff --git a/web/pages/index.html b/web/pages/index.html index e1ae477..e49f7c0 100644 --- a/web/pages/index.html +++ b/web/pages/index.html @@ -12,419 +12,38 @@ - - + - - - - - + .animate-pulse-slow { animation: pulse-slow 6s infinite ease-in-out; } + .text-shadow-glow { text-shadow: 0 0 15px rgba(56, 189, 248, 0.4); } + - + + + +
+ Numbus Logo +

Welcome to NUMBUS

+

Transform your device into a secure, reliable and private appliance
using the power of open-soure software.

+

You will be guided through the configuration process.

-
- - - -
- - -
-

Language

-
-

Set your regional preferences to ensure correct time and language display.

-
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
-

Device Type

-
-

Select the device type for your new Numbus machine that matches your needs.

-
- - - - -
-
- - -
-

Deployment Mode

-
-

Select your preferred deployment mode. Non-interactive requires a ready-to-go configuration hosted on a git platform.

-
- - -
-
- - -
-

Replication Mode

-
-

Select your preferred replication mode. It compares the current deployment to the old one.

-

Hardware

-
- - -
-

Strategy

-
- - -
-

Secrets

-
- - -
-
- - -
-

Live Setup

-
-

Provide the necessary information to connect to the device. It needs to be in a NixOS live environment.

-
-
- Live Target IP Address - -
-
- Temporary Password - -
-
-
- - -
-
- - - - - +
+ + + +

Privacy First: No data entered here ever leaves your device.
This configurator runs entirely locally in your browser and is fully private.

+ + Get Started +
- \ No newline at end of file + \ No newline at end of file diff --git a/web/pages/preparation.html b/web/pages/preparation.html new file mode 100644 index 0000000..e1ae477 --- /dev/null +++ b/web/pages/preparation.html @@ -0,0 +1,430 @@ + + + + + + Numbus Configurator + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + +
+

Language

+
+

Set your regional preferences to ensure correct time and language display.

+
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+

Device Type

+
+

Select the device type for your new Numbus machine that matches your needs.

+
+ + + + +
+
+ + +
+

Deployment Mode

+
+

Select your preferred deployment mode. Non-interactive requires a ready-to-go configuration hosted on a git platform.

+
+ + +
+
+ + +
+

Replication Mode

+
+

Select your preferred replication mode. It compares the current deployment to the old one.

+

Hardware

+
+ + +
+

Strategy

+
+ + +
+

Secrets

+
+ + +
+
+ + +
+

Live Setup

+
+

Provide the necessary information to connect to the device. It needs to be in a NixOS live environment.

+
+
+ Live Target IP Address + +
+
+ Temporary Password + +
+
+
+ + +
+
+ + + + + +
+ +
+ + + \ No newline at end of file -- 2.52.0 From 522ee16611eae7cbfadc48a96a1e915a88801827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 21:54:41 +0200 Subject: [PATCH 11/91] Update non-interactive python bridge. Updated index.html for the new folder hierachy. --- web/logic/non-interactive.py | 23 ++++++++++++++--------- web/pages/index.html | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/web/logic/non-interactive.py b/web/logic/non-interactive.py index ea2385f..3d9774e 100644 --- a/web/logic/non-interactive.py +++ b/web/logic/non-interactive.py @@ -3,24 +3,29 @@ import json import os import sys -# Use a memory-backed path for temporary secrets if available, else local -SECRET_PATH = "/run/user/{}/numbus".format(os.getuid()) if os.path.exists("/run/user/{}".format(os.getuid())) else "." +### 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_file = f'deploy-{log_type}.log' + 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_file): - with open(log_file, 'r') as f: + 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 @@ -37,14 +42,14 @@ class BridgeHandler(http.server.SimpleHTTPRequestHandler): self.send_response(200) self.end_headers() # Signal Bash that discovery data is ready - with open(".discovery_ready", "w") as f: f.write("1") + with open(os.path.join(SIGNALS_DIR, ".discovery_ready"), "w") as f: f.write("1") elif self.path == '/deploy': - with open("../numbus.yaml", "wb") as f: + 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(".deploy_signal", "w") as f: f.write("1") + with open(os.path.join(SIGNALS_DIR, ".deploy_signal"), "w") as f: f.write("1") -os.chdir("configurator") +os.chdir(PAGES_DIR) http.server.HTTPServer(('localhost', 8088), BridgeHandler).serve_forever() \ No newline at end of file diff --git a/web/pages/index.html b/web/pages/index.html index e49f7c0..b61b420 100644 --- a/web/pages/index.html +++ b/web/pages/index.html @@ -28,7 +28,7 @@
- Numbus Logo + Numbus Logo

Welcome to NUMBUS

Transform your device into a secure, reliable and private appliance
using the power of open-soure software.

You will be guided through the configuration process.

@@ -41,7 +41,7 @@
- Get Started + Get Started
-- 2.52.0 From 4ca5ae5c8f39fb0d012be39ae294f12a30babbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 22:23:49 +0200 Subject: [PATCH 12/91] Removed a lot of unnecessary code. Now all configuration is done through the browser. Need to work more on the deployment process. --- script/deploy.sh | 339 +++-------------------------------------------- 1 file changed, 21 insertions(+), 318 deletions(-) diff --git a/script/deploy.sh b/script/deploy.sh index 6c9cc6d..3f84101 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -238,118 +238,12 @@ launch_gui() { sleep 5 echo -e "\n ➡️ If it doesn't automatically, open your browser at: $(gum style --foreground 212 "http://localhost:${WEBSERVER_PORT}")" - } # --- MAIN WEB FUNCTIONS ---< # --- MAIN TUI FUNCTIONS ---> -preparation() { - echo -e "\n ➡️ This script will now guide you through the configuration and gather the necessary information." - - echo "" - RAW_DEVICE_TYPE=$(gum choose --header "Choose the device you want to deploy :" \ - "Numbus Server : Professional-grade hosting, strictly kept under your roof." \ - "Numbus Backup Server : Automated, high-efficiency protection for your entire ecosystem." \ - "Numbus Computer : A modern, privacy-respecting machine built for work, creation, and play — without the corporate bloat." \ - "Numbus TV : A premium cinematic experience free from trackers and forced subscriptions." \ - "Numbus Game Console : An unbreakable Steam bigscreen experience.") - - case "${RAW_DEVICE_TYPE}" in - "Numbus Server : "* ) DEVICE_TYPE="server" ;; - "Numbus Backup Server : "* ) DEVICE_TYPE="backup" ;; - "Numbus Computer : "* ) DEVICE_TYPE="computer" ;; - "Numbus TV : "* ) DEVICE_TYPE="tv" ;; - "Numbus Game Console : "* ) DEVICE_TYPE="console" ;; - esac - - RAW_DEPLOYMENT_MODE=$(gum choose --header "Choose your preferred deployment mode :" \ - "Interactive : You don't already have a configuration." \ - "Non-interactive : You have a valid configuration hosted on a Git platform.") - - case "${RAW_DEPLOYMENT_MODE}" in - "Interactive : "* ) DEPLOYMENT_MODE="interactive" ;; - "Non-interactive : "* ) DEPLOYMENT_MODE="non-interactive" ;; - esac - - if [[ "${DEPLOYMENT_MODE}" == "non-interactive" ]]; then - git_url() { - IMPORTED_CONFIG_URL=$(gum input --placeholder "https://yourgitplatform.tld/your-user/repo-containing-the-configuration" --header "Please provide the URL to the git repository containing your configuration :") - } - - git_url - - until git clone "${IMPORTED_CONFIG_URL}" imported_configuration; do - echo -e "\n ⚠️ This did not work correctly." - - echo -e "\n Is this URL correct [y/n] ? ${IMPORTED_CONFIG_URL}" - read URL - - if [[ "${URL^^}" == "N" ]]; then - git_url - fi - - echo -e "\n You will be prompted for your credentials again. Make sure that they are correct." - done - fi - - echo "" - gum format -- \ - "➡️ To continue, you need to start the target device in a NixOS live environment : - 1. Download the NixOS iso from the **[official website](https://nixos.org/download/)**. - 2. Flash it to a USB stick. (use a flashing tool like **[Rufus](https://rufus.ie/en/#download)**, **[BalenaEtcher](https://etcher.balena.io/#download-etcher)**, **[Impression](https://flathub.org/en/apps/io.gitlab.adhami3310.Impression)**, ...) - 3. Make sure your computer allows booting from USB drives and is in UEFI mode. - 4. Boot into the NixOS live environment. - 5. Launch a terminal. Set a password using \`passwd\` and find the IP address using \`ip a\`" - - echo "" - gum confirm "Is the device ready ?" || { echo "❌ You need to prepare the device. The script cannot continue."; exit 1; } - - # 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}" - user_input "LIVE_TARGET_PASSWORD" " Please enter the password for '${TARGET_USER}@${LIVE_TARGET_IP}' :" "${LIVE_TARGET_IP}'s password" "" "" "true" - - # INTERNATIONALIZATION SETTINGS - user_input "INTERNATIONALIZATION_TIMEZONE" " Please provide the wanted timezone :" "For example : Europe/Paris, Europe/Berlin, Europe/London, etc" - user_input "INTERNATIONALIZATION_LANGUAGE" " Please provide the wanted language :" "For example : French, Deutsch, English, etc" - user_input "INTERNATIONALIZATION_COUNTRY" " Please provide your country :" "For example : France, Germany, Great-Britain, etc" -} - -configuration() { - if [[ "${DEVICE_TYPE}" == "server" ]]; then - - # Users & Groups - user_input "SERVER_OWNER_NAME" " Please provide the name of the owner of this server :" "For example : Steve" - user_input "SERVER_ADMIN_EMAIL" " Please provide a valid ADMIN email address (ACME, system failures notifications, etc) :" "For example : myemail@mydomain.mytld" "${EMAIL_REGEX}" - user_input "AUTHORIZED_SSH_PUBLIC_KEY" " Please provide the SSH public key of an authorized device (or a comma-separated list) :" "For example : ssh-ed25519 AAAAC3Nzam0uYewNAbxL8Fci8 user@your-pc or ssh-* * *, ssh-* * *, etc" "${SSH_KEY_REGEX}" "Invalid SSH key format (must start with ssh-...)." - - 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" - # TRAEFIK SETTINGS - user_input "DOMAIN_NAME" " Please provide the domain name (FQDN) your home server will use :" "For example : yourdomain.com" "${DOMAIN_REGEX}" - 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)" - # SMTP SETTINGS - user_input "SMTP_SERVER_USERNAME" " Please provide a valid sender email address :" "For example : myemail@gmail.com" "${EMAIL_REGEX}" - 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." - elif [[ "${DEVICE_TYPE}" == "backup" ]]; then - : - elif [[ "${DEVICE_TYPE}" == "computer" ]]; then - : - elif [[ "${DEVICE_TYPE}" == "tv" ]]; then - : - fi -} - setup_ssh() { echod "\n ✅ Generating new SSH key for numbus-admin..." @@ -370,125 +264,6 @@ setup_ssh() { fi } -services_selection() { - services_choice() { - local SERVICES_LIST=( "${1[@]}" ) - local SERVICES_DESCRIPTION=( "${2[@]}" ) - local FINAL_VARIABLE="${3}" - local HEADER="${4}" - local LIMIT="${5:---no-limit}" - - local SELECTED_SERVICES=() - local SELECTED_SERVICES_DESCRIPTION=() - - local SELECTED_SERVICES_DESCRIPTION=$(gum choose ${LIMIT} --header "${HEADER}" "${SERVICES_DESCRIPTION[@]}") - - for i in ${!SERVICES_LIST[@]}; do - if printf '%s' "${SELECTED_SERVICES_DESCRIPTION}" | grep -iq "${SERVICES_LIST[${i}]}"; then - SELECTED_SERVICES+=("${SERVICES_LIST[${i}]}") - fi - done - - export "${FINAL_VARIABLE}=(${SELECTED_SERVICES[@]})" - } - - echo -e "\n\n ➡️ You will now select the services you want installed on your server:" - - services_choice "${DNS_SERVICES_LIST[@]}" "${DNS_SERVICES_DESCRIPTION[@]}" "SELECTED_DNS_SERVICE" "Choose your preferred DNS service :" "--limit=1" - services_choice "${WEB_APPLICATIONS_LIST[@]}" "${WEB_APPLICATIONS_DESCRIPTION[@]}" "SELECTED_WEB_APPLICATIONS" "Choose the web applications you want to install :" - services_choice "${SYSTEM_SERVICES_LIST[@]}" "${SYSTEM_SERVICES_DESCRIPTION[@]}" "SELECTED_SYSTEM_SERVICES" "Choose the system services you want to install :" - - gum confirm "Do you want to edit the default subdomain of your services ?" || { echo -e "\n\n✅ Continuing..."; return 0; } - - for service in ${SELECTED_WEB_APPLICATIONS[@]} ${SELECTED_DNS_SERVICE[@]}; do - if gum confirm "Change the subdomain of ${service} ?"; then - SELECTED_WEB_APPLICATIONS_SUBDOMAIN+=( "$(gum input --placeholder "${service}" --header "Please provide the desired subdomain for ${service}:")" ) - fi - done - - return 0 -} - -disks_selection() { - 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)" - - if [[ "${#DISK_NAME[@]}" -eq 0 ]]; then - echo -e "\n❌ No disks found on the target host. Aborting." - exit 1 - fi - - local HEADER=$(printf " %-12s %-12s %-12s %-12s %s" "Device" "Type" "Size" "SMART" "Path") - - for i in ${!DISK_NAME[@]}; do - local GUM_PRINTED_ELEMENT=$(printf "%-12s %-12s %-12s %-12s %s" \ - "${DISK_NAME[${i}]}" "${DISK_TYPE[${i}]}" "${DISK_SIZE[${i}]}" \ - "${DISK_HEALTH[${i}]}" "${DISK_DEVPATH[${i}]}") - local GUM_PRINTED_ELEMENTS+=("${GUM_PRINTED_ELEMENT}") - done - - echo "" - gum style --foreground 212 "➡️ Please choose one (stripe) or two (mirror) disks for your NixOS boot installation :" - - local 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 - BOOT_DISKS_ID_LIST+=("\"${DISK_ID[${i}]:-${DISK_DEVPATH[${i}]}}\"") - BOOT_DISKS_NAME+=("${DISK_NAME[${i}]}") - unset "GUM_PRINTED_ELEMENTS[${i}]" - fi - done - - echo "" - gum style --foreground 212 "➡️ Please choose data and parity disks (up to 9 total) :" - - local 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 - DATA_DISKS_ID+=("${DISK_ID[${i}]:-${DISK_DEVPATH[${i}]}}") - DATA_DISKS_TYPE+=("${DISK_TYPE[${i}]}") - fi - done - - if [[ "${#DATA_DISKS_ID[@]}" -eq 1 ]]; then - export PARITY_DISK_NUMBER=0 - export CONTENT_DISK_NUMBER=1 - export PARITY_DISK_LIST=() - export CONTENT_DISK_LIST=("\"${DATA_DISKS_ID[0]}\"") - else - export PARITY_DISK_NUMBER=$(((${#DATA_DISKS_ID[@]} + 2) / 3)) - export CONTENT_DISK_NUMBER=$((${#DATA_DISKS_ID[@]} - PARITY_DISK_NUMBER)) - for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do - CONTENT_DISK_LIST+=("\"${DATA_DISKS_ID[${i}]}\"") - done - for i in $(seq $CONTENT_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do - PARITY_DISK_LIST+=("\"${DATA_DISKS_ID[${i}]}\"") - done - fi - - if [[ "${#DATA_DISKS_ID[@]}" -gt 0 ]]; then - for i in ${!DATA_DISKS_ID[@]}; do - if [[ "${DATA_DISKS_TYPE[${i}]}" == "HDD" ]]; then - SPINDOWN_DISKS_LIST+=("\"${DATA_DISKS_ID[${i}]}\"") - fi - done - fi - - export SPINDOWN_DISKS_LIST - export BOOT_DISKS_ID_LIST - export PARITY_DISK_LIST - export CONTENT_DISK_LIST -} - server_config_generation() { echod "\n 📝 Generating structured settings.json..." @@ -616,87 +391,6 @@ EOF --output ${EXTRA_FILES_PATH}/etc/nixos/secrets/secrets.yaml } -sum_up() { - DISK_RECAP_CONTENT=$(cat << EOF -### Disk Configuration Summary - -Please review the selected disk layout before proceeding. - -**Boot Disks (${#BOOT_DISKS_ID_LIST[@]}) :** - -* **Boot 1:** \`${BOOT_DISKS_ID_LIST[0]}\` -$( [[ -n "${BOOT_DISKS_ID_LIST[1]:-}" ]] && echo "* **Boot 2:** \`${BOOT_DISKS_ID_LIST[1]}\`" ) - -**Data Disks ($CONTENT_DISK_NUMBER) :** - -$( [[ $CONTENT_DISK_NUMBER -gt 0 ]] && j=1 && for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do echo "* **Data ${j}:** \`${DATA_DISKS_ID[${i}]}\`" && j=$((j + 1)); done ) -$( [[ $CONTENT_DISK_NUMBER -eq 0 ]] && echo "* *Not configured*" ) - -**Parity Disks ($PARITY_DISK_NUMBER) :** - -$( [[ $PARITY_DISK_NUMBER -gt 0 ]] && j=1 && for i in $(seq $CONTENT_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do echo "* **Parity ${j}:** \`${DATA_DISKS_ID[${i}]}\`" && j=$((j + 1)); done ) -$( [[ $PARITY_DISK_NUMBER -eq 0 ]] && echo "* *Not configured*" ) - -EOF -) - - gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "${DISK_RECAP_CONTENT}")" - gum confirm "➡️ Proceed with this disk configuration?" || { echo -e "\n\n❌ Aborting as requested."; exit 1; } - - SERVICES_RECAP_CONTENT=$(cat << EOF -### Services Configuration Summary - -Please review the selected services before proceeding. - -**DNS Service (${#SELECTED_DNS_SERVICE[@]}) :** - -$(echo "* \`${SELECTED_DNS_SERVICE[0]^}\`") - -**Web Applications (${#SELECTED_WEB_APPLICATIONS[@]}) :** - -$(for app in "${SELECTED_WEB_APPLICATIONS[@]}"; do echo "* \`${app^}\`"; done) - -**System Services (${#SELECTED_SYSTEM_SERVICES[@]}) :** - -$(for service in "${SELECTED_SYSTEM_SERVICES[@]}"; do echo "* \`${service^}\`"; done) - -EOF -) - - gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "${SERVICES_RECAP_CONTENT}")" - gum confirm "➡️ Proceed with this services configuration?" || { echo -e "\n\n❌ Aborting as requested."; exit 1; } - - DISK_RECAP_CONTENT=$(cat << EOF -### Secrets Summary - -Please save the following secrets to a secure place (i.e. your local password manager, or a hidden sheet of paper). - -**Boot Disks (${#BOOT_DISKS_ID_LIST[@]}) :** - -* **Disk 1 Secret Key :** \`$( cat ${EXTRA_FILES_PATH}/etc/secrets/disks/boot-1 )\` -$( [[ -n "${BOOT_DISKS_ID_LIST[1]:-}" ]] && echo "* **Disk 2 secret key :** \`$( cat ${EXTRA_FILES_PATH}/etc/secrets/disks/boot-2 )\`" ) - -**Data Disks ($CONTENT_DISK_NUMBER):** - -$( [[ $CONTENT_DISK_NUMBER -eq 0 ]] && echo "* *Not configured*" ) -$( [[ $CONTENT_DISK_NUMBER -gt 0 ]] && j=1 && for i in $(seq 0 $(($CONTENT_DISK_NUMBER - 1))); do echo "* **Disk ${j} Secret Key :** \`$( cat ${EXTRA_FILES_PATH}/etc/secrets/disks/content-${j} )\`" && j=$((j + 1)); done ) - -**Parity Disks ($PARITY_DISK_NUMBER):** - -$( [[ $PARITY_DISK_NUMBER -eq 0 ]] && echo "* *Not configured*" ) -$( [[ $PARITY_DISK_NUMBER -gt 0 ]] && j=1 && for i in $(seq $CONTENT_DISK_NUMBER $((${#DATA_DISKS_ID[@]} - 1))); do echo "* **Disk ${j} Secret Key :** \`$( cat ${EXTRA_FILES_PATH}/etc/secrets/disks/parity-${j} )\`" && j=$((j + 1)); done ) - -EOF -) - - gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "$(gum format <<< "${DISK_RECAP_CONTENT}")" - gum confirm "✅ I have stored these credentials in a safe place" || { echo -e "\n\n❌ Please store these credentials in a safe place as you will need them later."; exit 1; } - - gum confirm "➡️ Would you like to manually edit the configuration (⚠️ advanced users only)" || { echo -e "\n\n✅ continuing with the installation..."; return 0; } - - nano ${EXTRA_FILES_PATH}/etc/nixos/configuration.nix -} - cloudflare_dns_setup() { gum confirm "➡️ This script can automatically create DNS records for your services. Proceed? (recommended)" || { echo -e "\n\n ⚠️ skipping the DNS records creation step..."; return 0; } @@ -914,9 +608,8 @@ securely on a hidden sheet of paper or add it to your password manager (locally # --- DEFAULT VARIABLES ---> WEBSERVER_PORT=${WEBSERVER_PORT:-8088} -LIVE_DATA_PATH="/run/user/$(id -u)/numbus/web/live_settings.json" -HARDWARE_DATA_PATH="/run/user/$(id -u)/numbus/web/hardware.json" - +LIVE_DATA_FILE="../config/live.yaml" +HW_DATA_FILE="../config/hardware.yaml" CONFIG_FILE="../config/numbus.yaml" TARGET_USER="nixos" @@ -978,15 +671,25 @@ DEPLOYMENT_STRATEGY=$(gum choose --header "Choose your preferred deployment stra 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 + done + until [[ -e ../web/signals/deployment_ready ]]; do + done else - DEPLOYMENT_MODE=$(gum choose --header "Choose your preferred deployment mode :" \ - "Through my web browser (Recommended for beginners)" \ - "Through my terminal (TUI)") - if [[ "${DEPLOYMENT_MODE}" == "Through my web browser (Recommended for beginners)" ]]; then - BRIDGE_SCRIPT="../web/logic/non-interactive.py" - launch_gui - else - launch_tui - fi + BRIDGE_SCRIPT="../web/logic/non-interactive.py" + launch_gui + hierarchy_preparation + until [[ -e ../web/signals/hw_detection_ready ]]; do + done + until [[ -e ../web/signals/hw_detection_ready ]]; do + done + until [[ -e ../web/signals/hw_detection_ready ]]; do + done fi # --- MAIN LOGIC ---< \ No newline at end of file -- 2.52.0 From 27433d625879d6c74fdd5e4966540e5cc016dec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 22:24:47 +0200 Subject: [PATCH 13/91] Added sleep timers. --- script/deploy.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/script/deploy.sh b/script/deploy.sh index 3f84101..cd4db6c 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -678,6 +678,7 @@ if [[ "${DEPLOYMENT_STRATEGY}" == "I don't have a configuration" ]]; then 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 done @@ -686,10 +687,13 @@ else 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 # --- MAIN LOGIC ---< \ No newline at end of file -- 2.52.0 From 36c1039df6a1d0a71285ed4e3644371105cbbc6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 22:25:27 +0200 Subject: [PATCH 14/91] Added sleep timers. --- script/deploy.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/script/deploy.sh b/script/deploy.sh index cd4db6c..77eeaf2 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -681,6 +681,7 @@ if [[ "${DEPLOYMENT_STRATEGY}" == "I don't have a configuration" ]]; then sleep 5 done until [[ -e ../web/signals/deployment_ready ]]; do + sleep 5 done else BRIDGE_SCRIPT="../web/logic/non-interactive.py" -- 2.52.0 From 30745adf4f9b4eaeb1716ad708746fb883a8cb7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Fri, 15 May 2026 22:33:41 +0200 Subject: [PATCH 15/91] Added folders needed by the script. Removed TUI. --- script/deploy.sh | 12 ++---------- script/terminology.md | 8 ++++---- web/config/.gitignore | 0 web/logs/.gitignore | 0 web/secrets/.gitignore | 0 web/signals/.gitignore | 0 6 files changed, 6 insertions(+), 14 deletions(-) create mode 100644 web/config/.gitignore create mode 100644 web/logs/.gitignore create mode 100644 web/secrets/.gitignore create mode 100644 web/signals/.gitignore diff --git a/script/deploy.sh b/script/deploy.sh index 77eeaf2..f9da35e 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -77,20 +77,12 @@ cleanup() { hierarchy_preparation() { echod "\n 🔄 Preparing the folder hierarchy for the final configuration..." - # Script folders - mkdir -${MKDIR_FLAGS} ${TMP_FILES_PATH}/config - mkdir -${MKDIR_FLAGS} ${TMP_FILES_PATH}/logs - mkdir -${MKDIR_FLAGS} ${TMP_FILES_PATH}/tmp - [[ ${WEB_MODE} -eq 1 ]] && mkdir -${MKDIR_FLAGS} ${TMP_FILES_PATH}/web - - # Secrets + # 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 - if [[ "${DEVICE_TYPE}" == "server" || "${DEVICE_TYPE}" == "backup" ]]; then - mkdir -${MKDIR_FLAGS} ${EXTRA_FILES_PATH}/etc/nixos/secrets/podman - fi + mkdir -${MKDIR_FLAGS} ${EXTRA_FILES_PATH}/etc/nixos/secrets/podman echod "\n ✅ Folder hierarchy ready" } diff --git a/script/terminology.md b/script/terminology.md index b9fd977..9f2a851 100644 --- a/script/terminology.md +++ b/script/terminology.md @@ -1,6 +1,6 @@ # Terminology for the variables used -|Variable|Meaning|Possible values| -|-|-------|-| -|DEPLOYMENT_STRATEGY|Either deploy the machine with a config you already have or let the script guide you through the config options|**interactive** or **non-interactive**| -|DEPLOYMENT_MODE|Either configure the machine through your terminal (TUI) or through a slick web UI (GUI)|**TUI** (only available for **non-interactive** strategy) or **GUI**| \ No newline at end of file +|Variable|Meaning|Possible values|Deprecated| +|-|-------|-|-| +|DEPLOYMENT_STRATEGY|Either deploy the machine with a config you already have or let the script guide you through the config options|**interactive** or **non-interactive**|NO| +|DEPLOYMENT_MODE|Either configure the machine through your terminal (TUI) or through a slick web UI (GUI)|**TUI** (only available for **non-interactive** strategy) or **GUI**|YES| \ No newline at end of file diff --git a/web/config/.gitignore b/web/config/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/web/logs/.gitignore b/web/logs/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/web/secrets/.gitignore b/web/secrets/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/web/signals/.gitignore b/web/signals/.gitignore new file mode 100644 index 0000000..e69de29 -- 2.52.0 From c5d81677a132aacb45f011adf7813252bc39196a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Sun, 17 May 2026 13:40:08 +0200 Subject: [PATCH 16/91] Laid a base for better hardware detection. Removed unnecessary packages. Removed unnecessary TUI. --- script/deploy.sh | 434 +++++++++++++++++++++-------------- web/logic/interactive.py | 10 +- web/logic/non-interactive.py | 55 ----- 3 files changed, 268 insertions(+), 231 deletions(-) delete mode 100644 web/logic/non-interactive.py 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 -- 2.52.0 From 8c4dc60b68a39970c66a095f2355b464128bc8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Sun, 17 May 2026 13:42:16 +0200 Subject: [PATCH 17/91] Trying to get python bridge to work. --- web/logic/interactive.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/logic/interactive.py b/web/logic/interactive.py index 188074e..b1b6022 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 = "../logs/" -PAGES_DIR = "../pages/" -CONFIG_DIR = "../config/" -SIGNALS_DIR = "../signal/" +LOGS_DIR = "../web/logs/" +PAGES_DIR = "../web/pages/" +CONFIG_DIR = "../web/config/" +SIGNALS_DIR = "../web/signal/" ### <-- Variables class BridgeHandler(http.server.SimpleHTTPRequestHandler): -- 2.52.0 From bf4d973a32d9a8efd819b7441407c8d48b28006a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Numbus?= Date: Sun, 17 May 2026 14:00:36 +0200 Subject: [PATCH 18/91] Trying to get html images to work. --- web/logic/interactive.py | 8 ++++---- web/pages/configuration.html | 8 ++++---- web/pages/index.html | 4 ++-- web/pages/preparation.html | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/web/logic/interactive.py b/web/logic/interactive.py index b1b6022..e4a7543 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): diff --git a/web/pages/configuration.html b/web/pages/configuration.html index 1a498b7..4b9e17f 100644 --- a/web/pages/configuration.html +++ b/web/pages/configuration.html @@ -63,7 +63,7 @@

Select the device type for your new Numbus machine that matches your needs.