Ubuntu: Как поддержать в актуальном состоянии компьютер без интернета?

16 апреля 2011 г.

Начну с того, что есть компьютер с Ubuntu, который не подключен к интернету. Вопрос «почему?» оставим за кадром.
И у меня, как и у любого линуксоида, есть желание иметь на нем свежее ПО.
Встает вопрос: как обновляться или устанавливать новое ПО на этот комп без больших трудозатрат?
Уточню, что есть некий второй компьютер с выходом в интернет. Но он может отличаться архитектурой (i386/amd64), версией убунту, да и не убунту это может быть — так что скопировать /var/cache/apt/ не вариант.

Вариантов у нас несколько:

  1. Сделать полное зеркало репозитория (например, при помощи apt-mirror), принести на каком-то носителе и обновляться с него.
  2. Качать deb-пакеты в интернете со всеми зависимостями вручную.
  3. Попробовать что-нибудь придумать поинтересней, т.е. самим реализовать то, что нам нужно.

Давайте рассмотрим за и против для этих вариантов. И в результате всё равно напишем своё!
Всё описанное в статье проверено на Ubuntu hardy/karmic/lucid/maverick, но по идее должно работать на любом дистрибутиве на основе Debian.

Полное зеркало репозитория

При этом подходе репозитории полностью выкачиваются на флешку (внешний хард) при помощи программы подобной apt-mirror.
За:

  1. Всё очень просто и легко настраивается

Против:

  1. Требуется очень много места, особенно если хочется зеркалировать не только официальный репозиторий, но и какие-нибудь ppa на launchpad’е.
  2. Требуется толстый канал в интернет или долго ждать.
  3. Скачиваются гигабайты софта, который может никогда и не потребоваться.

Качать deb-пакеты вручную

За:

  1. Качается только те пакеты, которые необходимы.
  2. Сокращается объем трафика.
  3. Занимает мало места на носителе — вместо дорогого внешнего харда можно использовать дешевую мелкую флешку.

Против:

  1. Очень трудоёмко и сложно отследить все зависимости.

Придумываем свой вариант

Хотелось бы объединить преимущества первого и второго варианта. Наша поделка должна:

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

Что нам надо:

  1. Возможность закачки заголовков (файлов Release, Packages и необходимой структуры каталогов).
  2. Прием «заявки» от клиента на необходимые пакеты, формирование очереди загрузки.
  3. Возможность загрузки из интернета на втором компе наиболее простым способом.

Но поскольку этим требованиям не удовлетворяет ни один проект, найденный мною в сети. Поэтому было принято решение написать что-то свое.

Что у нас есть:

  • А — комп с доступом в интернет,
  • Б — комп без интернета,
  • флешка — носитель с набором скриптов и данными, перетаскиваемый между А и Б.

Придуман следующий алгоритм:

  1. На базе конфига при помощи скрипта gen_sources.pl создаётся sources.list, который прописывается на компе Б и указывает на структуру папок на флешке.
  2. Скрипт apt-mirror.pl выкачивает структуру папок и заголовки репозитория с сети учитывая нужные: карманы (от pocket в официальной документации, например: hardy lucid), архитектуры, языки, компоненты (например: main universe). Данные сохраняются на флешке.
  3. На компе Б, подключив флешку, делаем sudo apt-get update
  4. На компе Б при помощи скрипта apt-get-offline.pl формируем задание загрузки (файл download.info) командами типа
    ./apt-get-offline.pl dist-upgrade
    ./apt-get-offline.pl install some-package
  5. Идем к компу А, вставляем флешку и запускаем ./fill_pool.pl. Скрипт сливает с интернета файлы, которые заданы в файле download.info.
  6. На компе Б выполняем те же команды, что и на шаге 4, только указывая уже не apt-get-offline.pl, а sudo apt-get
  7. Если всё прошло удачно, то удаляем файл download.info. В противном случае повторяем шаги 4-7.

Для очередного обновления системы повторяем шаги 2-7.

Ограничения
  • Все операции лучше проводить находясь в той директории флешки, где лежат скрипты.
  • Структура папок и все данные создаются в тойже папке.
  • Простой, но кривенький формат файла конфигурации.
  • Файл download.info надо удалять вручную. Это сделано из-за того, что не всегда закачка происходит с первого раза (обрывы связи) — приходится запускать fill_pool.pl повторно.
  • Не получится обновить дистрибутив до следующей стабильной версии, т.к. программе do-release-upgrade требуется подключение к интернету.
Формат конфигурационного файла

Разберем формат на примере одной строки:
ubuntu^http://mirror.yandex.ru/ubuntu^1^lucid lucid-updates^main restricted^i386 amd64^ru de^src

Строка состоит из 7 обязательных полей, разделенных символом “^”, и одного необязательного.
Первое поле (ubuntu) — название репозитория, уникальное имя для папки, в которую будет сохраняться данные этого репозитория.
Второе поле (URL) — адрес репозитория (http(s)/ftp).
Третье поле (1) — число директорий в URL, требуется для передачи ключу –cut-dirs программы wget.
Четвертое поле (lucid lucid-updates) — карманы, один или несколько, разделяются пробелом.
Пятое поле (main restricted) — компоненты, один или несколько, разделяются пробелом.
Шестое поле (i386 amd64) — архитектуры, одна или несколько, разделяются пробелом.
Седьмое поле (ru de) — языки, одна или несколько, разделяются пробелом.
Восьмое поле (src) — необязательное, указывает на то, что надо скачать описание для deb-src.

Почитать:
1. Структура репозитория Debian
2. apt-mirror

PS. есть идеи проекта на эту тематику покрупнее, но то идеи, а это уже работает!

Исходный код скриптов на Perl:

apt-mirror.pl
#!/usr/bin/perl
use Cwd;
use strict;
#************************
# Changelog
# v.1.0 - initial release
# v.1.1 - added remove of wget-logs
# v.1.2 - added source support (deb-src)
#************************

my $main_dir;
$main_dir = getcwd;
#read config
open CONFIG, "<apt-repos.conf";
my ($i, @repos);
$i = 0;
while (){
 chomp($_);
 next if ($_ eq "");
 @{$repos[$i]} = split (/\^/, $_);
 $i++;
}
close CONFIG;

foreach (@repos){
 my @repo;
 @repo = @{$_};
 my $dstdir = "$main_dir/$repo[0]";
 my $repurl = $repo[1];
 my $cutdirs = $repo[2];
 my @dists = split (/ /,$repo[3]);
 my @pockets = split (/ /,$repo[4]);
 my @archs = split (/ /,$repo[5]);
 my @langs = split (/ /,$repo[6]);
 my $has_src = $repo[7];

my ($dist, $pocket, $arch, $lang);
 foreach $dist (@dists){

#load Release
 system "mkdir -p '$dstdir/dists/$dist'";
 chdir "$dstdir" or die "\nCannot chdir to '$dstdir'";
 
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/Release";
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/Release.gpg";

foreach $pocket (@pockets){
 system "mkdir -p '$dstdir/dists/$dist/$pocket'";
 chdir $dstdir or die "\nCannot chdir to '$dstdir'";

# load pocket (meaning component)
 foreach $arch (@archs){
 chdir $dstdir or die "\nCannot chdir to '$dstdir'";
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/Contents-$arch.gz";
 system "wget -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/binary-$arch/Packages.bz2";
 system "wget -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/binary-$arch/Packages.gz";
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/binary-$arch/Release";
 if ($has_src ne ""){
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/source/Release";
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/source/Sources.bz2";
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/source/Sources.gz";
 }
 }
 foreach $lang (@langs){
 chdir $dstdir or die "\nCannot chdir to '$dstdir'";
 system "mkdir -p '$dstdir/dists/$dist/$pocket/i18n/'";
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/i18n/Translation-$lang";
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/i18n/Translation-$lang.bz2";
 system "wget -b -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/dists/$dist/$pocket/i18n/Translation-$lang.gz";
 }
 }
 }
# remove wget-logs
system "rm -rf $dstdir/wget-log*";
}

print "\n****************\nAll done!\nNow run on your target machine 'apt-get update', 'apt-get --print-uris ACTION'! Then download this files and put it in pool.\n";
fill_pool.pl
#!/usr/bin/perl

use Cwd;

my $main_dir;
$main_dir = getcwd;
#read config
open CONFIG, "<apt-repos.conf";
my ($i, @repos);
$i = 0;
while (){
 chomp($_);
 next if ($_ eq "");
 @{$repos[$i]} = split (/\^/, $_);
 #print "name=$repos[$i][0]\nurl=$repos[$i][1]\ncut-dirs=$repos[$i][2]\ndists=$repos[$i][3]\npockets=$repos[$i][4]\narchs=$repos[$i][5]\nlangs=$repos[$i][6]\n";
 $i++;
}
close CONFIG;

open (IN,"<download.info");

while ($line = ){
 if ($line =~ m{/([\w-]*)(/pool/.*\.deb)'}){
 $name = $1;
 $url= $2;
 foreach (@repos){
 my @repo;
 @repo = @{$_};
 if ($name eq $repo[0]){
 my $dstdir = "$main_dir/$repo[0]";
 my $repurl = $repo[1];
 my $cutdirs = $repo[2];
 my @dists = split (/ /,$repo[3]);
 my @pockets = split (/ /,$repo[4]);
 my @archs = split (/ /,$repo[5]);
 my @langs = split (/ /,$repo[6]);

system "mkdir -p '$dstdir/pool'";
 chdir "$dstdir" or die "\nCannot chdir to '$dstdir'";
 system "wget -mcNr -nH --cut-dirs=$cutdirs --relative -l 1 $repurl/$url";
 }
 }
 }
}
 
close IN;
apt-get-offline.pl
#!/usr/bin/perl

$\ = " ";
system "apt-get -y --print-uris @ARGV >> download.info";
gen_sources.pl
#!/usr/bin/perl

use Cwd;

my $main_dir;
$main_dir = getcwd;
#read config
open CONFIG, "<apt-repos.conf";
my ($i, @repos);
$i = 0;
while (){
 chomp($_);
 next if ($_ eq "");
 @{$repos[$i]} = split (/\^/, $_);
 $i++;
}
close CONFIG;

foreach (@repos){
 my @repo;
 @repo = @{$_};
 my $dstdir = "$main_dir/$repo[0]";
 my $repurl = $repo[1];
 my $cutdirs = $repo[2];
 my @dists = split (/ /,$repo[3]);
 my @pockets = split (/ /,$repo[4]);
 my @archs = split (/ /,$repo[5]);
 my @langs = split (/ /,$repo[6]);
 my $has_src = $repo[7];

print "deb file://$dstdir $_ $repo[4]\n" foreach (@dists);
 if ($has_src ne "") { print "deb-src file://$dstdir $_ $repo[4]\n" foreach (@dists); }
}
пример apt-repos.conf

ubuntu^http://mirror.yandex.ru/ubuntu^1^lucid lucid-updates lucid-proposed lucid-backports^main restricted universe multiverse^i386^ru^src
ubuntu-security^http://security.ubuntu.com/ubuntu^1^lucid-security^main restricted^i386^ru^src
kubuntu-ppa^http://ppa.launchpad.net/kubuntu-ppa/ppa/ubuntu^3^lucid^main^i386^ru^src

Теги: рубрика Интернет