Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,30 @@ jobs:
- platform: alpine-amd64
cli_script: docker-tests/test_alpine_amd64.sh
python_script: docker-tests/python/test_alpine_amd64.sh
- platform: missing-libs-debian-amd64
cli_script: docker-tests/test_missing_libs.sh
python_script: ""

steps:
- uses: actions/checkout@v4

- name: Install Rust
if: matrix.platform == 'missing-libs-debian-amd64'
uses: dtolnay/rust-toolchain@stable

- name: Build Linux binary
if: matrix.platform == 'missing-libs-debian-amd64'
run: |
cargo build --release
echo "PG0_BINARY_PATH=$(pwd)/target/release/pg0" >> $GITHUB_ENV

- name: Run CLI Docker test
run: |
chmod +x ${{ matrix.cli_script }}
bash ${{ matrix.cli_script }}

- name: Run Python SDK Docker test
if: matrix.python_script != ''
run: |
chmod +x ${{ matrix.python_script }}
bash ${{ matrix.python_script }}
Expand Down
1 change: 1 addition & 0 deletions docker-tests/run_all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ run_test "Debian AMD64" "$DIR/test_debian_amd64.sh"
run_test "Debian ARM64" "$DIR/test_debian_arm64.sh"
run_test "Alpine AMD64" "$DIR/test_alpine_amd64.sh"
run_test "Alpine ARM64" "$DIR/test_alpine_arm64.sh"
run_test "Missing Libs Detection (Debian AMD64)" "$DIR/test_missing_libs.sh"

# Print summary
echo ""
Expand Down
122 changes: 122 additions & 0 deletions docker-tests/test_missing_libs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/bin/bash
set -e

echo "============================================="
echo "Testing pg0 missing shared library detection"
echo "Image: python:3.11-slim"
echo "Platform: linux/amd64"
echo "============================================="

# Get the script directory to find install.sh
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
INSTALL_SCRIPT="$SCRIPT_DIR/../install.sh"

# PG0_BINARY_PATH is required - this test must use a binary built from source
# (with the shared library detection code), not the released binary
if [ -z "${PG0_BINARY_PATH:-}" ]; then
echo "ERROR: PG0_BINARY_PATH must be set to a Linux binary built from this branch"
exit 1
fi

echo "Using local binary: $PG0_BINARY_PATH"

# Create a temporary test script to run inside the container
# This avoids nested heredoc quoting issues
TEMP_SCRIPT=$(mktemp)
cat > "$TEMP_SCRIPT" << 'INNERSCRIPT'
#!/bin/bash
set -e

echo "=== System Info ==="
uname -m
cat /etc/os-release | grep PRETTY_NAME

echo ""
echo "=== Installing dependencies ==="
apt-get update -qq
apt-get install -y curl libxml2 libssl3 libgssapi-krb5-2 sudo procps 2>&1 | grep -v "^Get:" || true
apt-get install -y libicu72 2>/dev/null || apt-get install -y libicu74 2>/dev/null || apt-get install -y libicu* 2>&1 | head -5

echo ""
echo "=== Creating non-root user ==="
useradd -m -s /bin/bash pguser
echo "pguser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

echo ""
echo "=== Installing pg0 from local binary ==="
cp /tmp/pg0-binary /usr/local/bin/pg0
chmod 755 /usr/local/bin/pg0

echo ""
echo "=== Phase 1: Initial extraction with all deps present ==="
su -s /bin/bash - pguser -c '
set -e
export PATH="/usr/local/bin:$PATH"

echo "=== Starting PostgreSQL (initial extraction) ==="
pg0 start
sleep 3

echo "=== Stopping PostgreSQL ==="
pg0 stop
sleep 1

echo "=== Removing extracted installation to force re-extraction ==="
rm -rf ~/.pg0/installation
echo "Installation directory cleared."
'

echo ""
echo "=== Phase 2: Remove libxml2 to simulate missing library ==="
apt-get remove -y libxml2 2>&1 | tail -3

echo ""
echo "=== Phase 3: Verify pg0 detects missing libraries ==="
su -s /bin/bash - pguser -c '
set -e
export PATH="/usr/local/bin:$PATH"

echo "=== Starting pg0 (should fail with missing library error) ==="
OUTPUT=$(pg0 start 2>&1 || true)
echo "$OUTPUT"

echo ""
echo "=== Checking error message ==="

if echo "$OUTPUT" | grep -q "missing required system libraries"; then
echo "PASS: Found missing required system libraries message"
else
echo "FAIL: Missing expected error message about shared libraries"
exit 1
fi

if echo "$OUTPUT" | grep -qi "libxml2"; then
echo "PASS: Found libxml2 in the missing library list"
else
echo "FAIL: Expected libxml2 to be listed as missing"
exit 1
fi

if echo "$OUTPUT" | grep -q "Install the missing libraries"; then
echo "PASS: Found install guidance message"
else
echo "FAIL: Missing install guidance"
exit 1
fi

echo ""
echo "============================================="
echo "ALL CHECKS PASSED - Missing libs detected"
echo "============================================="
'
INNERSCRIPT

docker run --rm --platform=linux/amd64 \
-v "$PG0_BINARY_PATH:/tmp/pg0-binary:ro" \
-v "$TEMP_SCRIPT:/tmp/test_script.sh:ro" \
python:3.11-slim bash /tmp/test_script.sh

rm -f "$TEMP_SCRIPT"

echo ""
echo "Test completed successfully!"
43 changes: 43 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,53 @@ fn extract_bundled_postgresql(installation_dir: &PathBuf, pg_version: &str) -> R
}
}

// Check for missing shared libraries on Linux
#[cfg(target_os = "linux")]
check_shared_libraries(&bin_dir)?;

println!("PostgreSQL {} extracted successfully.", pg_version);
Ok(version_dir)
}

/// Check that the postgres binary can find all required shared libraries.
/// Only called on Linux. If ldd is unavailable, silently skips the check.
#[cfg(target_os = "linux")]
fn check_shared_libraries(bin_dir: &std::path::Path) -> Result<(), CliError> {
let postgres_path = bin_dir.join("postgres");
let output = match std::process::Command::new("ldd")
.arg(&postgres_path)
.output()
{
Ok(output) => output,
Err(e) => {
tracing::debug!("Could not run ldd to check shared libraries: {}", e);
return Ok(());
}
};

let stdout = String::from_utf8_lossy(&output.stdout);
let missing: Vec<&str> = stdout
.lines()
.filter(|line| line.contains("not found"))
.map(|line| line.trim())
.collect();

if missing.is_empty() {
return Ok(());
}

let missing_list = missing.join("\n ");
Err(CliError::Other(format!(
"The bundled PostgreSQL binary is missing required system libraries:\n \
{}\n\n\
Install the missing libraries using your system package manager. For example:\n \
Arch Linux: sudo pacman -S <package>\n \
Ubuntu/Debian: sudo apt install <package>\n \
Fedora/RHEL: sudo dnf install <package>",
missing_list
)))
}

/// Install pgvector extension files into the PostgreSQL installation
fn install_pgvector(installation_dir: &PathBuf, pg_version: &str) -> Result<(), CliError> {
let pg_major = pg_version.split('.').next().unwrap_or("16");
Expand Down
Loading