Ubuntu: Как поддержать в актуальном состоянии компьютер без интернета?
Начну с того, что есть компьютер с Ubuntu, который не подключен к интернету. Вопрос «почему?» оставим за кадром.
И у меня, как и у любого линуксоида, есть желание иметь на нем свежее ПО.
Встает вопрос: как обновляться или устанавливать новое ПО на этот комп без больших трудозатрат?
Уточню, что есть некий второй компьютер с выходом в интернет. Но он может отличаться архитектурой (i386/amd64), версией убунту, да и не убунту это может быть — так что скопировать /var/cache/apt/ не вариант.
Вариантов у нас несколько:
- Сделать полное зеркало репозитория (например, при помощи apt-mirror), принести на каком-то носителе и обновляться с него.
- Качать deb-пакеты в интернете со всеми зависимостями вручную.
- Попробовать что-нибудь придумать поинтересней, т.е. самим реализовать то, что нам нужно.
Давайте рассмотрим за и против для этих вариантов. И в результате всё равно напишем своё!
Всё описанное в статье проверено на Ubuntu hardy/karmic/lucid/maverick, но по идее должно работать на любом дистрибутиве на основе Debian.
Полное зеркало репозитория
При этом подходе репозитории полностью выкачиваются на флешку (внешний хард) при помощи программы подобной apt-mirror.
За:
- Всё очень просто и легко настраивается
Против:
- Требуется очень много места, особенно если хочется зеркалировать не только официальный репозиторий, но и какие-нибудь ppa на launchpad’е.
- Требуется толстый канал в интернет или долго ждать.
- Скачиваются гигабайты софта, который может никогда и не потребоваться.
Качать deb-пакеты вручную
За:
- Качается только те пакеты, которые необходимы.
- Сокращается объем трафика.
- Занимает мало места на носителе — вместо дорогого внешнего харда можно использовать дешевую мелкую флешку.
Против:
- Очень трудоёмко и сложно отследить все зависимости.
Придумываем свой вариант
Хотелось бы объединить преимущества первого и второго варианта. Наша поделка должна:
- легко настраиваться,
- не требовать много места,
- иметь простой алгоритм работы,
- качать только то, что нужно.
Что нам надо:
- Возможность закачки заголовков (файлов Release, Packages и необходимой структуры каталогов).
- Прием «заявки» от клиента на необходимые пакеты, формирование очереди загрузки.
- Возможность загрузки из интернета на втором компе наиболее простым способом.
Но поскольку этим требованиям не удовлетворяет ни один проект, найденный мною в сети. Поэтому было принято решение написать что-то свое.
Что у нас есть:
- А — комп с доступом в интернет,
- Б — комп без интернета,
- флешка — носитель с набором скриптов и данными, перетаскиваемый между А и Б.
Придуман следующий алгоритм:
- На базе конфига при помощи скрипта gen_sources.pl создаётся sources.list, который прописывается на компе Б и указывает на структуру папок на флешке.
- Скрипт apt-mirror.pl выкачивает структуру папок и заголовки репозитория с сети учитывая нужные: карманы (от pocket в официальной документации, например: hardy lucid), архитектуры, языки, компоненты (например: main universe). Данные сохраняются на флешке.
- На компе Б, подключив флешку, делаем
sudo apt-get update - На компе Б при помощи скрипта apt-get-offline.pl формируем задание загрузки (файл download.info) командами типа
./apt-get-offline.pl dist-upgrade
./apt-get-offline.pl install some-package - Идем к компу А, вставляем флешку и запускаем
./fill_pool.pl. Скрипт сливает с интернета файлы, которые заданы в файле download.info. - На компе Б выполняем те же команды, что и на шаге 4, только указывая уже не
apt-get-offline.pl, аsudo apt-get - Если всё прошло удачно, то удаляем файл 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