Files
Project_Astral/scripts/boot_astral.ts
2026-02-25 00:50:23 +05:30

289 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string> {
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<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function ensureBackendDeps(): Promise<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
console.log("\n🔬 PHASE 4: Self-Validation");
console.log("═══════════════════════════════════════════════════════════════\n");
await run("node", [TSX_CLI, VALIDATE_SCRIPT], ROOT);
}
// ═══════════════════════════════════════════════════════════════
// MAIN ORCHESTRATION
// ═══════════════════════════════════════════════════════════════
async function main(): Promise<void> {
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;
});