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).
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user