Files
altsp-downloader/altsp-download-repo.sh
T
a.gritsenko 33e8d18f94 Add altsp-download-repo.sh: build standalone offline apt-rpm repo for ALT SP Server
Downloads one or more packages together with their full dependency
closure (resolved against an empty rpm db so the result works on a
target machine with an arbitrary baseline, not just what's missing on
the machine running the script), then builds a local apt-rpm "classic"
repository via genbasedir --bloat (required: without --bloat, genbasedir
strips version data from some soname Provides, e.g. libssh2 -> mc, which
breaks dependency resolution on a truly clean target even though the
same packages resolve fine against the real upstream repo).

Verified end-to-end against a live ALT SP Server 10.2.2 test machine,
including simulating a from-scratch install using only the generated
repo (no upstream access).
2026-09-22 03:33:52 +03:00

160 lines
6.1 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
PACKAGES=()
ORIGINAL_ARGS=("$@")
usage() {
cat <<EOF
Использование: $SCRIPT_NAME [-o|--output DIR] [--skip-update] PKG1 [PKG2 ...]
Опции:
-o, --output DIR Каталог репозитория (по умолчанию: $OUTDIR)
--skip-update Не выполнять apt-get update перед скачиванием
-h, --help Показать эту справку
Повторные запуски с другим набором пакетов дополняют существующий
каталог репозитория, не скачивая заново уже имеющиеся пакеты.
Пример:
sudo $SCRIPT_NAME -o /srv/offline-repo bash mc htop
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output)
OUTDIR="$2"; shift 2 ;;
--skip-update)
SKIP_UPDATE=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
echo "Скачивание пакетов и зависимостей: ${PACKAGES[*]}" >&2
apt-get \
-o RPM::RootDir="$FAKEROOT" \
-o Dir::Cache::Archives="$ARCHIVES" \
-y -d install "${PACKAGES[@]}"
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
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")
echo
echo "Готово."
echo "Репозиторий: $OUTDIR"
echo "Пакетов (rpm): $rpm_count"
echo "Суммарный размер: $human_size"
echo "Инструкция: $README"