Вроде бы на шел решение и даже работает
Сначала подготовим инструменты и материалы. На сервере должен быть поднят SSH сервер и настроен на доступ по ключам с рабочей машины. Так же требуется флешка.
Флешка по умолчанию монтируется в /media/backup и имеет в корне каталог /.update На всех машинах в домашнем каталоге пользователя, от имени которого осуществляется апдейт, тоже есть каталог /.update
Придется дать пользователю, от имени которого запускается скрипт, права на беспарольную установку пакетов, апдейт и копирование из под sudo. Для этого прикрутим аутентификацию по USB.
===
Описание структуры ===
В каталоге /media/backup/.update/ имеетюся каталоги с именами обновляемых хостов. В нашем случае это /media/backup/.update/server-name и /media/backup/.update/local-comp-name но их может быть сколько угодно.
В каждом каталоге с именм хоста есть каталог /lists, где лежит база apt-get с соответствующей машины - это копия каталога /var/lib/apt/lists исключая файл lock и вложеный каталог /partail.
Там же находится каталог /pakages для пакетов апдейта.
Там же находится файл repositories.lst со списком обновляемых репозиториев.
Там же находится файл pakages.lst со списком обновляемых пакетов.
Флаг синхронизации - это пустой файл с именем sincronized. Нужен для определения того, синхронизирован каталог /lists с интернетом или нет. Этот файл создает скрипт на рабочей машине, после обновления каталога /var/lib/apt/lists и удаляет скрипт на машине с интернетом, если при побайтовом сравнении между скачанными с интернета и находящимися в каталоге lists/ появилась разница.
Итого структура:
Рабочая машина
local:~/.update/
local:~/.update/local-comp-name/lists/
local:~/.update/local-comp-name/pakages/
local:~/.update/server-name/lists/
local:~/.update/server-name/pakages/
Флэшка
local:/media/backup/
local:/media/backup/.update/
local:/media/backup/.update/local-comp-name/
local:/media/backup/.update/local-comp-name/lists/
local:/media/backup/.update/local-comp-name/pakages/
local:/media/backup/.update/local-comp-name/pakages/pakages.lst
local:/media/backup/.update/local-comp-name/repositories.lst
local:/media/backup/.update/local-comp-name/sincronized
local:/media/backup/.update/server-name/
local:/media/backup/.update/server-name/lists/
local:/media/backup/.update/server-name/pakages/
local:/media/backup/.update/server-name/pakages/pakages.lst
local:/media/backup/.update/server-name/repositories.lst
local:/media/backup/.update/server-name/sincronized
Сервер
server:~/.update/
server:~/.update/lists/
server:~/.update/pakages/
Машина с интернетом
inet:~/.update/
inet:~/.update/local-comp-name/lists/
inet:~/.update/local-comp-name/pakages/
inet:~/.update/server-name/lists/
inet:~/.update/server-name/pakages/
===
Подготовка ===
Генерируем ключи и делимя публичным с сервером, если этого не сделано раньше
user@local:~$ ssh-keygen -t rsa
user@local:~$ ssh-copy-id -i /home/user/.ssh/id_rsa user@<ip сервера>
https://help.ubuntu.ru/wiki/ssh - для справки
На рабочей машине устанавливаем pam-usb (естественно ключевой должна быть флешка, которую мы используем для переноса обновлений)
user@local:~$ sudo ap-get install libpam-usb pamusb-tools
user@local:~$ sudo pamusb-conf --add-device имя_устройства
user@local:~$ sudo pamusb-conf --add-user имя_пользователя
user@local:~$ sudo pamusb-check имя_пользователя
+ изменить /etc/pam.d/common-auth
http://pamusb.org/ The PAM_USB Project- для справки
На сервере придется дать пользователю, от имени которого происходит апдэйт права на запись и изменение всех файлов в каталоге /var/lib/apt/lists. Я просто сделал его владельцем папки и всех файлов в ней кроме lock - он нам не нужен. (сам знаю что нехорошо сделал, но другого решения пока не вижу)
===
Рабочий алгоритм ===
Внимание! Все имена машин, пользователей, ip адреса вымышленные, для использования их надо заменить на те что есть у вас
Приходим на работу, включаем комп, втыкаем флешку, запускаем скрипт off-update-local
#!/bin/bash
# это скрипт для запуска на рабочей машине
FileSource="repositories.lst"
FilePakages="pakages.lst"
ServerName="server-name"
LocalName="local-comp-name"
InetName="inet-comp-name"
UpdateLocalDir="/home/user/.update"
UpdateDir="/media/backup/.update"
LOG="/home/user/.update/off-install.log"
TMP="/tmp/tmp.lst"
TMP1="/tmp/tmp1.lst"
flag_new_deb=0
# убедиться, что флешка подключена
df /media/* | grep backup > /dev/null
# если флешка не подключена - то выход
if [ $? -eq 1 ] ; then
echo "Не вставлена флешка -- не могу продолжить"
exit 1
fi
# ************* Обновление рабочей машины ****************
# если в каталоге /pakages есть файлы .deb, то
if [ "$(ls ${UpdateDir}/$LocalName/pakages | grep \.deb)" ] ; then
echo "******starting installing new pakages********"
# стираем старые пакеты
rm -f ${UpdateLocalDir}/$LocalName/pakages/*
# копируем новые пакеты в установочную директорию и устанавливаем
cp ${UpdateDir}/$LocalName/pakages/*deb ${UpdateLocalDir}/$LocalName/pakages
cd ${UpdateLocalDir}/$LocalName/pakages
sudo dpkg -i *.deb || echo "WARNING! - error installing new pakages" >> $LOG
# стираем пакеты с флэшки
rm -f ${UpdateDir}/$LocalName/pakages/*
fi
# проверяем наличие файла sincronized
if [ ! -e ${UpdateDir}/$LocalName/sincronized ] ; then
# копируем списки пакетов в репозиториях в место их постоянного проживания
sudo cp -u ${UpdateDir}/$LocalName/lists/* /var/lib/apt/lists
# обновляем список репозиториев на тот случай, если мы добавили новый
sudo apt-get -y --print-uris update > ${UpdateDir}/$LocalName/${FileSource} || echo "error write repositories.lst"
# создаем файл-флаг синхронизации
touch ${UpdateDir}/$LocalName/sincronized
# создаем список пакетов для обновления
sudo apt-get -y --print-uris upgrade | grep \.deb | cut -f2 -d"'" > $TMP
# и если он не пустой
if [ -s $TMP ] ; then
# помещаем его в соответствующий каталог на флэшке для скачки
mv $TMP ${UpdateDir}/$LocalName/pakages/${FilePakages} || echo "error write pakages.lst"
else
rm -f ${UpdateDir}/$LocalName/pakages/${FilePakages}
fi
fi
# ******************** Обновление сервера **************************
# если в каталоге /pakages есть файлы .deb, то копируем их в установочную директорию и устанавливаем
if [ "$(ls ${UpdateDir}/$ServerName/pakages | grep \.deb)" ] ; then
echo ******starting copy new pakages to server********
# удаляем старые пакеты
ssh user@10.10.0.22 'rm -f ~/.update/pakages/*'
scp ${UpdateDir}/$ServerName/pakages/*deb bull@10.10.0.22:/home/user/.update/pakages
notify-send "Есть обновления для сервера" "Выберите время их установить"
flag_new_deb=1
fi
# проверяем наличие файла sincronized
if [ ! -e ${UpdateDir}/$ServerName/sincronized ] ; then
#
ssh user@10.10.0.22 'rm -f ~/.update/lists/*'
# копируем списки пакетов в репозиториях в место их постоянного проживания
scp ${UpdateDir}/$ServerName/lists/* user@10.10.0.22:~/.update/lists && ssh user@10.10.0.22 'cp ~/.update/lists/* /var/lib/apt/lists' || notify-send "При обновлении были проблемы" "Выберите время посмотреть /var/lib/apt/lists"
touch ${UpdateDir}/$ServerName/sincronized
fi
# если были новые пакеты для установки (т.е. $flag_new_deb -eq 1), то нет смысла давать команду apt-get upgrade
# сначала ручками установим то, что уже есть, а завтра upgrade выполнится само по себе
if [ $flag_new_deb ] ; then
# если кто-нибудь объяснит, почему при доступе по ssh для команды apt-get upgrade не надо выполнять sudo
# я буду очень благодарен (а то как-то неспокойно на душе)
ssh user@10.10.0.22 'apt-get -y --print-uris upgrade | grep \.deb' > $TMP
# если есть пакеты для апгрэйда
if [ -s $TMP ] ; then
cut -f2 -d"'" $TMP > $TMP1
# помещаем список пакетов в соответствующий каталог на флэшке для скачки
mv $TMP1 ${UpdateDir}/$ServerName/pakages/${FilePakages} || echo "error write pakages.lst for server"
else
rm -f ${UpdateDir}/$ServerName/pakages/${FilePakages}
fi
rm $TMP
fi
Приходим домой, включаем комп, втыкаем флэшку, запускаем скрипт off-update-inet, идем ужинать

#!/bin/bash
# это скрипт для запуска на машине с интернетом
FileSource="repositories.lst"
FilePakages="pakages.lst"
ServerName="server-name"
LocalName="local-comp-name"
InetName="inet-comp-name"
UpdateLocalDir="/home/user/.update"
UpdateDir="/media/backup/.update"
LOG="/home/user/far-apt-get.log"
flag_sync=0
# если это не машина с интернетом, то с негодованием выходим
if [ ! ${HOSTNAME} = ${InetName} ] ; then
exit 1
fi
# убедиться, что флешка подключена
df /media/* | grep backup > /dev/null
# если флешка не подключена - то выход
if [ $? -eq 1 ] ; then
echo "Не вставлена флешка -- не могу продолжить"
exit 1
fi
for catalog in ${UpdateDir}/* # цикл по всем компам для обновления
do
comp=${catalog##*/} # оставляем только имя машины
echo ****** обновление для $comp ******
notify-send "Обновление ПО" "для $comp в процессе..."
# очищаем каталог от старых файлов
rm -f ${UpdateLocalDir}/$comp/lists/* 2>&1
# проверяем наличие файла-источника репозиториев
if [ -e ${UpdateDir}/$comp/${FileSource} ] ; then
# Дальше надо скачать каждый файл из по адресу из списка ${FileSource}, если это архив то
# распаковать, переименовать в имена представленные в том же файле ${FileSource}
# все это делается по одному, потому что имена повторяются (типа Packages.bz2 или Sources.bz2)
while read line; do
# выцепляем http адреса (они заключены в одинарные кавычки)
http_adr=${line%%\' *} # это http адрес файла с ' впереди
http_adr=${http_adr#\'} # просто http адрес файла
# echo -e "${http_adr}"
# выцепляем имена файлов (они ограничены пробелами)
s1=${line##*\' } # удаляем все от начала до первого пробела включительно
file_nam=${s1%% *} # удаляем с конца строки до первого пробела с начала строки включительно
# echo -e "${file_nam}\n"
s1=${http_adr##*/} # Удаляем полный путь, оставляем только имя файла с расширением и '
download_file_name=${s1%\'} # откусываем кавычку сзади, получаем имя скачаного файла
wget -c $http_adr # скачиваем файл по http_adr
# проверяем это архив или нет
mimetype=$(file $download_file_name | cut -f2 -d':' | cut -f2 -d' ') # определяем миме тип файла
if [ $mimetype = "bzip2" ] # если это архив bz2
then
bzip2 -d $download_file_name # то распаковываем
download_file_name=${download_file_name%.bz2} # теперь имя файла без расширения
fi
# переносим файл в каталог /lists и переименовываем в file_nam
mv $download_file_name ${UpdateLocalDir}/$comp/lists/$file_nam
done < ${UpdateDir}/$comp/$FileSource
# сравниваем все скачаные файлы с аналогичными файлами на флешке
for file in ${UpdateLocalDir}/$comp/lists/*
do
old_file=${file##*/} # оставляем только имя файла
if [ -e ${UpdateDir}/$comp/lists/${old_file} ] ; then
if cmp $file ${UpdateDir}/$comp/lists/${old_file} ; then
echo ${file} не изменился
else
# echo не совпадает ${file}
# если не совпадают (то есть было обновление с последней проверки), то устанавливаем флаг обновления
flag_sync=1
# копируем новый файл
cp ${file} ${UpdateDir}/$comp/lists
fi
else
# echo отсутствует ${file}
flag_sync=1
cp ${file} ${UpdateDir}/$comp/lists
fi
done # for file in
fi # if [ -e ${UpdateDir}/$comp/${FileSource} ]
# если были изменения, то грохаем файл-флаг синхронизации
if [ $flag_sync -eq 1 ] ; then
echo в репозиториях машины $comp были изменения
# грохнуть файл-флаг синхронизации, если такой есть
if [ -e ${UpdateDir}/$comp/sincronized ] ; then
rm ${UpdateDir}/$comp/sincronized
fi
fi
# очищаем каталог от старых файлов
rm -f ${UpdateLocalDir}/$comp/pakages/* 2>&1
# если в каталоге есть файлы .lst
if [ "$(ls -a ${UpdateDir}/$comp/pakages | grep \.lst)" ] ; then
# копируем все списки пакетов для скачивания
cp ${UpdateDir}/$comp/pakages/*.lst ${UpdateLocalDir}/$comp/pakages
# переходим в каталог пакетов
cd ${UpdateLocalDir}/$comp/pakages
# скачиваем пакеты для каждого файла .lst в каталоге
# pakages.lst содержит пакеты для обновления
# все остальные файлы - это списки пакетов для установки нового ПО
for file in *\.lst
do
# echo скачиваю пакеты для $file
wget -i $file
cp -u *.deb ${UpdateDir}/$comp/pakages
done # for file in *\.lst
else
echo нет свежих пакетов для закачки
fi
done # for catalog in ${UpdateDir}/*
notify-send "Обновление ПО" "полностью завершилось"
echo "Обновление заняло $SECONDS секунд"
Последние два действия можно запихать в автозапуск при вставке флэшки как описано в PAM_USB Project, но я этого делать не буду пока не прикручу к этим скриптам полную обработку ошибок с оповещением и запись всех действий в лог.
Обновления на рабочую машину ставятся автоматом, а когда появляются обновления для сервера - нас извещают, можно зайти по ssh и выполнить команду вида:
user@server:~$ sudo dpkg -i ~/.update/pakages/*deb
или прямо с рабочей машины
user@local:~$ ssh user@server 'sudo dpkg -i ~/.update/pakages/*deb'
===
Недостатки метода: ===
1) Все заточено под конкретную ситуацию и в других условиях работать не будет.
2) На машине с инетом при каждом запуске скрипта выкачивается значительный объем информации (в данный момент у меня около 132 MB)
3) При установке большого количества пакетов иногда роисходит нарушение зависимостей из-за порядка следования имен пакетов - ничего страшного, запускаем установку по новой и все нормально, но раздражает.
4) В нашей работе появился хардварный ключ, который позволяет делать команды от sudo без ввода пароля - это расслабляет. К тому же можно забыть вытащить флешку, когда отходишь по делам (а если рядом есть любознательный организм, то может случится полный nord fox).
Последняя проблема решается блокировкой компа по блютузу с привязкой к телефону (пакет libpam-blue) но это отдельная история.
===
Достоинства ===
При таком подходе можно обновлять сервер и рабочую машину без графического интерфейса с машины другой архитектуры и даже другого дистрибутива
Можно неделями обновлять глухой сервер и рабочую машину без интернета вводя руками только одну команду.
Это нам и требовалось!PS: это совсем не окончательный вариант, а рабочая "бета". Я думаю, что отполирую ее (проверка ошибок, уведомления, ведение логов и т.п.) месяца за два. Буду благодарен за конструктивную критику и предложения по улучшению