#!/usr/bin/env bash # Installs apt packages in CI without depending on fast mirrors: reuses cached .deb # archives when present and retries with per-attempt timeouts if apt stalls or fails. # # Usage: scripts/apt-install.sh ... set -euo pipefail [ $# -ge 2 ] || { echo "usage: $0 ..." >&2; exit 2; } cache="$1"; shift pkgs=("$@") mkdir -p "$cache/partial" # apt runs as root and leaves files (lock, .deb) unreadable by non-root runner users; # restore ownership on exit so cache directories remain accessible. trap 'sudo chown -R "$(id -u):$(id -g)" "$cache" 2>/dev/null || true; rm -f "$cache/lock"' EXIT opts=(-o "Dir::Cache::archives=$cache" -y) # 1) With cached .deb files and package index present: install directly without updating apt lists if [ -z "$(dpkg --print-foreign-architectures)" ] && ls "$cache"/*.deb >/dev/null 2>&1 && ls /var/lib/apt/lists/*Packages* >/dev/null 2>&1 && timeout 300 sudo apt-get "${opts[@]}" install "${pkgs[@]}"; then echo "installed from cache" exit 0 fi # 2) Cache miss (or multiarch / incomplete): update and install, up to 3 attempts with timeout for attempt in 1 2 3; do if timeout 240 sudo apt-get update && { [ -z "$(dpkg --print-foreign-architectures)" ] || timeout 600 sudo apt-get "${opts[@]}" upgrade; } && timeout 600 sudo apt-get "${opts[@]}" install "${pkgs[@]}"; then echo "installed (attempt $attempt)" exit 0 fi echo "apt failed or timed out (attempt $attempt of 3)" >&2 sleep $((attempt * 10)) done echo "failed to install: ${pkgs[*]}" >&2 exit 1