Compare commits

..
1 Commits
Author SHA1 Message Date
shamoonandCodex b68ce4b977 Lets just see this idea
Co-Authored-By: Codex <267193182+codex@users.noreply.github.com>
2026-10-22 16:03:07 -07:00
4 changed files with 112 additions and 34 deletions

No files matched your search

+43 -34
View File
@@ -1,53 +1,62 @@
name: Install apt packages name: Install apt packages
description: > 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. Caches apt packages and their dependencies for an exact hosted runner image. Cache hits install offline; cache misses download from Ubuntu mirrors with bounded retries.
inputs: inputs:
packages: packages:
description: Space separated list of packages to install description: Space separated list of package names to install
required: true required: true
attempts: attempts:
description: Maximum number of attempts description: Maximum number of download attempts
default: "3" default: "3"
attempt-timeout: attempt-timeout:
description: Seconds before a single attempt is killed description: Seconds before a single download attempt is killed
default: "180" default: "180"
runs: runs:
using: composite using: composite
steps: steps:
- name: Identify apt cache
id: cache-key
shell: bash
env:
PACKAGES: ${{ inputs.packages }}
run: |
# The bundle contains only dependencies missing from this runner. Never
# restore it onto a different image or initial package installation.
# Without an image version (e.g. self-hosted), download normally.
if [ -n "${ImageVersion:-}" ]; then
fingerprint=$({
printf '%s\n' "$PACKAGES"
dpkg --print-architecture
dpkg-query -W -f='${binary:Package}=${Version} ${db:Status-Abbrev}\n'
sha256sum "$GITHUB_ACTION_PATH/action.yml" "$GITHUB_ACTION_PATH/install.sh" | cut -d ' ' -f 1
} | sha256sum | cut -d ' ' -f 1)
echo "key=apt-v1-${ImageOS}-${ImageVersion}-${fingerprint}" >> "$GITHUB_OUTPUT"
fi
- name: Restore apt packages
id: cache
if: steps.cache-key.outputs.key != ''
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
${{ runner.temp }}/apt-packages/*.deb
${{ runner.temp }}/apt-packages/complete
key: ${{ steps.cache-key.outputs.key }}
- name: Install ${{ inputs.packages }} - name: Install ${{ inputs.packages }}
shell: bash shell: bash
env: env:
PACKAGES: ${{ inputs.packages }} PACKAGES: ${{ inputs.packages }}
ATTEMPTS: ${{ inputs.attempts }} ATTEMPTS: ${{ inputs.attempts }}
ATTEMPT_TIMEOUT: ${{ inputs.attempt-timeout }} ATTEMPT_TIMEOUT: ${{ inputs.attempt-timeout }}
run: | CACHE_DIR: ${{ runner.temp }}/apt-packages
SUDO="" CACHE_HIT: ${{ steps.cache.outputs.cache-hit }}
if [ "$(id -u)" -ne 0 ]; then run: bash "$GITHUB_ACTION_PATH/install.sh"
SUDO="sudo" # Save immediately: a later test failure must not discard a good apt bundle.
fi - name: Save apt packages
if: steps.cache-key.outputs.key != '' && steps.cache.outputs.cache-hit != 'true'
attempt_install() { uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
$SUDO apt-get update -q with:
# Intentionally unquoted, this is a list of package names path: |
$SUDO apt-get install -q --yes --no-install-recommends $PACKAGES ${{ runner.temp }}/apt-packages/*.deb
} ${{ runner.temp }}/apt-packages/complete
export -f attempt_install key: ${{ steps.cache-key.outputs.key }}
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
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
set -euo pipefail
SUDO=()
if [ "$(id -u)" -ne 0 ]; then
SUDO=(sudo)
fi
# Inputs are package names, separated by whitespace (not shell expressions).
read -r -a requested_packages <<< "$PACKAGES"
mkdir -p "$CACHE_DIR"
install_local() {
local archives=("$CACHE_DIR"/*.deb)
if [ -e "${archives[0]}" ]; then
# Local .deb arguments pin the downloaded versions. No package lists or
# network access are needed, and apt still runs all package maintainer scripts.
"${SUDO[@]}" chmod a+r "${archives[@]}"
"${SUDO[@]}" apt-get install -q --yes --no-install-recommends \
--no-download -o "Dir::Cache::archives=$CACHE_DIR" "${archives[@]}"
fi
# An empty bundle is valid when the runner already includes every package.
local package
for package in "${requested_packages[@]}"; do
[ "$(dpkg-query -W -f='${Status}' "$package")" = "install ok installed" ]
done
}
if [ "$CACHE_HIT" = true ]; then
echo "Installing cached apt packages without contacting package repositories"
install_local
exit 0
fi
# The hosted runner gives Azure priority 1. A slow transfer never triggers its
# fallback mirrors, so retrying alone keeps selecting the same unhealthy mirror.
if [ -f /etc/apt/apt-mirrors.txt ]; then
"${SUDO[@]}" sed -i '\|azure\.archive\.ubuntu\.com|d' /etc/apt/apt-mirrors.txt
fi
# Download first so the bundle contains every missing transitive dependency.
# Keep downloads outside apt's default cache, which Docker cleanup hooks may wipe.
download_packages() {
"${SUDO[@]}" apt-get update -q --error-on=any
"${SUDO[@]}" apt-get install -q --yes --no-install-recommends \
--download-only -o "Dir::Cache::archives=$CACHE_DIR" "${requested_packages[@]}"
}
# Recreate the sudo and package arrays in the timed child process.
if [ "${1:-}" = download ]; then
download_packages
exit 0
fi
for attempt in $(seq 1 "$ATTEMPTS"); do
echo "::group::apt download attempt ${attempt}/${ATTEMPTS}"
if timeout --kill-after=10 "$ATTEMPT_TIMEOUT" bash "$0" download; then
echo "::endgroup::"
install_local
touch "$CACHE_DIR/complete"
exit 0
fi
echo "::endgroup::"
echo "::warning::apt download attempt ${attempt}/${ATTEMPTS} failed or exceeded ${ATTEMPT_TIMEOUT}s"
done
echo "::error::apt download failed after ${ATTEMPTS} attempts"
exit 1
+1
View File
@@ -76,6 +76,7 @@ jobs:
- 'uv.lock' - 'uv.lock'
- 'docker/compose/docker-compose.ci-test.yml' - 'docker/compose/docker-compose.ci-test.yml'
- '.github/workflows/ci-backend.yml' - '.github/workflows/ci-backend.yml'
- '.github/actions/apt-install/**'
test: test:
needs: changes needs: changes
if: needs.changes.outputs.backend_changed == 'true' if: needs.changes.outputs.backend_changed == 'true'
+1
View File
@@ -68,6 +68,7 @@ jobs:
frontend: frontend:
- 'src-ui/**' - 'src-ui/**'
- '.github/workflows/ci-frontend.yml' - '.github/workflows/ci-frontend.yml'
- '.github/actions/apt-install/**'
install-dependencies: install-dependencies:
needs: changes needs: changes
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'