Files
altsp-downloader/altsp-download-repo.sh
T
a.gritsenko 98b1d44671 Don't abort the whole build if some packages fail to download
Download packages one at a time instead of a single batch apt-get call,
so a typo'd or unavailable package name (or a transient network error on
one package) no longer aborts the entire run. Failed packages are
collected, printed as a warning, and the repo/archive are still built
from whatever succeeded.

Exit codes: 0 = all packages downloaded, 1 = fatal (nothing downloaded
at all), 2 = repo/archive built but some packages were skipped.

Verified manually against the ALT SP Server test machine.
2026-09-25 18:09:02 +03:00

206 lines
8.0 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Скачивает пакеты ALT Linux вместе со всеми зависимостями и собирает
# из них автономный локальный apt-rpm репозиторий (схема "classic"),
# готовый для переноса на машину без интернета.
set -euo pipefail
SCRIPT_NAME=$(basename "$0")
OUTDIR="./altsp-repo"
SKIP_UPDATE=0
ARCHIVE=0
PACKAGES=()
ORIGINAL_ARGS=("$@")
usage() {
cat <<EOF
Использование: $SCRIPT_NAME [-o|--output DIR] [--skip-update] [-a|--archive] PKG1 [PKG2 ...]
Опции:
-o, --output DIR Каталог репозитория (по умолчанию: $OUTDIR)
--skip-update Не выполнять apt-get update перед скачиванием
-a, --archive Дополнительно упаковать готовый репозиторий в
<каталог>.tar.gz (для переноса одним файлом)
-h, --help Показать эту справку
Повторные запуски с другим набором пакетов дополняют существующий
каталог репозитория, не скачивая заново уже имеющиеся пакеты.
Если часть пакетов не удалось скачать, остальные всё равно собираются
в репозиторий/архив; список пропущенных выводится предупреждением.
Коды возврата:
0 — все пакеты скачаны успешно
1 — фатальная ошибка (ничего не скачано и т.п.)
2 — репозиторий/архив собраны, но часть пакетов пропущена
Пример:
sudo $SCRIPT_NAME -o /srv/offline-repo -a bash mc htop
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
OUTDIR="$2"; shift 2 ;;
--skip-update)
SKIP_UPDATE=1; shift ;;
-a|--archive)
ARCHIVE=1; shift ;;
-h|--help)
usage; exit 0 ;;
--)
shift; PACKAGES+=("$@"); break ;;
-*)
echo "Неизвестная опция: $1" >&2; usage >&2; exit 1 ;;
*)
PACKAGES+=("$1"); shift ;;
esac
done
if [[ ${#PACKAGES[@]} -eq 0 ]]; then
echo "Ошибка: не указано ни одного пакета." >&2
usage >&2
exit 1
fi
if [[ $EUID -ne 0 ]]; then
echo "Требуются права root, перезапуск через sudo..." >&2
exec sudo -- "$0" "${ORIGINAL_ARGS[@]}"
fi
if ! command -v genbasedir >/dev/null 2>&1; then
echo "genbasedir не найден, устанавливаю пакет apt-repo-tools..." >&2
apt-get install -y apt-repo-tools
fi
if [[ $SKIP_UPDATE -eq 0 ]]; then
echo "Обновление индексов apt-get..." >&2
apt-get update
fi
mkdir -p "$OUTDIR"
OUTDIR=$(cd "$OUTDIR" && pwd)
CACHE="$OUTDIR/.cache"
FAKEROOT="$CACHE/fakeroot"
ARCHIVES="$CACHE/archives"
mkdir -p "$FAKEROOT" "$ARCHIVES/partial"
# Пустая rpm-база => apt-get считает, что на целевой системе не установлено
# вообще ничего, и скачивает ПОЛНОЕ дерево зависимостей, а не только то,
# чего не хватает на этой (текущей) машине.
if [[ ! -d "$FAKEROOT/var/lib/rpm" ]]; then
rpm --root="$FAKEROOT" --initdb
fi
FAILED_PACKAGES=()
for pkg in "${PACKAGES[@]}"; do
echo "Скачивание пакета и зависимостей: $pkg" >&2
if ! apt-get \
-o RPM::RootDir="$FAKEROOT" \
-o Dir::Cache::Archives="$ARCHIVES" \
-y -d install "$pkg"; then
echo "ПРЕДУПРЕЖДЕНИЕ: не удалось скачать '$pkg' — пропускаю, продолжаю с остальными" >&2
FAILED_PACKAGES+=("$pkg")
fi
done
echo "Раскладка пакетов по архитектурам..." >&2
shopt -s nullglob
for rpmfile in "$ARCHIVES"/*.rpm; do
arch=$(rpm -qp --qf '%{ARCH}' "$rpmfile" 2>/dev/null) || continue
[[ -z "$arch" ]] && continue
destdir="$OUTDIR/$arch/RPMS.classic"
mkdir -p "$destdir"
dest="$destdir/$(basename "$rpmfile")"
[[ -e "$dest" ]] || ln -f "$rpmfile" "$dest" 2>/dev/null || cp -f "$rpmfile" "$dest"
done
shopt -u nullglob
shopt -s nullglob
arch_dirs=("$OUTDIR"/*/RPMS.classic)
shopt -u nullglob
if [[ ${#arch_dirs[@]} -eq 0 ]]; then
echo "Ошибка: ни один пакет не был успешно скачан (${FAILED_PACKAGES[*]})." >&2
exit 1
fi
for archdir in "$OUTDIR"/*/RPMS.classic; do
[[ -d "$archdir" ]] || continue
arch=$(basename "$(dirname "$archdir")")
echo "Генерация метаданных репозитория для $arch..." >&2
# --bloat: без этого флага genbasedir урезает данные о версии для
# некоторых автоматических soname-зависимостей (Provides с "set:..."
# хешем символьных версий), из-за чего apt-get на офлайн-машине не может
# разрешить некоторые реальные зависимости (например libssh2 -> mc),
# хотя с апстрим-репозиторием те же самые .rpm разрешаются нормально.
genbasedir --progress --bloat --create --topdir="$OUTDIR" "$arch" classic
done
MANIFEST="$OUTDIR/manifest.txt"
{
echo "# $(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf '%s\n' "${PACKAGES[@]}"
} >> "$MANIFEST"
README="$OUTDIR/README.txt"
{
echo "Автономный apt-rpm репозиторий ALT Linux"
echo "Собран: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo
echo "Как подключить на машине без интернета:"
echo
echo "1. Скопируйте этот каталог целиком на целевую машину, например в /srv/offline-repo"
echo " (rsync/scp/флешка — как удобно)."
echo
echo "2. Добавьте в /etc/apt/sources.list (или новый файл в /etc/apt/sources.list.d/) строки:"
echo
for archdir in "$OUTDIR"/*/RPMS.classic; do
[[ -d "$archdir" ]] || continue
arch=$(basename "$(dirname "$archdir")")
echo " rpm file:/srv/offline-repo $arch classic"
done
echo
echo " (замените /srv/offline-repo на реальный путь на целевой машине;"
echo " тег подписи вроде [cert8] указывать не нужно, репозиторий не подписан)"
echo
echo "3. Выполните на целевой машине:"
echo " apt-get update"
echo " apt-get install <имена пакетов>"
} > "$README"
rpm_count=$(find "$OUTDIR" -path "$CACHE" -prune -o -name '*.rpm' -print | wc -l)
total_bytes=0
for archdir in "$OUTDIR"/*/RPMS.classic; do
[[ -d "$archdir" ]] || continue
sz=$(du -sb "$archdir" | cut -f1)
total_bytes=$((total_bytes + sz))
done
human_size=$(numfmt --to=iec "$total_bytes" 2>/dev/null || echo "${total_bytes} bytes")
ARCHIVE_PATH=""
if [[ $ARCHIVE -eq 1 ]]; then
ARCHIVE_PATH="$OUTDIR.tar.gz"
echo "Упаковка репозитория в архив..." >&2
tar -czf "$ARCHIVE_PATH" \
-C "$(dirname "$OUTDIR")" \
--exclude="$(basename "$OUTDIR")/.cache" \
"$(basename "$OUTDIR")"
fi
echo
echo "Готово."
echo "Репозиторий: $OUTDIR"
echo "Пакетов (rpm): $rpm_count"
echo "Суммарный размер: $human_size"
echo "Инструкция: $README"
if [[ -n "$ARCHIVE_PATH" ]]; then
echo "Архив: $ARCHIVE_PATH"
fi
if [[ ${#FAILED_PACKAGES[@]} -gt 0 ]]; then
echo
echo "ПРЕДУПРЕЖДЕНИЕ: не скачаны ${#FAILED_PACKAGES[@]} пакет(ов): ${FAILED_PACKAGES[*]}"
exit 2
fi