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 @@
|
|||||||
|
*.sh text eol=lf
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
creds.txt
|
||||||
|
altsp-repo/
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# altsp-downloader
|
||||||
|
|
||||||
|
Скрипт для ALT SP Server: скачивает один или несколько пакетов вместе со
|
||||||
|
всеми зависимостями и собирает из них автономный локальный apt-rpm
|
||||||
|
репозиторий (схема `classic`), готовый для переноса на машину без
|
||||||
|
интернета.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- ALT Linux / ALT SP Server, apt-rpm (`apt-get`, `rpm`).
|
||||||
|
- Доступ к реальным (интернет) репозиториям на машине, где запускается
|
||||||
|
скрипт.
|
||||||
|
- root / sudo.
|
||||||
|
|
||||||
|
Пакет `apt-repo-tools` (для `genbasedir`) устанавливается скриптом
|
||||||
|
автоматически, если отсутствует.
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./altsp-download-repo.sh [-o|--output DIR] PKG1 [PKG2 ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Пакеты передаются аргументами командной строки — можно указать сразу
|
||||||
|
несколько для batch-скачивания.
|
||||||
|
- `-o/--output DIR` — каталог репозитория (по умолчанию `./altsp-repo`).
|
||||||
|
- `--skip-update` — пропустить `apt-get update` перед скачиванием.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./altsp-download-repo.sh -o /srv/offline-repo bash mc htop
|
||||||
|
```
|
||||||
|
|
||||||
|
Повторные запуски с другим набором пакетов дополняют существующий каталог
|
||||||
|
репозитория, не скачивая заново уже имеющиеся пакеты.
|
||||||
|
|
||||||
|
## Как это работает
|
||||||
|
|
||||||
|
Зависимости резолвятся против **пустой** rpm-базы (`rpm --initdb` во
|
||||||
|
временном fakeroot + `apt-get -o RPM::RootDir=...`), поэтому скачивается
|
||||||
|
**полное** дерево зависимостей запрошенных пакетов, а не только то, чего
|
||||||
|
не хватает на машине, где запущен скрипт. Это важно, так как репозиторий
|
||||||
|
предназначен для переноса на другую, возможно не идентичную по составу
|
||||||
|
пакетов машину.
|
||||||
|
|
||||||
|
Скачанные `.rpm` раскладываются по архитектурам
|
||||||
|
(`<repo>/<arch>/RPMS.classic/`), для каждой архитектуры генерируются
|
||||||
|
метаданные через `genbasedir` (`<repo>/<arch>/base/`).
|
||||||
|
|
||||||
|
## Установка на офлайн-машине
|
||||||
|
|
||||||
|
Результат работы скрипта (`README.txt` внутри каталога репозитория)
|
||||||
|
содержит готовые строки для `sources.list`, например:
|
||||||
|
|
||||||
|
```
|
||||||
|
rpm file:/srv/offline-repo x86_64 classic
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt-get update
|
||||||
|
apt-get install <имена пакетов>
|
||||||
|
```
|
||||||
|
|
||||||
|
Подпись репозитория не требуется — тег вроде `[cert8]` указывать не нужно.
|
||||||
@@ -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