Compare commits

..

4 Commits

Author SHA1 Message Date
MickLesk fb7ceab989 fix(tools): only use corepack where scripts actually need it
Remove bulk corepack prefix from global pnpm/yarn installs and limit corepack enable in setup_nodejs to explicit corepack modules or Node 25+.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 22:05:32 +02:00
MickLesk 1363e5eab8 fix(ct): ensure corepack is enabled before yarn/pnpm updates
Run setup_nodejs with corepack in update scripts that use bare package manager commands after corepack prepare.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 22:00:32 +02:00
MickLesk 16ea8eb8f6 refactor(install): use setup_nodejs corepack via NODE_MODULE
Remove redundant corepack enable calls from install scripts and rely on centralized setup_nodejs handling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 21:56:29 +02:00
MickLesk 1f01f32853 refactor(tools): centralize Node.js corepack and npm handling in setup_nodejs
Move corepack install/enable into setup_nodejs and update CT scripts to use NODE_MODULE instead of manual corepack calls.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 21:55:20 +02:00
2 changed files with 97 additions and 232 deletions
-12
View File
@@ -496,18 +496,6 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- [arm64] port scripts titled between papra and qbittorrent to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15258](https://github.com/community-scripts/ProxmoxVE/pull/15258))
- [arm64] Port scripts between mediamtx-nocodb to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15254](https://github.com/community-scripts/ProxmoxVE/pull/15254))
- #### 🔧 Refactor
- tools.func: centralize Node.js corepack and npm handling in `setup_nodejs()` [@MickLesk](https://github.com/MickLesk) ([#15268](https://github.com/community-scripts/ProxmoxVE/pull/15268))
### 💾 Core
- #### 🐞 Bug Fixes
- tools.func: APT install and deb822 repo reliability [@MickLesk](https://github.com/MickLesk) ([#15272](https://github.com/community-scripts/ProxmoxVE/pull/15272))
- tools.func: prevent MySQL data loss and fix repo version matching [@MickLesk](https://github.com/MickLesk) ([#15271](https://github.com/community-scripts/ProxmoxVE/pull/15271))
- tools.func: runtime hardening for API helpers and Docker/MeiliSearch [@MickLesk](https://github.com/MickLesk) ([#15273](https://github.com/community-scripts/ProxmoxVE/pull/15273))
## 2026-06-20
### 🆕 New Scripts
+97 -220
View File
@@ -24,7 +24,6 @@
# cleanup_tool_keyrings() - Remove keyrings from all 3 locations
# stop_all_services() - Stop services by pattern (e.g. "php*-fpm")
# verify_tool_version() - Validate installed version matches expected
# version_matches_spec() - Compare installed semver against spec (8.0 matches 8.0.40)
# cleanup_legacy_install() - Remove nvm, rbenv, rustup, etc.
# prepare_repository_setup() - Cleanup repos + keyrings + validate APT
# install_packages_with_retry() - Install with 3 retries and APT refresh
@@ -283,7 +282,7 @@ get_cached_version() {
cat "/var/cache/app-versions/${app}_version.txt"
return 0
fi
return 1
return 0
}
# ------------------------------------------------------------------------------
@@ -345,37 +344,6 @@ verify_tool_version() {
return 0
}
# ------------------------------------------------------------------------------
# Compare installed semver against a version spec at the spec's precision.
# Returns: 0 if match (e.g. spec 8.0 matches installed 8.0.40), 1 otherwise
# Usage: version_matches_spec "8.0.40" "8.0"
# ------------------------------------------------------------------------------
version_matches_spec() {
local installed="$1"
local spec="$2"
local spec_depth prefix i
local -a spec_parts installed_parts
[[ -n "$installed" && -n "$spec" ]] || return 1
IFS='.' read -ra spec_parts <<<"$spec"
spec_depth=${#spec_parts[@]}
((spec_depth > 0)) || return 1
if ((spec_depth == 1)); then
[[ "${installed%%.*}" == "$spec" ]] && return 0
return 1
fi
IFS='.' read -ra installed_parts <<<"$installed"
prefix=""
for ((i = 0; i < spec_depth && i < ${#installed_parts[@]}; i++)); do
[[ -n "$prefix" ]] && prefix+="."
prefix+="${installed_parts[i]}"
done
[[ "$prefix" == "$spec" ]]
}
# ------------------------------------------------------------------------------
# Clean up legacy installation methods (nvm, rbenv, rustup, etc.)
# Usage: cleanup_legacy_install "nodejs" -> removes nvm
@@ -494,10 +462,10 @@ install_packages_with_retry() {
fi
fi
done
# If some packages installed, consider partial success
if [[ ${#failed[@]} -lt ${#packages[@]} ]]; then
if [[ ${#failed[@]} -gt 0 ]]; then
msg_error "Partial install — failed packages: ${failed[*]}"
return 100
msg_warn "Partially installed. Failed packages: ${failed[*]}"
fi
return 0
fi
@@ -652,15 +620,13 @@ remove_old_tool_version() {
mysql)
stop_all_services "mysql"
$STD apt purge -y 'mysql*' >/dev/null 2>&1 || true
# Keep data directory for safety (remove manually if needed)
# rm -rf /var/lib/mysql 2>/dev/null || true
rm -rf /var/lib/mysql 2>/dev/null || true
cleanup_tool_keyrings "mysql"
;;
mongodb)
stop_all_services "mongod"
$STD apt purge -y 'mongodb*' >/dev/null 2>&1 || true
# Keep data directory for safety (remove manually if needed)
# rm -rf /var/lib/mongodb 2>/dev/null || true
rm -rf /var/lib/mongodb 2>/dev/null || true
cleanup_tool_keyrings "mongodb"
;;
node | nodejs)
@@ -705,8 +671,7 @@ remove_old_tool_version() {
clickhouse)
stop_all_services "clickhouse-server"
$STD apt purge -y 'clickhouse*' >/dev/null 2>&1 || true
# Keep data directory for safety (remove manually if needed)
# rm -rf /var/lib/clickhouse 2>/dev/null || true
rm -rf /var/lib/clickhouse 2>/dev/null || true
cleanup_tool_keyrings "clickhouse"
;;
esac
@@ -730,8 +695,8 @@ should_update_tool() {
# Get currently installed version
current_version=$(is_tool_installed "$tool_name" 2>/dev/null) || return 0 # Not installed = needs install
# If versions match at the requested precision, no update needed
if version_matches_spec "$current_version" "$target_version"; then
# If versions are identical, no update needed
if [[ "$current_version" == "$target_version" ]]; then
return 1 # No update needed
fi
@@ -926,49 +891,6 @@ Suites: $distro_codename
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /usr/share/keyrings/deb.sury.org-php.gpg
EOF
return 0
;;
mysql)
if [[ -z "$gpg_key_url" ]]; then
msg_error "MySQL repository requires gpg_key_url"
return 65
fi
cleanup_old_repo_files "mysql"
if ! download_gpg_key "$gpg_key_url" "/etc/apt/keyrings/mysql.gpg" "dearmor"; then
msg_error "Failed to import MySQL GPG key"
return 7
fi
local distro_codename suite component
distro_codename=$(get_os_info codename)
if [[ "$distro_id" == "debian" ]]; then
case "$distro_codename" in
trixie | forky | sid) suite="bookworm" ;;
bookworm | bullseye) suite="$distro_codename" ;;
*) suite="bookworm" ;;
esac
else
suite=$(get_fallback_suite "$distro_id" "$distro_codename" "$repo_url")
fi
case "$version" in
8.4 | 8.4.*) component="mysql-8.4-lts" ;;
8.0 | 8.0.*) component="mysql-8.0" ;;
*) component="mysql-${version}" ;;
esac
cat <<EOF >/etc/apt/sources.list.d/mysql.sources
Types: deb
URIs: ${repo_url}/
Suites: ${suite}
Components: ${component}
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/mysql.gpg
EOF
return 0
;;
@@ -1200,34 +1122,11 @@ get_system_arch() {
# ------------------------------------------------------------------------------
# Create temporary directory with automatic cleanup
# Appends to a shared list so existing EXIT traps are preserved.
# ------------------------------------------------------------------------------
_tools_temp_dirs=()
_tools_cleanup_temp_dirs() {
local d
for d in "${_tools_temp_dirs[@]}"; do
rm -rf "$d" 2>/dev/null || true
done
}
_tools_register_temp_cleanup() {
[[ "${_TOOLS_TEMP_TRAP_SET:-}" == "1" ]] && return 0
_TOOLS_TEMP_TRAP_SET=1
local existing
existing=$(trap -p EXIT 2>/dev/null | sed -n "s/^trap -- '\\(.*\\)' EXIT/\1/p" || true)
if [[ -n "$existing" && "$existing" != "_tools_cleanup_temp_dirs" ]]; then
trap "_tools_cleanup_temp_dirs; ${existing}" EXIT ERR INT TERM
else
trap _tools_cleanup_temp_dirs EXIT ERR INT TERM
fi
}
create_temp_dir() {
local tmp_dir
tmp_dir=$(mktemp -d) || return 1
_tools_temp_dirs+=("$tmp_dir")
_tools_register_temp_cleanup
local tmp_dir=$(mktemp -d)
# Set trap to cleanup on EXIT, ERR, INT, TERM
trap "rm -rf '$tmp_dir'" EXIT ERR INT TERM
echo "$tmp_dir"
}
@@ -2150,11 +2049,9 @@ setup_deb822_repo() {
[[ -n "$enabled" ]] && echo "Enabled: $enabled"
} >/etc/apt/sources.list.d/${name}.sources
if ! $STD apt update; then
msg_error "apt update failed after adding repository: ${name}"
msg_error "Hint: Verify suite '${suite}' and URI '${repo_url}' are valid for this distribution."
return 100
fi
$STD apt update || {
msg_warn "apt update failed after adding repository: ${name}"
}
}
# ------------------------------------------------------------------------------
@@ -2585,8 +2482,6 @@ check_for_gh_tag() {
# - Does not modify anything, only checks version state
# - Does not support pre-releases
# ------------------------------------------------------------------------------
TOOLS_GH_REL_JSON=""
check_for_gh_release() {
local app="$1"
local source="$2"
@@ -2606,10 +2501,6 @@ check_for_gh_release() {
ensure_dependencies jq
local gh_check_json
gh_check_json=$(mktemp /tmp/tools-gh-check-XXXXXX.json) || return 7
trap 'rm -f "$gh_check_json"' RETURN
# Build auth header if token is available
local header_args=()
[[ -n "${GITHUB_TOKEN:-}" ]] && header_args=(-H "Authorization: Bearer $GITHUB_TOKEN")
@@ -2620,14 +2511,14 @@ check_for_gh_release() {
# For pinned versions, query the specific release tag directly
if [[ -n "$pinned_version_in" ]]; then
local pinned_version_encoded="${pinned_version_in//\//%2F}"
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o "$gh_check_json" \
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gh_check.json \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
"${header_args[@]}" \
"https://api.github.com/repos/${source}/releases/tags/${pinned_version_encoded}" 2>/dev/null) || true
if [[ "$http_code" == "200" ]] && [[ -s "$gh_check_json" ]]; then
releases_json="[$(<"$gh_check_json")]"
if [[ "$http_code" == "200" ]] && [[ -s /tmp/gh_check.json ]]; then
releases_json="[$(</tmp/gh_check.json)]"
elif [[ "$http_code" == "401" ]]; then
msg_error "GitHub API authentication failed (HTTP 401)."
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
@@ -2635,27 +2526,27 @@ check_for_gh_release() {
else
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
fi
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
return 22
elif [[ "$http_code" == "403" ]]; then
msg_error "GitHub API rate limit exceeded (HTTP 403)."
msg_error "To increase the limit, export a GitHub token before running the script:"
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
return 22
fi
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
fi
if [[ -z "$pinned_version_in" ]]; then
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o "$gh_check_json" \
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gh_check.json \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
"${header_args[@]}" \
"https://api.github.com/repos/${source}/releases/latest" 2>/dev/null) || true
if [[ "$http_code" == "200" ]] && [[ -s "$gh_check_json" ]]; then
releases_json="[$(<"$gh_check_json")]"
if [[ "$http_code" == "200" ]] && [[ -s /tmp/gh_check.json ]]; then
releases_json="[$(</tmp/gh_check.json)]"
elif [[ "$http_code" == "401" ]]; then
msg_error "GitHub API authentication failed (HTTP 401)."
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
@@ -2663,28 +2554,28 @@ check_for_gh_release() {
else
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
fi
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
return 22
elif [[ "$http_code" == "403" ]]; then
msg_error "GitHub API rate limit exceeded (HTTP 403)."
msg_error "To increase the limit, export a GitHub token before running the script:"
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
return 22
fi
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
fi
# If no releases yet (pinned version OR /latest failed), fetch up to 100
if [[ -z "$releases_json" ]]; then
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o "$gh_check_json" \
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gh_check.json \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
"${header_args[@]}" \
"https://api.github.com/repos/${source}/releases?per_page=100" 2>/dev/null) || true
if [[ "$http_code" == "200" ]] && [[ -s "$gh_check_json" ]]; then
releases_json=$(<"$gh_check_json")
if [[ "$http_code" == "200" ]] && [[ -s /tmp/gh_check.json ]]; then
releases_json=$(</tmp/gh_check.json)
elif [[ "$http_code" == "401" ]]; then
msg_error "GitHub API authentication failed (HTTP 401)."
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
@@ -2692,25 +2583,25 @@ check_for_gh_release() {
else
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
fi
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
return 22
elif [[ "$http_code" == "403" ]]; then
msg_error "GitHub API rate limit exceeded (HTTP 403)."
msg_error "To increase the limit, export a GitHub token before running the script:"
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
return 22
elif [[ "$http_code" == "000" || -z "$http_code" ]]; then
msg_error "GitHub API connection failed (no response)."
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
return 7
else
msg_error "Unable to fetch releases for ${app} (HTTP ${http_code})"
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
return 22
fi
rm -f "$gh_check_json"
rm -f /tmp/gh_check.json
fi
mapfile -t raw_tags < <(jq -r '.[] | select(.draft==false and .prerelease==false) | .tag_name' <<<"$releases_json")
@@ -3256,14 +3147,10 @@ fetch_and_deploy_codeberg_release() {
return 6
fi
local codeberg_rel_json
codeberg_rel_json=$(mktemp /tmp/tools-codeberg-rel-XXXXXX.json) || return 7
trap 'rm -f "$codeberg_rel_json"' RETURN
local attempt=0 success=false resp http_code
while ((attempt < ${#api_timeouts[@]})); do
resp=$(curl --connect-timeout 10 --max-time "${api_timeouts[$attempt]}" -fsSL -w "%{http_code}" -o "$codeberg_rel_json" "$api_url") && success=true && break
resp=$(curl --connect-timeout 10 --max-time "${api_timeouts[$attempt]}" -fsSL -w "%{http_code}" -o /tmp/codeberg_rel.json "$api_url") && success=true && break
attempt=$((attempt + 1))
if ((attempt < ${#api_timeouts[@]})); then
msg_warn "API request timed out after ${api_timeouts[$((attempt - 1))]}s, retrying... (attempt $((attempt + 1))/${#api_timeouts[@]})"
@@ -3282,7 +3169,7 @@ fetch_and_deploy_codeberg_release() {
}
local json tag_name
json=$(<"$codeberg_rel_json")
json=$(</tmp/codeberg_rel.json)
# For "latest", the API returns an array - take the first (most recent) release
if [[ "$version" == "latest" ]]; then
@@ -3740,13 +3627,6 @@ fetch_and_deploy_gh_release() {
ensure_dependencies jq
if [[ -n "${TOOLS_GH_REL_JSON:-}" && -f "$TOOLS_GH_REL_JSON" ]]; then
rm -f "$TOOLS_GH_REL_JSON"
fi
local gh_rel_json
gh_rel_json=$(mktemp /tmp/tools-gh-rel-XXXXXX.json) || return 7
TOOLS_GH_REL_JSON="$gh_rel_json"
local api_url="https://api.github.com/repos/$repo/releases"
[[ "$version" != "latest" ]] && api_url="$api_url/tags/$version" || api_url="$api_url/latest"
local header=()
@@ -3763,7 +3643,7 @@ fetch_and_deploy_gh_release() {
local max_retries=${#api_timeouts[@]} retry_delay=2 attempt=1 success=false http_code
while ((attempt <= max_retries)); do
http_code=$(curl --connect-timeout 10 --max-time "${api_timeouts[$((attempt - 1))]:-240}" -sSL -w "%{http_code}" -o "$gh_rel_json" "${header[@]}" "$api_url" 2>/dev/null) || true
http_code=$(curl --connect-timeout 10 --max-time "${api_timeouts[$((attempt - 1))]:-240}" -sSL -w "%{http_code}" -o /tmp/gh_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
if [[ "$http_code" == "200" ]]; then
success=true
break
@@ -3810,7 +3690,7 @@ fetch_and_deploy_gh_release() {
fi
local json tag_name
json=$(<"$gh_rel_json")
json=$(</tmp/gh_rel.json)
tag_name=$(echo "$json" | jq -r '.tag_name // .name // empty')
# Only strip leading 'v' when followed by a digit (e.g. v1.2.3), not words like "version/..."
[[ "$tag_name" =~ ^v[0-9] ]] && version="${tag_name:1}" || version="$tag_name"
@@ -4415,12 +4295,7 @@ setup_composer() {
# - Updates Docker Engine if newer version available
# - Interactive container update with multi-select
# - Portainer installation and update support
# - Set DOCKER_NONINTERACTIVE=1 to skip interactive prompts (CI/unattended)
# ------------------------------------------------------------------------------
_docker_is_noninteractive() {
[[ "${DOCKER_NONINTERACTIVE:-}" == "1" || "${DOCKER_NONINTERACTIVE:-}" == "true" || "${DOCKER_NONINTERACTIVE:-}" == "TRUE" ]] || [[ ! -t 0 ]]
}
setup_docker() {
local docker_installed=false
local portainer_installed=false
@@ -4560,25 +4435,21 @@ EOF
PORTAINER_LATEST=$(curl -fsSL https://registry.hub.docker.com/v2/repositories/portainer/portainer-ce/tags?page_size=100 | grep -oP '"name":"\K[0-9]+\.[0-9]+\.[0-9]+"' | head -1 | tr -d '"')
if [ "$PORTAINER_CURRENT" != "$PORTAINER_LATEST" ]; then
if _docker_is_noninteractive; then
msg_info "Skipping Portainer update prompt (non-interactive)"
else
read -r -p "${TAB3}Update Portainer $PORTAINER_CURRENT$PORTAINER_LATEST? <y/N> " prompt
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
msg_info "Updating Portainer"
docker stop portainer
docker rm portainer
docker pull portainer/portainer-ce:latest
docker run -d \
-p 9000:9000 \
-p 9443:9443 \
--name=portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
msg_ok "Updated Portainer to $PORTAINER_LATEST"
fi
read -r -p "${TAB3}Update Portainer $PORTAINER_CURRENT$PORTAINER_LATEST? <y/N> " prompt
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
msg_info "Updating Portainer"
docker stop portainer
docker rm portainer
docker pull portainer/portainer-ce:latest
docker run -d \
-p 9000:9000 \
-p 9443:9443 \
--name=portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
msg_ok "Updated Portainer to $PORTAINER_LATEST"
fi
else
msg_ok "Portainer is up-to-date ($PORTAINER_CURRENT)"
@@ -4601,7 +4472,7 @@ EOF
fi
# Interactive Container Update Check
if [[ "${DOCKER_SKIP_UPDATES:-}" != "true" ]] && [ "$docker_installed" = true ] && ! _docker_is_noninteractive; then
if [[ "${DOCKER_SKIP_UPDATES:-}" != "true" ]] && [ "$docker_installed" = true ]; then
msg_info "Checking for container updates"
# Get list of running containers with update status
@@ -5375,15 +5246,15 @@ setup_hwaccel() {
# ══════════════════════════════════════════════════════════════════════════════
# Resolve the IGC tag that the latest compute-runtime was built against.
# Must be called AFTER a fetch_and_deploy_gh_release for intel/compute-runtime
# so that TOOLS_GH_REL_JSON contains the compute-runtime release metadata.
# so that /tmp/gh_rel.json contains the compute-runtime release metadata.
# Sets the variable named by $1 (default: igc_tag) to the discovered tag.
# ══════════════════════════════════════════════════════════════════════════════
_resolve_igc_tag() {
local -n _out_ref="${1:-igc_tag}"
_out_ref="latest"
if [[ -n "${TOOLS_GH_REL_JSON:-}" && -f "$TOOLS_GH_REL_JSON" ]]; then
if [[ -f /tmp/gh_rel.json ]]; then
local _body _parsed
_body=$(jq -r '.body // empty' "$TOOLS_GH_REL_JSON" 2>/dev/null) || return 0
_body=$(jq -r '.body // empty' /tmp/gh_rel.json 2>/dev/null) || return 0
_parsed=$(grep -oP 'intel-graphics-compiler/releases/tag/\K[^\s\)]+' <<<"$_body" | head -1)
[[ -n "$_parsed" ]] && _out_ref="$_parsed"
fi
@@ -5417,7 +5288,7 @@ _setup_intel_arc() {
if [[ "$os_codename" == "trixie" || "$os_codename" == "sid" ]]; then
msg_info "Fetching Intel compute-runtime from GitHub for Arc support"
# Fetch a compute-runtime package first so TOOLS_GH_REL_JSON is populated,
# Fetch a compute-runtime package first so /tmp/gh_rel.json is populated,
# then resolve the matching IGC tag from the release notes.
# libigdgmm - bundled in compute-runtime releases
fetch_and_deploy_gh_release "libigdgmm12" "intel/compute-runtime" "binary" "latest" "" "libigdgmm12_*_amd64.deb" || true
@@ -5481,7 +5352,7 @@ _setup_intel_modern() {
if [[ "$os_codename" == "trixie" || "$os_codename" == "sid" ]]; then
msg_info "Fetching Intel compute-runtime from GitHub"
# Fetch a compute-runtime package first so TOOLS_GH_REL_JSON is populated,
# Fetch a compute-runtime package first so /tmp/gh_rel.json is populated,
# then resolve the matching IGC tag from the release notes.
# libigdgmm first (bundled in compute-runtime releases)
fetch_and_deploy_gh_release "libigdgmm12" "intel/compute-runtime" "binary" "latest" "" "libigdgmm12_*_amd64.deb" || true
@@ -6535,7 +6406,7 @@ EOF
fi
# Scenario 1: Already installed at target version - just update packages
if [[ -n "$CURRENT_VERSION" ]] && version_matches_spec "$CURRENT_VERSION" "$MARIADB_VERSION"; then
if [[ -n "$CURRENT_VERSION" && "$CURRENT_VERSION" == "$MARIADB_VERSION" ]]; then
msg_info "Update MariaDB $MARIADB_VERSION"
# Ensure APT is working
@@ -6567,7 +6438,7 @@ EOF
fi
# Scenario 2b: Different version installed - clean upgrade
if [[ -n "$CURRENT_VERSION" ]] && ! version_matches_spec "$CURRENT_VERSION" "$MARIADB_VERSION"; then
if [[ -n "$CURRENT_VERSION" && "$CURRENT_VERSION" != "$MARIADB_VERSION" ]]; then
msg_info "Upgrade MariaDB from $CURRENT_VERSION to $MARIADB_VERSION"
remove_old_tool_version "mariadb"
fi
@@ -6856,13 +6727,24 @@ setup_meilisearch() {
fi
fi
# If migration is needed but dump failed, we have options:
# 1. Abort the update (safest, but annoying)
# 2. Backup data directory and proceed (allows manual recovery)
# 3. Just proceed and hope for the best (dangerous)
# We choose option 2: backup and proceed with warning
if [[ "$NEEDS_MIGRATION" == "true" ]] && [[ -z "$DUMP_UID" ]]; then
msg_error "MeiliSearch migration requires a successful dump before upgrade"
msg_error "Ensure the service is running and master_key is configured, or set MEILISEARCH_SKIP_MIGRATION=1 to force (data loss risk)"
if [[ "${MEILISEARCH_SKIP_MIGRATION:-}" != "1" ]]; then
return 100
local MEILI_DB_PATH
MEILI_DB_PATH=$(grep -E "^db_path\s*=" /etc/meilisearch.toml 2>/dev/null | sed 's/.*=\s*"\(.*\)"/\1/' | tr -d ' ' || true)
MEILI_DB_PATH="${MEILI_DB_PATH:-/var/lib/meilisearch/data}"
if [[ -d "$MEILI_DB_PATH" ]] && [[ -n "$(ls -A "$MEILI_DB_PATH" 2>/dev/null)" ]]; then
local BACKUP_PATH="${MEILI_DB_PATH}.backup.$(date +%Y%m%d%H%M%S)"
msg_warn "Backing up MeiliSearch data to ${BACKUP_PATH}"
mv "$MEILI_DB_PATH" "$BACKUP_PATH"
mkdir -p "$MEILI_DB_PATH"
msg_info "Data backed up. After update, you may need to reindex your data."
msg_info "Old data is preserved at: ${BACKUP_PATH}"
fi
msg_warn "MEILISEARCH_SKIP_MIGRATION=1 — proceeding without dump (manual reindex may be required)"
fi
# Stop service and update binary
@@ -7271,7 +7153,7 @@ setup_mysql() {
# Scenario 2: Use official MySQL repository (USE_MYSQL_REPO=true)
# Scenario 2a: Already at target version - just update packages
if [[ -n "$CURRENT_VERSION" ]] && version_matches_spec "$CURRENT_VERSION" "$MYSQL_VERSION"; then
if [[ -n "$CURRENT_VERSION" && "$CURRENT_VERSION" == "$MYSQL_VERSION" ]]; then
msg_info "Update MySQL $MYSQL_VERSION"
ensure_apt_working || return 100
@@ -7287,7 +7169,7 @@ setup_mysql() {
fi
# Scenario 2: Different version installed - clean upgrade
if [[ -n "$CURRENT_VERSION" ]] && ! version_matches_spec "$CURRENT_VERSION" "$MYSQL_VERSION"; then
if [[ -n "$CURRENT_VERSION" && "$CURRENT_VERSION" != "$MYSQL_VERSION" ]]; then
msg_info "Upgrade MySQL from $CURRENT_VERSION to $MYSQL_VERSION"
remove_old_tool_version "mysql"
else
@@ -8677,10 +8559,13 @@ setup_uv() {
local UV_BIN="/usr/local/bin/uv"
local UVX_BIN="/usr/local/bin/uvx"
local TMP_DIR=$(mktemp -d)
local CACHED_VERSION
# trap for TMP Cleanup
trap "rm -rf '$TMP_DIR'" EXIT
CACHED_VERSION=$(get_cached_version "uv")
# Architecture Detection
local ARCH=$(uname -m)
local OS_TYPE=""
@@ -9156,10 +9041,6 @@ check_for_gl_release() {
ensure_dependencies jq
local gl_check_json
gl_check_json=$(mktemp /tmp/tools-gl-check-XXXXXX.json) || return 7
trap 'rm -f "$gl_check_json"' RETURN
local repo_encoded
repo_encoded=$(printf '%s' "$source" | sed 's|/|%2F|g')
@@ -9171,23 +9052,23 @@ check_for_gl_release() {
# For pinned versions, try to fetch the specific release tag first
if [[ -n "$pinned_version_in" ]]; then
local pinned_encoded="${pinned_version_in//\//%2F}"
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o "$gl_check_json" \
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gl_check.json \
"${header[@]}" \
"https://gitlab.com/api/v4/projects/$repo_encoded/releases/$pinned_encoded" 2>/dev/null) || true
if [[ "$http_code" == "200" ]] && [[ -s "$gl_check_json" ]]; then
releases_json="[$(<"$gl_check_json")]"
if [[ "$http_code" == "200" ]] && [[ -s /tmp/gl_check.json ]]; then
releases_json="[$(</tmp/gl_check.json)]"
fi
rm -f "$gl_check_json"
rm -f /tmp/gl_check.json
fi
# Fetch full releases list if needed
if [[ -z "$releases_json" ]]; then
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o "$gl_check_json" \
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gl_check.json \
"${header[@]}" \
"https://gitlab.com/api/v4/projects/$repo_encoded/releases?per_page=100&order_by=released_at&sort=desc" 2>/dev/null) || true
if [[ "$http_code" == "200" ]] && [[ -s "$gl_check_json" ]]; then
releases_json=$(<"$gl_check_json")
if [[ "$http_code" == "200" ]] && [[ -s /tmp/gl_check.json ]]; then
releases_json=$(</tmp/gl_check.json)
elif [[ "$http_code" == "401" ]]; then
msg_error "GitLab API authentication failed (HTTP 401)."
if [[ -n "${GITLAB_TOKEN:-}" ]]; then
@@ -9195,28 +9076,28 @@ check_for_gl_release() {
else
msg_error "The repository may require authentication. Try: export GITLAB_TOKEN=\"glpat-your_token\""
fi
rm -f "$gl_check_json"
rm -f /tmp/gl_check.json
return 22
elif [[ "$http_code" == "404" ]]; then
msg_error "GitLab project not found (HTTP 404). Ensure '${source}' is correct and publicly accessible."
rm -f "$gl_check_json"
rm -f /tmp/gl_check.json
return 22
elif [[ "$http_code" == "429" ]]; then
msg_error "GitLab API rate limit exceeded (HTTP 429)."
msg_error "To increase the limit, export a GitLab token: export GITLAB_TOKEN=\"glpat-your_token_here\""
rm -f "$gl_check_json"
rm -f /tmp/gl_check.json
return 22
elif [[ "$http_code" == "000" || -z "$http_code" ]]; then
msg_error "GitLab API connection failed (no response)."
msg_error "Check your network/DNS: curl -sSL https://gitlab.com/api/v4/version"
rm -f "$gl_check_json"
rm -f /tmp/gl_check.json
return 7
else
msg_error "Unable to fetch releases for ${app} (HTTP ${http_code})"
rm -f "$gl_check_json"
rm -f /tmp/gl_check.json
return 22
fi
rm -f "$gl_check_json"
rm -f /tmp/gl_check.json
fi
mapfile -t raw_tags < <(jq -r '.[] | .tag_name' <<<"$releases_json")
@@ -9436,10 +9317,6 @@ fetch_and_deploy_gl_release() {
ensure_dependencies jq
local gl_rel_json
gl_rel_json=$(mktemp /tmp/tools-gl-rel-XXXXXX.json) || return 7
trap 'rm -f "$gl_rel_json"' RETURN
local repo_encoded
repo_encoded=$(printf '%s' "$repo" | sed 's|/|%2F|g')
@@ -9457,7 +9334,7 @@ fetch_and_deploy_gl_release() {
local max_retries=3 retry_delay=2 attempt=1 success=false http_code
while ((attempt <= max_retries)); do
http_code=$(curl $api_timeout -sSL -w "%{http_code}" -o "$gl_rel_json" "${header[@]}" "$api_url" 2>/dev/null) || true
http_code=$(curl $api_timeout -sSL -w "%{http_code}" -o /tmp/gl_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
if [[ "$http_code" == "200" ]]; then
success=true
break
@@ -9498,7 +9375,7 @@ fetch_and_deploy_gl_release() {
fi
local json tag_name
json=$(<"$gl_rel_json")
json=$(</tmp/gl_rel.json)
if [[ "$version" == "latest" ]]; then
json=$(echo "$json" | jq '.[0] // empty')