import axios from "axios"; import { exec, spawn, ChildProcess } from "child_process"; import { promisify } from "util"; import fs from "fs"; import path from "path"; const execAsync = promisify(exec); // ═══════════════════════════════════════════════════════════════ // PATH CONFIGURATION // ═══════════════════════════════════════════════════════════════ const ROOT = process.cwd(); const BLOCKCHAIN_DIR = path.join(ROOT, "blockchain"); const BACKEND_DIR = path.join(ROOT, "backend-agent"); const NODE_MODULES = path.join(BACKEND_DIR, "node_modules"); const TSX_CLI = path.join(BACKEND_DIR, "node_modules", "tsx", "dist", "cli.mjs"); const MOCK_COMFY_SCRIPT = path.join(ROOT, "scripts", "mock_comfy.ts"); const VALIDATE_SCRIPT = path.join(ROOT, "scripts", "validate_system.ts"); const MIDDLEWARE_CONFIG = path.join(ROOT, "middleware", "blockchain_config.json"); const BLOCKCHAIN_CONFIG = path.join(BLOCKCHAIN_DIR, "blockchain_config.json"); const DEPLOY_ADDRESS_FILE = path.join(BLOCKCHAIN_DIR, ".last_deploy_address"); // ═══════════════════════════════════════════════════════════════ // HELPER FUNCTIONS // ═══════════════════════════════════════════════════════════════ function shellQuote(arg: string): string { return `"${arg.replace(/"/g, '\\"')}"`; } function buildCommand(command: string, args: string[]): string { return [command, ...args].map(shellQuote).join(" "); } async function run(command: string, args: string[], cwd: string): Promise { const cmd = buildCommand(command, args); const { stdout, stderr } = await execAsync(cmd, { cwd, windowsHide: true, maxBuffer: 10 * 1024 * 1024, env: { ...process.env, NODE_PATH: NODE_MODULES } }); if (stdout) { process.stdout.write(stdout); } if (stderr) { process.stderr.write(stderr); } return stdout; } async function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } async function ensureBackendDeps(): Promise { if (!fs.existsSync(NODE_MODULES)) { console.log("📦 Installing backend dependencies..."); await run("npm", ["install"], BACKEND_DIR); } } function spawnDetached(command: string, args: string[], cwd: string): ChildProcess { const child = spawn(command, args, { cwd, detached: true, stdio: "ignore", shell: false, windowsHide: true, env: { ...process.env, NODE_PATH: NODE_MODULES } }); child.unref(); return child; } function readLastDeployAddress(): string { if (!fs.existsSync(DEPLOY_ADDRESS_FILE)) { throw new Error(`Missing deploy address file: ${DEPLOY_ADDRESS_FILE}`); } const address = fs.readFileSync(DEPLOY_ADDRESS_FILE, "utf8").trim(); if (!address) { throw new Error("Deploy address file is empty."); } return address; } // ═══════════════════════════════════════════════════════════════ // PHASE 0: DOCKER CHECK (FAULT-TOLERANT LOOP) // ═══════════════════════════════════════════════════════════════ async function waitForDocker(maxAttempts: number, delayMs: number): Promise { console.log("\n🔍 PHASE 0: Docker Daemon Check"); console.log("═══════════════════════════════════════════════════════════════\n"); for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { try { await run("docker", ["info"], ROOT); console.log("✅ Docker detected. Starting infrastructure....\n"); return; } catch { if (attempt === maxAttempts) { throw new Error( `Docker daemon not detected after ${maxAttempts} retries (${(maxAttempts * delayMs) / 1000}s total).` ); } console.log(`🟡 Waiting for Docker Daemon... (Please start Docker Desktop). Attempt ${attempt}/${maxAttempts}`); await sleep(delayMs); } } } // ═══════════════════════════════════════════════════════════════ // PHASE 1: BLOCKCHAIN IGNITION // ═══════════════════════════════════════════════════════════════ async function startBlockchain(): Promise { console.log("\n⚡ PHASE 1: Blockchain Ignition"); console.log("═══════════════════════════════════════════════════════════════\n"); console.log("🚀 Starting Docker Compose for Private Geth Network..."); await run("docker", ["compose", "up", "-d"], BLOCKCHAIN_DIR); console.log("⏳ Waiting for Geth RPC to become available..."); await waitForGeth(60000); console.log("✅ Private Geth Network is Live.\n"); } async function waitForGeth(timeoutMs: number): Promise { const started = Date.now(); let attempts = 0; while (Date.now() - started < timeoutMs) { attempts += 1; try { const response = await axios.post( "http://127.0.0.1:8545", { jsonrpc: "2.0", id: 1, method: "eth_blockNumber", params: [] }, { timeout: 4000 } ); if (typeof response.data?.result === "string") { const blockNumber = parseInt(response.data.result, 16); console.log(` 📡 Geth responded. Current block: ${blockNumber}`); return; } } catch (error) { const elapsed = ((Date.now() - started) / 1000).toFixed(1); if (attempts % 5 === 0) { console.log(` ⏳ Still waiting... (${elapsed}s elapsed)`); } } await sleep(2000); } throw new Error("Timed out waiting for Geth RPC response."); } // ═══════════════════════════════════════════════════════════════ // PHASE 2: THE LEGAL LAYER (AUTO-DEPLOY) // ═══════════════════════════════════════════════════════════════ async function deployContracts(): Promise { console.log("\n📜 PHASE 2: The Legal Layer (Smart Contracts)"); console.log("═══════════════════════════════════════════════════════════════\n"); // console.log("🔨 Compiling contracts..."); // await run("npx", ["hardhat", "compile"], BLOCKCHAIN_DIR); console.log("🔨 Deploying AstralAccess smart contract..."); await run("node", ["deploy.js"], BLOCKCHAIN_DIR); const contractAddress = readLastDeployAddress(); console.log(` 📍 Contract deployed at: ${contractAddress}`); console.log("📤 Exporting ABI and generating config..."); await run("node", ["scripts/export_abi.js", contractAddress], BLOCKCHAIN_DIR); // Verify config was generated if (!fs.existsSync(MIDDLEWARE_CONFIG)) { throw new Error(`Expected generated config missing: ${MIDDLEWARE_CONFIG}`); } // Copy to blockchain directory for reference fs.copyFileSync(MIDDLEWARE_CONFIG, BLOCKCHAIN_CONFIG); console.log("✅ Smart Contracts Deployed & Config Exported.\n"); } // ═══════════════════════════════════════════════════════════════ // PHASE 3: SERVICE LAYER // ═══════════════════════════════════════════════════════════════ async function startServices(): Promise { console.log("\n🛠️ PHASE 3: Service Layer"); console.log("═══════════════════════════════════════════════════════════════\n"); console.log("🎨 Starting Mock ComfyUI (AI Engine)..."); spawnDetached("node", [TSX_CLI, MOCK_COMFY_SCRIPT], ROOT); await sleep(1000); // Brief pause for process spawn console.log("🦞 Starting OpenClaw Middleware..."); // spawnDetached("npm", ["start"], BACKEND_DIR); spawnDetached("node", [TSX_CLI, "server.ts"], BACKEND_DIR); await sleep(2000); // Brief pause for process spawn console.log("⏳ Polling services for availability..."); await waitForUrl("http://127.0.0.1:3001/health", 60000, "Middleware"); console.log(" ✅ OpenClaw Middleware is responding (Port 3001)"); await waitForUrl("http://127.0.0.1:8188/system_stats", 60000, "Mock AI"); console.log(" ✅ Mock ComfyUI is responding (Port 8188)"); console.log("\n✅ All Services Online.\n"); } async function waitForUrl(url: string, timeoutMs: number, serviceName: string): Promise { const started = Date.now(); let attempts = 0; while (Date.now() - started < timeoutMs) { attempts += 1; try { const response = await axios.get(url, { timeout: 4000, validateStatus: () => true }); if (response.status >= 200 && response.status < 500) { return; } } catch (error) { const elapsed = ((Date.now() - started) / 1000).toFixed(1); if (attempts % 10 === 0) { console.log(` ⏳ ${serviceName} not ready yet... (${elapsed}s elapsed)`); } } await sleep(2000); } throw new Error(`Timed out waiting for ${serviceName}: ${url}`); } // ═══════════════════════════════════════════════════════════════ // PHASE 4: SELF-VALIDATION // ═══════════════════════════════════════════════════════════════ async function runValidation(): Promise { console.log("\n🔬 PHASE 4: Self-Validation"); console.log("═══════════════════════════════════════════════════════════════\n"); await run("node", [TSX_CLI, VALIDATE_SCRIPT], ROOT); } // ═══════════════════════════════════════════════════════════════ // MAIN ORCHESTRATION // ═══════════════════════════════════════════════════════════════ async function main(): Promise { console.log("\n"); console.log("╔═══════════════════════════════════════════════════════════════╗"); console.log("║ 🌌 PROJECT ASTRAL - ROBUST BOOTLOADER v2.0 ║"); console.log("╚═══════════════════════════════════════════════════════════════╝"); await ensureBackendDeps(); // Phase 0: Docker Check (Retry Loop - 20 attempts × 5s = 100s max) await waitForDocker(20, 5000); // Phase 1: Blockchain Ignition await startBlockchain(); // Phase 2: Smart Contract Deployment await deployContracts(); // Phase 3: Service Layer (Middleware + AI) await startServices(); // Phase 4: Self-Validation await runValidation(); console.log("\n"); console.log("╔═══════════════════════════════════════════════════════════════╗"); console.log("║ 🎉 ASTRAL BOOT SEQUENCE COMPLETE 🎉 ║"); console.log("╚═══════════════════════════════════════════════════════════════╝"); console.log("\n"); } main().catch((error) => { console.error("\n❌ FATAL ERROR during boot sequence:"); console.error(error instanceof Error ? error.message : String(error)); console.error("\nBoot sequence aborted.\n"); process.exitCode = 1; });