Mám blbuvzdorný pythonovský updatovací skriptík pro deb-based systémy. Jedná se v podstatě o jednoduchý wrapper nad apt-getem s několika málo možnostmi nastavení. Primárně je určen pro bezobslužné aktualizace systémů a pro vypisování zastaralých balíčků v reportech. Nedávno mi bylo nahlášeno, že při jednom updatu balíčku samby se skript nějak rozbil a nedoběhl do konce.
Opakování, matka moudrosti
Problémem bylo, že update samby nahrazoval již existující ručně modifikovaný konfigurační soubor. Apt-get byl sice spouštěn ve „dvojitě tichém“ režimu, implikujícím kladnou odpověď na jakoukoliv otázku, jenže tuhle otázku nepokládal apt-get, ale jím spouštěný dpkg, takže odpověď nebyla pokryta a dotaz na přepsání konfigurace zůstal viset na pozadí. Při zjišťování, jak apt-get přemluvit, aby se při updatu vůbec na nic neptal, jsem ale zjistil, že k plně bezobslužnému režimu je třeba říci „ano“ ve čtyřech různých parametrech! Opravdu user-friendly.
- parametr
-y
- ten sice automaticky odpovídá „ano“, ale jen na otázky apt-getu a jen na ty nekritické. Taková otázka je zpravidla jen jedna a ptá se, jestli skutečně chcete balíčky upgradovat. Jak je zmíněno výše, použití supertichého režimu skrze-qq
parametr-y
implikuje. - Parametr
--force-yes
- vynucení kladné odpovědi na všechny otázky apt-getu, tedy například i otázku na update GPG klíče, která je při použití-y
stejně zobrazena. - Parametr
-o Dpkg::Options::="--force-confold"
- ten nastaví výchozí odpověď na otázky ohledně zachování konfigurace, které klade dpkg v případě, že zjistí, že updatovaný soubor byl změněn třetí stranou. Pokud si budete přát starý soubor nezachovávat a vždy instalovat nový, zaměňte slůvkoconfold
zaconfnew
. - Proměnná prostředí
DEBIAN_FRONTEND=noninteractive
- zakazuje debconfu zobrazovat whiptailová okna a klást doplňující dotazy. Takto otravné jsou například balíčky mysql-serveru a postfixu.
Absolutně bezobslužný upgrade oneliner, který si sám odpoví na všechny svoje dotazy, vypadá následovně
apt-get -qq --force-yes update >/dev/null 2>&1 && DEBIAN_FRONTEND=noninteractive && apt-get -qq --force-yes -o Dpkg::Options::="--force-confold" upgrade >/dev/null 2>&1
Nevím jak vám, ale mě přijde trošičku překombinovaný. Samozřejmě je možno výchozí chování změnit přímo v konfiguračních souborech apt-getu a dpkg, ale taková změna se nedá doporučit, pokud má k systému přístup více uživatelů, kteří očekávají výchozí chování komponent.
Upgrade skript
Můj upgradovací skript všechny čtyři výše uvedené parametry obsahuje a díky tomu umožňuje bezobslužný upgrade systému jediným příkazem.
#!/usr/bin/python
from argparse import ArgumentParser
from datetime import datetime, timedelta
from os import devnull, path, environ
from re import compile
from subprocess import Popen, PIPE
from time import sleep
def main(args):
# Set some variables
upgrade_method = 'dist-upgrade' if args.dist else 'upgrade'
fnull = open(devnull, 'w')
# Update package info
last_update_time = path.getmtime('/var/cache/apt')
last_update_delta = datetime.now() - datetime.fromtimestamp(last_update_time)
if last_update_delta > timedelta(minutes=10):
if not args.quiet:
print '\nUpdating package info...'
p = Popen(['apt-get', '-qq', '--force-yes', 'update'], stdout=fnull, stderr=fnull)
p.wait()
# Get names and versions of upgradable packages
package_list = []
regex = compile('Inst (.*) \[(.*)\] \((.*?) .*')
p = Popen(['apt-get', '-s', upgrade_method], stdout=PIPE, stderr=fnull)
for line in p.stdout:
match = regex.match(line)
if match:
package_list.append(match.groups())
# Print packages table
if len(package_list) == 0:
if not args.quiet:
print '\nNo upgradable packages found.\n'
else:
if args.list_only or not args.quiet:
if not args.quiet:
print
max_name_len = len(max([a[0] for a in package_list], key=len))
max_from_len = len(max([a[1] for a in package_list], key=len))
for package in package_list:
print package[0].ljust(max_name_len), package[1].ljust(max_from_len), '->', package[2]
if not args.quiet:
print
# Upgrade packages
if not args.list_only:
if not args.quiet:
print 'Upgrading packages...\n'
environ['DEBIAN_FRONTEND'] = 'noninteractive'
p = Popen(['apt-get', '-qq', '--force-yes', '-o Dpkg::Options::="--force-confold"', upgrade_method], stdout=fnull, stderr=fnull)
p.wait()
if not args.quiet:
print 'Done.\n'
# Schedule reboot if requested
if args.reboot:
Popen(['shutdown', '-r', args.reboot], stdout=fnull, stderr=fnull)
sleep(0.1)
fnull.close()
if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('-d', '--dist', action='store_true', help='Performs dist-upgrade instead of ordinary upgrade')
parser.add_argument('-l', '--list-only', action='store_true', help='Just lists upgradable packages. Does not upgrade anything')
parser.add_argument('-q', '--quiet', action='store_true', help='Hides all output. In conjunction with -l, list will be printed headless')
parser.add_argument('-r', '--reboot', action='store', help='If some upgrades were installed, reboots machine at given time. Syntax is the same as for shutdown command.')
args = parser.parse_args()
main(args)
- Spuštění bez parametru nakoukne do repozitáře, vypíše seznam balíčků, které bude aktualizovat a pustí bezobslužný
apt-get upgrade
. - Parametr
-h
nebo--help
vypíše nápovědu. - Parametr
-d
nebo--dist
použijedist-upgrade
místo výchozíhoupgrade
. - Parametr
-l
nebo--list-only
pouze vypíše seznam zastaralých balíčků a jejich verzí. - Parametr
-q
nebo--quiet
zapne tichý režim, takže upgrade proběhne bez jakýchkoliv hlášek. V kombinaci s--list-only
je vypsán pouze seznam balíčků bez jakýchkoliv dalších informací okolo. - Parametr
-r
nastavuje čas restartu stroje. Restart je proveden, pouze pokud není použit parametr--list-only
a pokud byl upgradován alespoň jeden balíček.
Parametry se samozřejmě dají kombinovat, takže například ./upgrade.py -dl
vypíše seznam updatovatelných balíčků včetně kernelu a dalších věcí, které by se s obyčejným apt-get upgrade
neupgradovaly.
root@lts:~# ./upgrade.py -l
libssl1.0.0 1.0.1-4ubuntu5.3 -> 1.0.1-4ubuntu5.5
openssl 1.0.1-4ubuntu5.3 -> 1.0.1-4ubuntu5.5
linux-firmware 1.79 -> 1.79.1
root@lts:~# ./upgrade.py -dl
libssl1.0.0 1.0.1-4ubuntu5.3 -> 1.0.1-4ubuntu5.5
openssl 1.0.1-4ubuntu5.3 -> 1.0.1-4ubuntu5.5
linux-firmware 1.79 -> 1.79.1
linux-headers-server 3.2.0.29.31 -> 3.2.0.30.32
linux-server 3.2.0.29.31 -> 3.2.0.30.32
linux-image-server 3.2.0.29.31 -> 3.2.0.30.32
A ./upgrade.py -dqr 23:30
spustí tichý apt-get dist-upgrade
a pokud bude aktualizován alespoň jeden balíček, nastaví restart počítače na 23:30.
root@lts:~# ./upgrade.py -dqr 23:30
Broadcast message from root@lts.dasm.cz
(/dev/pts/0) at 15:50 ...
The system is going down for reboot in 460 minutes!