вторник, 9 октября 2007 г.

Создание локального репозитория

Update: только стоило мне написать скрипт и эту статью, как один мой друг дал мне ссылку на уже готовый продукт (debmirror). :) Пол года назад, когда я выкачивал себе репозиторий Ubuntu 7.04, мне не удалось найти ничего подобного, поэтому в этот раз я даже не стал искать... Слегка поэкспериментировав с debmirror, я пришел к выводу, что все-таки мой скрипт имеет как минимум одно преимущество перед ним - если пользоваться моим скриптом, то для обновления репозитория вам не нужно хранить на своей машине копию ранее созданного репозитория, а после обновления опять нести весь этот объем на компьютер, для которого предназначен создаваемый репозиторий.

Введение
Есть большое желание использовать свой любимый Ubuntu там, где интернет слишком дорог или его вообще нет? У меня есть такое желание и оно начинает обостряться в связи со скорым выходом Ubuntu 7.10. На работе у меня с Интернетом довольно туго, поэтому когда я пол года назад ставил там себе Ubuntu 7.04, я сделал копию всего репозитория и принес на работу, после чего получил возможность быстро и, не тратя ни килобайта Интернет трафика, устанавливать любой пакет, имеющийся в репозитории Ubuntu 7.04. Единственный недостаток такого подхода - размер репозитория. Когда я выкачивал его для 7.04, я скачивал все бинарные пакеты. Результат - свыше 20000 пакетов и 16 Гб места на диске. Но, думаю, с сегодняшними объемами жестких дисков 16 Гб под такую полезную вещь может позволить себе практически каждый.

Так вот чтобы создать локальный репозиторий для Ubuntu 7.10, я решил написать скрипт для выкачивания всех необходимых пакетов и выложить его сюда, т. к. данный скрипт может кому-нибудь пригодиться.

Сразу скажу, что при написании скрипта я не ставил перед собой задачу предусмотреть и обработать все возможные ошибки при его работе, что практически не нужно и только увеличило бы его объем в несколько раз и отняло бы у меня солидную часть свободного времени. Например, если в /etc/apt/sources.list кроме обычных адресов вида http://... и ftp://... встретится что-нибудь вроде cdrom://..., то скрипт предупредит об ошибке, но сработает нормально.

Кратко опишу, что делает данный скрипт (а точнее их три):
Первый - get_lists просматривает /etc/apt/sources.list, в котором содержится список используемых вами репозиториев, скачивает необходимые файлы, содержащие информацию о всех пакетах в репозитории и составляет файл с URL'ами на данные пакеты.
Второй - get_packages просматривает полученный список URL'ов и скачивает их wget'ом.
Третий - create_repository формирует на основе скачанных пакетов репозиторий.

Так как объем скачиваемых файлов велик, я сделал так, что если вы прервете работу get_packages, а потом запустите снова, то он продолжит скачивание с того пакета, на котором остановился в прошлый раз.

Примечание 1: по умолчанию скрипт выкачивает пакеты с архитектурой i386. Если вы используете 64-битную версию Ununtu, то вам следует в файле get_lists исправить строку
dist_arch="i386"
на
dist_arch="amd64"

Примечание 2: то, что в данной статье я пока что упоминал только Ubuntu, не означает, что мой скрипт будет работать только под этим дистрибутивом. Он будет без проблем работать со всеми дистрибутивами, использующими apt-get и deb пакеты. Для дистрибутивов, которые используют apt-get и RPM, данный скрипт не подойдет, но "заточить" под них будет очень легко - буквально надо будет изменить несколько строк.

Создание репозитория
Ну а теперь, пожалуй, перейдем к действию. Покажу, как с помощью моих скриптов создать себе локальный репозиторий. Предположим, что мы хотим, чтобы наш будущий репозиторий лежал в папке ./repository. Запускаем последовательно все три скрипта:
$ ./get_lists ./repository
Получаем списки пакетов...
http://archive.ubuntu.com/ubuntu/dists/feisty/main/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty/universe/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty/restricted/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty/multiverse/binary-i386/Packages.gz [OK]
http://security.ubuntu.com/ubuntu/dists/feisty-security/universe/binary-i386/Packages.gz [OK]
http://security.ubuntu.com/ubuntu/dists/feisty-security/main/binary-i386/Packages.gz [OK]
http://security.ubuntu.com/ubuntu/dists/feisty-security/multiverse/binary-i386/Packages.gz [OK]
http://security.ubuntu.com/ubuntu/dists/feisty-security/restricted/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-updates/universe/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-updates/main/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-updates/multiverse/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-updates/restricted/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-proposed/universe/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-proposed/main/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-proposed/multiverse/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-proposed/restricted/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-backports/universe/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-backports/main/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-backports/multiverse/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/feisty-backports/restricted/binary-i386/Packages.gz [OK]

Списки пакетов получены успешно

$ ./get_packages ./repository
Скачиваем пакеты...
http://security.ubuntu.com/ubuntu/pool/universe/a/aircrack-ng/aircrack_0.6.2-7ubuntu1.1_all.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/aircrack-ng/aircrack-ng_0.6.2-7ubuntu1.1_i386.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/apache2-mpm-itk/apache2-mpm-itk_2.2.3-01-1build1.1_i386.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk_1.2.16~dfsg-1ubuntu3.1_all.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk-bristuff_1.2.16~dfsg-1ubuntu3.1_i386.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk-classic_1.2.16~dfsg-1ubuntu3.1_i386.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk-config_1.2.16~dfsg-1ubuntu3.1_all.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk-dev_1.2.16~dfsg-1ubuntu3.1_all.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk-doc_1.2.16~dfsg-1ubuntu3.1_all.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk-h323_1.2.16~dfsg-1ubuntu3.1_i386.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk-sounds-main_1.2.16~dfsg-1ubuntu3.1_all.deb [OK]
http://security.ubuntu.com/ubuntu/pool/universe/a/asterisk/asterisk-web-vmail_1.2.16~dfsg-1ubuntu3.1_all.deb [OK]
.
.
.
.
.
http://archive.ubuntu.com/ubuntu/pool/main/z/zsh/zsh_4.3.2-25ubuntu1_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/z/zsh/zsh-dbg_4.3.2-25ubuntu1_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/z/zsh/zsh-dev_4.3.2-25ubuntu1_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/z/zsh/zsh-doc_4.3.2-25ubuntu1_all.deb [OK]

Все необходимые пакеты скачаны.

$ ./create_repository ./repository
Репозиторий создан.

Все, теперь запасаемся болванками, записываем на них папку ./repository и несем на компьютер, до которого еще не дошла цивилизация в лице безлимитного интернета.

На этом компьютере кладем папку repository, например, в /home/user/repository и в /etc/apt/sources.list прописываем следующую строку:
deb file:/home/user/repository ./
выполняем
# apt-get update
И с радостью пользуемся собственным репозиторием.

Обновление репозитория
Теперь переходим к следующему моменту: допустим вам мало просто репозитория - вы хотите его еще и регулярно обновлять. Нет проблем! Если вы заметили, то в нашей папке ./repository после создания репозитория появился файл Packages.list. Это список всех пакетов в вашем репозитории. Так вот допустим, что сегодня утром вы проснулись и у вас появилась твердая мысль обновить ранее созданный репозиторий. :) Вот что вам нужно сделать:
- Cоздайте на своем компьютере уже знакомую нам папку ./repository
- Заберите из ранее созданного репозитория файл Packages.list и киньте его в ./repository.
- Запустите
$ ./get_lists ./repository
$ ./get_packages ./repository

Тем самым вы скачаете все новые пакеты, которые появились с того момента, когда вы создавали свой репозиторий (скрипт смотрит в Packages.list, какие пакеты уже есть в репозитории и скачивает все кроме них.
Теперь закатывайте ./repository на болванку, несите на компьютер с уже устаревшим репозиторием и копируйте все файлы и папки из ./repository в папку со старым репозиторием с заменой файлов с одинаковыми именами.
Далее запустите create_repository передав ему в качестве аргумента путь к папке в которой лежит теперь уже обновленный репозиторий.
$ ./create_repository /home/user/repository

Теперь в вашем репозитории будут доступны все новые пакеты.

Вот, собственно и все. Желаю успехов.

get_lists:
#!/bin/bash
#***************************************************************************
#*   Copyright (C) 2007, Konishchev Dmitry                                 *
#*   http://konishchevdmitry.blogspot.com/                                 *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU General Public License as published by  *
#*   the Free Software Foundation; either version 3 of the License, or     *
#*   (at your option) any later version.                                   *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU General Public License for more details.                          *
#**************************************************************************/

dist_arch="i386"

# Удаление всех файлов, созданных скриптом
cleanup()
{
if [ -e "$repository_dir/temp_list.gz" ]
then
rm "$repository_dir/temp_list.gz"
fi

if [ -e "$repository_dir/packages_list" ]
then
rm "$repository_dir/packages_list"
fi
}

# Аварийное завершение работы при ошибках
die()
{
echo "Ошибка!"
cleanup
exit 1
}

# Проверка "исходных данных" и создание необходимых файлов и папок -->
if [ "$1" == "" ]
then
echo "Исспользование: get_lists /path/to/repository"
exit 1
fi

repository_dir="$1"

mkdir -p "$repository_dir" || exit 1
echo -n '' > "$repository_dir/packages_list" || die
# Проверка "исходных данных" и создание необходимых файлов и папок <--

# Получаем из /etc/apt/sources.list всю необходимую информацию -->
packages_lists=`egrep '^deb ' /etc/apt/sources.list \
| sed 's/#.*//' \
| awk '{
for(i = 0; i < NF - 3; i++)
printf "%sdists/%s/%s/%s/Packages.gz\n", $2, $3, $(4 + i), "binary-'"$dist_arch"'";
}'`

packages_base_addresses=`egrep '^deb ' /etc/apt/sources.list \
| sed 's/#.*//' \
| awk '{ print $2; }'`
# Получаем из /etc/apt/sources.list всю необходимую информацию <--

# Скачиваем все необходимые списки пакетов и выдираем из них URL'ы на пакеты -->
echo "Получаем списки пакетов..."

is_wget_error=0
counter=0
for url in $packages_lists
do
((counter++))

# Удаляем прошлый скачанный файл -->
if [ -e "$repository_dir/temp_list.gz" ]
then
rm "$repository_dir/temp_list.gz" || die
fi
# Удаляем прошлый скачанный файл <--

# Скачиваем и обрабатываем очередной список пакетов -->
echo -n $url
wget --quiet -O "$repository_dir/temp_list.gz" $url
if [ $? -ne 0 ]
then
is_wget_error=1
echo ' [ERROR]'
else
echo ' [OK]'

# Получаем "базовый" адрес для списка пакетов -->
i=1
for base_address in $packages_base_addresses
do
if [ $i -eq $counter ]
then
break;
fi
done
# Получаем "базовый" адрес для списка пакетов <--

# Получаем список URL'ов на пакеты из скачанного списка -->
gzip --decompress --stdout "$repository_dir/temp_list.gz" \
| awk '{ if($1 == "Filename:") printf "%s%s\n", "'"$base_address"'", $2; }' \
>> "$repository_dir/packages_list"
# Получаем список URL'ов на пакеты из скачанного списка <--
fi
# Скачиваем и обрабатываем очередной список пакетов <--
done

# Чистим за собой -->
if [ -e "$repository_dir/temp_list.gz" ]
then
rm "$repository_dir/temp_list.gz" || die
fi
# Чистим за собой <--

# Проверяем, были ли ошибки при скачивании списков пакетов -->
if [ $counter -eq 0 ]
then
echo -e "\nНе удалось прочитать ни одного списка пакетов из /etc/apt/sources.list."
die
fi

if [ $is_wget_error -eq 0 ]
then
echo -e "\nСписки пакетов получены успешно"
else
echo -e "\nПри получении списков пакетов произошли ошибки."
fi
# Проверяем, были ли ошибки при скачивании списков пакетов <--
# Скачиваем все необходимые списки пакетов и выдираем из них URL'ы на пакеты -->

get_packages:
#!/bin/bash
#***************************************************************************
#*   Copyright (C) 2007, Konishchev Dmitry                                 *
#*   http://konishchevdmitry.blogspot.com/                                 *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU General Public License as published by  *
#*   the Free Software Foundation; either version 3 of the License, or     *
#*   (at your option) any later version.                                   *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU General Public License for more details.                          *
#**************************************************************************/

# Проверка "исходных данных" -->
if [ "$1" == "" ]
then
echo "Исспользование: get_packages /path/to/repository"
exit 1
fi

repository_dir="$1"

if [ ! -e "$repository_dir" ]
then
echo "Ошибка! Дирректории \"$repository_dir\" не существует."
exit 1
fi

if [ ! -e "$repository_dir/packages_list" ]
then
echo "Ошибка! Файла \"$repository_dir/packages_list\" не существует. Перед запуском get_packages вам необходимо запустить скрипт get_lists."
exit 1
fi
# Проверка "исходных данных" <--

# Скачиваем все пакеты -->
echo "Скачиваем пакеты..."

urls=`cat "$repository_dir/packages_list"`
for url in $urls
do
# Проверяем, есть ли у нас уже этот пакет в репозитории -->
is_downloaded=0

if [ -e "$repository_dir/Packages.list" ]
then
if grep "$url" "$repository_dir/Packages.list" > /dev/null
then
is_downloaded=1
fi
fi
# Проверяем, есть ли у нас уже этот пакет в репозитории <--

# Скачиваем пакет
if [ $is_downloaded -eq 0 ]
then
wget_result=1

while [ $wget_result -ne 0 ]
do
echo -n $url

wget_output=`wget --timestamping --no-verbose --directory-prefix="$repository_dir" --no-host-directories --force-directories $url 2>&1`
wget_result=$?
if [ $wget_result -ne 0 ]
then
echo ' [ERROR]'
echo $wget_output
echo "Через 5 секунд произойдет повторная попытка..."
sleep 5
else
echo ' [OK]'
fi
done

echo $url >> "$repository_dir/Packages.list" || exit 1
# Пропускаем пакет
else
echo "$url [ALREADY IN REPOSITORY]"
fi
done
# Скачиваем все пакеты <--

echo -e "\nВсе необходимые пакеты скачаны."

# Чистим за собой
rm "$repository_dir/packages_list" || exit 1

create_repository:
#!/bin/bash
#***************************************************************************
#*   Copyright (C) 2007, Konishchev Dmitry                                 *
#*   http://konishchevdmitry.blogspot.com/                                 *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU General Public License as published by  *
#*   the Free Software Foundation; either version 3 of the License, or     *
#*   (at your option) any later version.                                   *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU General Public License for more details.                          *
#**************************************************************************/

tmp_file="/tmp/create_repository_$$"

# Проверка "исходных данных" -->
if [ "$1" == "" ]
then
echo "Исспользование: create_repository /path/to/repository"
exit 1
fi

repository_dir="$1"

if [ ! -e "$repository_dir" ]
then
echo "Ошибка! Дирректории \"$repository_dir\" не существует."
exit 1
fi
# Проверка "исходных данных" <--

# Создаем репозиторий -->
cd "$repository_dir" || exit 1
dpkg-scanpackages . /dev/null 2> $tmp_file | gzip -9c > Packages.gz
if [ $? -eq 0 ]
then
echo "Репозиторий создан."
else
echo "Произошла ошибка при создании репозитория."
cat $tmp_file
exit 1
fi

if [ -e $tmp_file ]
then
rm $tmp_file || exit 1
fi
# Создаем репозиторий <--

8 комментариев:

Анонимный комментирует...

А как выбрать нужные мне секции?
Допустим, мне нужно только main и univere, а restricted и multiverse мне не нужно?

А ещё - как выбрать нужную мне версию, например, gutsy или hardy?

Кроме того, какие адреса посоветуете для обновления? (updates, security и т.д.)

Из предыдущего выпадает вопрос - а как выбрать, например, gutsy,gutsy-updates,gutsy-security и т.д.?

Dmitry Konishchev комментирует...

Скрипт читает /etc/apt/sources.list и выкачивает описанные там репозитории (только по http). Соответственно, вам необходимо сформировать такой /etc/apt/sources.list, который соответствовал бы вашим требованиям.

Адреса посоветую те, которые по умолчанию установлены в дистрибутиве. :)

Анонимный комментирует...

спасибо! грамотный скрипт. очень выручил.

Анонимный комментирует...

я так и не понял
запускаю скрипты а они ничего не делают
не могли бы вы обьяснить куда их положить в какую папку и какую папку создать
я полный нуб ))

Dmitry Konishchev комментирует...

Анонимный, только что выкачал часть репозитория - все работает.

/etc/apt/sources.list у меня содержал следующую строку:
deb http://archive.ubuntu.com/ubuntu/ gutsy main universe restricted multiverse

Вот список команд, которые я выполнял:
/my_files/temp$ mkdir test
/my_files/temp$ cd test/
/my_files/temp/test$ mkdir repository
/my_files/temp/test$ /my_files/just4fun/scripts/my_create_local_repository/get_lists repository/
Получаем списки пакетов...
http://archive.ubuntu.com/ubuntu/dists/gutsy/main/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/gutsy/universe/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/gutsy/restricted/binary-i386/Packages.gz [OK]
http://archive.ubuntu.com/ubuntu/dists/gutsy/multiverse/binary-i386/Packages.gz [OK]

Списки пакетов получены успешно
/my_files/temp/test$ /my_files/just4fun/scripts/my_create_local_repository/get_packages repository/
Скачиваем пакеты...
http://archive.ubuntu.com/ubuntu/pool/main/a/abiword/abiword_2.4.6-2ubuntu2_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/abiword/abiword-common_2.4.6-2ubuntu2_all.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/abiword/abiword-gnome_2.4.6-2ubuntu2_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/abiword/abiword-help_2.4.6-2ubuntu2_all.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/abiword/abiword-plugins_2.4.6-2ubuntu2_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/abiword/abiword-plugins-gnome_2.4.6-2ubuntu2_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/acct/acct_6.4~pre1-4ubuntu1_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/acpi/acpi_0.09-3ubuntu1_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/acpi-support/acpi-support_0.103_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/acpid/acpid_1.0.4-5ubuntu8_i386.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/adduser/adduser_3.103ubuntu1_all.deb [OK]
http://archive.ubuntu.com/ubuntu/pool/main/a/adept/adept_2.1.3ubuntu17_all.deb [OK]
...
/my_files/temp/test$ /my_files/just4fun/scripts/my_create_local_repository/create_repository repository/
Репозиторий создан.

GiNeR комментирует...

"...если пользоваться моим скриптом, то для обновления репозитория вам не нужно хранить на своей машине копию ранее созданного репозитория..." - Поясни, плиз, я не понял.

Dmitry Konishchev комментирует...

GiNeR, ну вот, допустим, выкачал ты весь репозиторий и отнес другу, у которого нет Интернета, а у себя его удалил. Прошло какое-то время, появились обновления и друг захотел репозиторий со свежими пакетами. Что делать? Заново выкачивать весь репозиторий? Или вынуть жесткий диск, принести его другу, скопировать на него уже устаревший репозиторий, вернуться домой, натравить на него debmirror, чтобы он скачал только новые пакеты, и отнести обратно другу? :)

Оба способа не очень удобны. Для того чтобы обновить репозиторий при помощи моего скрипта, необходимо взять у друга только один файл в несколько мегабайт, содержащий список всех пакетов, которые у него уже есть - мой скрипт выкачает только те пакеты, которых нет в этом списке.

P.S.: Честно говоря, сам я уже не пользуюсь своим скриптом, а использую debmirror + еще несколько моих скриптов, которые как раз реализуют эту возможность. Делаю я это потому, что именно с выкачиванием пакетов debmirror справляется немного лучше моего скрипта - в созданном им репозитории все пакеты остаются подписаны ключом pgp, что не вызывает предупреждений apt-get'а. Плюс, запуская debmirror из еще одного скрипта, я заставил нормально работать apt-file с моим зеркалом репозитория.

Анонимный комментирует...

Удобно, хотя сам пользуюсь debmirror )