Convenient for transferring the offline repo as one file instead of a directory tree; excludes the internal .cache/ (download cache + fakeroot rpm db), which the target machine doesn't need.
178 lines
6.8 KiB
Bash
178 lines
6.8 KiB
Bash
#!/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 Показать эту справку
|
||
|
||
Повторные запуски с другим набором пакетов дополняют существующий
|
||
каталог репозитория, не скачивая заново уже имеющиеся пакеты.
|
||
|
||
Пример:
|
||
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
|
||
|
||
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")
|
||
|
||
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
|