name: Install apt packages description: > Installs apt packages with a per-attempt timeout and retries. The hosted runner's Azure apt mirror intermittently serves archives at a crawl, which apt's own timeout does not catch. A stuck attempt is killed and retried, which usually lands on a healthy mirror node. inputs: packages: description: Space separated list of packages to install required: true attempts: description: Maximum number of attempts default: "3" attempt-timeout: description: Seconds before a single attempt is killed default: "180" runs: using: composite steps: - name: Install ${{ inputs.packages }} shell: bash env: PACKAGES: ${{ inputs.packages }} ATTEMPTS: ${{ inputs.attempts }} ATTEMPT_TIMEOUT: ${{ inputs.attempt-timeout }} run: | SUDO="" if [ "$(id -u)" -ne 0 ]; then SUDO="sudo" fi attempt_install() { $SUDO apt-get update -q # Intentionally unquoted, this is a list of package names $SUDO apt-get install -q --yes --no-install-recommends $PACKAGES } export -f attempt_install export SUDO PACKAGES for attempt in $(seq 1 "${ATTEMPTS}"); do echo "::group::apt install attempt ${attempt}/${ATTEMPTS}" start=$(date +%s) if timeout --kill-after=10 "${ATTEMPT_TIMEOUT}" bash -c attempt_install; then echo "::endgroup::" echo "Installed in $(( $(date +%s) - start ))s" exit 0 fi echo "::endgroup::" echo "::warning::apt install attempt ${attempt}/${ATTEMPTS} failed or exceeded ${ATTEMPT_TIMEOUT}s" # A killed attempt can leave dpkg half configured $SUDO dpkg --configure -a || true done echo "::error::apt install failed after ${ATTEMPTS} attempts" exit 1