UNPKG

caravan-x

Version:

A terminal-based utility for managing Caravan multisig wallets in regtest mode. This tool simplifies development and testing with Caravan by providing an easy-to-use interface

341 lines (340 loc) • 13.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SetupWizard = void 0; const prompts_1 = require("@inquirer/prompts"); const chalk_1 = __importDefault(require("chalk")); const boxen_1 = __importDefault(require("boxen")); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const config_1 = require("../types/config"); const docker_1 = require("../core/docker"); class SetupWizard { constructor(appDir) { this.appDir = appDir; fs.ensureDirSync(appDir); } /** * Setup Docker mode - called from index.ts * @param skipDockerStart - if true, don't start containers (just return config) */ async setupDockerMode(skipDockerStart = false) { console.log((0, boxen_1.default)(chalk_1.default.white.bold("🐳 Docker Mode Configuration\n\n") + chalk_1.default.gray("Caravan-X will configure:\n") + chalk_1.default.white(" • Bitcoin Core regtest container\n") + chalk_1.default.white(" • RPC authentication\n") + chalk_1.default.white(" • Nginx proxy for easy access\n") + chalk_1.default.white(" • Watch-only wallet"), { padding: 1, margin: 1, borderStyle: "round", borderColor: "cyan", })); // Bitcoin data directory - default to under appDir const defaultBitcoinDataDir = path.join(this.appDir, "docker-data", "bitcoin-data"); // Check platform for recommendations const isMacOS = process.platform === "darwin"; if (isMacOS) { console.log((0, boxen_1.default)(chalk_1.default.cyan.bold("šŸŽ macOS Note\n\n") + chalk_1.default.white("For Docker compatibility, data will be stored under:\n") + chalk_1.default.cyan(defaultBitcoinDataDir), { padding: 1, margin: 1, borderStyle: "round", borderColor: "cyan", })); } const useCustomBitcoinDir = await (0, prompts_1.confirm)({ message: `Use default Bitcoin data location? (${defaultBitcoinDataDir})`, default: true, }); let bitcoinDataDir; if (useCustomBitcoinDir) { bitcoinDataDir = defaultBitcoinDataDir; } else { bitcoinDataDir = await (0, prompts_1.input)({ message: "Enter Bitcoin data directory path:", default: defaultBitcoinDataDir, validate: (inputPath) => { if (!inputPath.trim()) return "Path cannot be empty"; return true; }, }); } // Ensure directory exists and is writable try { await fs.ensureDir(bitcoinDataDir); const testFile = path.join(bitcoinDataDir, ".write-test"); await fs.writeFile(testFile, "test"); await fs.remove(testFile); } catch (error) { console.log((0, boxen_1.default)(chalk_1.default.red.bold("āŒ Permission Error\n\n") + chalk_1.default.white(`Cannot write to: ${bitcoinDataDir}\n\n`) + chalk_1.default.yellow("Please choose a different directory or fix permissions."), { padding: 1, margin: 1, borderStyle: "round", borderColor: "red", })); throw error; } console.log(chalk_1.default.white("\nšŸ“‹ Docker Configuration:\n")); const containerName = await (0, prompts_1.input)({ message: "Docker container name:", default: config_1.DEFAULT_DOCKER_CONFIG.containerName, }); const rpcPort = await (0, prompts_1.number)({ message: "Bitcoin RPC port:", default: config_1.DEFAULT_DOCKER_CONFIG.ports.rpc, }); const p2pPort = await (0, prompts_1.number)({ message: "Bitcoin P2P port:", default: config_1.DEFAULT_DOCKER_CONFIG.ports.p2p, }); console.log(chalk_1.default.white("\nšŸ” RPC Authentication:\n")); const rpcUser = await (0, prompts_1.input)({ message: "RPC username:", default: "caravan_user", validate: (input) => input.length > 0 || "Username cannot be empty", }); const rpcPassword = await (0, prompts_1.input)({ message: "RPC password:", default: "caravan_pass", validate: (input) => input.length > 0 || "Password cannot be empty", }); console.log(chalk_1.default.white("\nšŸ’¼ Wallet Configuration:\n")); const walletName = await (0, prompts_1.input)({ message: "Watch-only wallet name:", default: "caravan_watcher", validate: (input) => { if (input.length === 0) return "Wallet name cannot be empty"; if (!/^[a-zA-Z0-9_]+$/.test(input)) return "Only alphanumeric and underscore allowed"; return true; }, }); const preGenerateBlocks = await (0, prompts_1.confirm)({ message: "Generate 101 initial blocks? (Needed for spending)", default: true, }); // Create Docker config const dockerConfig = { enabled: true, image: config_1.DEFAULT_DOCKER_CONFIG.image, containerName, ports: { rpc: rpcPort, p2p: p2pPort, nginx: 8080, }, volumes: { bitcoinData: bitcoinDataDir, coordinator: path.join(this.appDir, "coordinator"), }, network: config_1.DEFAULT_DOCKER_CONFIG.network, autoStart: true, }; // Create shared config const sharedConfig = { version: "1.0.0", name: "Caravan-X Docker Setup", mode: config_1.SetupMode.DOCKER, bitcoin: { network: "regtest", rpcPort: rpcPort, p2pPort: p2pPort, rpcUser, rpcPassword, }, docker: dockerConfig, initialState: { blockHeight: preGenerateBlocks ? 101 : 0, preGenerateBlocks, wallets: [], transactions: [], }, walletName, snapshots: { enabled: true, autoSnapshot: false, }, }; // Create enhanced config (nginx port will be updated after Docker starts) const config = { mode: config_1.SetupMode.DOCKER, sharedConfig, docker: dockerConfig, bitcoin: { protocol: "http", host: "localhost", port: 8080, // Will be updated to actual nginx port user: rpcUser, pass: rpcPassword, dataDir: bitcoinDataDir, }, appDir: this.appDir, caravanDir: path.join(this.appDir, "wallets"), keysDir: path.join(this.appDir, "keys"), snapshots: { enabled: true, directory: path.join(this.appDir, "snapshots"), autoSnapshot: false, }, scenariosDir: path.join(this.appDir, "scenarios"), }; // Optionally start Docker if (!skipDockerStart) { const startNow = await (0, prompts_1.confirm)({ message: "Start Docker containers now?", default: true, }); if (startNow) { const dockerService = new docker_1.DockerService(config.docker, path.join(config.appDir, "docker-data")); const nginxPort = await dockerService.completeSetup(config.sharedConfig); config.bitcoin.port = nginxPort; } } // Ensure directories exist await fs.ensureDir(config.caravanDir); await fs.ensureDir(config.keysDir); await fs.ensureDir(config.snapshots.directory); await fs.ensureDir(config.scenariosDir); return config; } /** * Setup Manual mode - called from index.ts */ async setupManualMode() { console.log((0, boxen_1.default)(chalk_1.default.white.bold("āš™ļø Manual Mode Configuration\n\n") + chalk_1.default.gray("You'll need to:\n") + chalk_1.default.white(" • Have Bitcoin Core installed\n") + chalk_1.default.white(" • Run bitcoind in regtest mode\n") + chalk_1.default.white(" • Configure RPC authentication"), { padding: 1, margin: 1, borderStyle: "round", borderColor: "yellow", })); console.log(chalk_1.default.white("\nšŸ“” Bitcoin Core RPC Settings:\n")); const protocol = await (0, prompts_1.select)({ message: "RPC protocol:", choices: [ { name: "HTTP", value: "http" }, { name: "HTTPS", value: "https" }, ], default: "http", }); const host = await (0, prompts_1.input)({ message: "RPC host:", default: "127.0.0.1", }); const port = await (0, prompts_1.number)({ message: "RPC port:", default: 18443, }); const user = await (0, prompts_1.input)({ message: "RPC username:", default: "user", }); const pass = await (0, prompts_1.input)({ message: "RPC password:", default: "pass", }); const dataDir = await (0, prompts_1.input)({ message: "Bitcoin Core data directory:", default: path.join(process.env.HOME || "~", ".bitcoin"), }); // Create shared config const sharedConfig = { version: "1.0.0", name: "Caravan-X Manual Setup", description: "Manual Bitcoin Core configuration", mode: config_1.SetupMode.MANUAL, bitcoin: { network: "regtest", rpcPort: port, p2pPort: 18444, rpcUser: user, rpcPassword: pass, }, initialState: { blockHeight: 0, preGenerateBlocks: false, wallets: [], transactions: [], }, snapshots: { enabled: true, autoSnapshot: false, }, }; // Create enhanced config const config = { mode: config_1.SetupMode.MANUAL, sharedConfig, bitcoin: { protocol: protocol, host, port: port, user, pass, dataDir, }, appDir: this.appDir, caravanDir: path.join(this.appDir, "wallets"), keysDir: path.join(this.appDir, "keys"), snapshots: { enabled: true, directory: path.join(this.appDir, "snapshots"), autoSnapshot: false, }, scenariosDir: path.join(this.appDir, "scenarios"), }; // Ensure directories exist await fs.ensureDir(config.caravanDir); await fs.ensureDir(config.keysDir); await fs.ensureDir(config.snapshots.directory); await fs.ensureDir(config.scenariosDir); return config; } } exports.SetupWizard = SetupWizard;