mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-23 16:18:55 +00:00
Compare commits
1
Commits
dev
...
chore/flAke-PT
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b68ce4b977 |
No files matched your search
@@ -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
|
|
||||||
@@ -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
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in new issue
Block a user