mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-24 05:43:58 +00:00
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:
parent
88a9b4ceb6
commit
ff4009897d
4 changed files with 569 additions and 136 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -6540,7 +6540,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ruvector-postgres"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"bincode 1.3.3",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue