diff --git a/Cargo.lock b/Cargo.lock index 60089c0d..ead081db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6540,7 +6540,7 @@ dependencies = [ [[package]] name = "ruvector-postgres" -version = "0.2.4" +version = "0.2.5" dependencies = [ "approx", "bincode 1.3.3", diff --git a/npm/packages/postgres-cli/package.json b/npm/packages/postgres-cli/package.json index e6380553..e8219dc9 100644 --- a/npm/packages/postgres-cli/package.json +++ b/npm/packages/postgres-cli/package.json @@ -1,6 +1,6 @@ { "name": "@ruvector/postgres-cli", - "version": "0.2.3", + "version": "0.2.4", "description": "Advanced AI vector database CLI for PostgreSQL - pgvector drop-in replacement with 53+ SQL functions, 39 attention mechanisms, GNN layers, hyperbolic embeddings, and self-learning capabilities", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/npm/packages/postgres-cli/src/cli.ts b/npm/packages/postgres-cli/src/cli.ts index 8e15f598..b4328761 100644 --- a/npm/packages/postgres-cli/src/cli.ts +++ b/npm/packages/postgres-cli/src/cli.ts @@ -942,7 +942,7 @@ program program .command('install') - .description('Install RuVector PostgreSQL (Docker or native)') + .description('Install RuVector PostgreSQL (Docker or native with full dependency installation)') .option('-m, --method ', 'Installation method: docker, native, auto', 'auto') .option('-p, --port ', 'PostgreSQL port', '5432') .option('-u, --user ', 'Database user', 'ruvector') @@ -950,7 +950,10 @@ program .option('-d, --database ', 'Database name', 'ruvector') .option('--data-dir ', 'Persistent data directory') .option('--name ', 'Container name', 'ruvector-postgres') - .option('--version ', 'RuVector version', '0.2.3') + .option('--version ', 'RuVector version', '0.2.5') + .option('--pg-version ', 'PostgreSQL version for native install (14, 15, 16, 17)', '16') + .option('--skip-postgres', 'Skip PostgreSQL installation (use existing)') + .option('--skip-rust', 'Skip Rust installation (use existing)') .action(async (options) => { try { await InstallCommands.install({ @@ -962,6 +965,9 @@ program dataDir: options.dataDir, name: options.name, version: options.version, + pgVersion: options.pgVersion, + skipPostgres: options.skipPostgres, + skipRust: options.skipRust, }); } catch (err) { console.error(chalk.red('Error:'), (err as Error).message); diff --git a/npm/packages/postgres-cli/src/commands/install.ts b/npm/packages/postgres-cli/src/commands/install.ts index 75b8e401..5454d182 100644 --- a/npm/packages/postgres-cli/src/commands/install.ts +++ b/npm/packages/postgres-cli/src/commands/install.ts @@ -2,26 +2,26 @@ * RuVector PostgreSQL Installation Commands * * Provides complete installation of RuVector PostgreSQL extension: - * - Docker-based installation (recommended) - * - Native installation with pre-built binaries + * - Full native installation (PostgreSQL + Rust + pgrx + extension) + * - Docker-based installation (recommended for quick start) * - Extension management (enable, disable, upgrade) */ -import { execSync, spawn, exec } from 'child_process'; +import { execSync, spawn, spawnSync } from 'child_process'; import { promisify } from 'util'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; -import * as https from 'https'; import chalk from 'chalk'; import ora from 'ora'; -const execAsync = promisify(exec); - // Constants -const DOCKER_IMAGE = 'ruvector-postgres'; // Local image name -const DOCKER_IMAGE_VERSION = '0.2.3'; -const GITHUB_RELEASES_URL = 'https://api.github.com/repos/ruvnet/ruvector/releases/latest'; +const DOCKER_IMAGE = 'ruvector-postgres'; +const DOCKER_IMAGE_VERSION = '0.2.5'; +const RUVECTOR_CRATE_VERSION = '0.2.5'; +const PGRX_VERSION = '0.12.6'; +const DEFAULT_PG_VERSION = '16'; +const SUPPORTED_PG_VERSIONS = ['14', '15', '16', '17']; const DEFAULT_PORT = 5432; const DEFAULT_USER = 'ruvector'; const DEFAULT_PASSWORD = 'ruvector'; @@ -35,8 +35,11 @@ interface InstallOptions { database?: string; dataDir?: string; version?: string; + pgVersion?: string; detach?: boolean; name?: string; + skipPostgres?: boolean; + skipRust?: boolean; } interface StatusInfo { @@ -49,38 +52,527 @@ interface StatusInfo { connectionString?: string; } +interface SystemInfo { + platform: NodeJS.Platform; + arch: string; + docker: boolean; + postgres: boolean; + pgVersion: string | null; + pgConfig: string | null; + rust: boolean; + rustVersion: string | null; + cargo: boolean; + pgrx: boolean; + pgrxVersion: string | null; + sudo: boolean; + packageManager: 'apt' | 'yum' | 'dnf' | 'brew' | 'pacman' | 'unknown'; +} + export class InstallCommands { /** - * Check system requirements + * Comprehensive system check */ - static async checkRequirements(): Promise<{ docker: boolean; postgres: boolean; pgConfig: string | null }> { - const result = { docker: false, postgres: false, pgConfig: null as string | null }; + static async checkSystem(): Promise { + const info: SystemInfo = { + platform: os.platform(), + arch: os.arch(), + docker: false, + postgres: false, + pgVersion: null, + pgConfig: null, + rust: false, + rustVersion: null, + cargo: false, + pgrx: false, + pgrxVersion: null, + sudo: false, + packageManager: 'unknown', + }; // Check Docker try { execSync('docker --version', { stdio: 'pipe' }); - result.docker = true; - } catch { - result.docker = false; - } + info.docker = true; + } catch { /* not available */ } // Check PostgreSQL try { - execSync('psql --version', { stdio: 'pipe' }); - result.postgres = true; - } catch { - result.postgres = false; - } + const pgVersion = execSync('psql --version', { stdio: 'pipe', encoding: 'utf-8' }); + info.postgres = true; + const match = pgVersion.match(/(\d+)/); + if (match) info.pgVersion = match[1]; + } catch { /* not available */ } // Check pg_config try { - result.pgConfig = execSync('pg_config --libdir', { stdio: 'pipe', encoding: 'utf-8' }).trim(); - } catch { - result.pgConfig = null; + info.pgConfig = execSync('pg_config --libdir', { stdio: 'pipe', encoding: 'utf-8' }).trim(); + } catch { /* not available */ } + + // Check Rust + try { + const rustVersion = execSync('rustc --version', { stdio: 'pipe', encoding: 'utf-8' }); + info.rust = true; + const match = rustVersion.match(/rustc (\d+\.\d+\.\d+)/); + if (match) info.rustVersion = match[1]; + } catch { /* not available */ } + + // Check Cargo + try { + execSync('cargo --version', { stdio: 'pipe' }); + info.cargo = true; + } catch { /* not available */ } + + // Check pgrx + try { + const pgrxVersion = execSync('cargo pgrx --version', { stdio: 'pipe', encoding: 'utf-8' }); + info.pgrx = true; + const match = pgrxVersion.match(/cargo-pgrx (\d+\.\d+\.\d+)/); + if (match) info.pgrxVersion = match[1]; + } catch { /* not available */ } + + // Check sudo + try { + execSync('sudo -n true', { stdio: 'pipe' }); + info.sudo = true; + } catch { /* not available or needs password */ } + + // Detect package manager + if (info.platform === 'darwin') { + try { + execSync('brew --version', { stdio: 'pipe' }); + info.packageManager = 'brew'; + } catch { /* not available */ } + } else if (info.platform === 'linux') { + if (fs.existsSync('/usr/bin/apt-get')) { + info.packageManager = 'apt'; + } else if (fs.existsSync('/usr/bin/dnf')) { + info.packageManager = 'dnf'; + } else if (fs.existsSync('/usr/bin/yum')) { + info.packageManager = 'yum'; + } else if (fs.existsSync('/usr/bin/pacman')) { + info.packageManager = 'pacman'; + } } - return result; + return info; + } + + /** + * Check system requirements (backward compatible) + */ + static async checkRequirements(): Promise<{ docker: boolean; postgres: boolean; pgConfig: string | null }> { + const sys = await this.checkSystem(); + return { + docker: sys.docker, + postgres: sys.postgres, + pgConfig: sys.pgConfig, + }; + } + + /** + * Run command with sudo if needed + */ + static sudoExec(command: string, options: { silent?: boolean } = {}): string { + const needsSudo = process.getuid?.() !== 0; + const fullCommand = needsSudo ? `sudo ${command}` : command; + + return execSync(fullCommand, { + stdio: options.silent ? 'pipe' : 'inherit', + encoding: 'utf-8', + }); + } + + /** + * Install PostgreSQL + */ + static async installPostgreSQL(pgVersion: string, sys: SystemInfo): Promise { + const spinner = ora(`Installing PostgreSQL ${pgVersion}...`).start(); + + try { + if (sys.platform === 'darwin') { + if (sys.packageManager !== 'brew') { + spinner.fail('Homebrew not found. Please install it first: https://brew.sh'); + return false; + } + execSync(`brew install postgresql@${pgVersion}`, { stdio: 'inherit' }); + execSync(`brew services start postgresql@${pgVersion}`, { stdio: 'inherit' }); + + // Add to PATH + const brewPrefix = execSync('brew --prefix', { encoding: 'utf-8' }).trim(); + process.env.PATH = `${brewPrefix}/opt/postgresql@${pgVersion}/bin:${process.env.PATH}`; + + spinner.succeed(`PostgreSQL ${pgVersion} installed via Homebrew`); + return true; + } + + if (sys.platform === 'linux') { + switch (sys.packageManager) { + case 'apt': + // Add PostgreSQL APT repository + spinner.text = 'Adding PostgreSQL APT repository...'; + this.sudoExec('apt-get update'); + this.sudoExec('apt-get install -y wget gnupg2 lsb-release'); + this.sudoExec('sh -c \'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list\''); + this.sudoExec('wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -'); + this.sudoExec('apt-get update'); + + // Install PostgreSQL and dev files + spinner.text = `Installing PostgreSQL ${pgVersion} and development files...`; + this.sudoExec(`apt-get install -y postgresql-${pgVersion} postgresql-server-dev-${pgVersion}`); + + // Start service + this.sudoExec(`systemctl start postgresql`); + this.sudoExec(`systemctl enable postgresql`); + + spinner.succeed(`PostgreSQL ${pgVersion} installed via APT`); + return true; + + case 'dnf': + case 'yum': + const pkg = sys.packageManager; + spinner.text = 'Adding PostgreSQL repository...'; + this.sudoExec(`${pkg} install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm`); + this.sudoExec(`${pkg} install -y postgresql${pgVersion}-server postgresql${pgVersion}-devel`); + this.sudoExec(`/usr/pgsql-${pgVersion}/bin/postgresql-${pgVersion}-setup initdb`); + this.sudoExec(`systemctl start postgresql-${pgVersion}`); + this.sudoExec(`systemctl enable postgresql-${pgVersion}`); + + spinner.succeed(`PostgreSQL ${pgVersion} installed via ${pkg.toUpperCase()}`); + return true; + + case 'pacman': + this.sudoExec(`pacman -S --noconfirm postgresql`); + this.sudoExec(`su - postgres -c "initdb -D /var/lib/postgres/data"`); + this.sudoExec(`systemctl start postgresql`); + this.sudoExec(`systemctl enable postgresql`); + + spinner.succeed('PostgreSQL installed via Pacman'); + return true; + + default: + spinner.fail('Unknown package manager. Please install PostgreSQL manually.'); + return false; + } + } + + spinner.fail(`Unsupported platform: ${sys.platform}`); + return false; + + } catch (error) { + spinner.fail('Failed to install PostgreSQL'); + console.error(chalk.red((error as Error).message)); + return false; + } + } + + /** + * Install Rust + */ + static async installRust(): Promise { + const spinner = ora('Installing Rust...').start(); + + try { + // Use rustup to install Rust + execSync('curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y', { + stdio: 'inherit', + shell: '/bin/bash', + }); + + // Source cargo env + const cargoEnv = path.join(os.homedir(), '.cargo', 'env'); + if (fs.existsSync(cargoEnv)) { + process.env.PATH = `${path.join(os.homedir(), '.cargo', 'bin')}:${process.env.PATH}`; + } + + spinner.succeed('Rust installed via rustup'); + return true; + } catch (error) { + spinner.fail('Failed to install Rust'); + console.error(chalk.red((error as Error).message)); + return false; + } + } + + /** + * Install required build dependencies + */ + static async installBuildDeps(sys: SystemInfo): Promise { + const spinner = ora('Installing build dependencies...').start(); + + try { + if (sys.platform === 'darwin') { + execSync('brew install llvm pkg-config openssl cmake', { stdio: 'inherit' }); + } else if (sys.platform === 'linux') { + switch (sys.packageManager) { + case 'apt': + this.sudoExec('apt-get install -y build-essential libclang-dev clang pkg-config libssl-dev cmake'); + break; + case 'dnf': + case 'yum': + this.sudoExec(`${sys.packageManager} install -y gcc gcc-c++ clang clang-devel openssl-devel cmake make`); + break; + case 'pacman': + this.sudoExec('pacman -S --noconfirm base-devel clang openssl cmake'); + break; + default: + spinner.warn('Please install: gcc, clang, libclang-dev, pkg-config, libssl-dev, cmake'); + return true; + } + } + + spinner.succeed('Build dependencies installed'); + return true; + } catch (error) { + spinner.fail('Failed to install build dependencies'); + console.error(chalk.red((error as Error).message)); + return false; + } + } + + /** + * Install cargo-pgrx + */ + static async installPgrx(pgVersion: string): Promise { + const spinner = ora(`Installing cargo-pgrx ${PGRX_VERSION}...`).start(); + + try { + execSync(`cargo install cargo-pgrx --version ${PGRX_VERSION} --locked`, { stdio: 'inherit' }); + spinner.succeed(`cargo-pgrx ${PGRX_VERSION} installed`); + + // Initialize pgrx + spinner.start(`Initializing pgrx for PostgreSQL ${pgVersion}...`); + + // Find pg_config + let pgConfigPath: string; + try { + pgConfigPath = execSync(`which pg_config`, { encoding: 'utf-8' }).trim(); + } catch { + // Try common paths + const commonPaths = [ + `/usr/lib/postgresql/${pgVersion}/bin/pg_config`, + `/usr/pgsql-${pgVersion}/bin/pg_config`, + `/opt/homebrew/opt/postgresql@${pgVersion}/bin/pg_config`, + `/usr/local/opt/postgresql@${pgVersion}/bin/pg_config`, + ]; + pgConfigPath = commonPaths.find(p => fs.existsSync(p)) || 'pg_config'; + } + + execSync(`cargo pgrx init --pg${pgVersion}=${pgConfigPath}`, { stdio: 'inherit' }); + spinner.succeed(`pgrx initialized for PostgreSQL ${pgVersion}`); + + return true; + } catch (error) { + spinner.fail('Failed to install/initialize pgrx'); + console.error(chalk.red((error as Error).message)); + return false; + } + } + + /** + * Build and install ruvector-postgres extension + */ + static async buildAndInstallExtension(pgVersion: string): Promise { + const spinner = ora('Building ruvector-postgres extension from crates.io...').start(); + + try { + // Create temporary directory + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvector-')); + const projectDir = path.join(tmpDir, 'ruvector-postgres'); + + spinner.text = 'Creating build project...'; + fs.mkdirSync(projectDir, { recursive: true }); + + // Create minimal Cargo.toml to build the extension + const cargoToml = ` +[package] +name = "ruvector-build" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[features] +default = ["pg${pgVersion}"] +pg14 = ["ruvector-postgres/pg14"] +pg15 = ["ruvector-postgres/pg15"] +pg16 = ["ruvector-postgres/pg16"] +pg17 = ["ruvector-postgres/pg17"] + +[dependencies] +ruvector-postgres = "${RUVECTOR_CRATE_VERSION}" +pgrx = "0.12" + +[dev-dependencies] +pgrx-tests = "0.12" +`; + + fs.writeFileSync(path.join(projectDir, 'Cargo.toml'), cargoToml); + + // Create minimal lib.rs that re-exports ruvector-postgres + fs.mkdirSync(path.join(projectDir, 'src')); + fs.writeFileSync(path.join(projectDir, 'src', 'lib.rs'), ` +pub use ruvector_postgres::*; +`); + + spinner.text = 'Downloading and compiling (this may take 5-10 minutes)...'; + + // Build and install using pgrx + execSync(`cargo pgrx install --features pg${pgVersion} --release`, { + cwd: projectDir, + stdio: 'inherit', + env: { + ...process.env, + CARGO_NET_GIT_FETCH_WITH_CLI: 'true', + }, + }); + + // Cleanup + fs.rmSync(tmpDir, { recursive: true, force: true }); + + spinner.succeed('ruvector-postgres extension installed'); + return true; + } catch (error) { + spinner.fail('Failed to build extension'); + console.error(chalk.red((error as Error).message)); + return false; + } + } + + /** + * Configure PostgreSQL for the extension + */ + static async configurePostgreSQL(options: InstallOptions): Promise { + const spinner = ora('Configuring PostgreSQL...').start(); + + const user = options.user || DEFAULT_USER; + const password = options.password || DEFAULT_PASSWORD; + const database = options.database || DEFAULT_DB; + + try { + // Create user and database + const commands = [ + `CREATE USER ${user} WITH PASSWORD '${password}' SUPERUSER;`, + `CREATE DATABASE ${database} OWNER ${user};`, + `\\c ${database}`, + `CREATE EXTENSION IF NOT EXISTS ruvector;`, + ]; + + for (const cmd of commands) { + try { + execSync(`sudo -u postgres psql -c "${cmd}"`, { stdio: 'pipe' }); + } catch { + // User/DB might already exist, that's OK + } + } + + spinner.succeed('PostgreSQL configured'); + return true; + } catch (error) { + spinner.fail('Failed to configure PostgreSQL'); + console.error(chalk.red((error as Error).message)); + return false; + } + } + + /** + * Full native installation + */ + static async installNativeFull(options: InstallOptions = {}): Promise { + const pgVersion = options.pgVersion || DEFAULT_PG_VERSION; + + console.log(chalk.bold.blue('\nšŸš€ RuVector PostgreSQL Native Installation\n')); + console.log(chalk.gray('This will install PostgreSQL, Rust, and the RuVector extension.\n')); + + // Check system + let sys = await this.checkSystem(); + + console.log(chalk.bold('šŸ“‹ System Check:')); + console.log(` Platform: ${chalk.cyan(sys.platform)} ${chalk.cyan(sys.arch)}`); + console.log(` PostgreSQL: ${sys.postgres ? chalk.green(`āœ“ ${sys.pgVersion}`) : chalk.yellow('āœ— Not installed')}`); + console.log(` Rust: ${sys.rust ? chalk.green(`āœ“ ${sys.rustVersion}`) : chalk.yellow('āœ— Not installed')}`); + console.log(` cargo-pgrx: ${sys.pgrx ? chalk.green(`āœ“ ${sys.pgrxVersion}`) : chalk.yellow('āœ— Not installed')}`); + console.log(` Pkg Manager: ${chalk.cyan(sys.packageManager)}`); + console.log(); + + // Install PostgreSQL if needed + if (!sys.postgres && !options.skipPostgres) { + console.log(chalk.bold(`\nšŸ“¦ Step 1: Installing PostgreSQL ${pgVersion}`)); + const installed = await this.installPostgreSQL(pgVersion, sys); + if (!installed) { + throw new Error('Failed to install PostgreSQL'); + } + sys = await this.checkSystem(); // Refresh + } else if (sys.postgres) { + console.log(chalk.green(`āœ“ PostgreSQL ${sys.pgVersion} already installed`)); + } + + // Install build dependencies + console.log(chalk.bold('\nšŸ”§ Step 2: Installing build dependencies')); + await this.installBuildDeps(sys); + + // Install Rust if needed + if (!sys.rust && !options.skipRust) { + console.log(chalk.bold('\nšŸ¦€ Step 3: Installing Rust')); + const installed = await this.installRust(); + if (!installed) { + throw new Error('Failed to install Rust'); + } + sys = await this.checkSystem(); // Refresh + } else if (sys.rust) { + console.log(chalk.green(`āœ“ Rust ${sys.rustVersion} already installed`)); + } + + // Install pgrx if needed + const targetPgVersion = options.pgVersion || sys.pgVersion || DEFAULT_PG_VERSION; + if (!sys.pgrx || sys.pgrxVersion !== PGRX_VERSION) { + console.log(chalk.bold('\nšŸ”Œ Step 4: Installing cargo-pgrx')); + const installed = await this.installPgrx(targetPgVersion); + if (!installed) { + throw new Error('Failed to install pgrx'); + } + } else { + console.log(chalk.green(`āœ“ cargo-pgrx ${sys.pgrxVersion} already installed`)); + } + + // Build and install extension + console.log(chalk.bold('\nšŸ—ļø Step 5: Building RuVector extension')); + const built = await this.buildAndInstallExtension(targetPgVersion); + if (!built) { + throw new Error('Failed to build extension'); + } + + // Configure PostgreSQL + console.log(chalk.bold('\nāš™ļø Step 6: Configuring PostgreSQL')); + await this.configurePostgreSQL(options); + + // Success! + const port = options.port || DEFAULT_PORT; + const user = options.user || DEFAULT_USER; + const password = options.password || DEFAULT_PASSWORD; + const database = options.database || DEFAULT_DB; + const connString = `postgresql://${user}:${password}@localhost:${port}/${database}`; + + console.log(chalk.green.bold('\nāœ… RuVector PostgreSQL installed successfully!\n')); + + console.log(chalk.bold('Connection Details:')); + console.log(` Host: ${chalk.cyan('localhost')}`); + console.log(` Port: ${chalk.cyan(port.toString())}`); + console.log(` User: ${chalk.cyan(user)}`); + console.log(` Password: ${chalk.cyan(password)}`); + console.log(` Database: ${chalk.cyan(database)}`); + + console.log(chalk.bold('\nConnection String:')); + console.log(` ${chalk.cyan(connString)}`); + + console.log(chalk.bold('\nQuick Test:')); + console.log(chalk.gray(` psql "${connString}" -c "SELECT ruvector_version();"`)); + + console.log(chalk.bold('\nExample Usage:')); + console.log(chalk.gray(' CREATE TABLE embeddings (id serial, vec real[384]);')); + console.log(chalk.gray(' CREATE INDEX ON embeddings USING hnsw (vec);')); + console.log(chalk.gray(' INSERT INTO embeddings (vec) VALUES (ARRAY[0.1, 0.2, ...]);')); } /** @@ -90,35 +582,32 @@ export class InstallCommands { const spinner = ora('Checking system requirements...').start(); try { - const reqs = await this.checkRequirements(); + const sys = await this.checkSystem(); spinner.succeed('System check complete'); console.log(chalk.bold('\nšŸ“‹ System Status:')); - console.log(` Docker: ${reqs.docker ? chalk.green('āœ“ Available') : chalk.yellow('āœ— Not found')}`); - console.log(` PostgreSQL: ${reqs.postgres ? chalk.green('āœ“ Available') : chalk.yellow('āœ— Not found')}`); + console.log(` Docker: ${sys.docker ? chalk.green('āœ“ Available') : chalk.yellow('āœ— Not found')}`); + console.log(` PostgreSQL: ${sys.postgres ? chalk.green(`āœ“ ${sys.pgVersion}`) : chalk.yellow('āœ— Not found')}`); + console.log(` Rust: ${sys.rust ? chalk.green(`āœ“ ${sys.rustVersion}`) : chalk.yellow('āœ— Not found')}`); const method = options.method || 'auto'; if (method === 'auto') { - if (reqs.docker) { - console.log(chalk.cyan('\n→ Using Docker installation (recommended)\n')); + // Prefer Docker for simplicity, fall back to native + if (sys.docker) { + console.log(chalk.cyan('\n→ Using Docker installation (fastest)\n')); await this.installDocker(options); - } else if (reqs.postgres && reqs.pgConfig) { - console.log(chalk.cyan('\n→ Using native installation\n')); - await this.installNative(options); } else { - throw new Error('Neither Docker nor PostgreSQL found. Please install Docker or PostgreSQL first.'); + console.log(chalk.cyan('\n→ Using native installation (will install all dependencies)\n')); + await this.installNativeFull(options); } } else if (method === 'docker') { - if (!reqs.docker) { + if (!sys.docker) { throw new Error('Docker not found. Please install Docker first: https://docs.docker.com/get-docker/'); } await this.installDocker(options); } else if (method === 'native') { - if (!reqs.postgres) { - throw new Error('PostgreSQL not found. Please install PostgreSQL first.'); - } - await this.installNative(options); + await this.installNativeFull(options); } } catch (error) { spinner.fail('Installation failed'); @@ -141,7 +630,7 @@ export class InstallCommands { // Check if container already exists const existingSpinner = ora('Checking for existing installation...').start(); try { - const existing = execSync(`docker ps -a --filter name=${containerName} --format "{{.ID}}"`, { encoding: 'utf-8' }).trim(); + const existing = execSync(`docker ps -a --filter name=^${containerName}$ --format "{{.ID}}"`, { encoding: 'utf-8' }).trim(); if (existing) { existingSpinner.warn(`Container '${containerName}' already exists`); console.log(chalk.yellow(` Run 'ruvector-pg uninstall' first or use a different --name`)); @@ -174,9 +663,13 @@ export class InstallCommands { } catch { pullSpinner.fail('Image not found locally or on Docker Hub'); console.log(chalk.yellow('\nšŸ“¦ To build the image locally, run:')); - console.log(chalk.gray(' docker build -f crates/ruvector-postgres/docker/Dockerfile -t ruvector-postgres:0.2.3 .')); - console.log(chalk.yellow('\n Then run this install command again.\n')); - throw new Error(`RuVector Docker image not available. Build it first or check Docker Hub.`); + console.log(chalk.gray(' git clone https://github.com/ruvnet/ruvector.git')); + console.log(chalk.gray(' cd ruvector')); + console.log(chalk.gray(' docker build -f crates/ruvector-postgres/docker/Dockerfile -t ruvector-postgres:0.2.5 .')); + console.log(chalk.yellow('\n Then run this install command again.')); + console.log(chalk.yellow('\nšŸ’” Or use native installation:')); + console.log(chalk.gray(' npx @ruvector/postgres-cli install --method native\n')); + throw new Error(`RuVector Docker image not available. Build it first or use native installation.`); } } } @@ -201,7 +694,7 @@ export class InstallCommands { // Run container const runSpinner = ora('Starting RuVector PostgreSQL...').start(); try { - const containerId = execSync(runCmd, { encoding: 'utf-8' }).trim(); + execSync(runCmd, { encoding: 'utf-8' }); runSpinner.succeed('Container started'); // Wait for PostgreSQL to be ready @@ -270,80 +763,11 @@ export class InstallCommands { } /** - * Install native extension (download pre-built binaries) + * Install native extension (download pre-built binaries) - Legacy method */ static async installNative(options: InstallOptions = {}): Promise { - const spinner = ora('Detecting system...').start(); - - const platform = os.platform(); - const arch = os.arch(); - - spinner.text = `Detected: ${platform}-${arch}`; - - // Determine binary name - let binaryName: string; - if (platform === 'linux' && arch === 'x64') { - binaryName = 'ruvector-pg16-linux-x64.tar.gz'; - } else if (platform === 'darwin' && arch === 'arm64') { - binaryName = 'ruvector-pg16-darwin-arm64.tar.gz'; - } else if (platform === 'darwin' && arch === 'x64') { - binaryName = 'ruvector-pg16-darwin-x64.tar.gz'; - } else { - spinner.fail(`Unsupported platform: ${platform}-${arch}`); - console.log(chalk.yellow('\nPre-built binaries not available for your platform.')); - console.log(chalk.yellow('Please use Docker installation or build from source:')); - console.log(chalk.gray(' cargo install cargo-pgrx')); - console.log(chalk.gray(' cargo pgrx install')); - return; - } - - spinner.succeed(`System: ${platform}-${arch}`); - - // Get pg_config paths - const pgConfigSpinner = ora('Getting PostgreSQL paths...').start(); - let libDir: string; - let shareDir: string; - - try { - libDir = execSync('pg_config --pkglibdir', { encoding: 'utf-8' }).trim(); - shareDir = execSync('pg_config --sharedir', { encoding: 'utf-8' }).trim(); - pgConfigSpinner.succeed('PostgreSQL paths found'); - console.log(` Library dir: ${chalk.cyan(libDir)}`); - console.log(` Share dir: ${chalk.cyan(shareDir)}`); - } catch { - pgConfigSpinner.fail('Could not find pg_config'); - throw new Error('PostgreSQL development files not found. Install postgresql-server-dev-XX package.'); - } - - // Download release - const downloadSpinner = ora('Fetching latest release info...').start(); - - try { - // For now, provide manual instructions - // In production, this would download from GitHub releases - downloadSpinner.info('Native installation requires manual steps'); - - console.log(chalk.bold('\nšŸ“¦ Manual Installation Steps:\n')); - console.log('1. Download the pre-built extension:'); - console.log(chalk.gray(` https://github.com/ruvnet/ruvector/releases/latest`)); - console.log(` Look for: ${chalk.cyan(binaryName)}`); - - console.log('\n2. Extract and copy files:'); - console.log(chalk.gray(` tar -xzf ${binaryName}`)); - console.log(chalk.gray(` sudo cp ruvector.so ${libDir}/`)); - console.log(chalk.gray(` sudo cp ruvector.control ${shareDir}/extension/`)); - console.log(chalk.gray(` sudo cp ruvector--*.sql ${shareDir}/extension/`)); - - console.log('\n3. Enable the extension:'); - console.log(chalk.gray(` psql -c "CREATE EXTENSION ruvector;"`)); - - console.log(chalk.yellow('\nšŸ’” Tip: Use Docker for easier installation:')); - console.log(chalk.gray(' ruvector-pg install --method docker')); - - } catch (error) { - downloadSpinner.fail('Failed to get release info'); - throw error; - } + // Redirect to full native installation + await this.installNativeFull(options); } /** @@ -411,18 +835,26 @@ export class InstallCommands { info.containerId = execSync(`docker inspect ${containerName} --format '{{.Id}}'`, { encoding: 'utf-8' }).trim().substring(0, 12); // Get port mapping - const portMapping = execSync( - `docker port ${containerName} 5432`, - { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] } - ).trim(); - const portMatch = portMapping.match(/:(\d+)$/); - if (portMatch) { - info.port = parseInt(portMatch[1]); - info.connectionString = `postgresql://ruvector:ruvector@localhost:${info.port}/ruvector`; - } + try { + const portMapping = execSync( + `docker port ${containerName} 5432`, + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] } + ).trim(); + const portMatch = portMapping.match(/:(\d+)$/); + if (portMatch) { + info.port = parseInt(portMatch[1]); + info.connectionString = `postgresql://ruvector:ruvector@localhost:${info.port}/ruvector`; + } + } catch { /* port not mapped */ } } catch { - // No Docker installation found + // No Docker installation found, check native + try { + execSync('psql -c "SELECT 1 FROM pg_extension WHERE extname = \'ruvector\'" 2>/dev/null', { stdio: 'pipe' }); + info.installed = true; + info.running = true; + info.method = 'native'; + } catch { /* not installed */ } } return info; @@ -518,11 +950,6 @@ export class InstallCommands { const containerName = options.name || 'ruvector-postgres'; const tail = options.tail || 100; - let cmd = `docker logs ${containerName} --tail ${tail}`; - if (options.follow) { - cmd += ' -f'; - } - try { if (options.follow) { const child = spawn('docker', ['logs', containerName, '--tail', tail.toString(), '-f'], { @@ -532,7 +959,7 @@ export class InstallCommands { console.error(chalk.red(`Error: ${err.message}`)); }); } else { - const output = execSync(cmd, { encoding: 'utf-8' }); + const output = execSync(`docker logs ${containerName} --tail ${tail}`, { encoding: 'utf-8' }); console.log(output); } } catch (error) {