From 6fc66ba13fbbe1c806b3e7c94b762f82fe05e1d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 22:27:55 +0000 Subject: [PATCH] feat: add remote deployment mode (remote-deploy.nix, headless installer, ISO SSH/mDNS) Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/8e2ed0be-2db9-4437-81d7-c6efec45d6db Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> --- iso/common.nix | 20 +++ iso/sovran-install-headless.sh | 254 +++++++++++++++++++++++++++++++++ modules/core/remote-deploy.nix | 117 +++++++++++++++ modules/modules.nix | 1 + 4 files changed, 392 insertions(+) create mode 100755 iso/sovran-install-headless.sh create mode 100644 modules/core/remote-deploy.nix diff --git a/iso/common.nix b/iso/common.nix index 0937431..a1c6d89 100644 --- a/iso/common.nix +++ b/iso/common.nix @@ -62,8 +62,28 @@ in nixos-install-tools git curl + openssh ]; + # Remote install support — SSH on the live ISO + services.openssh = { + enable = true; + listenAddresses = [{ addr = "0.0.0.0"; port = 22; }]; + settings = { + PasswordAuthentication = true; + PermitRootLogin = "yes"; + }; + }; + users.users.root.initialPassword = "sovran-remote"; + + # mDNS so the machine is discoverable as sovran-installer.local + services.avahi = { + enable = true; + hostName = "sovran-installer"; + nssmdns4 = true; + publish = { enable = true; addresses = true; }; + }; + environment.etc."sovran/logo.png".source = ./assets/splash-logo.png; environment.etc."sovran/flake".source = sovranSource; environment.etc."sovran/installer.py".source = ./installer.py; diff --git a/iso/sovran-install-headless.sh b/iso/sovran-install-headless.sh new file mode 100755 index 0000000..c23c847 --- /dev/null +++ b/iso/sovran-install-headless.sh @@ -0,0 +1,254 @@ +#!/usr/bin/env bash +# sovran-install-headless.sh — Non-interactive remote installer for Sovran_SystemsOS + +usage() { + cat <<'USAGE' +Usage: sovran-install-headless.sh [OPTIONS] + +Options: + --disk /dev/sda Target OS disk (required) + --data-disk /dev/sdb Data disk for Bitcoin (optional) + --role server|desktop|node Installation role (default: server) + --deploy-key "ssh-ed25519 AAAA..." SSH pubkey for remote access after install + --relay-host HOST Reverse tunnel relay hostname + --relay-user USER Relay username (default: deploy) + --relay-port PORT Relay SSH port (default: 22) + --tunnel-port PORT Reverse tunnel port on relay (default: 2222) +USAGE +} + +set -euo pipefail + +# ── Defaults ────────────────────────────────────────────────────────────────── +DISK="" +DATA_DISK="" +ROLE="server" +DEPLOY_KEY="" +RELAY_HOST="" +RELAY_USER="deploy" +RELAY_PORT="22" +TUNNEL_PORT="2222" + +FLAKE="/etc/sovran/flake" +LOG="/tmp/sovran-headless-install.log" + +BYTES_256GB=$((256 * 1024 * 1024 * 1024)) +BYTES_2TB=$((2 * 1000 * 1000 * 1000 * 1000)) + +# ── Logging ─────────────────────────────────────────────────────────────────── +exec > >(tee -a "$LOG") 2>&1 + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" +} + +die() { + log "ERROR: $*" + exit 1 +} + +# ── Argument parsing ───────────────────────────────────────────────────────── +while [[ $# -gt 0 ]]; do + case "$1" in + --disk) DISK="$2"; shift 2 ;; + --data-disk) DATA_DISK="$2"; shift 2 ;; + --role) ROLE="$2"; shift 2 ;; + --deploy-key) DEPLOY_KEY="$2"; shift 2 ;; + --relay-host) RELAY_HOST="$2"; shift 2 ;; + --relay-user) RELAY_USER="$2"; shift 2 ;; + --relay-port) RELAY_PORT="$2"; shift 2 ;; + --tunnel-port) TUNNEL_PORT="$2"; shift 2 ;; + -h|--help) + usage + exit 0 + ;; + *) die "Unknown argument: $1" ;; + esac +done + +# ── Validate required arguments ─────────────────────────────────────────────── +[[ -n "$DISK" ]] || die "--disk is required" + +case "$ROLE" in + server|desktop|node) ;; + *) die "--role must be one of: server, desktop, node" ;; +esac + +# ── Validate disk existence and size ───────────────────────────────────────── +log "=== Validating disks ===" + +[[ -b "$DISK" ]] || die "OS disk not found: $DISK" + +disk_size_bytes() { + local dev="$1" + lsblk -b -dno SIZE "$dev" 2>/dev/null || echo 0 +} + +OS_SIZE=$(disk_size_bytes "$DISK") +log "OS disk $DISK: $OS_SIZE bytes" +[[ "$OS_SIZE" -ge "$BYTES_256GB" ]] \ + || die "OS disk $DISK is too small ($(( OS_SIZE / 1024 / 1024 / 1024 )) GB). Minimum is 256 GB." + +if [[ -n "$DATA_DISK" ]]; then + [[ -b "$DATA_DISK" ]] || die "Data disk not found: $DATA_DISK" + [[ "$DATA_DISK" != "$DISK" ]] || die "OS disk and data disk cannot be the same device" + DATA_SIZE=$(disk_size_bytes "$DATA_DISK") + log "Data disk $DATA_DISK: $DATA_SIZE bytes" + [[ "$DATA_SIZE" -ge "$BYTES_2TB" ]] \ + || die "Data disk $DATA_DISK is too small ($(( DATA_SIZE / 1024 / 1024 / 1024 )) GB). Minimum is 2 TB." +fi + +# ── Helper: partition suffix ────────────────────────────────────────────────── +part_suffix() { + local dev="$1" n="$2" + if [[ "$dev" == *nvme* ]]; then + echo "${dev}p${n}" + else + echo "${dev}${n}" + fi +} + +# ── Step 1: Wipe disks ──────────────────────────────────────────────────────── +log "=== Wiping disk(s) ===" + +sgdisk --zap-all "$DISK" +wipefs --all --force "$DISK" + +if [[ -n "$DATA_DISK" ]]; then + sgdisk --zap-all "$DATA_DISK" + wipefs --all --force "$DATA_DISK" +fi + +partprobe "$DISK" +[[ -n "$DATA_DISK" ]] && partprobe "$DATA_DISK" +sleep 2 + +# ── Step 2: Partition OS disk ───────────────────────────────────────────────── +log "=== Partitioning OS disk ===" + +sgdisk \ + -n "1:1M:+512M" -t "1:EF00" -c "1:ESP" \ + -n "2:0:0" -t "2:8300" -c "2:root" \ + "$DISK" + +partprobe "$DISK" +sleep 2 + +# ── Step 3: Partition data disk (if present) ────────────────────────────────── +if [[ -n "$DATA_DISK" ]]; then + log "=== Partitioning data disk ===" + sgdisk \ + -n "1:1M:0" -t "1:8300" -c "1:primary" \ + "$DATA_DISK" + partprobe "$DATA_DISK" + sleep 2 +fi + +# ── Step 4: Format partitions ───────────────────────────────────────────────── +log "=== Formatting partitions ===" + +BOOT_P1=$(part_suffix "$DISK" 1) +BOOT_P2=$(part_suffix "$DISK" 2) + +mkfs.vfat -F 32 "$BOOT_P1" +mkfs.ext4 -F -L sovran_systemsos "$BOOT_P2" + +if [[ -n "$DATA_DISK" ]]; then + DATA_P1=$(part_suffix "$DATA_DISK" 1) + mkfs.ext4 -F -L BTCEcoandBackup "$DATA_P1" +fi + +# ── Step 5: Mount filesystems ───────────────────────────────────────────────── +log "=== Mounting filesystems ===" + +mount "$BOOT_P2" /mnt +mkdir -p /mnt/boot/efi +mount -o umask=0077,defaults "$BOOT_P1" /mnt/boot/efi + +if [[ -n "$DATA_DISK" ]]; then + DATA_P1=$(part_suffix "$DATA_DISK" 1) + mkdir -p /mnt/run/media/Second_Drive + mount "$DATA_P1" /mnt/run/media/Second_Drive + + # ── Step 6: Create Bitcoin data directories ───────────────────────────── + log "=== Creating Bitcoin data directories ===" + mkdir -p /mnt/run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node + mkdir -p /mnt/run/media/Second_Drive/BTCEcoandBackup/Electrs_Data + mkdir -p /mnt/run/media/Second_Drive/BTCEcoandBackup/NixOS_Snapshot_Backup +fi + +# ── Step 7: Generate hardware config ───────────────────────────────────────── +log "=== Generating hardware config ===" +nixos-generate-config --root /mnt + +# ── Step 8: Copy flake source ───────────────────────────────────────────────── +log "=== Copying flake to /mnt ===" +cp /mnt/etc/nixos/hardware-configuration.nix /tmp/hardware-configuration.nix +rm -rf /mnt/etc/nixos/ +mkdir -p /mnt/etc/nixos +cp -a "${FLAKE}/." /mnt/etc/nixos/ +cp /tmp/hardware-configuration.nix /mnt/etc/nixos/hardware-configuration.nix + +# ── Step 9: Write role-state.nix ───────────────────────────────────────────── +log "=== Writing role config ===" + +case "$ROLE" in + server) + IS_SERVER=true; IS_DESKTOP=false; IS_NODE=false ;; + desktop) + IS_SERVER=false; IS_DESKTOP=true; IS_NODE=false ;; + node) + IS_SERVER=false; IS_DESKTOP=false; IS_NODE=true ;; +esac + +cat > /mnt/etc/nixos/role-state.nix < /mnt/etc/nixos/custom.nix < /etc/sovran/deploy-mode + ''; + path = [ pkgs.coreutils ]; + }; + + # ── Deploy-mode indicator file ──────────────────────────────────────────── + environment.etc."sovran/deploy-mode".text = "active"; + }; +} diff --git a/modules/modules.nix b/modules/modules.nix index 62bffb6..e6d12dd 100755 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -13,6 +13,7 @@ ./core/sshd-localhost.nix ./core/sovran-hub.nix ./core/legacy-cleanup.nix + ./core/remote-deploy.nix # ── Always on (no flag) ─────────────────────────────────── ./php.nix