Skip to content

Commit 7dbbc23

Browse files
authored
fix: detect missing system shared libraries on Linux (#8)
* fix: detect missing system shared libraries on Linux On some Linux distros (e.g. Arch Linux), system shared libraries like libxml2 may be missing. The OS can't execute the postgres binary, causing initdb to report a misleading "postgres not found" error. After extraction, run ldd on the postgres binary (Linux only) and report any missing .so dependencies with actionable install guidance. If ldd is unavailable, the check is silently skipped. Adds a Docker integration test that removes libxml2 and verifies the new error message is surfaced correctly. Ref: vectorize-io/hindsight#919 * fix: build Linux binary for missing-libs test and fix heredoc issues - Add Rust build step in CI for the missing-libs test so it uses the PR binary (with shared lib detection) instead of the released one - Rewrite test script to use a mounted script file instead of nested heredocs to avoid bash quoting issues - Require PG0_BINARY_PATH to be set (test needs the PR binary) * fix: use fully qualified Path type in Linux-only function The check_shared_libraries function uses &Path but it's gated with #[cfg(target_os = "linux")]. Use std::path::Path inline to avoid unused import warnings on non-Linux platforms.
1 parent 93a19d1 commit 7dbbc23

4 files changed

Lines changed: 180 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,30 @@ jobs:
7171
- platform: alpine-amd64
7272
cli_script: docker-tests/test_alpine_amd64.sh
7373
python_script: docker-tests/python/test_alpine_amd64.sh
74+
- platform: missing-libs-debian-amd64
75+
cli_script: docker-tests/test_missing_libs.sh
76+
python_script: ""
7477

7578
steps:
7679
- uses: actions/checkout@v4
7780

81+
- name: Install Rust
82+
if: matrix.platform == 'missing-libs-debian-amd64'
83+
uses: dtolnay/rust-toolchain@stable
84+
85+
- name: Build Linux binary
86+
if: matrix.platform == 'missing-libs-debian-amd64'
87+
run: |
88+
cargo build --release
89+
echo "PG0_BINARY_PATH=$(pwd)/target/release/pg0" >> $GITHUB_ENV
90+
7891
- name: Run CLI Docker test
7992
run: |
8093
chmod +x ${{ matrix.cli_script }}
8194
bash ${{ matrix.cli_script }}
8295
8396
- name: Run Python SDK Docker test
97+
if: matrix.python_script != ''
8498
run: |
8599
chmod +x ${{ matrix.python_script }}
86100
bash ${{ matrix.python_script }}

docker-tests/run_all_tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ run_test "Debian AMD64" "$DIR/test_debian_amd64.sh"
5050
run_test "Debian ARM64" "$DIR/test_debian_arm64.sh"
5151
run_test "Alpine AMD64" "$DIR/test_alpine_amd64.sh"
5252
run_test "Alpine ARM64" "$DIR/test_alpine_arm64.sh"
53+
run_test "Missing Libs Detection (Debian AMD64)" "$DIR/test_missing_libs.sh"
5354

5455
# Print summary
5556
echo ""

docker-tests/test_missing_libs.sh

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "============================================="
5+
echo "Testing pg0 missing shared library detection"
6+
echo "Image: python:3.11-slim"
7+
echo "Platform: linux/amd64"
8+
echo "============================================="
9+
10+
# Get the script directory to find install.sh
11+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
12+
INSTALL_SCRIPT="$SCRIPT_DIR/../install.sh"
13+
14+
# PG0_BINARY_PATH is required - this test must use a binary built from source
15+
# (with the shared library detection code), not the released binary
16+
if [ -z "${PG0_BINARY_PATH:-}" ]; then
17+
echo "ERROR: PG0_BINARY_PATH must be set to a Linux binary built from this branch"
18+
exit 1
19+
fi
20+
21+
echo "Using local binary: $PG0_BINARY_PATH"
22+
23+
# Create a temporary test script to run inside the container
24+
# This avoids nested heredoc quoting issues
25+
TEMP_SCRIPT=$(mktemp)
26+
cat > "$TEMP_SCRIPT" << 'INNERSCRIPT'
27+
#!/bin/bash
28+
set -e
29+
30+
echo "=== System Info ==="
31+
uname -m
32+
cat /etc/os-release | grep PRETTY_NAME
33+
34+
echo ""
35+
echo "=== Installing dependencies ==="
36+
apt-get update -qq
37+
apt-get install -y curl libxml2 libssl3 libgssapi-krb5-2 sudo procps 2>&1 | grep -v "^Get:" || true
38+
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
39+
40+
echo ""
41+
echo "=== Creating non-root user ==="
42+
useradd -m -s /bin/bash pguser
43+
echo "pguser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
44+
45+
echo ""
46+
echo "=== Installing pg0 from local binary ==="
47+
cp /tmp/pg0-binary /usr/local/bin/pg0
48+
chmod 755 /usr/local/bin/pg0
49+
50+
echo ""
51+
echo "=== Phase 1: Initial extraction with all deps present ==="
52+
su -s /bin/bash - pguser -c '
53+
set -e
54+
export PATH="/usr/local/bin:$PATH"
55+
56+
echo "=== Starting PostgreSQL (initial extraction) ==="
57+
pg0 start
58+
sleep 3
59+
60+
echo "=== Stopping PostgreSQL ==="
61+
pg0 stop
62+
sleep 1
63+
64+
echo "=== Removing extracted installation to force re-extraction ==="
65+
rm -rf ~/.pg0/installation
66+
echo "Installation directory cleared."
67+
'
68+
69+
echo ""
70+
echo "=== Phase 2: Remove libxml2 to simulate missing library ==="
71+
apt-get remove -y libxml2 2>&1 | tail -3
72+
73+
echo ""
74+
echo "=== Phase 3: Verify pg0 detects missing libraries ==="
75+
su -s /bin/bash - pguser -c '
76+
set -e
77+
export PATH="/usr/local/bin:$PATH"
78+
79+
echo "=== Starting pg0 (should fail with missing library error) ==="
80+
OUTPUT=$(pg0 start 2>&1 || true)
81+
echo "$OUTPUT"
82+
83+
echo ""
84+
echo "=== Checking error message ==="
85+
86+
if echo "$OUTPUT" | grep -q "missing required system libraries"; then
87+
echo "PASS: Found missing required system libraries message"
88+
else
89+
echo "FAIL: Missing expected error message about shared libraries"
90+
exit 1
91+
fi
92+
93+
if echo "$OUTPUT" | grep -qi "libxml2"; then
94+
echo "PASS: Found libxml2 in the missing library list"
95+
else
96+
echo "FAIL: Expected libxml2 to be listed as missing"
97+
exit 1
98+
fi
99+
100+
if echo "$OUTPUT" | grep -q "Install the missing libraries"; then
101+
echo "PASS: Found install guidance message"
102+
else
103+
echo "FAIL: Missing install guidance"
104+
exit 1
105+
fi
106+
107+
echo ""
108+
echo "============================================="
109+
echo "ALL CHECKS PASSED - Missing libs detected"
110+
echo "============================================="
111+
'
112+
INNERSCRIPT
113+
114+
docker run --rm --platform=linux/amd64 \
115+
-v "$PG0_BINARY_PATH:/tmp/pg0-binary:ro" \
116+
-v "$TEMP_SCRIPT:/tmp/test_script.sh:ro" \
117+
python:3.11-slim bash /tmp/test_script.sh
118+
119+
rm -f "$TEMP_SCRIPT"
120+
121+
echo ""
122+
echo "Test completed successfully!"

src/main.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,10 +452,53 @@ fn extract_bundled_postgresql(installation_dir: &PathBuf, pg_version: &str) -> R
452452
}
453453
}
454454

455+
// Check for missing shared libraries on Linux
456+
#[cfg(target_os = "linux")]
457+
check_shared_libraries(&bin_dir)?;
458+
455459
println!("PostgreSQL {} extracted successfully.", pg_version);
456460
Ok(version_dir)
457461
}
458462

463+
/// Check that the postgres binary can find all required shared libraries.
464+
/// Only called on Linux. If ldd is unavailable, silently skips the check.
465+
#[cfg(target_os = "linux")]
466+
fn check_shared_libraries(bin_dir: &std::path::Path) -> Result<(), CliError> {
467+
let postgres_path = bin_dir.join("postgres");
468+
let output = match std::process::Command::new("ldd")
469+
.arg(&postgres_path)
470+
.output()
471+
{
472+
Ok(output) => output,
473+
Err(e) => {
474+
tracing::debug!("Could not run ldd to check shared libraries: {}", e);
475+
return Ok(());
476+
}
477+
};
478+
479+
let stdout = String::from_utf8_lossy(&output.stdout);
480+
let missing: Vec<&str> = stdout
481+
.lines()
482+
.filter(|line| line.contains("not found"))
483+
.map(|line| line.trim())
484+
.collect();
485+
486+
if missing.is_empty() {
487+
return Ok(());
488+
}
489+
490+
let missing_list = missing.join("\n ");
491+
Err(CliError::Other(format!(
492+
"The bundled PostgreSQL binary is missing required system libraries:\n \
493+
{}\n\n\
494+
Install the missing libraries using your system package manager. For example:\n \
495+
Arch Linux: sudo pacman -S <package>\n \
496+
Ubuntu/Debian: sudo apt install <package>\n \
497+
Fedora/RHEL: sudo dnf install <package>",
498+
missing_list
499+
)))
500+
}
501+
459502
/// Install pgvector extension files into the PostgreSQL installation
460503
fn install_pgvector(installation_dir: &PathBuf, pg_version: &str) -> Result<(), CliError> {
461504
let pg_major = pg_version.split('.').next().unwrap_or("16");

0 commit comments

Comments
 (0)