Skip to content

Commit ecd038f

Browse files
committed
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
1 parent 93a19d1 commit ecd038f

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ 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
@@ -81,6 +84,7 @@ jobs:
8184
bash ${{ matrix.cli_script }}
8285
8386
- name: Run Python SDK Docker test
87+
if: matrix.python_script != ''
8488
run: |
8589
chmod +x ${{ matrix.python_script }}
8690
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: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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+
# Check if PG0_BINARY_PATH is set (local binary to test)
15+
VOLUME_ARGS=""
16+
BINARY_ENV=""
17+
if [ -n "${PG0_BINARY_PATH:-}" ]; then
18+
echo "Using local binary: $PG0_BINARY_PATH"
19+
VOLUME_ARGS="-v $PG0_BINARY_PATH:/tmp/pg0-binary:ro"
20+
BINARY_ENV="-e PG0_BINARY_URL=file:///tmp/pg0-binary"
21+
fi
22+
23+
docker run --rm --platform=linux/amd64 \
24+
$BINARY_ENV \
25+
-v "$INSTALL_SCRIPT:/tmp/install.sh:ro" \
26+
$VOLUME_ARGS \
27+
python:3.11-slim bash -c '
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 "=== Copying local install.sh ==="
47+
cp /tmp/install.sh /usr/local/bin/install.sh
48+
chmod 755 /usr/local/bin/install.sh
49+
50+
echo ""
51+
echo "=== Phase 1: Install pg0 and do initial extraction ==="
52+
su - pguser << EOF
53+
set -e
54+
export PG0_BINARY_URL="${PG0_BINARY_URL}"
55+
56+
echo "=== Installing pg0 ==="
57+
bash /usr/local/bin/install.sh
58+
export PATH="\$HOME/.local/bin:\$PATH"
59+
60+
echo ""
61+
echo "=== Starting PostgreSQL (initial extraction) ==="
62+
pg0 start
63+
sleep 3
64+
65+
echo ""
66+
echo "=== Stopping PostgreSQL ==="
67+
pg0 stop
68+
sleep 1
69+
70+
echo ""
71+
echo "=== Removing extracted installation to force re-extraction ==="
72+
rm -rf ~/.pg0/installation
73+
echo "Installation directory cleared."
74+
EOF
75+
76+
echo ""
77+
echo "=== Phase 2: Remove libxml2 to simulate missing library ==="
78+
apt-get remove -y libxml2 2>&1 | tail -3
79+
80+
echo ""
81+
echo "=== Phase 3: Verify pg0 detects missing libraries ==="
82+
su - pguser << EOF
83+
set -e
84+
export PATH="\$HOME/.local/bin:\$PATH"
85+
86+
echo "=== Starting pg0 (should fail with missing library error) ==="
87+
OUTPUT=\$(pg0 start 2>&1 || true)
88+
EXIT_CODE=\${PIPESTATUS[0]:-\$?}
89+
echo "\$OUTPUT"
90+
91+
echo ""
92+
echo "=== Checking error message ==="
93+
94+
if echo "\$OUTPUT" | grep -q "missing required system libraries"; then
95+
echo "PASS: Found 'missing required system libraries' message"
96+
else
97+
echo "FAIL: Missing expected error message about shared libraries"
98+
exit 1
99+
fi
100+
101+
if echo "\$OUTPUT" | grep -q "libxml2"; then
102+
echo "PASS: Found 'libxml2' in the missing library list"
103+
else
104+
echo "FAIL: Expected libxml2 to be listed as missing"
105+
exit 1
106+
fi
107+
108+
if echo "\$OUTPUT" | grep -q "Install the missing libraries"; then
109+
echo "PASS: Found install guidance message"
110+
else
111+
echo "FAIL: Missing install guidance"
112+
exit 1
113+
fi
114+
115+
echo ""
116+
echo "============================================="
117+
echo "ALL CHECKS PASSED - Missing libs detected"
118+
echo "============================================="
119+
EOF
120+
'
121+
122+
echo ""
123+
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: &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)