feat(postgres-cli): Add full native installation support

The CLI now supports complete native installation without Docker:
- Auto-detects and installs PostgreSQL (apt, yum, dnf, pacman, brew)
- Installs Rust via rustup if not present
- Installs cargo-pgrx and initializes for target PG version
- Builds ruvector-postgres 0.2.5 from crates.io
- Configures PostgreSQL with user, database, and extension

New install options:
  --method native       Force native installation
  --pg-version <ver>    PostgreSQL version (14, 15, 16, 17)
  --skip-postgres       Use existing PostgreSQL
  --skip-rust           Use existing Rust

Usage:
  npx @ruvector/postgres-cli install --method native
  npx @ruvector/postgres-cli install --method native --pg-version 17

Published as @ruvector/postgres-cli@0.2.4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
rUv 2025-12-06 17:52:48 +00:00
parent 88a9b4ceb6
commit ff4009897d
4 changed files with 569 additions and 136 deletions

2
Cargo.lock generated
View file

@ -6540,7 +6540,7 @@ dependencies = [
[[package]]
name = "ruvector-postgres"
version = "0.2.4"
version = "0.2.5"
dependencies = [
"approx",
"bincode 1.3.3",

View file

@ -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",

View file

@ -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 <type>', 'Installation method: docker, native, auto', 'auto')
.option('-p, --port <number>', 'PostgreSQL port', '5432')
.option('-u, --user <name>', 'Database user', 'ruvector')
@ -950,7 +950,10 @@ program
.option('-d, --database <name>', 'Database name', 'ruvector')
.option('--data-dir <path>', 'Persistent data directory')
.option('--name <name>', 'Container name', 'ruvector-postgres')
.option('--version <version>', 'RuVector version', '0.2.3')
.option('--version <version>', 'RuVector version', '0.2.5')
.option('--pg-version <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);

View file

@ -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<SystemInfo> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<void> {
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<void> {
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) {