fix(tools): improve error diagnostics and actionable hints across install functions

- Add _diagnose_deb_failure() helper: extracts package metadata from failed .deb installs,
  detects PostgreSQL version conflicts (e.g., postgresql-16-vchord with PG17 active),
  lists unmet dependencies, and provides specific actionable hints
- Replace all 4 generic 'Both apt and dpkg installation failed' messages in
  fetch_and_deploy_{codeberg,gh,gl}_release and fetch_and_deploy_from_url with
  _diagnose_deb_failure() for targeted diagnostics
- install_packages_with_retry: on failure, check which packages are missing from
  configured repos and name them with a distribution-specific hint
- upgrade_packages_with_retry: add hint about held-back packages / apt-cache policy
- setup_postgresql: when PGDG repo is unavailable for trixie/forky/sid, show which
  distro PG version will be installed and warn that extension packages must match
- setup_deb822_repo: include GPG key URL and firewall hint in download failure message
- curl_download: add network/DNS hint to the failure message
- error_handler: add log-pattern analysis block after Node.js OOM detection that
  scans the last 60 log lines for 5 common failure patterns and emits msg_warn hints:
    * APT/dpkg dependency conflict (generic + PostgreSQL version mismatch)
    * APT GPG/signature verification failure (sqv, KEYEXPIRED, NO_PUBKEY)
    * Network/DNS failure (Could not resolve, Failed to fetch)
    * APT lock held by another process
    * Disk space exhaustion (ENOSPC)
This commit is contained in:
MickLesk
2026-05-24 21:03:14 +02:00
parent 947d032f96
commit 85aa701e31
2 changed files with 139 additions and 6 deletions

View File

@@ -358,6 +358,55 @@ error_handler() {
fi
fi
# ── Log-pattern analysis: detect common failure causes and emit actionable hints ──
if [[ -n "$active_log" && -s "$active_log" ]]; then
local _log_tail
_log_tail=$(tail -n 60 "$active_log" 2>/dev/null || true)
# 1. APT/dpkg dependency conflict
if echo "$_log_tail" | grep -qE "Depends:|depends on.*but.*not installed|broken packages|unmet dep|dependency problems"; then
# Check for PostgreSQL-specific version mismatch (most actionable)
local _pg_conflict
_pg_conflict=$(echo "$_log_tail" | grep -oE 'postgresql-[0-9]+ but.*installed' | head -1 || true)
if [[ -n "$_pg_conflict" ]]; then
if declare -f msg_warn >/dev/null 2>&1; then
msg_warn "PostgreSQL version conflict: ${_pg_conflict}"
msg_warn "Hint: A package requires a specific PostgreSQL version that is not installed. Your distribution may have installed a different PG version than expected."
fi
else
if declare -f msg_warn >/dev/null 2>&1; then
msg_warn "APT dependency conflict detected. A required package is not available or is the wrong version for this system."
msg_warn "Hint: Run 'apt-get install -f' inside the container or check that all required repositories are configured for your distribution."
fi
fi
# 2. APT/GPG signature verification failure
elif echo "$_log_tail" | grep -qE "sqv|KEYEXPIRED|NO_PUBKEY|key is not certified|signature verification failed|is not signed|Sub-process.*sqv"; then
if declare -f msg_warn >/dev/null 2>&1; then
msg_warn "APT repository signature error detected."
msg_warn "Hint: A repository GPG key may be missing, expired, or the keyring file is not yet present (/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc etc.)."
msg_warn "Hint: Install the 'postgresql-common' package first, or re-add the repository with its correct signing key."
fi
# 3. Network / DNS failure during apt-get or curl
elif echo "$_log_tail" | grep -qE "Could not resolve|Failed to fetch|Unable to connect|Name or service not known|Network is unreachable|curl.*resolve"; then
if declare -f msg_warn >/dev/null 2>&1; then
msg_warn "Network or DNS failure detected."
msg_warn "Hint: Check the container's network connectivity, DNS settings, and whether any firewall or ad-blocker is intercepting traffic."
fi
# 4. APT lock held by another process
elif echo "$_log_tail" | grep -qE "Could not get lock|dpkg frontend lock|waiting for it to exit|E: Unable to lock"; then
if declare -f msg_warn >/dev/null 2>&1; then
msg_warn "APT or dpkg lock conflict detected."
msg_warn "Hint: Another package manager process may be running. Try 'rm /var/lib/dpkg/lock-frontend && dpkg --configure -a' inside the container."
fi
# 5. Disk space exhaustion
elif echo "$_log_tail" | grep -qE "No space left on device|disk quota exceeded|ENOSPC"; then
if declare -f msg_warn >/dev/null 2>&1; then
msg_warn "Disk space exhausted during installation."
msg_warn "Hint: Increase the container's disk size (pct resize <ctid> rootfs +2G) or clean up space first."
fi
fi
fi
# Detect context: Container (INSTALL_LOG set + inside container /root) vs Host
if [[ -n "${INSTALL_LOG:-}" && -f "${INSTALL_LOG:-}" && -d /root ]]; then
# CONTAINER CONTEXT: Copy log and create flag file for host

View File

@@ -477,6 +477,21 @@ install_packages_with_retry() {
done
msg_error "Failed to install packages after $((max_retries + 1)) attempts: ${packages[*]}"
# Provide a quick diagnostic: check if package exists in any configured repo
local _os_codename
_os_codename=$(awk -F= '/^VERSION_CODENAME=/{gsub(/"/, "", $2); print $2}' /etc/os-release 2>/dev/null || echo "unknown")
local _unavailable=()
for _pkg in "${packages[@]}"; do
if ! apt-cache show "$_pkg" &>/dev/null; then
_unavailable+=("$_pkg")
fi
done
if [[ ${#_unavailable[@]} -gt 0 ]]; then
msg_error "Package(s) not found in any configured repository: ${_unavailable[*]}"
msg_error "Hint: These packages may not be available for '${_os_codename}'. Check repository configuration or package names."
else
msg_error "Hint: Package(s) exist in the repo but could not be installed — run 'apt-get install -f' inside the container or check for dependency conflicts."
fi
return 100
}
@@ -508,6 +523,7 @@ upgrade_packages_with_retry() {
done
msg_error "Failed to upgrade packages after $((max_retries + 1)) attempts: ${packages[*]}"
msg_error "Hint: The package may be held back, have conflicting dependencies, or the repository is unreachable. Check 'apt-cache policy ${packages[*]}' inside the container."
return 100
}
@@ -1468,6 +1484,7 @@ download_file() {
done
msg_error "Failed to download: $url"
msg_error "Hint: Check network connectivity or DNS resolution. The server may be unreachable or the URL may have changed."
return 250
}
@@ -1896,7 +1913,8 @@ setup_deb822_repo() {
local tmp_gpg
tmp_gpg=$(mktemp) || return 252
curl -fsSL "$gpg_url" -o "$tmp_gpg" || {
msg_error "Failed to download GPG key for ${name}"
msg_error "Failed to download GPG key for ${name} from: ${gpg_url}"
msg_error "Hint: Check network connectivity. If behind a proxy or firewall, ensure HTTPS access to $(echo "$gpg_url" | grep -oE 'https?://[^/]+') is allowed."
rm -f "$tmp_gpg"
return 7
}
@@ -2873,6 +2891,66 @@ function curl_download() {
# fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" "tag" "v0.11.3" "/opt/autocaliweb"
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# _diagnose_deb_failure()
#
# - Called when both apt and dpkg fail to install a .deb package
# - Extracts package metadata and detects common failure patterns
# - Outputs enhanced error messages with actionable hints:
# * PostgreSQL version conflicts (e.g., postgresql-16-foo with pg17 active)
# * Missing declared dependencies
# * Generic fallback hint pointing to the log
#
# Usage: _diagnose_deb_failure "/path/to/file.deb"
# Returns: always 0 (diagnostic only — caller must return the error code)
# ------------------------------------------------------------------------------
_diagnose_deb_failure() {
local deb_path="$1"
local filename="${deb_path##*/}"
local pkg_name pkg_deps pkg_version
pkg_name=$(dpkg-deb -f "$deb_path" Package 2>/dev/null || echo "${filename%%_*}")
pkg_version=$(dpkg-deb -f "$deb_path" Version 2>/dev/null || true)
pkg_deps=$(dpkg-deb -f "$deb_path" Depends 2>/dev/null || true)
msg_error "Failed to install '${pkg_name}${pkg_version:+ (${pkg_version})}' — both apt and dpkg reported errors"
# Detect PostgreSQL version conflict (e.g., postgresql-16-vchord while pg17 is active)
local pg_ver_needed pg_ver_installed
pg_ver_needed=$(echo "$filename" | grep -oP '(?<=postgresql-)[0-9]+(?=-)' | head -1 || true)
if [[ -n "$pg_ver_needed" ]]; then
pg_ver_installed=$(psql -V 2>/dev/null | awk '{print $3}' | cut -d. -f1 || true)
if [[ -n "$pg_ver_installed" && "$pg_ver_needed" != "$pg_ver_installed" ]]; then
msg_error "Version conflict: '${pkg_name}' is built for PostgreSQL ${pg_ver_needed}, but PostgreSQL ${pg_ver_installed} is installed on this system."
msg_error "Hint: Your distribution installed a different PostgreSQL version than expected. The script may need updating to use postgresql-${pg_ver_installed}-* packages."
return 0
fi
fi
# Show which declared dependencies are not satisfied
if [[ -n "$pkg_deps" ]]; then
local missing_deps=()
while IFS=',' read -ra dep_list; do
for dep_entry in "${dep_list[@]}"; do
local dep_pkg
dep_pkg=$(echo "$dep_entry" | awk '{print $1}' | tr -d ' ')
[[ -z "$dep_pkg" || "$dep_pkg" == "("* ]] && continue
if ! dpkg-query -W -f='${Status}' "$dep_pkg" 2>/dev/null | grep -q "install ok installed"; then
missing_deps+=("$dep_pkg")
fi
done
done <<<"$pkg_deps"
if [[ ${#missing_deps[@]} -gt 0 ]]; then
msg_error "Unmet dependencies: ${missing_deps[*]}"
msg_error "Hint: Run 'apt-get install -f' inside the container to attempt automatic dependency resolution."
else
msg_error "Hint: Declared dependencies appear present but installation still failed. Check the log above for the exact error."
fi
else
msg_error "Hint: Check the installation log above for the exact dependency or configuration error."
fi
}
function fetch_and_deploy_codeberg_release() {
local app="$1"
local repo="$2"
@@ -3106,7 +3184,7 @@ function fetch_and_deploy_codeberg_release() {
chmod 644 "$tmpdir/$filename"
$STD apt install -y "$tmpdir/$filename" || {
$STD dpkg -i "$tmpdir/$filename" || {
msg_error "Both apt and dpkg installation failed"
_diagnose_deb_failure "$tmpdir/$filename"
rm -rf "$tmpdir"
return 100
}
@@ -3651,7 +3729,7 @@ function fetch_and_deploy_gh_release() {
[[ "${DPKG_FORCE_CONFNEW:-}" == "1" ]] && dpkg_opts="-o Dpkg::Options::=--force-confnew"
DEBIAN_FRONTEND=noninteractive SYSTEMD_OFFLINE=1 $STD apt install -y $dpkg_opts "$tmpdir/$filename" || {
SYSTEMD_OFFLINE=1 $STD dpkg -i "$tmpdir/$filename" || {
msg_error "Both apt and dpkg installation failed"
_diagnose_deb_failure "$tmpdir/$filename"
rm -rf "$tmpdir"
return 100
}
@@ -7040,7 +7118,13 @@ setup_postgresql() {
SUITE="trixie-pgdg"
else
msg_warn "PGDG repo not available for ${DISTRO_CODENAME}, falling back to distro packages"
local _distro_pg_ver
_distro_pg_ver=$(apt-cache show postgresql 2>/dev/null | awk '/^Version:/{print $2; exit}' | grep -oE '^[0-9]+' || true)
msg_warn "PGDG repository not available for ${DISTRO_CODENAME} — falling back to distro-provided PostgreSQL packages"
if [[ -n "$_distro_pg_ver" ]]; then
msg_warn "Distro will install PostgreSQL ${_distro_pg_ver} (not the requested ${PG_VERSION})."
msg_warn "Any PostgreSQL extension packages (e.g. vchord, pgvector) must be built for PostgreSQL ${_distro_pg_ver} on ${DISTRO_CODENAME}."
fi
USE_PGDG_REPO=false setup_postgresql
return $?
fi
@@ -8558,7 +8642,7 @@ function fetch_and_deploy_from_url() {
chmod 644 "$tmpdir/$filename"
$STD apt install -y "$tmpdir/$filename" || {
$STD dpkg -i "$tmpdir/$filename" || {
msg_error "Both apt and dpkg installation failed"
_diagnose_deb_failure "$tmpdir/$filename"
rm -rf "$tmpdir"
return 100
}
@@ -9226,7 +9310,7 @@ function fetch_and_deploy_gl_release() {
[[ "${DPKG_FORCE_CONFNEW:-}" == "1" ]] && dpkg_opts="-o Dpkg::Options::=--force-confnew"
DEBIAN_FRONTEND=noninteractive SYSTEMD_OFFLINE=1 $STD apt install -y $dpkg_opts "$tmpdir/$filename" || {
SYSTEMD_OFFLINE=1 $STD dpkg -i "$tmpdir/$filename" || {
msg_error "Both apt and dpkg installation failed"
_diagnose_deb_failure "$tmpdir/$filename"
rm -rf "$tmpdir"
return 1
}